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 .
21 #include "CommonSalLayout.hxx"
23 #include <vcl/unohelp.hxx>
25 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
26 #include <i18nlangtag/mslangid.hxx>
29 #include <unicode/uchar.h>
42 static hb_blob_t
* getFontTable(hb_face_t
* /*face*/, hb_tag_t nTableTag
, void* pUserData
)
45 pTagName
[0] = (char)(nTableTag
>> 24);
46 pTagName
[1] = (char)(nTableTag
>> 16);
47 pTagName
[2] = (char)(nTableTag
>> 8);
48 pTagName
[3] = (char)(nTableTag
);
51 sal_uLong nLength
= 0;
53 unsigned char* pBuffer
= nullptr;
54 HFONT hFont
= static_cast<HFONT
>(pUserData
);
55 HDC hDC
= GetDC(nullptr);
56 HGDIOBJ hOrigFont
= SelectObject(hDC
, hFont
);
57 nLength
= ::GetFontData(hDC
, OSL_NETDWORD(nTableTag
), 0, nullptr, 0);
58 if (nLength
> 0 && nLength
!= GDI_ERROR
)
60 pBuffer
= new unsigned char[nLength
];
61 ::GetFontData(hDC
, OSL_NETDWORD(nTableTag
), 0, pBuffer
, nLength
);
63 SelectObject(hDC
, hOrigFont
);
64 ReleaseDC(nullptr, hDC
);
65 #elif defined(MACOSX) || defined(IOS)
66 unsigned char* pBuffer
= nullptr;
67 CoreTextFontFace
* pFont
= static_cast<CoreTextFontFace
*>(pUserData
);
68 nLength
= pFont
->GetFontTable(pTagName
, nullptr);
71 pBuffer
= new unsigned char[nLength
];
72 pFont
->GetFontTable(pTagName
, pBuffer
);
75 const unsigned char* pBuffer
= nullptr;
76 FreetypeFont
* pFont
= static_cast<FreetypeFont
*>(pUserData
);
77 pBuffer
= pFont
->GetTable(pTagName
, &nLength
);
80 hb_blob_t
* pBlob
= nullptr;
81 if (pBuffer
!= nullptr)
82 #if defined(_WIN32) || defined(MACOSX) || defined(IOS)
83 pBlob
= hb_blob_create(reinterpret_cast<const char*>(pBuffer
), nLength
, HB_MEMORY_MODE_READONLY
,
84 pBuffer
, [](void* data
){ delete[] static_cast<unsigned char*>(data
); });
86 pBlob
= hb_blob_create(reinterpret_cast<const char*>(pBuffer
), nLength
, HB_MEMORY_MODE_READONLY
, nullptr, nullptr);
92 static hb_font_t
* createHbFont(hb_face_t
* pHbFace
)
94 hb_font_t
* pHbFont
= hb_font_create(pHbFace
);
95 unsigned int nUPEM
= hb_face_get_upem(pHbFace
);
96 hb_font_set_scale(pHbFont
, nUPEM
, nUPEM
);
97 hb_ot_font_set_funcs(pHbFont
);
99 hb_face_destroy(pHbFace
);
104 void CommonSalLayout::getScale(double* nXScale
, double* nYScale
)
106 hb_face_t
* pHbFace
= hb_font_get_face(mpHbFont
);
107 unsigned int nUPEM
= hb_face_get_upem(pHbFace
);
109 double nHeight(mrFontSelData
.mnHeight
);
111 // On Windows, mnWidth is relative to average char width not font height,
112 // and we need to keep it that way for GDI to correctly scale the glyphs.
113 // Here we compensate for this so that HarfBuzz gives us the correct glyph
115 double nWidth(mrFontSelData
.mnWidth
? mrFontSelData
.mnWidth
* mnAveWidthFactor
: nHeight
);
117 double nWidth(mrFontSelData
.mnWidth
? mrFontSelData
.mnWidth
: nHeight
);
121 *nYScale
= nHeight
/ nUPEM
;
124 *nXScale
= nWidth
/ nUPEM
;
127 #if !HB_VERSION_ATLEAST(1, 1, 0)
128 // Disabled Unicode compatibility decomposition, see fdo#66715
129 static unsigned int unicodeDecomposeCompatibility(hb_unicode_funcs_t
* /*ufuncs*/,
130 hb_codepoint_t
/*u*/,
131 hb_codepoint_t
* /*decomposed*/,
137 static hb_unicode_funcs_t
* getUnicodeFuncs()
139 static hb_unicode_funcs_t
* ufuncs
= hb_unicode_funcs_create(hb_icu_get_unicode_funcs());
140 hb_unicode_funcs_set_decompose_compatibility_func(ufuncs
, unicodeDecomposeCompatibility
, nullptr, nullptr);
145 void CommonSalLayout::ParseFeatures(const OUString
& aName
)
147 if (aName
.indexOf(FontSelectPatternAttributes::FEAT_PREFIX
) < 0)
150 OString sName
= OUStringToOString(aName
, RTL_TEXTENCODING_ASCII_US
);
151 sName
= sName
.getToken(1, FontSelectPatternAttributes::FEAT_PREFIX
);
152 sal_Int32 nIndex
= 0;
155 OString sToken
= sName
.getToken(0, FontSelectPatternAttributes::FEAT_SEPARATOR
, nIndex
);
156 if (sToken
.startsWith("lang="))
158 msLanguage
= sToken
.getToken(1, '=');
162 hb_feature_t aFeature
;
163 if (hb_feature_from_string(sToken
.getStr(), sToken
.getLength(), &aFeature
))
164 maFeatures
.push_back(aFeature
);
171 CommonSalLayout::CommonSalLayout(HDC hDC
, WinFontInstance
& rWinFontInstance
, const WinFontFace
& rWinFontFace
)
172 : mrFontSelData(rWinFontInstance
.maFontSelData
)
174 , mhFont(static_cast<HFONT
>(GetCurrentObject(hDC
, OBJ_FONT
)))
175 , mrWinFontInstance(rWinFontInstance
)
176 , mnAveWidthFactor(1.0f
)
177 , mpVertGlyphs(nullptr)
179 mpHbFont
= rWinFontFace
.GetHbFont();
182 hb_face_t
* pHbFace
= hb_face_create_for_tables(getFontTable
, mhFont
, nullptr);
184 mpHbFont
= createHbFont(pHbFace
);
185 rWinFontFace
.SetHbFont(mpHbFont
);
188 // Calculate the mnAveWidthFactor, see the comment where it is used.
189 if (mrFontSelData
.mnWidth
)
191 double nUPEM
= hb_face_get_upem(hb_font_get_face(mpHbFont
));
194 GetObjectW(mhFont
, sizeof(LOGFONTW
), &aLogFont
);
196 // Set the height (font size) to EM to minimize rounding errors.
197 aLogFont
.lfHeight
= -nUPEM
;
198 // Set width to the default to get the original value in the metrics.
199 aLogFont
.lfWidth
= 0;
201 // Get the font metrics.
202 HFONT hNewFont
= CreateFontIndirectW(&aLogFont
);
203 HFONT hOldFont
= static_cast<HFONT
>(SelectObject(hDC
, hNewFont
));
204 TEXTMETRICW aFontMetric
;
205 GetTextMetricsW(hDC
, &aFontMetric
);
206 SelectObject(hDC
, hOldFont
);
207 DeleteObject(hNewFont
);
209 mnAveWidthFactor
= nUPEM
/ aFontMetric
.tmAveCharWidth
;
213 bool CommonSalLayout::hasHScale() const
215 int nHeight(mrFontSelData
.mnHeight
);
216 int nWidth(mrFontSelData
.mnWidth
? mrFontSelData
.mnWidth
* mnAveWidthFactor
: nHeight
);
217 return nWidth
!= nHeight
;
220 #elif defined(MACOSX) || defined(IOS)
221 CommonSalLayout::CommonSalLayout(const CoreTextStyle
& rCoreTextStyle
)
222 : mrFontSelData(rCoreTextStyle
.maFontSelData
)
223 , mrCoreTextStyle(rCoreTextStyle
)
224 , mpVertGlyphs(nullptr)
226 mpHbFont
= rCoreTextStyle
.GetHbFont();
229 // On macOS we use HarfBuzz for AAT shaping, but HarfBuzz will then
230 // need a CGFont (as it offloads the actual AAT shaping to Core Text),
231 // if we have one we use it to create the hb_face_t.
233 CTFontRef pCTFont
= static_cast<CTFontRef
>(CFDictionaryGetValue(rCoreTextStyle
.GetStyleDict(), kCTFontAttributeName
));
234 CGFontRef pCGFont
= CTFontCopyGraphicsFont(pCTFont
, nullptr);
236 pHbFace
= hb_coretext_face_create(pCGFont
);
238 pHbFace
= hb_face_create_for_tables(getFontTable
, const_cast<CoreTextFontFace
*>(rCoreTextStyle
.mpFontData
), nullptr);
239 CGFontRelease(pCGFont
);
241 mpHbFont
= createHbFont(pHbFace
);
242 rCoreTextStyle
.SetHbFont(mpHbFont
);
247 CommonSalLayout::CommonSalLayout(FreetypeFont
& rFreetypeFont
)
248 : mrFontSelData(rFreetypeFont
.GetFontSelData())
249 , mrFreetypeFont(rFreetypeFont
)
250 , mpVertGlyphs(nullptr)
252 mpHbFont
= rFreetypeFont
.GetHbFont();
255 hb_face_t
* pHbFace
= hb_face_create_for_tables(getFontTable
, &rFreetypeFont
, nullptr);
257 mpHbFont
= createHbFont(pHbFace
);
258 mrFreetypeFont
.SetHbFont(mpHbFont
);
263 void CommonSalLayout::InitFont() const
266 SelectObject(mhDC
, mhFont
);
274 hb_script_t maScript
;
275 hb_direction_t maDirection
;
284 Run(int32_t nStart_
, int32_t nEnd_
, UScriptCode nCode_
)
291 class TextLayoutCache
294 std::vector
<vcl::Run
> runs
;
295 TextLayoutCache(sal_Unicode
const* pStr
, sal_Int32
const nEnd
)
297 vcl::ScriptRun
aScriptRun(
298 reinterpret_cast<const UChar
*>(pStr
),
300 while (aScriptRun
.next())
302 runs
.push_back(Run(aScriptRun
.getScriptStart(),
303 aScriptRun
.getScriptEnd(), aScriptRun
.getScriptCode()));
310 #include "VerticalOrientationData.cxx"
312 // These must match the values in the file included above.
313 enum class VerticalOrientation
{
316 TransformedUpright
= 2,
317 TransformedRotated
= 3
320 VerticalOrientation
GetVerticalOrientation(sal_UCS4 cCh
, const LanguageTag
& rTag
)
322 // Override fullwidth colon and semi-colon orientation. Tu is preferred.
323 if ((cCh
== 0xff1a || cCh
== 0xff1b) && rTag
.getLanguage() == "zh")
324 return VerticalOrientation::TransformedUpright
;
330 nRet
= sVerticalOrientationValues
[sVerticalOrientationPages
[0][cCh
>> kVerticalOrientationCharBits
]]
331 [cCh
& ((1 << kVerticalOrientationCharBits
) - 1)];
333 else if (cCh
< (kVerticalOrientationMaxPlane
+ 1) * 0x10000)
335 nRet
= sVerticalOrientationValues
[sVerticalOrientationPages
[sVerticalOrientationPlanes
[(cCh
>> 16) - 1]]
336 [(cCh
& 0xffff) >> kVerticalOrientationCharBits
]]
337 [cCh
& ((1 << kVerticalOrientationCharBits
) - 1)];
341 // Default value for unassigned
342 SAL_WARN("vcl.gdi", "Getting VerticalOrientation for codepoint outside Unicode range");
345 return VerticalOrientation(nRet
);
350 std::shared_ptr
<vcl::TextLayoutCache
> CommonSalLayout::CreateTextLayoutCache(OUString
const& rString
) const
352 return std::make_shared
<vcl::TextLayoutCache
>(rString
.getStr(), rString
.getLength());
355 void CommonSalLayout::SetNeedFallback(ImplLayoutArgs
& rArgs
, sal_Int32 nCharPos
, bool bRightToLeft
)
360 using namespace ::com::sun::star
;
363 mxBreak
= vcl::unohelper::CreateBreakIterator();
365 lang::Locale
aLocale(rArgs
.maLanguageTag
.getLocale());
367 //if position nCharPos is missing in the font, grab the entire grapheme and
368 //mark all glyphs as missing so the whole thing is rendered with the same
371 sal_Int32 nGraphemeStartPos
=
372 mxBreak
->previousCharacters(rArgs
.mrStr
, nCharPos
+ 1, aLocale
,
373 i18n::CharacterIteratorMode::SKIPCELL
, 1, nDone
);
374 sal_Int32 nGraphemeEndPos
=
375 mxBreak
->nextCharacters(rArgs
.mrStr
, nCharPos
, aLocale
,
376 i18n::CharacterIteratorMode::SKIPCELL
, 1, nDone
);
378 rArgs
.NeedFallback(nGraphemeStartPos
, nGraphemeEndPos
, bRightToLeft
);
381 void CommonSalLayout::AdjustLayout(ImplLayoutArgs
& rArgs
)
383 SalLayout::AdjustLayout(rArgs
);
387 else if (rArgs
.mnLayoutWidth
)
388 Justify(rArgs
.mnLayoutWidth
);
390 // apply asian kerning if the glyphs are not already formatted
391 if ((rArgs
.mnFlags
& SalLayoutFlags::KerningAsian
)
392 && !(rArgs
.mnFlags
& SalLayoutFlags::Vertical
))
393 if ((rArgs
.mpDXArray
!= nullptr) || (rArgs
.mnLayoutWidth
!= 0))
394 ApplyAsianKerning(rArgs
.mrStr
);
397 void CommonSalLayout::DrawText(SalGraphics
& rSalGraphics
) const
399 //call platform dependent DrawText functions
400 rSalGraphics
.DrawTextLayout( *this );
403 // Find if the nominal glyph of the character is an input to “vert” feature.
404 // We don’t check for a specific script or language as it shouldn’t matter
405 // here; if the glyph would be the result from applying “vert” for any
406 // script/language then we want to always treat it as upright glyph.
407 bool CommonSalLayout::HasVerticalAlternate(sal_UCS4 aChar
, sal_UCS4 aVariationSelector
)
409 hb_codepoint_t nGlyphIndex
= 0;
410 if (!hb_font_get_glyph(mpHbFont
, aChar
, aVariationSelector
, &nGlyphIndex
))
415 hb_face_t
* pHbFace
= hb_font_get_face(mpHbFont
);
416 mpVertGlyphs
= hb_set_create();
418 // Find all GSUB lookups for “vert” feature.
419 hb_set_t
* pLookups
= hb_set_create();
420 hb_tag_t pFeatures
[] = { HB_TAG('v','e','r','t'), HB_TAG_NONE
};
421 hb_ot_layout_collect_lookups(pHbFace
, HB_OT_TAG_GSUB
, nullptr, nullptr, pFeatures
, pLookups
);
422 if (!hb_set_is_empty(pLookups
))
424 // Find the output glyphs in each lookup (i.e. the glyphs that
425 // would result from applying this lookup).
426 hb_codepoint_t nIdx
= HB_SET_VALUE_INVALID
;
427 while (hb_set_next(pLookups
, &nIdx
))
429 hb_set_t
* pGlyphs
= hb_set_create();
430 hb_ot_layout_lookup_collect_glyphs(pHbFace
, HB_OT_TAG_GSUB
, nIdx
,
431 nullptr, // glyphs before
432 pGlyphs
, // glyphs input
433 nullptr, // glyphs after
434 nullptr); // glyphs out
435 hb_set_union(mpVertGlyphs
, pGlyphs
);
440 return hb_set_has(mpVertGlyphs
, nGlyphIndex
) != 0;
443 bool CommonSalLayout::LayoutText(ImplLayoutArgs
& rArgs
)
445 hb_face_t
* pHbFace
= hb_font_get_face(mpHbFont
);
447 int nGlyphCapacity
= 2 * (rArgs
.mnEndCharPos
- rArgs
.mnMinCharPos
);
448 Reserve(nGlyphCapacity
);
450 const int nLength
= rArgs
.mrStr
.getLength();
451 const sal_Unicode
*pStr
= rArgs
.mrStr
.getStr();
453 std::unique_ptr
<vcl::TextLayoutCache
> pNewScriptRun
;
454 vcl::TextLayoutCache
const* pTextLayout
;
455 if (rArgs
.m_pTextLayoutCache
)
457 pTextLayout
= rArgs
.m_pTextLayoutCache
; // use cache!
461 pNewScriptRun
.reset(new vcl::TextLayoutCache(pStr
, rArgs
.mnEndCharPos
));
462 pTextLayout
= pNewScriptRun
.get();
465 hb_buffer_t
* pHbBuffer
= hb_buffer_create();
466 hb_buffer_pre_allocate(pHbBuffer
, nGlyphCapacity
);
467 #if !HB_VERSION_ATLEAST(1, 1, 0)
468 static hb_unicode_funcs_t
* pHbUnicodeFuncs
= getUnicodeFuncs();
469 hb_buffer_set_unicode_funcs(pHbBuffer
, pHbUnicodeFuncs
);
472 if (rArgs
.mnFlags
& SalLayoutFlags::DisableKerning
)
474 SAL_INFO("vcl.harfbuzz", "Disabling kerning for font: " << mrFontSelData
.maTargetName
);
475 maFeatures
.push_back({ HB_TAG('k','e','r','n'), 0, 0, static_cast<unsigned int>(-1) });
478 ParseFeatures(mrFontSelData
.maTargetName
);
482 getScale(&nXScale
, &nYScale
);
484 Point
aCurrPos(0, 0);
487 int nBidiMinRunPos
, nBidiEndRunPos
;
489 if (!rArgs
.GetNextRun(&nBidiMinRunPos
, &nBidiEndRunPos
, &bRightToLeft
))
492 // Find script subruns.
493 int nCurrentPos
= nBidiMinRunPos
;
494 std::vector
<SubRun
> aSubRuns
;
496 for (; k
< pTextLayout
->runs
.size(); ++k
)
498 vcl::Run
const& rRun(pTextLayout
->runs
[k
]);
499 if (rRun
.nStart
<= nCurrentPos
&& nCurrentPos
< rRun
.nEnd
)
505 while (nCurrentPos
< nBidiEndRunPos
&& k
< pTextLayout
->runs
.size())
507 int32_t nMinRunPos
= nCurrentPos
;
508 int32_t nEndRunPos
= std::min(pTextLayout
->runs
[k
].nEnd
, nBidiEndRunPos
);
509 hb_direction_t aDirection
= bRightToLeft
? HB_DIRECTION_RTL
: HB_DIRECTION_LTR
;
510 hb_script_t aScript
= hb_icu_script_to_script(pTextLayout
->runs
[k
].nCode
);
512 // For vertical text, further divide the runs based on character
514 if (rArgs
.mnFlags
& SalLayoutFlags::Vertical
)
516 sal_Int32 nIdx
= nMinRunPos
;
517 while (nIdx
< nEndRunPos
)
519 sal_Int32 nPrevIdx
= nIdx
;
520 sal_UCS4 aChar
= rArgs
.mrStr
.iterateCodePoints(&nIdx
);
521 VerticalOrientation aVo
= GetVerticalOrientation(aChar
, rArgs
.maLanguageTag
);
523 sal_UCS4 aVariationSelector
= 0;
524 if (nIdx
< nEndRunPos
)
526 sal_Int32 nNextIdx
= nIdx
;
527 sal_UCS4 aNextChar
= rArgs
.mrStr
.iterateCodePoints(&nNextIdx
);
528 if (u_hasBinaryProperty(aNextChar
, UCHAR_VARIATION_SELECTOR
))
531 aVariationSelector
= aNextChar
;
535 // Charters with U and Tu vertical orientation should
536 // be shaped in vertical direction. But characters
537 // with Tr should be shaped in vertical direction
538 // only if they have vertical alternates, otherwise
539 // they should be shaped in horizontal direction
541 // See http://unicode.org/reports/tr50/#vo
542 if (aVo
== VerticalOrientation::Upright
||
543 aVo
== VerticalOrientation::TransformedUpright
||
544 (aVo
== VerticalOrientation::TransformedRotated
&&
545 HasVerticalAlternate(aChar
, aVariationSelector
)))
547 aDirection
= HB_DIRECTION_TTB
;
551 aDirection
= bRightToLeft
? HB_DIRECTION_RTL
: HB_DIRECTION_LTR
;
554 if (aSubRuns
.empty() || aSubRuns
.back().maDirection
!= aDirection
)
555 aSubRuns
.push_back({ nPrevIdx
, nIdx
, aScript
, aDirection
});
557 aSubRuns
.back().mnEnd
= nIdx
;
562 aSubRuns
.push_back({ nMinRunPos
, nEndRunPos
, aScript
, aDirection
});
565 nCurrentPos
= nEndRunPos
;
569 // RTL subruns should be reversed to ensure that final glyph order is
572 std::reverse(aSubRuns
.begin(), aSubRuns
.end());
574 for (const auto& aSubRun
: aSubRuns
)
576 hb_buffer_clear_contents(pHbBuffer
);
578 int nMinRunPos
= aSubRun
.mnMin
;
579 int nEndRunPos
= aSubRun
.mnEnd
;
580 int nRunLen
= nEndRunPos
- nMinRunPos
;
582 OString sLanguage
= msLanguage
;
583 if (sLanguage
.isEmpty())
584 sLanguage
= OUStringToOString(rArgs
.maLanguageTag
.getBcp47(), RTL_TEXTENCODING_ASCII_US
);
586 int nHbFlags
= HB_BUFFER_FLAGS_DEFAULT
;
588 nHbFlags
|= HB_BUFFER_FLAG_BOT
; /* Beginning-of-text */
589 if (nEndRunPos
== nLength
)
590 nHbFlags
|= HB_BUFFER_FLAG_EOT
; /* End-of-text */
592 hb_buffer_set_direction(pHbBuffer
, aSubRun
.maDirection
);
593 hb_buffer_set_script(pHbBuffer
, aSubRun
.maScript
);
594 hb_buffer_set_language(pHbBuffer
, hb_language_from_string(sLanguage
.getStr(), -1));
595 hb_buffer_set_flags(pHbBuffer
, (hb_buffer_flags_t
) nHbFlags
);
597 pHbBuffer
, reinterpret_cast<uint16_t const *>(pStr
), nLength
,
598 nMinRunPos
, nRunLen
);
599 hb_buffer_set_cluster_level(pHbBuffer
, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS
);
601 // The shapers that we want HarfBuzz to use, in the order of
602 // preference. The coretext_aat shaper is available only on macOS,
603 // but there is no harm in always including it, HarfBuzz will
604 // ignore unavailable shapers.
605 const char* pHbShapers
[] = { "graphite2", "coretext_aat", "ot", "fallback", nullptr };
606 hb_segment_properties_t aHbProps
;
607 hb_buffer_get_segment_properties(pHbBuffer
, &aHbProps
);
608 hb_shape_plan_t
* pHbPlan
= hb_shape_plan_create_cached(pHbFace
, &aHbProps
, maFeatures
.data(), maFeatures
.size(), pHbShapers
);
609 bool ok
= hb_shape_plan_execute(pHbPlan
, mpHbFont
, pHbBuffer
, maFeatures
.data(), maFeatures
.size());
612 hb_buffer_set_content_type(pHbBuffer
, HB_BUFFER_CONTENT_TYPE_GLYPHS
);
613 hb_shape_plan_destroy(pHbPlan
);
615 int nRunGlyphCount
= hb_buffer_get_length(pHbBuffer
);
616 hb_glyph_info_t
*pHbGlyphInfos
= hb_buffer_get_glyph_infos(pHbBuffer
, nullptr);
617 hb_glyph_position_t
*pHbPositions
= hb_buffer_get_glyph_positions(pHbBuffer
, nullptr);
619 for (int i
= 0; i
< nRunGlyphCount
; ++i
) {
620 int32_t nGlyphIndex
= pHbGlyphInfos
[i
].codepoint
;
621 int32_t nCharPos
= pHbGlyphInfos
[i
].cluster
;
623 // if needed request glyph fallback by updating LayoutArgs
626 SetNeedFallback(rArgs
, nCharPos
, bRightToLeft
);
627 if (SalLayoutFlags::ForFallback
& rArgs
.mnFlags
)
631 bool bInCluster
= false;
632 if (i
> 0 && pHbGlyphInfos
[i
].cluster
== pHbGlyphInfos
[i
- 1].cluster
)
635 long nGlyphFlags
= 0;
637 nGlyphFlags
|= GlyphItem::IS_RTL_GLYPH
;
640 nGlyphFlags
|= GlyphItem::IS_IN_CLUSTER
;
642 sal_Int32 indexUtf16
= nCharPos
;
643 sal_UCS4 aChar
= rArgs
.mrStr
.iterateCodePoints(&indexUtf16
, 0);
645 if (u_getIntPropertyValue(aChar
, UCHAR_GENERAL_CATEGORY
) == U_NON_SPACING_MARK
)
646 nGlyphFlags
|= GlyphItem::IS_DIACRITIC
;
648 if (u_isUWhiteSpace(aChar
))
649 nGlyphFlags
|= GlyphItem::IS_SPACING
;
651 if ((aSubRun
.maScript
== HB_SCRIPT_ARABIC
||
652 aSubRun
.maScript
== HB_SCRIPT_SYRIAC
) &&
653 HB_DIRECTION_IS_BACKWARD(aSubRun
.maDirection
) &&
654 (nGlyphFlags
& GlyphItem::IS_SPACING
) == 0)
656 nGlyphFlags
|= GlyphItem::ALLOW_KASHIDA
;
657 rArgs
.mnFlags
|= SalLayoutFlags::KashidaJustification
;
660 DeviceCoordinate nAdvance
, nXOffset
, nYOffset
;
661 if (aSubRun
.maDirection
== HB_DIRECTION_TTB
)
663 nGlyphFlags
|= GlyphItem::IS_VERTICAL
;
665 nAdvance
= -pHbPositions
[i
].y_advance
;
666 nXOffset
= pHbPositions
[i
].y_offset
;
667 nYOffset
= pHbPositions
[i
].x_offset
;
671 nAdvance
= pHbPositions
[i
].x_advance
;
672 nXOffset
= pHbPositions
[i
].x_offset
;
673 nYOffset
= -pHbPositions
[i
].y_offset
;
676 nAdvance
= std::lround(nAdvance
* nXScale
);
677 nXOffset
= std::lround(nXOffset
* nXScale
);
678 nYOffset
= std::lround(nYOffset
* nYScale
);
680 Point
aNewPos(aCurrPos
.X() + nXOffset
, aCurrPos
.Y() + nYOffset
);
681 const GlyphItem
aGI(nCharPos
, nGlyphIndex
, aNewPos
, nGlyphFlags
,
685 aCurrPos
.X() += nAdvance
;
690 hb_buffer_destroy(pHbBuffer
);
695 bool CommonSalLayout::GetCharWidths(DeviceCoordinate
* pCharWidths
) const
697 int nCharCount
= mnEndCharPos
- mnMinCharPos
;
699 for (int i
= 0; i
< nCharCount
; ++i
)
702 for (auto const& aGlyphItem
: m_GlyphItems
)
703 pCharWidths
[aGlyphItem
.mnCharPos
- mnMinCharPos
] += aGlyphItem
.mnNewWidth
;
708 // A note on how Kashida justification is implemented (because it took me 5
709 // years to figure it out):
710 // The decision to insert Kashidas, where and how much is taken by Writer.
711 // This decision is communicated to us in a very indirect way; by increasing
712 // the width of the character after which Kashidas should be inserted by the
715 // Writer eventually calls IsKashidaPosValid() to check whether it can insert a
716 // Kashida between two characters or not.
719 // - In LayoutText() set KashidaJustification flag based on text script.
720 // - In ApplyDXArray():
721 // * Check the above flag to decide whether to insert Kashidas or not.
722 // * For any RTL glyph that has DX adjustment, insert enough Kashidas to
723 // fill in the added space.
725 void CommonSalLayout::ApplyDXArray(ImplLayoutArgs
& rArgs
)
727 if (rArgs
.mpDXArray
== nullptr)
730 int nCharCount
= mnEndCharPos
- mnMinCharPos
;
731 std::unique_ptr
<DeviceCoordinate
[]> const pOldCharWidths(new DeviceCoordinate
[nCharCount
]);
732 std::unique_ptr
<DeviceCoordinate
[]> const pNewCharWidths(new DeviceCoordinate
[nCharCount
]);
734 // Get the natural character widths (i.e. before applying DX adjustments).
735 GetCharWidths(pOldCharWidths
.get());
737 // Calculate the character widths after DX adjustments.
738 for (int i
= 0; i
< nCharCount
; ++i
)
741 pNewCharWidths
[i
] = rArgs
.mpDXArray
[i
];
743 pNewCharWidths
[i
] = rArgs
.mpDXArray
[i
] - rArgs
.mpDXArray
[i
- 1];
747 bool bKashidaJustify
= false;
748 DeviceCoordinate nKashidaWidth
= 0;
749 hb_codepoint_t nKashidaIndex
= 0;
750 if (rArgs
.mnFlags
& SalLayoutFlags::KashidaJustification
)
752 // Find Kashida glyph width and index.
753 if (hb_font_get_glyph(mpHbFont
, 0x0640, 0, &nKashidaIndex
))
756 getScale(&nXScale
, nullptr);
757 nKashidaWidth
= hb_font_get_glyph_h_advance(mpHbFont
, nKashidaIndex
) * nXScale
;
759 bKashidaJustify
= nKashidaWidth
!= 0;
762 // Map of Kashida insertion points (in the glyph items vector) and the
764 std::map
<size_t, DeviceCoordinate
> pKashidas
;
766 // The accumulated difference in X position.
767 DeviceCoordinate nDelta
= 0;
769 // Apply the DX adjustments to glyph positions and widths.
771 while (i
< m_GlyphItems
.size())
773 int nCharPos
= m_GlyphItems
[i
].mnCharPos
- mnMinCharPos
;
774 DeviceCoordinate nDiff
= pNewCharWidths
[nCharPos
] - pOldCharWidths
[nCharPos
];
776 // Adjust the width of the first glyph in the cluster.
777 m_GlyphItems
[i
].mnNewWidth
+= nDiff
;
779 // Apply the X position of all glyphs in the cluster.
781 while (j
< m_GlyphItems
.size())
783 if (m_GlyphItems
[j
].mnCharPos
!= m_GlyphItems
[i
].mnCharPos
)
785 m_GlyphItems
[j
].maLinearPos
.X() += nDelta
;
786 // For RTL, put all DX adjustment space to the left of the glyph.
787 if (m_GlyphItems
[i
].IsRTLGlyph())
788 m_GlyphItems
[j
].maLinearPos
.X() += nDiff
;
792 // Id this glyph is Kashida-justifiable, then mark this as a Kashida
793 // position. Since this must be a RTL glyph, we mark the last glyph in
794 // the cluster not the fisrt as this would be the base glyph.
795 // nDiff > 1 to ignore rounding errors.
796 if (bKashidaJustify
&& m_GlyphItems
[i
].AllowKashida() && nDiff
> 1)
798 pKashidas
[j
- 1] = nDiff
;
799 // Move any non-spacing marks attached to this cluster as well.
800 // Looping backward because this is RTL glyph.
803 auto pGlyph
= m_GlyphItems
.begin() + i
- 1;
804 while (pGlyph
!= m_GlyphItems
.begin() && pGlyph
->IsDiacritic())
806 pGlyph
->maLinearPos
.X() += nDiff
;
813 // Increment the delta, the loop above makes sure we do so only once
814 // for every character (cluster) not for every glyph (otherwise we
815 // would apply it multiple times for each glyphs belonging to the same
816 // character which is wrong since DX adjustments are character based).
821 // Insert Kashida glyphs.
822 if (bKashidaJustify
&& !pKashidas
.empty())
824 size_t nInserted
= 0;
825 for (auto const& pKashida
: pKashidas
)
827 auto pGlyphIter
= m_GlyphItems
.begin() + nInserted
+ pKashida
.first
;
829 // The total Kashida width.
830 DeviceCoordinate nTotalWidth
= pKashida
.second
;
832 // Number of times to repeat each Kashida.
834 if (nTotalWidth
> nKashidaWidth
)
835 nCopies
= nTotalWidth
/ nKashidaWidth
;
837 // See if we can improve the fit by adding an extra Kashidas and
838 // squeezing them together a bit.
839 DeviceCoordinate nOverlap
= 0;
840 DeviceCoordinate nShortfall
= nTotalWidth
- nKashidaWidth
* nCopies
;
844 DeviceCoordinate nExcess
= nCopies
* nKashidaWidth
- nTotalWidth
;
846 nOverlap
= nExcess
/ (nCopies
- 1);
849 Point
aPos(pGlyphIter
->maLinearPos
.X() - nTotalWidth
, 0);
850 int nCharPos
= pGlyphIter
->mnCharPos
;
851 int nFlags
= GlyphItem::IS_IN_CLUSTER
| GlyphItem::IS_RTL_GLYPH
;
854 GlyphItem
aKashida(nCharPos
, nKashidaIndex
, aPos
, nFlags
, nKashidaWidth
);
855 pGlyphIter
= m_GlyphItems
.insert(pGlyphIter
, aKashida
);
856 aPos
.X() += nKashidaWidth
;
857 aPos
.X() -= nOverlap
;
865 bool CommonSalLayout::IsKashidaPosValid(int nCharPos
) const
867 for (auto pIter
= m_GlyphItems
.begin(); pIter
!= m_GlyphItems
.end(); ++pIter
)
869 if (pIter
->mnCharPos
== nCharPos
)
871 // The position is the first glyphs, this would happen if we
872 // changed the text styling in the middle of a word. Since we don’t
873 // do ligatures across layout engine instances, this can’t be a
874 // ligature so it should be fine.
875 if (pIter
== m_GlyphItems
.begin())
878 // If the character was not supported by this layout, return false
879 // so that fallback layouts would be checked for it.
880 if (pIter
->maGlyphId
== 0)
883 // Search backwards for previous glyph belonging to a different
884 // character. We are looking backwards because we are dealing with
885 // RTL glyphs, which will be in visual order.
886 for (auto pPrev
= pIter
- 1; pPrev
!= m_GlyphItems
.begin(); --pPrev
)
888 if (pPrev
->mnCharPos
!= nCharPos
)
890 // Check if the found glyph belongs to the next character,
891 // otherwise the current glyph will be a ligature which is
892 // invalid kashida position.
893 if (pPrev
->mnCharPos
== (nCharPos
+ 1))
904 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */