1 // Copyright 2021 Google LLC.
2 #include "include/core/SkBitmap.h"
3 #include "include/core/SkCanvas.h"
4 #include "include/core/SkColor.h"
5 #include "include/core/SkEncodedImageFormat.h"
6 #include "include/core/SkFontMgr.h"
7 #include "include/core/SkFontStyle.h"
8 #include "include/core/SkImageEncoder.h"
9 #include "include/core/SkPaint.h"
10 #include "include/core/SkPoint.h"
11 #include "include/core/SkRect.h"
12 #include "include/core/SkRefCnt.h"
13 #include "include/core/SkScalar.h"
14 #include "include/core/SkSpan.h"
15 #include "include/core/SkStream.h"
16 #include "include/core/SkString.h"
17 #include "include/core/SkTypeface.h"
18 #include "include/core/SkTypes.h"
19 #include "tests/Test.h"
20 #include "tools/Resources.h"
21
22 #include "experimental/sktext/include/Text.h"
23 #include "experimental/sktext/src/Paint.h"
24
25 #include <string.h>
26 #include <algorithm>
27 #include <limits>
28 #include <memory>
29 #include <string>
30 #include <utility>
31 #include <vector>
32
33 struct GrContextOptions;
34
35 #define VeryLongCanvasWidth 1000000
36 #define TestCanvasWidth 1000
37 #define TestCanvasHeight 600
38
39 using namespace skia::text;
40
41 struct TestLine {
42 size_t index;
43 TextRange lineText;
44 bool hardBreak;
45 SkRect bounds;
46 GlyphRange trailingSpaces;
47 Range<RunIndex> runRange;
48 };
49
50 struct TestRun {
51 const SkFont& font;
52 DirTextRange dirTextRange; // Currently we make sure that the run edges are the grapheme cluster edges
53 SkRect bounds; // bounds contains the physical boundaries of the run
54 size_t trailingSpaces; // Depending of TextDirection it goes right to the end (LTR) or left to the start (RTL)
55 SkSpan<const uint16_t> glyphs;
56 SkSpan<const SkPoint> positions;
57 SkSpan<const TextIndex> clusters;
58 };
59
60 class TestVisitor : public Visitor {
61 public:
onBeginLine(size_t index,TextRange lineText,bool hardBreak,SkRect bounds)62 void onBeginLine(size_t index, TextRange lineText, bool hardBreak, SkRect bounds) override {
63 SkASSERT(fTestLines.size() == index);
64 fTestLines.push_back({ index, lineText, hardBreak, bounds, EMPTY_RANGE, Range<RunIndex>(fTestRuns.size(), fTestRuns.size()) });
65 }
onEndLine(size_t index,TextRange lineText,GlyphRange trailingSpaces,size_t glyphCount)66 void onEndLine(size_t index, TextRange lineText, GlyphRange trailingSpaces, size_t glyphCount) override {
67 SkASSERT(fTestLines.size() == index + 1);
68 fTestLines.back().trailingSpaces = trailingSpaces;
69 fTestLines.back().runRange.fEnd = fTestRuns.size();
70 }
onGlyphRun(const SkFont & font,DirTextRange dirTextRange,SkRect bounds,TextIndex trailingSpaces,size_t glyphCount,const uint16_t glyphs[],const SkPoint positions[],const TextIndex clusters[])71 void onGlyphRun(const SkFont& font,
72 DirTextRange dirTextRange,
73 SkRect bounds,
74 TextIndex trailingSpaces,
75 size_t glyphCount, // Just the number of glyphs
76 const uint16_t glyphs[],
77 const SkPoint positions[], // Positions relative to the line
78 const TextIndex clusters[]) override
79 {
80 fTestRuns.push_back({font, dirTextRange, bounds, trailingSpaces,
81 SkSpan<const uint16_t>(&glyphs[0], glyphCount),
82 SkSpan<const SkPoint>(&positions[0], glyphCount + 1),
83 SkSpan<const TextIndex>(&clusters[0], glyphCount + 1),
84 });
85 }
onPlaceholder(TextRange,const SkRect & bounds)86 void onPlaceholder(TextRange, const SkRect& bounds) override { }
87
88 std::vector<TestLine> fTestLines;
89 std::vector<TestRun> fTestRuns;
90 };
91
UNIX_ONLY_TEST(SkText_WrappedText_Spaces,reporter)92 UNIX_ONLY_TEST(SkText_WrappedText_Spaces, reporter) {
93 sk_sp<TrivialFontChain> fontChain = sk_make_sp<TrivialFontChain>("Roboto", 40.0f, SkFontStyle::Normal());
94 if (fontChain->empty()) return;
95
96 std::u16string utf16(u" Leading spaces\nTrailing spaces \nLong text with collapsed spaces inside wrapped into few lines");
97 UnicodeText unicodeText(SkUnicode::Make(), SkSpan<uint16_t>((uint16_t*)utf16.data(), utf16.size()));
98 if (!unicodeText.getUnicode()) return;
99
100 FontBlock fontBlock(utf16.size(), fontChain);
101 auto fontResolvedText = unicodeText.resolveFonts(SkSpan<FontBlock>(&fontBlock, 1));
102 auto shapedText = fontResolvedText->shape(&unicodeText, TextDirection::kLtr);
103 auto wrappedText = shapedText->wrap(&unicodeText, 440.0f, 500.0f);
104
105 TestVisitor testVisitor;
106 wrappedText->visit(&testVisitor);
107
108 REPORTER_ASSERT(reporter, testVisitor.fTestLines.size() == 5);
109 REPORTER_ASSERT(reporter, testVisitor.fTestRuns.size() == 5);
110
111 REPORTER_ASSERT(reporter, testVisitor.fTestLines[0].trailingSpaces.width() == 0);
112 REPORTER_ASSERT(reporter, testVisitor.fTestLines[1].trailingSpaces.width() == 4);
113 REPORTER_ASSERT(reporter, testVisitor.fTestLines[2].trailingSpaces.width() == 6);
114 REPORTER_ASSERT(reporter, testVisitor.fTestLines[3].trailingSpaces.width() == 1);
115 REPORTER_ASSERT(reporter, testVisitor.fTestLines[4].trailingSpaces.width() == 0);
116
117 auto break1 = utf16.find_first_of(u"\n");
118 auto break2 = utf16.find_last_of(u"\n");
119
120 REPORTER_ASSERT(reporter, testVisitor.fTestLines[0].lineText.width() == break1);
121 REPORTER_ASSERT(reporter, testVisitor.fTestLines[1].lineText.width() == break2 - break1 - 1);
122
123 RunIndex runIndex = 0;
124 SkScalar verticalOffset = 0.0f;
125 for (int lineIndex = 0; lineIndex < testVisitor.fTestLines.size(); ++lineIndex) {
126 auto& line = testVisitor.fTestLines[lineIndex];
127 REPORTER_ASSERT(reporter, line.runRange == Range<RunIndex>(runIndex, runIndex + 1));
128 REPORTER_ASSERT(reporter, line.runRange.width() == 1);
129 auto& run = testVisitor.fTestRuns[runIndex];
130 REPORTER_ASSERT(reporter, line.lineText == run.dirTextRange);
131 REPORTER_ASSERT(reporter, runIndex <= 1 ? line.hardBreak : !line.hardBreak);
132 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(verticalOffset, line.bounds.fTop));
133
134 // There is only one line that is wrapped and it has enough trailing spaces to exceed the line width
135 REPORTER_ASSERT(reporter, (line.index == 2 ? line.bounds.width() > 440.0f: line.bounds.width() < 440.0f));
136 verticalOffset = line.bounds.fBottom;
137 ++runIndex;
138 }
139 }
140
UNIX_ONLY_TEST(SkText_WrappedText_LongRTL,reporter)141 UNIX_ONLY_TEST(SkText_WrappedText_LongRTL, reporter) {
142 sk_sp<TrivialFontChain> fontChain = sk_make_sp<TrivialFontChain>("Noto Naskh Arabic", 40.0f, SkFontStyle::Normal());
143 if (fontChain->empty()) return;
144
145 std::u16string utf16(u"يَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُيَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُ يَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُ يَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُ يَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُيَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُ يَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُ يَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُ");
146 UnicodeText unicodeText(SkUnicode::Make(), SkSpan<uint16_t>((uint16_t*)utf16.data(), utf16.size()));
147 if (!unicodeText.getUnicode()) return;
148
149 FontBlock fontBlock(utf16.size(), fontChain);
150 auto fontResolvedText = unicodeText.resolveFonts(SkSpan<FontBlock>(&fontBlock, 1));
151 auto shapedText = fontResolvedText->shape(&unicodeText, TextDirection::kLtr);
152 auto wrappedText = shapedText->wrap(&unicodeText, 800.0f, 800.0f);
153
154 TestVisitor testVisitor;
155 wrappedText->visit(&testVisitor);
156
157 REPORTER_ASSERT(reporter, testVisitor.fTestLines.size() == 4);
158 REPORTER_ASSERT(reporter, testVisitor.fTestRuns.size() == 4);
159
160 REPORTER_ASSERT(reporter, testVisitor.fTestLines[0].trailingSpaces.width() == 1);
161 REPORTER_ASSERT(reporter, testVisitor.fTestLines[1].trailingSpaces.width() == 1);
162 REPORTER_ASSERT(reporter, testVisitor.fTestLines[2].trailingSpaces.width() == 1);
163 REPORTER_ASSERT(reporter, testVisitor.fTestLines[3].trailingSpaces.width() == 0);
164 }
165
166