Add halfLeading to TextStyle and StrutStyle: https://github.com/flutter/engine/pull/24668

Change-Id: I5627cb163400880992db622bb887093da92d1cd5
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/394969
Commit-Queue: Julia Lavrova <jlavrova@google.com>
Reviewed-by: Julia Lavrova <jlavrova@google.com>
This commit is contained in:
weiyuhuang 2021-04-13 15:01:47 -07:00 committed by Skia Commit-Bot
parent 1456aff211
commit 32f15531c4
12 changed files with 375 additions and 41 deletions

View File

@ -69,9 +69,9 @@ public:
// descent`. Ascent and descent are provided as positive numbers. Raw numbers
// for specific runs of text can be obtained in run_metrics_map. These values
// are the cumulative metrics for the entire line.
double fAscent = 0.0;
double fDescent = 0.0;
double fUnscaledAscent = 0.0;
double fAscent = SK_ScalarMax;
double fDescent = SK_ScalarMax;
double fUnscaledAscent = SK_ScalarMax;
// Total height of the paragraph including the current line.
//
// The height of the current line is `round(ascent + descent)`.

View File

@ -45,10 +45,14 @@ struct StrutStyle {
bool getHeightOverride() const { return fHeightOverride; }
void setHeightOverride(bool v) { fHeightOverride = v; }
void setHalfLeading(bool halfLeading) { fHalfLeading = halfLeading; }
bool getHalfLeading() const { return fHalfLeading; }
bool operator==(const StrutStyle& rhs) const {
return this->fEnabled == rhs.fEnabled &&
this->fHeightOverride == rhs.fHeightOverride &&
this->fForceHeight == rhs.fForceHeight &&
this->fHalfLeading == rhs.fHalfLeading &&
nearlyEqual(this->fLeading, rhs.fLeading) &&
nearlyEqual(this->fHeight, rhs.fHeight) &&
nearlyEqual(this->fFontSize, rhs.fFontSize) &&
@ -66,6 +70,9 @@ private:
bool fForceHeight;
bool fEnabled;
bool fHeightOverride;
// true: half leading.
// false: scale ascent/descent with fHeight.
bool fHalfLeading;
};
struct ParagraphStyle {

View File

@ -221,6 +221,9 @@ public:
void setHeightOverride(bool heightOverride) { fHeightOverride = heightOverride; }
bool getHeightOverride() const { return fHeightOverride; }
void setHalfLeading(bool halfLeading) { fHalfLeading = halfLeading; }
bool getHalfLeading() const { return fHalfLeading; }
void setLetterSpacing(SkScalar letterSpacing) { fLetterSpacing = letterSpacing; }
SkScalar getLetterSpacing() const { return fLetterSpacing; }
@ -262,6 +265,9 @@ private:
SkScalar fFontSize = 14.0;
SkScalar fHeight = 1.0;
bool fHeightOverride = false;
// true: half leading.
// false: scale ascent/descent with fHeight.
bool fHalfLeading = false;
SkString fLocale = {};
SkScalar fLetterSpacing = 0.0;
SkScalar fWordSpacing = 0.0;

View File

@ -144,7 +144,8 @@ void OneLineShaper::fillGaps(size_t startingCount) {
}
}
void OneLineShaper::finish(TextRange blockText, SkScalar height, SkScalar& advanceX) {
void OneLineShaper::finish(const Block& block, SkScalar height, SkScalar& advanceX) {
auto blockText = block.fRange;
// Add all unresolved blocks to resolved blocks
while (!fUnresolvedBlocks.empty()) {
@ -165,30 +166,30 @@ void OneLineShaper::finish(TextRange blockText, SkScalar height, SkScalar& advan
// Go through all of them
size_t lastTextEnd = blockText.start;
for (auto& block : fResolvedBlocks) {
for (auto& resolvedBlock : fResolvedBlocks) {
if (block.fText.end <= blockText.start) {
if (resolvedBlock.fText.end <= blockText.start) {
continue;
}
if (block.fRun != nullptr) {
fParagraph->fFontSwitches.emplace_back(block.fText.start, block.fRun->fFont);
if (resolvedBlock.fRun != nullptr) {
fParagraph->fFontSwitches.emplace_back(resolvedBlock.fText.start, resolvedBlock.fRun->fFont);
}
auto run = block.fRun;
auto glyphs = block.fGlyphs;
auto text = block.fText;
auto run = resolvedBlock.fRun;
auto glyphs = resolvedBlock.fGlyphs;
auto text = resolvedBlock.fText;
if (lastTextEnd != text.start) {
SkDEBUGF("Text ranges mismatch: ...:%d] - [%d:%d] (%d-%d)\n", lastTextEnd, text.start, text.end, glyphs.start, glyphs.end);
SkASSERT(false);
}
lastTextEnd = text.end;
if (block.isFullyResolved()) {
if (resolvedBlock.isFullyResolved()) {
// Just move the entire run
block.fRun->fIndex = this->fParagraph->fRuns.size();
this->fParagraph->fRuns.emplace_back(*block.fRun);
block.fRun.reset();
resolvedBlock.fRun->fIndex = this->fParagraph->fRuns.size();
this->fParagraph->fRuns.emplace_back(*resolvedBlock.fRun);
resolvedBlock.fRun.reset();
continue;
} else if (run == nullptr) {
continue;
@ -207,6 +208,7 @@ void OneLineShaper::finish(TextRange blockText, SkScalar height, SkScalar& advan
info,
run->fClusterStart,
height,
block.fStyle.getHalfLeading(),
this->fParagraph->fRuns.count(),
advanceX
);
@ -598,6 +600,7 @@ bool OneLineShaper::iterateThroughShapingRegions(const ShapeVisitor& shape) {
runInfo,
0,
0.0f,
false,
fParagraph->fRuns.count(),
advanceX);
@ -633,6 +636,7 @@ bool OneLineShaper::shape() {
// Start from the beginning (hoping that it's a simple case one block - one run)
fHeight = block.fStyle.getHeightOverride() ? block.fStyle.getHeight() : 0;
fUseHalfLeading = block.fStyle.getHalfLeading();
fAdvance = SkVector::Make(advanceX, 0);
fCurrentText = block.fRange;
fUnresolvedBlocks.emplace_back(RunBlock(block.fRange));
@ -696,7 +700,7 @@ bool OneLineShaper::shape() {
}
});
this->finish(block.fRange, fHeight, advanceX);
this->finish(block, fHeight, advanceX);
});
return true;

View File

@ -18,6 +18,7 @@ public:
explicit OneLineShaper(ParagraphImpl* paragraph)
: fParagraph(paragraph)
, fHeight(0.0f)
, fUseHalfLeading(false)
, fAdvance(SkPoint::Make(0.0f, 0.0f))
, fUnresolvedGlyphs(0)
, fUniqueRunId(paragraph->fRuns.size()){ }
@ -69,7 +70,7 @@ private:
#ifdef SK_DEBUG
void printState();
#endif
void finish(TextRange text, SkScalar height, SkScalar& advanceX);
void finish(const Block& block, SkScalar height, SkScalar& advanceX);
void beginLine() override {}
void runInfo(const RunInfo&) override {}
@ -81,6 +82,7 @@ private:
info,
fCurrentText.start,
fHeight,
fUseHalfLeading,
++fUniqueRunId,
fAdvance.fX);
return fCurrentRun->newRunBuffer();
@ -101,6 +103,7 @@ private:
ParagraphImpl* fParagraph;
TextRange fCurrentText;
SkScalar fHeight;
bool fUseHalfLeading;
SkVector fAdvance;
size_t fUnresolvedGlyphs;
size_t fUniqueRunId;

View File

@ -914,11 +914,20 @@ void ParagraphImpl::computeEmptyMetrics() {
fEmptyMetrics.leading());
} else if (!paragraphStyle().getStrutStyle().getForceStrutHeight() &&
textStyle.getHeightOverride()) {
auto multiplier = textStyle.getHeight() * textStyle.getFontSize() / fEmptyMetrics.height();
fEmptyMetrics.update(
fEmptyMetrics.ascent() * multiplier,
fEmptyMetrics.descent() * multiplier,
fEmptyMetrics.leading() * multiplier);
const auto intrinsicHeight = fEmptyMetrics.height();
const auto strutHeight = textStyle.getHeight() * textStyle.getFontSize();
if (paragraphStyle().getStrutStyle().getHalfLeading()) {
fEmptyMetrics.update(
fEmptyMetrics.ascent(),
fEmptyMetrics.descent(),
fEmptyMetrics.leading() + strutHeight - intrinsicHeight);
} else {
const auto multiplier = strutHeight / intrinsicHeight;
fEmptyMetrics.update(
fEmptyMetrics.ascent() * multiplier,
fEmptyMetrics.descent() * multiplier,
fEmptyMetrics.leading() * multiplier);
}
}
if (fParagraphStyle.getStrutStyle().getStrutEnabled()) {

View File

@ -16,6 +16,7 @@ StrutStyle::StrutStyle() {
fLeading = -1;
fForceHeight = false;
fHeightOverride = false;
fHalfLeading = false;
fEnabled = false;
}

View File

@ -18,6 +18,7 @@ Run::Run(ParagraphImpl* owner,
const SkShaper::RunHandler::RunInfo& info,
size_t firstChar,
SkScalar heightMultiplier,
bool useHalfLeading,
size_t index,
SkScalar offsetX)
: fOwner(owner)
@ -26,6 +27,7 @@ Run::Run(ParagraphImpl* owner,
, fFont(info.fFont)
, fClusterStart(firstChar)
, fHeightMultiplier(heightMultiplier)
, fUseHalfLeading(useHalfLeading)
{
fBidiLevel = info.fBidiLevel;
fAdvance = info.fAdvance;
@ -53,9 +55,17 @@ void Run::calculateMetrics() {
fCorrectAscent = fFontMetrics.fAscent - fFontMetrics.fLeading * 0.5;
fCorrectDescent = fFontMetrics.fDescent + fFontMetrics.fLeading * 0.5;
fCorrectLeading = 0;
if (!SkScalarNearlyZero(fHeightMultiplier)) {
auto multiplier = fHeightMultiplier * fFont.getSize() /
(fFontMetrics.fDescent - fFontMetrics.fAscent + fFontMetrics.fLeading);
if (SkScalarNearlyZero(fHeightMultiplier)) {
return;
}
const auto runHeight = fHeightMultiplier * fFont.getSize();
const auto fontIntrinsicHeight = fCorrectDescent - fCorrectAscent;
if (fUseHalfLeading) {
const auto extraLeading = (runHeight - fontIntrinsicHeight) / 2;
fCorrectAscent -= extraLeading;
fCorrectDescent += extraLeading;
} else {
const auto multiplier = runHeight / fontIntrinsicHeight;
fCorrectAscent *= multiplier;
fCorrectDescent *= multiplier;
}

View File

@ -58,6 +58,7 @@ public:
const SkShaper::RunHandler::RunInfo& info,
size_t firstChar,
SkScalar heightMultiplier,
bool useHalfLeading,
size_t index,
SkScalar shiftX);
Run(const Run&) = default;
@ -95,6 +96,7 @@ public:
TextDirection getTextDirection() const { return leftToRight() ? TextDirection::kLtr : TextDirection::kRtl; }
size_t index() const { return fIndex; }
SkScalar heightMultiplier() const { return fHeightMultiplier; }
bool useHalfLeading() const { return fUseHalfLeading; }
PlaceholderStyle* placeholderStyle() const;
bool isPlaceholder() const { return fPlaceholderIndex != std::numeric_limits<size_t>::max(); }
size_t clusterIndex(size_t pos) const { return fClusterIndexes[pos]; }
@ -190,6 +192,7 @@ private:
SkFontMetrics fFontMetrics;
const SkScalar fHeightMultiplier;
const bool fUseHalfLeading;
SkScalar fCorrectAscent;
SkScalar fCorrectDescent;
SkScalar fCorrectLeading;
@ -382,7 +385,6 @@ public:
}
void add(Run* run) {
if (fForceStrut) {
return;
}
@ -406,11 +408,11 @@ public:
}
void clean() {
fAscent = 0;
fDescent = 0;
fAscent = SK_ScalarMax;
fDescent = SK_ScalarMin;
fLeading = 0;
fRawAscent = 0;
fRawDescent = 0;
fRawAscent = SK_ScalarMax;
fRawDescent = SK_ScalarMin;
fRawLeading = 0;
}

View File

@ -531,8 +531,8 @@ std::unique_ptr<Run> TextLine::shapeEllipsis(const SkString& ellipsis, const Run
class ShapeHandler final : public SkShaper::RunHandler {
public:
ShapeHandler(SkScalar lineHeight, const SkString& ellipsis)
: fRun(nullptr), fLineHeight(lineHeight), fEllipsis(ellipsis) {}
ShapeHandler(SkScalar lineHeight, bool useHalfLeading, const SkString& ellipsis)
: fRun(nullptr), fLineHeight(lineHeight), fUseHalfLeading(useHalfLeading), fEllipsis(ellipsis) {}
Run* run() & { return fRun.get(); }
std::unique_ptr<Run> run() && { return std::move(fRun); }
@ -545,7 +545,7 @@ std::unique_ptr<Run> TextLine::shapeEllipsis(const SkString& ellipsis, const Run
Buffer runBuffer(const RunInfo& info) override {
SkASSERT(!fRun);
fRun = std::make_unique<Run>(nullptr, info, 0, fLineHeight, 0, 0);
fRun = std::make_unique<Run>(nullptr, info, 0, fLineHeight, fUseHalfLeading, 0, 0);
return fRun->newRunBuffer();
}
@ -560,10 +560,11 @@ std::unique_ptr<Run> TextLine::shapeEllipsis(const SkString& ellipsis, const Run
std::unique_ptr<Run> fRun;
SkScalar fLineHeight;
bool fUseHalfLeading;
SkString fEllipsis;
};
ShapeHandler handler(run.heightMultiplier(), ellipsis);
ShapeHandler handler(run.heightMultiplier(), run.useHalfLeading(), ellipsis);
std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder();
SkASSERT_RELEASE(shaper != nullptr);
shaper->shape(ellipsis.c_str(), ellipsis.size(), run.font(), true,
@ -1010,13 +1011,12 @@ void TextLine::getRectsForRange(TextRange textRange0,
}
break;
case RectHeightStyle::kTight: {
if (run->fHeightMultiplier > 0) {
// This is a special case when we do not need to take in account this height multiplier
auto correctedHeight = clip.height() / run->fHeightMultiplier;
auto verticalShift = this->sizes().runTop(context.run, LineMetricStyle::Typographic);
clip.fTop += verticalShift;
clip.fBottom = clip.fTop + correctedHeight;
if (run->fHeightMultiplier <= 0) {
break;
}
const auto effectiveBaseline = this->baseline() + this->sizes().delta();
clip.fTop = effectiveBaseline + run->ascent();
clip.fBottom = effectiveBaseline + run->descent();
}
break;
default:

View File

@ -20,6 +20,7 @@ TextStyle::TextStyle(const TextStyle& other, bool placeholder) {
fHeightOverride = other.fHeightOverride;
fIsPlaceholder = placeholder;
fFontFeatures = other.fFontFeatures;
fHalfLeading = other.fHalfLeading;
}
bool TextStyle::equals(const TextStyle& other) const {
@ -49,6 +50,9 @@ bool TextStyle::equals(const TextStyle& other) const {
if (fHeight != other.fHeight) {
return false;
}
if (fHalfLeading != other.fHalfLeading) {
return false;
}
if (fFontSize != other.fFontSize) {
return false;
}
@ -134,7 +138,8 @@ bool TextStyle::matchOneAttribute(StyleType styleType, const TextStyle& other) c
fLocale == other.fLocale &&
fFontFamilies == other.fFontFamilies &&
fFontSize == other.fFontSize &&
fHeight == other.fHeight;
fHeight == other.fHeight &&
fHalfLeading == other.fHalfLeading;
default:
SkASSERT(false);
return false;

View File

@ -1267,6 +1267,293 @@ DEF_TEST(SkParagraph_HeightOverrideParagraph, reporter) {
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.bottom(), 165.495f, EPSILON5));
}
DEF_TEST(SkParagraph_BasicHalfLeading, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
if (!fontCollection->fontsFound()) {
return;
}
const char* text = "01234満毎冠行来昼本可\nabcd\n満毎冠行来昼本可";
const size_t len = strlen(text);
TestCanvas canvas("SkParagraph_BasicHalfLeading.png");
ParagraphStyle paragraph_style;
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setFontSize(20.0f);
text_style.setColor(SK_ColorBLACK);
text_style.setLetterSpacing(0.0f);
text_style.setWordSpacing(0.0f);
text_style.setHeightOverride(true);
text_style.setHeight(3.6345f);
text_style.setHalfLeading(true);
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
builder.pushStyle(text_style);
builder.addText(text);
auto paragraph = builder.Build();
paragraph->layout(550);
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
REPORTER_ASSERT(reporter, impl->styles().size() == 1); // paragraph style does not count
REPORTER_ASSERT(reporter, impl->styles()[0].fStyle.equals(text_style));
paragraph->paint(canvas.get(), 0, 0);
const RectWidthStyle rect_width_style = RectWidthStyle::kTight;
std::vector<TextBox> boxes = paragraph->getRectsForRange(0, len, RectHeightStyle::kTight, rect_width_style);
std::vector<TextBox> lineBoxes = paragraph->getRectsForRange(0, len, RectHeightStyle::kMax, rect_width_style);
canvas.drawRects(SK_ColorBLUE, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 3ull);
REPORTER_ASSERT(reporter, lineBoxes.size() == boxes.size());
const auto line_spacing1 = boxes[1].rect.top() - boxes[0].rect.bottom();
const auto line_spacing2 = boxes[2].rect.top() - boxes[1].rect.bottom();
// Uniform line spacing.
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(line_spacing1, line_spacing2));
// line spacing is distributed evenly over and under the text.
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(lineBoxes[0].rect.bottom() - boxes[0].rect.bottom(), boxes[0].rect.top() - lineBoxes[0].rect.top()));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(lineBoxes[1].rect.bottom() - boxes[1].rect.bottom(), boxes[1].rect.top() - lineBoxes[1].rect.top()));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(lineBoxes[2].rect.bottom() - boxes[2].rect.bottom(), boxes[2].rect.top() - lineBoxes[2].rect.top()));
// Half leading does not move the text horizontally.
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.left(), 0, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.right(), 43.843f, EPSILON100));
}
DEF_TEST(SkParagraph_NearZeroHeightMixedDistribution, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
if (!fontCollection->fontsFound()) {
return;
}
const char* text = "Cookies need love";
const size_t len = strlen(text);
TestCanvas canvas("SkParagraph_ZeroHeightHalfLeading.png");
ParagraphStyle paragraph_style;
paragraph_style.setTextHeightBehavior(TextHeightBehavior::kAll);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setFontSize(20.0f);
text_style.setColor(SK_ColorBLACK);
text_style.setLetterSpacing(0.0f);
text_style.setWordSpacing(0.0f);
text_style.setHeightOverride(true);
text_style.setHeight(0.001f);
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
// First run, half leading.
text_style.setHalfLeading(true);
builder.pushStyle(text_style);
builder.addText(text);
// Second run, no half leading.
text_style.setHalfLeading(false);
builder.pushStyle(text_style);
builder.addText(text);
auto paragraph = builder.Build();
paragraph->layout(550);
paragraph->paint(canvas.get(), 0, 0);
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
REPORTER_ASSERT(reporter, impl->runs().size() == 2);
REPORTER_ASSERT(reporter, impl->styles().size() == 2); // paragraph style does not count
REPORTER_ASSERT(reporter, impl->lines().size() == 1ull);
const RectWidthStyle rect_width_style = RectWidthStyle::kTight;
std::vector<TextBox> boxes = paragraph->getRectsForRange(0, len, RectHeightStyle::kTight, rect_width_style);
std::vector<TextBox> lineBoxes = paragraph->getRectsForRange(0, len, RectHeightStyle::kMax, rect_width_style);
canvas.drawRects(SK_ColorBLUE, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1ull);
REPORTER_ASSERT(reporter, lineBoxes.size() == boxes.size());
// From font metrics.
const auto metricsAscent = -18.5546875f;
const auto metricsDescent = 4.8828125f;
// As the height multiplier converges to 0 (but not 0 since 0 is used as a
// magic value to indicate there's no height multiplier), the `Run`s top
// edge and bottom edge will converge to a horizontal line:
// - When half leading is used the vertical line is roughly the center of
// of the glyphs in the run ((fontMetrics.descent - fontMetrics.ascent) / 2)
// - When half leading is disabled the line is the alphabetic baseline.
// Expected values in baseline coordinate space:
const auto run1_ascent = (metricsAscent + metricsDescent) / 2;
const auto run1_descent = (metricsAscent + metricsDescent) / 2;
const auto run2_ascent = 0.0f;
const auto run2_descent = 0.0f;
const auto line_top = std::min(run1_ascent, run2_ascent);
const auto line_bottom = std::max(run1_descent, run2_descent);
// Expected glyph height in linebox coordinate space:
const auto glyphs_top = metricsAscent - line_top;
const auto glyphs_bottom = metricsDescent - line_top;
// kTight reports the glyphs' bounding box in the linebox's coordinate
// space.
const auto actual_glyphs_top = boxes[0].rect.top() - lineBoxes[0].rect.top();
const auto actual_glyphs_bottom = boxes[0].rect.bottom() - lineBoxes[0].rect.top();
// Use a relatively large epsilon since the heightMultiplier is not actually
// 0.
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(glyphs_top, actual_glyphs_top, EPSILON20));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(glyphs_bottom, actual_glyphs_bottom, EPSILON20));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(lineBoxes[0].rect.height(), line_bottom - line_top, EPSILON2));
REPORTER_ASSERT(reporter, lineBoxes[0].rect.height() > 1);
// Half leading does not move the text horizontally.
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 0, EPSILON100));
}
DEF_TEST(SkParagraph_StrutHalfLeading, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
if (!fontCollection->fontsFound()) {
return;
}
const char* text = "01234満毎冠行来昼本可\nabcd\n満毎冠行来昼本可";
const size_t len = strlen(text);
TestCanvas canvas("SkParagraph_StrutHalfLeading.png");
ParagraphStyle paragraph_style;
// Tiny font and height multiplier to ensure the height is entirely decided
// by the strut.
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setFontSize(1.0f);
text_style.setColor(SK_ColorBLACK);
text_style.setLetterSpacing(0.0f);
text_style.setWordSpacing(0.0f);
text_style.setHeight(0.1f);
StrutStyle strut_style;
strut_style.setFontFamilies({SkString("Roboto")});
strut_style.setFontSize(20.0f);
strut_style.setHeight(3.6345f);
strut_style.setHalfLeading(true);
strut_style.setStrutEnabled(true);
strut_style.setForceStrutHeight(true);
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
builder.pushStyle(text_style);
builder.addText(text);
auto paragraph = builder.Build();
paragraph->layout(550);
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
REPORTER_ASSERT(reporter, impl->styles().size() == 1); // paragraph style does not count
paragraph->paint(canvas.get(), 0, 0);
const RectWidthStyle rect_width_style = RectWidthStyle::kTight;
std::vector<TextBox> boxes = paragraph->getRectsForRange(0, len, RectHeightStyle::kTight, rect_width_style);
std::vector<TextBox> lineBoxes = paragraph->getRectsForRange(0, len, RectHeightStyle::kMax, rect_width_style);
canvas.drawRects(SK_ColorBLUE, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 3ull);
REPORTER_ASSERT(reporter, lineBoxes.size() == boxes.size());
const auto line_spacing1 = boxes[1].rect.top() - boxes[0].rect.bottom();
const auto line_spacing2 = boxes[2].rect.top() - boxes[1].rect.bottom();
// Uniform line spacing.
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(line_spacing1, line_spacing2));
// line spacing is distributed evenly over and under the text.
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(lineBoxes[0].rect.bottom() - boxes[0].rect.bottom(), boxes[0].rect.top() - lineBoxes[0].rect.top()));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(lineBoxes[1].rect.bottom() - boxes[1].rect.bottom(), boxes[1].rect.top() - lineBoxes[1].rect.top()));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(lineBoxes[2].rect.bottom() - boxes[2].rect.bottom(), boxes[2].rect.top() - lineBoxes[2].rect.top()));
// Half leading does not move the text horizontally.
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.left(), 0, EPSILON100));
}
DEF_TEST(SkParagraph_TrimLeadingDistribution, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
if (!fontCollection->fontsFound()) {
return;
}
const char* text = "01234満毎冠行来昼本可\nabcd\n満毎冠行来昼本可";
const size_t len = strlen(text);
TestCanvas canvas("SkParagraph_TrimHalfLeading.png");
ParagraphStyle paragraph_style;
paragraph_style.setTextHeightBehavior(TextHeightBehavior::kDisableAll);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setFontSize(20.0f);
text_style.setColor(SK_ColorBLACK);
text_style.setLetterSpacing(0.0f);
text_style.setWordSpacing(0.0f);
text_style.setHeightOverride(true);
text_style.setHeight(3.6345f);
text_style.setHalfLeading(true);
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
builder.pushStyle(text_style);
builder.addText(text);
auto paragraph = builder.Build();
paragraph->layout(550);
paragraph->paint(canvas.get(), 0, 0);
const RectWidthStyle rect_width_style = RectWidthStyle::kTight;
std::vector<TextBox> boxes = paragraph->getRectsForRange(0, len, RectHeightStyle::kTight, rect_width_style);
std::vector<TextBox> lineBoxes = paragraph->getRectsForRange(0, len, RectHeightStyle::kMax, rect_width_style);
canvas.drawRects(SK_ColorBLUE, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 3ull);
REPORTER_ASSERT(reporter, lineBoxes.size() == boxes.size());
const auto line_spacing1 = boxes[1].rect.top() - boxes[0].rect.bottom();
const auto line_spacing2 = boxes[2].rect.top() - boxes[1].rect.bottom();
// Uniform line spacing. The delta is introduced by the height rounding.
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(line_spacing1, line_spacing2, 1));
// Trim the first line's top leading.
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(lineBoxes[0].rect.top(), boxes[0].rect.top()));
// Trim the last line's bottom leading.
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(lineBoxes[2].rect.bottom(), boxes[2].rect.bottom()));
const auto halfLeading = lineBoxes[0].rect.bottom() - boxes[0].rect.bottom();
// Large epsilon because of rounding.
const auto epsilon = EPSILON10;
// line spacing is distributed evenly over and under the text.
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.top() - lineBoxes[1].rect.top(), halfLeading, epsilon));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(lineBoxes[1].rect.bottom() - boxes[1].rect.bottom(), halfLeading));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[2].rect.top() - lineBoxes[2].rect.top(), halfLeading, epsilon));
// Half leading does not move the text horizontally.
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.left(), 0, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.right(), 43.843f, EPSILON100));
}
DEF_TEST(SkParagraph_LeftAlignParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
if (!fontCollection->fontsFound()) return;