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 15 static inline int is_ws(int c) 16 { 17 return !((c - 1) >> 5); 18 } 19 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 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 85 SkTextBox::SkTextBox() 86 { 87 fBox.setEmpty(); 88 fSpacingMul = SK_Scalar1; 89 fSpacingAdd = 0; 90 fMode = kLineBreak_Mode; 91 fSpacingAlign = kStart_SpacingAlign; 92 } 93 94 void SkTextBox::setMode(Mode mode) 95 { 96 SkASSERT((unsigned)mode < kModeCount); 97 fMode = SkToU8(mode); 98 } 99 100 void SkTextBox::setSpacingAlign(SpacingAlign align) 101 { 102 SkASSERT((unsigned)align < kSpacingAlignCount); 103 fSpacingAlign = SkToU8(align); 104 } 105 106 void SkTextBox::getBox(SkRect* box) const 107 { 108 if (box) 109 *box = fBox; 110 } 111 112 void SkTextBox::setBox(const SkRect& box) 113 { 114 fBox = box; 115 } 116 117 void SkTextBox::setBox(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom) 118 { 119 fBox.set(left, top, right, bottom); 120 } 121 122 void SkTextBox::getSpacing(SkScalar* mul, SkScalar* add) const 123 { 124 if (mul) 125 *mul = fSpacingMul; 126 if (add) 127 *add = fSpacingAdd; 128 } 129 130 void SkTextBox::setSpacing(SkScalar mul, SkScalar add) 131 { 132 fSpacingMul = mul; 133 fSpacingAdd = add; 134 } 135 136 ///////////////////////////////////////////////////////////////////////////////////////////// 137 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 211 void SkTextBox::setText(const char text[], size_t len, const SkPaint& paint) { 212 fText = text; 213 fLen = len; 214 fPaint = &paint; 215 } 216 217 void SkTextBox::draw(SkCanvas* canvas) { 218 this->draw(canvas, fText, fLen, *fPaint); 219 } 220 221 int SkTextBox::countLines() const { 222 return SkTextLineBreaker::CountLines(fText, fLen, *fPaint, fBox.width()); 223 } 224 225 SkScalar SkTextBox::getTextHeight() const { 226 SkScalar spacing = SkScalarMul(fPaint->getTextSize(), fSpacingMul) + fSpacingAdd; 227 return this->countLines() * spacing; 228 } 229 230