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