1 /* libs/graphics/views/SkTextBox.cpp
2 **
3 ** Copyright 2006, The Android Open Source Project
4 **
5 ** Licensed under the Apache License, Version 2.0 (the "License");
6 ** you may not use this file except in compliance with the License.
7 ** You may obtain a copy of the License at
8 **
9 ** http://www.apache.org/licenses/LICENSE-2.0
10 **
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
16 */
17
18 #include "SkTextBox.h"
19 #include "SkGlyphCache.h"
20 #include "SkUtils.h"
21 #include "SkAutoKern.h"
22
is_ws(int c)23 static inline int is_ws(int c)
24 {
25 return !((c - 1) >> 5);
26 }
27
linebreak(const char text[],const char stop[],const SkPaint & paint,SkScalar margin)28 static size_t linebreak(const char text[], const char stop[], const SkPaint& paint, SkScalar margin)
29 {
30 const char* start = text;
31
32 SkAutoGlyphCache ac(paint, NULL);
33 SkGlyphCache* cache = ac.getCache();
34 SkFixed w = 0;
35 SkFixed limit = SkScalarToFixed(margin);
36 SkAutoKern autokern;
37
38 const char* word_start = text;
39 int prevWS = true;
40
41 while (text < stop)
42 {
43 const char* prevText = text;
44 SkUnichar uni = SkUTF8_NextUnichar(&text);
45 int currWS = is_ws(uni);
46 const SkGlyph& glyph = cache->getUnicharMetrics(uni);
47
48 if (!currWS && prevWS)
49 word_start = prevText;
50 prevWS = currWS;
51
52 w += autokern.adjust(glyph) + glyph.fAdvanceX;
53 if (w > limit)
54 {
55 if (currWS) // eat the rest of the whitespace
56 {
57 while (text < stop && is_ws(SkUTF8_ToUnichar(text)))
58 text += SkUTF8_CountUTF8Bytes(text);
59 }
60 else // backup until a whitespace (or 1 char)
61 {
62 if (word_start == start)
63 {
64 if (prevText > start)
65 text = prevText;
66 }
67 else
68 text = word_start;
69 }
70 break;
71 }
72 }
73 return text - start;
74 }
75
CountLines(const char text[],size_t len,const SkPaint & paint,SkScalar width)76 int SkTextLineBreaker::CountLines(const char text[], size_t len, const SkPaint& paint, SkScalar width)
77 {
78 const char* stop = text + len;
79 int count = 0;
80
81 if (width > 0)
82 {
83 do {
84 count += 1;
85 text += linebreak(text, stop, paint, width);
86 } while (text < stop);
87 }
88 return count;
89 }
90
91 //////////////////////////////////////////////////////////////////////////////
92
SkTextBox()93 SkTextBox::SkTextBox()
94 {
95 fBox.setEmpty();
96 fSpacingMul = SK_Scalar1;
97 fSpacingAdd = 0;
98 fMode = kLineBreak_Mode;
99 fSpacingAlign = kStart_SpacingAlign;
100 }
101
setMode(Mode mode)102 void SkTextBox::setMode(Mode mode)
103 {
104 SkASSERT((unsigned)mode < kModeCount);
105 fMode = SkToU8(mode);
106 }
107
setSpacingAlign(SpacingAlign align)108 void SkTextBox::setSpacingAlign(SpacingAlign align)
109 {
110 SkASSERT((unsigned)align < kSpacingAlignCount);
111 fSpacingAlign = SkToU8(align);
112 }
113
getBox(SkRect * box) const114 void SkTextBox::getBox(SkRect* box) const
115 {
116 if (box)
117 *box = fBox;
118 }
119
setBox(const SkRect & box)120 void SkTextBox::setBox(const SkRect& box)
121 {
122 fBox = box;
123 }
124
setBox(SkScalar left,SkScalar top,SkScalar right,SkScalar bottom)125 void SkTextBox::setBox(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom)
126 {
127 fBox.set(left, top, right, bottom);
128 }
129
getSpacing(SkScalar * mul,SkScalar * add) const130 void SkTextBox::getSpacing(SkScalar* mul, SkScalar* add) const
131 {
132 if (mul)
133 *mul = fSpacingMul;
134 if (add)
135 *add = fSpacingAdd;
136 }
137
setSpacing(SkScalar mul,SkScalar add)138 void SkTextBox::setSpacing(SkScalar mul, SkScalar add)
139 {
140 fSpacingMul = mul;
141 fSpacingAdd = add;
142 }
143
144 /////////////////////////////////////////////////////////////////////////////////////////////
145
draw(SkCanvas * canvas,const char text[],size_t len,const SkPaint & paint)146 void SkTextBox::draw(SkCanvas* canvas, const char text[], size_t len, const SkPaint& paint)
147 {
148 SkASSERT(canvas && &paint && (text || len == 0));
149
150 SkScalar marginWidth = fBox.width();
151
152 if (marginWidth <= 0 || len == 0)
153 return;
154
155 const char* textStop = text + len;
156
157 SkScalar x, y, scaledSpacing, height, fontHeight;
158 SkPaint::FontMetrics metrics;
159
160 switch (paint.getTextAlign()) {
161 case SkPaint::kLeft_Align:
162 x = 0;
163 break;
164 case SkPaint::kCenter_Align:
165 x = SkScalarHalf(marginWidth);
166 break;
167 default:
168 x = marginWidth;
169 break;
170 }
171 x += fBox.fLeft;
172
173 fontHeight = paint.getFontMetrics(&metrics);
174 scaledSpacing = SkScalarMul(fontHeight, fSpacingMul) + fSpacingAdd;
175 height = fBox.height();
176
177 // compute Y position for first line
178 {
179 SkScalar textHeight = fontHeight;
180
181 if (fMode == kLineBreak_Mode && fSpacingAlign != kStart_SpacingAlign)
182 {
183 int count = SkTextLineBreaker::CountLines(text, textStop - text, paint, marginWidth);
184 SkASSERT(count > 0);
185 textHeight += scaledSpacing * (count - 1);
186 }
187
188 switch (fSpacingAlign) {
189 case kStart_SpacingAlign:
190 y = 0;
191 break;
192 case kCenter_SpacingAlign:
193 y = SkScalarHalf(height - textHeight);
194 break;
195 default:
196 SkASSERT(fSpacingAlign == kEnd_SpacingAlign);
197 y = height - textHeight;
198 break;
199 }
200 y += fBox.fTop - metrics.fAscent;
201 }
202
203 for (;;)
204 {
205 len = linebreak(text, textStop, paint, marginWidth);
206 if (y + metrics.fDescent + metrics.fLeading > 0)
207 canvas->drawText(text, len, x, y, paint);
208 text += len;
209 if (text >= textStop)
210 break;
211 y += scaledSpacing;
212 if (y + metrics.fAscent >= height)
213 break;
214 }
215 }
216
217