|  | /* | 
|  | * Copyright 2006 The Android Open Source Project | 
|  | * | 
|  | * Use of this source code is governed by a BSD-style license that can be | 
|  | * found in the LICENSE file. | 
|  | */ | 
|  |  | 
|  | #include "SkTextBox.h" | 
|  | #include "SkUtils.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, | 
|  | size_t* trailing = NULL) | 
|  | { | 
|  | size_t lengthBreak = paint.breakText(text, stop - text, margin); | 
|  |  | 
|  | //Check for white space or line breakers before the lengthBreak | 
|  | const char* start = text; | 
|  | const char* word_start = text; | 
|  | int prevWS = true; | 
|  | if (trailing) { | 
|  | *trailing = 0; | 
|  | } | 
|  |  | 
|  | while (text < stop) { | 
|  | const char* prevText = text; | 
|  | SkUnichar uni = SkUTF8_NextUnichar(&text); | 
|  | int currWS = is_ws(uni); | 
|  |  | 
|  | if (!currWS && prevWS) { | 
|  | word_start = prevText; | 
|  | } | 
|  | prevWS = currWS; | 
|  |  | 
|  | if (text > start + lengthBreak) { | 
|  | if (currWS) { | 
|  | // eat the rest of the whitespace | 
|  | while (text < stop && is_ws(SkUTF8_ToUnichar(text))) { | 
|  | text += SkUTF8_CountUTF8Bytes(text); | 
|  | } | 
|  | if (trailing) { | 
|  | *trailing = text - prevText; | 
|  | } | 
|  | } else { | 
|  | // backup until a whitespace (or 1 char) | 
|  | if (word_start == start) { | 
|  | if (prevText > start) { | 
|  | text = prevText; | 
|  | } | 
|  | } else { | 
|  | text = word_start; | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | if ('\n' == uni) { | 
|  | size_t ret = text - start; | 
|  | size_t lineBreakSize = 1; | 
|  | if (text < stop) { | 
|  | uni = SkUTF8_NextUnichar(&text); | 
|  | if ('\r' == uni) { | 
|  | ret = text - start; | 
|  | ++lineBreakSize; | 
|  | } | 
|  | } | 
|  | if (trailing) { | 
|  | *trailing = lineBreakSize; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | if ('\r' == uni) { | 
|  | size_t ret = text - start; | 
|  | size_t lineBreakSize = 1; | 
|  | if (text < stop) { | 
|  | uni = SkUTF8_NextUnichar(&text); | 
|  | if ('\n' == uni) { | 
|  | ret = text - start; | 
|  | ++lineBreakSize; | 
|  | } | 
|  | } | 
|  | if (trailing) { | 
|  | *trailing = lineBreakSize; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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 (;;) | 
|  | { | 
|  | size_t trailing; | 
|  | len = linebreak(text, textStop, paint, marginWidth, &trailing); | 
|  | if (y + metrics.fDescent + metrics.fLeading > 0) | 
|  | canvas->drawText(text, len - trailing, x, y, paint); | 
|  | text += len; | 
|  | if (text >= textStop) | 
|  | break; | 
|  | y += scaledSpacing; | 
|  | if (y + metrics.fAscent >= fBox.fBottom) | 
|  | 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; | 
|  | } |