Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / vcl / source / gdi / CommonSalLayout.cxx
blobf0acbdd99114353b9ccd5bb09e2becea2a5b7658
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 <sal/log.hxx>
29 #include <unotools/configmgr.hxx>
30 #include <vcl/unohelp.hxx>
31 #include <vcl/font/Feature.hxx>
32 #include <vcl/font/FeatureParser.hxx>
33 #include <scrptrun.h>
34 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
35 #include <salgdi.hxx>
36 #include <unicode/uchar.h>
38 #include <fontselect.hxx>
40 #if !HB_VERSION_ATLEAST(1, 1, 0)
41 // Disabled Unicode compatibility decomposition, see fdo#66715
42 static unsigned int unicodeDecomposeCompatibility(hb_unicode_funcs_t* /*ufuncs*/,
43 hb_codepoint_t /*u*/,
44 hb_codepoint_t* /*decomposed*/,
45 void* /*user_data*/)
47 return 0;
50 static hb_unicode_funcs_t* getUnicodeFuncs()
52 static hb_unicode_funcs_t* ufuncs = hb_unicode_funcs_create(hb_icu_get_unicode_funcs());
53 hb_unicode_funcs_set_decompose_compatibility_func(ufuncs, unicodeDecomposeCompatibility, nullptr, nullptr);
54 return ufuncs;
56 #endif
58 GenericSalLayout::GenericSalLayout(LogicalFontInstance &rFont)
59 : mpVertGlyphs(nullptr)
60 , mbFuzzing(utl::ConfigManager::IsFuzzing())
62 new SalLayoutGlyphsImpl(m_GlyphItems, rFont);
65 GenericSalLayout::~GenericSalLayout()
69 void GenericSalLayout::ParseFeatures(const OUString& aName)
71 vcl::font::FeatureParser aParser(aName);
72 const OUString& sLanguage = aParser.getLanguage();
73 if (!sLanguage.isEmpty())
74 msLanguage = OUStringToOString(sLanguage, RTL_TEXTENCODING_ASCII_US);
76 for (auto const &rFeat : aParser.getFeatures())
78 hb_feature_t aFeature { rFeat.m_nTag, rFeat.m_nValue, rFeat.m_nStart, rFeat.m_nEnd };
79 maFeatures.push_back(aFeature);
83 struct SubRun
85 int32_t mnMin;
86 int32_t mnEnd;
87 hb_script_t maScript;
88 hb_direction_t maDirection;
91 namespace vcl {
92 struct Run
94 int32_t const nStart;
95 int32_t const nEnd;
96 UScriptCode const nCode;
97 Run(int32_t nStart_, int32_t nEnd_, UScriptCode nCode_)
98 : nStart(nStart_)
99 , nEnd(nEnd_)
100 , nCode(nCode_)
104 class TextLayoutCache
106 public:
107 std::vector<vcl::Run> runs;
108 TextLayoutCache(sal_Unicode const* pStr, sal_Int32 const nEnd)
110 vcl::ScriptRun aScriptRun(
111 reinterpret_cast<const UChar *>(pStr),
112 nEnd);
113 while (aScriptRun.next())
115 runs.emplace_back(aScriptRun.getScriptStart(),
116 aScriptRun.getScriptEnd(), aScriptRun.getScriptCode());
120 } // namespace vcl
122 namespace {
123 #if U_ICU_VERSION_MAJOR_NUM >= 63
124 enum class VerticalOrientation {
125 Upright = U_VO_UPRIGHT,
126 Rotated = U_VO_ROTATED,
127 TransformedUpright = U_VO_TRANSFORMED_UPRIGHT,
128 TransformedRotated = U_VO_TRANSFORMED_ROTATED
130 #else
131 #include "VerticalOrientationData.cxx"
133 // These must match the values in the file included above.
134 enum class VerticalOrientation {
135 Upright = 0,
136 Rotated = 1,
137 TransformedUpright = 2,
138 TransformedRotated = 3
140 #endif
142 VerticalOrientation GetVerticalOrientation(sal_UCS4 cCh, const LanguageTag& rTag)
144 // Override orientation of fullwidth colon , semi-colon,
145 // and Bopomofo tonal marks.
146 if ((cCh == 0xff1a || cCh == 0xff1b
147 || cCh == 0x2ca || cCh == 0x2cb || cCh == 0x2c7 || cCh == 0x2d9)
148 && rTag.getLanguage() == "zh")
149 return VerticalOrientation::TransformedUpright;
151 #if U_ICU_VERSION_MAJOR_NUM >= 63
152 int32_t nRet = u_getIntPropertyValue(cCh, UCHAR_VERTICAL_ORIENTATION);
153 #else
154 uint8_t nRet = 1;
156 if (cCh < 0x10000)
158 nRet = sVerticalOrientationValues[sVerticalOrientationPages[0][cCh >> kVerticalOrientationCharBits]]
159 [cCh & ((1 << kVerticalOrientationCharBits) - 1)];
161 else if (cCh < (kVerticalOrientationMaxPlane + 1) * 0x10000)
163 nRet = sVerticalOrientationValues[sVerticalOrientationPages[sVerticalOrientationPlanes[(cCh >> 16) - 1]]
164 [(cCh & 0xffff) >> kVerticalOrientationCharBits]]
165 [cCh & ((1 << kVerticalOrientationCharBits) - 1)];
167 else
169 // Default value for unassigned
170 SAL_WARN("vcl.gdi", "Getting VerticalOrientation for codepoint outside Unicode range");
172 #endif
174 return VerticalOrientation(nRet);
177 } // namespace
179 std::shared_ptr<vcl::TextLayoutCache> GenericSalLayout::CreateTextLayoutCache(OUString const& rString)
181 return std::make_shared<vcl::TextLayoutCache>(rString.getStr(), rString.getLength());
184 const SalLayoutGlyphs* GenericSalLayout::GetGlyphs() const
186 return &m_GlyphItems;
189 void GenericSalLayout::SetNeedFallback(ImplLayoutArgs& rArgs, sal_Int32 nCharPos, bool bRightToLeft)
191 if (nCharPos < 0 || mbFuzzing)
192 return;
194 using namespace ::com::sun::star;
196 if (!mxBreak.is())
197 mxBreak = vcl::unohelper::CreateBreakIterator();
199 lang::Locale aLocale(rArgs.maLanguageTag.getLocale());
201 //if position nCharPos is missing in the font, grab the entire grapheme and
202 //mark all glyphs as missing so the whole thing is rendered with the same
203 //font
204 sal_Int32 nDone;
205 sal_Int32 nGraphemeEndPos =
206 mxBreak->nextCharacters(rArgs.mrStr, nCharPos, aLocale,
207 i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
208 // Safely advance nCharPos in case it is a non-BMP character.
209 rArgs.mrStr.iterateCodePoints(&nCharPos);
210 sal_Int32 nGraphemeStartPos =
211 mxBreak->previousCharacters(rArgs.mrStr, nCharPos, aLocale,
212 i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
214 rArgs.NeedFallback(nGraphemeStartPos, nGraphemeEndPos, bRightToLeft);
217 void GenericSalLayout::AdjustLayout(ImplLayoutArgs& rArgs)
219 SalLayout::AdjustLayout(rArgs);
221 if (rArgs.mpDXArray)
222 ApplyDXArray(rArgs);
223 else if (rArgs.mnLayoutWidth)
224 Justify(rArgs.mnLayoutWidth);
225 // apply asian kerning if the glyphs are not already formatted
226 else if ((rArgs.mnFlags & SalLayoutFlags::KerningAsian)
227 && !(rArgs.mnFlags & SalLayoutFlags::Vertical))
228 ApplyAsianKerning(rArgs.mrStr);
231 void GenericSalLayout::DrawText(SalGraphics& rSalGraphics) const
233 //call platform dependent DrawText functions
234 rSalGraphics.DrawTextLayout( *this );
237 // Find if the nominal glyph of the character is an input to “vert” feature.
238 // We don’t check for a specific script or language as it shouldn’t matter
239 // here; if the glyph would be the result from applying “vert” for any
240 // script/language then we want to always treat it as upright glyph.
241 bool GenericSalLayout::HasVerticalAlternate(sal_UCS4 aChar, sal_UCS4 aVariationSelector)
243 hb_codepoint_t nGlyphIndex = 0;
244 hb_font_t *pHbFont = GetFont().GetHbFont();
245 if (!hb_font_get_glyph(pHbFont, aChar, aVariationSelector, &nGlyphIndex))
246 return false;
248 if (!mpVertGlyphs)
250 hb_face_t* pHbFace = hb_font_get_face(pHbFont);
251 mpVertGlyphs = hb_set_create();
253 // Find all GSUB lookups for “vert” feature.
254 hb_set_t* pLookups = hb_set_create();
255 hb_tag_t const pFeatures[] = { HB_TAG('v','e','r','t'), HB_TAG_NONE };
256 hb_ot_layout_collect_lookups(pHbFace, HB_OT_TAG_GSUB, nullptr, nullptr, pFeatures, pLookups);
257 if (!hb_set_is_empty(pLookups))
259 // Find the output glyphs in each lookup (i.e. the glyphs that
260 // would result from applying this lookup).
261 hb_codepoint_t nIdx = HB_SET_VALUE_INVALID;
262 while (hb_set_next(pLookups, &nIdx))
264 hb_set_t* pGlyphs = hb_set_create();
265 hb_ot_layout_lookup_collect_glyphs(pHbFace, HB_OT_TAG_GSUB, nIdx,
266 nullptr, // glyphs before
267 pGlyphs, // glyphs input
268 nullptr, // glyphs after
269 nullptr); // glyphs out
270 hb_set_union(mpVertGlyphs, pGlyphs);
275 return hb_set_has(mpVertGlyphs, nGlyphIndex) != 0;
278 bool GenericSalLayout::LayoutText(ImplLayoutArgs& rArgs, const SalLayoutGlyphs* pGlyphs)
280 // No need to touch m_GlyphItems at all for an empty string.
281 if (rArgs.mnEndCharPos - rArgs.mnMinCharPos <= 0)
282 return true;
284 if (pGlyphs)
286 // Work with pre-computed glyph items.
287 m_GlyphItems = *pGlyphs;
288 // Some flags are set as a side effect of text layout, restore them here.
289 rArgs.mnFlags |= pGlyphs->Impl()->mnFlags;
290 return true;
293 hb_font_t *pHbFont = GetFont().GetHbFont();
294 bool isGraphite = GetFont().IsGraphiteFont();
296 int nGlyphCapacity = 2 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos);
297 m_GlyphItems.Impl()->reserve(nGlyphCapacity);
299 const int nLength = rArgs.mrStr.getLength();
300 const sal_Unicode *pStr = rArgs.mrStr.getStr();
302 std::unique_ptr<vcl::TextLayoutCache> pNewScriptRun;
303 vcl::TextLayoutCache const* pTextLayout;
304 if (rArgs.m_pTextLayoutCache)
306 pTextLayout = rArgs.m_pTextLayoutCache; // use cache!
308 else
310 pNewScriptRun.reset(new vcl::TextLayoutCache(pStr, rArgs.mnEndCharPos));
311 pTextLayout = pNewScriptRun.get();
314 hb_buffer_t* pHbBuffer = hb_buffer_create();
315 hb_buffer_pre_allocate(pHbBuffer, nGlyphCapacity);
316 #if !HB_VERSION_ATLEAST(1, 1, 0)
317 static hb_unicode_funcs_t* pHbUnicodeFuncs = getUnicodeFuncs();
318 hb_buffer_set_unicode_funcs(pHbBuffer, pHbUnicodeFuncs);
319 #endif
321 const FontSelectPattern& rFontSelData = GetFont().GetFontSelectPattern();
322 if (rArgs.mnFlags & SalLayoutFlags::DisableKerning)
324 SAL_INFO("vcl.harfbuzz", "Disabling kerning for font: " << rFontSelData.maTargetName);
325 maFeatures.push_back({ HB_TAG('k','e','r','n'), 0, 0, static_cast<unsigned int>(-1) });
328 ParseFeatures(rFontSelData.maTargetName);
330 double nXScale = 0;
331 double nYScale = 0;
332 GetFont().GetScale(&nXScale, &nYScale);
334 Point aCurrPos(0, 0);
335 while (true)
337 int nBidiMinRunPos, nBidiEndRunPos;
338 bool bRightToLeft;
339 if (!rArgs.GetNextRun(&nBidiMinRunPos, &nBidiEndRunPos, &bRightToLeft))
340 break;
342 // Find script subruns.
343 std::vector<SubRun> aSubRuns;
344 int nCurrentPos = nBidiMinRunPos;
345 size_t k = 0;
346 for (; k < pTextLayout->runs.size(); ++k)
348 vcl::Run const& rRun(pTextLayout->runs[k]);
349 if (rRun.nStart <= nCurrentPos && nCurrentPos < rRun.nEnd)
351 break;
355 if (isGraphite)
357 hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode);
358 aSubRuns.push_back({ nBidiMinRunPos, nBidiEndRunPos, aScript, bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR });
360 else
362 while (nCurrentPos < nBidiEndRunPos && k < pTextLayout->runs.size())
364 int32_t nMinRunPos = nCurrentPos;
365 int32_t nEndRunPos = std::min(pTextLayout->runs[k].nEnd, nBidiEndRunPos);
366 hb_direction_t aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
367 hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode);
368 // For vertical text, further divide the runs based on character
369 // orientation.
370 if (rArgs.mnFlags & SalLayoutFlags::Vertical)
372 sal_Int32 nIdx = nMinRunPos;
373 while (nIdx < nEndRunPos)
375 sal_Int32 nPrevIdx = nIdx;
376 sal_UCS4 aChar = rArgs.mrStr.iterateCodePoints(&nIdx);
377 VerticalOrientation aVo = GetVerticalOrientation(aChar, rArgs.maLanguageTag);
379 sal_UCS4 aVariationSelector = 0;
380 if (nIdx < nEndRunPos)
382 sal_Int32 nNextIdx = nIdx;
383 sal_UCS4 aNextChar = rArgs.mrStr.iterateCodePoints(&nNextIdx);
384 if (u_hasBinaryProperty(aNextChar, UCHAR_VARIATION_SELECTOR))
386 nIdx = nNextIdx;
387 aVariationSelector = aNextChar;
391 // Charters with U and Tu vertical orientation should
392 // be shaped in vertical direction. But characters
393 // with Tr should be shaped in vertical direction
394 // only if they have vertical alternates, otherwise
395 // they should be shaped in horizontal direction
396 // and then rotated.
397 // See http://unicode.org/reports/tr50/#vo
398 if (aVo == VerticalOrientation::Upright ||
399 aVo == VerticalOrientation::TransformedUpright ||
400 (aVo == VerticalOrientation::TransformedRotated &&
401 HasVerticalAlternate(aChar, aVariationSelector)))
403 aDirection = HB_DIRECTION_TTB;
405 else
407 aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
410 if (aSubRuns.empty() || aSubRuns.back().maDirection != aDirection)
411 aSubRuns.push_back({ nPrevIdx, nIdx, aScript, aDirection });
412 else
413 aSubRuns.back().mnEnd = nIdx;
416 else
418 aSubRuns.push_back({ nMinRunPos, nEndRunPos, aScript, aDirection });
421 nCurrentPos = nEndRunPos;
422 ++k;
426 // RTL subruns should be reversed to ensure that final glyph order is
427 // correct.
428 if (bRightToLeft)
429 std::reverse(aSubRuns.begin(), aSubRuns.end());
431 for (const auto& aSubRun : aSubRuns)
433 hb_buffer_clear_contents(pHbBuffer);
435 const int nMinRunPos = aSubRun.mnMin;
436 const int nEndRunPos = aSubRun.mnEnd;
437 const int nRunLen = nEndRunPos - nMinRunPos;
439 OString sLanguage = msLanguage;
440 if (sLanguage.isEmpty())
441 sLanguage = OUStringToOString(rArgs.maLanguageTag.getBcp47(), RTL_TEXTENCODING_ASCII_US);
443 int nHbFlags = HB_BUFFER_FLAGS_DEFAULT;
444 if (nMinRunPos == 0)
445 nHbFlags |= HB_BUFFER_FLAG_BOT; /* Beginning-of-text */
446 if (nEndRunPos == nLength)
447 nHbFlags |= HB_BUFFER_FLAG_EOT; /* End-of-text */
449 hb_buffer_set_direction(pHbBuffer, aSubRun.maDirection);
450 hb_buffer_set_script(pHbBuffer, aSubRun.maScript);
451 hb_buffer_set_language(pHbBuffer, hb_language_from_string(sLanguage.getStr(), -1));
452 hb_buffer_set_flags(pHbBuffer, static_cast<hb_buffer_flags_t>(nHbFlags));
453 hb_buffer_add_utf16(
454 pHbBuffer, reinterpret_cast<uint16_t const *>(pStr), nLength,
455 nMinRunPos, nRunLen);
456 hb_buffer_set_cluster_level(pHbBuffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
458 // The shapers that we want HarfBuzz to use, in the order of
459 // preference. The coretext_aat shaper is available only on macOS,
460 // but there is no harm in always including it, HarfBuzz will
461 // ignore unavailable shapers.
462 const char*const pHbShapers[] = { "graphite2", "coretext_aat", "ot", "fallback", nullptr };
463 bool ok = hb_shape_full(pHbFont, pHbBuffer, maFeatures.data(), maFeatures.size(), pHbShapers);
464 assert(ok);
465 (void) ok;
467 int nRunGlyphCount = hb_buffer_get_length(pHbBuffer);
468 hb_glyph_info_t *pHbGlyphInfos = hb_buffer_get_glyph_infos(pHbBuffer, nullptr);
469 hb_glyph_position_t *pHbPositions = hb_buffer_get_glyph_positions(pHbBuffer, nullptr);
471 for (int i = 0; i < nRunGlyphCount; ++i) {
472 int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint;
473 int32_t nCharPos = pHbGlyphInfos[i].cluster;
474 int32_t nCharCount = 0;
475 bool bInCluster = false;
476 bool bClusterStart = false;
478 // Find the number of characters that make up this glyph.
479 if (!bRightToLeft)
481 // If the cluster is the same as previous glyph, then this
482 // already consumed, skip.
483 if (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster)
485 nCharCount = 0;
486 bInCluster = true;
488 else
490 // Find the next glyph with a different cluster, or the
491 // end of text.
492 int j = i;
493 int32_t nNextCharPos = nCharPos;
494 while (nNextCharPos == nCharPos && j < nRunGlyphCount)
495 nNextCharPos = pHbGlyphInfos[j++].cluster;
497 if (nNextCharPos == nCharPos)
498 nNextCharPos = nEndRunPos;
499 nCharCount = nNextCharPos - nCharPos;
500 if ((i == 0 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i - 1].cluster) &&
501 (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster))
502 bClusterStart = true;
505 else
507 // If the cluster is the same as previous glyph, then this
508 // will be consumed later, skip.
509 if (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster)
511 nCharCount = 0;
512 bInCluster = true;
514 else
516 // Find the previous glyph with a different cluster, or
517 // the end of text.
518 int j = i;
519 int32_t nNextCharPos = nCharPos;
520 while (nNextCharPos == nCharPos && j >= 0)
521 nNextCharPos = pHbGlyphInfos[j--].cluster;
523 if (nNextCharPos == nCharPos)
524 nNextCharPos = nEndRunPos;
525 nCharCount = nNextCharPos - nCharPos;
526 if ((i == nRunGlyphCount - 1 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i + 1].cluster) &&
527 (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster))
528 bClusterStart = true;
532 // if needed request glyph fallback by updating LayoutArgs
533 if (!nGlyphIndex)
535 SetNeedFallback(rArgs, nCharPos, bRightToLeft);
536 if (SalLayoutFlags::ForFallback & rArgs.mnFlags)
537 continue;
540 GlyphItemFlags nGlyphFlags = GlyphItemFlags::NONE;
541 if (bRightToLeft)
542 nGlyphFlags |= GlyphItemFlags::IS_RTL_GLYPH;
544 if (bClusterStart)
545 nGlyphFlags |= GlyphItemFlags::IS_CLUSTER_START;
547 if (bInCluster)
548 nGlyphFlags |= GlyphItemFlags::IS_IN_CLUSTER;
550 sal_Int32 indexUtf16 = nCharPos;
551 sal_UCS4 aChar = rArgs.mrStr.iterateCodePoints(&indexUtf16, 0);
553 if (u_getIntPropertyValue(aChar, UCHAR_GENERAL_CATEGORY) == U_NON_SPACING_MARK)
554 nGlyphFlags |= GlyphItemFlags::IS_DIACRITIC;
556 if (u_isUWhiteSpace(aChar))
557 nGlyphFlags |= GlyphItemFlags::IS_SPACING;
559 if (aSubRun.maScript == HB_SCRIPT_ARABIC &&
560 HB_DIRECTION_IS_BACKWARD(aSubRun.maDirection) &&
561 !(nGlyphFlags & GlyphItemFlags::IS_SPACING))
563 nGlyphFlags |= GlyphItemFlags::ALLOW_KASHIDA;
564 rArgs.mnFlags |= SalLayoutFlags::KashidaJustification;
567 DeviceCoordinate nAdvance, nXOffset, nYOffset;
568 if (aSubRun.maDirection == HB_DIRECTION_TTB)
570 nGlyphFlags |= GlyphItemFlags::IS_VERTICAL;
572 // We have glyph offsets that is relative to h origin now,
573 // add the origin back so it is relative to v origin.
574 hb_font_add_glyph_origin_for_direction(pHbFont,
575 nGlyphIndex,
576 HB_DIRECTION_TTB,
577 &pHbPositions[i].x_offset ,
578 &pHbPositions[i].y_offset );
579 nAdvance = -pHbPositions[i].y_advance;
580 nXOffset = -pHbPositions[i].y_offset;
581 nYOffset = -pHbPositions[i].x_offset;
583 else
585 nAdvance = pHbPositions[i].x_advance;
586 nXOffset = pHbPositions[i].x_offset;
587 nYOffset = -pHbPositions[i].y_offset;
590 nAdvance = std::lround(nAdvance * nXScale);
591 nXOffset = std::lround(nXOffset * nXScale);
592 nYOffset = std::lround(nYOffset * nYScale);
594 Point aNewPos(aCurrPos.X() + nXOffset, aCurrPos.Y() + nYOffset);
595 const GlyphItem aGI(nCharPos, nCharCount, nGlyphIndex, aNewPos, nGlyphFlags,
596 nAdvance, nXOffset, &GetFont());
597 m_GlyphItems.Impl()->push_back(aGI);
599 aCurrPos.AdjustX(nAdvance );
604 hb_buffer_destroy(pHbBuffer);
606 // Some flags are set as a side effect of text layout, save them here.
607 if (rArgs.mnFlags & SalLayoutFlags::GlyphItemsOnly)
608 m_GlyphItems.Impl()->mnFlags = rArgs.mnFlags;
610 return true;
613 void GenericSalLayout::GetCharWidths(DeviceCoordinate* pCharWidths) const
615 const int nCharCount = mnEndCharPos - mnMinCharPos;
617 for (int i = 0; i < nCharCount; ++i)
618 pCharWidths[i] = 0;
620 for (auto const& aGlyphItem : *m_GlyphItems.Impl())
622 const int nIndex = aGlyphItem.charPos() - mnMinCharPos;
623 if (nIndex >= nCharCount)
624 continue;
625 pCharWidths[nIndex] += aGlyphItem.m_nNewWidth;
629 // A note on how Kashida justification is implemented (because it took me 5
630 // years to figure it out):
631 // The decision to insert Kashidas, where and how much is taken by Writer.
632 // This decision is communicated to us in a very indirect way; by increasing
633 // the width of the character after which Kashidas should be inserted by the
634 // desired amount.
636 // Writer eventually calls IsKashidaPosValid() to check whether it can insert a
637 // Kashida between two characters or not.
639 // Here we do:
640 // - In LayoutText() set KashidaJustification flag based on text script.
641 // - In ApplyDXArray():
642 // * Check the above flag to decide whether to insert Kashidas or not.
643 // * For any RTL glyph that has DX adjustment, insert enough Kashidas to
644 // fill in the added space.
646 void GenericSalLayout::ApplyDXArray(const ImplLayoutArgs& rArgs)
648 if (rArgs.mpDXArray == nullptr)
649 return;
651 int nCharCount = mnEndCharPos - mnMinCharPos;
652 std::unique_ptr<DeviceCoordinate[]> const pOldCharWidths(new DeviceCoordinate[nCharCount]);
653 std::unique_ptr<DeviceCoordinate[]> const pNewCharWidths(new DeviceCoordinate[nCharCount]);
655 // Get the natural character widths (i.e. before applying DX adjustments).
656 GetCharWidths(pOldCharWidths.get());
658 // Calculate the character widths after DX adjustments.
659 for (int i = 0; i < nCharCount; ++i)
661 if (i == 0)
662 pNewCharWidths[i] = rArgs.mpDXArray[i];
663 else
664 pNewCharWidths[i] = rArgs.mpDXArray[i] - rArgs.mpDXArray[i - 1];
667 bool bKashidaJustify = false;
668 DeviceCoordinate nKashidaWidth = 0;
669 hb_codepoint_t nKashidaIndex = 0;
670 if (rArgs.mnFlags & SalLayoutFlags::KashidaJustification)
672 hb_font_t *pHbFont = GetFont().GetHbFont();
673 // Find Kashida glyph width and index.
674 if (hb_font_get_glyph(pHbFont, 0x0640, 0, &nKashidaIndex))
675 nKashidaWidth = GetFont().GetKashidaWidth();
676 bKashidaJustify = nKashidaWidth != 0;
679 // Map of Kashida insertion points (in the glyph items vector) and the
680 // requested width.
681 std::map<size_t, DeviceCoordinate> pKashidas;
683 // The accumulated difference in X position.
684 DeviceCoordinate nDelta = 0;
686 // Apply the DX adjustments to glyph positions and widths.
687 size_t i = 0;
688 while (i < m_GlyphItems.Impl()->size())
690 // Accumulate the width difference for all characters corresponding to
691 // this glyph.
692 int nCharPos = (*m_GlyphItems.Impl())[i].charPos() - mnMinCharPos;
693 DeviceCoordinate nDiff = 0;
694 for (int j = 0; j < (*m_GlyphItems.Impl())[i].charCount(); j++)
695 nDiff += pNewCharWidths[nCharPos + j] - pOldCharWidths[nCharPos + j];
697 if (!(*m_GlyphItems.Impl())[i].IsRTLGlyph())
699 // Adjust the width and position of the first (leftmost) glyph in
700 // the cluster.
701 (*m_GlyphItems.Impl())[i].m_nNewWidth += nDiff;
702 (*m_GlyphItems.Impl())[i].m_aLinearPos.AdjustX(nDelta);
704 // Adjust the position of the rest of the glyphs in the cluster.
705 while (++i < m_GlyphItems.Impl()->size())
707 if (!(*m_GlyphItems.Impl())[i].IsInCluster())
708 break;
709 (*m_GlyphItems.Impl())[i].m_aLinearPos.AdjustX(nDelta);
712 else if ((*m_GlyphItems.Impl())[i].IsInCluster())
714 // RTL glyph in the middle of the cluster, will be handled in the
715 // loop below.
716 i++;
718 else
720 // Adjust the width and position of the first (rightmost) glyph in
721 // the cluster.
722 // For RTL, we put all the adjustment to the left of the glyph.
723 (*m_GlyphItems.Impl())[i].m_nNewWidth += nDiff;
724 (*m_GlyphItems.Impl())[i].m_aLinearPos.AdjustX(nDelta + nDiff);
726 // Adjust the X position of all glyphs in the cluster.
727 size_t j = i;
728 while (j > 0)
730 --j;
731 if (!(*m_GlyphItems.Impl())[j].IsInCluster())
732 break;
733 (*m_GlyphItems.Impl())[j].m_aLinearPos.AdjustX(nDelta + nDiff);
736 // If this glyph is Kashida-justifiable, then mark this as a
737 // Kashida position. Since this must be a RTL glyph, we mark the
738 // last glyph in the cluster not the first as this would be the
739 // base glyph.
740 if (bKashidaJustify && (*m_GlyphItems.Impl())[i].AllowKashida() &&
741 nDiff > (*m_GlyphItems.Impl())[i].charCount()) // Rounding errors, 1 pixel per character!
743 pKashidas[i] = nDiff;
744 // Move any non-spacing marks attached to this cluster as well.
745 // Looping backward because this is RTL glyph.
746 while (j > 0)
748 if (!(*m_GlyphItems.Impl())[j].IsDiacritic())
749 break;
750 (*m_GlyphItems.Impl())[j--].m_aLinearPos.AdjustX(nDiff);
753 i++;
756 // Increment the delta, the loop above makes sure we do so only once
757 // for every character (cluster) not for every glyph (otherwise we
758 // would apply it multiple times for each glyphs belonging to the same
759 // character which is wrong since DX adjustments are character based).
760 nDelta += nDiff;
763 // Insert Kashida glyphs.
764 if (bKashidaJustify && !pKashidas.empty())
766 size_t nInserted = 0;
767 for (auto const& pKashida : pKashidas)
769 auto pGlyphIter = m_GlyphItems.Impl()->begin() + nInserted + pKashida.first;
771 // The total Kashida width.
772 DeviceCoordinate nTotalWidth = pKashida.second;
774 // Number of times to repeat each Kashida.
775 int nCopies = 1;
776 if (nTotalWidth > nKashidaWidth)
777 nCopies = nTotalWidth / nKashidaWidth;
779 // See if we can improve the fit by adding an extra Kashidas and
780 // squeezing them together a bit.
781 DeviceCoordinate nOverlap = 0;
782 DeviceCoordinate nShortfall = nTotalWidth - nKashidaWidth * nCopies;
783 if (nShortfall > 0)
785 ++nCopies;
786 DeviceCoordinate nExcess = nCopies * nKashidaWidth - nTotalWidth;
787 if (nExcess > 0)
788 nOverlap = nExcess / (nCopies - 1);
791 Point aPos(pGlyphIter->m_aLinearPos.getX() - nTotalWidth, 0);
792 int nCharPos = pGlyphIter->charPos();
793 GlyphItemFlags const nFlags = GlyphItemFlags::IS_IN_CLUSTER | GlyphItemFlags::IS_RTL_GLYPH;
794 while (nCopies--)
796 GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, nKashidaWidth, 0, &GetFont());
797 pGlyphIter = m_GlyphItems.Impl()->insert(pGlyphIter, aKashida);
798 aPos.AdjustX(nKashidaWidth );
799 aPos.AdjustX( -nOverlap );
800 ++pGlyphIter;
801 ++nInserted;
807 bool GenericSalLayout::IsKashidaPosValid(int nCharPos) const
809 for (auto pIter = m_GlyphItems.Impl()->begin(); pIter != m_GlyphItems.Impl()->end(); ++pIter)
811 if (pIter->charPos() == nCharPos)
813 // The position is the first glyph, this would happen if we
814 // changed the text styling in the middle of a word. Since we don’t
815 // do ligatures across layout engine instances, this can’t be a
816 // ligature so it should be fine.
817 if (pIter == m_GlyphItems.Impl()->begin())
818 return true;
820 // If the character is not supported by this layout, return false
821 // so that fallback layouts would be checked for it.
822 if (pIter->glyphId() == 0)
823 break;
825 // Search backwards for previous glyph belonging to a different
826 // character. We are looking backwards because we are dealing with
827 // RTL glyphs, which will be in visual order.
828 for (auto pPrev = pIter - 1; pPrev != m_GlyphItems.Impl()->begin(); --pPrev)
830 if (pPrev->charPos() != nCharPos)
832 // Check if the found glyph belongs to the next character,
833 // otherwise the current glyph will be a ligature which is
834 // invalid kashida position.
835 if (pPrev->charPos() == (nCharPos + 1))
836 return true;
837 break;
843 return false;
846 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */