/src/libreoffice/vcl/source/gdi/CommonSalLayout.cxx
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
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/. |
8 | | * |
9 | | * This file incorporates work covered by the following license notice: |
10 | | * |
11 | | * Licensed to the Apache Software Foundation (ASF) under one or more |
12 | | * contributor license agreements. See the NOTICE file distributed |
13 | | * with this work for additional information regarding copyright |
14 | | * ownership. The ASF licenses this file to you under the Apache |
15 | | * License, Version 2.0 (the "License"); you may not use this file |
16 | | * except in compliance with the License. You may obtain a copy of |
17 | | * the License at http://www.apache.org/licenses/LICENSE-2.0 . |
18 | | */ |
19 | | |
20 | | #include <sal/config.h> |
21 | | |
22 | | #include <sal/log.hxx> |
23 | | #include <comphelper/configuration.hxx> |
24 | | #include <o3tl/temporary.hxx> |
25 | | |
26 | | #include <vcl/glyphitem.hxx> |
27 | | #include <vcl/unohelp.hxx> |
28 | | #include <vcl/font/Feature.hxx> |
29 | | #include <vcl/font/FeatureParser.hxx> |
30 | | #include <vcl/svapp.hxx> |
31 | | |
32 | | #include <ImplLayoutArgs.hxx> |
33 | | #include <TextLayoutCache.hxx> |
34 | | #include <font/FontSelectPattern.hxx> |
35 | | #include <salgdi.hxx> |
36 | | #include <sallayout.hxx> |
37 | | |
38 | | #include <com/sun/star/i18n/CharacterIteratorMode.hpp> |
39 | | |
40 | | #include <unicode/uchar.h> |
41 | | #include <hb-ot.h> |
42 | | #include <hb-graphite2.h> |
43 | | #include <hb-icu.h> |
44 | | #include <hb-aat.h> |
45 | | |
46 | | #include <map> |
47 | | #include <memory> |
48 | | #include <set> |
49 | | |
50 | | GenericSalLayout::GenericSalLayout(LogicalFontInstance &rFont) |
51 | 14.2M | : m_GlyphItems(rFont) |
52 | 14.2M | , mpVertGlyphs(nullptr) |
53 | 14.2M | , mbFuzzing(comphelper::IsFuzzing()) |
54 | 14.2M | { |
55 | 14.2M | } |
56 | | |
57 | | GenericSalLayout::~GenericSalLayout() |
58 | 14.2M | { |
59 | 14.2M | if (mpVertGlyphs) |
60 | 458 | hb_set_destroy(mpVertGlyphs); |
61 | 14.2M | } |
62 | | |
63 | | void GenericSalLayout::ParseFeatures(std::u16string_view aName) |
64 | 6.98M | { |
65 | 6.98M | vcl::font::FeatureParser aParser(aName); |
66 | 6.98M | const OUString& sLanguage = aParser.getLanguage(); |
67 | 6.98M | if (!sLanguage.isEmpty()) |
68 | 0 | msLanguage = OUStringToOString(sLanguage, RTL_TEXTENCODING_ASCII_US); |
69 | | |
70 | 6.98M | for (auto const &rFeat : aParser.getFeatures()) |
71 | 289k | { |
72 | 289k | hb_feature_t aFeature { rFeat.m_nTag, rFeat.m_nValue, rFeat.m_nStart, rFeat.m_nEnd }; |
73 | 289k | maFeatures.push_back(aFeature); |
74 | 289k | } |
75 | 6.98M | } |
76 | | |
77 | | namespace { |
78 | | |
79 | | struct SubRun |
80 | | { |
81 | | int32_t mnMin; |
82 | | int32_t mnEnd; |
83 | | hb_script_t maScript; |
84 | | hb_direction_t maDirection; |
85 | | }; |
86 | | |
87 | | struct UnclusteredGlyphData |
88 | | { |
89 | | sal_Int32 m_nGlyphId; |
90 | | bool m_bUsed = false; |
91 | | |
92 | | explicit UnclusteredGlyphData(sal_Int32 nGlyphId) |
93 | 3.06M | : m_nGlyphId(nGlyphId) |
94 | 3.06M | { |
95 | 3.06M | } |
96 | | }; |
97 | | |
98 | | // This is a helper class to enable correct styling and glyph placement when a grapheme cluster is |
99 | | // split across multiple adjoining layouts. |
100 | | // |
101 | | // In order to justify text, we need glyphs grouped into grapheme clusters so diacritics will stay |
102 | | // attached to characters under adjustment. However, in order to correctly position and style |
103 | | // grapheme clusters that span multiple layouts, we need best-effort character-level position data. |
104 | | // |
105 | | // At time of writing, HarfBuzz cannot provide both types of information simultaneously. As a work- |
106 | | // around, this helper class runs HarfBuzz a second time to get the missing information. Should a |
107 | | // future version of HarfBuzz support this use case directly, this helper code should be deleted. |
108 | | // |
109 | | // See tdf#61444, tdf#71956, tdf#124116 |
110 | | class UnclusteredGlyphMapper |
111 | | { |
112 | | private: |
113 | | hb_buffer_t* m_pHbBuffer = nullptr; |
114 | | std::multimap<sal_Int32, UnclusteredGlyphData> m_aGlyphs; |
115 | | bool m_bEnable = false; |
116 | | |
117 | | public: |
118 | | UnclusteredGlyphMapper(bool bEnable, int nGlyphCapacity) |
119 | 6.98M | : m_bEnable(bEnable) |
120 | 6.98M | { |
121 | 6.98M | if (!m_bEnable) |
122 | 6.93M | { |
123 | 6.93M | return; |
124 | 6.93M | } |
125 | | |
126 | 42.2k | m_pHbBuffer = hb_buffer_create(); |
127 | 42.2k | hb_buffer_pre_allocate(m_pHbBuffer, nGlyphCapacity); |
128 | 42.2k | } |
129 | | |
130 | | ~UnclusteredGlyphMapper() |
131 | 6.98M | { |
132 | 6.98M | if (m_bEnable) |
133 | 42.2k | { |
134 | 42.2k | hb_buffer_destroy(m_pHbBuffer); |
135 | 42.2k | } |
136 | 6.98M | } |
137 | | |
138 | | [[nodiscard]] sal_Int32 RemapGlyph(sal_Int32 nClusterId, sal_Int32 nGlyphId) |
139 | 454M | { |
140 | 454M | if (auto it = m_aGlyphs.lower_bound(nClusterId); it != m_aGlyphs.end()) |
141 | 6.13M | { |
142 | 6.14M | for (; it != m_aGlyphs.end(); ++it) |
143 | 6.14M | { |
144 | 6.14M | if (it->second.m_nGlyphId == nGlyphId && !it->second.m_bUsed) |
145 | 6.13M | { |
146 | 6.13M | it->second.m_bUsed = true; |
147 | 6.13M | return it->first; |
148 | 6.13M | } |
149 | 6.14M | } |
150 | 6.13M | } |
151 | | |
152 | 448M | return nClusterId; |
153 | 454M | } |
154 | | |
155 | | void Reset() |
156 | 19.9M | { |
157 | 19.9M | for (auto& rElement : m_aGlyphs) |
158 | 3.06M | { |
159 | 3.06M | rElement.second.m_bUsed = false; |
160 | 3.06M | } |
161 | 19.9M | } |
162 | | |
163 | | void ShapeSubRun(const sal_Unicode* pStr, const int nLength, const SubRun& aSubRun, |
164 | | hb_font_t* pHbFont, const std::vector<hb_feature_t>& maFeatures, |
165 | | hb_language_t oHbLanguage) |
166 | 19.9M | { |
167 | 19.9M | if (!m_bEnable) |
168 | 19.8M | { |
169 | 19.8M | return; |
170 | 19.8M | } |
171 | | |
172 | 67.9k | m_aGlyphs.clear(); |
173 | | |
174 | 67.9k | hb_buffer_clear_contents(m_pHbBuffer); |
175 | | |
176 | 67.9k | const int nMinRunPos = aSubRun.mnMin; |
177 | 67.9k | const int nEndRunPos = aSubRun.mnEnd; |
178 | 67.9k | const int nRunLen = nEndRunPos - nMinRunPos; |
179 | | |
180 | 67.9k | int nHbFlags = HB_BUFFER_FLAGS_DEFAULT; |
181 | 67.9k | nHbFlags |= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL; |
182 | | |
183 | 67.9k | if (nMinRunPos == 0) |
184 | 1.23k | { |
185 | 1.23k | nHbFlags |= HB_BUFFER_FLAG_BOT; /* Beginning-of-text */ |
186 | 1.23k | } |
187 | | |
188 | 67.9k | if (nEndRunPos == nLength) |
189 | 1.84k | { |
190 | 1.84k | nHbFlags |= HB_BUFFER_FLAG_EOT; /* End-of-text */ |
191 | 1.84k | } |
192 | | |
193 | 67.9k | hb_buffer_set_flags(m_pHbBuffer, static_cast<hb_buffer_flags_t>(nHbFlags)); |
194 | | |
195 | 67.9k | hb_buffer_set_cluster_level(m_pHbBuffer, HB_BUFFER_CLUSTER_LEVEL_CHARACTERS); |
196 | | |
197 | 67.9k | hb_buffer_set_direction(m_pHbBuffer, aSubRun.maDirection); |
198 | 67.9k | hb_buffer_set_script(m_pHbBuffer, aSubRun.maScript); |
199 | 67.9k | hb_buffer_set_language(m_pHbBuffer, oHbLanguage); |
200 | | |
201 | 67.9k | hb_buffer_add_utf16(m_pHbBuffer, reinterpret_cast<uint16_t const*>(pStr), nLength, |
202 | 67.9k | nMinRunPos, nRunLen); |
203 | | |
204 | | // The shapers that we want HarfBuzz to use, in the order of |
205 | | // preference. |
206 | 67.9k | const char* const pHbShapers[] = { "graphite2", "ot", "fallback", nullptr }; |
207 | 67.9k | if (!hb_shape_full(pHbFont, m_pHbBuffer, maFeatures.data(), maFeatures.size(), pHbShapers)) |
208 | 0 | { |
209 | 0 | SAL_WARN("vcl.harfbuzz", "hb_shape_full failed"); |
210 | 0 | hb_buffer_set_length(m_pHbBuffer, 0); |
211 | 0 | } |
212 | | |
213 | 67.9k | int nRunGlyphCount = hb_buffer_get_length(m_pHbBuffer); |
214 | 67.9k | hb_glyph_info_t* pHbGlyphInfos = hb_buffer_get_glyph_infos(m_pHbBuffer, nullptr); |
215 | | |
216 | 3.13M | for (int i = 0; i < nRunGlyphCount; ++i) |
217 | 3.06M | { |
218 | 3.06M | int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint; |
219 | 3.06M | int32_t nCharPos = pHbGlyphInfos[i].cluster; |
220 | | |
221 | 3.06M | m_aGlyphs.emplace(nCharPos, UnclusteredGlyphData{ nGlyphIndex }); |
222 | 3.06M | } |
223 | 67.9k | } |
224 | | }; |
225 | | } |
226 | | |
227 | | namespace { |
228 | | int32_t GetVerticalOrientation(sal_UCS4 cCh, const LanguageTag& rTag) |
229 | 7.80M | { |
230 | | // Override orientation of fullwidth colon , semi-colon, |
231 | | // and Bopomofo tonal marks. |
232 | 7.80M | if ((cCh == 0xff1a || cCh == 0xff1b |
233 | 7.80M | || cCh == 0x2ca || cCh == 0x2cb || cCh == 0x2c7 || cCh == 0x2d9) |
234 | 3.42k | && rTag.getLanguage() == "zh") |
235 | 7 | return U_VO_TRANSFORMED_UPRIGHT; |
236 | | |
237 | 7.80M | return u_getIntPropertyValue(cCh, UCHAR_VERTICAL_ORIENTATION); |
238 | 7.80M | } |
239 | | } // namespace |
240 | | |
241 | | SalLayoutGlyphs GenericSalLayout::GetGlyphs() const |
242 | 3.77M | { |
243 | 3.77M | SalLayoutGlyphs glyphs; |
244 | 3.77M | glyphs.AppendImpl(m_GlyphItems.clone()); |
245 | 3.77M | return glyphs; |
246 | 3.77M | } |
247 | | |
248 | | void GenericSalLayout::SetNeedFallback(vcl::text::ImplLayoutArgs& rArgs, sal_Int32 nCharPos, |
249 | | sal_Int32 nCharEnd, bool bRightToLeft) |
250 | 8.80M | { |
251 | 8.80M | if (nCharPos < 0 || nCharPos == nCharEnd || mbFuzzing) |
252 | 8.80M | return; |
253 | | |
254 | 0 | if (!mxBreak.is()) |
255 | 0 | mxBreak = vcl::unohelper::CreateBreakIterator(); |
256 | |
|
257 | 0 | const css::lang::Locale& rLocale(rArgs.maLanguageTag.getLocale()); |
258 | | |
259 | | //if position nCharPos is missing in the font, grab the entire grapheme and |
260 | | //mark all glyphs as missing so the whole thing is rendered with the same |
261 | | //font |
262 | 0 | sal_Int32 nDone; |
263 | 0 | int nGraphemeEndPos = mxBreak->nextCharacters(rArgs.mrStr, nCharEnd - 1, rLocale, |
264 | 0 | css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); |
265 | | // Safely advance nCharPos in case it is a non-BMP character. |
266 | 0 | rArgs.mrStr.iterateCodePoints(&nCharPos); |
267 | 0 | int nGraphemeStartPos = |
268 | 0 | mxBreak->previousCharacters(rArgs.mrStr, nCharPos, rLocale, |
269 | 0 | css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); |
270 | | |
271 | | // tdf#107612 |
272 | | // If the start of the fallback run is Mongolian character and the previous |
273 | | // character is NNBSP, we want to include the NNBSP in the fallback since |
274 | | // it has special uses in Mongolian and have to be in the same text run to |
275 | | // work. |
276 | 0 | sal_Int32 nTempPos = nGraphemeStartPos; |
277 | 0 | if (nGraphemeStartPos > 0) |
278 | 0 | { |
279 | 0 | auto nCurrChar = rArgs.mrStr.iterateCodePoints(&nTempPos, 0); |
280 | 0 | auto nPrevChar = rArgs.mrStr.iterateCodePoints(&nTempPos, -1); |
281 | 0 | if (nPrevChar == 0x202F |
282 | 0 | && u_getIntPropertyValue(nCurrChar, UCHAR_SCRIPT) == USCRIPT_MONGOLIAN) |
283 | 0 | nGraphemeStartPos = nTempPos; |
284 | 0 | } |
285 | | |
286 | | //stay inside the Layout range (e.g. with tdf124116-1.odt) |
287 | 0 | nGraphemeStartPos = std::max(rArgs.mnMinCharPos, nGraphemeStartPos); |
288 | 0 | nGraphemeEndPos = std::min(rArgs.mnEndCharPos, nGraphemeEndPos); |
289 | |
|
290 | 0 | rArgs.AddFallbackRun(nGraphemeStartPos, nGraphemeEndPos, bRightToLeft); |
291 | 0 | } |
292 | | |
293 | | void GenericSalLayout::AdjustLayout(vcl::text::ImplLayoutArgs& rArgs) |
294 | 10.5M | { |
295 | 10.5M | SalLayout::AdjustLayout(rArgs); |
296 | | |
297 | 10.5M | if (!rArgs.mstJustification.empty()) |
298 | 504k | { |
299 | 504k | ApplyJustificationData(rArgs.mstJustification); |
300 | 504k | } |
301 | 10.0M | else if (rArgs.mnLayoutWidth) |
302 | 309 | { |
303 | 309 | Justify(rArgs.mnLayoutWidth); |
304 | 309 | } |
305 | 10.0M | else if ((rArgs.mnFlags & SalLayoutFlags::KerningAsian) |
306 | 8.07k | && !(rArgs.mnFlags & SalLayoutFlags::Vertical)) |
307 | 773 | { |
308 | | // apply asian kerning if the glyphs are not already formatted |
309 | 773 | ApplyAsianKerning(rArgs.mrStr); |
310 | 773 | } |
311 | 10.5M | } |
312 | | |
313 | | void GenericSalLayout::DrawText(SalGraphics& rSalGraphics) const |
314 | 13.9k | { |
315 | | //call platform dependent DrawText functions |
316 | 13.9k | rSalGraphics.DrawTextLayout( *this ); |
317 | 13.9k | } |
318 | | |
319 | | // Find if the nominal glyph of the character is an input to “vert” feature. |
320 | | // We don’t check for a specific script or language as it shouldn’t matter |
321 | | // here; if the glyph would be the result from applying “vert” for any |
322 | | // script/language then we want to always treat it as upright glyph. |
323 | | bool GenericSalLayout::HasVerticalAlternate(sal_UCS4 aChar, sal_UCS4 aVariationSelector) |
324 | 25.3k | { |
325 | 25.3k | sal_GlyphId nGlyphIndex = GetFont().GetGlyphIndex(aChar, aVariationSelector); |
326 | 25.3k | if (!nGlyphIndex) |
327 | 4.89k | return false; |
328 | | |
329 | 20.4k | if (!mpVertGlyphs) |
330 | 458 | { |
331 | 458 | hb_face_t* pHbFace = hb_font_get_face(GetFont().GetHbFont()); |
332 | 458 | mpVertGlyphs = hb_set_create(); |
333 | | |
334 | | // Find all GSUB lookups for “vert” feature. |
335 | 458 | hb_set_t* pLookups = hb_set_create(); |
336 | 458 | hb_tag_t const pFeatures[] = { HB_TAG('v','e','r','t'), HB_TAG_NONE }; |
337 | 458 | hb_ot_layout_collect_lookups(pHbFace, HB_OT_TAG_GSUB, nullptr, nullptr, pFeatures, pLookups); |
338 | 458 | if (!hb_set_is_empty(pLookups)) |
339 | 0 | { |
340 | | // Find the input glyphs in each lookup (i.e. the glyphs that |
341 | | // this lookup applies to). |
342 | 0 | hb_codepoint_t nIdx = HB_SET_VALUE_INVALID; |
343 | 0 | while (hb_set_next(pLookups, &nIdx)) |
344 | 0 | { |
345 | 0 | hb_set_t* pGlyphs = hb_set_create(); |
346 | 0 | hb_ot_layout_lookup_collect_glyphs(pHbFace, HB_OT_TAG_GSUB, nIdx, |
347 | 0 | nullptr, // glyphs before |
348 | 0 | pGlyphs, // glyphs input |
349 | 0 | nullptr, // glyphs after |
350 | 0 | nullptr); // glyphs out |
351 | 0 | hb_set_union(mpVertGlyphs, pGlyphs); |
352 | 0 | } |
353 | 0 | } |
354 | 458 | hb_set_destroy(pLookups); |
355 | 458 | } |
356 | | |
357 | 20.4k | return hb_set_has(mpVertGlyphs, nGlyphIndex) != 0; |
358 | 25.3k | } |
359 | | |
360 | | bool GenericSalLayout::LayoutText(vcl::text::ImplLayoutArgs& rArgs, const SalLayoutGlyphsImpl* pGlyphs) |
361 | 14.2M | { |
362 | | // No need to touch m_GlyphItems at all for an empty string. |
363 | 14.2M | if (rArgs.mnEndCharPos - rArgs.mnMinCharPos <= 0) |
364 | 9.94k | return true; |
365 | | |
366 | 14.2M | ImplLayoutRuns aFallbackRuns; |
367 | | |
368 | 14.2M | if (pGlyphs) |
369 | 7.29M | { |
370 | | // Work with pre-computed glyph items. |
371 | 7.29M | m_GlyphItems = *pGlyphs; |
372 | | |
373 | 7.29M | for(const GlyphItem& item : m_GlyphItems) |
374 | 332M | { |
375 | 332M | if(!item.glyphId()) |
376 | 19.7M | { |
377 | 19.7M | sal_Int32 nCurrCharPos = item.charPos(); |
378 | 19.7M | auto aCurrChar = rArgs.mrStr.iterateCodePoints(&nCurrCharPos, 0); |
379 | | // tdf#126111: fallback is meaningless for PUA codepoints |
380 | 19.7M | if (u_charType(aCurrChar) != U_PRIVATE_USE_CHAR) |
381 | 19.1M | aFallbackRuns.AddPos(item.charPos(), item.IsRTLGlyph()); |
382 | 19.7M | } |
383 | 332M | } |
384 | | |
385 | 7.29M | for (const auto& rRun : aFallbackRuns) |
386 | 3.16M | { |
387 | 3.16M | SetNeedFallback(rArgs, rRun.m_nMinRunPos, rRun.m_nEndRunPos, rRun.m_bRTL); |
388 | 3.16M | } |
389 | | |
390 | | // Some flags are set as a side effect of text layout, restore them here. |
391 | 7.29M | rArgs.mnFlags |= pGlyphs->GetFlags(); |
392 | 7.29M | return true; |
393 | 7.29M | } |
394 | | |
395 | 6.98M | hb_font_t *pHbFont = GetFont().GetHbFont(); |
396 | 6.98M | bool isGraphite = GetFont().IsGraphiteFont(); |
397 | | |
398 | | // tdf#163215: Identify layouts that don't have strict kashida position validation. |
399 | 6.98M | m_bHasFontKashidaPositions = false; |
400 | 6.98M | if (!(rArgs.mnFlags & SalLayoutFlags::DisableKashidaValidation)) |
401 | 6.98M | { |
402 | 6.98M | hb_face_t* pHbFace = hb_font_get_face(pHbFont); |
403 | 6.98M | m_bHasFontKashidaPositions = !hb_aat_layout_has_substitution(pHbFace); |
404 | 6.98M | } |
405 | | |
406 | 6.98M | int nGlyphCapacity = 2 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos); |
407 | 6.98M | m_GlyphItems.reserve(nGlyphCapacity); |
408 | | |
409 | 6.98M | const int nLength = rArgs.mrStr.getLength(); |
410 | 6.98M | const sal_Unicode *pStr = rArgs.mrStr.getStr(); |
411 | | |
412 | 6.98M | std::shared_ptr<const vcl::text::TextLayoutCache> pNewScriptRun; |
413 | 6.98M | vcl::text::TextLayoutCache const* pTextLayout; |
414 | 6.98M | if (rArgs.m_pTextLayoutCache) |
415 | 3.77M | { |
416 | 3.77M | pTextLayout = rArgs.m_pTextLayoutCache; // use cache! |
417 | 3.77M | } |
418 | 3.20M | else |
419 | 3.20M | { |
420 | | // tdf#92064, tdf#162663: |
421 | | // Also use the global LRU cache for full string script runs. |
422 | | // This obviates O(n^2) calls to vcl::ScriptRun::next() when laying out large paragraphs. |
423 | 3.20M | pNewScriptRun = vcl::text::TextLayoutCache::Create(rArgs.mrStr); |
424 | 3.20M | pTextLayout = pNewScriptRun.get(); |
425 | 3.20M | } |
426 | | |
427 | | // nBaseOffset is used to align vertical text to the center of rotated |
428 | | // horizontal text. That is the offset from original baseline to |
429 | | // the center of EM box. Maybe we can use OpenType base table to improve this |
430 | | // in the future. |
431 | 6.98M | double nBaseOffset = 0; |
432 | 6.98M | if (rArgs.mnFlags & SalLayoutFlags::Vertical) |
433 | 237k | { |
434 | 237k | hb_font_extents_t extents; |
435 | 237k | if (hb_font_get_h_extents(pHbFont, &extents)) |
436 | 237k | nBaseOffset = ( extents.ascender + extents.descender ) / 2.0; |
437 | 237k | } |
438 | | |
439 | 6.98M | UnclusteredGlyphMapper stClusterMapper{ |
440 | 6.98M | bool{ rArgs.mnFlags & SalLayoutFlags::UnclusteredGlyphs }, nGlyphCapacity |
441 | 6.98M | }; |
442 | | |
443 | 6.98M | hb_buffer_t* pHbBuffer = hb_buffer_create(); |
444 | 6.98M | hb_buffer_pre_allocate(pHbBuffer, nGlyphCapacity); |
445 | | |
446 | 6.98M | const vcl::font::FontSelectPattern& rFontSelData = GetFont().GetFontSelectPattern(); |
447 | 6.98M | if (rArgs.mnFlags & SalLayoutFlags::DisableKerning) |
448 | 2.52M | { |
449 | 2.52M | SAL_INFO("vcl.harfbuzz", "Disabling kerning for font: " << rFontSelData.maTargetName); |
450 | 2.52M | maFeatures.push_back({ HB_TAG('k','e','r','n'), 0, 0, static_cast<unsigned int>(-1) }); |
451 | 2.52M | } |
452 | | |
453 | 6.98M | if (rArgs.mnFlags & SalLayoutFlags::DisableLigatures) |
454 | 106k | { |
455 | 106k | SAL_INFO("vcl.harfbuzz", "Disabling ligatures for font: " << rFontSelData.maTargetName); |
456 | | |
457 | | // Both of these are optional ligatures, enabled by default but not for |
458 | | // orthographically-required ligatures. |
459 | 106k | maFeatures.push_back({ HB_TAG('l','i','g','a'), 0, 0, static_cast<unsigned int>(-1) }); |
460 | 106k | maFeatures.push_back({ HB_TAG('c','l','i','g'), 0, 0, static_cast<unsigned int>(-1) }); |
461 | 106k | } |
462 | | |
463 | 6.98M | ParseFeatures(rFontSelData.maTargetName); |
464 | | |
465 | 6.98M | double nXScale = 0; |
466 | 6.98M | double nYScale = 0; |
467 | 6.98M | GetFont().GetScale(&nXScale, &nYScale); |
468 | | |
469 | 6.98M | double nCurrX = 0.0; |
470 | 19.9M | while (true) |
471 | 19.9M | { |
472 | 19.9M | int nBidiMinRunPos, nBidiEndRunPos; |
473 | 19.9M | bool bRightToLeft; |
474 | 19.9M | if (!rArgs.GetNextRun(&nBidiMinRunPos, &nBidiEndRunPos, &bRightToLeft)) |
475 | 6.98M | break; |
476 | | |
477 | | // Find script subruns. |
478 | 12.9M | std::vector<SubRun> aSubRuns; |
479 | 12.9M | int nCurrentPos = nBidiMinRunPos; |
480 | 12.9M | size_t k = 0; |
481 | 6.49G | for (; k < pTextLayout->runs.size(); ++k) |
482 | 6.49G | { |
483 | 6.49G | vcl::text::Run const& rRun(pTextLayout->runs[k]); |
484 | 6.49G | if (rRun.nStart <= nCurrentPos && nCurrentPos < rRun.nEnd) |
485 | 12.9M | { |
486 | 12.9M | break; |
487 | 12.9M | } |
488 | 6.49G | } |
489 | | |
490 | 12.9M | if (isGraphite) |
491 | 0 | { |
492 | 0 | hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode); |
493 | 0 | aSubRuns.push_back({ nBidiMinRunPos, nBidiEndRunPos, aScript, bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR }); |
494 | 0 | } |
495 | 12.9M | else |
496 | 12.9M | { |
497 | 32.5M | while (nCurrentPos < nBidiEndRunPos && k < pTextLayout->runs.size()) |
498 | 19.5M | { |
499 | 19.5M | int32_t nMinRunPos = nCurrentPos; |
500 | 19.5M | int32_t nEndRunPos = std::min(pTextLayout->runs[k].nEnd, nBidiEndRunPos); |
501 | 19.5M | hb_direction_t aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR; |
502 | 19.5M | hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode); |
503 | | // For vertical text, further divide the runs based on character |
504 | | // orientation. |
505 | 19.5M | if (rArgs.mnFlags & SalLayoutFlags::Vertical) |
506 | 3.88M | { |
507 | 3.88M | sal_Int32 nIdx = nMinRunPos; |
508 | 11.6M | while (nIdx < nEndRunPos) |
509 | 7.80M | { |
510 | 7.80M | sal_Int32 nPrevIdx = nIdx; |
511 | 7.80M | sal_UCS4 aChar = rArgs.mrStr.iterateCodePoints(&nIdx); |
512 | 7.80M | int32_t aVo = GetVerticalOrientation(aChar, rArgs.maLanguageTag); |
513 | | |
514 | 7.80M | sal_UCS4 aVariationSelector = 0; |
515 | 7.80M | if (nIdx < nEndRunPos) |
516 | 3.93M | { |
517 | 3.93M | sal_Int32 nNextIdx = nIdx; |
518 | 3.93M | sal_UCS4 aNextChar = rArgs.mrStr.iterateCodePoints(&nNextIdx); |
519 | 3.93M | if (u_hasBinaryProperty(aNextChar, UCHAR_VARIATION_SELECTOR)) |
520 | 8.61k | { |
521 | 8.61k | nIdx = nNextIdx; |
522 | 8.61k | aVariationSelector = aNextChar; |
523 | 8.61k | } |
524 | 3.93M | } |
525 | | |
526 | | // Characters with U and Tu vertical orientation should |
527 | | // be shaped in vertical direction. But characters |
528 | | // with Tr should be shaped in vertical direction |
529 | | // only if they have vertical alternates, otherwise |
530 | | // they should be shaped in horizontal direction |
531 | | // and then rotated. |
532 | | // See http://unicode.org/reports/tr50/#vo |
533 | 7.80M | if (aVo == U_VO_UPRIGHT || aVo == U_VO_TRANSFORMED_UPRIGHT || |
534 | 3.74M | (aVo == U_VO_TRANSFORMED_ROTATED && |
535 | 25.3k | HasVerticalAlternate(aChar, aVariationSelector))) |
536 | 4.06M | { |
537 | 4.06M | aDirection = HB_DIRECTION_TTB; |
538 | 4.06M | } |
539 | 3.74M | else |
540 | 3.74M | { |
541 | 3.74M | aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR; |
542 | 3.74M | } |
543 | | |
544 | 7.80M | if (aSubRuns.empty() || aSubRuns.back().maDirection != aDirection || aSubRuns.back().maScript != aScript) |
545 | 4.25M | aSubRuns.push_back({ nPrevIdx, nIdx, aScript, aDirection }); |
546 | 3.55M | else |
547 | 3.55M | aSubRuns.back().mnEnd = nIdx; |
548 | 7.80M | } |
549 | 3.88M | } |
550 | 15.6M | else |
551 | 15.6M | { |
552 | 15.6M | aSubRuns.push_back({ nMinRunPos, nEndRunPos, aScript, aDirection }); |
553 | 15.6M | } |
554 | | |
555 | 19.5M | nCurrentPos = nEndRunPos; |
556 | 19.5M | ++k; |
557 | 19.5M | } |
558 | 12.9M | } |
559 | | |
560 | | // RTL subruns should be reversed to ensure that final glyph order is |
561 | | // correct. |
562 | 12.9M | if (bRightToLeft) |
563 | 1.11M | std::reverse(aSubRuns.begin(), aSubRuns.end()); |
564 | | |
565 | 12.9M | for (const auto& aSubRun : aSubRuns) |
566 | 19.9M | { |
567 | 19.9M | hb_buffer_clear_contents(pHbBuffer); |
568 | | |
569 | 19.9M | const int nMinRunPos = aSubRun.mnMin; |
570 | 19.9M | const int nEndRunPos = aSubRun.mnEnd; |
571 | 19.9M | const int nRunLen = nEndRunPos - nMinRunPos; |
572 | | |
573 | 19.9M | int nHbFlags = HB_BUFFER_FLAGS_DEFAULT; |
574 | | |
575 | | // Produce HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL that we use below. |
576 | 19.9M | nHbFlags |= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL; |
577 | | // Produce the unsafe-to-concat flag, which marks the cluster boundaries at which a |
578 | | // glyph subset cannot be cut out and reused without reshaping (see isSafeToBreak). |
579 | 19.9M | nHbFlags |= HB_BUFFER_FLAG_PRODUCE_UNSAFE_TO_CONCAT; |
580 | | |
581 | 19.9M | if (nMinRunPos == 0) |
582 | 3.27M | nHbFlags |= HB_BUFFER_FLAG_BOT; /* Beginning-of-text */ |
583 | 19.9M | if (nEndRunPos == nLength) |
584 | 3.40M | nHbFlags |= HB_BUFFER_FLAG_EOT; /* End-of-text */ |
585 | | |
586 | 19.9M | hb_buffer_set_direction(pHbBuffer, aSubRun.maDirection); |
587 | 19.9M | hb_buffer_set_script(pHbBuffer, aSubRun.maScript); |
588 | | |
589 | 19.9M | hb_language_t oHbLanguage = nullptr; |
590 | 19.9M | if (!msLanguage.isEmpty()) |
591 | 0 | { |
592 | 0 | oHbLanguage = hb_language_from_string(msLanguage.getStr(), msLanguage.getLength()); |
593 | 0 | } |
594 | 19.9M | else |
595 | 19.9M | { |
596 | 19.9M | OString sLanguage |
597 | 19.9M | = OUStringToOString(rArgs.maLanguageTag.getBcp47(), RTL_TEXTENCODING_ASCII_US); |
598 | 19.9M | oHbLanguage = hb_language_from_string(sLanguage.getStr(), sLanguage.getLength()); |
599 | 19.9M | } |
600 | | |
601 | 19.9M | hb_buffer_set_language(pHbBuffer, oHbLanguage); |
602 | | |
603 | 19.9M | hb_buffer_set_flags(pHbBuffer, static_cast<hb_buffer_flags_t>(nHbFlags)); |
604 | 19.9M | hb_buffer_add_utf16( |
605 | 19.9M | pHbBuffer, reinterpret_cast<uint16_t const *>(pStr), nLength, |
606 | 19.9M | nMinRunPos, nRunLen); |
607 | | |
608 | | // The shapers that we want HarfBuzz to use, in the order of |
609 | | // preference. |
610 | 19.9M | const char*const pHbShapers[] = { "graphite2", "ot", "fallback", nullptr }; |
611 | 19.9M | if (!hb_shape_full(pHbFont, pHbBuffer, maFeatures.data(), maFeatures.size(), pHbShapers)) |
612 | 0 | { |
613 | 0 | SAL_WARN("vcl.harfbuzz", "hb_shape_full failed"); |
614 | 0 | hb_buffer_set_length(pHbBuffer, 0); |
615 | 0 | } |
616 | | |
617 | | // Populate glyph cluster remapping data |
618 | 19.9M | stClusterMapper.ShapeSubRun(pStr, nLength, aSubRun, pHbFont, maFeatures, oHbLanguage); |
619 | | |
620 | 19.9M | int nRunGlyphCount = hb_buffer_get_length(pHbBuffer); |
621 | 19.9M | hb_glyph_info_t *pHbGlyphInfos = hb_buffer_get_glyph_infos(pHbBuffer, nullptr); |
622 | 19.9M | hb_glyph_position_t *pHbPositions = hb_buffer_get_glyph_positions(pHbBuffer, nullptr); |
623 | | |
624 | | // tdf#164106: Grapheme clusters can be split across multiple layouts. To do this, |
625 | | // the complete string is laid out, and only the necessary glyphs are extracted. |
626 | | // These sub-layouts are positioned side-by-side to form the complete text. |
627 | | // This approach is good enough for most diacritic cases, but it cannot handle cases |
628 | | // where a glyph with an advance is reordered into a different sub-layout. |
629 | 19.9M | bool bStartClusterOutOfOrder = false; |
630 | 19.9M | bool bEndClusterOutOfOrder = false; |
631 | 19.9M | { |
632 | 19.9M | double nNormalAdvance = 0.0; |
633 | 19.9M | double nStartAdvance = 0.0; |
634 | 19.9M | double nEndAdvance = 0.0; |
635 | | |
636 | 19.9M | auto fnHandleGlyph = [&](int i) |
637 | 227M | { |
638 | 227M | int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint; |
639 | 227M | int32_t nCluster = pHbGlyphInfos[i].cluster; |
640 | 227M | auto nOrigCharPos = stClusterMapper.RemapGlyph(nCluster, nGlyphIndex); |
641 | | |
642 | 227M | double nAdvance = 0.0; |
643 | 227M | if (aSubRun.maDirection == HB_DIRECTION_TTB) |
644 | 4.06M | { |
645 | 4.06M | nAdvance = -pHbPositions[i].y_advance; |
646 | 4.06M | } |
647 | 223M | else |
648 | 223M | { |
649 | 223M | nAdvance = pHbPositions[i].x_advance; |
650 | 223M | } |
651 | | |
652 | 227M | nNormalAdvance += nAdvance; |
653 | | |
654 | 227M | if (nOrigCharPos < rArgs.mnDrawMinCharPos) |
655 | 153k | { |
656 | 153k | nStartAdvance += nAdvance; |
657 | 153k | if (nStartAdvance != nNormalAdvance) |
658 | 0 | { |
659 | 0 | bStartClusterOutOfOrder = true; |
660 | 0 | } |
661 | 153k | } |
662 | | |
663 | 227M | if (nOrigCharPos < rArgs.mnDrawEndCharPos) |
664 | 214M | { |
665 | 214M | nEndAdvance += nAdvance; |
666 | 214M | if (nEndAdvance != nNormalAdvance) |
667 | 0 | { |
668 | 0 | bEndClusterOutOfOrder = true; |
669 | 0 | } |
670 | 214M | } |
671 | 227M | }; |
672 | | |
673 | 19.9M | if (bRightToLeft) |
674 | 1.43M | { |
675 | 50.6M | for (int i = nRunGlyphCount - 1; i >= 0; --i) |
676 | 49.2M | { |
677 | 49.2M | fnHandleGlyph(i); |
678 | 49.2M | } |
679 | 1.43M | } |
680 | 18.4M | else |
681 | 18.4M | { |
682 | 196M | for (int i = 0; i < nRunGlyphCount; ++i) |
683 | 178M | { |
684 | 178M | fnHandleGlyph(i); |
685 | 178M | } |
686 | 18.4M | } |
687 | | |
688 | 19.9M | stClusterMapper.Reset(); |
689 | 19.9M | } |
690 | | |
691 | 247M | for (int i = 0; i < nRunGlyphCount; ++i) { |
692 | 227M | int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint; |
693 | 227M | int32_t nCharPos = pHbGlyphInfos[i].cluster; |
694 | 227M | int32_t nCharCount = 0; |
695 | 227M | bool bInCluster = false; |
696 | 227M | bool bClusterStart = false; |
697 | | |
698 | | // Find the number of characters that make up this glyph. |
699 | 227M | if (!bRightToLeft) |
700 | 178M | { |
701 | | // If the cluster is the same as previous glyph, then this |
702 | | // already consumed, skip. |
703 | 178M | if (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster) |
704 | 992k | { |
705 | 992k | nCharCount = 0; |
706 | 992k | bInCluster = true; |
707 | 992k | } |
708 | 177M | else |
709 | 177M | { |
710 | | // Find the next glyph with a different cluster, or the |
711 | | // end of text. |
712 | 177M | int j = i; |
713 | 177M | int32_t nNextCharPos = nCharPos; |
714 | 514M | while (nNextCharPos == nCharPos && j < nRunGlyphCount) |
715 | 336M | nNextCharPos = pHbGlyphInfos[j++].cluster; |
716 | | |
717 | 177M | if (nNextCharPos == nCharPos) |
718 | 18.4M | nNextCharPos = nEndRunPos; |
719 | 177M | nCharCount = nNextCharPos - nCharPos; |
720 | 177M | if ((i == 0 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i - 1].cluster) && |
721 | 177M | (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster)) |
722 | 605k | bClusterStart = true; |
723 | 177M | } |
724 | 178M | } |
725 | 49.2M | else |
726 | 49.2M | { |
727 | | // If the cluster is the same as previous glyph, then this |
728 | | // will be consumed later, skip. |
729 | 49.2M | if (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster) |
730 | 136k | { |
731 | 136k | nCharCount = 0; |
732 | 136k | bInCluster = true; |
733 | 136k | } |
734 | 49.0M | else |
735 | 49.0M | { |
736 | | // Find the previous glyph with a different cluster, or |
737 | | // the end of text. |
738 | 49.0M | int j = i; |
739 | 49.0M | int32_t nNextCharPos = nCharPos; |
740 | 145M | while (nNextCharPos == nCharPos && j >= 0) |
741 | 96.8M | nNextCharPos = pHbGlyphInfos[j--].cluster; |
742 | | |
743 | 49.0M | if (nNextCharPos == nCharPos) |
744 | 1.43M | nNextCharPos = nEndRunPos; |
745 | 49.0M | nCharCount = nNextCharPos - nCharPos; |
746 | 49.0M | if ((i == nRunGlyphCount - 1 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i + 1].cluster) && |
747 | 49.0M | (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster)) |
748 | 79.8k | bClusterStart = true; |
749 | 49.0M | } |
750 | 49.2M | } |
751 | | |
752 | | // if needed request glyph fallback by updating LayoutArgs |
753 | 227M | auto nOrigCharPos = stClusterMapper.RemapGlyph(nCharPos, nGlyphIndex); |
754 | 227M | if (!nGlyphIndex) |
755 | 25.6M | { |
756 | | // Only request fallback for grapheme clusters that are drawn |
757 | 25.6M | if (nOrigCharPos >= rArgs.mnDrawMinCharPos |
758 | 25.6M | && nOrigCharPos < rArgs.mnDrawEndCharPos) |
759 | 25.6M | { |
760 | 25.6M | sal_Int32 nCurrCharPos = nOrigCharPos; |
761 | 25.6M | auto aCurrChar = rArgs.mrStr.iterateCodePoints(&nCurrCharPos, 0); |
762 | | // tdf#126111: fallback is meaningless for PUA codepoints |
763 | 25.6M | if (u_charType(aCurrChar) != U_PRIVATE_USE_CHAR) |
764 | 23.8M | { |
765 | 23.8M | aFallbackRuns.AddPos(nOrigCharPos, bRightToLeft); |
766 | 23.8M | if (SalLayoutFlags::ForFallback & rArgs.mnFlags) |
767 | 0 | continue; |
768 | 23.8M | } |
769 | 25.6M | } |
770 | 25.6M | } |
771 | | |
772 | 227M | GlyphItemFlags nGlyphFlags = GlyphItemFlags::NONE; |
773 | 227M | if (bRightToLeft) |
774 | 49.2M | nGlyphFlags |= GlyphItemFlags::IS_RTL_GLYPH; |
775 | | |
776 | 227M | if (bClusterStart) |
777 | 685k | nGlyphFlags |= GlyphItemFlags::IS_CLUSTER_START; |
778 | | |
779 | 227M | if (bInCluster) |
780 | 1.12M | nGlyphFlags |= GlyphItemFlags::IS_IN_CLUSTER; |
781 | | |
782 | 227M | sal_UCS4 aChar |
783 | 227M | = rArgs.mrStr.iterateCodePoints(&o3tl::temporary(sal_Int32(nCharPos)), 0); |
784 | | |
785 | 227M | if (u_isUWhiteSpace(aChar)) |
786 | 4.79M | nGlyphFlags |= GlyphItemFlags::IS_SPACING; |
787 | | |
788 | 227M | hb_glyph_flags_t const eHbGlyphFlags |
789 | 227M | = hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i]); |
790 | 227M | if (eHbGlyphFlags & HB_GLYPH_FLAG_UNSAFE_TO_CONCAT) |
791 | 98.6M | nGlyphFlags |= GlyphItemFlags::IS_UNSAFE_TO_CONCAT; |
792 | | |
793 | 227M | if (!m_bHasFontKashidaPositions |
794 | 227M | || (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i]) |
795 | 227M | & HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL)) |
796 | 32.5k | nGlyphFlags |= GlyphItemFlags::IS_SAFE_TO_INSERT_KASHIDA; |
797 | | |
798 | 227M | double nAdvance, nXOffset, nYOffset; |
799 | 227M | if (aSubRun.maDirection == HB_DIRECTION_TTB) |
800 | 4.06M | { |
801 | 4.06M | nGlyphFlags |= GlyphItemFlags::IS_VERTICAL; |
802 | | |
803 | 4.06M | nAdvance = -pHbPositions[i].y_advance; |
804 | 4.06M | nXOffset = -pHbPositions[i].y_offset; |
805 | 4.06M | nYOffset = -pHbPositions[i].x_offset - nBaseOffset; |
806 | 4.06M | } |
807 | 223M | else |
808 | 223M | { |
809 | 223M | nAdvance = pHbPositions[i].x_advance; |
810 | 223M | nXOffset = pHbPositions[i].x_offset; |
811 | 223M | nYOffset = -pHbPositions[i].y_offset; |
812 | 223M | } |
813 | | |
814 | 227M | nAdvance = nAdvance * nXScale; |
815 | 227M | nXOffset = nXOffset * nXScale; |
816 | 227M | nYOffset = nYOffset * nYScale; |
817 | 227M | if (!GetSubpixelPositioning()) |
818 | 18.0M | { |
819 | 18.0M | nAdvance = std::round(nAdvance); |
820 | 18.0M | nXOffset = std::round(nXOffset); |
821 | 18.0M | nYOffset = std::round(nYOffset); |
822 | 18.0M | } |
823 | | |
824 | 227M | basegfx::B2DPoint aNewPos(nCurrX + nXOffset, nYOffset); |
825 | 227M | const GlyphItem aGI(nCharPos, nCharCount, nGlyphIndex, aNewPos, nGlyphFlags, |
826 | 227M | nAdvance, nXOffset, nYOffset, nOrigCharPos); |
827 | | |
828 | 227M | auto nLowerBound = (bStartClusterOutOfOrder ? aGI.charPos() : aGI.origCharPos()); |
829 | 227M | auto nUpperBound = (bEndClusterOutOfOrder ? aGI.charPos() : aGI.origCharPos()); |
830 | 227M | if (nLowerBound >= rArgs.mnDrawMinCharPos && nUpperBound < rArgs.mnDrawEndCharPos) |
831 | 214M | { |
832 | 214M | m_GlyphItems.push_back(aGI); |
833 | 214M | } |
834 | | |
835 | 227M | if (nLowerBound >= rArgs.mnDrawOriginCluster |
836 | 227M | && nUpperBound < rArgs.mnDrawEndCharPos) |
837 | 214M | { |
838 | 214M | nCurrX += nAdvance; |
839 | 214M | } |
840 | 227M | } |
841 | 19.9M | } |
842 | 12.9M | } |
843 | | |
844 | 6.98M | hb_buffer_destroy(pHbBuffer); |
845 | | |
846 | 6.98M | for (const auto& rRun : aFallbackRuns) |
847 | 5.63M | { |
848 | 5.63M | SetNeedFallback(rArgs, rRun.m_nMinRunPos, rRun.m_nEndRunPos, rRun.m_bRTL); |
849 | 5.63M | } |
850 | | |
851 | | // Some flags are set as a side effect of text layout, save them here. |
852 | 6.98M | if (rArgs.mnFlags & SalLayoutFlags::GlyphItemsOnly) |
853 | 3.77M | m_GlyphItems.SetFlags(rArgs.mnFlags); |
854 | | |
855 | 6.98M | return true; |
856 | 6.98M | } |
857 | | |
858 | | void GenericSalLayout::GetCharWidths(std::vector<double>& rCharWidths, const OUString& rStr) const |
859 | 7.41M | { |
860 | 7.41M | const int nCharCount = mnEndCharPos - mnMinCharPos; |
861 | | |
862 | 7.41M | rCharWidths.clear(); |
863 | 7.41M | rCharWidths.resize(nCharCount, 0); |
864 | | |
865 | 7.41M | css::uno::Reference<css::i18n::XBreakIterator> xBreak; |
866 | 7.41M | const css::lang::Locale& rLocale(maLanguageTag.getLocale()); |
867 | | |
868 | 7.41M | for (auto const& aGlyphItem : m_GlyphItems) |
869 | 339M | { |
870 | 339M | if (aGlyphItem.charPos() >= mnEndCharPos) |
871 | 0 | continue; |
872 | | |
873 | 339M | unsigned int nGraphemeCount = 0; |
874 | 339M | if (aGlyphItem.charCount() > 1 && aGlyphItem.newWidth() != 0 && !rStr.isEmpty()) |
875 | 491k | { |
876 | | // We are calculating DX array for cursor positions and this is a |
877 | | // ligature, find out how many grapheme clusters are in it. |
878 | 491k | if (!xBreak.is()) |
879 | 266k | xBreak = mxBreak.is() ? mxBreak : vcl::unohelper::CreateBreakIterator(); |
880 | | |
881 | | // Count grapheme clusters in the ligature. |
882 | 491k | sal_Int32 nDone; |
883 | 491k | sal_Int32 nPos = aGlyphItem.charPos(); |
884 | 990k | while (nPos < aGlyphItem.charPos() + aGlyphItem.charCount()) |
885 | 498k | { |
886 | 498k | nPos = xBreak->nextCharacters(rStr, nPos, rLocale, |
887 | 498k | css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); |
888 | 498k | nGraphemeCount++; |
889 | 498k | } |
890 | 491k | } |
891 | | |
892 | 339M | if (nGraphemeCount > 1) |
893 | 4.88k | { |
894 | | // More than one grapheme cluster, we want to distribute the glyph |
895 | | // width over them. |
896 | 4.88k | std::vector<double> aWidths(nGraphemeCount); |
897 | | |
898 | | // Check if the glyph has ligature caret positions. |
899 | 4.88k | unsigned int nCarets = nGraphemeCount; |
900 | 4.88k | std::vector<hb_position_t> aCarets(nGraphemeCount); |
901 | 4.88k | hb_ot_layout_get_ligature_carets(GetFont().GetHbFont(), |
902 | 4.88k | aGlyphItem.IsRTLGlyph() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR, |
903 | 4.88k | aGlyphItem.glyphId(), 0, &nCarets, aCarets.data()); |
904 | | |
905 | | // Carets are 1-less than the grapheme count (since the last |
906 | | // position is defined by glyph width), if the count does not |
907 | | // match, ignore it. |
908 | 4.88k | if (nCarets == nGraphemeCount - 1) |
909 | 0 | { |
910 | | // Scale the carets and apply glyph offset to them since they |
911 | | // are based on the default glyph metrics. |
912 | 0 | double fScale = 0; |
913 | 0 | GetFont().GetScale(&fScale, nullptr); |
914 | 0 | for (size_t i = 0; i < nCarets; i++) |
915 | 0 | aCarets[i] = (aCarets[i] * fScale) + aGlyphItem.xOffset(); |
916 | | |
917 | | // Use the glyph width for the last caret. |
918 | 0 | aCarets[nCarets] = aGlyphItem.newWidth(); |
919 | | |
920 | | // Carets are absolute from the X origin of the glyph, turn |
921 | | // them to relative widths that we need below. |
922 | 0 | for (size_t i = 0; i < nGraphemeCount; i++) |
923 | 0 | aWidths[i] = aCarets[i] - (i == 0 ? 0 : aCarets[i - 1]); |
924 | | |
925 | | // Carets are in visual order, but we want widths in logical |
926 | | // order. |
927 | 0 | if (aGlyphItem.IsRTLGlyph()) |
928 | 0 | std::reverse(aWidths.begin(), aWidths.end()); |
929 | 0 | } |
930 | 4.88k | else |
931 | 4.88k | { |
932 | | // The glyph has no carets, distribute the width evenly. |
933 | 4.88k | auto nWidth = aGlyphItem.newWidth() / nGraphemeCount; |
934 | 4.88k | std::fill(aWidths.begin(), aWidths.end(), nWidth); |
935 | | |
936 | | // Add rounding difference to the last component to maintain |
937 | | // ligature width. |
938 | 4.88k | aWidths[nGraphemeCount - 1] += aGlyphItem.newWidth() - (nWidth * nGraphemeCount); |
939 | 4.88k | } |
940 | | |
941 | | // Set the width of each grapheme cluster. |
942 | 4.88k | sal_Int32 nDone; |
943 | 4.88k | sal_Int32 nPos = aGlyphItem.charPos(); |
944 | 4.88k | for (auto nWidth : aWidths) |
945 | 12.4k | { |
946 | 12.4k | rCharWidths[nPos - mnMinCharPos] += nWidth; |
947 | 12.4k | nPos = xBreak->nextCharacters(rStr, nPos, rLocale, |
948 | 12.4k | css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); |
949 | 12.4k | } |
950 | 4.88k | } |
951 | 339M | else |
952 | 339M | rCharWidths[aGlyphItem.charPos() - mnMinCharPos] += aGlyphItem.newWidth(); |
953 | 339M | } |
954 | 7.41M | } |
955 | | |
956 | | // - stJustification: |
957 | | // - contains adjustments to glyph advances (usually due to justification). |
958 | | // - contains kashida insertion positions, for Arabic script justification. |
959 | | // - The number of kashidas is calculated from the adjusted advances. |
960 | | void GenericSalLayout::ApplyJustificationData(const JustificationData& rstJustification) |
961 | 504k | { |
962 | 504k | int nCharCount = mnEndCharPos - mnMinCharPos; |
963 | 504k | std::vector<double> aOldCharWidths; |
964 | 504k | std::unique_ptr<double[]> const pNewCharWidths(new double[nCharCount]); |
965 | | |
966 | | // Get the natural character widths (i.e. before applying DX adjustments). |
967 | 504k | GetCharWidths(aOldCharWidths, {}); |
968 | | |
969 | | // Calculate the character widths after DX adjustments. |
970 | 20.6M | for (int i = 0; i < nCharCount; ++i) |
971 | 20.1M | { |
972 | 20.1M | if (i == 0) |
973 | 504k | { |
974 | 504k | pNewCharWidths[i] = rstJustification.GetTotalAdvance(mnMinCharPos + i); |
975 | 504k | } |
976 | 19.6M | else |
977 | 19.6M | { |
978 | 19.6M | pNewCharWidths[i] = rstJustification.GetTotalAdvance(mnMinCharPos + i) |
979 | 19.6M | - rstJustification.GetTotalAdvance(mnMinCharPos + i - 1); |
980 | 19.6M | } |
981 | 20.1M | } |
982 | | |
983 | | // Map of Kashida insertion points (in the glyph items vector) and the |
984 | | // requested width. |
985 | 504k | std::map<size_t, std::pair<double, double>> pKashidas; |
986 | | |
987 | | // The accumulated difference in X position. |
988 | 504k | double nDelta = 0; |
989 | | |
990 | | // Apply the DX adjustments to glyph positions and widths. |
991 | 504k | size_t i = 0; |
992 | 11.8M | while (i < m_GlyphItems.size()) |
993 | 11.3M | { |
994 | | // Accumulate the width difference for all characters corresponding to |
995 | | // this glyph. |
996 | 11.3M | int nCharPos = m_GlyphItems[i].charPos() - mnMinCharPos; |
997 | 11.3M | double nDiff = 0; |
998 | 22.6M | for (int j = 0; j < m_GlyphItems[i].charCount(); j++) |
999 | 11.3M | nDiff += pNewCharWidths[nCharPos + j] - aOldCharWidths[nCharPos + j]; |
1000 | | |
1001 | 11.3M | if (!m_GlyphItems[i].IsRTLGlyph()) |
1002 | 9.34M | { |
1003 | | // Adjust the width and position of the first (leftmost) glyph in |
1004 | | // the cluster. |
1005 | 9.34M | m_GlyphItems[i].addNewWidth(nDiff); |
1006 | 9.34M | m_GlyphItems[i].adjustLinearPosX(nDelta); |
1007 | | |
1008 | | // Adjust the position of the rest of the glyphs in the cluster. |
1009 | 9.34M | while (++i < m_GlyphItems.size()) |
1010 | 8.88M | { |
1011 | 8.88M | if (!m_GlyphItems[i].IsInCluster()) |
1012 | 8.88M | break; |
1013 | 2.11k | m_GlyphItems[i].adjustLinearPosX(nDelta); |
1014 | 2.11k | } |
1015 | 9.34M | } |
1016 | 1.96M | else if (m_GlyphItems[i].IsInCluster()) |
1017 | 1.31k | { |
1018 | | // RTL glyph in the middle of the cluster, will be handled in the |
1019 | | // loop below. |
1020 | 1.31k | i++; |
1021 | 1.31k | } |
1022 | 1.96M | else // RTL |
1023 | 1.96M | { |
1024 | | // Adjust the width and position of the first (rightmost) glyph in |
1025 | | // the cluster. This is RTL, so we put all the adjustment to the |
1026 | | // left of the glyph. |
1027 | 1.96M | m_GlyphItems[i].addNewWidth(nDiff); |
1028 | 1.96M | m_GlyphItems[i].adjustLinearPosX(nDelta + nDiff); |
1029 | | |
1030 | | // Adjust the X position of the rest of the glyphs in the cluster. |
1031 | | // We iterate backwards since this is an RTL glyph. |
1032 | 1.96M | for (size_t j = i; j >= 1 && m_GlyphItems[j - 1].IsInCluster(); --j) |
1033 | 1.32k | m_GlyphItems[j - 1].adjustLinearPosX(nDelta + nDiff); |
1034 | | |
1035 | | // This is a Kashida insertion position, mark it. Kashida glyphs |
1036 | | // will be inserted below. |
1037 | 1.96M | if (rstJustification.GetPositionHasKashida(mnMinCharPos + nCharPos).value_or(false)) |
1038 | 1.84k | { |
1039 | 1.84k | pKashidas[i] = { nDiff, pNewCharWidths[nCharPos] }; |
1040 | 1.84k | } |
1041 | | |
1042 | 1.96M | i++; |
1043 | 1.96M | } |
1044 | | |
1045 | | // Increment the delta, the loop above makes sure we do so only once |
1046 | | // for every character (cluster) not for every glyph (otherwise we |
1047 | | // would apply it multiple times for each glyph belonging to the same |
1048 | | // character which is wrong as DX adjustments are character based). |
1049 | 11.3M | nDelta += nDiff; |
1050 | 11.3M | } |
1051 | | |
1052 | | // Insert Kashida glyphs. |
1053 | 504k | if (pKashidas.empty()) |
1054 | 504k | return; |
1055 | | |
1056 | | // Find Kashida glyph width and index. |
1057 | 15 | sal_GlyphId nKashidaIndex = GetFont().GetGlyphIndex(0x0640); |
1058 | 15 | double nKashidaWidth = GetFont().GetKashidaWidth(); |
1059 | 15 | if (!GetSubpixelPositioning()) |
1060 | 1 | nKashidaWidth = std::ceil(nKashidaWidth); |
1061 | | |
1062 | 15 | if (nKashidaWidth <= 0) |
1063 | 15 | { |
1064 | 15 | SAL_WARN("vcl.gdi", "Asked to insert Kashidas in a font with bogus Kashida width"); |
1065 | 15 | return; |
1066 | 15 | } |
1067 | | |
1068 | 0 | size_t nInserted = 0; |
1069 | 0 | for (auto const& pKashida : pKashidas) |
1070 | 0 | { |
1071 | 0 | auto pGlyphIter = m_GlyphItems.begin() + nInserted + pKashida.first; |
1072 | | |
1073 | | // The total Kashida width. |
1074 | 0 | auto const& [nTotalWidth, nClusterWidth] = pKashida.second; |
1075 | | |
1076 | | // Number of times to repeat each Kashida. |
1077 | 0 | int nCopies = 1; |
1078 | 0 | if (nTotalWidth > nKashidaWidth) |
1079 | 0 | nCopies = nTotalWidth / nKashidaWidth; |
1080 | | |
1081 | | // See if we can improve the fit by adding an extra Kashidas and |
1082 | | // squeezing them together a bit. |
1083 | 0 | double nOverlap = 0; |
1084 | 0 | double nShortfall = nTotalWidth - nKashidaWidth * nCopies; |
1085 | 0 | if (nShortfall > 0) |
1086 | 0 | { |
1087 | 0 | ++nCopies; |
1088 | 0 | double nExcess = nCopies * nKashidaWidth - nTotalWidth; |
1089 | 0 | if (nExcess > 0) |
1090 | 0 | nOverlap = nExcess / (nCopies - 1); |
1091 | 0 | } |
1092 | |
|
1093 | 0 | basegfx::B2DPoint aPos = pGlyphIter->linearPos(); |
1094 | 0 | int nCharPos = pGlyphIter->charPos(); |
1095 | 0 | GlyphItemFlags const nFlags = GlyphItemFlags::IS_IN_CLUSTER | GlyphItemFlags::IS_RTL_GLYPH; |
1096 | | // Move to the left side of the adjusted width and start inserting |
1097 | | // glyphs there. |
1098 | 0 | aPos.adjustX(-nClusterWidth + pGlyphIter->origWidth()); |
1099 | 0 | while (nCopies--) |
1100 | 0 | { |
1101 | 0 | GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, 0, 0, 0, nCharPos); |
1102 | 0 | pGlyphIter = m_GlyphItems.insert(pGlyphIter, aKashida); |
1103 | 0 | aPos.adjustX(nKashidaWidth - nOverlap); |
1104 | 0 | ++pGlyphIter; |
1105 | 0 | ++nInserted; |
1106 | 0 | } |
1107 | 0 | } |
1108 | 0 | } |
1109 | | |
1110 | 0 | bool GenericSalLayout::HasFontKashidaPositions() const { return m_bHasFontKashidaPositions; } |
1111 | | |
1112 | | // Kashida will be inserted between nCharPos and nNextCharPos. |
1113 | | bool GenericSalLayout::IsKashidaPosValid(int nCharPos, int nNextCharPos) const |
1114 | 0 | { |
1115 | | // Search for glyph items corresponding to nCharPos and nNextCharPos. |
1116 | 0 | auto const aGlyph = std::find_if(m_GlyphItems.begin(), m_GlyphItems.end(), |
1117 | 0 | [&](const GlyphItem& g) { return g.charPos() == nCharPos; }); |
1118 | 0 | auto const aNextGlyph = std::find_if(m_GlyphItems.begin(), m_GlyphItems.end(), |
1119 | 0 | [&](const GlyphItem& g) { return g.charPos() == nNextCharPos; }); |
1120 | | |
1121 | | // If either is not found then a ligature is created at this position, we |
1122 | | // can’t insert Kashida here. |
1123 | 0 | if (aGlyph == m_GlyphItems.end() || aNextGlyph == m_GlyphItems.end()) |
1124 | 0 | return false; |
1125 | | |
1126 | | // If the either character is not supported by this layout, return false so |
1127 | | // that fallback layouts would be checked for it. |
1128 | 0 | if (aGlyph->glyphId() == 0 || aNextGlyph->glyphId() == 0) |
1129 | 0 | return false; |
1130 | | |
1131 | | // Lastly check if this position is kashida-safe. |
1132 | 0 | return aNextGlyph->IsSafeToInsertKashida(); |
1133 | 0 | } |
1134 | | |
1135 | | void GenericSalLayout::drawSalLayout(void* pSurface, const basegfx::BColor& rTextColor, bool bAntiAliased) const |
1136 | 0 | { |
1137 | 0 | Application::GetDefaultDevice()->GetGraphics()->DrawSalLayout(*this, pSurface, rTextColor, bAntiAliased); |
1138 | 0 | } |
1139 | | |
1140 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |