tdf#154285 Check upper bound of arguments in SbRtl_Minute function
[LibreOffice.git] / vcl / source / gdi / CommonSalLayout.cxx
blobb40851a2f18cab2785e491ae94e65b0f1e42bcd2
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <sal/config.h>
22 #include <sal/log.hxx>
23 #include <comphelper/configuration.hxx>
24 #include <o3tl/temporary.hxx>
26 #include <vcl/unohelp.hxx>
27 #include <vcl/font/Feature.hxx>
28 #include <vcl/font/FeatureParser.hxx>
29 #include <vcl/svapp.hxx>
31 #include <ImplLayoutArgs.hxx>
32 #include <TextLayoutCache.hxx>
33 #include <font/FontSelectPattern.hxx>
34 #include <salgdi.hxx>
35 #include <sallayout.hxx>
37 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
39 #include <unicode/uchar.h>
40 #include <hb-ot.h>
41 #include <hb-graphite2.h>
42 #include <hb-icu.h>
43 #include <hb-aat.h>
45 #include <map>
46 #include <memory>
47 #include <set>
49 GenericSalLayout::GenericSalLayout(LogicalFontInstance &rFont)
50 : m_GlyphItems(rFont)
51 , mpVertGlyphs(nullptr)
52 , mbFuzzing(comphelper::IsFuzzing())
56 GenericSalLayout::~GenericSalLayout()
58 if (mpVertGlyphs)
59 hb_set_destroy(mpVertGlyphs);
62 void GenericSalLayout::ParseFeatures(std::u16string_view aName)
64 vcl::font::FeatureParser aParser(aName);
65 const OUString& sLanguage = aParser.getLanguage();
66 if (!sLanguage.isEmpty())
67 msLanguage = OUStringToOString(sLanguage, RTL_TEXTENCODING_ASCII_US);
69 for (auto const &rFeat : aParser.getFeatures())
71 hb_feature_t aFeature { rFeat.m_nTag, rFeat.m_nValue, rFeat.m_nStart, rFeat.m_nEnd };
72 maFeatures.push_back(aFeature);
76 namespace {
78 struct SubRun
80 int32_t mnMin;
81 int32_t mnEnd;
82 hb_script_t maScript;
83 hb_direction_t maDirection;
86 struct UnclusteredGlyphData
88 sal_Int32 m_nGlyphId;
89 bool m_bUsed = false;
91 explicit UnclusteredGlyphData(sal_Int32 nGlyphId)
92 : m_nGlyphId(nGlyphId)
97 // This is a helper class to enable correct styling and glyph placement when a grapheme cluster is
98 // split across multiple adjoining layouts.
100 // In order to justify text, we need glyphs grouped into grapheme clusters so diacritics will stay
101 // attached to characters under adjustment. However, in order to correctly position and style
102 // grapheme clusters that span multiple layouts, we need best-effort character-level position data.
104 // At time of writing, HarfBuzz cannot provide both types of information simultaneously. As a work-
105 // around, this helper class runs HarfBuzz a second time to get the missing information. Should a
106 // future version of HarfBuzz support this use case directly, this helper code should be deleted.
108 // See tdf#61444, tdf#71956, tdf#124116
109 class UnclusteredGlyphMapper
111 private:
112 hb_buffer_t* m_pHbBuffer = nullptr;
113 std::multimap<sal_Int32, UnclusteredGlyphData> m_aGlyphs;
114 bool m_bEnable = false;
116 public:
117 UnclusteredGlyphMapper(bool bEnable, int nGlyphCapacity)
118 : m_bEnable(bEnable)
120 if (!m_bEnable)
122 return;
125 m_pHbBuffer = hb_buffer_create();
126 hb_buffer_pre_allocate(m_pHbBuffer, nGlyphCapacity);
129 ~UnclusteredGlyphMapper()
131 if (m_bEnable)
133 hb_buffer_destroy(m_pHbBuffer);
137 [[nodiscard]] sal_Int32 RemapGlyph(sal_Int32 nClusterId, sal_Int32 nGlyphId)
139 if (auto it = m_aGlyphs.lower_bound(nClusterId); it != m_aGlyphs.end())
141 for (; it != m_aGlyphs.end(); ++it)
143 if (it->second.m_nGlyphId == nGlyphId && !it->second.m_bUsed)
145 it->second.m_bUsed = true;
146 return it->first;
151 return nClusterId;
154 void Reset()
156 for (auto& rElement : m_aGlyphs)
158 rElement.second.m_bUsed = false;
162 void ShapeSubRun(const sal_Unicode* pStr, const int nLength, const SubRun& aSubRun,
163 hb_font_t* pHbFont, const std::vector<hb_feature_t>& maFeatures,
164 hb_language_t oHbLanguage)
166 if (!m_bEnable)
168 return;
171 m_aGlyphs.clear();
173 hb_buffer_clear_contents(m_pHbBuffer);
175 const int nMinRunPos = aSubRun.mnMin;
176 const int nEndRunPos = aSubRun.mnEnd;
177 const int nRunLen = nEndRunPos - nMinRunPos;
179 int nHbFlags = HB_BUFFER_FLAGS_DEFAULT;
180 nHbFlags |= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL;
182 if (nMinRunPos == 0)
184 nHbFlags |= HB_BUFFER_FLAG_BOT; /* Beginning-of-text */
187 if (nEndRunPos == nLength)
189 nHbFlags |= HB_BUFFER_FLAG_EOT; /* End-of-text */
192 hb_buffer_set_flags(m_pHbBuffer, static_cast<hb_buffer_flags_t>(nHbFlags));
194 hb_buffer_set_cluster_level(m_pHbBuffer, HB_BUFFER_CLUSTER_LEVEL_CHARACTERS);
196 hb_buffer_set_direction(m_pHbBuffer, aSubRun.maDirection);
197 hb_buffer_set_script(m_pHbBuffer, aSubRun.maScript);
198 hb_buffer_set_language(m_pHbBuffer, oHbLanguage);
200 hb_buffer_add_utf16(m_pHbBuffer, reinterpret_cast<uint16_t const*>(pStr), nLength,
201 nMinRunPos, nRunLen);
203 // The shapers that we want HarfBuzz to use, in the order of
204 // preference.
205 const char* const pHbShapers[] = { "graphite2", "ot", "fallback", nullptr };
206 bool ok
207 = hb_shape_full(pHbFont, m_pHbBuffer, maFeatures.data(), maFeatures.size(), pHbShapers);
208 assert(ok);
209 (void)ok;
211 int nRunGlyphCount = hb_buffer_get_length(m_pHbBuffer);
212 hb_glyph_info_t* pHbGlyphInfos = hb_buffer_get_glyph_infos(m_pHbBuffer, nullptr);
214 for (int i = 0; i < nRunGlyphCount; ++i)
216 int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint;
217 int32_t nCharPos = pHbGlyphInfos[i].cluster;
219 m_aGlyphs.emplace(nCharPos, UnclusteredGlyphData{ nGlyphIndex });
225 namespace {
226 int32_t GetVerticalOrientation(sal_UCS4 cCh, const LanguageTag& rTag)
228 // Override orientation of fullwidth colon , semi-colon,
229 // and Bopomofo tonal marks.
230 if ((cCh == 0xff1a || cCh == 0xff1b
231 || cCh == 0x2ca || cCh == 0x2cb || cCh == 0x2c7 || cCh == 0x2d9)
232 && rTag.getLanguage() == "zh")
233 return U_VO_TRANSFORMED_UPRIGHT;
235 return u_getIntPropertyValue(cCh, UCHAR_VERTICAL_ORIENTATION);
237 } // namespace
239 SalLayoutGlyphs GenericSalLayout::GetGlyphs() const
241 SalLayoutGlyphs glyphs;
242 glyphs.AppendImpl(m_GlyphItems.clone());
243 return glyphs;
246 void GenericSalLayout::SetNeedFallback(vcl::text::ImplLayoutArgs& rArgs, sal_Int32 nCharPos,
247 sal_Int32 nCharEnd, bool bRightToLeft)
249 if (nCharPos < 0 || nCharPos == nCharEnd || mbFuzzing)
250 return;
252 if (!mxBreak.is())
253 mxBreak = vcl::unohelper::CreateBreakIterator();
255 const css::lang::Locale& rLocale(rArgs.maLanguageTag.getLocale());
257 //if position nCharPos is missing in the font, grab the entire grapheme and
258 //mark all glyphs as missing so the whole thing is rendered with the same
259 //font
260 sal_Int32 nDone;
261 int nGraphemeEndPos = mxBreak->nextCharacters(rArgs.mrStr, nCharEnd - 1, rLocale,
262 css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
263 // Safely advance nCharPos in case it is a non-BMP character.
264 rArgs.mrStr.iterateCodePoints(&nCharPos);
265 int nGraphemeStartPos =
266 mxBreak->previousCharacters(rArgs.mrStr, nCharPos, rLocale,
267 css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
269 // tdf#107612
270 // If the start of the fallback run is Mongolian character and the previous
271 // character is NNBSP, we want to include the NNBSP in the fallback since
272 // it has special uses in Mongolian and have to be in the same text run to
273 // work.
274 sal_Int32 nTempPos = nGraphemeStartPos;
275 if (nGraphemeStartPos > 0)
277 auto nCurrChar = rArgs.mrStr.iterateCodePoints(&nTempPos, 0);
278 auto nPrevChar = rArgs.mrStr.iterateCodePoints(&nTempPos, -1);
279 if (nPrevChar == 0x202F
280 && u_getIntPropertyValue(nCurrChar, UCHAR_SCRIPT) == USCRIPT_MONGOLIAN)
281 nGraphemeStartPos = nTempPos;
284 //stay inside the Layout range (e.g. with tdf124116-1.odt)
285 nGraphemeStartPos = std::max(rArgs.mnMinCharPos, nGraphemeStartPos);
286 nGraphemeEndPos = std::min(rArgs.mnEndCharPos, nGraphemeEndPos);
288 rArgs.AddFallbackRun(nGraphemeStartPos, nGraphemeEndPos, bRightToLeft);
291 void GenericSalLayout::AdjustLayout(vcl::text::ImplLayoutArgs& rArgs)
293 SalLayout::AdjustLayout(rArgs);
295 if (!rArgs.mstJustification.empty())
297 ApplyJustificationData(rArgs.mstJustification);
299 else if (rArgs.mnLayoutWidth)
301 Justify(rArgs.mnLayoutWidth);
303 else if ((rArgs.mnFlags & SalLayoutFlags::KerningAsian)
304 && !(rArgs.mnFlags & SalLayoutFlags::Vertical))
306 // apply asian kerning if the glyphs are not already formatted
307 ApplyAsianKerning(rArgs.mrStr);
311 void GenericSalLayout::DrawText(SalGraphics& rSalGraphics) const
313 //call platform dependent DrawText functions
314 rSalGraphics.DrawTextLayout( *this );
317 // Find if the nominal glyph of the character is an input to “vert” feature.
318 // We don’t check for a specific script or language as it shouldn’t matter
319 // here; if the glyph would be the result from applying “vert” for any
320 // script/language then we want to always treat it as upright glyph.
321 bool GenericSalLayout::HasVerticalAlternate(sal_UCS4 aChar, sal_UCS4 aVariationSelector)
323 sal_GlyphId nGlyphIndex = GetFont().GetGlyphIndex(aChar, aVariationSelector);
324 if (!nGlyphIndex)
325 return false;
327 if (!mpVertGlyphs)
329 hb_face_t* pHbFace = hb_font_get_face(GetFont().GetHbFont());
330 mpVertGlyphs = hb_set_create();
332 // Find all GSUB lookups for “vert” feature.
333 hb_set_t* pLookups = hb_set_create();
334 hb_tag_t const pFeatures[] = { HB_TAG('v','e','r','t'), HB_TAG_NONE };
335 hb_ot_layout_collect_lookups(pHbFace, HB_OT_TAG_GSUB, nullptr, nullptr, pFeatures, pLookups);
336 if (!hb_set_is_empty(pLookups))
338 // Find the input glyphs in each lookup (i.e. the glyphs that
339 // this lookup applies to).
340 hb_codepoint_t nIdx = HB_SET_VALUE_INVALID;
341 while (hb_set_next(pLookups, &nIdx))
343 hb_set_t* pGlyphs = hb_set_create();
344 hb_ot_layout_lookup_collect_glyphs(pHbFace, HB_OT_TAG_GSUB, nIdx,
345 nullptr, // glyphs before
346 pGlyphs, // glyphs input
347 nullptr, // glyphs after
348 nullptr); // glyphs out
349 hb_set_union(mpVertGlyphs, pGlyphs);
352 hb_set_destroy(pLookups);
355 return hb_set_has(mpVertGlyphs, nGlyphIndex) != 0;
358 bool GenericSalLayout::LayoutText(vcl::text::ImplLayoutArgs& rArgs, const SalLayoutGlyphsImpl* pGlyphs)
360 // No need to touch m_GlyphItems at all for an empty string.
361 if (rArgs.mnEndCharPos - rArgs.mnMinCharPos <= 0)
362 return true;
364 ImplLayoutRuns aFallbackRuns;
366 if (pGlyphs)
368 // Work with pre-computed glyph items.
369 m_GlyphItems = *pGlyphs;
371 for(const GlyphItem& item : m_GlyphItems)
372 if(!item.glyphId())
373 aFallbackRuns.AddPos(item.charPos(), item.IsRTLGlyph());
375 for (const auto& rRun : aFallbackRuns)
377 SetNeedFallback(rArgs, rRun.m_nMinRunPos, rRun.m_nEndRunPos, rRun.m_bRTL);
380 // Some flags are set as a side effect of text layout, restore them here.
381 rArgs.mnFlags |= pGlyphs->GetFlags();
382 return true;
385 hb_font_t *pHbFont = GetFont().GetHbFont();
386 bool isGraphite = GetFont().IsGraphiteFont();
388 // tdf#163215: Identify layouts that don't have strict kashida position validation.
389 m_bHasFontKashidaPositions = false;
390 if (!(rArgs.mnFlags & SalLayoutFlags::DisableKashidaValidation))
392 hb_face_t* pHbFace = hb_font_get_face(pHbFont);
393 m_bHasFontKashidaPositions = !hb_aat_layout_has_substitution(pHbFace);
396 int nGlyphCapacity = 2 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos);
397 m_GlyphItems.reserve(nGlyphCapacity);
399 const int nLength = rArgs.mrStr.getLength();
400 const sal_Unicode *pStr = rArgs.mrStr.getStr();
402 std::shared_ptr<const vcl::text::TextLayoutCache> pNewScriptRun;
403 vcl::text::TextLayoutCache const* pTextLayout;
404 if (rArgs.m_pTextLayoutCache)
406 pTextLayout = rArgs.m_pTextLayoutCache; // use cache!
408 else
410 // tdf#92064, tdf#162663:
411 // Also use the global LRU cache for full string script runs.
412 // This obviates O(n^2) calls to vcl::ScriptRun::next() when laying out large paragraphs.
413 pNewScriptRun = vcl::text::TextLayoutCache::Create(rArgs.mrStr);
414 pTextLayout = pNewScriptRun.get();
417 // nBaseOffset is used to align vertical text to the center of rotated
418 // horizontal text. That is the offset from original baseline to
419 // the center of EM box. Maybe we can use OpenType base table to improve this
420 // in the future.
421 double nBaseOffset = 0;
422 if (rArgs.mnFlags & SalLayoutFlags::Vertical)
424 hb_font_extents_t extents;
425 if (hb_font_get_h_extents(pHbFont, &extents))
426 nBaseOffset = ( extents.ascender + extents.descender ) / 2.0;
429 UnclusteredGlyphMapper stClusterMapper{
430 bool{ rArgs.mnFlags & SalLayoutFlags::UnclusteredGlyphs }, nGlyphCapacity
433 hb_buffer_t* pHbBuffer = hb_buffer_create();
434 hb_buffer_pre_allocate(pHbBuffer, nGlyphCapacity);
436 const vcl::font::FontSelectPattern& rFontSelData = GetFont().GetFontSelectPattern();
437 if (rArgs.mnFlags & SalLayoutFlags::DisableKerning)
439 SAL_INFO("vcl.harfbuzz", "Disabling kerning for font: " << rFontSelData.maTargetName);
440 maFeatures.push_back({ HB_TAG('k','e','r','n'), 0, 0, static_cast<unsigned int>(-1) });
443 if (rArgs.mnFlags & SalLayoutFlags::DisableLigatures)
445 SAL_INFO("vcl.harfbuzz", "Disabling ligatures for font: " << rFontSelData.maTargetName);
447 // Both of these are optional ligatures, enabled by default but not for
448 // orthographically-required ligatures.
449 maFeatures.push_back({ HB_TAG('l','i','g','a'), 0, 0, static_cast<unsigned int>(-1) });
450 maFeatures.push_back({ HB_TAG('c','l','i','g'), 0, 0, static_cast<unsigned int>(-1) });
453 ParseFeatures(rFontSelData.maTargetName);
455 double nXScale = 0;
456 double nYScale = 0;
457 GetFont().GetScale(&nXScale, &nYScale);
459 double nCurrX = 0.0;
460 while (true)
462 int nBidiMinRunPos, nBidiEndRunPos;
463 bool bRightToLeft;
464 if (!rArgs.GetNextRun(&nBidiMinRunPos, &nBidiEndRunPos, &bRightToLeft))
465 break;
467 // Find script subruns.
468 std::vector<SubRun> aSubRuns;
469 int nCurrentPos = nBidiMinRunPos;
470 size_t k = 0;
471 for (; k < pTextLayout->runs.size(); ++k)
473 vcl::text::Run const& rRun(pTextLayout->runs[k]);
474 if (rRun.nStart <= nCurrentPos && nCurrentPos < rRun.nEnd)
476 break;
480 if (isGraphite)
482 hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode);
483 aSubRuns.push_back({ nBidiMinRunPos, nBidiEndRunPos, aScript, bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR });
485 else
487 while (nCurrentPos < nBidiEndRunPos && k < pTextLayout->runs.size())
489 int32_t nMinRunPos = nCurrentPos;
490 int32_t nEndRunPos = std::min(pTextLayout->runs[k].nEnd, nBidiEndRunPos);
491 hb_direction_t aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
492 hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode);
493 // For vertical text, further divide the runs based on character
494 // orientation.
495 if (rArgs.mnFlags & SalLayoutFlags::Vertical)
497 sal_Int32 nIdx = nMinRunPos;
498 while (nIdx < nEndRunPos)
500 sal_Int32 nPrevIdx = nIdx;
501 sal_UCS4 aChar = rArgs.mrStr.iterateCodePoints(&nIdx);
502 int32_t aVo = GetVerticalOrientation(aChar, rArgs.maLanguageTag);
504 sal_UCS4 aVariationSelector = 0;
505 if (nIdx < nEndRunPos)
507 sal_Int32 nNextIdx = nIdx;
508 sal_UCS4 aNextChar = rArgs.mrStr.iterateCodePoints(&nNextIdx);
509 if (u_hasBinaryProperty(aNextChar, UCHAR_VARIATION_SELECTOR))
511 nIdx = nNextIdx;
512 aVariationSelector = aNextChar;
516 // Characters with U and Tu vertical orientation should
517 // be shaped in vertical direction. But characters
518 // with Tr should be shaped in vertical direction
519 // only if they have vertical alternates, otherwise
520 // they should be shaped in horizontal direction
521 // and then rotated.
522 // See http://unicode.org/reports/tr50/#vo
523 if (aVo == U_VO_UPRIGHT || aVo == U_VO_TRANSFORMED_UPRIGHT ||
524 (aVo == U_VO_TRANSFORMED_ROTATED &&
525 HasVerticalAlternate(aChar, aVariationSelector)))
527 aDirection = HB_DIRECTION_TTB;
529 else
531 aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
534 if (aSubRuns.empty() || aSubRuns.back().maDirection != aDirection || aSubRuns.back().maScript != aScript)
535 aSubRuns.push_back({ nPrevIdx, nIdx, aScript, aDirection });
536 else
537 aSubRuns.back().mnEnd = nIdx;
540 else
542 aSubRuns.push_back({ nMinRunPos, nEndRunPos, aScript, aDirection });
545 nCurrentPos = nEndRunPos;
546 ++k;
550 // RTL subruns should be reversed to ensure that final glyph order is
551 // correct.
552 if (bRightToLeft)
553 std::reverse(aSubRuns.begin(), aSubRuns.end());
555 for (const auto& aSubRun : aSubRuns)
557 hb_buffer_clear_contents(pHbBuffer);
559 const int nMinRunPos = aSubRun.mnMin;
560 const int nEndRunPos = aSubRun.mnEnd;
561 const int nRunLen = nEndRunPos - nMinRunPos;
563 int nHbFlags = HB_BUFFER_FLAGS_DEFAULT;
565 // Produce HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL that we use below.
566 nHbFlags |= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL;
568 if (nMinRunPos == 0)
569 nHbFlags |= HB_BUFFER_FLAG_BOT; /* Beginning-of-text */
570 if (nEndRunPos == nLength)
571 nHbFlags |= HB_BUFFER_FLAG_EOT; /* End-of-text */
573 hb_buffer_set_direction(pHbBuffer, aSubRun.maDirection);
574 hb_buffer_set_script(pHbBuffer, aSubRun.maScript);
576 hb_language_t oHbLanguage = nullptr;
577 if (!msLanguage.isEmpty())
579 oHbLanguage = hb_language_from_string(msLanguage.getStr(), msLanguage.getLength());
581 else
583 OString sLanguage
584 = OUStringToOString(rArgs.maLanguageTag.getBcp47(), RTL_TEXTENCODING_ASCII_US);
585 oHbLanguage = hb_language_from_string(sLanguage.getStr(), sLanguage.getLength());
588 hb_buffer_set_language(pHbBuffer, oHbLanguage);
590 hb_buffer_set_flags(pHbBuffer, static_cast<hb_buffer_flags_t>(nHbFlags));
591 hb_buffer_add_utf16(
592 pHbBuffer, reinterpret_cast<uint16_t const *>(pStr), nLength,
593 nMinRunPos, nRunLen);
595 // The shapers that we want HarfBuzz to use, in the order of
596 // preference.
597 const char*const pHbShapers[] = { "graphite2", "ot", "fallback", nullptr };
598 bool ok = hb_shape_full(pHbFont, pHbBuffer, maFeatures.data(), maFeatures.size(), pHbShapers);
599 assert(ok);
600 (void) ok;
602 // Populate glyph cluster remapping data
603 stClusterMapper.ShapeSubRun(pStr, nLength, aSubRun, pHbFont, maFeatures, oHbLanguage);
605 int nRunGlyphCount = hb_buffer_get_length(pHbBuffer);
606 hb_glyph_info_t *pHbGlyphInfos = hb_buffer_get_glyph_infos(pHbBuffer, nullptr);
607 hb_glyph_position_t *pHbPositions = hb_buffer_get_glyph_positions(pHbBuffer, nullptr);
609 // tdf#164106: Grapheme clusters can be split across multiple layouts. To do this,
610 // the complete string is laid out, and only the necessary glyphs are extracted.
611 // These sub-layouts are positioned side-by-side to form the complete text.
612 // This approach is good enough for most diacritic cases, but it cannot handle cases
613 // where a glyph with an advance is reordered into a different sub-layout.
614 bool bStartClusterOutOfOrder = false;
615 bool bEndClusterOutOfOrder = false;
617 double nNormalAdvance = 0.0;
618 double nStartAdvance = 0.0;
619 double nEndAdvance = 0.0;
621 auto fnHandleGlyph = [&](int i)
623 int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint;
624 int32_t nCluster = pHbGlyphInfos[i].cluster;
625 auto nOrigCharPos = stClusterMapper.RemapGlyph(nCluster, nGlyphIndex);
627 double nAdvance = 0.0;
628 if (aSubRun.maDirection == HB_DIRECTION_TTB)
630 nAdvance = -pHbPositions[i].y_advance;
632 else
634 nAdvance = pHbPositions[i].x_advance;
637 nNormalAdvance += nAdvance;
639 if (nOrigCharPos < rArgs.mnDrawMinCharPos)
641 nStartAdvance += nAdvance;
642 if (nStartAdvance != nNormalAdvance)
644 bStartClusterOutOfOrder = true;
648 if (nOrigCharPos < rArgs.mnDrawEndCharPos)
650 nEndAdvance += nAdvance;
651 if (nEndAdvance != nNormalAdvance)
653 bEndClusterOutOfOrder = true;
658 if (bRightToLeft)
660 for (int i = nRunGlyphCount - 1; i >= 0; --i)
662 fnHandleGlyph(i);
665 else
667 for (int i = 0; i < nRunGlyphCount; ++i)
669 fnHandleGlyph(i);
673 stClusterMapper.Reset();
676 for (int i = 0; i < nRunGlyphCount; ++i) {
677 int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint;
678 int32_t nCharPos = pHbGlyphInfos[i].cluster;
679 int32_t nCharCount = 0;
680 bool bInCluster = false;
681 bool bClusterStart = false;
683 // Find the number of characters that make up this glyph.
684 if (!bRightToLeft)
686 // If the cluster is the same as previous glyph, then this
687 // already consumed, skip.
688 if (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster)
690 nCharCount = 0;
691 bInCluster = true;
693 else
695 // Find the next glyph with a different cluster, or the
696 // end of text.
697 int j = i;
698 int32_t nNextCharPos = nCharPos;
699 while (nNextCharPos == nCharPos && j < nRunGlyphCount)
700 nNextCharPos = pHbGlyphInfos[j++].cluster;
702 if (nNextCharPos == nCharPos)
703 nNextCharPos = nEndRunPos;
704 nCharCount = nNextCharPos - nCharPos;
705 if ((i == 0 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i - 1].cluster) &&
706 (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster))
707 bClusterStart = true;
710 else
712 // If the cluster is the same as previous glyph, then this
713 // will be consumed later, skip.
714 if (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster)
716 nCharCount = 0;
717 bInCluster = true;
719 else
721 // Find the previous glyph with a different cluster, or
722 // the end of text.
723 int j = i;
724 int32_t nNextCharPos = nCharPos;
725 while (nNextCharPos == nCharPos && j >= 0)
726 nNextCharPos = pHbGlyphInfos[j--].cluster;
728 if (nNextCharPos == nCharPos)
729 nNextCharPos = nEndRunPos;
730 nCharCount = nNextCharPos - nCharPos;
731 if ((i == nRunGlyphCount - 1 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i + 1].cluster) &&
732 (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster))
733 bClusterStart = true;
737 // if needed request glyph fallback by updating LayoutArgs
738 auto nOrigCharPos = stClusterMapper.RemapGlyph(nCharPos, nGlyphIndex);
739 if (!nGlyphIndex)
741 // Only request fallback for grapheme clusters that are drawn
742 if (nOrigCharPos >= rArgs.mnDrawMinCharPos
743 && nOrigCharPos < rArgs.mnDrawEndCharPos)
745 aFallbackRuns.AddPos(nOrigCharPos, bRightToLeft);
746 if (SalLayoutFlags::ForFallback & rArgs.mnFlags)
747 continue;
751 GlyphItemFlags nGlyphFlags = GlyphItemFlags::NONE;
752 if (bRightToLeft)
753 nGlyphFlags |= GlyphItemFlags::IS_RTL_GLYPH;
755 if (bClusterStart)
756 nGlyphFlags |= GlyphItemFlags::IS_CLUSTER_START;
758 if (bInCluster)
759 nGlyphFlags |= GlyphItemFlags::IS_IN_CLUSTER;
761 sal_UCS4 aChar
762 = rArgs.mrStr.iterateCodePoints(&o3tl::temporary(sal_Int32(nCharPos)), 0);
764 if (u_isUWhiteSpace(aChar))
765 nGlyphFlags |= GlyphItemFlags::IS_SPACING;
767 if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i]) & HB_GLYPH_FLAG_UNSAFE_TO_BREAK)
768 nGlyphFlags |= GlyphItemFlags::IS_UNSAFE_TO_BREAK;
770 if (!m_bHasFontKashidaPositions
771 || (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i])
772 & HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL))
773 nGlyphFlags |= GlyphItemFlags::IS_SAFE_TO_INSERT_KASHIDA;
775 double nAdvance, nXOffset, nYOffset;
776 if (aSubRun.maDirection == HB_DIRECTION_TTB)
778 nGlyphFlags |= GlyphItemFlags::IS_VERTICAL;
780 nAdvance = -pHbPositions[i].y_advance;
781 nXOffset = -pHbPositions[i].y_offset;
782 nYOffset = -pHbPositions[i].x_offset - nBaseOffset;
784 if (GetFont().NeedOffsetCorrection(pHbPositions[i].y_offset))
786 // We need glyph's advance, top bearing, and height to
787 // correct y offset.
788 basegfx::B2DRectangle aRect;
789 // Get cached bound rect value for the font,
790 GetFont().GetGlyphBoundRect(nGlyphIndex, aRect, true);
792 nXOffset = -(aRect.getMinX() / nXScale + ( pHbPositions[i].y_advance
793 + ( aRect.getHeight() / nXScale ) ) / 2.0 );
797 else
799 nAdvance = pHbPositions[i].x_advance;
800 nXOffset = pHbPositions[i].x_offset;
801 nYOffset = -pHbPositions[i].y_offset;
804 nAdvance = nAdvance * nXScale;
805 nXOffset = nXOffset * nXScale;
806 nYOffset = nYOffset * nYScale;
807 if (!GetSubpixelPositioning())
809 nAdvance = std::round(nAdvance);
810 nXOffset = std::round(nXOffset);
811 nYOffset = std::round(nYOffset);
814 basegfx::B2DPoint aNewPos(nCurrX + nXOffset, nYOffset);
815 const GlyphItem aGI(nCharPos, nCharCount, nGlyphIndex, aNewPos, nGlyphFlags,
816 nAdvance, nXOffset, nYOffset, nOrigCharPos);
818 auto nLowerBound = (bStartClusterOutOfOrder ? aGI.charPos() : aGI.origCharPos());
819 auto nUpperBound = (bEndClusterOutOfOrder ? aGI.charPos() : aGI.origCharPos());
820 if (nLowerBound >= rArgs.mnDrawMinCharPos && nUpperBound < rArgs.mnDrawEndCharPos)
822 m_GlyphItems.push_back(aGI);
825 if (nLowerBound >= rArgs.mnDrawOriginCluster
826 && nUpperBound < rArgs.mnDrawEndCharPos)
828 nCurrX += nAdvance;
834 hb_buffer_destroy(pHbBuffer);
836 for (const auto& rRun : aFallbackRuns)
838 SetNeedFallback(rArgs, rRun.m_nMinRunPos, rRun.m_nEndRunPos, rRun.m_bRTL);
841 // Some flags are set as a side effect of text layout, save them here.
842 if (rArgs.mnFlags & SalLayoutFlags::GlyphItemsOnly)
843 m_GlyphItems.SetFlags(rArgs.mnFlags);
845 return true;
848 void GenericSalLayout::GetCharWidths(std::vector<double>& rCharWidths, const OUString& rStr) const
850 const int nCharCount = mnEndCharPos - mnMinCharPos;
852 rCharWidths.clear();
853 rCharWidths.resize(nCharCount, 0);
855 css::uno::Reference<css::i18n::XBreakIterator> xBreak;
856 const css::lang::Locale& rLocale(maLanguageTag.getLocale());
858 for (auto const& aGlyphItem : m_GlyphItems)
860 if (aGlyphItem.charPos() >= mnEndCharPos)
861 continue;
863 unsigned int nGraphemeCount = 0;
864 if (aGlyphItem.charCount() > 1 && aGlyphItem.newWidth() != 0 && !rStr.isEmpty())
866 // We are calculating DX array for cursor positions and this is a
867 // ligature, find out how many grapheme clusters are in it.
868 if (!xBreak.is())
869 xBreak = mxBreak.is() ? mxBreak : vcl::unohelper::CreateBreakIterator();
871 // Count grapheme clusters in the ligature.
872 sal_Int32 nDone;
873 sal_Int32 nPos = aGlyphItem.charPos();
874 while (nPos < aGlyphItem.charPos() + aGlyphItem.charCount())
876 nPos = xBreak->nextCharacters(rStr, nPos, rLocale,
877 css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
878 nGraphemeCount++;
882 if (nGraphemeCount > 1)
884 // More than one grapheme cluster, we want to distribute the glyph
885 // width over them.
886 std::vector<double> aWidths(nGraphemeCount);
888 // Check if the glyph has ligature caret positions.
889 unsigned int nCarets = nGraphemeCount;
890 std::vector<hb_position_t> aCarets(nGraphemeCount);
891 hb_ot_layout_get_ligature_carets(GetFont().GetHbFont(),
892 aGlyphItem.IsRTLGlyph() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR,
893 aGlyphItem.glyphId(), 0, &nCarets, aCarets.data());
895 // Carets are 1-less than the grapheme count (since the last
896 // position is defined by glyph width), if the count does not
897 // match, ignore it.
898 if (nCarets == nGraphemeCount - 1)
900 // Scale the carets and apply glyph offset to them since they
901 // are based on the default glyph metrics.
902 double fScale = 0;
903 GetFont().GetScale(&fScale, nullptr);
904 for (size_t i = 0; i < nCarets; i++)
905 aCarets[i] = (aCarets[i] * fScale) + aGlyphItem.xOffset();
907 // Use the glyph width for the last caret.
908 aCarets[nCarets] = aGlyphItem.newWidth();
910 // Carets are absolute from the X origin of the glyph, turn
911 // them to relative widths that we need below.
912 for (size_t i = 0; i < nGraphemeCount; i++)
913 aWidths[i] = aCarets[i] - (i == 0 ? 0 : aCarets[i - 1]);
915 // Carets are in visual order, but we want widths in logical
916 // order.
917 if (aGlyphItem.IsRTLGlyph())
918 std::reverse(aWidths.begin(), aWidths.end());
920 else
922 // The glyph has no carets, distribute the width evenly.
923 auto nWidth = aGlyphItem.newWidth() / nGraphemeCount;
924 std::fill(aWidths.begin(), aWidths.end(), nWidth);
926 // Add rounding difference to the last component to maintain
927 // ligature width.
928 aWidths[nGraphemeCount - 1] += aGlyphItem.newWidth() - (nWidth * nGraphemeCount);
931 // Set the width of each grapheme cluster.
932 sal_Int32 nDone;
933 sal_Int32 nPos = aGlyphItem.charPos();
934 for (auto nWidth : aWidths)
936 rCharWidths[nPos - mnMinCharPos] += nWidth;
937 nPos = xBreak->nextCharacters(rStr, nPos, rLocale,
938 css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
941 else
942 rCharWidths[aGlyphItem.charPos() - mnMinCharPos] += aGlyphItem.newWidth();
946 // - stJustification:
947 // - contains adjustments to glyph advances (usually due to justification).
948 // - contains kashida insertion positions, for Arabic script justification.
949 // - The number of kashidas is calculated from the adjusted advances.
950 void GenericSalLayout::ApplyJustificationData(const JustificationData& rstJustification)
952 int nCharCount = mnEndCharPos - mnMinCharPos;
953 std::vector<double> aOldCharWidths;
954 std::unique_ptr<double[]> const pNewCharWidths(new double[nCharCount]);
956 // Get the natural character widths (i.e. before applying DX adjustments).
957 GetCharWidths(aOldCharWidths, {});
959 // Calculate the character widths after DX adjustments.
960 for (int i = 0; i < nCharCount; ++i)
962 if (i == 0)
964 pNewCharWidths[i] = rstJustification.GetTotalAdvance(mnMinCharPos + i);
966 else
968 pNewCharWidths[i] = rstJustification.GetTotalAdvance(mnMinCharPos + i)
969 - rstJustification.GetTotalAdvance(mnMinCharPos + i - 1);
973 // Map of Kashida insertion points (in the glyph items vector) and the
974 // requested width.
975 std::map<size_t, std::pair<double, double>> pKashidas;
977 // The accumulated difference in X position.
978 double nDelta = 0;
980 // Apply the DX adjustments to glyph positions and widths.
981 size_t i = 0;
982 while (i < m_GlyphItems.size())
984 // Accumulate the width difference for all characters corresponding to
985 // this glyph.
986 int nCharPos = m_GlyphItems[i].charPos() - mnMinCharPos;
987 double nDiff = 0;
988 for (int j = 0; j < m_GlyphItems[i].charCount(); j++)
989 nDiff += pNewCharWidths[nCharPos + j] - aOldCharWidths[nCharPos + j];
991 if (!m_GlyphItems[i].IsRTLGlyph())
993 // Adjust the width and position of the first (leftmost) glyph in
994 // the cluster.
995 m_GlyphItems[i].addNewWidth(nDiff);
996 m_GlyphItems[i].adjustLinearPosX(nDelta);
998 // Adjust the position of the rest of the glyphs in the cluster.
999 while (++i < m_GlyphItems.size())
1001 if (!m_GlyphItems[i].IsInCluster())
1002 break;
1003 m_GlyphItems[i].adjustLinearPosX(nDelta);
1006 else if (m_GlyphItems[i].IsInCluster())
1008 // RTL glyph in the middle of the cluster, will be handled in the
1009 // loop below.
1010 i++;
1012 else // RTL
1014 // Adjust the width and position of the first (rightmost) glyph in
1015 // the cluster. This is RTL, so we put all the adjustment to the
1016 // left of the glyph.
1017 m_GlyphItems[i].addNewWidth(nDiff);
1018 m_GlyphItems[i].adjustLinearPosX(nDelta + nDiff);
1020 // Adjust the X position of the rest of the glyphs in the cluster.
1021 // We iterate backwards since this is an RTL glyph.
1022 for (size_t j = i; j >= 1 && m_GlyphItems[j - 1].IsInCluster(); --j)
1023 m_GlyphItems[j - 1].adjustLinearPosX(nDelta + nDiff);
1025 // This is a Kashida insertion position, mark it. Kashida glyphs
1026 // will be inserted below.
1027 if (rstJustification.GetPositionHasKashida(mnMinCharPos + nCharPos).value_or(false))
1029 pKashidas[i] = { nDiff, pNewCharWidths[nCharPos] };
1032 i++;
1035 // Increment the delta, the loop above makes sure we do so only once
1036 // for every character (cluster) not for every glyph (otherwise we
1037 // would apply it multiple times for each glyph belonging to the same
1038 // character which is wrong as DX adjustments are character based).
1039 nDelta += nDiff;
1042 // Insert Kashida glyphs.
1043 if (pKashidas.empty())
1044 return;
1046 // Find Kashida glyph width and index.
1047 sal_GlyphId nKashidaIndex = GetFont().GetGlyphIndex(0x0640);
1048 double nKashidaWidth = GetFont().GetKashidaWidth();
1049 if (!GetSubpixelPositioning())
1050 nKashidaWidth = std::ceil(nKashidaWidth);
1052 if (nKashidaWidth <= 0)
1054 SAL_WARN("vcl.gdi", "Asked to insert Kashidas in a font with bogus Kashida width");
1055 return;
1058 size_t nInserted = 0;
1059 for (auto const& pKashida : pKashidas)
1061 auto pGlyphIter = m_GlyphItems.begin() + nInserted + pKashida.first;
1063 // The total Kashida width.
1064 auto const& [nTotalWidth, nClusterWidth] = pKashida.second;
1066 // Number of times to repeat each Kashida.
1067 int nCopies = 1;
1068 if (nTotalWidth > nKashidaWidth)
1069 nCopies = nTotalWidth / nKashidaWidth;
1071 // See if we can improve the fit by adding an extra Kashidas and
1072 // squeezing them together a bit.
1073 double nOverlap = 0;
1074 double nShortfall = nTotalWidth - nKashidaWidth * nCopies;
1075 if (nShortfall > 0)
1077 ++nCopies;
1078 double nExcess = nCopies * nKashidaWidth - nTotalWidth;
1079 if (nExcess > 0)
1080 nOverlap = nExcess / (nCopies - 1);
1083 basegfx::B2DPoint aPos = pGlyphIter->linearPos();
1084 int nCharPos = pGlyphIter->charPos();
1085 GlyphItemFlags const nFlags = GlyphItemFlags::IS_IN_CLUSTER | GlyphItemFlags::IS_RTL_GLYPH;
1086 // Move to the left side of the adjusted width and start inserting
1087 // glyphs there.
1088 aPos.adjustX(-nClusterWidth + pGlyphIter->origWidth());
1089 while (nCopies--)
1091 GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, 0, 0, 0, nCharPos);
1092 pGlyphIter = m_GlyphItems.insert(pGlyphIter, aKashida);
1093 aPos.adjustX(nKashidaWidth - nOverlap);
1094 ++pGlyphIter;
1095 ++nInserted;
1100 bool GenericSalLayout::HasFontKashidaPositions() const { return m_bHasFontKashidaPositions; }
1102 // Kashida will be inserted between nCharPos and nNextCharPos.
1103 bool GenericSalLayout::IsKashidaPosValid(int nCharPos, int nNextCharPos) const
1105 // Search for glyph items corresponding to nCharPos and nNextCharPos.
1106 auto const aGlyph = std::find_if(m_GlyphItems.begin(), m_GlyphItems.end(),
1107 [&](const GlyphItem& g) { return g.charPos() == nCharPos; });
1108 auto const aNextGlyph = std::find_if(m_GlyphItems.begin(), m_GlyphItems.end(),
1109 [&](const GlyphItem& g) { return g.charPos() == nNextCharPos; });
1111 // If either is not found then a ligature is created at this position, we
1112 // can’t insert Kashida here.
1113 if (aGlyph == m_GlyphItems.end() || aNextGlyph == m_GlyphItems.end())
1114 return false;
1116 // If the either character is not supported by this layout, return false so
1117 // that fallback layouts would be checked for it.
1118 if (aGlyph->glyphId() == 0 || aNextGlyph->glyphId() == 0)
1119 return false;
1121 // Lastly check if this position is kashida-safe.
1122 return aNextGlyph->IsSafeToInsertKashida();
1125 void GenericSalLayout::drawSalLayout(void* pSurface, const basegfx::BColor& rTextColor, bool bAntiAliased) const
1127 Application::GetDefaultDevice()->GetGraphics()->DrawSalLayout(*this, pSurface, rTextColor, bAntiAliased);
1130 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */