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