Like breaking for a single line text
Change-Id: Ib9955d56bcf658aa78eb8dc8d5c48e8ccdf01581 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/528440 Reviewed-by: Ben Wagner <bungeman@google.com> Commit-Queue: Julia Lavrova <jlavrova@google.com>
This commit is contained in:
parent
a23c2118d1
commit
62f07d262c
@ -3617,27 +3617,38 @@ protected:
|
||||
void onDrawContent(SkCanvas* canvas) override {
|
||||
|
||||
canvas->drawColor(SK_ColorWHITE);
|
||||
auto fontCollection = sk_make_sp<FontCollection>();
|
||||
fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
|
||||
auto fontCollection = getFontCollection();
|
||||
|
||||
StrutStyle strut_style;
|
||||
strut_style.setFontFamilies({SkString("Roboto")});
|
||||
strut_style.setStrutEnabled(true);
|
||||
strut_style.setFontSize(8);
|
||||
strut_style.setForceStrutHeight(true);
|
||||
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({SkString("Roboto")});
|
||||
text_style.setFontSize(100);
|
||||
SkPaint black;
|
||||
black.setColor(SK_ColorBLACK);
|
||||
text_style.setForegroundColor(black);
|
||||
SkPaint red;
|
||||
red.setColor(SK_ColorRED);
|
||||
text_style.setBackgroundColor(red);
|
||||
text_style.setFontSize(14);
|
||||
text_style.setColor(SK_ColorBLACK);
|
||||
|
||||
ParagraphStyle paragraph_style;
|
||||
paragraph_style.setTextStyle(text_style);
|
||||
paragraph_style.setStrutStyle(strut_style);
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(".");
|
||||
builder.addText("something");
|
||||
auto paragraph = builder.Build();
|
||||
paragraph->layout(SK_ScalarInfinity);
|
||||
paragraph->paint(canvas, 0, 0);
|
||||
SkDebugf("height=%f\n", paragraph->getHeight());
|
||||
/*
|
||||
auto boxes =
|
||||
paragraph->getRectsForRange(0, 1, RectHeightStyle::kTight, RectWidthStyle::kTight);
|
||||
for (auto& box : boxes) {
|
||||
SkDebugf("[%f,%f:%f,%f]\n",
|
||||
box.rect.fLeft, box.rect.fTop, box.rect.fRight, box.rect.fBottom);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -73,7 +73,10 @@ public:
|
||||
, fWords(paragraph->fWords)
|
||||
, fBidiRegions(paragraph->fBidiRegions)
|
||||
, fUTF8IndexForUTF16Index(paragraph->fUTF8IndexForUTF16Index)
|
||||
, fUTF16IndexForUTF8Index(paragraph->fUTF16IndexForUTF8Index) { }
|
||||
, fUTF16IndexForUTF8Index(paragraph->fUTF16IndexForUTF8Index)
|
||||
, fHasLineBreaks(paragraph->fHasLineBreaks)
|
||||
, fHasWhitespacesInside(paragraph->fHasWhitespacesInside)
|
||||
, fTrailingSpaces(paragraph->fTrailingSpaces) { }
|
||||
|
||||
// Input == key
|
||||
ParagraphCacheKey fKey;
|
||||
@ -88,6 +91,9 @@ public:
|
||||
std::vector<SkUnicode::BidiRegion> fBidiRegions;
|
||||
SkTArray<TextIndex, true> fUTF8IndexForUTF16Index;
|
||||
SkTArray<size_t, true> fUTF16IndexForUTF8Index;
|
||||
bool fHasLineBreaks;
|
||||
bool fHasWhitespacesInside;
|
||||
TextIndex fTrailingSpaces;
|
||||
};
|
||||
|
||||
uint32_t ParagraphCacheKey::mix(uint32_t hash, uint32_t data) {
|
||||
@ -253,6 +259,9 @@ void ParagraphCache::updateTo(ParagraphImpl* paragraph, const Entry* entry) {
|
||||
paragraph->fBidiRegions = entry->fValue->fBidiRegions;
|
||||
paragraph->fUTF8IndexForUTF16Index = entry->fValue->fUTF8IndexForUTF16Index;
|
||||
paragraph->fUTF16IndexForUTF8Index = entry->fValue->fUTF16IndexForUTF8Index;
|
||||
paragraph->fHasLineBreaks = entry->fValue->fHasLineBreaks;
|
||||
paragraph->fHasWhitespacesInside = entry->fValue->fHasWhitespacesInside;
|
||||
paragraph->fTrailingSpaces = entry->fValue->fTrailingSpaces;
|
||||
for (auto& run : paragraph->fRuns) {
|
||||
run.setOwner(paragraph);
|
||||
}
|
||||
|
@ -78,6 +78,9 @@ ParagraphImpl::ParagraphImpl(const SkString& text,
|
||||
, fOldWidth(0)
|
||||
, fOldHeight(0)
|
||||
, fUnicode(std::move(unicode))
|
||||
, fHasLineBreaks(false)
|
||||
, fHasWhitespacesInside(false)
|
||||
, fTrailingSpaces(0)
|
||||
{
|
||||
SkASSERT(fUnicode);
|
||||
}
|
||||
@ -237,21 +240,38 @@ bool ParagraphImpl::computeCodeUnitProperties() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get all spaces
|
||||
// Collect all spaces and some extra information
|
||||
fTrailingSpaces = fText.size();
|
||||
TextIndex firstWhitespace = EMPTY_INDEX;
|
||||
fUnicode->forEachCodepoint(fText.c_str(), fText.size(),
|
||||
[this](SkUnichar unichar, int32_t start, int32_t end, int32_t count) {
|
||||
[this, &firstWhitespace](SkUnichar unichar, int32_t start, int32_t end, int32_t count) {
|
||||
if (fUnicode->isWhitespace(unichar)) {
|
||||
for (auto i = start; i < end; ++i) {
|
||||
fCodeUnitProperties[i] |= CodeUnitFlags::kPartOfWhiteSpaceBreak;
|
||||
}
|
||||
if (fTrailingSpaces == fText.size()) {
|
||||
fTrailingSpaces = start;
|
||||
}
|
||||
if (firstWhitespace == EMPTY_INDEX) {
|
||||
firstWhitespace = start;
|
||||
}
|
||||
} else {
|
||||
fTrailingSpaces = fText.size();
|
||||
}
|
||||
if (fUnicode->isSpace(unichar)) {
|
||||
for (auto i = start; i < end; ++i) {
|
||||
fCodeUnitProperties[i] |= CodeUnitFlags::kPartOfIntraWordBreak;
|
||||
}
|
||||
}
|
||||
if (fUnicode->isHardBreak(unichar)) {
|
||||
fHasLineBreaks = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (firstWhitespace < fTrailingSpaces) {
|
||||
fHasWhitespacesInside = true;
|
||||
}
|
||||
|
||||
// Get line breaks
|
||||
std::vector<SkUnicode::LineBreakBefore> lineBreaks;
|
||||
if (!fUnicode->getLineBreaks(fText.c_str(), fText.size(), &lineBreaks)) {
|
||||
@ -381,7 +401,7 @@ void ParagraphImpl::applySpacingAndBuildClusterTable() {
|
||||
this->buildClusterTable();
|
||||
// This is something Flutter requires
|
||||
for (auto& cluster : fClusters) {
|
||||
cluster.setHalfLetterSpacing(fTextStyles[0].fStyle.getLetterSpacing()/2);
|
||||
cluster.setHalfLetterSpacing(style.getLetterSpacing()/2);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -515,6 +535,69 @@ bool ParagraphImpl::shapeTextIntoEndlessLine() {
|
||||
}
|
||||
|
||||
void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
|
||||
|
||||
if (!fHasLineBreaks &&
|
||||
!fHasWhitespacesInside &&
|
||||
fPlaceholders.size() == 1 &&
|
||||
fRuns.size() == 1 && fRuns[0].fAdvance.fX <= maxWidth) {
|
||||
// This is a short version of a line breaking when we know that:
|
||||
// 1. We have only one line of text
|
||||
// 2. It's shaped into a single run
|
||||
// 3. There are no placeholders
|
||||
// 4. There are no linebreaks (which will format text into multiple lines)
|
||||
// 5. There are no whitespaces so the minIntrinsicWidth=maxIntrinsicWidth
|
||||
// (To think about that, the last condition is not quite right;
|
||||
// we should calculate minIntrinsicWidth by soft line breaks.
|
||||
// However, it's how it's done in Flutter now)
|
||||
auto& run = this->fRuns[0];
|
||||
auto advance = run.advance();
|
||||
auto textRange = TextRange(0, this->text().size());
|
||||
auto textExcludingSpaces = TextRange(0, fTrailingSpaces);
|
||||
InternalLineMetrics metrics(this->strutForceHeight());
|
||||
metrics.add(&run);
|
||||
if (this->strutEnabled()) {
|
||||
this->strutMetrics().updateLineMetrics(metrics);
|
||||
}
|
||||
auto disableFirstAscent = this->paragraphStyle().getTextHeightBehavior() &
|
||||
TextHeightBehavior::kDisableFirstAscent;
|
||||
auto disableLastDescent = this->paragraphStyle().getTextHeightBehavior() &
|
||||
TextHeightBehavior::kDisableLastDescent;
|
||||
if (disableFirstAscent) {
|
||||
metrics.fAscent = metrics.fRawAscent;
|
||||
}
|
||||
if (disableLastDescent) {
|
||||
metrics.fDescent = metrics.fRawDescent;
|
||||
}
|
||||
ClusterIndex trailingSpaces = fClusters.size();
|
||||
do {
|
||||
--trailingSpaces;
|
||||
auto& cluster = fClusters[trailingSpaces];
|
||||
if (!cluster.isWhitespaceBreak()) {
|
||||
++trailingSpaces;
|
||||
break;
|
||||
}
|
||||
advance.fX -= cluster.width();
|
||||
} while (trailingSpaces != 0);
|
||||
|
||||
advance.fY = metrics.height();
|
||||
auto clusterRange = ClusterRange(0, trailingSpaces);
|
||||
auto clusterRangeWithGhosts = ClusterRange(0, this->clusters().size() - 1);
|
||||
this->addLine(SkPoint::Make(0, 0), advance,
|
||||
textExcludingSpaces, textRange, textRange,
|
||||
clusterRange, clusterRangeWithGhosts, run.advance().x(),
|
||||
metrics);
|
||||
|
||||
fLongestLine = nearlyZero(advance.fX) ? run.advance().fX : advance.fX;
|
||||
fHeight = advance.fY;
|
||||
fWidth = maxWidth;
|
||||
fMaxIntrinsicWidth = run.advance().fX;
|
||||
fMinIntrinsicWidth = advance.fX;
|
||||
fAlphabeticBaseline = fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline();
|
||||
fIdeographicBaseline = fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline();
|
||||
fExceededMaxLines = false;
|
||||
return;
|
||||
}
|
||||
|
||||
TextWrapper textWrapper;
|
||||
textWrapper.breakTextIntoLines(
|
||||
this,
|
||||
@ -536,7 +619,6 @@ void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
|
||||
if (addEllipsis) {
|
||||
line.createEllipsis(maxWidth, getEllipsis(), true);
|
||||
}
|
||||
|
||||
fLongestLine = std::max(fLongestLine, nearlyZero(advance.fX) ? widthWithSpaces : advance.fX);
|
||||
});
|
||||
|
||||
|
@ -270,6 +270,9 @@ private:
|
||||
SkScalar fMaxWidthWithTrailingSpaces;
|
||||
|
||||
std::shared_ptr<SkUnicode> fUnicode;
|
||||
bool fHasLineBreaks;
|
||||
bool fHasWhitespacesInside;
|
||||
TextIndex fTrailingSpaces;
|
||||
};
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
|
@ -401,7 +401,6 @@ public:
|
||||
if (fForceStrut) {
|
||||
return;
|
||||
}
|
||||
|
||||
fAscent = std::min(fAscent, run->correctAscent());
|
||||
fDescent = std::max(fDescent, run->correctDescent());
|
||||
fLeading = std::max(fLeading, run->correctLeading());
|
||||
@ -463,6 +462,11 @@ public:
|
||||
fLeading = l;
|
||||
}
|
||||
|
||||
void updateRawData(SkScalar ra, SkScalar rd) {
|
||||
fRawAscent = ra;
|
||||
fRawDescent = rd;
|
||||
}
|
||||
|
||||
SkScalar alphabeticBaseline() const { return fLeading / 2 - fAscent; }
|
||||
SkScalar ideographicBaseline() const { return fDescent - fAscent + fLeading; }
|
||||
SkScalar deltaBaselines() const { return fLeading / 2 + fDescent; }
|
||||
@ -477,6 +481,7 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
friend class ParagraphImpl;
|
||||
friend class TextWrapper;
|
||||
friend class TextLine;
|
||||
|
||||
|
@ -6790,3 +6790,42 @@ UNIX_ONLY_TEST(SkParagraph_EllipsisGetRectForRange, reporter) {
|
||||
canvas.drawRects(SK_ColorRED, boxes1);
|
||||
canvas.drawRects(SK_ColorRED, boxes2);
|
||||
}
|
||||
|
||||
UNIX_ONLY_TEST(SkParagraph_StrutAndTextBehavior, reporter) {
|
||||
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
TestCanvas canvas("SkParagraph_StrutAndTextBehavior.png");
|
||||
const char* text = " ";
|
||||
const size_t len = strlen(text);
|
||||
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({SkString("Ahem")});
|
||||
text_style.setFontSize(16.0);
|
||||
text_style.setColor(SK_ColorBLACK);
|
||||
StrutStyle strut_style;
|
||||
strut_style.setStrutEnabled(true);
|
||||
strut_style.setForceStrutHeight(true);
|
||||
strut_style.setHeight(1.5);
|
||||
strut_style.setHeightOverride(true);
|
||||
strut_style.setFontFamilies({SkString("Ahem")});
|
||||
strut_style.setFontSize(16.0);
|
||||
ParagraphStyle paragraph_style;
|
||||
paragraph_style.setStrutStyle(strut_style);
|
||||
paragraph_style.setTextStyle(text_style);
|
||||
|
||||
auto draw = [&](TextHeightBehavior tb) {
|
||||
paragraph_style.setTextHeightBehavior(tb);
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(text, len);
|
||||
auto paragraph = builder.Build();
|
||||
paragraph->layout(SK_ScalarInfinity);
|
||||
return paragraph->getHeight();
|
||||
};
|
||||
|
||||
auto height1 = draw(TextHeightBehavior::kDisableAll);
|
||||
auto height2 = draw(TextHeightBehavior::kAll);
|
||||
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(height1, 16.0f));
|
||||
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(height2, 24.0f));
|
||||
}
|
||||
|
@ -115,6 +115,7 @@ class SKUNICODE_API SkUnicode {
|
||||
virtual bool isControl(SkUnichar utf8) = 0;
|
||||
virtual bool isWhitespace(SkUnichar utf8) = 0;
|
||||
virtual bool isSpace(SkUnichar utf8) = 0;
|
||||
virtual bool isHardBreak(SkUnichar utf8) = 0;
|
||||
virtual SkString toUpper(const SkString&) = 0;
|
||||
|
||||
// Methods used in SkShaper and SkText
|
||||
|
@ -419,6 +419,11 @@ public:
|
||||
return SkScriptIterator_icu::makeScriptIterator();
|
||||
}
|
||||
|
||||
static bool isHardLineBreak(SkUnichar utf8) {
|
||||
auto property = sk_u_getIntPropertyValue(utf8, UCHAR_LINE_BREAK);
|
||||
return property == U_LB_LINE_FEED || property == U_LB_MANDATORY_BREAK;
|
||||
}
|
||||
|
||||
// TODO: Use ICU data file to detect controls and whitespaces
|
||||
bool isControl(SkUnichar utf8) override {
|
||||
return sk_u_iscntrl(utf8);
|
||||
@ -432,9 +437,8 @@ public:
|
||||
return sk_u_isspace(utf8);
|
||||
}
|
||||
|
||||
static bool isHardLineBreak(SkUnichar utf8) {
|
||||
auto property = sk_u_getIntPropertyValue(utf8, UCHAR_LINE_BREAK);
|
||||
return property == U_LB_LINE_FEED || property == U_LB_MANDATORY_BREAK;
|
||||
bool isHardBreak(SkUnichar utf8) override {
|
||||
return SkUnicode_icu::isHardLineBreak(utf8);
|
||||
}
|
||||
|
||||
SkString toUpper(const SkString& str) override {
|
||||
|
Loading…
Reference in New Issue
Block a user