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/.
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>
35 #include <sallayout.hxx>
37 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
39 #include <unicode/uchar.h>
41 #include <hb-graphite2.h>
49 GenericSalLayout::GenericSalLayout(LogicalFontInstance
&rFont
)
51 , mpVertGlyphs(nullptr)
52 , mbFuzzing(comphelper::IsFuzzing())
56 GenericSalLayout::~GenericSalLayout()
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
);
83 hb_direction_t maDirection
;
86 struct UnclusteredGlyphData
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
112 hb_buffer_t
* m_pHbBuffer
= nullptr;
113 std::multimap
<sal_Int32
, UnclusteredGlyphData
> m_aGlyphs
;
114 bool m_bEnable
= false;
117 UnclusteredGlyphMapper(bool bEnable
, int nGlyphCapacity
)
125 m_pHbBuffer
= hb_buffer_create();
126 hb_buffer_pre_allocate(m_pHbBuffer
, nGlyphCapacity
);
129 ~UnclusteredGlyphMapper()
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;
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
)
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
;
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
205 const char* const pHbShapers
[] = { "graphite2", "ot", "fallback", nullptr };
207 = hb_shape_full(pHbFont
, m_pHbBuffer
, maFeatures
.data(), maFeatures
.size(), pHbShapers
);
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
});
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
);
239 SalLayoutGlyphs
GenericSalLayout::GetGlyphs() const
241 SalLayoutGlyphs glyphs
;
242 glyphs
.AppendImpl(m_GlyphItems
.clone());
246 void GenericSalLayout::SetNeedFallback(vcl::text::ImplLayoutArgs
& rArgs
, sal_Int32 nCharPos
,
247 sal_Int32 nCharEnd
, bool bRightToLeft
)
249 if (nCharPos
< 0 || nCharPos
== nCharEnd
|| mbFuzzing
)
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
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
);
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
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
);
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)
364 ImplLayoutRuns aFallbackRuns
;
368 // Work with pre-computed glyph items.
369 m_GlyphItems
= *pGlyphs
;
371 for(const GlyphItem
& item
: m_GlyphItems
)
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();
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!
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
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
);
457 GetFont().GetScale(&nXScale
, &nYScale
);
462 int nBidiMinRunPos
, nBidiEndRunPos
;
464 if (!rArgs
.GetNextRun(&nBidiMinRunPos
, &nBidiEndRunPos
, &bRightToLeft
))
467 // Find script subruns.
468 std::vector
<SubRun
> aSubRuns
;
469 int nCurrentPos
= nBidiMinRunPos
;
471 for (; k
< pTextLayout
->runs
.size(); ++k
)
473 vcl::text::Run
const& rRun(pTextLayout
->runs
[k
]);
474 if (rRun
.nStart
<= nCurrentPos
&& nCurrentPos
< rRun
.nEnd
)
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
});
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
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
))
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
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
;
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
});
537 aSubRuns
.back().mnEnd
= nIdx
;
542 aSubRuns
.push_back({ nMinRunPos
, nEndRunPos
, aScript
, aDirection
});
545 nCurrentPos
= nEndRunPos
;
550 // RTL subruns should be reversed to ensure that final glyph order is
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
;
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());
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
));
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
597 const char*const pHbShapers
[] = { "graphite2", "ot", "fallback", nullptr };
598 bool ok
= hb_shape_full(pHbFont
, pHbBuffer
, maFeatures
.data(), maFeatures
.size(), pHbShapers
);
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
;
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;
660 for (int i
= nRunGlyphCount
- 1; i
>= 0; --i
)
667 for (int i
= 0; i
< nRunGlyphCount
; ++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.
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
)
695 // Find the next glyph with a different cluster, or the
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;
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
)
721 // Find the previous glyph with a different cluster, or
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
);
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
)
751 GlyphItemFlags nGlyphFlags
= GlyphItemFlags::NONE
;
753 nGlyphFlags
|= GlyphItemFlags::IS_RTL_GLYPH
;
756 nGlyphFlags
|= GlyphItemFlags::IS_CLUSTER_START
;
759 nGlyphFlags
|= GlyphItemFlags::IS_IN_CLUSTER
;
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
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 );
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
)
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
);
848 void GenericSalLayout::GetCharWidths(std::vector
<double>& rCharWidths
, const OUString
& rStr
) const
850 const int nCharCount
= mnEndCharPos
- mnMinCharPos
;
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
)
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.
869 xBreak
= mxBreak
.is() ? mxBreak
: vcl::unohelper::CreateBreakIterator();
871 // Count grapheme clusters in the ligature.
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
);
882 if (nGraphemeCount
> 1)
884 // More than one grapheme cluster, we want to distribute the glyph
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
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.
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
917 if (aGlyphItem
.IsRTLGlyph())
918 std::reverse(aWidths
.begin(), aWidths
.end());
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
928 aWidths
[nGraphemeCount
- 1] += aGlyphItem
.newWidth() - (nWidth
* nGraphemeCount
);
931 // Set the width of each grapheme cluster.
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
);
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
)
964 pNewCharWidths
[i
] = rstJustification
.GetTotalAdvance(mnMinCharPos
+ i
);
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
975 std::map
<size_t, std::pair
<double, double>> pKashidas
;
977 // The accumulated difference in X position.
980 // Apply the DX adjustments to glyph positions and widths.
982 while (i
< m_GlyphItems
.size())
984 // Accumulate the width difference for all characters corresponding to
986 int nCharPos
= m_GlyphItems
[i
].charPos() - mnMinCharPos
;
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
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())
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
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
] };
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).
1042 // Insert Kashida glyphs.
1043 if (pKashidas
.empty())
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");
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.
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
;
1078 double nExcess
= nCopies
* nKashidaWidth
- nTotalWidth
;
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
1088 aPos
.adjustX(-nClusterWidth
+ pGlyphIter
->origWidth());
1091 GlyphItem
aKashida(nCharPos
, 0, nKashidaIndex
, aPos
, nFlags
, 0, 0, 0, nCharPos
);
1092 pGlyphIter
= m_GlyphItems
.insert(pGlyphIter
, aKashida
);
1093 aPos
.adjustX(nKashidaWidth
- nOverlap
);
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())
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)
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: */