1
2 /*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9
10 #include "SkTextBox.h"
11 #include "../core/SkGlyphCache.h"
12 #include "SkUtils.h"
13 #include "SkAutoKern.h"
14
is_ws(int c)15 static inline int is_ws(int c)
16 {
17 return !((c - 1) >> 5);
18 }
19
linebreak(const char text[],const char stop[],const SkPaint & paint,SkScalar margin)20 static size_t linebreak(const char text[], const char stop[], const SkPaint& paint, SkScalar margin)
21 {
22 const char* start = text;
23
24 SkAutoGlyphCache ac(paint, NULL);
25 SkGlyphCache* cache = ac.getCache();
26 SkFixed w = 0;
27 SkFixed limit = SkScalarToFixed(margin);
28 SkAutoKern autokern;
29
30 const char* word_start = text;
31 int prevWS = true;
32
33 while (text < stop)
34 {
35 const char* prevText = text;
36 SkUnichar uni = SkUTF8_NextUnichar(&text);
37 int currWS = is_ws(uni);
38 const SkGlyph& glyph = cache->getUnicharMetrics(uni);
39
40 if (!currWS && prevWS)
41 word_start = prevText;
42 prevWS = currWS;
43
44 w += autokern.adjust(glyph) + glyph.fAdvanceX;
45 if (w > limit)
46 {
47 if (currWS) // eat the rest of the whitespace
48 {
49 while (text < stop && is_ws(SkUTF8_ToUnichar(text)))
50 text += SkUTF8_CountUTF8Bytes(text);
51 }
52 else // backup until a whitespace (or 1 char)
53 {
54 if (word_start == start)
55 {
56 if (prevText > start)
57 text = prevText;
58 }
59 else
60 text = word_start;
61 }
62 break;
63 }
64 }
65 return text - start;
66 }
67
CountLines(const char text[],size_t len,const SkPaint & paint,SkScalar width)68 int SkTextLineBreaker::CountLines(const char text[], size_t len, const SkPaint& paint, SkScalar width)
69 {
70 const char* stop = text + len;
71 int count = 0;
72
73 if (width > 0)
74 {
75 do {
76 count += 1;
77 text += linebreak(text, stop, paint, width);
78 } while (text < stop);
79 }
80 return count;
81 }
82
83 //////////////////////////////////////////////////////////////////////////////
84
SkTextBox()85 SkTextBox::SkTextBox()
86 {
87 fBox.setEmpty();
88 fSpacingMul = SK_Scalar1;
89 fSpacingAdd = 0;
90 fMode = kLineBreak_Mode;
91 fSpacingAlign = kStart_SpacingAlign;
92 }
93
setMode(Mode mode)94 void SkTextBox::setMode(Mode mode)
95 {
96 SkASSERT((unsigned)mode < kModeCount);
97 fMode = SkToU8(mode);
98 }
99
setSpacingAlign(SpacingAlign align)100 void SkTextBox::setSpacingAlign(SpacingAlign align)
101 {
102 SkASSERT((unsigned)align < kSpacingAlignCount);
103 fSpacingAlign = SkToU8(align);
104 }
105
getBox(SkRect * box) const106 void SkTextBox::getBox(SkRect* box) const
107 {
108 if (box)
109 *box = fBox;
110 }
111
setBox(const SkRect & box)112 void SkTextBox::setBox(const SkRect& box)
113 {
114 fBox = box;
115 }
116
setBox(SkScalar left,SkScalar top,SkScalar right,SkScalar bottom)117 void SkTextBox::setBox(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom)
118 {
119 fBox.set(left, top, right, bottom);
120 }
121
getSpacing(SkScalar * mul,SkScalar * add) const122 void SkTextBox::getSpacing(SkScalar* mul, SkScalar* add) const
123 {
124 if (mul)
125 *mul = fSpacingMul;
126 if (add)
127 *add = fSpacingAdd;
128 }
129
setSpacing(SkScalar mul,SkScalar add)130 void SkTextBox::setSpacing(SkScalar mul, SkScalar add)
131 {
132 fSpacingMul = mul;
133 fSpacingAdd = add;
134 }
135
136 /////////////////////////////////////////////////////////////////////////////////////////////
137
draw(SkCanvas * canvas,const char text[],size_t len,const SkPaint & paint)138 void SkTextBox::draw(SkCanvas* canvas, const char text[], size_t len, const SkPaint& paint)
139 {
140 SkASSERT(canvas && &paint && (text || len == 0));
141
142 SkScalar marginWidth = fBox.width();
143
144 if (marginWidth <= 0 || len == 0)
145 return;
146
147 const char* textStop = text + len;
148
149 SkScalar x, y, scaledSpacing, height, fontHeight;
150 SkPaint::FontMetrics metrics;
151
152 switch (paint.getTextAlign()) {
153 case SkPaint::kLeft_Align:
154 x = 0;
155 break;
156 case SkPaint::kCenter_Align:
157 x = SkScalarHalf(marginWidth);
158 break;
159 default:
160 x = marginWidth;
161 break;
162 }
163 x += fBox.fLeft;
164
165 fontHeight = paint.getFontMetrics(&metrics);
166 scaledSpacing = SkScalarMul(fontHeight, fSpacingMul) + fSpacingAdd;
167 height = fBox.height();
168
169 // compute Y position for first line
170 {
171 SkScalar textHeight = fontHeight;
172
173 if (fMode == kLineBreak_Mode && fSpacingAlign != kStart_SpacingAlign)
174 {
175 int count = SkTextLineBreaker::CountLines(text, textStop - text, paint, marginWidth);
176 SkASSERT(count > 0);
177 textHeight += scaledSpacing * (count - 1);
178 }
179
180 switch (fSpacingAlign) {
181 case kStart_SpacingAlign:
182 y = 0;
183 break;
184 case kCenter_SpacingAlign:
185 y = SkScalarHalf(height - textHeight);
186 break;
187 default:
188 SkASSERT(fSpacingAlign == kEnd_SpacingAlign);
189 y = height - textHeight;
190 break;
191 }
192 y += fBox.fTop - metrics.fAscent;
193 }
194
195 for (;;)
196 {
197 len = linebreak(text, textStop, paint, marginWidth);
198 if (y + metrics.fDescent + metrics.fLeading > 0)
199 canvas->drawText(text, len, x, y, paint);
200 text += len;
201 if (text >= textStop)
202 break;
203 y += scaledSpacing;
204 if (y + metrics.fAscent >= height)
205 break;
206 }
207 }
208
209 ///////////////////////////////////////////////////////////////////////////////
210
setText(const char text[],size_t len,const SkPaint & paint)211 void SkTextBox::setText(const char text[], size_t len, const SkPaint& paint) {
212 fText = text;
213 fLen = len;
214 fPaint = &paint;
215 }
216
draw(SkCanvas * canvas)217 void SkTextBox::draw(SkCanvas* canvas) {
218 this->draw(canvas, fText, fLen, *fPaint);
219 }
220
countLines() const221 int SkTextBox::countLines() const {
222 return SkTextLineBreaker::CountLines(fText, fLen, *fPaint, fBox.width());
223 }
224
getTextHeight() const225 SkScalar SkTextBox::getTextHeight() const {
226 SkScalar spacing = SkScalarMul(fPaint->getTextSize(), fSpacingMul) + fSpacingAdd;
227 return this->countLines() * spacing;
228 }
229
230