1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #include <config_features.h>
11 #include <config_fonts.h>
15 #include <tools/long.hxx>
16 #include <vcl/glyphitemcache.hxx>
19 // must be declared before inclusion of test/bootstrapfixture.hxx
20 static std::ostream
& operator<<(std::ostream
& rStream
, const std::vector
<sal_Int32
>& rVec
);
21 static std::ostream
& operator<<(std::ostream
& rStream
, const std::vector
<double>& rVec
);
23 #include <test/bootstrapfixture.hxx>
25 #include <vcl/wrkwin.hxx>
26 #include <vcl/virdev.hxx>
27 // workaround MSVC2015 issue with std::unique_ptr
28 #include <sallayout.hxx>
32 #include <ImplLayoutArgs.hxx>
35 static std::ostream
& operator<<(std::ostream
& rStream
, const std::vector
<sal_Int32
>& rVec
)
38 for (size_t i
= 0; i
< rVec
.size() - 1; i
++)
39 rStream
<< rVec
[i
] << ", ";
40 rStream
<< rVec
.back();
44 static std::ostream
& operator<<(std::ostream
& rStream
, const std::vector
<double>& rVec
)
47 for (size_t i
= 0; i
< rVec
.size() - 1; i
++)
48 rStream
<< rVec
[i
] << ", ";
49 rStream
<< rVec
.back();
55 class VclComplexTextTest
: public test::BootstrapFixture
58 OUString maDataUrl
= u
"/vcl/qa/cppunit/data/"_ustr
;
60 OUString
getFullUrl(std::u16string_view sFileName
)
62 return m_directories
.getURLFromSrc(maDataUrl
) + sFileName
;
66 bool addFont(OutputDevice
* pOutDev
, std::u16string_view sFileName
,
67 std::u16string_view sFamilyName
)
69 OutputDevice::ImplClearAllFontData(true);
70 bool bAdded
= pOutDev
->AddTempDevFont(getFullUrl(sFileName
), OUString(sFamilyName
));
71 OutputDevice::ImplRefreshAllFontData(true);
78 : BootstrapFixture(true, false)
83 CPPUNIT_TEST_FIXTURE(VclComplexTextTest
, testArabic
)
86 OUString
aOneTwoThree(u
"واحِدْ إثٍنين ثلاثةٌ"_ustr
);
88 vcl::Font
aFont(u
"DejaVu Sans"_ustr
, u
"Book"_ustr
, Size(0, 2048));
90 ScopedVclPtrInstance
<VirtualDevice
> pOutDev
;
91 pOutDev
->SetFont( aFont
);
93 // absolute character widths AKA text array.
94 tools::Long nRefTextWidth
= 12595;
95 KernArray aRefCharWidths
= { 989, 1558, 2824, 2824, 3899,
96 3899, 4550, 5119, 5689, 5689, 6307, 6925, 8484, 9135, 9705, 10927,
97 10927, 11497, 12595, 12595 };
98 KernArray aCharWidths
;
99 tools::Long nTextWidth
100 = basegfx::fround
<tools::Long
>(pOutDev
->GetTextArray(aOneTwoThree
, &aCharWidths
).nWidth
);
102 CPPUNIT_ASSERT_EQUAL(aRefCharWidths
, aCharWidths
);
103 // this sporadically returns 75 or 74 on some of the windows tinderboxes eg. tb73
104 CPPUNIT_ASSERT_EQUAL(nRefTextWidth
, nTextWidth
);
105 CPPUNIT_ASSERT_EQUAL(nTextWidth
, tools::Long(aCharWidths
.back()));
107 // text advance width and line height
108 CPPUNIT_ASSERT_EQUAL(nRefTextWidth
, pOutDev
->GetTextWidth(aOneTwoThree
));
109 CPPUNIT_ASSERT_EQUAL(tools::Long(2384), pOutDev
->GetTextHeight());
111 // exact bounding rectangle, not essentially the same as text width/height
112 tools::Rectangle aBoundRect
;
113 pOutDev
->GetTextBoundRect(aBoundRect
, aOneTwoThree
);
114 CPPUNIT_ASSERT_EQUAL(tools::Long(145), aBoundRect
.Left());
115 CPPUNIT_ASSERT_EQUAL(tools::Long(212), aBoundRect
.Top());
116 CPPUNIT_ASSERT_EQUAL(tools::Long(12294), aBoundRect
.GetWidth());
117 CPPUNIT_ASSERT_EQUAL(tools::Long(2279), aBoundRect
.getOpenHeight());
119 // normal orientation
120 tools::Rectangle aInput
;
121 tools::Rectangle aRect
= pOutDev
->GetTextRect( aInput
, aOneTwoThree
);
123 // now rotate 270 degrees
124 vcl::Font
aRotated( aFont
);
125 aRotated
.SetOrientation( 2700_deg10
);
126 pOutDev
->SetFont( aRotated
);
127 tools::Rectangle aRectRot
= pOutDev
->GetTextRect( aInput
, aOneTwoThree
);
129 // Check that we did do the rotation...
130 CPPUNIT_ASSERT_EQUAL( aRectRot
.GetWidth(), aRect
.GetHeight() );
131 CPPUNIT_ASSERT_EQUAL( aRectRot
.GetHeight(), aRect
.GetWidth() );
135 CPPUNIT_TEST_FIXTURE(VclComplexTextTest
, testTdf95650
)
137 static constexpr OUStringLiteral aTxt
=
138 u
"\u0131\u0302\u0504\u4E44\u3031\u3030\u3531\u2D30"
139 "\u3037\u0706\u0908\u0B0A\u0D0C\u0F0E\u072E\u100A"
140 "\u0D11\u1312\u0105\u020A\u0512\u1403\u030C\u1528"
141 "\u2931\u632E\u7074\u0D20\u0E0A\u100A\uF00D\u0D20"
142 "\u030A\u0C0B\u20E0\u0A0D";
143 ScopedVclPtrInstance
<VirtualDevice
> pOutDev
;
144 // Check that the following executes without failing assertion
145 pOutDev
->ImplLayout(aTxt
, 9, 1, Point(), 0, {}, {}, SalLayoutFlags::BiDiRtl
);
148 static void checkCompareGlyphs( const SalLayoutGlyphs
& aGlyphs1
, const SalLayoutGlyphs
& aGlyphs2
,
149 const std::string
& message
)
151 CPPUNIT_ASSERT_EQUAL_MESSAGE(message
, aGlyphs1
.IsValid(), aGlyphs2
.IsValid());
152 // And check it's the same.
153 for( int level
= 0; level
< MAX_FALLBACK
; ++level
)
155 const std::string
messageLevel( Concat2View(OString::Concat(std::string_view(message
))
156 + ", level: " + OString::number(level
)) );
157 if( aGlyphs1
.Impl(level
) == nullptr)
159 CPPUNIT_ASSERT_MESSAGE(messageLevel
, aGlyphs2
.Impl(level
) == nullptr);
162 const SalLayoutGlyphsImpl
* g1
= aGlyphs1
.Impl(level
);
163 const SalLayoutGlyphsImpl
* g2
= aGlyphs2
.Impl(level
);
164 CPPUNIT_ASSERT_EQUAL_MESSAGE(messageLevel
, g1
->GetFont().get(), g2
->GetFont().get());
165 CPPUNIT_ASSERT_EQUAL_MESSAGE(messageLevel
, g1
->size(), g2
->size());
166 for( size_t i
= 0; i
< g1
->size(); ++i
)
168 const bool equal
= (*g1
)[i
].isLayoutEquivalent((*g2
)[i
]);
169 CPPUNIT_ASSERT_MESSAGE(messageLevel
, equal
);
174 static void testCachedGlyphs( const OUString
& aText
, const OUString
& aFontName
)
176 const std::string
message( OUString("Font: " + aFontName
+ ", text: '" + aText
+ "'").toUtf8() );
177 ScopedVclPtrInstance
<VirtualDevice
> pOutputDevice
;
178 vcl::Font
aFont( aFontName
, Size(0, 12));
179 pOutputDevice
->SetFont( aFont
);
180 SalLayoutGlyphsCache::self()->clear();
181 // Get the glyphs for the text.
182 std::unique_ptr
<SalLayout
> pLayout1
= pOutputDevice
->ImplLayout(
183 aText
, 0, aText
.getLength(), Point(0, 0), 0, {}, {}, SalLayoutFlags::GlyphItemsOnly
);
184 SalLayoutGlyphs aGlyphs1
= pLayout1
->GetGlyphs();
185 // Reuse the cached glyphs to get glyphs again.
186 std::unique_ptr
<SalLayout
> pLayout2
= pOutputDevice
->ImplLayout(
187 aText
, 0, aText
.getLength(), Point(0, 0), 0, {}, {}, SalLayoutFlags::GlyphItemsOnly
, nullptr, &aGlyphs1
);
188 SalLayoutGlyphs aGlyphs2
= pLayout2
->GetGlyphs();
189 checkCompareGlyphs(aGlyphs1
, aGlyphs2
, message
+ " (reuse)");
190 // Get cached glyphs from SalLayoutGlyphsCache.
191 const SalLayoutGlyphs
* aGlyphs3
= SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
192 pOutputDevice
, aText
, 0, aText
.getLength(), 0);
193 CPPUNIT_ASSERT_MESSAGE(message
, aGlyphs3
!= nullptr);
194 checkCompareGlyphs(aGlyphs1
, *aGlyphs3
, message
+ " (cache)");
197 // Check that caching using SalLayoutGlyphs gives same results as without caching.
198 // This should preferably use fonts that come with LO.
199 CPPUNIT_TEST_FIXTURE(VclComplexTextTest
, testCaching
)
201 // Just something basic, no font fallback.
202 testCachedGlyphs( u
"test"_ustr
, u
"Dejavu Sans"_ustr
);
203 // This font does not have latin characters, will need fallback.
204 testCachedGlyphs( u
"test"_ustr
, u
"Noto Kufi Arabic"_ustr
);
206 testCachedGlyphs( u
"يوسف My name is"_ustr
, u
"Liberation Sans"_ustr
);
209 static void testCachedGlyphsSubstring( const OUString
& aText
, const OUString
& aFontName
, bool rtl
)
211 const std::string
prefix( OUString("Font: " + aFontName
+ ", text: '" + aText
+ "'").toUtf8() );
212 ScopedVclPtrInstance
<VirtualDevice
> pOutputDevice
;
213 // BiDiStrong is needed, otherwise SalLayoutGlyphsImpl::cloneCharRange() will not do anything.
214 vcl::text::ComplexTextLayoutFlags layoutFlags
= vcl::text::ComplexTextLayoutFlags::BiDiStrong
;
216 layoutFlags
|= vcl::text::ComplexTextLayoutFlags::BiDiRtl
;
217 pOutputDevice
->SetLayoutMode( layoutFlags
);
218 vcl::Font
aFont( aFontName
, Size(0, 12));
219 pOutputDevice
->SetFont( aFont
);
220 SalLayoutGlyphsCache::self()->clear();
221 std::shared_ptr
<const vcl::text::TextLayoutCache
> layoutCache
= OutputDevice::CreateTextLayoutCache(aText
);
222 // Get the glyphs for the entire text once, to ensure the cache can built subsets from it.
223 pOutputDevice
->ImplLayout( aText
, 0, aText
.getLength(), Point(0, 0), 0, {}, {}, SalLayoutFlags::GlyphItemsOnly
,
225 // Now check for all subsets. Some of them possibly do not make sense in practice, but the code
226 // should cope with them.
227 for( sal_Int32 len
= 1; len
<= aText
.getLength(); ++len
)
228 for( sal_Int32 pos
= 0; pos
< aText
.getLength() - len
; ++pos
)
230 std::string message
= prefix
+ " (" + std::to_string(pos
) + "/" + std::to_string(len
) + ")";
231 std::unique_ptr
<SalLayout
> pLayout1
= pOutputDevice
->ImplLayout(
232 aText
, pos
, len
, Point(0, 0), 0, {}, {}, SalLayoutFlags::GlyphItemsOnly
, layoutCache
.get());
233 SalLayoutGlyphs aGlyphs1
= pLayout1
->GetGlyphs();
234 const SalLayoutGlyphs
* aGlyphs2
= SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
235 pOutputDevice
, aText
, pos
, len
, 0, layoutCache
.get());
236 CPPUNIT_ASSERT_MESSAGE(message
, aGlyphs2
!= nullptr);
237 checkCompareGlyphs(aGlyphs1
, *aGlyphs2
, message
);
242 // Check that SalLayoutGlyphsCache works properly when it builds a subset
243 // of glyphs using SalLayoutGlyphsImpl::cloneCharRange().
244 // This should preferably use fonts that come with LO.
245 CPPUNIT_TEST_FIXTURE(VclComplexTextTest
, testCachingSubstring
)
247 // Just something basic.
248 testCachedGlyphsSubstring( u
"test"_ustr
, u
"Dejavu Sans"_ustr
, false );
249 // And complex arabic text, taken from tdf104649.docx .
250 OUString
text(u
"فصل (پاره 2): درخواست حاجت از ديگران و برآوردن حاجت ديگران 90"_ustr
);
251 testCachedGlyphsSubstring( text
, u
"Dejavu Sans"_ustr
, true );
252 // The text is RTL, but Writer will sometimes try to lay it out as LTR, for whatever reason
253 // (tdf#149264)./ So make sure that gets handled properly too (SalLayoutGlyphsCache should
254 // not use glyph subsets in that case).
255 testCachedGlyphsSubstring( text
, u
"Dejavu Sans"_ustr
, false );
258 CPPUNIT_TEST_FIXTURE(VclComplexTextTest
, testCaret
)
261 // Test caret placement in fonts *without* ligature carets in GDEF table.
263 // Set font size to its UPEM to decrease rounding issues
264 vcl::Font
aFont(u
"DejaVu Sans"_ustr
, u
"Book"_ustr
, Size(0, 2048));
266 ScopedVclPtrInstance
<VirtualDevice
> pOutDev
;
267 pOutDev
->SetFont( aFont
);
270 KernArray aCharWidths
;
271 KernArray aRefCharWidths
;
272 tools::Long nTextWidth
, nTextWidth2
, nRefTextWidth
;
275 aText
= u
"لا بلا"_ustr
;
277 // 1) Regular DX array, the ligature width is given to the first components
278 // and the next ones are all zero width.
279 nRefTextWidth
= 3611;
280 aRefCharWidths
= { 1168, 1168, 1819, 2389, 3611, 3611 };
281 nTextWidth
= basegfx::fround
<tools::Long
>(
282 pOutDev
->GetTextArray(aText
, &aCharWidths
, 0, -1, /*bCaret*/ false).nWidth
);
283 CPPUNIT_ASSERT_EQUAL(aRefCharWidths
, aCharWidths
);
284 CPPUNIT_ASSERT_EQUAL(nRefTextWidth
, nTextWidth
);
285 CPPUNIT_ASSERT_EQUAL(nTextWidth
, tools::Long(aCharWidths
.back()));
287 // 2) Caret placement DX array, ligature width is distributed over its
289 aRefCharWidths
= { 584, 1168, 1819, 2389, 3000, 3611 };
290 nTextWidth
= basegfx::fround
<tools::Long
>(
291 pOutDev
->GetTextArray(aText
, &aCharWidths
, 0, -1, /*bCaret*/ true).nWidth
);
292 CPPUNIT_ASSERT_EQUAL(aRefCharWidths
, aCharWidths
);
293 CPPUNIT_ASSERT_EQUAL(nRefTextWidth
, nTextWidth
);
294 CPPUNIT_ASSERT_EQUAL(nTextWidth
, tools::Long(aCharWidths
.back()));
296 // 3) caret placement with combining marks, they should not add to ligature
298 aText
= u
"لَاَ بلَاَ"_ustr
;
299 aRefCharWidths
= { 584, 584, 1168, 1168, 1819, 2389, 3000, 3000, 3611, 3611 };
300 nTextWidth2
= basegfx::fround
<tools::Long
>(
301 pOutDev
->GetTextArray(aText
, &aCharWidths
, 0, -1, /*bCaret*/ true).nWidth
);
302 CPPUNIT_ASSERT_EQUAL(aCharWidths
[0], aCharWidths
[1]);
303 CPPUNIT_ASSERT_EQUAL(aCharWidths
[2], aCharWidths
[3]);
304 CPPUNIT_ASSERT_EQUAL(aCharWidths
[6], aCharWidths
[7]);
305 CPPUNIT_ASSERT_EQUAL(aCharWidths
[8], aCharWidths
[9]);
306 CPPUNIT_ASSERT_EQUAL(aRefCharWidths
, aCharWidths
);
307 CPPUNIT_ASSERT_EQUAL(nRefTextWidth
, nTextWidth2
);
308 CPPUNIT_ASSERT_EQUAL(nTextWidth
, nTextWidth2
);
309 CPPUNIT_ASSERT_EQUAL(nTextWidth
, tools::Long(aCharWidths
.back()));
312 aText
= u
"fi fl ffi ffl"_ustr
;
314 // 1) Regular DX array, the ligature width is given to the first components
315 // and the next ones are all zero width.
316 nRefTextWidth
= 8493;
317 aRefCharWidths
= { 1290, 1290, 1941, 3231, 3231, 3882, 5862, 5862, 5862, 6513, 8493, 8493, 8493 };
318 nTextWidth
= basegfx::fround
<tools::Long
>(
319 pOutDev
->GetTextArray(aText
, &aCharWidths
, 0, -1, /*bCaret*/ false).nWidth
);
320 CPPUNIT_ASSERT_EQUAL(aRefCharWidths
, aCharWidths
);
321 CPPUNIT_ASSERT_EQUAL(nRefTextWidth
, nTextWidth
);
322 CPPUNIT_ASSERT_EQUAL(nTextWidth
, tools::Long(aCharWidths
.back()));
324 // 2) Caret placement DX array, ligature width is distributed over its
326 aRefCharWidths
= { 645, 1290, 1941, 2586, 3231, 3882, 4542, 5202, 5862, 6513, 7173, 7833, 8493 };
327 nTextWidth
= basegfx::fround
<tools::Long
>(
328 pOutDev
->GetTextArray(aText
, &aCharWidths
, 0, -1, /*bCaret*/ true).nWidth
);
329 CPPUNIT_ASSERT_EQUAL(aRefCharWidths
, aCharWidths
);
330 CPPUNIT_ASSERT_EQUAL(nRefTextWidth
, nTextWidth
);
331 CPPUNIT_ASSERT_EQUAL(nTextWidth
, tools::Long(aCharWidths
.back()));
335 CPPUNIT_TEST_FIXTURE(VclComplexTextTest
, testGdefCaret
)
338 // Test caret placement in fonts *with* ligature carets in GDEF table.
340 ScopedVclPtrInstance
<VirtualDevice
> pOutDev
;
344 KernArray aCharWidths
;
345 KernArray aRefCharWidths
;
346 tools::Long nTextWidth
, nTextWidth2
, nRefTextWidth
;
349 // Set font size to its UPEM to decrease rounding issues
350 aFont
= vcl::Font(u
"Noto Sans Arabic"_ustr
, u
"Regular"_ustr
, Size(0, 1000));
351 pOutDev
->SetFont(aFont
);
353 aText
= u
"لا بلا"_ustr
;
355 // 1) Regular DX array, the ligature width is given to the first components
356 // and the next ones are all zero width.
357 nRefTextWidth
= 1710;
358 aRefCharWidths
= { 582, 582, 842, 1111, 1710, 1710 };
359 nTextWidth
= basegfx::fround
<tools::Long
>(
360 pOutDev
->GetTextArray(aText
, &aCharWidths
, 0, -1, /*bCaret*/ false).nWidth
);
361 CPPUNIT_ASSERT_EQUAL(aRefCharWidths
, aCharWidths
);
362 CPPUNIT_ASSERT_EQUAL(nRefTextWidth
, nTextWidth
);
363 CPPUNIT_ASSERT_EQUAL(nTextWidth
, tools::Long(aCharWidths
.back()));
365 // 2) Caret placement DX array, ligature width is distributed over its
367 aRefCharWidths
= { 291, 582, 842, 1111, 1410, 1710 };
368 nTextWidth
= basegfx::fround
<tools::Long
>(
369 pOutDev
->GetTextArray(aText
, &aCharWidths
, 0, -1, /*bCaret*/ true).nWidth
);
370 CPPUNIT_ASSERT_EQUAL(aRefCharWidths
, aCharWidths
);
371 CPPUNIT_ASSERT_EQUAL(nRefTextWidth
, nTextWidth
);
372 CPPUNIT_ASSERT_EQUAL(nTextWidth
, tools::Long(aCharWidths
.back()));
374 // 3) caret placement with combining marks, they should not add to ligature
376 aText
= u
"لَاَ بلَاَ"_ustr
;
377 aRefCharWidths
= { 291, 291, 582, 582, 842, 1111, 1410, 1410, 1710, 1710 };
378 nTextWidth2
= basegfx::fround
<tools::Long
>(
379 pOutDev
->GetTextArray(aText
, &aCharWidths
, 0, -1, /*bCaret*/ true).nWidth
);
380 CPPUNIT_ASSERT_EQUAL(aCharWidths
[0], aCharWidths
[1]);
381 CPPUNIT_ASSERT_EQUAL(aCharWidths
[2], aCharWidths
[3]);
382 CPPUNIT_ASSERT_EQUAL(aCharWidths
[6], aCharWidths
[7]);
383 CPPUNIT_ASSERT_EQUAL(aCharWidths
[8], aCharWidths
[9]);
384 CPPUNIT_ASSERT_EQUAL(aRefCharWidths
, aCharWidths
);
385 CPPUNIT_ASSERT_EQUAL(nRefTextWidth
, nTextWidth2
);
386 CPPUNIT_ASSERT_EQUAL(nTextWidth
, nTextWidth2
);
387 CPPUNIT_ASSERT_EQUAL(nTextWidth
, tools::Long(aCharWidths
.back()));
390 // Set font size to its UPEM to decrease rounding issues
391 aFont
= vcl::Font(u
"Amiri"_ustr
, u
"Regular"_ustr
, Size(0, 1000));
392 pOutDev
->SetFont(aFont
);
394 aText
= u
"fi ffi fl ffl fb ffb"_ustr
;
396 // 1) Regular DX array, the ligature width is given to the first components
397 // and the next ones are all zero width.
398 nRefTextWidth
= 5996;
399 aRefCharWidths
= { 519, 519, 811, 1606, 1606, 1606, 1898, 2439, 2439, 2731,
400 3544, 3544, 3544, 3836, 4634, 4634, 4926, 5996, 5996, 5996 };
401 nTextWidth
= basegfx::fround
<tools::Long
>(
402 pOutDev
->GetTextArray(aText
, &aCharWidths
, 0, -1, /*bCaret*/ false).nWidth
);
403 CPPUNIT_ASSERT_EQUAL(aRefCharWidths
, aCharWidths
);
404 CPPUNIT_ASSERT_EQUAL(nRefTextWidth
, nTextWidth
);
405 CPPUNIT_ASSERT_EQUAL(nTextWidth
, tools::Long(aCharWidths
.back()));
407 // 2) Caret placement DX array, ligature width is distributed over its
409 aRefCharWidths
= { 269, 519, 811, 1080, 1348, 1606, 1898, 2171, 2439, 2731,
410 3004, 3278, 3544, 3836, 4138, 4634, 4926, 5199, 5494, 5996 };
411 nTextWidth
= basegfx::fround
<tools::Long
>(
412 pOutDev
->GetTextArray(aText
, &aCharWidths
, 0, -1, /*bCaret*/ true).nWidth
);
413 CPPUNIT_ASSERT_EQUAL(aRefCharWidths
, aCharWidths
);
414 CPPUNIT_ASSERT_EQUAL(nRefTextWidth
, nTextWidth
);
415 CPPUNIT_ASSERT_EQUAL(nTextWidth
, tools::Long(aCharWidths
.back()));
419 CPPUNIT_TEST_FIXTURE(VclComplexTextTest
, testTdf152048
)
422 OUString
aText(u
"میشود"_ustr
);
424 vcl::Font
aFont(u
"Noto Naskh Arabic"_ustr
, u
"Regular"_ustr
, Size(0, 2048));
426 ScopedVclPtrInstance
<VirtualDevice
> pOutDev
;
427 pOutDev
->SetFont(aFont
);
429 // get an compare the default text array
430 KernArray aRefCharWidths
{ 934, 2341, 2341, 3689, 4647, 5495 };
431 tools::Long
nRefTextWidth(5495);
433 KernArray aCharWidths
;
434 tools::Long nTextWidth
435 = basegfx::fround
<tools::Long
>(pOutDev
->GetTextArray(aText
, &aCharWidths
).nWidth
);
437 CPPUNIT_ASSERT_EQUAL(aRefCharWidths
, aCharWidths
);
438 CPPUNIT_ASSERT_EQUAL(nRefTextWidth
, nTextWidth
);
439 CPPUNIT_ASSERT_EQUAL(nTextWidth
, tools::Long(aCharWidths
.back()));
441 // Simulate Kashida insertion using Kashida array and extending text array
442 // to have room for Kashida.
443 std::vector
<sal_Bool
> aKashidaArray
{ false, false, false, true, false, false };
444 auto nKashida
= 4000;
446 aCharWidths
[3] += nKashida
;
447 aCharWidths
[4] += nKashida
;
448 aCharWidths
[5] += nKashida
;
449 auto pLayout
= pOutDev
->ImplLayout(aText
, 0, -1, Point(0, 0), 0, aCharWidths
, aKashidaArray
);
451 // Without the fix this fails with:
454 CPPUNIT_ASSERT_EQUAL(double(nRefTextWidth
+ nKashida
), pLayout
->GetTextWidth());
458 CPPUNIT_TEST_FIXTURE(VclComplexTextTest
, testTdf152048_2
)
461 vcl::Font
aFont(u
"Noto Naskh Arabic"_ustr
, u
"Regular"_ustr
, Size(0, 72));
463 ScopedVclPtrInstance
<VirtualDevice
> pOutDev
;
464 pOutDev
->SetFont(aFont
);
466 // get an compare the default text array
467 KernArray aCharWidths
;
469 = basegfx::fround
<tools::Long
>(pOutDev
->GetTextArray(u
"ع a ع"_ustr
, &aCharWidths
).nWidth
);
471 // Text width should always be equal to the width of the last glyph in the
473 // Without the fix this fails with:
476 CPPUNIT_ASSERT_EQUAL(tools::Long(aCharWidths
.back()), nTextWidth
);
480 CPPUNIT_TEST_FIXTURE(VclComplexTextTest
, testTdf153440
)
483 vcl::Font
aFont(u
"Noto Naskh Arabic"_ustr
, u
"Regular"_ustr
, Size(0, 72));
485 ScopedVclPtrInstance
<VirtualDevice
> pOutDev
;
486 pOutDev
->SetFont(aFont
);
488 #if !defined _WIN32 // TODO: Fails on jenkins but passes locally
489 // Add an emoji font so that we are sure a font will be found for the
490 // emoji. The font is subset and supports only 🌿.
491 bool bAdded
= addFont(pOutDev
, u
"tdf153440.ttf", u
"Noto Emoji");
492 CPPUNIT_ASSERT_EQUAL(true, bAdded
);
495 for (auto& aString
: { u
"ع 🌿 ع", u
"a 🌿 a" })
497 OUString
aText(aString
);
498 bool bRTL
= aText
.startsWith(u
"ع");
500 auto pLayout
= pOutDev
->ImplLayout(aText
, 0, -1, Point(0, 0), 0, {}, {});
503 basegfx::B2DPoint aPos
;
504 const GlyphItem
* pGlyphItem
;
505 while (pLayout
->GetNextGlyph(&pGlyphItem
, aPos
, nStart
))
507 // Assert glyph ID is not 0, if it is 0 then font fallback didn’t
509 CPPUNIT_ASSERT(pGlyphItem
->glyphId());
511 // Assert that we are indeed doing RTL layout for RTL text since
512 // the bug does not happen for LTR text.
513 CPPUNIT_ASSERT_EQUAL(bRTL
, pGlyphItem
->IsRTLGlyph());
519 CPPUNIT_TEST_FIXTURE(VclComplexTextTest
, testMixedCJKLatinScript_glyph_advancements
)
523 OUString
aTestScript(u
"根据10.1(37BA) Eng"_ustr
);
525 ScopedVclPtrInstance
<VirtualDevice
> pOutDev
;
526 // note you can only run this once and it was designed for tdf#107718
527 bool bAdded
= addFont(pOutDev
, u
"tdf107718.otf", u
"Source Han Sans");
528 CPPUNIT_ASSERT_EQUAL(true, bAdded
);
530 vcl::Font
aFont(u
"Source Han Sans"_ustr
, u
"Regular"_ustr
, Size(0, 72));
531 pOutDev
->SetFont( aFont
);
533 vcl::Font
aFallbackFont(u
"DejaVu Sans"_ustr
, u
"Book"_ustr
, Size(0, 72));
534 pOutDev
->ForceFallbackFont(aFallbackFont
);
536 // absolute character widths AKA text array.
537 tools::Long nRefTextWidth
= 704;
538 KernArray aRefCharWidths
= { 72, 144, 190, 236, 259, 305, 333, 379, 425, 474, 523, 551, 567, 612, 658, 704 };
539 KernArray aCharWidths
;
540 tools::Long nTextWidth
541 = basegfx::fround
<tools::Long
>(pOutDev
->GetTextArray(aTestScript
, &aCharWidths
).nWidth
);
543 CPPUNIT_ASSERT_EQUAL(aRefCharWidths
, aCharWidths
);
544 CPPUNIT_ASSERT_EQUAL(nRefTextWidth
, nTextWidth
);
545 CPPUNIT_ASSERT_EQUAL(nTextWidth
, tools::Long(aCharWidths
.back()));
547 // text advance width and line height
548 CPPUNIT_ASSERT_EQUAL(nRefTextWidth
, pOutDev
->GetTextWidth(aTestScript
));
549 CPPUNIT_ASSERT_EQUAL(tools::Long(105), pOutDev
->GetTextHeight());
554 CPPUNIT_TEST_FIXTURE(VclComplexTextTest
, testTdf107718
)
557 #if !defined _WIN32 // TODO: Fails on jenkins but passes locally
558 vcl::Font
aFont(u
"Source Han Sans"_ustr
, u
"Regular"_ustr
, Size(0, 72));
560 ScopedVclPtrInstance
<VirtualDevice
> pOutDev
;
562 OUString
aText(u
"\u4E16\u1109\u1168\u11BC\u302E"_ustr
);
563 for (bool bVertical
: { false, true })
565 aFont
.SetVertical(bVertical
);
566 pOutDev
->SetFont(aFont
);
568 auto pLayout
= pOutDev
->ImplLayout(aText
, 0, -1, Point(0, 0), 0, {}, {});
571 basegfx::B2DPoint aPos
;
572 const GlyphItem
* pGlyphItem
;
573 while (pLayout
->GetNextGlyph(&pGlyphItem
, aPos
, nStart
))
575 // Check that we found a font for all characters, a zero glyph ID
576 // means no font was found so the rest of the test would be
578 CPPUNIT_ASSERT(pGlyphItem
->glyphId());
580 // Assert that we are indeed doing vertical layout for vertical
581 // font since the bug does not happen for horizontal text.
582 CPPUNIT_ASSERT_EQUAL(bVertical
, pGlyphItem
->IsVertical());
584 // For the second glyph, assert that it is a composition of characters 1 to 4
585 // Without the fix this fails with:
590 CPPUNIT_ASSERT_EQUAL(1, pGlyphItem
->charPos());
591 CPPUNIT_ASSERT_EQUAL(4, pGlyphItem
->charCount());
595 // Assert there are only three glyphs
596 // Without the fix this fails with:
599 CPPUNIT_ASSERT_EQUAL(3, nStart
);
605 CPPUNIT_TEST_FIXTURE(VclComplexTextTest
, testTdf107612
)
608 vcl::Font
aFont(u
"DejaVu Sans"_ustr
, u
"Book"_ustr
, Size(0, 72));
610 ScopedVclPtrInstance
<VirtualDevice
> pOutDev
;
611 pOutDev
->SetFont(aFont
);
613 auto pLayout
= pOutDev
->ImplLayout(u
"a\u202F\u1823"_ustr
, 0, -1, Point(0, 0), 0, {}, {});
615 // If font fallback happened, then the returned layout must be a
616 // MultiSalLayout instance.
617 auto pMultiLayout
= dynamic_cast<MultiSalLayout
*>(pLayout
.get());
618 CPPUNIT_ASSERT(pMultiLayout
);
620 auto pFallbackRuns
= pMultiLayout
->GetFallbackRuns();
621 CPPUNIT_ASSERT(!pFallbackRuns
->IsEmpty());
625 std::vector
<sal_Int32
> aFallbacks
;
626 while (pFallbackRuns
->GetNextPos(&nCharPos
, &bRTL
))
627 aFallbacks
.push_back(nCharPos
);
629 // Assert that U+202F is included in the fallback run.
630 // Without the fix this fails with:
631 // - Expected: { 1, 2 }
633 std::vector
<sal_Int32
> aExpctedFallbacks
= { 1, 2 };
634 CPPUNIT_ASSERT_EQUAL(aExpctedFallbacks
, aExpctedFallbacks
);
638 CPPUNIT_TEST_FIXTURE(VclComplexTextTest
, testPartialKoreanJamoComposition
)
640 OUString aStr
= u
"은"_ustr
;
641 vcl::Font
aFont(u
"DejaVu Sans"_ustr
, u
"Book"_ustr
, Size(0, 2048));
643 ScopedVclPtrInstance
<VirtualDevice
> pOutDev
;
644 pOutDev
->SetFont(aFont
);
646 // Absolute character widths for the complete array.
647 KernArray aCompleteWidths
;
648 auto nCompleteWidth
= pOutDev
->GetTextArray(aStr
, &aCompleteWidths
).nWidth
;
650 CPPUNIT_ASSERT_EQUAL(size_t{ 3 }, aCompleteWidths
.size());
652 // Accumulate partial widths
653 double nPartialWidth
= 0.0;
655 sal_Int32 nPrevWidth
= 0;
656 for (sal_Int32 i
= 0; i
< 3; ++i
)
658 KernArray aFragmentWidths
;
661 ->GetPartialTextArray(aStr
, &aFragmentWidths
, /*nIndex*/ 0, /*nLen*/ 3,
662 /*nPartIndex*/ i
, /*nPartLen*/ 1)
664 nPartialWidth
+= nFragmentWidth
;
666 CPPUNIT_ASSERT_EQUAL(size_t{ 1 }, aFragmentWidths
.size());
667 CPPUNIT_ASSERT_EQUAL(aCompleteWidths
[i
] - nPrevWidth
, aFragmentWidths
[0]);
668 nPrevWidth
= aCompleteWidths
[i
];
671 CPPUNIT_ASSERT_DOUBLES_EQUAL(nCompleteWidth
, nPartialWidth
, /*delta*/ 0.01);
674 CPPUNIT_TEST_FIXTURE(VclComplexTextTest
, testPartialArabicComposition
)
676 OUString aStr
= u
"سُكُونْ"_ustr
;
677 vcl::Font
aFont(u
"DejaVu Sans"_ustr
, u
"Book"_ustr
, Size(0, 2048));
679 ScopedVclPtrInstance
<VirtualDevice
> pOutDev
;
680 pOutDev
->SetFont(aFont
);
682 // Absolute character widths for the complete array.
683 KernArray aCompleteWidths
;
684 auto nCompleteWidth
= pOutDev
->GetTextArray(aStr
, &aCompleteWidths
).nWidth
;
686 CPPUNIT_ASSERT_EQUAL(size_t{ 7 }, aCompleteWidths
.size());
688 // Accumulate partial widths
689 double nPartialWidth
= 0.0;
691 sal_Int32 nPrevWidth
= 0;
692 for (sal_Int32 i
= 0; i
< 7; ++i
)
694 KernArray aFragmentWidths
;
697 ->GetPartialTextArray(aStr
, &aFragmentWidths
, /*nIndex*/ 0, /*nLen*/ 7,
698 /*nPartIndex*/ i
, /*nPartLen*/ 1)
700 nPartialWidth
+= nFragmentWidth
;
702 CPPUNIT_ASSERT_EQUAL(size_t{ 1 }, aFragmentWidths
.size());
703 CPPUNIT_ASSERT_EQUAL(aCompleteWidths
[i
] - nPrevWidth
, aFragmentWidths
[0]);
704 nPrevWidth
= aCompleteWidths
[i
];
707 CPPUNIT_ASSERT_DOUBLES_EQUAL(nCompleteWidth
, nPartialWidth
, /*delta*/ 0.01);
710 CPPUNIT_TEST_FIXTURE(VclComplexTextTest
, testTdf163215
)
712 OUString aStr
= u
"ببببب"_ustr
;
713 vcl::Font
aFont(u
"DejaVu Sans"_ustr
, u
"Book"_ustr
, Size(0, 2048));
715 ScopedVclPtrInstance
<VirtualDevice
> pOutDev
;
716 pOutDev
->SetFont(aFont
);
718 // Characteristic case with kashida position validation
719 auto pLayout1
= pOutDev
->ImplLayout(aStr
, 0, aStr
.getLength(), Point(), 0, {}, {},
720 SalLayoutFlags::GlyphItemsOnly
);
721 CPPUNIT_ASSERT(pLayout1
->HasFontKashidaPositions());
723 SalLayoutGlyphs aGlyphs1
= pLayout1
->GetGlyphs();
725 std::vector
<bool> aFoundPositions1
;
726 for (const auto& stGlyph
: *aGlyphs1
.Impl(0))
728 aFoundPositions1
.push_back(stGlyph
.IsSafeToInsertKashida());
731 CPPUNIT_ASSERT_EQUAL(size_t(5), aFoundPositions1
.size());
732 CPPUNIT_ASSERT(aFoundPositions1
.at(0));
733 CPPUNIT_ASSERT(aFoundPositions1
.at(1));
734 CPPUNIT_ASSERT(aFoundPositions1
.at(2));
735 CPPUNIT_ASSERT(aFoundPositions1
.at(3));
736 CPPUNIT_ASSERT(!aFoundPositions1
.at(4));
738 // Case with kashida position validation disabled
739 auto pLayout2
= pOutDev
->ImplLayout(aStr
, 0, aStr
.getLength(), Point(), 0, {}, {},
740 SalLayoutFlags::GlyphItemsOnly
741 | SalLayoutFlags::DisableKashidaValidation
);
742 CPPUNIT_ASSERT(!pLayout2
->HasFontKashidaPositions());
744 SalLayoutGlyphs aGlyphs2
= pLayout2
->GetGlyphs();
746 std::vector
<bool> aFoundPositions2
;
747 for (const auto& stGlyph
: *aGlyphs2
.Impl(0))
749 aFoundPositions2
.push_back(stGlyph
.IsSafeToInsertKashida());
752 // With position validation disabled, all positions must be marked as valid
753 CPPUNIT_ASSERT_EQUAL(size_t(5), aFoundPositions2
.size());
754 CPPUNIT_ASSERT(aFoundPositions2
.at(0));
755 CPPUNIT_ASSERT(aFoundPositions2
.at(1));
756 CPPUNIT_ASSERT(aFoundPositions2
.at(2));
757 CPPUNIT_ASSERT(aFoundPositions2
.at(3));
758 CPPUNIT_ASSERT(aFoundPositions2
.at(4));
761 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */