michael@0: /* michael@0: * Copyright 2006 The Android Open Source Project michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: #include "SkTextBox.h" michael@0: #include "SkUtils.h" michael@0: michael@0: static inline int is_ws(int c) michael@0: { michael@0: return !((c - 1) >> 5); michael@0: } michael@0: michael@0: static size_t linebreak(const char text[], const char stop[], michael@0: const SkPaint& paint, SkScalar margin, michael@0: size_t* trailing = NULL) michael@0: { michael@0: size_t lengthBreak = paint.breakText(text, stop - text, margin); michael@0: michael@0: //Check for white space or line breakers before the lengthBreak michael@0: const char* start = text; michael@0: const char* word_start = text; michael@0: int prevWS = true; michael@0: if (trailing) { michael@0: *trailing = 0; michael@0: } michael@0: michael@0: while (text < stop) { michael@0: const char* prevText = text; michael@0: SkUnichar uni = SkUTF8_NextUnichar(&text); michael@0: int currWS = is_ws(uni); michael@0: michael@0: if (!currWS && prevWS) { michael@0: word_start = prevText; michael@0: } michael@0: prevWS = currWS; michael@0: michael@0: if (text > start + lengthBreak) { michael@0: if (currWS) { michael@0: // eat the rest of the whitespace michael@0: while (text < stop && is_ws(SkUTF8_ToUnichar(text))) { michael@0: text += SkUTF8_CountUTF8Bytes(text); michael@0: } michael@0: if (trailing) { michael@0: *trailing = text - prevText; michael@0: } michael@0: } else { michael@0: // backup until a whitespace (or 1 char) michael@0: if (word_start == start) { michael@0: if (prevText > start) { michael@0: text = prevText; michael@0: } michael@0: } else { michael@0: text = word_start; michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: michael@0: if ('\n' == uni) { michael@0: size_t ret = text - start; michael@0: size_t lineBreakSize = 1; michael@0: if (text < stop) { michael@0: uni = SkUTF8_NextUnichar(&text); michael@0: if ('\r' == uni) { michael@0: ret = text - start; michael@0: ++lineBreakSize; michael@0: } michael@0: } michael@0: if (trailing) { michael@0: *trailing = lineBreakSize; michael@0: } michael@0: return ret; michael@0: } michael@0: michael@0: if ('\r' == uni) { michael@0: size_t ret = text - start; michael@0: size_t lineBreakSize = 1; michael@0: if (text < stop) { michael@0: uni = SkUTF8_NextUnichar(&text); michael@0: if ('\n' == uni) { michael@0: ret = text - start; michael@0: ++lineBreakSize; michael@0: } michael@0: } michael@0: if (trailing) { michael@0: *trailing = lineBreakSize; michael@0: } michael@0: return ret; michael@0: } michael@0: } michael@0: michael@0: return text - start; michael@0: } michael@0: michael@0: int SkTextLineBreaker::CountLines(const char text[], size_t len, const SkPaint& paint, SkScalar width) michael@0: { michael@0: const char* stop = text + len; michael@0: int count = 0; michael@0: michael@0: if (width > 0) michael@0: { michael@0: do { michael@0: count += 1; michael@0: text += linebreak(text, stop, paint, width); michael@0: } while (text < stop); michael@0: } michael@0: return count; michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: SkTextBox::SkTextBox() michael@0: { michael@0: fBox.setEmpty(); michael@0: fSpacingMul = SK_Scalar1; michael@0: fSpacingAdd = 0; michael@0: fMode = kLineBreak_Mode; michael@0: fSpacingAlign = kStart_SpacingAlign; michael@0: } michael@0: michael@0: void SkTextBox::setMode(Mode mode) michael@0: { michael@0: SkASSERT((unsigned)mode < kModeCount); michael@0: fMode = SkToU8(mode); michael@0: } michael@0: michael@0: void SkTextBox::setSpacingAlign(SpacingAlign align) michael@0: { michael@0: SkASSERT((unsigned)align < kSpacingAlignCount); michael@0: fSpacingAlign = SkToU8(align); michael@0: } michael@0: michael@0: void SkTextBox::getBox(SkRect* box) const michael@0: { michael@0: if (box) michael@0: *box = fBox; michael@0: } michael@0: michael@0: void SkTextBox::setBox(const SkRect& box) michael@0: { michael@0: fBox = box; michael@0: } michael@0: michael@0: void SkTextBox::setBox(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom) michael@0: { michael@0: fBox.set(left, top, right, bottom); michael@0: } michael@0: michael@0: void SkTextBox::getSpacing(SkScalar* mul, SkScalar* add) const michael@0: { michael@0: if (mul) michael@0: *mul = fSpacingMul; michael@0: if (add) michael@0: *add = fSpacingAdd; michael@0: } michael@0: michael@0: void SkTextBox::setSpacing(SkScalar mul, SkScalar add) michael@0: { michael@0: fSpacingMul = mul; michael@0: fSpacingAdd = add; michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: void SkTextBox::draw(SkCanvas* canvas, const char text[], size_t len, const SkPaint& paint) michael@0: { michael@0: SkASSERT(canvas && &paint && (text || len == 0)); michael@0: michael@0: SkScalar marginWidth = fBox.width(); michael@0: michael@0: if (marginWidth <= 0 || len == 0) michael@0: return; michael@0: michael@0: const char* textStop = text + len; michael@0: michael@0: SkScalar x, y, scaledSpacing, height, fontHeight; michael@0: SkPaint::FontMetrics metrics; michael@0: michael@0: switch (paint.getTextAlign()) { michael@0: case SkPaint::kLeft_Align: michael@0: x = 0; michael@0: break; michael@0: case SkPaint::kCenter_Align: michael@0: x = SkScalarHalf(marginWidth); michael@0: break; michael@0: default: michael@0: x = marginWidth; michael@0: break; michael@0: } michael@0: x += fBox.fLeft; michael@0: michael@0: fontHeight = paint.getFontMetrics(&metrics); michael@0: scaledSpacing = SkScalarMul(fontHeight, fSpacingMul) + fSpacingAdd; michael@0: height = fBox.height(); michael@0: michael@0: // compute Y position for first line michael@0: { michael@0: SkScalar textHeight = fontHeight; michael@0: michael@0: if (fMode == kLineBreak_Mode && fSpacingAlign != kStart_SpacingAlign) michael@0: { michael@0: int count = SkTextLineBreaker::CountLines(text, textStop - text, paint, marginWidth); michael@0: SkASSERT(count > 0); michael@0: textHeight += scaledSpacing * (count - 1); michael@0: } michael@0: michael@0: switch (fSpacingAlign) { michael@0: case kStart_SpacingAlign: michael@0: y = 0; michael@0: break; michael@0: case kCenter_SpacingAlign: michael@0: y = SkScalarHalf(height - textHeight); michael@0: break; michael@0: default: michael@0: SkASSERT(fSpacingAlign == kEnd_SpacingAlign); michael@0: y = height - textHeight; michael@0: break; michael@0: } michael@0: y += fBox.fTop - metrics.fAscent; michael@0: } michael@0: michael@0: for (;;) michael@0: { michael@0: size_t trailing; michael@0: len = linebreak(text, textStop, paint, marginWidth, &trailing); michael@0: if (y + metrics.fDescent + metrics.fLeading > 0) michael@0: canvas->drawText(text, len - trailing, x, y, paint); michael@0: text += len; michael@0: if (text >= textStop) michael@0: break; michael@0: y += scaledSpacing; michael@0: if (y + metrics.fAscent >= fBox.fBottom) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: void SkTextBox::setText(const char text[], size_t len, const SkPaint& paint) { michael@0: fText = text; michael@0: fLen = len; michael@0: fPaint = &paint; michael@0: } michael@0: michael@0: void SkTextBox::draw(SkCanvas* canvas) { michael@0: this->draw(canvas, fText, fLen, *fPaint); michael@0: } michael@0: michael@0: int SkTextBox::countLines() const { michael@0: return SkTextLineBreaker::CountLines(fText, fLen, *fPaint, fBox.width()); michael@0: } michael@0: michael@0: SkScalar SkTextBox::getTextHeight() const { michael@0: SkScalar spacing = SkScalarMul(fPaint->getTextSize(), fSpacingMul) + fSpacingAdd; michael@0: return this->countLines() * spacing; michael@0: }