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:
Julia Lavrova 2022-04-07 14:19:18 -04:00 committed by SkCQ
parent a23c2118d1
commit 62f07d262c
8 changed files with 173 additions and 19 deletions

View File

@ -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:

View File

@ -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);
}

View File

@ -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);
});

View File

@ -270,6 +270,9 @@ private:
SkScalar fMaxWidthWithTrailingSpaces;
std::shared_ptr<SkUnicode> fUnicode;
bool fHasLineBreaks;
bool fHasWhitespacesInside;
TextIndex fTrailingSpaces;
};
} // namespace textlayout
} // namespace skia

View File

@ -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;

View File

@ -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));
}

View File

@ -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

View File

@ -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 {