1 /* 2 * Copyright 2006 The Android Open Source Project 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include "SkTextBox.h" 9 #include "SkUtils.h" 10 11 static inline int is_ws(int c) 12 { 13 return !((c - 1) >> 5); 14 } 15 16 static size_t linebreak(const char text[], const char stop[], 17 const SkPaint& paint, SkScalar margin, 18 size_t* trailing = nullptr) 19 { 20 size_t lengthBreak = paint.breakText(text, stop - text, margin); 21 22 //Check for white space or line breakers before the lengthBreak 23 const char* start = text; 24 const char* word_start = text; 25 int prevWS = true; 26 if (trailing) { 27 *trailing = 0; 28 } 29 30 while (text < stop) { 31 const char* prevText = text; 32 SkUnichar uni = SkUTF8_NextUnichar(&text); 33 int currWS = is_ws(uni); 34 35 if (!currWS && prevWS) { 36 word_start = prevText; 37 } 38 prevWS = currWS; 39 40 if (text > start + lengthBreak) { 41 if (currWS) { 42 // eat the rest of the whitespace 43 while (text < stop && is_ws(SkUTF8_ToUnichar(text))) { 44 text += SkUTF8_CountUTF8Bytes(text); 45 } 46 if (trailing) { 47 *trailing = text - prevText; 48 } 49 } else { 50 // backup until a whitespace (or 1 char) 51 if (word_start == start) { 52 if (prevText > start) { 53 text = prevText; 54 } 55 } else { 56 text = word_start; 57 } 58 } 59 break; 60 } 61 62 if ('\n' == uni) { 63 size_t ret = text - start; 64 size_t lineBreakSize = 1; 65 if (text < stop) { 66 uni = SkUTF8_NextUnichar(&text); 67 if ('\r' == uni) { 68 ret = text - start; 69 ++lineBreakSize; 70 } 71 } 72 if (trailing) { 73 *trailing = lineBreakSize; 74 } 75 return ret; 76 } 77 78 if ('\r' == uni) { 79 size_t ret = text - start; 80 size_t lineBreakSize = 1; 81 if (text < stop) { 82 uni = SkUTF8_NextUnichar(&text); 83 if ('\n' == uni) { 84 ret = text - start; 85 ++lineBreakSize; 86 } 87 } 88 if (trailing) { 89 *trailing = lineBreakSize; 90 } 91 return ret; 92 } 93 } 94 95 return text - start; 96 } 97 98 int SkTextLineBreaker::CountLines(const char text[], size_t len, const SkPaint& paint, SkScalar width) 99 { 100 const char* stop = text + len; 101 int count = 0; 102 103 if (width > 0) 104 { 105 do { 106 count += 1; 107 text += linebreak(text, stop, paint, width); 108 } while (text < stop); 109 } 110 return count; 111 } 112 113 ////////////////////////////////////////////////////////////////////////////// 114 115 SkTextBox::SkTextBox() 116 { 117 fBox.setEmpty(); 118 fSpacingMul = SK_Scalar1; 119 fSpacingAdd = 0; 120 fMode = kLineBreak_Mode; 121 fSpacingAlign = kStart_SpacingAlign; 122 } 123 124 void SkTextBox::setMode(Mode mode) 125 { 126 SkASSERT((unsigned)mode < kModeCount); 127 fMode = SkToU8(mode); 128 } 129 130 void SkTextBox::setSpacingAlign(SpacingAlign align) 131 { 132 SkASSERT((unsigned)align < kSpacingAlignCount); 133 fSpacingAlign = SkToU8(align); 134 } 135 136 void SkTextBox::getBox(SkRect* box) const 137 { 138 if (box) 139 *box = fBox; 140 } 141 142 void SkTextBox::setBox(const SkRect& box) 143 { 144 fBox = box; 145 } 146 147 void SkTextBox::setBox(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom) 148 { 149 fBox.set(left, top, right, bottom); 150 } 151 152 void SkTextBox::getSpacing(SkScalar* mul, SkScalar* add) const 153 { 154 if (mul) 155 *mul = fSpacingMul; 156 if (add) 157 *add = fSpacingAdd; 158 } 159 160 void SkTextBox::setSpacing(SkScalar mul, SkScalar add) 161 { 162 fSpacingMul = mul; 163 fSpacingAdd = add; 164 } 165 166 ///////////////////////////////////////////////////////////////////////////////////////////// 167 168 SkScalar SkTextBox::visit(Visitor& visitor, const char text[], size_t len, 169 const SkPaint& paint) const { 170 SkScalar marginWidth = fBox.width(); 171 172 if (marginWidth <= 0 || len == 0) { 173 return fBox.top(); 174 } 175 176 const char* textStop = text + len; 177 178 SkScalar x, y, scaledSpacing, height, fontHeight; 179 SkPaint::FontMetrics metrics; 180 181 switch (paint.getTextAlign()) { 182 case SkPaint::kLeft_Align: 183 x = 0; 184 break; 185 case SkPaint::kCenter_Align: 186 x = SkScalarHalf(marginWidth); 187 break; 188 default: 189 x = marginWidth; 190 break; 191 } 192 x += fBox.fLeft; 193 194 fontHeight = paint.getFontMetrics(&metrics); 195 scaledSpacing = fontHeight * fSpacingMul + fSpacingAdd; 196 height = fBox.height(); 197 198 // compute Y position for first line 199 { 200 SkScalar textHeight = fontHeight; 201 202 if (fMode == kLineBreak_Mode && fSpacingAlign != kStart_SpacingAlign) { 203 int count = SkTextLineBreaker::CountLines(text, textStop - text, paint, marginWidth); 204 SkASSERT(count > 0); 205 textHeight += scaledSpacing * (count - 1); 206 } 207 208 switch (fSpacingAlign) { 209 case kStart_SpacingAlign: 210 y = 0; 211 break; 212 case kCenter_SpacingAlign: 213 y = SkScalarHalf(height - textHeight); 214 break; 215 default: 216 SkASSERT(fSpacingAlign == kEnd_SpacingAlign); 217 y = height - textHeight; 218 break; 219 } 220 y += fBox.fTop - metrics.fAscent; 221 } 222 223 for (;;) { 224 size_t trailing; 225 len = linebreak(text, textStop, paint, marginWidth, &trailing); 226 if (y + metrics.fDescent + metrics.fLeading > 0) { 227 visitor(text, len - trailing, x, y, paint); 228 } 229 text += len; 230 if (text >= textStop) { 231 break; 232 } 233 y += scaledSpacing; 234 if (y + metrics.fAscent >= fBox.fBottom) { 235 break; 236 } 237 } 238 return y + metrics.fDescent + metrics.fLeading; 239 } 240 241 /////////////////////////////////////////////////////////////////////////////// 242 243 class CanvasVisitor : public SkTextBox::Visitor { 244 SkCanvas* fCanvas; 245 public: 246 CanvasVisitor(SkCanvas* canvas) : fCanvas(canvas) {} 247 248 void operator()(const char text[], size_t length, SkScalar x, SkScalar y, 249 const SkPaint& paint) override { 250 fCanvas->drawText(text, length, x, y, paint); 251 } 252 }; 253 254 void SkTextBox::setText(const char text[], size_t len, const SkPaint& paint) { 255 fText = text; 256 fLen = len; 257 fPaint = &paint; 258 } 259 260 void SkTextBox::draw(SkCanvas* canvas, const char text[], size_t len, const SkPaint& paint) { 261 CanvasVisitor sink(canvas); 262 this->visit(sink, text, len, paint); 263 } 264 265 void SkTextBox::draw(SkCanvas* canvas) { 266 this->draw(canvas, fText, fLen, *fPaint); 267 } 268 269 int SkTextBox::countLines() const { 270 return SkTextLineBreaker::CountLines(fText, fLen, *fPaint, fBox.width()); 271 } 272 273 SkScalar SkTextBox::getTextHeight() const { 274 SkScalar spacing = fPaint->getTextSize() * fSpacingMul + fSpacingAdd; 275 return this->countLines() * spacing; 276 } 277 278 /////////////////////////////////////////////////////////////////////////////// 279 280 #include "SkTextBlob.h" 281 282 class TextBlobVisitor : public SkTextBox::Visitor { 283 public: 284 SkTextBlobBuilder fBuilder; 285 286 void operator()(const char text[], size_t length, SkScalar x, SkScalar y, 287 const SkPaint& paint) override { 288 SkPaint p(paint); 289 p.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 290 const int count = paint.countText(text, length); 291 paint.textToGlyphs(text, length, fBuilder.allocRun(p, count, x, y).glyphs); 292 } 293 }; 294 295 sk_sp<SkTextBlob> SkTextBox::snapshotTextBlob(SkScalar* computedBottom) const { 296 TextBlobVisitor visitor; 297 SkScalar newB = this->visit(visitor, fText, fLen, *fPaint); 298 if (computedBottom) { 299 *computedBottom = newB; 300 } 301 return visitor.fBuilder.make(); 302 } 303