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 .
24 #include <hb-graphite2.h>
26 #include <sallayout.hxx>
28 #include <o3tl/temporary.hxx>
29 #include <sal/log.hxx>
30 #include <unotools/configmgr.hxx>
31 #include <vcl/unohelp.hxx>
32 #include <vcl/font/Feature.hxx>
33 #include <vcl/font/FeatureParser.hxx>
35 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
37 #include <unicode/uchar.h>
39 #include <fontselect.hxx>
41 #if !HB_VERSION_ATLEAST(1, 1, 0)
42 // Disabled Unicode compatibility decomposition, see fdo#66715
43 static unsigned int unicodeDecomposeCompatibility(hb_unicode_funcs_t
* /*ufuncs*/,
45 hb_codepoint_t
* /*decomposed*/,
51 static hb_unicode_funcs_t
* getUnicodeFuncs()
53 static hb_unicode_funcs_t
* ufuncs
= hb_unicode_funcs_create(hb_icu_get_unicode_funcs());
54 hb_unicode_funcs_set_decompose_compatibility_func(ufuncs
, unicodeDecomposeCompatibility
, nullptr, nullptr);
59 GenericSalLayout::GenericSalLayout(LogicalFontInstance
&rFont
)
61 , mpVertGlyphs(nullptr)
62 , mbFuzzing(utl::ConfigManager::IsFuzzing())
66 GenericSalLayout::~GenericSalLayout()
70 void GenericSalLayout::ParseFeatures(const OUString
& aName
)
72 vcl::font::FeatureParser
aParser(aName
);
73 const OUString
& sLanguage
= aParser
.getLanguage();
74 if (!sLanguage
.isEmpty())
75 msLanguage
= OUStringToOString(sLanguage
, RTL_TEXTENCODING_ASCII_US
);
77 for (auto const &rFeat
: aParser
.getFeatures())
79 hb_feature_t aFeature
{ rFeat
.m_nTag
, rFeat
.m_nValue
, rFeat
.m_nStart
, rFeat
.m_nEnd
};
80 maFeatures
.push_back(aFeature
);
91 hb_direction_t maDirection
;
104 Run(int32_t nStart_
, int32_t nEnd_
, UScriptCode nCode_
)
113 class TextLayoutCache
116 std::vector
<vcl::Run
> runs
;
117 TextLayoutCache(sal_Unicode
const* pStr
, sal_Int32
const nEnd
)
119 vcl::ScriptRun
aScriptRun(
120 reinterpret_cast<const UChar
*>(pStr
),
122 while (aScriptRun
.next())
124 runs
.emplace_back(aScriptRun
.getScriptStart(),
125 aScriptRun
.getScriptEnd(), aScriptRun
.getScriptCode());
132 #if U_ICU_VERSION_MAJOR_NUM >= 63
133 enum class VerticalOrientation
{
134 Upright
= U_VO_UPRIGHT
,
135 Rotated
= U_VO_ROTATED
,
136 TransformedUpright
= U_VO_TRANSFORMED_UPRIGHT
,
137 TransformedRotated
= U_VO_TRANSFORMED_ROTATED
140 #include "VerticalOrientationData.cxx"
142 // These must match the values in the file included above.
143 enum class VerticalOrientation
{
146 TransformedUpright
= 2,
147 TransformedRotated
= 3
151 VerticalOrientation
GetVerticalOrientation(sal_UCS4 cCh
, const LanguageTag
& rTag
)
153 // Override orientation of fullwidth colon , semi-colon,
154 // and Bopomofo tonal marks.
155 if ((cCh
== 0xff1a || cCh
== 0xff1b
156 || cCh
== 0x2ca || cCh
== 0x2cb || cCh
== 0x2c7 || cCh
== 0x2d9)
157 && rTag
.getLanguage() == "zh")
158 return VerticalOrientation::TransformedUpright
;
160 #if U_ICU_VERSION_MAJOR_NUM >= 63
161 int32_t nRet
= u_getIntPropertyValue(cCh
, UCHAR_VERTICAL_ORIENTATION
);
167 nRet
= sVerticalOrientationValues
[sVerticalOrientationPages
[0][cCh
>> kVerticalOrientationCharBits
]]
168 [cCh
& ((1 << kVerticalOrientationCharBits
) - 1)];
170 else if (cCh
< (kVerticalOrientationMaxPlane
+ 1) * 0x10000)
172 nRet
= sVerticalOrientationValues
[sVerticalOrientationPages
[sVerticalOrientationPlanes
[(cCh
>> 16) - 1]]
173 [(cCh
& 0xffff) >> kVerticalOrientationCharBits
]]
174 [cCh
& ((1 << kVerticalOrientationCharBits
) - 1)];
178 // Default value for unassigned
179 SAL_WARN("vcl.gdi", "Getting VerticalOrientation for codepoint outside Unicode range");
183 return VerticalOrientation(nRet
);
188 std::shared_ptr
<vcl::TextLayoutCache
> GenericSalLayout::CreateTextLayoutCache(OUString
const& rString
)
190 return std::make_shared
<vcl::TextLayoutCache
>(rString
.getStr(), rString
.getLength());
193 SalLayoutGlyphs
GenericSalLayout::GetGlyphs() const
195 SalLayoutGlyphs glyphs
;
196 glyphs
.AppendImpl(m_GlyphItems
.clone());
200 void GenericSalLayout::SetNeedFallback(ImplLayoutArgs
& rArgs
, sal_Int32 nCharPos
, bool bRightToLeft
)
202 if (nCharPos
< 0 || mbFuzzing
)
205 using namespace ::com::sun::star
;
208 mxBreak
= vcl::unohelper::CreateBreakIterator();
210 lang::Locale
aLocale(rArgs
.maLanguageTag
.getLocale());
212 //if position nCharPos is missing in the font, grab the entire grapheme and
213 //mark all glyphs as missing so the whole thing is rendered with the same
216 sal_Int32 nGraphemeEndPos
=
217 mxBreak
->nextCharacters(rArgs
.mrStr
, nCharPos
, aLocale
,
218 i18n::CharacterIteratorMode::SKIPCELL
, 1, nDone
);
219 // Safely advance nCharPos in case it is a non-BMP character.
220 rArgs
.mrStr
.iterateCodePoints(&nCharPos
);
221 sal_Int32 nGraphemeStartPos
=
222 mxBreak
->previousCharacters(rArgs
.mrStr
, nCharPos
, aLocale
,
223 i18n::CharacterIteratorMode::SKIPCELL
, 1, nDone
);
225 rArgs
.NeedFallback(nGraphemeStartPos
, nGraphemeEndPos
, bRightToLeft
);
228 void GenericSalLayout::AdjustLayout(ImplLayoutArgs
& rArgs
)
230 SalLayout::AdjustLayout(rArgs
);
234 else if (rArgs
.mnLayoutWidth
)
235 Justify(rArgs
.mnLayoutWidth
);
236 // apply asian kerning if the glyphs are not already formatted
237 else if ((rArgs
.mnFlags
& SalLayoutFlags::KerningAsian
)
238 && !(rArgs
.mnFlags
& SalLayoutFlags::Vertical
))
239 ApplyAsianKerning(rArgs
.mrStr
);
242 void GenericSalLayout::DrawText(SalGraphics
& rSalGraphics
) const
244 //call platform dependent DrawText functions
245 rSalGraphics
.DrawTextLayout( *this );
248 // Find if the nominal glyph of the character is an input to “vert” feature.
249 // We don’t check for a specific script or language as it shouldn’t matter
250 // here; if the glyph would be the result from applying “vert” for any
251 // script/language then we want to always treat it as upright glyph.
252 bool GenericSalLayout::HasVerticalAlternate(sal_UCS4 aChar
, sal_UCS4 aVariationSelector
)
254 hb_codepoint_t nGlyphIndex
= 0;
255 hb_font_t
*pHbFont
= GetFont().GetHbFont();
256 if (!hb_font_get_glyph(pHbFont
, aChar
, aVariationSelector
, &nGlyphIndex
))
261 hb_face_t
* pHbFace
= hb_font_get_face(pHbFont
);
262 mpVertGlyphs
= hb_set_create();
264 // Find all GSUB lookups for “vert” feature.
265 hb_set_t
* pLookups
= hb_set_create();
266 hb_tag_t
const pFeatures
[] = { HB_TAG('v','e','r','t'), HB_TAG_NONE
};
267 hb_ot_layout_collect_lookups(pHbFace
, HB_OT_TAG_GSUB
, nullptr, nullptr, pFeatures
, pLookups
);
268 if (!hb_set_is_empty(pLookups
))
270 // Find the output glyphs in each lookup (i.e. the glyphs that
271 // would result from applying this lookup).
272 hb_codepoint_t nIdx
= HB_SET_VALUE_INVALID
;
273 while (hb_set_next(pLookups
, &nIdx
))
275 hb_set_t
* pGlyphs
= hb_set_create();
276 hb_ot_layout_lookup_collect_glyphs(pHbFace
, HB_OT_TAG_GSUB
, nIdx
,
277 nullptr, // glyphs before
278 pGlyphs
, // glyphs input
279 nullptr, // glyphs after
280 nullptr); // glyphs out
281 hb_set_union(mpVertGlyphs
, pGlyphs
);
286 return hb_set_has(mpVertGlyphs
, nGlyphIndex
) != 0;
289 bool GenericSalLayout::LayoutText(ImplLayoutArgs
& rArgs
, const SalLayoutGlyphsImpl
* pGlyphs
)
291 // No need to touch m_GlyphItems at all for an empty string.
292 if (rArgs
.mnEndCharPos
- rArgs
.mnMinCharPos
<= 0)
297 // Work with pre-computed glyph items.
298 m_GlyphItems
= *pGlyphs
;
299 for(const GlyphItem
& item
: m_GlyphItems
)
301 SetNeedFallback(rArgs
, item
.charPos(), item
.IsRTLGlyph());
302 // Some flags are set as a side effect of text layout, restore them here.
303 rArgs
.mnFlags
|= pGlyphs
->mnFlags
;
307 hb_font_t
*pHbFont
= GetFont().GetHbFont();
308 bool isGraphite
= GetFont().IsGraphiteFont();
310 int nGlyphCapacity
= 2 * (rArgs
.mnEndCharPos
- rArgs
.mnMinCharPos
);
311 m_GlyphItems
.reserve(nGlyphCapacity
);
313 const int nLength
= rArgs
.mrStr
.getLength();
314 const sal_Unicode
*pStr
= rArgs
.mrStr
.getStr();
316 std::unique_ptr
<vcl::TextLayoutCache
> pNewScriptRun
;
317 vcl::TextLayoutCache
const* pTextLayout
;
318 if (rArgs
.m_pTextLayoutCache
)
320 pTextLayout
= rArgs
.m_pTextLayoutCache
; // use cache!
324 pNewScriptRun
.reset(new vcl::TextLayoutCache(pStr
, rArgs
.mnEndCharPos
));
325 pTextLayout
= pNewScriptRun
.get();
328 // nBaseOffset is used to align vertical text to the center of rotated
329 // horizontal text. That is the offset from original baseline to
330 // the center of EM box. Maybe we can use OpenType base table to improve this
332 DeviceCoordinate nBaseOffset
= 0;
333 if (rArgs
.mnFlags
& SalLayoutFlags::Vertical
)
335 hb_font_extents_t extents
;
336 if (hb_font_get_h_extents(pHbFont
, &extents
))
337 nBaseOffset
= ( extents
.ascender
+ extents
.descender
) / 2;
340 hb_buffer_t
* pHbBuffer
= hb_buffer_create();
341 hb_buffer_pre_allocate(pHbBuffer
, nGlyphCapacity
);
342 #if !HB_VERSION_ATLEAST(1, 1, 0)
343 static hb_unicode_funcs_t
* pHbUnicodeFuncs
= getUnicodeFuncs();
344 hb_buffer_set_unicode_funcs(pHbBuffer
, pHbUnicodeFuncs
);
347 const FontSelectPattern
& rFontSelData
= GetFont().GetFontSelectPattern();
348 if (rArgs
.mnFlags
& SalLayoutFlags::DisableKerning
)
350 SAL_INFO("vcl.harfbuzz", "Disabling kerning for font: " << rFontSelData
.maTargetName
);
351 maFeatures
.push_back({ HB_TAG('k','e','r','n'), 0, 0, static_cast<unsigned int>(-1) });
354 if (rFontSelData
.GetPitch() == PITCH_FIXED
)
356 SAL_INFO("vcl.harfbuzz", "Disabling ligatures for font: " << rFontSelData
.maTargetName
);
357 maFeatures
.push_back({ HB_TAG('l','i','g','a'), 0, 0, static_cast<unsigned int>(-1) });
360 ParseFeatures(rFontSelData
.maTargetName
);
364 GetFont().GetScale(&nXScale
, &nYScale
);
366 Point
aCurrPos(0, 0);
369 int nBidiMinRunPos
, nBidiEndRunPos
;
371 if (!rArgs
.GetNextRun(&nBidiMinRunPos
, &nBidiEndRunPos
, &bRightToLeft
))
374 // Find script subruns.
375 std::vector
<SubRun
> aSubRuns
;
376 int nCurrentPos
= nBidiMinRunPos
;
378 for (; k
< pTextLayout
->runs
.size(); ++k
)
380 vcl::Run
const& rRun(pTextLayout
->runs
[k
]);
381 if (rRun
.nStart
<= nCurrentPos
&& nCurrentPos
< rRun
.nEnd
)
389 hb_script_t aScript
= hb_icu_script_to_script(pTextLayout
->runs
[k
].nCode
);
390 aSubRuns
.push_back({ nBidiMinRunPos
, nBidiEndRunPos
, aScript
, bRightToLeft
? HB_DIRECTION_RTL
: HB_DIRECTION_LTR
});
394 while (nCurrentPos
< nBidiEndRunPos
&& k
< pTextLayout
->runs
.size())
396 int32_t nMinRunPos
= nCurrentPos
;
397 int32_t nEndRunPos
= std::min(pTextLayout
->runs
[k
].nEnd
, nBidiEndRunPos
);
398 hb_direction_t aDirection
= bRightToLeft
? HB_DIRECTION_RTL
: HB_DIRECTION_LTR
;
399 hb_script_t aScript
= hb_icu_script_to_script(pTextLayout
->runs
[k
].nCode
);
400 // For vertical text, further divide the runs based on character
402 if (rArgs
.mnFlags
& SalLayoutFlags::Vertical
)
404 sal_Int32 nIdx
= nMinRunPos
;
405 while (nIdx
< nEndRunPos
)
407 sal_Int32 nPrevIdx
= nIdx
;
408 sal_UCS4 aChar
= rArgs
.mrStr
.iterateCodePoints(&nIdx
);
409 VerticalOrientation aVo
= GetVerticalOrientation(aChar
, rArgs
.maLanguageTag
);
411 sal_UCS4 aVariationSelector
= 0;
412 if (nIdx
< nEndRunPos
)
414 sal_Int32 nNextIdx
= nIdx
;
415 sal_UCS4 aNextChar
= rArgs
.mrStr
.iterateCodePoints(&nNextIdx
);
416 if (u_hasBinaryProperty(aNextChar
, UCHAR_VARIATION_SELECTOR
))
419 aVariationSelector
= aNextChar
;
423 // Charters with U and Tu vertical orientation should
424 // be shaped in vertical direction. But characters
425 // with Tr should be shaped in vertical direction
426 // only if they have vertical alternates, otherwise
427 // they should be shaped in horizontal direction
429 // See http://unicode.org/reports/tr50/#vo
430 if (aVo
== VerticalOrientation::Upright
||
431 aVo
== VerticalOrientation::TransformedUpright
||
432 (aVo
== VerticalOrientation::TransformedRotated
&&
433 HasVerticalAlternate(aChar
, aVariationSelector
)))
435 aDirection
= HB_DIRECTION_TTB
;
439 aDirection
= bRightToLeft
? HB_DIRECTION_RTL
: HB_DIRECTION_LTR
;
442 if (aSubRuns
.empty() || aSubRuns
.back().maDirection
!= aDirection
)
443 aSubRuns
.push_back({ nPrevIdx
, nIdx
, aScript
, aDirection
});
445 aSubRuns
.back().mnEnd
= nIdx
;
450 aSubRuns
.push_back({ nMinRunPos
, nEndRunPos
, aScript
, aDirection
});
453 nCurrentPos
= nEndRunPos
;
458 // RTL subruns should be reversed to ensure that final glyph order is
461 std::reverse(aSubRuns
.begin(), aSubRuns
.end());
463 for (const auto& aSubRun
: aSubRuns
)
465 hb_buffer_clear_contents(pHbBuffer
);
467 const int nMinRunPos
= aSubRun
.mnMin
;
468 const int nEndRunPos
= aSubRun
.mnEnd
;
469 const int nRunLen
= nEndRunPos
- nMinRunPos
;
471 OString sLanguage
= msLanguage
;
472 if (sLanguage
.isEmpty())
473 sLanguage
= OUStringToOString(rArgs
.maLanguageTag
.getBcp47(), RTL_TEXTENCODING_ASCII_US
);
475 int nHbFlags
= HB_BUFFER_FLAGS_DEFAULT
;
477 nHbFlags
|= HB_BUFFER_FLAG_BOT
; /* Beginning-of-text */
478 if (nEndRunPos
== nLength
)
479 nHbFlags
|= HB_BUFFER_FLAG_EOT
; /* End-of-text */
481 hb_buffer_set_direction(pHbBuffer
, aSubRun
.maDirection
);
482 hb_buffer_set_script(pHbBuffer
, aSubRun
.maScript
);
483 hb_buffer_set_language(pHbBuffer
, hb_language_from_string(sLanguage
.getStr(), -1));
484 hb_buffer_set_flags(pHbBuffer
, static_cast<hb_buffer_flags_t
>(nHbFlags
));
486 pHbBuffer
, reinterpret_cast<uint16_t const *>(pStr
), nLength
,
487 nMinRunPos
, nRunLen
);
488 hb_buffer_set_cluster_level(pHbBuffer
, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS
);
490 // The shapers that we want HarfBuzz to use, in the order of
491 // preference. The coretext_aat shaper is available only on macOS,
492 // but there is no harm in always including it, HarfBuzz will
493 // ignore unavailable shapers.
494 const char*const pHbShapers
[] = { "graphite2", "coretext_aat", "ot", "fallback", nullptr };
495 bool ok
= hb_shape_full(pHbFont
, pHbBuffer
, maFeatures
.data(), maFeatures
.size(), pHbShapers
);
499 int nRunGlyphCount
= hb_buffer_get_length(pHbBuffer
);
500 hb_glyph_info_t
*pHbGlyphInfos
= hb_buffer_get_glyph_infos(pHbBuffer
, nullptr);
501 hb_glyph_position_t
*pHbPositions
= hb_buffer_get_glyph_positions(pHbBuffer
, nullptr);
503 for (int i
= 0; i
< nRunGlyphCount
; ++i
) {
504 int32_t nGlyphIndex
= pHbGlyphInfos
[i
].codepoint
;
505 int32_t nCharPos
= pHbGlyphInfos
[i
].cluster
;
506 int32_t nCharCount
= 0;
507 bool bInCluster
= false;
508 bool bClusterStart
= false;
510 // Find the number of characters that make up this glyph.
513 // If the cluster is the same as previous glyph, then this
514 // already consumed, skip.
515 if (i
> 0 && pHbGlyphInfos
[i
].cluster
== pHbGlyphInfos
[i
- 1].cluster
)
522 // Find the next glyph with a different cluster, or the
525 int32_t nNextCharPos
= nCharPos
;
526 while (nNextCharPos
== nCharPos
&& j
< nRunGlyphCount
)
527 nNextCharPos
= pHbGlyphInfos
[j
++].cluster
;
529 if (nNextCharPos
== nCharPos
)
530 nNextCharPos
= nEndRunPos
;
531 nCharCount
= nNextCharPos
- nCharPos
;
532 if ((i
== 0 || pHbGlyphInfos
[i
].cluster
!= pHbGlyphInfos
[i
- 1].cluster
) &&
533 (i
< nRunGlyphCount
- 1 && pHbGlyphInfos
[i
].cluster
== pHbGlyphInfos
[i
+ 1].cluster
))
534 bClusterStart
= true;
539 // If the cluster is the same as previous glyph, then this
540 // will be consumed later, skip.
541 if (i
< nRunGlyphCount
- 1 && pHbGlyphInfos
[i
].cluster
== pHbGlyphInfos
[i
+ 1].cluster
)
548 // Find the previous glyph with a different cluster, or
551 int32_t nNextCharPos
= nCharPos
;
552 while (nNextCharPos
== nCharPos
&& j
>= 0)
553 nNextCharPos
= pHbGlyphInfos
[j
--].cluster
;
555 if (nNextCharPos
== nCharPos
)
556 nNextCharPos
= nEndRunPos
;
557 nCharCount
= nNextCharPos
- nCharPos
;
558 if ((i
== nRunGlyphCount
- 1 || pHbGlyphInfos
[i
].cluster
!= pHbGlyphInfos
[i
+ 1].cluster
) &&
559 (i
> 0 && pHbGlyphInfos
[i
].cluster
== pHbGlyphInfos
[i
- 1].cluster
))
560 bClusterStart
= true;
564 // if needed request glyph fallback by updating LayoutArgs
567 SetNeedFallback(rArgs
, nCharPos
, bRightToLeft
);
568 if (SalLayoutFlags::ForFallback
& rArgs
.mnFlags
)
572 GlyphItemFlags nGlyphFlags
= GlyphItemFlags::NONE
;
574 nGlyphFlags
|= GlyphItemFlags::IS_RTL_GLYPH
;
577 nGlyphFlags
|= GlyphItemFlags::IS_CLUSTER_START
;
580 nGlyphFlags
|= GlyphItemFlags::IS_IN_CLUSTER
;
583 = rArgs
.mrStr
.iterateCodePoints(&o3tl::temporary(sal_Int32(nCharPos
)), 0);
585 if (u_getIntPropertyValue(aChar
, UCHAR_GENERAL_CATEGORY
) == U_NON_SPACING_MARK
)
586 nGlyphFlags
|= GlyphItemFlags::IS_DIACRITIC
;
588 if (u_isUWhiteSpace(aChar
))
589 nGlyphFlags
|= GlyphItemFlags::IS_SPACING
;
591 if (aSubRun
.maScript
== HB_SCRIPT_ARABIC
&&
592 HB_DIRECTION_IS_BACKWARD(aSubRun
.maDirection
) &&
593 !(nGlyphFlags
& GlyphItemFlags::IS_SPACING
))
595 nGlyphFlags
|= GlyphItemFlags::ALLOW_KASHIDA
;
596 rArgs
.mnFlags
|= SalLayoutFlags::KashidaJustification
;
599 DeviceCoordinate nAdvance
, nXOffset
, nYOffset
;
600 if (aSubRun
.maDirection
== HB_DIRECTION_TTB
)
602 nGlyphFlags
|= GlyphItemFlags::IS_VERTICAL
;
604 nAdvance
= -pHbPositions
[i
].y_advance
;
605 nXOffset
= -pHbPositions
[i
].y_offset
;
606 nYOffset
= -pHbPositions
[i
].x_offset
- nBaseOffset
;
610 nAdvance
= pHbPositions
[i
].x_advance
;
611 nXOffset
= pHbPositions
[i
].x_offset
;
612 nYOffset
= -pHbPositions
[i
].y_offset
;
615 nAdvance
= std::lround(nAdvance
* nXScale
);
616 nXOffset
= std::lround(nXOffset
* nXScale
);
617 nYOffset
= std::lround(nYOffset
* nYScale
);
619 Point
aNewPos(aCurrPos
.X() + nXOffset
, aCurrPos
.Y() + nYOffset
);
620 const GlyphItem
aGI(nCharPos
, nCharCount
, nGlyphIndex
, aNewPos
, nGlyphFlags
,
621 nAdvance
, nXOffset
, &GetFont());
622 m_GlyphItems
.push_back(aGI
);
624 aCurrPos
.AdjustX(nAdvance
);
629 hb_buffer_destroy(pHbBuffer
);
631 // Some flags are set as a side effect of text layout, save them here.
632 if (rArgs
.mnFlags
& SalLayoutFlags::GlyphItemsOnly
)
633 m_GlyphItems
.mnFlags
= rArgs
.mnFlags
;
638 void GenericSalLayout::GetCharWidths(DeviceCoordinate
* pCharWidths
) const
640 const int nCharCount
= mnEndCharPos
- mnMinCharPos
;
642 for (int i
= 0; i
< nCharCount
; ++i
)
645 for (auto const& aGlyphItem
: m_GlyphItems
)
647 const int nIndex
= aGlyphItem
.charPos() - mnMinCharPos
;
648 if (nIndex
>= nCharCount
)
650 pCharWidths
[nIndex
] += aGlyphItem
.m_nNewWidth
;
654 // A note on how Kashida justification is implemented (because it took me 5
655 // years to figure it out):
656 // The decision to insert Kashidas, where and how much is taken by Writer.
657 // This decision is communicated to us in a very indirect way; by increasing
658 // the width of the character after which Kashidas should be inserted by the
661 // Writer eventually calls IsKashidaPosValid() to check whether it can insert a
662 // Kashida between two characters or not.
665 // - In LayoutText() set KashidaJustification flag based on text script.
666 // - In ApplyDXArray():
667 // * Check the above flag to decide whether to insert Kashidas or not.
668 // * For any RTL glyph that has DX adjustment, insert enough Kashidas to
669 // fill in the added space.
671 void GenericSalLayout::ApplyDXArray(const ImplLayoutArgs
& rArgs
)
673 if (rArgs
.mpDXArray
== nullptr)
676 int nCharCount
= mnEndCharPos
- mnMinCharPos
;
677 std::unique_ptr
<DeviceCoordinate
[]> const pOldCharWidths(new DeviceCoordinate
[nCharCount
]);
678 std::unique_ptr
<DeviceCoordinate
[]> const pNewCharWidths(new DeviceCoordinate
[nCharCount
]);
680 // Get the natural character widths (i.e. before applying DX adjustments).
681 GetCharWidths(pOldCharWidths
.get());
683 // Calculate the character widths after DX adjustments.
684 for (int i
= 0; i
< nCharCount
; ++i
)
687 pNewCharWidths
[i
] = rArgs
.mpDXArray
[i
];
689 pNewCharWidths
[i
] = rArgs
.mpDXArray
[i
] - rArgs
.mpDXArray
[i
- 1];
692 bool bKashidaJustify
= false;
693 DeviceCoordinate nKashidaWidth
= 0;
694 hb_codepoint_t nKashidaIndex
= 0;
695 if (rArgs
.mnFlags
& SalLayoutFlags::KashidaJustification
)
697 hb_font_t
*pHbFont
= GetFont().GetHbFont();
698 // Find Kashida glyph width and index.
699 if (hb_font_get_glyph(pHbFont
, 0x0640, 0, &nKashidaIndex
))
700 nKashidaWidth
= GetFont().GetKashidaWidth();
701 bKashidaJustify
= nKashidaWidth
!= 0;
704 // Map of Kashida insertion points (in the glyph items vector) and the
706 std::map
<size_t, DeviceCoordinate
> pKashidas
;
708 // The accumulated difference in X position.
709 DeviceCoordinate nDelta
= 0;
711 // Apply the DX adjustments to glyph positions and widths.
713 while (i
< m_GlyphItems
.size())
715 // Accumulate the width difference for all characters corresponding to
717 int nCharPos
= m_GlyphItems
[i
].charPos() - mnMinCharPos
;
718 DeviceCoordinate nDiff
= 0;
719 for (int j
= 0; j
< m_GlyphItems
[i
].charCount(); j
++)
720 nDiff
+= pNewCharWidths
[nCharPos
+ j
] - pOldCharWidths
[nCharPos
+ j
];
722 if (!m_GlyphItems
[i
].IsRTLGlyph())
724 // Adjust the width and position of the first (leftmost) glyph in
726 m_GlyphItems
[i
].m_nNewWidth
+= nDiff
;
727 m_GlyphItems
[i
].m_aLinearPos
.AdjustX(nDelta
);
729 // Adjust the position of the rest of the glyphs in the cluster.
730 while (++i
< m_GlyphItems
.size())
732 if (!m_GlyphItems
[i
].IsInCluster())
734 m_GlyphItems
[i
].m_aLinearPos
.AdjustX(nDelta
);
737 else if (m_GlyphItems
[i
].IsInCluster())
739 // RTL glyph in the middle of the cluster, will be handled in the
745 // Adjust the width and position of the first (rightmost) glyph in
747 // For RTL, we put all the adjustment to the left of the glyph.
748 m_GlyphItems
[i
].m_nNewWidth
+= nDiff
;
749 m_GlyphItems
[i
].m_aLinearPos
.AdjustX(nDelta
+ nDiff
);
751 // Adjust the X position of all glyphs in the cluster.
756 if (!m_GlyphItems
[j
].IsInCluster())
758 m_GlyphItems
[j
].m_aLinearPos
.AdjustX(nDelta
+ nDiff
);
761 // If this glyph is Kashida-justifiable, then mark this as a
762 // Kashida position. Since this must be a RTL glyph, we mark the
763 // last glyph in the cluster not the first as this would be the
765 if (bKashidaJustify
&& m_GlyphItems
[i
].AllowKashida() &&
766 nDiff
> m_GlyphItems
[i
].charCount()) // Rounding errors, 1 pixel per character!
768 pKashidas
[i
] = nDiff
;
769 // Move any non-spacing marks attached to this cluster as well.
770 // Looping backward because this is RTL glyph.
773 if (!m_GlyphItems
[j
].IsDiacritic())
775 m_GlyphItems
[j
--].m_aLinearPos
.AdjustX(nDiff
);
781 // Increment the delta, the loop above makes sure we do so only once
782 // for every character (cluster) not for every glyph (otherwise we
783 // would apply it multiple times for each glyphs belonging to the same
784 // character which is wrong since DX adjustments are character based).
788 // Insert Kashida glyphs.
789 if (!bKashidaJustify
|| pKashidas
.empty())
792 size_t nInserted
= 0;
793 for (auto const& pKashida
: pKashidas
)
795 auto pGlyphIter
= m_GlyphItems
.begin() + nInserted
+ pKashida
.first
;
797 // The total Kashida width.
798 DeviceCoordinate nTotalWidth
= pKashida
.second
;
800 // Number of times to repeat each Kashida.
802 if (nTotalWidth
> nKashidaWidth
)
803 nCopies
= nTotalWidth
/ nKashidaWidth
;
805 // See if we can improve the fit by adding an extra Kashidas and
806 // squeezing them together a bit.
807 DeviceCoordinate nOverlap
= 0;
808 DeviceCoordinate nShortfall
= nTotalWidth
- nKashidaWidth
* nCopies
;
812 DeviceCoordinate nExcess
= nCopies
* nKashidaWidth
- nTotalWidth
;
814 nOverlap
= nExcess
/ (nCopies
- 1);
817 Point
aPos(pGlyphIter
->m_aLinearPos
.getX() - nTotalWidth
, 0);
818 int nCharPos
= pGlyphIter
->charPos();
819 GlyphItemFlags
const nFlags
= GlyphItemFlags::IS_IN_CLUSTER
| GlyphItemFlags::IS_RTL_GLYPH
;
822 GlyphItem
aKashida(nCharPos
, 0, nKashidaIndex
, aPos
, nFlags
, nKashidaWidth
, 0, &GetFont());
823 pGlyphIter
= m_GlyphItems
.insert(pGlyphIter
, aKashida
);
824 aPos
.AdjustX(nKashidaWidth
);
825 aPos
.AdjustX( -nOverlap
);
832 bool GenericSalLayout::IsKashidaPosValid(int nCharPos
) const
834 for (auto pIter
= m_GlyphItems
.begin(); pIter
!= m_GlyphItems
.end(); ++pIter
)
836 if (pIter
->charPos() == nCharPos
)
838 // The position is the first glyph, this would happen if we
839 // changed the text styling in the middle of a word. Since we don’t
840 // do ligatures across layout engine instances, this can’t be a
841 // ligature so it should be fine.
842 if (pIter
== m_GlyphItems
.begin())
845 // If the character is not supported by this layout, return false
846 // so that fallback layouts would be checked for it.
847 if (pIter
->glyphId() == 0)
850 // Search backwards for previous glyph belonging to a different
851 // character. We are looking backwards because we are dealing with
852 // RTL glyphs, which will be in visual order.
853 for (auto pPrev
= pIter
- 1; pPrev
!= m_GlyphItems
.begin(); --pPrev
)
855 if (pPrev
->charPos() != nCharPos
)
857 // Check if the found glyph belongs to the next character,
858 // otherwise the current glyph will be a ligature which is
859 // invalid kashida position.
860 if (pPrev
->charPos() == (nCharPos
+ 1))
871 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */