blob: 0898052a76ae1cff8908cbdf887e4e1b5258110d [file] [log] [blame]
/*
* Copyright 2019 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkEncodedImageFormat.h"
#include "include/core/SkFontMgr.h"
#include "include/core/SkFontStyle.h"
#include "include/core/SkImageEncoder.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkSpan.h"
#include "include/core/SkStream.h"
#include "include/core/SkString.h"
#include "include/core/SkTypeface.h"
#include "include/core/SkTypes.h"
#include "modules/skparagraph/include/DartTypes.h"
#include "modules/skparagraph/include/FontCollection.h"
#include "modules/skparagraph/include/Paragraph.h"
#include "modules/skparagraph/include/ParagraphCache.h"
#include "modules/skparagraph/include/ParagraphStyle.h"
#include "modules/skparagraph/include/TextShadow.h"
#include "modules/skparagraph/include/TextStyle.h"
#include "modules/skparagraph/include/TypefaceFontProvider.h"
#include "modules/skparagraph/src/ParagraphBuilderImpl.h"
#include "modules/skparagraph/src/ParagraphImpl.h"
#include "modules/skparagraph/src/Run.h"
#include "modules/skparagraph/src/TextLine.h"
#include "modules/skparagraph/tests/SkShaperJSONWriter.h"
#include "modules/skparagraph/utils/TestFontCollection.h"
#include "src/core/SkOSFile.h"
#include "src/utils/SkOSPath.h"
#include "tests/Test.h"
#include "tools/Resources.h"
#include <string.h>
#include <algorithm>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <thread>
struct GrContextOptions;
#define VeryLongCanvasWidth 1000000
#define TestCanvasWidth 1000
#define TestCanvasHeight 600
using namespace skia::textlayout;
namespace {
SkScalar EPSILON100 = 0.01f;
SkScalar EPSILON50 = 0.02f;
SkScalar EPSILON20 = 0.05f;
SkScalar EPSILON10 = 0.1f;
SkScalar EPSILON5 = 0.20f;
SkScalar EPSILON2 = 0.50f;
bool equal(const char* base, TextRange a, const char* b) {
return std::strncmp(b, base + a.start, a.width()) == 0;
}
std::u16string mirror(const std::string& text) {
std::u16string result;
result += u"\u202E";
for (auto i = text.size(); i > 0; --i) {
result += text[i - 1];
}
//result += u"\u202C";
return result;
}
std::u16string straight(const std::string& text) {
std::u16string result;
result += u"\u202D";
for (auto ch : text) {
result += ch;
}
return result;
}
class ResourceFontCollection : public FontCollection {
public:
ResourceFontCollection(bool testOnly = false)
: fFontsFound(false)
, fResolvedFonts(0)
, fResourceDir(GetResourcePath("fonts").c_str())
, fFontProvider(sk_make_sp<TypefaceFontProvider>()) {
std::vector<SkString> fonts;
SkOSFile::Iter iter(fResourceDir.c_str());
SkString path;
while (iter.next(&path)) {
if (path.endsWith("Roboto-Italic.ttf")) {
fFontsFound = true;
}
fonts.emplace_back(path);
}
if (!fFontsFound) {
// SkDebugf("Fonts not found, skipping all the tests\n");
return;
}
// Only register fonts if we have to
for (auto& font : fonts) {
SkString file_path;
file_path.printf("%s/%s", fResourceDir.c_str(), font.c_str());
fFontProvider->registerTypeface(SkTypeface::MakeFromFile(file_path.c_str()));
}
if (testOnly) {
this->setTestFontManager(std::move(fFontProvider));
} else {
this->setAssetFontManager(std::move(fFontProvider));
}
this->disableFontFallback();
}
size_t resolvedFonts() const { return fResolvedFonts; }
// TODO: temp solution until we check in fonts
bool fontsFound() const { return fFontsFound; }
private:
bool fFontsFound;
size_t fResolvedFonts;
std::string fResourceDir;
sk_sp<TypefaceFontProvider> fFontProvider;
};
class TestCanvas {
public:
TestCanvas(const char* testName) : name(testName) {
bits.allocN32Pixels(TestCanvasWidth, TestCanvasHeight);
canvas = new SkCanvas(bits);
canvas->clear(SK_ColorWHITE);
}
~TestCanvas() {
SkString tmpDir = skiatest::GetTmpDir();
if (!tmpDir.isEmpty()) {
SkString path = SkOSPath::Join(tmpDir.c_str(), name);
SkFILEWStream file(path.c_str());
if (!SkEncodeImage(&file, bits, SkEncodedImageFormat::kPNG, 100)) {
SkDebugf("Cannot write a picture %s\n", name);
}
}
delete canvas;
}
void drawRects(SkColor color, std::vector<TextBox>& result, bool fill = false) {
SkPaint paint;
if (!fill) {
paint.setStyle(SkPaint::kStroke_Style);
paint.setAntiAlias(true);
paint.setStrokeWidth(1);
}
paint.setColor(color);
for (auto& r : result) {
canvas->drawRect(r.rect, paint);
}
}
void drawLine(SkColor color, SkRect rect, bool vertical = true) {
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
paint.setAntiAlias(true);
paint.setStrokeWidth(1);
paint.setColor(color);
if (vertical) {
canvas->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);
} else {
canvas->drawLine(rect.fLeft, rect.fTop, rect.fRight, rect.fTop, paint);
}
}
void drawLines(SkColor color, std::vector<TextBox>& result) {
for (auto& r : result) {
drawLine(color, r.rect);
}
}
SkCanvas* get() { return canvas; }
private:
SkBitmap bits;
SkCanvas* canvas;
const char* name;
};
#if defined(SK_UNICODE_ICU_IMPLEMENTATION) && defined(SK_UNICODE_CLIENT_IMPLEMENTATION)
class SkUnicode_test : public SkUnicode {
public:
SkUnicode_test() { }
SkUnicode_test(SkSpan<char> text, const ParagraphStyle& style) {
fIcu = SkUnicode::Make();
std::vector<SkUnicode::BidiRegion> bidiRegions;
fIcu->getBidiRegions(text.data(),
text.size(),
style.getTextDirection() == skia::textlayout::TextDirection::kLtr
? TextDirection::kLTR
: TextDirection::kRTL,
&bidiRegions);
std::vector<SkUnicode::Position> words;
fIcu->getWords(text.data(), text.size(), nullptr, &words);
SkTArray<SkUnicode::CodeUnitFlags, true> codeUnitFlags;
fIcu->computeCodeUnitFlags(
text.data(), text.size(), false, &codeUnitFlags);
std::vector<SkUnicode::Position> graphemeBreaks;
std::vector<SkUnicode::LineBreakBefore> lineBreaks;
Position pos = 0;
for (auto& flag : codeUnitFlags) {
if (SkUnicode::isGraphemeStart(flag)) {
graphemeBreaks.emplace_back(pos);
}
if (SkUnicode::isHardLineBreak(flag)) {
lineBreaks.emplace_back(pos, SkUnicode::LineBreakType::kHardLineBreak);
}
if (SkUnicode::isSoftLineBreak(flag)) {
lineBreaks.emplace_back(pos, SkUnicode::LineBreakType::kSoftLineBreak);
}
++pos;
}
fClient = SkUnicode::MakeClientBasedUnicode(text, bidiRegions, words, graphemeBreaks, lineBreaks);
SkTArray<SkUnicode::CodeUnitFlags, true> codeUnitFlags1;
fClient->computeCodeUnitFlags(
text.data(), text.size(), style.getReplaceTabCharacters(), &codeUnitFlags1);
SkASSERT(codeUnitFlags.size() == codeUnitFlags1.size());
for (auto i = 0; i < codeUnitFlags.size(); ++i) {
// Deal with tabulation separately (to avoid all this trouble to copy the initial text)
if ((codeUnitFlags1[i] & CodeUnitFlags::kTabulation) == CodeUnitFlags::kTabulation) {
SkASSERT((codeUnitFlags[i] | CodeUnitFlags::kTabulation) ==
(codeUnitFlags1[i] | CodeUnitFlags::kControl));
} else {
SkASSERT(codeUnitFlags[i] == codeUnitFlags1[i]);
}
}
}
std::unique_ptr<SkUnicode> copy() override {
auto unicode = std::make_unique<SkUnicode_test>();
unicode->fIcu = fIcu->copy();
unicode->fClient = fClient->copy();
return unicode;
}
~SkUnicode_test() override = default;
// For SkShaper
std::unique_ptr<SkBidiIterator> makeBidiIterator(const uint16_t text[], int count,
SkBidiIterator::Direction dir) override;
std::unique_ptr<SkBidiIterator> makeBidiIterator(const char text[],
int count,
SkBidiIterator::Direction dir) override;
std::unique_ptr<SkBreakIterator> makeBreakIterator(const char locale[],
BreakType breakType) override;
std::unique_ptr<SkBreakIterator> makeBreakIterator(BreakType breakType) override;
// For SkParagraph
bool getBidiRegions(const char utf8[],
int utf8Units,
TextDirection dir,
std::vector<BidiRegion>* results) override {
std::vector<SkUnicode::BidiRegion> bidiRegions;
auto result0 = fIcu->getBidiRegions(utf8, utf8Units, dir, &bidiRegions);
auto result1 = fClient->getBidiRegions(utf8, utf8Units, dir, results);
SkASSERT(result0 == result1);
SkASSERT(results->size() == bidiRegions.size());
for (size_t i = 0; i < results->size(); ++i) {
SkASSERT(bidiRegions[i].level == (*results)[i].level);
SkASSERT(bidiRegions[i].start == (*results)[i].start);
SkASSERT(bidiRegions[i].end == (*results)[i].end);
}
return result1;
}
bool computeCodeUnitFlags(char utf8[], int utf8Units, bool replaceTabs,
SkTArray<SkUnicode::CodeUnitFlags, true>* results) override {
return fClient->computeCodeUnitFlags(utf8, utf8Units, replaceTabs, results);
}
bool computeCodeUnitFlags(char16_t utf16[], int utf16Units, bool replaceTabs,
SkTArray<SkUnicode::CodeUnitFlags, true>* results) override {
return fClient->computeCodeUnitFlags(utf16, utf16Units, replaceTabs, results);
}
bool getWords(const char utf8[], int utf8Units, const char* locale, std::vector<Position>* results) override {
return fClient->getWords(utf8, utf8Units, locale, results);
}
SkString toUpper(const SkString& str) override {
return fClient->toUpper(str);
}
void reorderVisual(const BidiLevel runLevels[],
int levelsCount,
int32_t logicalFromVisual[]) override {
std::vector<int32_t> logicalFromVisual0;
logicalFromVisual0.resize(levelsCount);
fIcu->reorderVisual(runLevels, levelsCount, logicalFromVisual0.data());
fClient->reorderVisual(runLevels, levelsCount, logicalFromVisual);
for (int i = 0; i < levelsCount; ++i) {
SkASSERT(logicalFromVisual0[i] == logicalFromVisual[i]);
}
}
private:
friend class SkBidiIterator_test;
friend class SkBreakIterator_test;
std::unique_ptr<SkUnicode> fIcu;
std::unique_ptr<SkUnicode> fClient;
};
class SkBidiIterator_test : public SkBidiIterator {
std::unique_ptr<SkBidiIterator> fIcuIter;
std::unique_ptr<SkBidiIterator> fClientIter;
public:
SkBidiIterator_test(SkUnicode* icu,
SkUnicode* client,
const char text[],
int count,
SkBidiIterator::Direction dir)
: fIcuIter(icu->makeBidiIterator(text, count, dir))
, fClientIter(client->makeBidiIterator(text, count, dir)) { }
SkBidiIterator_test(SkUnicode* icu,
SkUnicode* client,
const uint16_t text[],
int count,
SkBidiIterator::Direction dir)
: fIcuIter(icu->makeBidiIterator(text, count, dir))
, fClientIter(client->makeBidiIterator(text, count, dir)) { }
Position getLength() override {
SkASSERT(fIcuIter->getLength() == fClientIter->getLength());
return fClientIter->getLength();
}
Level getLevelAt(Position pos) override {
SkASSERT(fIcuIter->getLevelAt(pos) == fClientIter->getLevelAt(pos));
return fClientIter->getLevelAt(pos);
}
};
class SkBreakIterator_test: public SkBreakIterator {
std::unique_ptr<SkBreakIterator> fIcuIter;
std::unique_ptr<SkBreakIterator> fClientIter;
public:
explicit SkBreakIterator_test(SkUnicode* icu, SkUnicode* client, SkUnicode::BreakType breakType)
: fIcuIter(icu->makeBreakIterator(breakType))
, fClientIter(client->makeBreakIterator(breakType)) {}
explicit SkBreakIterator_test(SkUnicode* icu,
SkUnicode* client,
const char locale[],
SkUnicode::BreakType breakType)
: fIcuIter(icu->makeBreakIterator(breakType))
, fClientIter(client->makeBreakIterator(locale, breakType)) {}
Position first() override {
SkASSERT(fIcuIter->first() == fClientIter->first());
return fClientIter->first();
}
Position current() override {
SkASSERT(fIcuIter->current() == fClientIter->current());
return fClientIter->current();
}
Position next() override {
SkASSERT(fIcuIter->next() == fClientIter->next());
return fClientIter->next();
}
Status status() override {
SkASSERT(fIcuIter->status() == fClientIter->status());
return fClientIter->status();
}
bool isDone() override {
SkASSERT(fIcuIter->isDone() == fClientIter->isDone());
return fClientIter->isDone();
}
bool setText(const char utftext8[], int utf8Units) override {
fIcuIter->setText(utftext8, utf8Units);
return fClientIter->setText(utftext8, utf8Units);
}
bool setText(const char16_t utftext16[], int utf16Units) override {
fIcuIter->setText(utftext16, utf16Units);
return fClientIter->setText(utftext16, utf16Units);
}
};
std::unique_ptr<SkBidiIterator> SkUnicode_test::makeBidiIterator(const uint16_t text[], int count,
SkBidiIterator::Direction dir) {
return std::make_unique<SkBidiIterator_test>(fIcu.get(), fClient.get(), text, count, dir);
}
std::unique_ptr<SkBidiIterator> SkUnicode_test::makeBidiIterator(const char text[],
int count,
SkBidiIterator::Direction dir) {
return std::make_unique<SkBidiIterator_test>(fIcu.get(), fClient.get(), text, count, dir);
}
std::unique_ptr<SkBreakIterator> SkUnicode_test::makeBreakIterator(const char locale[],
BreakType breakType) {
return std::make_unique<SkBreakIterator_test>(fIcu.get(), fClient.get(), locale, breakType);
}
std::unique_ptr<SkBreakIterator> SkUnicode_test::makeBreakIterator(BreakType breakType) {
return std::make_unique<SkBreakIterator_test>(fIcu.get(), fClient.get(), breakType);
}
class TestParagraphBuilderImpl : public ParagraphBuilderImpl {
public:
TestParagraphBuilderImpl(const ParagraphStyle& style, sk_sp<FontCollection> fontCollection)
: ParagraphBuilderImpl(style, fontCollection) { }
std::unique_ptr<Paragraph> Build() override {
this->SetUnicode(
std::make_unique<SkUnicode_test>(this->getText(), this->getParagraphStyle()));
return ParagraphBuilderImpl::Build();
}
};
#else
typedef ParagraphBuilderImpl TestParagraphBuilderImpl;
#endif
} // namespace
UNIX_ONLY_TEST(SkParagraph_SimpleParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
if (!fontCollection->fontsFound()) return;
const char* text = "Hello World Text Dialog";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth);
REPORTER_ASSERT(reporter, paragraph->unresolvedGlyphs() == 0);
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
REPORTER_ASSERT(reporter, impl->styles().size() == 1); // paragraph style does not count
REPORTER_ASSERT(reporter, impl->styles()[0].fStyle.equals(text_style));
size_t index = 0;
for (auto& line : impl->lines()) {
line.scanStyles(StyleType::kDecorations,
[&index, reporter]
(TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
REPORTER_ASSERT(reporter, index == 0);
REPORTER_ASSERT(reporter, style.getColor() == SK_ColorBLACK);
++index;
});
}
}
UNIX_ONLY_TEST(SkParagraph_InlinePlaceholderParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
TestCanvas canvas("SkParagraph_InlinePlaceholderParagraph.png");
if (!fontCollection->fontsFound()) return;
const char* text = "012 34";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
paragraph_style.setMaxLines(14);
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setColor(SK_ColorBLACK);
text_style.setFontSize(26);
text_style.setWordSpacing(5);
text_style.setLetterSpacing(1);
text_style.setDecoration(TextDecoration::kUnderline);
text_style.setDecorationColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text, len);
PlaceholderStyle placeholder1(50, 50, PlaceholderAlignment::kBaseline, TextBaseline::kAlphabetic, 0);
builder.addPlaceholder(placeholder1);
builder.addText(text, len);
builder.addPlaceholder(placeholder1);
PlaceholderStyle placeholder2(5, 50, PlaceholderAlignment::kBaseline, TextBaseline::kAlphabetic, 50);
builder.addPlaceholder(placeholder2);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder2);
builder.addText(text, len);
builder.addPlaceholder(placeholder2);
builder.addText(text, len);
builder.addText(text, len);
builder.addPlaceholder(placeholder2);
builder.addPlaceholder(placeholder2);
builder.addPlaceholder(placeholder2);
builder.addPlaceholder(placeholder2);
builder.addPlaceholder(placeholder2);
builder.addPlaceholder(placeholder1);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addPlaceholder(placeholder2);
builder.addPlaceholder(placeholder1);
builder.addText(text, len);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth);
paragraph->paint(canvas.get(), 0, 0);
RectHeightStyle rect_height_style = RectHeightStyle::kTight;
RectWidthStyle rect_width_style = RectWidthStyle::kTight;
auto boxes = paragraph->getRectsForRange(0, 3, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorRED, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
boxes = paragraph->getRectsForRange(0, 3, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorGREEN, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
boxes = paragraph->getRectsForPlaceholders();
canvas.drawRects(SK_ColorRED, boxes);
boxes = paragraph->getRectsForRange(4, 17, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorBLUE, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 7);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.left(), 90.921f, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.top(), 50, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.right(), 90.921f + 50, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.bottom(), 100, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[3].rect.left(), 231.343f, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[3].rect.top(), 50, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[3].rect.right(), 231.343f + 50, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[3].rect.bottom(), 100, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[4].rect.left(), 281.343f, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[4].rect.top(), 0, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[4].rect.right(), 281.343f + 5, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[4].rect.bottom(), 50, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[6].rect.left(), 336.343f, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[6].rect.top(), 0, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[6].rect.right(), 336.343f + 5, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[6].rect.bottom(), 50, EPSILON100));
}
UNIX_ONLY_TEST(SkParagraph_InlinePlaceholderBaselineParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
TestCanvas canvas("SkParagraph_InlinePlaceholderBaselineParagraph.png");
if (!fontCollection->fontsFound()) return;
const char* text = "012 34";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
paragraph_style.setMaxLines(14);
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setColor(SK_ColorBLACK);
text_style.setFontSize(26);
text_style.setWordSpacing(5);
text_style.setLetterSpacing(1);
text_style.setDecoration(TextDecoration::kUnderline);
text_style.setDecorationColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text, len);
PlaceholderStyle placeholder(55, 50, PlaceholderAlignment::kBaseline, TextBaseline::kAlphabetic, 38.347f);
builder.addPlaceholder(placeholder);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth);
paragraph->paint(canvas.get(), 0, 0);
auto boxes = paragraph->getRectsForPlaceholders();
canvas.drawRects(SK_ColorRED, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 90.921f, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 0, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f + 55, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 50, EPSILON100));
RectHeightStyle rect_height_style = RectHeightStyle::kTight;
RectWidthStyle rect_width_style = RectWidthStyle::kTight;
boxes = paragraph->getRectsForRange(5, 6, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorBLUE, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 75.324f, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 14.226f, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 44.694f, EPSILON100));
}
UNIX_ONLY_TEST(SkParagraph_InlinePlaceholderAboveBaselineParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
TestCanvas canvas("SkParagraph_InlinePlaceholderAboveBaselineParagraph.png");
if (!fontCollection->fontsFound()) return;
const char* text = "012 34";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
paragraph_style.setMaxLines(14);
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setColor(SK_ColorBLACK);
text_style.setFontSize(26);
text_style.setWordSpacing(5);
text_style.setLetterSpacing(1);
text_style.setDecoration(TextDecoration::kUnderline);
text_style.setDecorationColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text, len);
PlaceholderStyle placeholder(55, 50, PlaceholderAlignment::kAboveBaseline, TextBaseline::kAlphabetic, 903129.129308f);
builder.addPlaceholder(placeholder);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth);
paragraph->paint(canvas.get(), 0, 0);
auto boxes = paragraph->getRectsForPlaceholders();
canvas.drawRects(SK_ColorRED, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 90.921f, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), -0.347f, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f + 55, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 49.652f, EPSILON100));
RectHeightStyle rect_height_style = RectHeightStyle::kTight;
RectWidthStyle rect_width_style = RectWidthStyle::kTight;
boxes = paragraph->getRectsForRange(5, 6, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorBLUE, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 75.324f, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 25.531f, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 56, EPSILON100));
}
UNIX_ONLY_TEST(SkParagraph_InlinePlaceholderBelowBaselineParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
TestCanvas canvas("SkParagraph_InlinePlaceholderBelowBaselineParagraph.png");
if (!fontCollection->fontsFound()) return;
const char* text = "012 34";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
paragraph_style.setMaxLines(14);
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setColor(SK_ColorBLACK);
text_style.setFontSize(26);
text_style.setWordSpacing(5);
text_style.setLetterSpacing(1);
text_style.setDecoration(TextDecoration::kUnderline);
text_style.setDecorationColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text, len);
PlaceholderStyle placeholder(55, 50, PlaceholderAlignment::kBelowBaseline, TextBaseline::kAlphabetic, 903129.129308f);
builder.addPlaceholder(placeholder);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth);
paragraph->paint(canvas.get(), 0, 0);
auto boxes = paragraph->getRectsForPlaceholders();
canvas.drawRects(SK_ColorRED, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 90.921f, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 24, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f + 55, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 74, EPSILON100));
RectHeightStyle rect_height_style = RectHeightStyle::kTight;
RectWidthStyle rect_width_style = RectWidthStyle::kTight;
boxes = paragraph->getRectsForRange(5, 6, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorBLUE, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 75.324f, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), -0.121f, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f, EPSILON2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 30.347f, EPSILON100));
}
UNIX_ONLY_TEST(SkParagraph_InlinePlaceholderBottomParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
TestCanvas canvas("SkParagraph_InlinePlaceholderBottomParagraph.png");
if (!fontCollection->fontsFound()) return;
const char* text = "012 34";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
paragraph_style.setMaxLines(14);
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setColor(SK_ColorBLACK);
text_style.setFontSize(26);
text_style.setWordSpacing(5);
text_style.setLetterSpacing(1);
text_style.setDecoration(TextDecoration::kUnderline);
text_style.setDecorationColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text, len);
PlaceholderStyle placeholder(55, 50, PlaceholderAlignment::kBottom, TextBaseline::kAlphabetic, 0);
builder.addPlaceholder(placeholder);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth);
paragraph->paint(canvas.get(), 0, 0);
RectHeightStyle rect_height_style = RectHeightStyle::kTight;
RectWidthStyle rect_width_style = RectWidthStyle::kTight;
auto boxes = paragraph->getRectsForPlaceholders();
canvas.drawRects(SK_ColorRED, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 90.921f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 0, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f + 55, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 50, EPSILON100));
boxes = paragraph->getRectsForRange(0, 1, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorBLUE, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 0.5f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 19.531f, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 16.097f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 50, EPSILON100));
}
UNIX_ONLY_TEST(SkParagraph_InlinePlaceholderTopParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
TestCanvas canvas("SkParagraph_InlinePlaceholderTopParagraph.png");
if (!fontCollection->fontsFound()) return;
const char* text = "012 34";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
paragraph_style.setMaxLines(14);
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setColor(SK_ColorBLACK);
text_style.setFontSize(26);
text_style.setWordSpacing(5);
text_style.setLetterSpacing(1);
text_style.setDecoration(TextDecoration::kUnderline);
text_style.setDecorationColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text, len);
PlaceholderStyle placeholder(55, 50, PlaceholderAlignment::kTop, TextBaseline::kAlphabetic, 0);
builder.addPlaceholder(placeholder);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth);
paragraph->paint(canvas.get(), 0, 0);
RectHeightStyle rect_height_style = RectHeightStyle::kTight;
RectWidthStyle rect_width_style = RectWidthStyle::kTight;
auto boxes = paragraph->getRectsForPlaceholders();
canvas.drawRects(SK_ColorRED, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 90.921f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 0, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f + 55, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 50, EPSILON100));
boxes = paragraph->getRectsForRange(0, 1, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorBLUE, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 0.5f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 0, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 16.097f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 30.468f, EPSILON100));
}
UNIX_ONLY_TEST(SkParagraph_InlinePlaceholderMiddleParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
TestCanvas canvas("SkParagraph_InlinePlaceholderMiddleParagraph.png");
if (!fontCollection->fontsFound()) return;
const char* text = "012 34";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
paragraph_style.setMaxLines(14);
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setColor(SK_ColorBLACK);
text_style.setFontSize(26);
text_style.setWordSpacing(5);
text_style.setLetterSpacing(1);
text_style.setDecoration(TextDecoration::kUnderline);
text_style.setDecorationColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text, len);
PlaceholderStyle placeholder(55, 50, PlaceholderAlignment::kMiddle, TextBaseline::kAlphabetic, 0);
builder.addPlaceholder(placeholder);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth);
paragraph->paint(canvas.get(), 0, 0);
RectHeightStyle rect_height_style = RectHeightStyle::kTight;
RectWidthStyle rect_width_style = RectWidthStyle::kTight;
auto boxes = paragraph->getRectsForPlaceholders();
canvas.drawRects(SK_ColorRED, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 90.921f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 0, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f + 55, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 50, EPSILON100));
boxes = paragraph->getRectsForRange(5, 6, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorBLUE, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 75.324f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 9.765f, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 40.234f, EPSILON100));
}
UNIX_ONLY_TEST(SkParagraph_InlinePlaceholderIdeographicBaselineParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
TestCanvas canvas("SkParagraph_InlinePlaceholderIdeographicBaselineParagraph.png");
if (!fontCollection->fontsFound()) return;
const char* text = "給能上目秘使";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
paragraph_style.setMaxLines(14);
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Source Han Serif CN")});
text_style.setColor(SK_ColorBLACK);
text_style.setFontSize(26);
text_style.setWordSpacing(5);
text_style.setLetterSpacing(1);
text_style.setDecoration(TextDecoration::kUnderline);
text_style.setDecorationColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text, len);
PlaceholderStyle placeholder(55, 50, PlaceholderAlignment::kBaseline, TextBaseline::kIdeographic, 38.347f);
builder.addPlaceholder(placeholder);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth);
paragraph->paint(canvas.get(), 0, 0);
RectHeightStyle rect_height_style = RectHeightStyle::kTight;
RectWidthStyle rect_width_style = RectWidthStyle::kTight;
auto boxes = paragraph->getRectsForPlaceholders();
canvas.drawRects(SK_ColorRED, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 162.5f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 0, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 162.5f + 55, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 50, EPSILON100));
boxes = paragraph->getRectsForRange(5, 6, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorBLUE, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 135.5f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 4.703f, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 162.5f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 42.065f, EPSILON100));
}
UNIX_ONLY_TEST(SkParagraph_InlinePlaceholderBreakParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
TestCanvas canvas("SkParagraph_InlinePlaceholderBreakParagraph.png");
if (!fontCollection->fontsFound()) return;
const char* text = "012 34";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
paragraph_style.setMaxLines(14);
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setColor(SK_ColorBLACK);
text_style.setFontSize(26);
text_style.setWordSpacing(5);
text_style.setLetterSpacing(1);
text_style.setDecoration(TextDecoration::kUnderline);
text_style.setDecorationColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text, len);
PlaceholderStyle placeholder1(50, 50, PlaceholderAlignment::kBaseline, TextBaseline::kAlphabetic, 50);
PlaceholderStyle placeholder2(25, 25, PlaceholderAlignment::kBaseline, TextBaseline::kAlphabetic, 12.5f);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder2);
builder.addPlaceholder(placeholder1);
builder.addText(text, len);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder2); // 4 + 1
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder2); // 6 + 1
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder2); // 7 + 1
builder.addPlaceholder(placeholder1);
builder.addText(text, len);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder2);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addPlaceholder(placeholder2);
builder.addPlaceholder(placeholder1);
builder.addText(text, len);
builder.addPlaceholder(placeholder2);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth - 100);
paragraph->paint(canvas.get(), 0, 0);
RectHeightStyle rect_height_style = RectHeightStyle::kTight;
RectWidthStyle rect_width_style = RectWidthStyle::kTight;
auto boxes = paragraph->getRectsForRange(0, 3, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorRED, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
boxes = paragraph->getRectsForRange(175, 176, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorGREEN, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 31.695f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 218.531f, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 47.292f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 249, EPSILON100));
boxes = paragraph->getRectsForPlaceholders();
canvas.drawRects(SK_ColorRED, boxes);
boxes = paragraph->getRectsForRange(4, 45, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorBLUE, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 30);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 59.726f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 26.378f, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 90.921f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 56.847f, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[11].rect.left(), 606.343f, EPSILON20));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[11].rect.top(), 38, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[11].rect.right(), 631.343f, EPSILON20));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[11].rect.bottom(), 63, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[17].rect.left(), 0.5f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[17].rect.top(), 63.5f, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[17].rect.right(), 50.5f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[17].rect.bottom(), 113.5f, EPSILON100));
}
UNIX_ONLY_TEST(SkParagraph_InlinePlaceholderGetRectsParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
TestCanvas canvas("SkParagraph_InlinePlaceholderGetRectsParagraph.png");
if (!fontCollection->fontsFound()) return;
const char* text = "012 34";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
paragraph_style.setMaxLines(14);
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setColor(SK_ColorBLACK);
text_style.setFontSize(26);
text_style.setWordSpacing(5);
text_style.setLetterSpacing(1);
text_style.setDecoration(TextDecoration::kUnderline);
text_style.setDecorationColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text, len);
PlaceholderStyle placeholder1(50, 50, PlaceholderAlignment::kBaseline, TextBaseline::kAlphabetic, 50);
PlaceholderStyle placeholder2(5, 20, PlaceholderAlignment::kBaseline, TextBaseline::kAlphabetic, 10);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder2); // 8 + 1
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder2); // 5 + 1
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder1); // 8 + 0
builder.addText(text, len);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder2);
builder.addPlaceholder(placeholder2); // 1 + 2
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder2);
builder.addPlaceholder(placeholder2); // 1 + 2
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len);
builder.addText(text, len); // 11
builder.addPlaceholder(placeholder2);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder2);
builder.addPlaceholder(placeholder1);
builder.addPlaceholder(placeholder2);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth);
paragraph->paint(canvas.get(), 0, 0);
RectHeightStyle rect_height_style = RectHeightStyle::kMax;
RectWidthStyle rect_width_style = RectWidthStyle::kTight;
auto boxes = paragraph->getRectsForPlaceholders();
canvas.drawRects(SK_ColorRED, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 34);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 90.921f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 0, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 140.921f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 50, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[16].rect.left(), 800.921f, EPSILON20));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[16].rect.top(), 0, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[16].rect.right(), 850.921f, EPSILON20));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[16].rect.bottom(), 50, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[33].rect.left(), 503.382f, EPSILON10));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[33].rect.top(), 160, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[33].rect.right(), 508.382f, EPSILON10));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[33].rect.bottom(), 180, EPSILON100));
boxes = paragraph->getRectsForRange(30, 50, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorBLUE, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 8);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 216.097f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 60, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 290.921f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 120, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.left(), 290.921f, EPSILON20));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.top(), 60, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.right(), 340.921f, EPSILON20));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.bottom(), 120, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[2].rect.left(), 340.921f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[2].rect.top(), 60, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[2].rect.right(), 345.921f, EPSILON50));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[2].rect.bottom(), 120, EPSILON100));
}
UNIX_ONLY_TEST(SkParagraph_SimpleRedParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
if (!fontCollection->fontsFound()) return;
const char* text = "I am RED";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setColor(SK_ColorRED);
builder.pushStyle(text_style);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth);
REPORTER_ASSERT(reporter, paragraph->unresolvedGlyphs() == 0);
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
REPORTER_ASSERT(reporter, impl->styles().size() == 1); // paragraph style does not count
REPORTER_ASSERT(reporter, impl->styles()[0].fStyle.equals(text_style));
size_t index = 0;
for (auto& line : impl->lines()) {
line.scanStyles(StyleType::kDecorations,
[reporter, &index](TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
REPORTER_ASSERT(reporter, index == 0);
REPORTER_ASSERT(reporter, style.getColor() == SK_ColorRED);
++index;
return true;
});
}
}
// Checked: DIFF+ (Space between 1 & 2 style blocks)
UNIX_ONLY_TEST(SkParagraph_RainbowParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
TestCanvas canvas("SkParagraph_RainbowParagraph.png");
if (!fontCollection->fontsFound()) return;
const char* text1 = "Red Roboto"; // [0:10)
const char* text2 = "big Greeen Default"; // [10:28)
const char* text3 = "Defcolor Homemade Apple"; // [28:51)
const char* text4 = "Small Blue Roboto"; // [51:68)
const char* text41 = "Small Blue ";
const char* text5 =
"Continue Last Style With lots of words to check if it overlaps "
"properly or not"; // [68:)
const char* text42 =
"Roboto"
"Continue Last Style With lots of words to check if it overlaps "
"properly or not";
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
paragraph_style.setTextAlign(TextAlign::kLeft);
paragraph_style.setMaxLines(2);
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style1;
text_style1.setFontFamilies({SkString("Roboto")});
text_style1.setColor(SK_ColorRED);
builder.pushStyle(text_style1);
builder.addText(text1, strlen(text1));
TextStyle text_style2;
text_style2.setFontFamilies({SkString("Roboto")});
text_style2.setFontSize(50);
text_style2.setFontStyle(SkFontStyle(SkFontStyle::kMedium_Weight, SkFontStyle::kNormal_Width,
SkFontStyle::kUpright_Slant));
text_style2.setLetterSpacing(10);
text_style2.setDecorationColor(SK_ColorBLACK);
text_style2.setDecoration((TextDecoration)(
TextDecoration::kUnderline | TextDecoration::kOverline | TextDecoration::kLineThrough));
text_style2.setWordSpacing(30);
text_style2.setColor(SK_ColorGREEN);
builder.pushStyle(text_style2);
builder.addText(text2, strlen(text2));
TextStyle text_style3;
text_style3.setFontFamilies({SkString("Homemade Apple")});
text_style3.setColor(SK_ColorBLACK);
builder.pushStyle(text_style3);
builder.addText(text3, strlen(text3));
TextStyle text_style4;
text_style4.setFontFamilies({SkString("Roboto")});
text_style4.setFontSize(14);
text_style4.setDecorationColor(SK_ColorBLACK);
text_style4.setDecoration((TextDecoration)(
TextDecoration::kUnderline | TextDecoration::kOverline | TextDecoration::kLineThrough));
text_style4.setColor(SK_ColorBLUE);
builder.pushStyle(text_style4);
builder.addText(text4, strlen(text4));
builder.addText(text5, strlen(text5));
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(1000);
paragraph->paint(canvas.get(), 0, 0);
REPORTER_ASSERT(reporter, paragraph->unresolvedGlyphs() == 0);
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
REPORTER_ASSERT(reporter, impl->runs().size() == 4);
REPORTER_ASSERT(reporter, impl->styles().size() == 4);
REPORTER_ASSERT(reporter, impl->lines().size() == 2);
auto rects = paragraph->getRectsForRange(0, impl->text().size(), RectHeightStyle::kMax, RectWidthStyle::kTight);
canvas.drawRects(SK_ColorMAGENTA, rects);
size_t index = 0;
impl->lines()[0].scanStyles(
StyleType::kAllAttributes,
[&](TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
switch (index) {
case 0:
REPORTER_ASSERT(reporter, style.equals(text_style1));
REPORTER_ASSERT(reporter, equal(impl->text().begin(), textRange, text1));
break;
case 1:
REPORTER_ASSERT(reporter, style.equals(text_style2));
REPORTER_ASSERT(reporter, equal(impl->text().begin(), textRange, text2));
break;
case 2:
REPORTER_ASSERT(reporter, style.equals(text_style3));
REPORTER_ASSERT(reporter, equal(impl->text().begin(), textRange, text3));
break;
case 3:
REPORTER_ASSERT(reporter, style.equals(text_style4));
REPORTER_ASSERT(reporter, equal(impl->text().begin(), textRange, text41));
break;
default:
REPORTER_ASSERT(reporter, false);
break;
}
++index;
return true;
});
impl->lines()[1].scanStyles(
StyleType::kAllAttributes,
[&](TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
switch (index) {
case 4:
REPORTER_ASSERT(reporter, style.equals(text_style4));
REPORTER_ASSERT(reporter, equal(impl->text().begin(), textRange, text42));
break;
default:
REPORTER_ASSERT(reporter, false);
break;
}
++index;
return true;
});
REPORTER_ASSERT(reporter, index == 5);
}
UNIX_ONLY_TEST(SkParagraph_DefaultStyleParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
if (!fontCollection->fontsFound()) return;
TestCanvas canvas("SkParagraph_DefaultStyleParagraph.png");
const char* text = "No TextStyle! Uh Oh!";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
TextStyle defaultStyle;
defaultStyle.setFontFamilies({SkString("Roboto")});
paragraph_style.setTextStyle(defaultStyle);
paragraph_style.turnHintingOff();
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
builder.addText(text, len);
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth);
paragraph->paint(canvas.get(), 10.0, 15.0);
REPORTER_ASSERT(reporter, paragraph->unresolvedGlyphs() == 0);
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
REPORTER_ASSERT(reporter, impl->styles().size() == 1);
REPORTER_ASSERT(reporter, impl->lines().size() == 1);
size_t index = 0;
impl->lines()[0].scanStyles(
StyleType::kAllAttributes,
[&](TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
REPORTER_ASSERT(reporter, style.equals(paragraph_style.getTextStyle()));
REPORTER_ASSERT(reporter, equal(impl->text().begin(), textRange, text));
++index;
return true;
});
REPORTER_ASSERT(reporter, index == 1);
}
UNIX_ONLY_TEST(SkParagraph_BoldParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
if (!fontCollection->fontsFound()) return;
TestCanvas canvas("SkParagraph_BoldParagraph.png");
const char* text = "This is Red max bold text!";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setColor(SK_ColorRED);
text_style.setFontSize(60);
text_style.setLetterSpacing(0);
text_style.setFontStyle(SkFontStyle(SkFontStyle::kBlack_Weight, SkFontStyle::kNormal_Width,
SkFontStyle::kUpright_Slant));
builder.pushStyle(text_style);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(VeryLongCanvasWidth);
paragraph->paint(canvas.get(), 10.0, 60.0);
REPORTER_ASSERT(reporter, paragraph->unresolvedGlyphs() == 0);
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
REPORTER_ASSERT(reporter, impl->styles().size() == 1);
REPORTER_ASSERT(reporter, impl->lines().size() == 1);
size_t index = 0;
impl->lines()[0].scanStyles(
StyleType::kAllAttributes,
[&](TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
REPORTER_ASSERT(reporter, style.equals(text_style));
REPORTER_ASSERT(reporter, equal(impl->text().begin(), textRange, text));
++index;
return true;
});
REPORTER_ASSERT(reporter, index == 1);
}
UNIX_ONLY_TEST(SkParagraph_HeightOverrideParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
if (!fontCollection->fontsFound()) return;
TestCanvas canvas("SkParagraph_HeightOverrideParagraph.png");
const char* text = "01234満毎冠行来昼本可\nabcd\n満毎冠行来昼本可";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
paragraph_style.setMaxLines(10);
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setFontSize(20);
text_style.setColor(SK_ColorBLACK);
text_style.setHeight(3.6345f);
text_style.setHeightOverride(true);
builder.pushStyle(text_style);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(550);
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
REPORTER_ASSERT(reporter, impl->runs().size() == 5);
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);
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
paint.setAntiAlias(true);
paint.setStrokeWidth(1);
// Tests for GetRectsForRange()
RectHeightStyle rect_height_style = RectHeightStyle::kIncludeLineSpacingMiddle;
RectWidthStyle rect_width_style = RectWidthStyle::kTight;
paint.setColor(SK_ColorRED);
std::vector<TextBox> boxes = paragraph->getRectsForRange(0, 0, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorRED, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 0ull);
boxes = paragraph->getRectsForRange(0, 40, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorBLUE, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 3ull);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.left(), 0, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.top(), 92.805f, EPSILON5));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.right(), 43.843f, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[1].rect.bottom(), 165.495f, EPSILON5));
}
UNIX_ONLY_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);
TestParagraphBuilderImpl 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));
}
UNIX_ONLY_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);
TestParagraphBuilderImpl 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));
}
UNIX_ONLY_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);
TestParagraphBuilderImpl 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));
}
UNIX_ONLY_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);
TestParagraphBuilderImpl 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));
}
UNIX_ONLY_TEST(SkParagraph_LeftAlignParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
if (!fontCollection->fontsFound()) return;
TestCanvas canvas("SkParagraph_LeftAlignParagraph.png");
const char* text =
"This is a very long sentence to test if the text will properly wrap "
"around and go to the next line. Sometimes, short sentence. Longer "
"sentences are okay too because they are nessecary. Very short. "
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
"mollit anim id est laborum. "
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
"mollit anim id est laborum.";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.setMaxLines(14);
paragraph_style.setTextAlign(TextAlign::kLeft);
paragraph_style.turnHintingOff();
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setFontSize(26);
text_style.setLetterSpacing(1);
text_style.setWordSpacing(5);
text_style.setColor(SK_ColorBLACK);
text_style.setHeight(1);
text_style.setDecoration(TextDecoration::kUnderline);
text_style.setDecorationColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth - 100);
paragraph->paint(canvas.get(), 0, 0);
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
REPORTER_ASSERT(reporter, impl->text().size() == std::string{text}.length());
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
REPORTER_ASSERT(reporter, impl->styles().size() == 1);
REPORTER_ASSERT(reporter, impl->styles()[0].fStyle.equals(text_style));
REPORTER_ASSERT(reporter, impl->lines().size() == paragraph_style.getMaxLines());
double expected_y = 0;
double epsilon = 0.01f;
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(impl->lines()[0].baseline(), 24.121f, epsilon));
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[0].offset().fY, expected_y, epsilon));
expected_y += 30;
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[1].offset().fY, expected_y, epsilon));
expected_y += 30;
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[2].offset().fY, expected_y, epsilon));
expected_y += 30;
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[3].offset().fY, expected_y, epsilon));
expected_y += 30 * 10;
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[13].offset().fY, expected_y, epsilon));
REPORTER_ASSERT(reporter,
paragraph_style.getTextAlign() == impl->paragraphStyle().getTextAlign());
// Tests for GetGlyphPositionAtCoordinate()
REPORTER_ASSERT(reporter, impl->getGlyphPositionAtCoordinate(0, 0).position == 0);
REPORTER_ASSERT(reporter, impl->getGlyphPositionAtCoordinate(1, 1).position == 0);
REPORTER_ASSERT(reporter, impl->getGlyphPositionAtCoordinate(1, 35).position == 68);
REPORTER_ASSERT(reporter, impl->getGlyphPositionAtCoordinate(1, 70).position == 134);
REPORTER_ASSERT(reporter, impl->getGlyphPositionAtCoordinate(2000, 35).position == 134);
}
UNIX_ONLY_TEST(SkParagraph_RightAlignParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
if (!fontCollection->fontsFound()) return;
TestCanvas canvas("SkParagraph_RightAlignParagraph.png");
const char* text =
"This is a very long sentence to test if the text will properly wrap "
"around and go to the next line. Sometimes, short sentence. Longer "
"sentences are okay too because they are nessecary. Very short. "
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
"mollit anim id est laborum. "
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
"mollit anim id est laborum.";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.setMaxLines(14);
paragraph_style.setTextAlign(TextAlign::kRight);
paragraph_style.turnHintingOff();
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setFontSize(26);
text_style.setLetterSpacing(1);
text_style.setWordSpacing(5);
text_style.setColor(SK_ColorBLACK);
text_style.setHeight(1);
text_style.setDecoration(TextDecoration::kUnderline);
text_style.setDecorationColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth - 100);
paragraph->paint(canvas.get(), 0, 0);
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
REPORTER_ASSERT(reporter, impl->styles().size() == 1);
REPORTER_ASSERT(reporter, impl->styles()[0].fStyle.equals(text_style));
REPORTER_ASSERT(reporter, impl->lines().size() == paragraph_style.getMaxLines());
double expected_y = 0;
double epsilon = 0.01f;
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(impl->lines()[0].baseline(), 24.121f, epsilon));
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[0].offset().fY, expected_y, epsilon));
expected_y += 30;
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[1].offset().fY, expected_y, epsilon));
expected_y += 30;
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[2].offset().fY, expected_y, epsilon));
expected_y += 30;
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[3].offset().fY, expected_y, epsilon));
expected_y += 30 * 10;
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[13].offset().fY, expected_y, epsilon));
auto calculate = [](const TextLine& line) -> SkScalar {
return TestCanvasWidth - 100 - line.offset().fX - line.width();
};
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(calculate(impl->lines()[0]), 0, epsilon));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(calculate(impl->lines()[1]), 0, epsilon));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(calculate(impl->lines()[2]), 0, epsilon));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(calculate(impl->lines()[3]), 0, epsilon));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(calculate(impl->lines()[13]), 0, epsilon));
REPORTER_ASSERT(reporter,
paragraph_style.getTextAlign() == impl->paragraphStyle().getTextAlign());
}
UNIX_ONLY_TEST(SkParagraph_CenterAlignParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
if (!fontCollection->fontsFound()) return;
TestCanvas canvas("SkParagraph_CenterAlignParagraph.png");
const char* text =
"This is a very long sentence to test if the text will properly wrap "
"around and go to the next line. Sometimes, short sentence. Longer "
"sentences are okay too because they are nessecary. Very short. "
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
"mollit anim id est laborum. "
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
"mollit anim id est laborum.";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.setMaxLines(14);
paragraph_style.setTextAlign(TextAlign::kCenter);
paragraph_style.turnHintingOff();
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setFontSize(26);
text_style.setLetterSpacing(1);
text_style.setWordSpacing(5);
text_style.setColor(SK_ColorBLACK);
text_style.setHeight(1);
text_style.setDecoration(TextDecoration::kUnderline);
text_style.setDecorationColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth - 100);
paragraph->paint(canvas.get(), 0, 0);
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
REPORTER_ASSERT(reporter, impl->text().size() == std::string{text}.length());
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
REPORTER_ASSERT(reporter, impl->styles().size() == 1);
REPORTER_ASSERT(reporter, impl->styles()[0].fStyle.equals(text_style));
REPORTER_ASSERT(reporter, impl->lines().size() == paragraph_style.getMaxLines());
double expected_y = 0;
double epsilon = 0.01f;
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(impl->lines()[0].baseline(), 24.121f, epsilon));
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[0].offset().fY, expected_y, epsilon));
expected_y += 30;
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[1].offset().fY, expected_y, epsilon));
expected_y += 30;
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[2].offset().fY, expected_y, epsilon));
expected_y += 30;
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[3].offset().fY, expected_y, epsilon));
expected_y += 30 * 10;
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[13].offset().fY, expected_y, epsilon));
auto calculate = [](const TextLine& line) -> SkScalar {
return TestCanvasWidth - 100 - (line.offset().fX * 2 + line.width());
};
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(calculate(impl->lines()[0]), 0, epsilon));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(calculate(impl->lines()[1]), 0, epsilon));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(calculate(impl->lines()[2]), 0, epsilon));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(calculate(impl->lines()[3]), 0, epsilon));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(calculate(impl->lines()[13]), 0, epsilon));
REPORTER_ASSERT(reporter,
paragraph_style.getTextAlign() == impl->paragraphStyle().getTextAlign());
}
UNIX_ONLY_TEST(SkParagraph_JustifyAlignParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
if (!fontCollection->fontsFound()) return;
TestCanvas canvas("SkParagraph_JustifyAlignParagraph.png");
const char* text =
"This is a very long sentence to test if the text will properly wrap "
"around and go to the next line. Sometimes, short sentence. Longer "
"sentences are okay too because they are nessecary. Very short. "
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
"mollit anim id est laborum. "
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
"velit esse cillum dolore eu fugiat.";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.setMaxLines(14);
paragraph_style.setTextAlign(TextAlign::kJustify);
paragraph_style.turnHintingOff();
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setFontSize(26);
text_style.setLetterSpacing(0);
text_style.setWordSpacing(5);
text_style.setColor(SK_ColorBLACK);
text_style.setHeight(1);
text_style.setDecoration(TextDecoration::kUnderline);
text_style.setDecorationColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth - 100);
paragraph->paint(canvas.get(), 0, 0);
RectHeightStyle rect_height_style = RectHeightStyle::kMax;
RectWidthStyle rect_width_style = RectWidthStyle::kTight;
auto boxes = paragraph->getRectsForRange(0, 100, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorRED, boxes);
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
REPORTER_ASSERT(reporter, impl->text().size() == std::string{text}.length());
REPORTER_ASSERT(reporter, impl->runs().size() == 1);
REPORTER_ASSERT(reporter, impl->styles().size() == 1);
REPORTER_ASSERT(reporter, impl->styles()[0].fStyle.equals(text_style));
double expected_y = 0;
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(impl->lines()[0].baseline(), 24.121f, EPSILON100));
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[0].offset().fY, expected_y, EPSILON100));
expected_y += 30;
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[1].offset().fY, expected_y, EPSILON100));
expected_y += 30;
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[2].offset().fY, expected_y, EPSILON100));
expected_y += 30;
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[3].offset().fY, expected_y, EPSILON100));
expected_y += 30 * 9;
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(impl->lines()[12].offset().fY, expected_y, EPSILON100));
auto calculate = [](const TextLine& line) -> SkScalar {
return line.offset().fX;
};
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(calculate(impl->lines()[0]), 0, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(calculate(impl->lines()[1]), 0, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(calculate(impl->lines()[2]), 0, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(calculate(impl->lines()[3]), 0, EPSILON100));
REPORTER_ASSERT(reporter,
paragraph_style.getTextAlign() == impl->paragraphStyle().getTextAlign());
}
// Checked: DIFF (ghost spaces as a separate box in TxtLib)
UNIX_ONLY_TEST(SkParagraph_JustifyRTL, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>(true);
if (!fontCollection->fontsFound()) return;
TestCanvas canvas("SkParagraph_JustifyRTL.png");
const char* text =
"אאא בּבּבּבּ אאאא בּבּ אאא בּבּבּ אאאאא בּבּבּבּ אאאא בּבּבּבּבּ "
"אאאאא בּבּבּבּבּ אאאבּבּבּבּבּבּאאאאא בּבּבּבּבּבּאאאאאבּבּבּבּבּבּ אאאאא בּבּבּבּבּ "
"אאאאא בּבּבּבּבּבּ אאאאא בּבּבּבּבּבּ אאאאא בּבּבּבּבּבּ אאאאא בּבּבּבּבּבּ אאאאא בּבּבּבּבּבּ";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.setMaxLines(14);
paragraph_style.setTextAlign(TextAlign::kJustify);
paragraph_style.setTextDirection(TextDirection::kRtl);
paragraph_style.turnHintingOff();
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Ahem")});
text_style.setFontSize(26);
text_style.setColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth - 100);
paragraph->paint(canvas.get(), 0, 0);
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
auto calculate = [](const TextLine& line) -> SkScalar {
return TestCanvasWidth - 100 - line.width();
};
for (auto& line : impl->lines()) {
if (&line == &impl->lines().back()) {
REPORTER_ASSERT(reporter, calculate(line) > EPSILON100);
} else {
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(calculate(line), 0, EPSILON100));
}
}
// Just make sure the the text is actually RTL
for (auto& run : impl->runs()) {
REPORTER_ASSERT(reporter, !run.leftToRight());
}
// Tests for GetRectsForRange()
RectHeightStyle rect_height_style = RectHeightStyle::kMax;
RectWidthStyle rect_width_style = RectWidthStyle::kTight;
auto boxes = paragraph->getRectsForRange(0, 100, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorRED, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 3);
boxes = paragraph->getRectsForRange(240, 250, rect_height_style, rect_width_style);
canvas.drawRects(SK_ColorBLUE, boxes);
REPORTER_ASSERT(reporter, boxes.size() == 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 588, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 130, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 640, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 156, EPSILON100));
}
UNIX_ONLY_TEST(SkParagraph_JustifyRTLNewLine, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>(true);
if (!fontCollection->fontsFound()) return;
TestCanvas canvas("SkParagraph_JustifyRTLNewLine.png");
const char* text =
"אאא בּבּבּבּ אאאא\nבּבּ אאא בּבּבּ אאאאא בּבּבּבּ אאאא בּבּבּבּבּ "
"אאאאא בּבּבּבּבּ אאאבּבּבּבּבּבּאאאאא בּבּבּבּבּבּאאאאאבּבּבּבּבּבּ אאאאא בּבּבּבּבּ "
"אאאאא בּבּבּבּבּבּ אאאאא בּבּבּבּבּבּ אאאאא בּבּבּבּבּבּ אאאאא בּבּבּבּבּבּ אאאאא בּבּבּבּבּבּ";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.setMaxLines(14);
paragraph_style.setTextAlign(TextAlign::kJustify);
paragraph_style.setTextDirection(TextDirection::kRtl);
paragraph_style.turnHintingOff();
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Ahem")});
text_style.setFontSize(26);
text_style.setColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth - 100);
paragraph->paint(canvas.get(), 0, 0);
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
paint.setAntiAlias(true);
paint.setStrokeWidth(1);
// Tests for GetRectsForRange()
RectHeightStyle rect_height_style = RectHeightStyle::kMax;
RectWidthStyle rect_width_style = RectWidthStyle::kTight;
paint.setColor(SK_ColorRED);
auto boxes = paragraph->getRectsForRange(0, 30, rect_height_style, rect_width_style);
for (size_t i = 0; i < boxes.size(); ++i) {
canvas.get()->drawRect(boxes[i].rect, paint);
}
REPORTER_ASSERT(reporter, boxes.size() == 2ull);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 562, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 0, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 900, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 26, EPSILON100));
paint.setColor(SK_ColorBLUE);
boxes = paragraph->getRectsForRange(240, 250, rect_height_style, rect_width_style);
for (size_t i = 0; i < boxes.size(); ++i) {
canvas.get()->drawRect(boxes[i].rect, paint);
}
REPORTER_ASSERT(reporter, boxes.size() == 1ull);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.left(), 68, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.top(), 130, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.right(), 120, EPSILON100));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(boxes[0].rect.bottom(), 156, EPSILON100));
// All lines should be justified to the width of the paragraph
// except for #0 (new line) and #5 (the last one)
for (auto& line : impl->lines()) {
ptrdiff_t num = &line - impl->lines().data();
if (num == 0 || num == 5) {
REPORTER_ASSERT(reporter, line.width() < TestCanvasWidth - 100);
} else {
REPORTER_ASSERT(reporter,
SkScalarNearlyEqual(line.width(), TestCanvasWidth - 100, EPSILON100),
"#%zd: %f <= %d\n", num, line.width(), TestCanvasWidth - 100);
}
}
}
UNIX_ONLY_TEST(SkParagraph_LeadingSpaceRTL, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>(true);
if (!fontCollection->fontsFound()) return;
TestCanvas canvas("SkParagraph_LeadingSpaceRTL.png");
const char* text = " leading space";
const size_t len = strlen(text);
ParagraphStyle paragraph_style;
paragraph_style.setMaxLines(14);
paragraph_style.setTextAlign(TextAlign::kJustify);
paragraph_style.setTextDirection(TextDirection::kRtl);
paragraph_style.turnHintingOff();
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Ahem")});
text_style.setFontSize(26);
text_style.setColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text, len);
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth - 100);
paragraph->paint(canvas.get(), 0, 0);
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
paint.setAntiAlias(true);
paint.setStrokeWidth(1);
// Tests for GetRectsForRange()
RectHeightStyle rect_height_style = RectHeightStyle::kMax;
RectWidthStyle rect_width_style = RectWidthStyle::kTight;
paint.setColor(SK_ColorRED);
auto boxes = paragraph->getRectsForRange(0, 100, rect_height_style, rect_width_style);
for (size_t i = 0; i < boxes.size(); ++i) {
canvas.get()->drawRect(boxes[i].rect, paint);
}
REPORTER_ASSERT(reporter, boxes.size() == 2ull);
}
UNIX_ONLY_TEST(SkParagraph_DecorationsParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
if (!fontCollection->fontsFound()) return;
TestCanvas canvas("SkParagraph_DecorationsParagraph.png");
const char* text1 = "This text should be";
const char* text2 = " decorated even when";
const char* text3 = " wrapped around to";
const char* text4 = " the next line.";
const char* text5 = " Otherwise, bad things happen.";
ParagraphStyle paragraph_style;
paragraph_style.setMaxLines(14);
paragraph_style.setTextAlign(TextAlign::kLeft);
paragraph_style.turnHintingOff();
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setFontSize(26);
text_style.setLetterSpacing(0);
text_style.setWordSpacing(5);
text_style.setColor(SK_ColorBLACK);
text_style.setHeight(2);
text_style.setDecoration(TextDecoration::kUnderline);
text_style.setDecorationColor(SK_ColorBLACK);
text_style.setDecoration((TextDecoration)(
TextDecoration::kUnderline | TextDecoration::kOverline | TextDecoration::kLineThrough));
text_style.setDecorationStyle(TextDecorationStyle::kSolid);
text_style.setDecorationColor(SK_ColorBLACK);
text_style.setDecorationThicknessMultiplier(2.0);
builder.pushStyle(text_style);
builder.addText(text1, strlen(text1));
text_style.setDecorationStyle(TextDecorationStyle::kDouble);
text_style.setDecorationColor(SK_ColorBLUE);
text_style.setDecorationThicknessMultiplier(1.0);
builder.pushStyle(text_style);
builder.addText(text2, strlen(text2));
text_style.setDecorationStyle(TextDecorationStyle::kDotted);
text_style.setDecorationColor(SK_ColorBLACK);
builder.pushStyle(text_style);
builder.addText(text3, strlen(text3));
text_style.setDecorationStyle(TextDecorationStyle::kDashed);
text_style.setDecorationColor(SK_ColorBLACK);
text_style.setDecorationThicknessMultiplier(3.0);
builder.pushStyle(text_style);
builder.addText(text4, strlen(text4));
text_style.setDecorationStyle(TextDecorationStyle::kWavy);
text_style.setDecorationColor(SK_ColorRED);
text_style.setDecorationThicknessMultiplier(1.0);
builder.pushStyle(text_style);
builder.addText(text5, strlen(text5));
builder.pop();
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth - 100);
paragraph->paint(canvas.get(), 0, 0);
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
size_t index = 0;
for (auto& line : impl->lines()) {
line.scanStyles(
StyleType::kDecorations,
[&](TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context) {
auto decoration = (TextDecoration)(TextDecoration::kUnderline |
TextDecoration::kOverline |
TextDecoration::kLineThrough);
REPORTER_ASSERT(reporter, style.getDecorationType() == decoration);
switch (index) {
case 0:
REPORTER_ASSERT(reporter, style.getDecorationStyle() ==
TextDecorationStyle::kSolid);
REPORTER_ASSERT(reporter, style.getDecorationColor() == SK_ColorBLACK);
REPORTER_ASSERT(reporter,
style.getDecorationThicknessMultiplier() == 2.0);
break;
case 1: // The style appears on 2 lines so it has 2 pieces
REPORTER_ASSERT(reporter, style.getDecorationStyle() ==
TextDecorationStyle::kDouble);
REPORTER_ASSERT(reporter, style.getDecorationColor() == SK_ColorBLUE);
REPORTER_ASSERT(reporter,
style.getDecorationThicknessMultiplier() == 1.0);
break;
case 2:
REPORTER_ASSERT(reporter, style.getDecorationStyle() ==
TextDecorationStyle::kDotted);
REPORTER_ASSERT(reporter, style.getDecorationColor() == SK_ColorBLACK);
REPORTER_ASSERT(reporter,
style.getDecorationThicknessMultiplier() == 1.0);
break;
case 3:
case 4:
REPORTER_ASSERT(reporter, style.getDecorationStyle() ==
TextDecorationStyle::kDashed);
REPORTER_ASSERT(reporter, style.getDecorationColor() == SK_ColorBLACK);
REPORTER_ASSERT(reporter,
style.getDecorationThicknessMultiplier() == 3.0);
break;
case 5:
REPORTER_ASSERT(reporter, style.getDecorationStyle() ==
TextDecorationStyle::kWavy);
REPORTER_ASSERT(reporter, style.getDecorationColor() == SK_ColorRED);
REPORTER_ASSERT(reporter,
style.getDecorationThicknessMultiplier() == 1.0);
break;
default:
REPORTER_ASSERT(reporter, false);
break;
}
++index;
return true;
});
}
}
// TODO: Add test for wavy decorations.
UNIX_ONLY_TEST(SkParagraph_ItalicsParagraph, reporter) {
sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
if (!fontCollection->fontsFound()) return;
TestCanvas canvas("SkParagraph_ItalicsParagraph.png");
const char* text1 = "No italic ";
const char* text2 = "Yes Italic ";
const char* text3 = "No Italic again.";
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
TestParagraphBuilderImpl builder(paragraph_style, fontCollection);
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setFontSize(10);
text_style.setColor(SK_ColorRED);
builder.pushStyle(text_style);
builder.addText(text1, strlen(text1));
text_style.setFontStyle(SkFontStyle(SkFontStyle::kNormal_Weight, SkFontStyle::kNormal_Width,
SkFontStyle::kItalic_Slant));
builder.pushStyle(text_style);
builder.addText(text2, strlen(text2));
builder.pop();
builder.addText(text3, strlen(text3));
auto paragraph = builder.Build();
paragraph->layout(TestCanvasWidth);