/* libs/graphics/views/SkTextBox.cpp ** ** Copyright 2006, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ #include "SkTextBox.h" #include "../core/SkGlyphCache.h" #include "SkUtils.h" #include "SkAutoKern.h" static inline int is_ws(int c) { return !((c - 1) >> 5); } static size_t linebreak(const char text[], const char stop[], const SkPaint& paint, SkScalar margin) { const char* start = text; SkAutoGlyphCache ac(paint, NULL); SkGlyphCache* cache = ac.getCache(); SkFixed w = 0; SkFixed limit = SkScalarToFixed(margin); SkAutoKern autokern; const char* word_start = text; int prevWS = true; while (text < stop) { const char* prevText = text; SkUnichar uni = SkUTF8_NextUnichar(&text); int currWS = is_ws(uni); const SkGlyph& glyph = cache->getUnicharMetrics(uni); if (!currWS && prevWS) word_start = prevText; prevWS = currWS; w += autokern.adjust(glyph) + glyph.fAdvanceX; if (w > limit) { if (currWS) // eat the rest of the whitespace { while (text < stop && is_ws(SkUTF8_ToUnichar(text))) text += SkUTF8_CountUTF8Bytes(text); } else // backup until a whitespace (or 1 char) { if (word_start == start) { if (prevText > start) text = prevText; } else text = word_start; } break; } } return text - start; } int SkTextLineBreaker::CountLines(const char text[], size_t len, const SkPaint& paint, SkScalar width) { const char* stop = text + len; int count = 0; if (width > 0) { do { count += 1; text += linebreak(text, stop, paint, width); } while (text < stop); } return count; } ////////////////////////////////////////////////////////////////////////////// SkTextBox::SkTextBox() { fBox.setEmpty(); fSpacingMul = SK_Scalar1; fSpacingAdd = 0; fMode = kLineBreak_Mode; fSpacingAlign = kStart_SpacingAlign; } void SkTextBox::setMode(Mode mode) { SkASSERT((unsigned)mode < kModeCount); fMode = SkToU8(mode); } void SkTextBox::setSpacingAlign(SpacingAlign align) { SkASSERT((unsigned)align < kSpacingAlignCount); fSpacingAlign = SkToU8(align); } void SkTextBox::getBox(SkRect* box) const { if (box) *box = fBox; } void SkTextBox::setBox(const SkRect& box) { fBox = box; } void SkTextBox::setBox(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom) { fBox.set(left, top, right, bottom); } void SkTextBox::getSpacing(SkScalar* mul, SkScalar* add) const { if (mul) *mul = fSpacingMul; if (add) *add = fSpacingAdd; } void SkTextBox::setSpacing(SkScalar mul, SkScalar add) { fSpacingMul = mul; fSpacingAdd = add; } ///////////////////////////////////////////////////////////////////////////////////////////// void SkTextBox::draw(SkCanvas* canvas, const char text[], size_t len, const SkPaint& paint) { SkASSERT(canvas && &paint && (text || len == 0)); SkScalar marginWidth = fBox.width(); if (marginWidth <= 0 || len == 0) return; const char* textStop = text + len; SkScalar x, y, scaledSpacing, height, fontHeight; SkPaint::FontMetrics metrics; switch (paint.getTextAlign()) { case SkPaint::kLeft_Align: x = 0; break; case SkPaint::kCenter_Align: x = SkScalarHalf(marginWidth); break; default: x = marginWidth; break; } x += fBox.fLeft; fontHeight = paint.getFontMetrics(&metrics); scaledSpacing = SkScalarMul(fontHeight, fSpacingMul) + fSpacingAdd; height = fBox.height(); // compute Y position for first line { SkScalar textHeight = fontHeight; if (fMode == kLineBreak_Mode && fSpacingAlign != kStart_SpacingAlign) { int count = SkTextLineBreaker::CountLines(text, textStop - text, paint, marginWidth); SkASSERT(count > 0); textHeight += scaledSpacing * (count - 1); } switch (fSpacingAlign) { case kStart_SpacingAlign: y = 0; break; case kCenter_SpacingAlign: y = SkScalarHalf(height - textHeight); break; default: SkASSERT(fSpacingAlign == kEnd_SpacingAlign); y = height - textHeight; break; } y += fBox.fTop - metrics.fAscent; } for (;;) { len = linebreak(text, textStop, paint, marginWidth); if (y + metrics.fDescent + metrics.fLeading > 0) canvas->drawText(text, len, x, y, paint); text += len; if (text >= textStop) break; y += scaledSpacing; if (y + metrics.fAscent >= height) break; } } /////////////////////////////////////////////////////////////////////////////// void SkTextBox::setText(const char text[], size_t len, const SkPaint& paint) { fText = text; fLen = len; fPaint = &paint; } void SkTextBox::draw(SkCanvas* canvas) { this->draw(canvas, fText, fLen, *fPaint); } int SkTextBox::countLines() const { return SkTextLineBreaker::CountLines(fText, fLen, *fPaint, fBox.width()); } SkScalar SkTextBox::getTextHeight() const { SkScalar spacing = SkScalarMul(fPaint->getTextSize(), fSpacingMul) + fSpacingAdd; return this->countLines() * spacing; }