/* 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;
}