Created
July 22, 2019 10:19
-
-
Save ryancheung/fd61c635a9482b420a21106191071afb to your computer and use it in GitHub Desktop.
SkTextBox cpp implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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 = nullptr) | |
{ | |
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; | |
} | |
///////////////////////////////////////////////////////////////////////////////////////////// | |
SkScalar SkTextBox::visit(Visitor& visitor, const char text[], size_t len, | |
const SkPaint& paint) const { | |
SkScalar marginWidth = fBox.width(); | |
if (marginWidth <= 0 || len == 0) { | |
return fBox.top(); | |
} | |
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) { | |
visitor(text, len - trailing, x, y, paint); | |
} | |
text += len; | |
if (text >= textStop) { | |
break; | |
} | |
y += scaledSpacing; | |
if (y + metrics.fAscent >= fBox.fBottom) { | |
break; | |
} | |
} | |
return y + metrics.fDescent + metrics.fLeading; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
class CanvasVisitor : public SkTextBox::Visitor { | |
SkCanvas* fCanvas; | |
public: | |
CanvasVisitor(SkCanvas* canvas) : fCanvas(canvas) {} | |
void operator()(const char text[], size_t length, SkScalar x, SkScalar y, | |
const SkPaint& paint) override { | |
fCanvas->drawText(text, length, x, y, paint); | |
} | |
}; | |
void SkTextBox::setText(const char text[], size_t len, const SkPaint& paint) { | |
fText = text; | |
fLen = len; | |
fPaint = &paint; | |
} | |
void SkTextBox::draw(SkCanvas* canvas, const char text[], size_t len, const SkPaint& paint) { | |
CanvasVisitor sink(canvas); | |
this->visit(sink, text, len, 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; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
#include "SkTextBlob.h" | |
class TextBlobVisitor : public SkTextBox::Visitor { | |
public: | |
SkTextBlobBuilder fBuilder; | |
void operator()(const char text[], size_t length, SkScalar x, SkScalar y, | |
const SkPaint& paint) override { | |
SkPaint p(paint); | |
p.setTextEncoding(SkPaint::kGlyphID_TextEncoding); | |
const int count = paint.countText(text, length); | |
paint.textToGlyphs(text, length, fBuilder.allocRun(p, count, x, y).glyphs); | |
} | |
}; | |
sk_sp<SkTextBlob> SkTextBox::snapshotTextBlob(SkScalar* computedBottom) const { | |
TextBlobVisitor visitor; | |
SkScalar newB = this->visit(visitor, fText, fLen, *fPaint); | |
if (computedBottom) { | |
*computedBottom = newB; | |
} | |
return visitor.fBuilder.make(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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. | |
*/ | |
#ifndef SkTextBox_DEFINED | |
#define SkTextBox_DEFINED | |
#include "SkCanvas.h" | |
/** \class SkTextBox | |
SkTextBox is a helper class for drawing 1 or more lines of text | |
within a rectangle. The textbox is positioned and clipped by its Frame. | |
The Margin rectangle controls where the text is drawn relative to | |
the Frame. Line-breaks occur inside the Margin rectangle. | |
Spacing is a linear equation used to compute the distance between lines | |
of text. Spacing consists of two scalars: mul and add, and the spacing | |
between lines is computed as: spacing = paint.getTextSize() * mul + add | |
*/ | |
class SkTextBox { | |
public: | |
SkTextBox(); | |
enum Mode { | |
kOneLine_Mode, | |
kLineBreak_Mode, | |
kModeCount | |
}; | |
Mode getMode() const { return (Mode)fMode; } | |
void setMode(Mode); | |
enum SpacingAlign { | |
kStart_SpacingAlign, | |
kCenter_SpacingAlign, | |
kEnd_SpacingAlign, | |
kSpacingAlignCount | |
}; | |
SpacingAlign getSpacingAlign() const { return (SpacingAlign)fSpacingAlign; } | |
void setSpacingAlign(SpacingAlign); | |
void getBox(SkRect*) const; | |
void setBox(const SkRect&); | |
void setBox(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom); | |
void getSpacing(SkScalar* mul, SkScalar* add) const; | |
void setSpacing(SkScalar mul, SkScalar add); | |
void draw(SkCanvas*, const char text[], size_t len, const SkPaint&); | |
void setText(const char text[], size_t len, const SkPaint&); | |
void draw(SkCanvas*); | |
int countLines() const; | |
SkScalar getTextHeight() const; | |
sk_sp<SkTextBlob> snapshotTextBlob(SkScalar* computedBottom) const; | |
class Visitor { | |
public: | |
virtual ~Visitor() {} | |
virtual void operator()(const char*, size_t, SkScalar x, SkScalar y, const SkPaint&) = 0; | |
}; | |
private: | |
SkRect fBox; | |
SkScalar fSpacingMul, fSpacingAdd; | |
uint8_t fMode, fSpacingAlign; | |
const char* fText; | |
size_t fLen; | |
const SkPaint* fPaint; | |
SkScalar visit(Visitor&, const char text[], size_t len, const SkPaint&) const; | |
}; | |
class SkTextLineBreaker { | |
public: | |
static int CountLines(const char text[], size_t len, const SkPaint&, SkScalar width); | |
}; | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment