blob: 6a88c6ce397882702478457a06f4f826addeba3d [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
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
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
10#include "SkTextBox.h"
reed@android.comf2b98d62010-12-20 18:26:13 +000011#include "../core/SkGlyphCache.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkUtils.h"
13#include "SkAutoKern.h"
14
15static inline int is_ws(int c)
16{
17 return !((c - 1) >> 5);
18}
19
20static 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
68int 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
85SkTextBox::SkTextBox()
86{
87 fBox.setEmpty();
88 fSpacingMul = SK_Scalar1;
89 fSpacingAdd = 0;
90 fMode = kLineBreak_Mode;
91 fSpacingAlign = kStart_SpacingAlign;
92}
93
94void SkTextBox::setMode(Mode mode)
95{
96 SkASSERT((unsigned)mode < kModeCount);
97 fMode = SkToU8(mode);
98}
99
100void SkTextBox::setSpacingAlign(SpacingAlign align)
101{
102 SkASSERT((unsigned)align < kSpacingAlignCount);
103 fSpacingAlign = SkToU8(align);
104}
105
106void SkTextBox::getBox(SkRect* box) const
107{
108 if (box)
109 *box = fBox;
110}
111
112void SkTextBox::setBox(const SkRect& box)
113{
114 fBox = box;
115}
116
117void SkTextBox::setBox(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom)
118{
119 fBox.set(left, top, right, bottom);
120}
121
122void SkTextBox::getSpacing(SkScalar* mul, SkScalar* add) const
123{
124 if (mul)
125 *mul = fSpacingMul;
126 if (add)
127 *add = fSpacingAdd;
128}
129
130void SkTextBox::setSpacing(SkScalar mul, SkScalar add)
131{
132 fSpacingMul = mul;
133 fSpacingAdd = add;
134}
135
136/////////////////////////////////////////////////////////////////////////////////////////////
137
138void 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
reed@android.com033e03c2010-05-18 21:17:43 +0000209///////////////////////////////////////////////////////////////////////////////
210
211void SkTextBox::setText(const char text[], size_t len, const SkPaint& paint) {
212 fText = text;
213 fLen = len;
214 fPaint = &paint;
215}
216
217void SkTextBox::draw(SkCanvas* canvas) {
218 this->draw(canvas, fText, fLen, *fPaint);
219}
220
221int SkTextBox::countLines() const {
222 return SkTextLineBreaker::CountLines(fText, fLen, *fPaint, fBox.width());
223}
224
225SkScalar SkTextBox::getTextHeight() const {
226 SkScalar spacing = SkScalarMul(fPaint->getTextSize(), fSpacingMul) + fSpacingAdd;
227 return this->countLines() * spacing;
228}
229