0bb7a6bf5a2b5794c4bc2e7987f3aee02e8562f9
[platform/upstream/libSkiaSharp.git] / src / utils / SkTextBox.cpp
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 = NULL)
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 = 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:
246     CanvasVisitor(SkCanvas* canvas) : fCanvas(canvas) {}
247     
248     virtual void operator()(const char text[], size_t length, SkScalar x, SkScalar y,
249                             const SkPaint& paint) SK_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 = 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     
286     virtual void operator()(const char text[], size_t length, SkScalar x, SkScalar y,
287                             const SkPaint& paint) SK_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 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