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 "../src/core/SkGlyphCache.h" 20 #include "SkUtils.h" 21 #include "SkAutoKern.h" 22 23 static inline int is_ws(int c) 24 { 25 return !((c - 1) >> 5); 26 } 27 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 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 93 SkTextBox::SkTextBox() 94 { 95 fBox.setEmpty(); 96 fSpacingMul = SK_Scalar1; 97 fSpacingAdd = 0; 98 fMode = kLineBreak_Mode; 99 fSpacingAlign = kStart_SpacingAlign; 100 } 101 102 void SkTextBox::setMode(Mode mode) 103 { 104 SkASSERT((unsigned)mode < kModeCount); 105 fMode = SkToU8(mode); 106 } 107 108 void SkTextBox::setSpacingAlign(SpacingAlign align) 109 { 110 SkASSERT((unsigned)align < kSpacingAlignCount); 111 fSpacingAlign = SkToU8(align); 112 } 113 114 void SkTextBox::getBox(SkRect* box) const 115 { 116 if (box) 117 *box = fBox; 118 } 119 120 void SkTextBox::setBox(const SkRect& box) 121 { 122 fBox = box; 123 } 124 125 void SkTextBox::setBox(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom) 126 { 127 fBox.set(left, top, right, bottom); 128 } 129 130 void SkTextBox::getSpacing(SkScalar* mul, SkScalar* add) const 131 { 132 if (mul) 133 *mul = fSpacingMul; 134 if (add) 135 *add = fSpacingAdd; 136 } 137 138 void SkTextBox::setSpacing(SkScalar mul, SkScalar add) 139 { 140 fSpacingMul = mul; 141 fSpacingAdd = add; 142 } 143 144 ///////////////////////////////////////////////////////////////////////////////////////////// 145 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