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 
is_ws(int c)11 static inline int is_ws(int c)
12 {
13     return !((c - 1) >> 5);
14 }
15 
linebreak(const char text[],const char stop[],const SkPaint & paint,SkScalar margin,size_t * trailing=nullptr)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 
CountLines(const char text[],size_t len,const SkPaint & paint,SkScalar width)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 
SkTextBox()115 SkTextBox::SkTextBox()
116 {
117     fBox.setEmpty();
118     fSpacingMul = SK_Scalar1;
119     fSpacingAdd = 0;
120     fMode = kLineBreak_Mode;
121     fSpacingAlign = kStart_SpacingAlign;
122 }
123 
setMode(Mode mode)124 void SkTextBox::setMode(Mode mode)
125 {
126     SkASSERT((unsigned)mode < kModeCount);
127     fMode = SkToU8(mode);
128 }
129 
setSpacingAlign(SpacingAlign align)130 void SkTextBox::setSpacingAlign(SpacingAlign align)
131 {
132     SkASSERT((unsigned)align < kSpacingAlignCount);
133     fSpacingAlign = SkToU8(align);
134 }
135 
getBox(SkRect * box) const136 void SkTextBox::getBox(SkRect* box) const
137 {
138     if (box)
139         *box = fBox;
140 }
141 
setBox(const SkRect & box)142 void SkTextBox::setBox(const SkRect& box)
143 {
144     fBox = box;
145 }
146 
setBox(SkScalar left,SkScalar top,SkScalar right,SkScalar bottom)147 void SkTextBox::setBox(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom)
148 {
149     fBox.set(left, top, right, bottom);
150 }
151 
getSpacing(SkScalar * mul,SkScalar * add) const152 void SkTextBox::getSpacing(SkScalar* mul, SkScalar* add) const
153 {
154     if (mul)
155         *mul = fSpacingMul;
156     if (add)
157         *add = fSpacingAdd;
158 }
159 
setSpacing(SkScalar mul,SkScalar add)160 void SkTextBox::setSpacing(SkScalar mul, SkScalar add)
161 {
162     fSpacingMul = mul;
163     fSpacingAdd = add;
164 }
165 
166 /////////////////////////////////////////////////////////////////////////////////////////////
167 
visit(Visitor & visitor,const char text[],size_t len,const SkPaint & paint) const168 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 = SkScalarMul(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:
CanvasVisitor(SkCanvas * canvas)246     CanvasVisitor(SkCanvas* canvas) : fCanvas(canvas) {}
247 
operator ()(const char text[],size_t length,SkScalar x,SkScalar y,const SkPaint & paint)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 
setText(const char text[],size_t len,const SkPaint & paint)254 void SkTextBox::setText(const char text[], size_t len, const SkPaint& paint) {
255     fText = text;
256     fLen = len;
257     fPaint = &paint;
258 }
259 
draw(SkCanvas * canvas,const char text[],size_t len,const SkPaint & paint)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 
draw(SkCanvas * canvas)265 void SkTextBox::draw(SkCanvas* canvas) {
266     this->draw(canvas, fText, fLen, *fPaint);
267 }
268 
countLines() const269 int SkTextBox::countLines() const {
270     return SkTextLineBreaker::CountLines(fText, fLen, *fPaint, fBox.width());
271 }
272 
getTextHeight() const273 SkScalar SkTextBox::getTextHeight() const {
274     SkScalar spacing = SkScalarMul(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 
operator ()(const char text[],size_t length,SkScalar x,SkScalar y,const SkPaint & paint)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 
snapshotTextBlob(SkScalar * computedBottom) const295 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 (SkTextBlob*)visitor.fBuilder.build();
302 }
303 
304