1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <gtest/gtest.h>
18
19 #include "FontTestUtils.h"
20 #include "UnicodeUtils.h"
21 #include "minikin/FontCollection.h"
22 #include "minikin/Layout.h"
23 #include "minikin/LayoutPieces.h"
24 #include "minikin/Measurement.h"
25
26 namespace minikin {
27
28 namespace {
29 constexpr bool LTR = false;
30 constexpr bool RTL = true;
31 } // namespace
32
33 class LayoutLetterSpacingTest : public testing::Test {
34 protected:
LayoutLetterSpacingTest()35 LayoutLetterSpacingTest() {}
~LayoutLetterSpacingTest()36 virtual ~LayoutLetterSpacingTest() {}
SetUp()37 virtual void SetUp() override {}
TearDown()38 virtual void TearDown() override {}
39
LayoutTest(std::initializer_list<float> expect_advances_args,std::initializer_list<float> expect_glyph_offsets_args,const std::string & utf8,bool isRtl,RunFlag runFlag)40 void LayoutTest(std::initializer_list<float> expect_advances_args,
41 std::initializer_list<float> expect_glyph_offsets_args, const std::string& utf8,
42 bool isRtl, RunFlag runFlag) {
43 ASSERT_TRUE(mPaint) << "Setup error: must call setShapeParam for setting up the test";
44
45 // Prepare parameters
46 std::vector<float> expect_advances(expect_advances_args.begin(),
47 expect_advances_args.end());
48 std::vector<float> expect_glyph_offsets(expect_glyph_offsets_args.begin(),
49 expect_glyph_offsets_args.end());
50 std::vector<uint16_t> text = utf8ToUtf16(utf8);
51 Range range(0, text.size());
52 Bidi bidiFlag = isRtl ? Bidi::RTL : Bidi::LTR;
53
54 // Compute widths and layout
55 std::vector<float> advances(text.size());
56 float width = Layout::measureText(text, range, bidiFlag, *mPaint, StartHyphenEdit::NO_EDIT,
57 EndHyphenEdit::NO_EDIT, advances.data(), nullptr, nullptr,
58 runFlag);
59 // Calculate width without per character widths.
60 float widthWithoutChars =
61 Layout::measureText(text, range, bidiFlag, *mPaint, StartHyphenEdit::NO_EDIT,
62 EndHyphenEdit::NO_EDIT, nullptr, nullptr, nullptr, runFlag);
63 Layout layout(text, range, bidiFlag, *mPaint, StartHyphenEdit::NO_EDIT,
64 EndHyphenEdit::NO_EDIT, runFlag);
65
66 SCOPED_TRACE(::testing::Message()
67 << "text=" << utf8 << ", flag=" << static_cast<int>(runFlag)
68 << ", rtl=" << isRtl << ", layout = " << layout);
69 // First, Layout creation and measureText should have the same output
70 EXPECT_EQ(width, widthWithoutChars);
71 EXPECT_EQ(advances, layout.getAdvances());
72 EXPECT_EQ(width, layout.getAdvance());
73
74 // Verify the advances
75 EXPECT_EQ(expect_advances, advances);
76 float total_advance = 0;
77 for (uint32_t i = 0; i < expect_advances.size(); ++i) {
78 total_advance += advances[i];
79 }
80 EXPECT_EQ(total_advance, width);
81
82 // Verify Glyph offset
83 EXPECT_EQ(expect_glyph_offsets.size(), layout.nGlyphs());
84 std::vector<float> actual_glyph_offsets;
85 for (uint32_t i = 0; i < expect_glyph_offsets.size(); ++i) {
86 actual_glyph_offsets.push_back(layout.getX(i));
87 }
88 EXPECT_EQ(expect_glyph_offsets, actual_glyph_offsets);
89 }
90
setShapeParam(const std::string & font,float letterSpacing)91 void setShapeParam(const std::string& font, float letterSpacing) {
92 auto fc = buildFontCollection(font);
93 mPaint = std::make_unique<MinikinPaint>(fc);
94
95 mPaint->letterSpacing = letterSpacing;
96 mPaint->size = 10.0f;
97 mPaint->scaleX = 1.0f;
98 }
99
100 private:
101 std::unique_ptr<MinikinPaint> mPaint;
102 };
103
TEST_F(LayoutLetterSpacingTest,measuredTextTest_LetterSpacing)104 TEST_F(LayoutLetterSpacingTest, measuredTextTest_LetterSpacing) {
105 // In this test case, use LayoutTestFont.ttf for testing. This font supports character I. The
106 // glyph of 'I' in this font has 1em width.
107 setShapeParam("LayoutTestFont.ttf", 1.0f);
108
109 // Single letter
110 LayoutTest({20}, {5}, "I", LTR, NONE);
111 LayoutTest({15}, {0}, "I", LTR, LEFT_EDGE);
112 LayoutTest({15}, {5}, "I", LTR, RIGHT_EDGE);
113 LayoutTest({10}, {0}, "I", LTR, WHOLE_LINE);
114
115 // Two letters
116 LayoutTest({20, 20}, {5, 25}, "II", LTR, NONE);
117 LayoutTest({15, 20}, {0, 20}, "II", LTR, LEFT_EDGE);
118 LayoutTest({20, 15}, {5, 25}, "II", LTR, RIGHT_EDGE);
119 LayoutTest({15, 15}, {0, 20}, "II", LTR, WHOLE_LINE);
120
121 // Three letters
122 LayoutTest({20, 20, 20}, {5, 25, 45}, "III", LTR, NONE);
123 LayoutTest({15, 20, 20}, {0, 20, 40}, "III", LTR, LEFT_EDGE);
124 LayoutTest({20, 20, 15}, {5, 25, 45}, "III", LTR, RIGHT_EDGE);
125 LayoutTest({15, 20, 15}, {0, 20, 40}, "III", LTR, WHOLE_LINE);
126 }
127
TEST_F(LayoutLetterSpacingTest,measuredTextTest_LetterSpacing_ligature)128 TEST_F(LayoutLetterSpacingTest, measuredTextTest_LetterSpacing_ligature) {
129 // In this test case, use Ligature.ttf for testing. This font supports both character 'f' and
130 // character 'i', then this font has ligatured form of sequence "fi". All letters in this font
131 // have 1em width.
132 setShapeParam("Ligature.ttf", 1.0f);
133
134 // Single letter
135 LayoutTest({20, 0}, {5}, "fi", LTR, NONE);
136 LayoutTest({15, 0}, {0}, "fi", LTR, LEFT_EDGE);
137 LayoutTest({15, 0}, {5}, "fi", LTR, RIGHT_EDGE);
138 LayoutTest({10, 0}, {0}, "fi", LTR, WHOLE_LINE);
139
140 // Two letters
141 LayoutTest({20, 0, 20, 0}, {5, 25}, "fifi", LTR, NONE);
142 LayoutTest({15, 0, 20, 0}, {0, 20}, "fifi", LTR, LEFT_EDGE);
143 LayoutTest({20, 0, 15, 0}, {5, 25}, "fifi", LTR, RIGHT_EDGE);
144 LayoutTest({15, 0, 15, 0}, {0, 20}, "fifi", LTR, WHOLE_LINE);
145
146 // Three letters
147 LayoutTest({20, 0, 20, 0, 20, 0}, {5, 25, 45}, "fififi", LTR, NONE);
148 LayoutTest({15, 0, 20, 0, 20, 0}, {0, 20, 40}, "fififi", LTR, LEFT_EDGE);
149 LayoutTest({20, 0, 20, 0, 15, 0}, {5, 25, 45}, "fififi", LTR, RIGHT_EDGE);
150 LayoutTest({15, 0, 20, 0, 15, 0}, {0, 20, 40}, "fififi", LTR, WHOLE_LINE);
151 }
152
TEST_F(LayoutLetterSpacingTest,measuredTextTest_LetterSpacing_Bidi)153 TEST_F(LayoutLetterSpacingTest, measuredTextTest_LetterSpacing_Bidi) {
154 const std::string LLRRLL = "aa\u05D0\u05D0aa";
155 const std::string RRLLRR = "\u05D0\u05D0aa\u05D0\u05D0";
156
157 // In this test case, use BiDi.ttf for testing. This font supports both character 'a' and
158 // Hebrew Alef. Both letters have 1em advance.
159 setShapeParam("BiDi.ttf", 1.0f);
160
161 // LLRRLL with LTR context
162 LayoutTest({20, 20, 20, 20, 20, 20}, {5, 25, 45, 65, 85, 105}, LLRRLL, LTR, NONE);
163 LayoutTest({15, 20, 20, 20, 20, 20}, {0, 20, 40, 60, 80, 100}, LLRRLL, LTR, LEFT_EDGE);
164 LayoutTest({20, 20, 20, 20, 20, 15}, {5, 25, 45, 65, 85, 105}, LLRRLL, LTR, RIGHT_EDGE);
165 LayoutTest({15, 20, 20, 20, 20, 15}, {0, 20, 40, 60, 80, 100}, LLRRLL, LTR, WHOLE_LINE);
166
167 // LLRRLL with RTL context
168 LayoutTest({20, 20, 20, 20, 20, 20}, {5, 25, 45, 65, 85, 105}, LLRRLL, RTL, NONE);
169 LayoutTest({20, 20, 20, 20, 15, 20}, {0, 20, 40, 60, 80, 100}, LLRRLL, RTL, LEFT_EDGE);
170 LayoutTest({20, 15, 20, 20, 20, 20}, {5, 25, 45, 65, 85, 105}, LLRRLL, RTL, RIGHT_EDGE);
171 LayoutTest({20, 15, 20, 20, 15, 20}, {0, 20, 40, 60, 80, 100}, LLRRLL, RTL, WHOLE_LINE);
172
173 // RRLLRR with LTR context
174 LayoutTest({20, 20, 20, 20, 20, 20}, {5, 25, 45, 65, 85, 105}, RRLLRR, LTR, NONE);
175 LayoutTest({20, 15, 20, 20, 20, 20}, {0, 20, 40, 60, 80, 100}, RRLLRR, LTR, LEFT_EDGE);
176 LayoutTest({20, 20, 20, 20, 15, 20}, {5, 25, 45, 65, 85, 105}, RRLLRR, LTR, RIGHT_EDGE);
177 LayoutTest({20, 15, 20, 20, 15, 20}, {0, 20, 40, 60, 80, 100}, RRLLRR, LTR, WHOLE_LINE);
178
179 // RRLLRR with RTL context
180 LayoutTest({20, 20, 20, 20, 20, 20}, {5, 25, 45, 65, 85, 105}, RRLLRR, RTL, NONE);
181 LayoutTest({20, 20, 20, 20, 20, 15}, {0, 20, 40, 60, 80, 100}, RRLLRR, RTL, LEFT_EDGE);
182 LayoutTest({15, 20, 20, 20, 20, 20}, {5, 25, 45, 65, 85, 105}, RRLLRR, RTL, RIGHT_EDGE);
183 LayoutTest({15, 20, 20, 20, 20, 15}, {0, 20, 40, 60, 80, 100}, RRLLRR, RTL, WHOLE_LINE);
184 }
185
TEST_F(LayoutLetterSpacingTest,measureTextTest_ControlCharacters)186 TEST_F(LayoutLetterSpacingTest, measureTextTest_ControlCharacters) {
187 // In this test case, use ControlCharacters.ttf for testing. This font supports ASCII plus
188 // following letters.
189 // U+FEFF: ZERO WIDTH NO-BREAK SPACE: Control character and no width.
190 setShapeParam("ControlCharacters.ttf", 1.0f);
191
192 // U+FEFF is a control character, so letter spacing should not be applied.
193 LayoutTest({0}, {0}, "\uFEFF", LTR, NONE);
194 LayoutTest({0}, {0}, "\uFEFF", LTR, LEFT_EDGE);
195 LayoutTest({0}, {0}, "\uFEFF", LTR, RIGHT_EDGE);
196 LayoutTest({0}, {0}, "\uFEFF", LTR, WHOLE_LINE);
197
198 LayoutTest({20, 0}, {5, 20}, "a\uFEFF", LTR, NONE);
199 LayoutTest({15, 0}, {0, 15}, "a\uFEFF", LTR, LEFT_EDGE);
200 LayoutTest({15, 0}, {5, 15}, "a\uFEFF", LTR, RIGHT_EDGE);
201 LayoutTest({10, 0}, {0, 10}, "a\uFEFF", LTR, WHOLE_LINE);
202
203 LayoutTest({20, 0, 20}, {5, 20, 25}, "a\uFEFFa", LTR, NONE);
204 LayoutTest({15, 0, 20}, {0, 15, 20}, "a\uFEFFa", LTR, LEFT_EDGE);
205 LayoutTest({20, 0, 15}, {5, 20, 25}, "a\uFEFFa", LTR, RIGHT_EDGE);
206 LayoutTest({15, 0, 15}, {0, 15, 20}, "a\uFEFFa", LTR, WHOLE_LINE);
207
208 LayoutTest({0, 20}, {0, 5}, "\uFEFFa", LTR, NONE);
209 LayoutTest({0, 15}, {0, 0}, "\uFEFFa", LTR, LEFT_EDGE);
210 LayoutTest({0, 15}, {0, 5}, "\uFEFFa", LTR, RIGHT_EDGE);
211 LayoutTest({0, 10}, {0, 0}, "\uFEFFa", LTR, WHOLE_LINE);
212
213 LayoutTest({0, 20, 0}, {0, 5, 20}, "\uFEFFa\uFEFF", LTR, NONE);
214 LayoutTest({0, 15, 0}, {0, 0, 15}, "\uFEFFa\uFEFF", LTR, LEFT_EDGE);
215 LayoutTest({0, 15, 0}, {0, 5, 15}, "\uFEFFa\uFEFF", LTR, RIGHT_EDGE);
216 LayoutTest({0, 10, 0}, {0, 0, 10}, "\uFEFFa\uFEFF", LTR, WHOLE_LINE);
217 }
218
TEST_F(LayoutLetterSpacingTest,measureTextTest_ControlCharacters_RTL)219 TEST_F(LayoutLetterSpacingTest, measureTextTest_ControlCharacters_RTL) {
220 // In this test case, use ControlCharacters.ttf for testing. This font supports ASCII plus
221 // following letters.
222 // U+FEFF: ZERO WIDTH NO-BREAK SPACE: Control character and no width.
223 setShapeParam("ControlCharacters.ttf", 1.0f);
224
225 // U+FEFF is a control character, so letter spacing should not be applied.
226 LayoutTest({0}, {0}, "\uFEFF", RTL, NONE);
227 LayoutTest({0}, {0}, "\uFEFF", RTL, LEFT_EDGE);
228 LayoutTest({0}, {0}, "\uFEFF", RTL, RIGHT_EDGE);
229 LayoutTest({0}, {0}, "\uFEFF", RTL, WHOLE_LINE);
230
231 LayoutTest({20, 0}, {0, 5}, "\u05D0\uFEFF", RTL, NONE);
232 LayoutTest({15, 0}, {0, 0}, "\u05D0\uFEFF", RTL, LEFT_EDGE);
233 LayoutTest({15, 0}, {0, 5}, "\u05D0\uFEFF", RTL, RIGHT_EDGE);
234 LayoutTest({10, 0}, {0, 0}, "\u05D0\uFEFF", RTL, WHOLE_LINE);
235
236 LayoutTest({20, 0, 20}, {5, 20, 25}, "\u05D0\uFEFF\u05D0", RTL, NONE);
237 LayoutTest({20, 0, 15}, {0, 15, 20}, "\u05D0\uFEFF\u05D0", RTL, LEFT_EDGE);
238 LayoutTest({15, 0, 20}, {5, 20, 25}, "\u05D0\uFEFF\u05D0", RTL, RIGHT_EDGE);
239 LayoutTest({15, 0, 15}, {0, 15, 20}, "\u05D0\uFEFF\u05D0", RTL, WHOLE_LINE);
240
241 LayoutTest({0, 20}, {5, 20}, "\uFEFF\u05D0", RTL, NONE);
242 LayoutTest({0, 15}, {0, 15}, "\uFEFF\u05D0", RTL, LEFT_EDGE);
243 LayoutTest({0, 15}, {5, 15}, "\uFEFF\u05D0", RTL, RIGHT_EDGE);
244 LayoutTest({0, 10}, {0, 10}, "\uFEFF\u05D0", RTL, WHOLE_LINE);
245
246 LayoutTest({0, 20, 0}, {0, 5, 20}, "\uFEFF\u05D0\uFEFF", RTL, NONE);
247 LayoutTest({0, 15, 0}, {0, 0, 15}, "\uFEFF\u05D0\uFEFF", RTL, LEFT_EDGE);
248 LayoutTest({0, 15, 0}, {0, 5, 15}, "\uFEFF\u05D0\uFEFF", RTL, RIGHT_EDGE);
249 LayoutTest({0, 10, 0}, {0, 0, 10}, "\uFEFF\u05D0\uFEFF", RTL, WHOLE_LINE);
250 }
251
TEST_F(LayoutLetterSpacingTest,measureTextTest_ControlCharacters_RTL_Arabic)252 TEST_F(LayoutLetterSpacingTest, measureTextTest_ControlCharacters_RTL_Arabic) {
253 // In this test case, use ControlCharacters.ttf for testing. This font supports ASCII plus
254 // following letters.
255 // U+FEFF: ZERO WIDTH NO-BREAK SPACE: Control character and no width.
256 setShapeParam("ControlCharacters.ttf", 1.0f);
257
258 // U+FEFF is a control character, so letter spacing should not be applied.
259 LayoutTest({10, 0}, {0, 0}, "\u0627\uFEFF", RTL, NONE);
260 LayoutTest({10, 0}, {0, 0}, "\u0627\uFEFF", RTL, LEFT_EDGE);
261 LayoutTest({10, 0}, {0, 0}, "\u0627\uFEFF", RTL, RIGHT_EDGE);
262 LayoutTest({10, 0}, {0, 0}, "\u0627\uFEFF", RTL, WHOLE_LINE);
263
264 LayoutTest({10, 0, 10}, {0, 10, 10}, "\u0627\uFEFF\u0627", RTL, NONE);
265 LayoutTest({10, 0, 10}, {0, 10, 10}, "\u0627\uFEFF\u0627", RTL, LEFT_EDGE);
266 LayoutTest({10, 0, 10}, {0, 10, 10}, "\u0627\uFEFF\u0627", RTL, RIGHT_EDGE);
267 LayoutTest({10, 0, 10}, {0, 10, 10}, "\u0627\uFEFF\u0627", RTL, WHOLE_LINE);
268
269 LayoutTest({0, 10}, {0, 10}, "\uFEFF\u0627", RTL, NONE);
270 LayoutTest({0, 10}, {0, 10}, "\uFEFF\u0627", RTL, LEFT_EDGE);
271 LayoutTest({0, 10}, {0, 10}, "\uFEFF\u0627", RTL, RIGHT_EDGE);
272 LayoutTest({0, 10}, {0, 10}, "\uFEFF\u0627", RTL, WHOLE_LINE);
273
274 LayoutTest({0, 10, 0}, {0, 0, 10}, "\uFEFF\u0627\uFEFF", RTL, NONE);
275 LayoutTest({0, 10, 0}, {0, 0, 10}, "\uFEFF\u0627\uFEFF", RTL, LEFT_EDGE);
276 LayoutTest({0, 10, 0}, {0, 0, 10}, "\uFEFF\u0627\uFEFF", RTL, RIGHT_EDGE);
277 LayoutTest({0, 10, 0}, {0, 0, 10}, "\uFEFF\u0627\uFEFF", RTL, WHOLE_LINE);
278 }
279
280 } // namespace minikin
281