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 <unotools/configmgr.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>
45 GenericSalLayout::GenericSalLayout(LogicalFontInstance
&rFont
)
47 , mpVertGlyphs(nullptr)
48 , mbFuzzing(utl::ConfigManager::IsFuzzing())
52 GenericSalLayout::~GenericSalLayout()
55 hb_set_destroy(mpVertGlyphs
);
58 void GenericSalLayout::ParseFeatures(std::u16string_view aName
)
60 vcl::font::FeatureParser
aParser(aName
);
61 const OUString
& sLanguage
= aParser
.getLanguage();
62 if (!sLanguage
.isEmpty())
63 msLanguage
= OUStringToOString(sLanguage
, RTL_TEXTENCODING_ASCII_US
);
65 for (auto const &rFeat
: aParser
.getFeatures())
67 hb_feature_t aFeature
{ rFeat
.m_nTag
, rFeat
.m_nValue
, rFeat
.m_nStart
, rFeat
.m_nEnd
};
68 maFeatures
.push_back(aFeature
);
79 hb_direction_t maDirection
;
85 #if U_ICU_VERSION_MAJOR_NUM >= 63
86 enum class VerticalOrientation
{
87 Upright
= U_VO_UPRIGHT
,
88 Rotated
= U_VO_ROTATED
,
89 TransformedUpright
= U_VO_TRANSFORMED_UPRIGHT
,
90 TransformedRotated
= U_VO_TRANSFORMED_ROTATED
93 #include "VerticalOrientationData.cxx"
95 // These must match the values in the file included above.
96 enum class VerticalOrientation
{
99 TransformedUpright
= 2,
100 TransformedRotated
= 3
104 VerticalOrientation
GetVerticalOrientation(sal_UCS4 cCh
, const LanguageTag
& rTag
)
106 // Override orientation of fullwidth colon , semi-colon,
107 // and Bopomofo tonal marks.
108 if ((cCh
== 0xff1a || cCh
== 0xff1b
109 || cCh
== 0x2ca || cCh
== 0x2cb || cCh
== 0x2c7 || cCh
== 0x2d9)
110 && rTag
.getLanguage() == "zh")
111 return VerticalOrientation::TransformedUpright
;
113 #if U_ICU_VERSION_MAJOR_NUM >= 63
114 int32_t nRet
= u_getIntPropertyValue(cCh
, UCHAR_VERTICAL_ORIENTATION
);
120 nRet
= sVerticalOrientationValues
[sVerticalOrientationPages
[0][cCh
>> kVerticalOrientationCharBits
]]
121 [cCh
& ((1 << kVerticalOrientationCharBits
) - 1)];
123 else if (cCh
< (kVerticalOrientationMaxPlane
+ 1) * 0x10000)
125 nRet
= sVerticalOrientationValues
[sVerticalOrientationPages
[sVerticalOrientationPlanes
[(cCh
>> 16) - 1]]
126 [(cCh
& 0xffff) >> kVerticalOrientationCharBits
]]
127 [cCh
& ((1 << kVerticalOrientationCharBits
) - 1)];
131 // Default value for unassigned
132 SAL_WARN("vcl.gdi", "Getting VerticalOrientation for codepoint outside Unicode range");
136 return VerticalOrientation(nRet
);
141 SalLayoutGlyphs
GenericSalLayout::GetGlyphs() const
143 SalLayoutGlyphs glyphs
;
144 glyphs
.AppendImpl(m_GlyphItems
.clone());
148 void GenericSalLayout::SetNeedFallback(vcl::text::ImplLayoutArgs
& rArgs
, sal_Int32 nCharPos
, bool bRightToLeft
)
150 if (nCharPos
< 0 || mbFuzzing
)
153 using namespace ::com::sun::star
;
156 mxBreak
= vcl::unohelper::CreateBreakIterator();
158 lang::Locale
aLocale(rArgs
.maLanguageTag
.getLocale());
160 //if position nCharPos is missing in the font, grab the entire grapheme and
161 //mark all glyphs as missing so the whole thing is rendered with the same
164 int nGraphemeEndPos
=
165 mxBreak
->nextCharacters(rArgs
.mrStr
, nCharPos
, aLocale
,
166 i18n::CharacterIteratorMode::SKIPCELL
, 1, nDone
);
167 // Safely advance nCharPos in case it is a non-BMP character.
168 rArgs
.mrStr
.iterateCodePoints(&nCharPos
);
169 int nGraphemeStartPos
=
170 mxBreak
->previousCharacters(rArgs
.mrStr
, nCharPos
, aLocale
,
171 i18n::CharacterIteratorMode::SKIPCELL
, 1, nDone
);
174 // If the start of the fallback run is Mongolian character and the previous
175 // character is NNBSP, we want to include the NNBSP in the fallback since
176 // it has special uses in Mongolian and have to be in the same text run to
178 sal_Int32 nTempPos
= nGraphemeStartPos
;
179 if (nGraphemeStartPos
> 0)
181 auto nCurrChar
= rArgs
.mrStr
.iterateCodePoints(&nTempPos
, 0);
182 auto nPrevChar
= rArgs
.mrStr
.iterateCodePoints(&nTempPos
, -1);
183 if (nPrevChar
== 0x202F
184 && u_getIntPropertyValue(nCurrChar
, UCHAR_SCRIPT
) == USCRIPT_MONGOLIAN
)
185 nGraphemeStartPos
= nTempPos
;
188 //stay inside the Layout range (e.g. with tdf124116-1.odt)
189 nGraphemeStartPos
= std::max(rArgs
.mnMinCharPos
, nGraphemeStartPos
);
190 nGraphemeEndPos
= std::min(rArgs
.mnEndCharPos
, nGraphemeEndPos
);
192 rArgs
.AddFallbackRun(nGraphemeStartPos
, nGraphemeEndPos
, bRightToLeft
);
195 void GenericSalLayout::AdjustLayout(vcl::text::ImplLayoutArgs
& rArgs
)
197 SalLayout::AdjustLayout(rArgs
);
199 if (rArgs
.mpNaturalDXArray
)
200 ApplyDXArray(rArgs
.mpNaturalDXArray
, rArgs
.mpKashidaArray
);
201 else if (rArgs
.mnLayoutWidth
)
202 Justify(rArgs
.mnLayoutWidth
);
203 // apply asian kerning if the glyphs are not already formatted
204 else if ((rArgs
.mnFlags
& SalLayoutFlags::KerningAsian
)
205 && !(rArgs
.mnFlags
& SalLayoutFlags::Vertical
))
206 ApplyAsianKerning(rArgs
.mrStr
);
209 void GenericSalLayout::DrawText(SalGraphics
& rSalGraphics
) const
211 //call platform dependent DrawText functions
212 rSalGraphics
.DrawTextLayout( *this );
215 // Find if the nominal glyph of the character is an input to “vert” feature.
216 // We don’t check for a specific script or language as it shouldn’t matter
217 // here; if the glyph would be the result from applying “vert” for any
218 // script/language then we want to always treat it as upright glyph.
219 bool GenericSalLayout::HasVerticalAlternate(sal_UCS4 aChar
, sal_UCS4 aVariationSelector
)
221 sal_GlyphId nGlyphIndex
= GetFont().GetGlyphIndex(aChar
, aVariationSelector
);
227 hb_face_t
* pHbFace
= hb_font_get_face(GetFont().GetHbFont());
228 mpVertGlyphs
= hb_set_create();
230 // Find all GSUB lookups for “vert” feature.
231 hb_set_t
* pLookups
= hb_set_create();
232 hb_tag_t
const pFeatures
[] = { HB_TAG('v','e','r','t'), HB_TAG_NONE
};
233 hb_ot_layout_collect_lookups(pHbFace
, HB_OT_TAG_GSUB
, nullptr, nullptr, pFeatures
, pLookups
);
234 if (!hb_set_is_empty(pLookups
))
236 // Find the output glyphs in each lookup (i.e. the glyphs that
237 // would result from applying this lookup).
238 hb_codepoint_t nIdx
= HB_SET_VALUE_INVALID
;
239 while (hb_set_next(pLookups
, &nIdx
))
241 hb_set_t
* pGlyphs
= hb_set_create();
242 hb_ot_layout_lookup_collect_glyphs(pHbFace
, HB_OT_TAG_GSUB
, nIdx
,
243 nullptr, // glyphs before
244 pGlyphs
, // glyphs input
245 nullptr, // glyphs after
246 nullptr); // glyphs out
247 hb_set_union(mpVertGlyphs
, pGlyphs
);
250 hb_set_destroy(pLookups
);
253 return hb_set_has(mpVertGlyphs
, nGlyphIndex
) != 0;
256 bool GenericSalLayout::LayoutText(vcl::text::ImplLayoutArgs
& rArgs
, const SalLayoutGlyphsImpl
* pGlyphs
)
258 // No need to touch m_GlyphItems at all for an empty string.
259 if (rArgs
.mnEndCharPos
- rArgs
.mnMinCharPos
<= 0)
264 // Work with pre-computed glyph items.
265 m_GlyphItems
= *pGlyphs
;
266 for(const GlyphItem
& item
: m_GlyphItems
)
268 SetNeedFallback(rArgs
, item
.charPos(), item
.IsRTLGlyph());
269 // Some flags are set as a side effect of text layout, restore them here.
270 rArgs
.mnFlags
|= pGlyphs
->GetFlags();
274 hb_font_t
*pHbFont
= GetFont().GetHbFont();
275 bool isGraphite
= GetFont().IsGraphiteFont();
277 int nGlyphCapacity
= 2 * (rArgs
.mnEndCharPos
- rArgs
.mnMinCharPos
);
278 m_GlyphItems
.reserve(nGlyphCapacity
);
280 const int nLength
= rArgs
.mrStr
.getLength();
281 const sal_Unicode
*pStr
= rArgs
.mrStr
.getStr();
283 std::optional
<vcl::text::TextLayoutCache
> oNewScriptRun
;
284 vcl::text::TextLayoutCache
const* pTextLayout
;
285 if (rArgs
.m_pTextLayoutCache
)
287 pTextLayout
= rArgs
.m_pTextLayoutCache
; // use cache!
291 oNewScriptRun
.emplace(pStr
, rArgs
.mnEndCharPos
);
292 pTextLayout
= &*oNewScriptRun
;
295 // nBaseOffset is used to align vertical text to the center of rotated
296 // horizontal text. That is the offset from original baseline to
297 // the center of EM box. Maybe we can use OpenType base table to improve this
299 DeviceCoordinate nBaseOffset
= 0;
300 if (rArgs
.mnFlags
& SalLayoutFlags::Vertical
)
302 hb_font_extents_t extents
;
303 if (hb_font_get_h_extents(pHbFont
, &extents
))
304 nBaseOffset
= ( extents
.ascender
+ extents
.descender
) / 2;
307 hb_buffer_t
* pHbBuffer
= hb_buffer_create();
308 hb_buffer_pre_allocate(pHbBuffer
, nGlyphCapacity
);
310 const vcl::font::FontSelectPattern
& rFontSelData
= GetFont().GetFontSelectPattern();
311 if (rArgs
.mnFlags
& SalLayoutFlags::DisableKerning
)
313 SAL_INFO("vcl.harfbuzz", "Disabling kerning for font: " << rFontSelData
.maTargetName
);
314 maFeatures
.push_back({ HB_TAG('k','e','r','n'), 0, 0, static_cast<unsigned int>(-1) });
317 if (rArgs
.mnFlags
& SalLayoutFlags::DisableLigatures
)
319 SAL_INFO("vcl.harfbuzz", "Disabling ligatures for font: " << rFontSelData
.maTargetName
);
321 // Both of these are optional ligatures, enabled by default but not for
322 // orthographically-required ligatures.
323 maFeatures
.push_back({ HB_TAG('l','i','g','a'), 0, 0, static_cast<unsigned int>(-1) });
324 maFeatures
.push_back({ HB_TAG('c','l','i','g'), 0, 0, static_cast<unsigned int>(-1) });
327 ParseFeatures(rFontSelData
.maTargetName
);
331 GetFont().GetScale(&nXScale
, &nYScale
);
333 DevicePoint
aCurrPos(0, 0);
336 int nBidiMinRunPos
, nBidiEndRunPos
;
338 if (!rArgs
.GetNextRun(&nBidiMinRunPos
, &nBidiEndRunPos
, &bRightToLeft
))
341 // Find script subruns.
342 std::vector
<SubRun
> aSubRuns
;
343 int nCurrentPos
= nBidiMinRunPos
;
345 for (; k
< pTextLayout
->runs
.size(); ++k
)
347 vcl::text::Run
const& rRun(pTextLayout
->runs
[k
]);
348 if (rRun
.nStart
<= nCurrentPos
&& nCurrentPos
< rRun
.nEnd
)
356 hb_script_t aScript
= hb_icu_script_to_script(pTextLayout
->runs
[k
].nCode
);
357 aSubRuns
.push_back({ nBidiMinRunPos
, nBidiEndRunPos
, aScript
, bRightToLeft
? HB_DIRECTION_RTL
: HB_DIRECTION_LTR
});
361 while (nCurrentPos
< nBidiEndRunPos
&& k
< pTextLayout
->runs
.size())
363 int32_t nMinRunPos
= nCurrentPos
;
364 int32_t nEndRunPos
= std::min(pTextLayout
->runs
[k
].nEnd
, nBidiEndRunPos
);
365 hb_direction_t aDirection
= bRightToLeft
? HB_DIRECTION_RTL
: HB_DIRECTION_LTR
;
366 hb_script_t aScript
= hb_icu_script_to_script(pTextLayout
->runs
[k
].nCode
);
367 // For vertical text, further divide the runs based on character
369 if (rArgs
.mnFlags
& SalLayoutFlags::Vertical
)
371 sal_Int32 nIdx
= nMinRunPos
;
372 while (nIdx
< nEndRunPos
)
374 sal_Int32 nPrevIdx
= nIdx
;
375 sal_UCS4 aChar
= rArgs
.mrStr
.iterateCodePoints(&nIdx
);
376 VerticalOrientation aVo
= GetVerticalOrientation(aChar
, rArgs
.maLanguageTag
);
378 sal_UCS4 aVariationSelector
= 0;
379 if (nIdx
< nEndRunPos
)
381 sal_Int32 nNextIdx
= nIdx
;
382 sal_UCS4 aNextChar
= rArgs
.mrStr
.iterateCodePoints(&nNextIdx
);
383 if (u_hasBinaryProperty(aNextChar
, UCHAR_VARIATION_SELECTOR
))
386 aVariationSelector
= aNextChar
;
390 // Characters with U and Tu vertical orientation should
391 // be shaped in vertical direction. But characters
392 // with Tr should be shaped in vertical direction
393 // only if they have vertical alternates, otherwise
394 // they should be shaped in horizontal direction
396 // See http://unicode.org/reports/tr50/#vo
397 if (aVo
== VerticalOrientation::Upright
||
398 aVo
== VerticalOrientation::TransformedUpright
||
399 (aVo
== VerticalOrientation::TransformedRotated
&&
400 HasVerticalAlternate(aChar
, aVariationSelector
)))
402 aDirection
= HB_DIRECTION_TTB
;
406 aDirection
= bRightToLeft
? HB_DIRECTION_RTL
: HB_DIRECTION_LTR
;
409 if (aSubRuns
.empty() || aSubRuns
.back().maDirection
!= aDirection
|| aSubRuns
.back().maScript
!= aScript
)
410 aSubRuns
.push_back({ nPrevIdx
, nIdx
, aScript
, aDirection
});
412 aSubRuns
.back().mnEnd
= nIdx
;
417 aSubRuns
.push_back({ nMinRunPos
, nEndRunPos
, aScript
, aDirection
});
420 nCurrentPos
= nEndRunPos
;
425 // RTL subruns should be reversed to ensure that final glyph order is
428 std::reverse(aSubRuns
.begin(), aSubRuns
.end());
430 for (const auto& aSubRun
: aSubRuns
)
432 hb_buffer_clear_contents(pHbBuffer
);
434 const int nMinRunPos
= aSubRun
.mnMin
;
435 const int nEndRunPos
= aSubRun
.mnEnd
;
436 const int nRunLen
= nEndRunPos
- nMinRunPos
;
438 int nHbFlags
= HB_BUFFER_FLAGS_DEFAULT
;
440 // Produce HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL that we use below.
441 nHbFlags
|= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL
;
444 nHbFlags
|= HB_BUFFER_FLAG_BOT
; /* Beginning-of-text */
445 if (nEndRunPos
== nLength
)
446 nHbFlags
|= HB_BUFFER_FLAG_EOT
; /* End-of-text */
448 hb_buffer_set_direction(pHbBuffer
, aSubRun
.maDirection
);
449 hb_buffer_set_script(pHbBuffer
, aSubRun
.maScript
);
450 if (!msLanguage
.isEmpty())
452 hb_buffer_set_language(pHbBuffer
, hb_language_from_string(msLanguage
.getStr(), msLanguage
.getLength()));
456 OString sLanguage
= OUStringToOString(rArgs
.maLanguageTag
.getBcp47(), RTL_TEXTENCODING_ASCII_US
);
457 hb_buffer_set_language(pHbBuffer
, hb_language_from_string(sLanguage
.getStr(), sLanguage
.getLength()));
459 hb_buffer_set_flags(pHbBuffer
, static_cast<hb_buffer_flags_t
>(nHbFlags
));
461 pHbBuffer
, reinterpret_cast<uint16_t const *>(pStr
), nLength
,
462 nMinRunPos
, nRunLen
);
464 // The shapers that we want HarfBuzz to use, in the order of
466 const char*const pHbShapers
[] = { "graphite2", "ot", "fallback", nullptr };
467 bool ok
= hb_shape_full(pHbFont
, pHbBuffer
, maFeatures
.data(), maFeatures
.size(), pHbShapers
);
471 int nRunGlyphCount
= hb_buffer_get_length(pHbBuffer
);
472 hb_glyph_info_t
*pHbGlyphInfos
= hb_buffer_get_glyph_infos(pHbBuffer
, nullptr);
473 hb_glyph_position_t
*pHbPositions
= hb_buffer_get_glyph_positions(pHbBuffer
, nullptr);
475 for (int i
= 0; i
< nRunGlyphCount
; ++i
) {
476 int32_t nGlyphIndex
= pHbGlyphInfos
[i
].codepoint
;
477 int32_t nCharPos
= pHbGlyphInfos
[i
].cluster
;
478 int32_t nCharCount
= 0;
479 bool bInCluster
= false;
480 bool bClusterStart
= false;
482 // Find the number of characters that make up this glyph.
485 // If the cluster is the same as previous glyph, then this
486 // already consumed, skip.
487 if (i
> 0 && pHbGlyphInfos
[i
].cluster
== pHbGlyphInfos
[i
- 1].cluster
)
494 // Find the next glyph with a different cluster, or the
497 int32_t nNextCharPos
= nCharPos
;
498 while (nNextCharPos
== nCharPos
&& j
< nRunGlyphCount
)
499 nNextCharPos
= pHbGlyphInfos
[j
++].cluster
;
501 if (nNextCharPos
== nCharPos
)
502 nNextCharPos
= nEndRunPos
;
503 nCharCount
= nNextCharPos
- nCharPos
;
504 if ((i
== 0 || pHbGlyphInfos
[i
].cluster
!= pHbGlyphInfos
[i
- 1].cluster
) &&
505 (i
< nRunGlyphCount
- 1 && pHbGlyphInfos
[i
].cluster
== pHbGlyphInfos
[i
+ 1].cluster
))
506 bClusterStart
= true;
511 // If the cluster is the same as previous glyph, then this
512 // will be consumed later, skip.
513 if (i
< nRunGlyphCount
- 1 && pHbGlyphInfos
[i
].cluster
== pHbGlyphInfos
[i
+ 1].cluster
)
520 // Find the previous glyph with a different cluster, or
523 int32_t nNextCharPos
= nCharPos
;
524 while (nNextCharPos
== nCharPos
&& j
>= 0)
525 nNextCharPos
= pHbGlyphInfos
[j
--].cluster
;
527 if (nNextCharPos
== nCharPos
)
528 nNextCharPos
= nEndRunPos
;
529 nCharCount
= nNextCharPos
- nCharPos
;
530 if ((i
== nRunGlyphCount
- 1 || pHbGlyphInfos
[i
].cluster
!= pHbGlyphInfos
[i
+ 1].cluster
) &&
531 (i
> 0 && pHbGlyphInfos
[i
].cluster
== pHbGlyphInfos
[i
- 1].cluster
))
532 bClusterStart
= true;
536 // if needed request glyph fallback by updating LayoutArgs
539 SetNeedFallback(rArgs
, nCharPos
, bRightToLeft
);
540 if (SalLayoutFlags::ForFallback
& rArgs
.mnFlags
)
544 GlyphItemFlags nGlyphFlags
= GlyphItemFlags::NONE
;
546 nGlyphFlags
|= GlyphItemFlags::IS_RTL_GLYPH
;
549 nGlyphFlags
|= GlyphItemFlags::IS_CLUSTER_START
;
552 nGlyphFlags
|= GlyphItemFlags::IS_IN_CLUSTER
;
555 = rArgs
.mrStr
.iterateCodePoints(&o3tl::temporary(sal_Int32(nCharPos
)), 0);
557 if (u_isUWhiteSpace(aChar
))
558 nGlyphFlags
|= GlyphItemFlags::IS_SPACING
;
560 if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos
[i
]) & HB_GLYPH_FLAG_UNSAFE_TO_BREAK
)
561 nGlyphFlags
|= GlyphItemFlags::IS_UNSAFE_TO_BREAK
;
563 if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos
[i
]) & HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL
)
564 nGlyphFlags
|= GlyphItemFlags::IS_SAFE_TO_INSERT_KASHIDA
;
566 DeviceCoordinate nAdvance
, nXOffset
, nYOffset
;
567 if (aSubRun
.maDirection
== HB_DIRECTION_TTB
)
569 nGlyphFlags
|= GlyphItemFlags::IS_VERTICAL
;
571 nAdvance
= -pHbPositions
[i
].y_advance
;
572 nXOffset
= -pHbPositions
[i
].y_offset
;
573 nYOffset
= -pHbPositions
[i
].x_offset
- nBaseOffset
;
575 if (GetFont().NeedOffsetCorrection(pHbPositions
[i
].y_offset
))
577 // We need glyph's advance, top bearing, and height to
579 tools::Rectangle aRect
;
580 // Get cached bound rect value for the font,
581 GetFont().GetGlyphBoundRect(nGlyphIndex
, aRect
, true);
583 nXOffset
= -(aRect
.Top() / nXScale
+ ( pHbPositions
[i
].y_advance
584 + ( aRect
.GetHeight() / nXScale
) ) / 2 );
590 nAdvance
= pHbPositions
[i
].x_advance
;
591 nXOffset
= pHbPositions
[i
].x_offset
;
592 nYOffset
= -pHbPositions
[i
].y_offset
;
595 nAdvance
= std::lround(nAdvance
* nXScale
);
596 nXOffset
= std::lround(nXOffset
* nXScale
);
597 nYOffset
= std::lround(nYOffset
* nYScale
);
599 DevicePoint
aNewPos(aCurrPos
.getX() + nXOffset
, aCurrPos
.getY() + nYOffset
);
600 const GlyphItem
aGI(nCharPos
, nCharCount
, nGlyphIndex
, aNewPos
, nGlyphFlags
,
601 nAdvance
, nXOffset
, nYOffset
);
602 m_GlyphItems
.push_back(aGI
);
604 aCurrPos
.adjustX(nAdvance
);
609 hb_buffer_destroy(pHbBuffer
);
611 // Some flags are set as a side effect of text layout, save them here.
612 if (rArgs
.mnFlags
& SalLayoutFlags::GlyphItemsOnly
)
613 m_GlyphItems
.SetFlags(rArgs
.mnFlags
);
618 void GenericSalLayout::GetCharWidths(std::vector
<DeviceCoordinate
>& rCharWidths
, const OUString
& rStr
) const
620 const int nCharCount
= mnEndCharPos
- mnMinCharPos
;
623 rCharWidths
.resize(nCharCount
, 0);
625 css::uno::Reference
<css::i18n::XBreakIterator
> xBreak
;
626 auto aLocale(maLanguageTag
.getLocale());
628 for (auto const& aGlyphItem
: m_GlyphItems
)
630 if (aGlyphItem
.charPos() >= mnEndCharPos
)
633 unsigned int nGraphemeCount
= 0;
634 if (aGlyphItem
.charCount() > 1 && aGlyphItem
.newWidth() != 0 && !rStr
.isEmpty())
636 // We are calculating DX array for cursor positions and this is a
637 // ligature, find out how many grapheme clusters are in it.
639 xBreak
= mxBreak
.is() ? mxBreak
: vcl::unohelper::CreateBreakIterator();
641 // Count grapheme clusters in the ligature.
643 sal_Int32 nPos
= aGlyphItem
.charPos();
644 while (nPos
< aGlyphItem
.charPos() + aGlyphItem
.charCount())
646 nPos
= xBreak
->nextCharacters(rStr
, nPos
, aLocale
,
647 css::i18n::CharacterIteratorMode::SKIPCELL
, 1, nDone
);
652 if (nGraphemeCount
> 1)
654 // More than one grapheme cluster, we want to distribute the glyph
656 std::vector
<DeviceCoordinate
> aWidths(nGraphemeCount
);
658 // Check if the glyph has ligature caret positions.
659 unsigned int nCarets
= nGraphemeCount
;
660 std::vector
<hb_position_t
> aCarets(nGraphemeCount
);
661 hb_ot_layout_get_ligature_carets(GetFont().GetHbFont(),
662 aGlyphItem
.IsRTLGlyph() ? HB_DIRECTION_RTL
: HB_DIRECTION_LTR
,
663 aGlyphItem
.glyphId(), 0, &nCarets
, aCarets
.data());
665 // Carets are 1-less than the grapheme count (since the last
666 // position is defined by glyph width), if the count does not
668 if (nCarets
== nGraphemeCount
- 1)
670 // Scale the carets and apply glyph offset to them since they
671 // are based on the default glyph metrics.
673 GetFont().GetScale(&fScale
, nullptr);
674 for (size_t i
= 0; i
< nCarets
; i
++)
675 aCarets
[i
] = (aCarets
[i
] * fScale
) + aGlyphItem
.xOffset();
677 // Use the glyph width for the last caret.
678 aCarets
[nCarets
] = aGlyphItem
.newWidth();
680 // Carets are absolute from the X origin of the glyph, turn
681 // them to relative widths that we need below.
682 for (size_t i
= 0; i
< nGraphemeCount
; i
++)
683 aWidths
[i
] = aCarets
[i
] - (i
== 0 ? 0 : aCarets
[i
- 1]);
685 // Carets are in visual order, but we want widths in logical
687 if (aGlyphItem
.IsRTLGlyph())
688 std::reverse(aWidths
.begin(), aWidths
.end());
692 // The glyph has no carets, distribute the width evenly.
693 auto nWidth
= aGlyphItem
.newWidth() / nGraphemeCount
;
694 std::fill(aWidths
.begin(), aWidths
.end(), nWidth
);
696 // Add rounding difference to the last component to maintain
698 aWidths
[nGraphemeCount
- 1] += aGlyphItem
.newWidth() - (nWidth
* nGraphemeCount
);
701 // Set the width of each grapheme cluster.
703 sal_Int32 nPos
= aGlyphItem
.charPos();
704 for (auto nWidth
: aWidths
)
706 rCharWidths
[nPos
- mnMinCharPos
] += nWidth
;
707 nPos
= xBreak
->nextCharacters(rStr
, nPos
, aLocale
,
708 css::i18n::CharacterIteratorMode::SKIPCELL
, 1, nDone
);
712 rCharWidths
[aGlyphItem
.charPos() - mnMinCharPos
] += aGlyphItem
.newWidth();
716 // - pDXArray: is the adjustments to glyph advances (usually due to
718 // - pKashidaArray: is the places where kashidas are inserted (for Arabic
719 // justification). The number of kashidas is calculated from the pDXArray.
720 void GenericSalLayout::ApplyDXArray(const double* pDXArray
, const sal_Bool
* pKashidaArray
)
722 int nCharCount
= mnEndCharPos
- mnMinCharPos
;
723 std::vector
<DeviceCoordinate
> aOldCharWidths
;
724 std::unique_ptr
<double[]> const pNewCharWidths(new double[nCharCount
]);
726 // Get the natural character widths (i.e. before applying DX adjustments).
727 GetCharWidths(aOldCharWidths
, {});
729 // Calculate the character widths after DX adjustments.
730 for (int i
= 0; i
< nCharCount
; ++i
)
733 pNewCharWidths
[i
] = pDXArray
[i
];
735 pNewCharWidths
[i
] = pDXArray
[i
] - pDXArray
[i
- 1];
738 // Map of Kashida insertion points (in the glyph items vector) and the
740 std::map
<size_t, std::pair
<DeviceCoordinate
, DeviceCoordinate
>> pKashidas
;
742 // The accumulated difference in X position.
745 // Apply the DX adjustments to glyph positions and widths.
747 while (i
< m_GlyphItems
.size())
749 // Accumulate the width difference for all characters corresponding to
751 int nCharPos
= m_GlyphItems
[i
].charPos() - mnMinCharPos
;
753 for (int j
= 0; j
< m_GlyphItems
[i
].charCount(); j
++)
754 nDiff
+= pNewCharWidths
[nCharPos
+ j
] - aOldCharWidths
[nCharPos
+ j
];
756 if (!m_GlyphItems
[i
].IsRTLGlyph())
758 // Adjust the width and position of the first (leftmost) glyph in
760 m_GlyphItems
[i
].addNewWidth(nDiff
);
761 m_GlyphItems
[i
].adjustLinearPosX(nDelta
);
763 // Adjust the position of the rest of the glyphs in the cluster.
764 while (++i
< m_GlyphItems
.size())
766 if (!m_GlyphItems
[i
].IsInCluster())
768 m_GlyphItems
[i
].adjustLinearPosX(nDelta
);
771 else if (m_GlyphItems
[i
].IsInCluster())
773 // RTL glyph in the middle of the cluster, will be handled in the
779 // Adjust the width and position of the first (rightmost) glyph in
780 // the cluster. This is RTL, so we put all the adjustment to the
781 // left of the glyph.
782 m_GlyphItems
[i
].addNewWidth(nDiff
);
783 m_GlyphItems
[i
].adjustLinearPosX(nDelta
+ nDiff
);
785 // Adjust the X position of the rest of the glyphs in the cluster.
786 // We iterate backwards since this is an RTL glyph.
787 for (int j
= i
- 1; j
>= 0 && m_GlyphItems
[j
].IsInCluster(); j
--)
788 m_GlyphItems
[j
].adjustLinearPosX(nDelta
+ nDiff
);
790 // This is a Kashida insertion position, mark it. Kashida glyphs
791 // will be inserted below.
792 if (pKashidaArray
&& pKashidaArray
[nCharPos
])
793 pKashidas
[i
] = { nDiff
, pNewCharWidths
[nCharPos
] };
798 // Increment the delta, the loop above makes sure we do so only once
799 // for every character (cluster) not for every glyph (otherwise we
800 // would apply it multiple times for each glyph belonging to the same
801 // character which is wrong as DX adjustments are character based).
805 // Insert Kashida glyphs.
806 if (pKashidas
.empty())
809 // Find Kashida glyph width and index.
810 sal_GlyphId nKashidaIndex
= GetFont().GetGlyphIndex(0x0640);
811 double nKashidaWidth
= GetFont().GetKashidaWidth();
813 if (nKashidaWidth
<= 0)
815 SAL_WARN("vcl.gdi", "Asked to insert Kashidas in a font with bogus Kashida width");
819 size_t nInserted
= 0;
820 for (auto const& pKashida
: pKashidas
)
822 auto pGlyphIter
= m_GlyphItems
.begin() + nInserted
+ pKashida
.first
;
824 // The total Kashida width.
825 auto const& [nTotalWidth
, nClusterWidth
] = pKashida
.second
;
827 // Number of times to repeat each Kashida.
829 if (nTotalWidth
> nKashidaWidth
)
830 nCopies
= nTotalWidth
/ nKashidaWidth
;
832 // See if we can improve the fit by adding an extra Kashidas and
833 // squeezing them together a bit.
835 double nShortfall
= nTotalWidth
- nKashidaWidth
* nCopies
;
839 double nExcess
= nCopies
* nKashidaWidth
- nTotalWidth
;
841 nOverlap
= nExcess
/ (nCopies
- 1);
844 DevicePoint aPos
= pGlyphIter
->linearPos();
845 int nCharPos
= pGlyphIter
->charPos();
846 GlyphItemFlags
const nFlags
= GlyphItemFlags::IS_IN_CLUSTER
| GlyphItemFlags::IS_RTL_GLYPH
;
847 // Move to the left side of the adjusted width and start inserting
849 aPos
.adjustX(-nClusterWidth
+ pGlyphIter
->origWidth());
852 GlyphItem
aKashida(nCharPos
, 0, nKashidaIndex
, aPos
, nFlags
, nKashidaWidth
, 0, 0);
853 pGlyphIter
= m_GlyphItems
.insert(pGlyphIter
, aKashida
);
854 aPos
.adjustX(nKashidaWidth
- nOverlap
);
861 // Kashida will be inserted between nCharPos and nNextCharPos.
862 bool GenericSalLayout::IsKashidaPosValid(int nCharPos
, int nNextCharPos
) const
864 // Search for glyph items corresponding to nCharPos and nNextCharPos.
865 auto const& rGlyph
= std::find_if(m_GlyphItems
.begin(), m_GlyphItems
.end(),
866 [&](const GlyphItem
& g
) { return g
.charPos() == nCharPos
; });
867 auto const& rNextGlyph
= std::find_if(m_GlyphItems
.begin(), m_GlyphItems
.end(),
868 [&](const GlyphItem
& g
) { return g
.charPos() == nNextCharPos
; });
870 // If either is not found then a ligature is created at this position, we
871 // can’t insert Kashida here.
872 if (rGlyph
== m_GlyphItems
.end() || rNextGlyph
== m_GlyphItems
.end())
875 // If the either character is not supported by this layout, return false so
876 // that fallback layouts would be checked for it.
877 if (rGlyph
->glyphId() == 0 || rNextGlyph
->glyphId() == 0)
880 // Lastly check if this position is kashida-safe.
881 return rNextGlyph
->IsSafeToInsertKashida();
884 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */