blob: 657f64805c1ddf58ce948f416dd3cb90731d3f09 [file] [log] [blame]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001/* 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
23static inline int is_ws(int c)
24{
25 return !((c - 1) >> 5);
26}
27
28static 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
76int 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
93SkTextBox::SkTextBox()
94{
95 fBox.setEmpty();
96 fSpacingMul = SK_Scalar1;
97 fSpacingAdd = 0;
98 fMode = kLineBreak_Mode;
99 fSpacingAlign = kStart_SpacingAlign;
100}
101
102void SkTextBox::setMode(Mode mode)
103{
104 SkASSERT((unsigned)mode < kModeCount);
105 fMode = SkToU8(mode);
106}
107
108void SkTextBox::setSpacingAlign(SpacingAlign align)
109{
110 SkASSERT((unsigned)align < kSpacingAlignCount);
111 fSpacingAlign = SkToU8(align);
112}
113
114void SkTextBox::getBox(SkRect* box) const
115{
116 if (box)
117 *box = fBox;
118}
119
120void SkTextBox::setBox(const SkRect& box)
121{
122 fBox = box;
123}
124
125void SkTextBox::setBox(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom)
126{
127 fBox.set(left, top, right, bottom);
128}
129
130void SkTextBox::getSpacing(SkScalar* mul, SkScalar* add) const
131{
132 if (mul)
133 *mul = fSpacingMul;
134 if (add)
135 *add = fSpacingAdd;
136}
137
138void SkTextBox::setSpacing(SkScalar mul, SkScalar add)
139{
140 fSpacingMul = mul;
141 fSpacingAdd = add;
142}
143
144/////////////////////////////////////////////////////////////////////////////////////////////
145
146void 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