bump product version to 7.2.5.1
[LibreOffice.git] / vcl / source / gdi / CommonSalLayout.cxx
blob50ad02843887a441c48a8bbdd3bfebdb7794b03e
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <memory>
22 #include <hb-icu.h>
23 #include <hb-ot.h>
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>
34 #include <scrptrun.h>
35 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
36 #include <salgdi.hxx>
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*/,
44 hb_codepoint_t /*u*/,
45 hb_codepoint_t* /*decomposed*/,
46 void* /*user_data*/)
48 return 0;
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);
55 return ufuncs;
57 #endif
59 GenericSalLayout::GenericSalLayout(LogicalFontInstance &rFont)
60 : m_GlyphItems(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);
84 namespace {
86 struct SubRun
88 int32_t mnMin;
89 int32_t mnEnd;
90 hb_script_t maScript;
91 hb_direction_t maDirection;
96 namespace vcl {
97 namespace {
99 struct Run
101 int32_t nStart;
102 int32_t nEnd;
103 UScriptCode nCode;
104 Run(int32_t nStart_, int32_t nEnd_, UScriptCode nCode_)
105 : nStart(nStart_)
106 , nEnd(nEnd_)
107 , nCode(nCode_)
113 class TextLayoutCache
115 public:
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),
121 nEnd);
122 while (aScriptRun.next())
124 runs.emplace_back(aScriptRun.getScriptStart(),
125 aScriptRun.getScriptEnd(), aScriptRun.getScriptCode());
129 } // namespace vcl
131 namespace {
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
139 #else
140 #include "VerticalOrientationData.cxx"
142 // These must match the values in the file included above.
143 enum class VerticalOrientation {
144 Upright = 0,
145 Rotated = 1,
146 TransformedUpright = 2,
147 TransformedRotated = 3
149 #endif
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);
162 #else
163 uint8_t nRet = 1;
165 if (cCh < 0x10000)
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)];
176 else
178 // Default value for unassigned
179 SAL_WARN("vcl.gdi", "Getting VerticalOrientation for codepoint outside Unicode range");
181 #endif
183 return VerticalOrientation(nRet);
186 } // namespace
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());
197 return glyphs;
200 void GenericSalLayout::SetNeedFallback(ImplLayoutArgs& rArgs, sal_Int32 nCharPos, bool bRightToLeft)
202 if (nCharPos < 0 || mbFuzzing)
203 return;
205 using namespace ::com::sun::star;
207 if (!mxBreak.is())
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
214 //font
215 sal_Int32 nDone;
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);
232 if (rArgs.mpDXArray)
233 ApplyDXArray(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))
257 return false;
259 if (!mpVertGlyphs)
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)
293 return true;
295 if (pGlyphs)
297 // Work with pre-computed glyph items.
298 m_GlyphItems = *pGlyphs;
299 for(const GlyphItem& item : m_GlyphItems)
300 if(!item.glyphId())
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;
304 return true;
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!
322 else
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
331 // in the future.
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);
345 #endif
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);
362 double nXScale = 0;
363 double nYScale = 0;
364 GetFont().GetScale(&nXScale, &nYScale);
366 Point aCurrPos(0, 0);
367 while (true)
369 int nBidiMinRunPos, nBidiEndRunPos;
370 bool bRightToLeft;
371 if (!rArgs.GetNextRun(&nBidiMinRunPos, &nBidiEndRunPos, &bRightToLeft))
372 break;
374 // Find script subruns.
375 std::vector<SubRun> aSubRuns;
376 int nCurrentPos = nBidiMinRunPos;
377 size_t k = 0;
378 for (; k < pTextLayout->runs.size(); ++k)
380 vcl::Run const& rRun(pTextLayout->runs[k]);
381 if (rRun.nStart <= nCurrentPos && nCurrentPos < rRun.nEnd)
383 break;
387 if (isGraphite)
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 });
392 else
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
401 // orientation.
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))
418 nIdx = nNextIdx;
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
428 // and then rotated.
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;
437 else
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 });
444 else
445 aSubRuns.back().mnEnd = nIdx;
448 else
450 aSubRuns.push_back({ nMinRunPos, nEndRunPos, aScript, aDirection });
453 nCurrentPos = nEndRunPos;
454 ++k;
458 // RTL subruns should be reversed to ensure that final glyph order is
459 // correct.
460 if (bRightToLeft)
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;
476 if (nMinRunPos == 0)
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));
485 hb_buffer_add_utf16(
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);
496 assert(ok);
497 (void) ok;
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.
511 if (!bRightToLeft)
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)
517 nCharCount = 0;
518 bInCluster = true;
520 else
522 // Find the next glyph with a different cluster, or the
523 // end of text.
524 int j = i;
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;
537 else
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)
543 nCharCount = 0;
544 bInCluster = true;
546 else
548 // Find the previous glyph with a different cluster, or
549 // the end of text.
550 int j = i;
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
565 if (!nGlyphIndex)
567 SetNeedFallback(rArgs, nCharPos, bRightToLeft);
568 if (SalLayoutFlags::ForFallback & rArgs.mnFlags)
569 continue;
572 GlyphItemFlags nGlyphFlags = GlyphItemFlags::NONE;
573 if (bRightToLeft)
574 nGlyphFlags |= GlyphItemFlags::IS_RTL_GLYPH;
576 if (bClusterStart)
577 nGlyphFlags |= GlyphItemFlags::IS_CLUSTER_START;
579 if (bInCluster)
580 nGlyphFlags |= GlyphItemFlags::IS_IN_CLUSTER;
582 sal_UCS4 aChar
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;
608 else
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;
635 return true;
638 void GenericSalLayout::GetCharWidths(DeviceCoordinate* pCharWidths) const
640 const int nCharCount = mnEndCharPos - mnMinCharPos;
642 for (int i = 0; i < nCharCount; ++i)
643 pCharWidths[i] = 0;
645 for (auto const& aGlyphItem : m_GlyphItems)
647 const int nIndex = aGlyphItem.charPos() - mnMinCharPos;
648 if (nIndex >= nCharCount)
649 continue;
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
659 // desired amount.
661 // Writer eventually calls IsKashidaPosValid() to check whether it can insert a
662 // Kashida between two characters or not.
664 // Here we do:
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)
674 return;
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)
686 if (i == 0)
687 pNewCharWidths[i] = rArgs.mpDXArray[i];
688 else
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
705 // requested width.
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.
712 size_t i = 0;
713 while (i < m_GlyphItems.size())
715 // Accumulate the width difference for all characters corresponding to
716 // this glyph.
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
725 // the cluster.
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())
733 break;
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
740 // loop below.
741 i++;
743 else
745 // Adjust the width and position of the first (rightmost) glyph in
746 // the cluster.
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.
752 size_t j = i;
753 while (j > 0)
755 --j;
756 if (!m_GlyphItems[j].IsInCluster())
757 break;
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
764 // base glyph.
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.
771 while (j > 0)
773 if (!m_GlyphItems[j].IsDiacritic())
774 break;
775 m_GlyphItems[j--].m_aLinearPos.AdjustX(nDiff);
778 i++;
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).
785 nDelta += nDiff;
788 // Insert Kashida glyphs.
789 if (!bKashidaJustify || pKashidas.empty())
790 return;
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.
801 int nCopies = 1;
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;
809 if (nShortfall > 0)
811 ++nCopies;
812 DeviceCoordinate nExcess = nCopies * nKashidaWidth - nTotalWidth;
813 if (nExcess > 0)
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;
820 while (nCopies--)
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 );
826 ++pGlyphIter;
827 ++nInserted;
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())
843 return true;
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)
848 break;
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))
861 return true;
862 break;
868 return false;
871 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */