android: Reuse launcher icon in activities
[LibreOffice.git] / vcl / source / gdi / CommonSalLayout.cxx
blobfaa127e5a925d04943134930aa6abea2ba7a447c
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 <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>
34 #include <salgdi.hxx>
35 #include <sallayout.hxx>
37 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
39 #include <unicode/uchar.h>
40 #include <hb-ot.h>
41 #include <hb-graphite2.h>
43 #include <memory>
45 GenericSalLayout::GenericSalLayout(LogicalFontInstance &rFont)
46 : m_GlyphItems(rFont)
47 , mpVertGlyphs(nullptr)
48 , mbFuzzing(utl::ConfigManager::IsFuzzing())
52 GenericSalLayout::~GenericSalLayout()
54 if (mpVertGlyphs)
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);
72 namespace {
74 struct SubRun
76 int32_t mnMin;
77 int32_t mnEnd;
78 hb_script_t maScript;
79 hb_direction_t maDirection;
84 namespace {
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
92 #else
93 #include "VerticalOrientationData.cxx"
95 // These must match the values in the file included above.
96 enum class VerticalOrientation {
97 Upright = 0,
98 Rotated = 1,
99 TransformedUpright = 2,
100 TransformedRotated = 3
102 #endif
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);
115 #else
116 uint8_t nRet = 1;
118 if (cCh < 0x10000)
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)];
129 else
131 // Default value for unassigned
132 SAL_WARN("vcl.gdi", "Getting VerticalOrientation for codepoint outside Unicode range");
134 #endif
136 return VerticalOrientation(nRet);
139 } // namespace
141 SalLayoutGlyphs GenericSalLayout::GetGlyphs() const
143 SalLayoutGlyphs glyphs;
144 glyphs.AppendImpl(m_GlyphItems.clone());
145 return glyphs;
148 void GenericSalLayout::SetNeedFallback(vcl::text::ImplLayoutArgs& rArgs, sal_Int32 nCharPos, bool bRightToLeft)
150 if (nCharPos < 0 || mbFuzzing)
151 return;
153 using namespace ::com::sun::star;
155 if (!mxBreak.is())
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
162 //font
163 sal_Int32 nDone;
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);
173 // tdf#107612
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
177 // work.
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);
222 if (!nGlyphIndex)
223 return false;
225 if (!mpVertGlyphs)
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)
260 return true;
262 if (pGlyphs)
264 // Work with pre-computed glyph items.
265 m_GlyphItems = *pGlyphs;
266 for(const GlyphItem& item : m_GlyphItems)
267 if(!item.glyphId())
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();
271 return true;
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!
289 else
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
298 // in the future.
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);
329 double nXScale = 0;
330 double nYScale = 0;
331 GetFont().GetScale(&nXScale, &nYScale);
333 DevicePoint aCurrPos(0, 0);
334 while (true)
336 int nBidiMinRunPos, nBidiEndRunPos;
337 bool bRightToLeft;
338 if (!rArgs.GetNextRun(&nBidiMinRunPos, &nBidiEndRunPos, &bRightToLeft))
339 break;
341 // Find script subruns.
342 std::vector<SubRun> aSubRuns;
343 int nCurrentPos = nBidiMinRunPos;
344 size_t k = 0;
345 for (; k < pTextLayout->runs.size(); ++k)
347 vcl::text::Run const& rRun(pTextLayout->runs[k]);
348 if (rRun.nStart <= nCurrentPos && nCurrentPos < rRun.nEnd)
350 break;
354 if (isGraphite)
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 });
359 else
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
368 // orientation.
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))
385 nIdx = nNextIdx;
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
395 // and then rotated.
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;
404 else
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 });
411 else
412 aSubRuns.back().mnEnd = nIdx;
415 else
417 aSubRuns.push_back({ nMinRunPos, nEndRunPos, aScript, aDirection });
420 nCurrentPos = nEndRunPos;
421 ++k;
425 // RTL subruns should be reversed to ensure that final glyph order is
426 // correct.
427 if (bRightToLeft)
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;
443 if (nMinRunPos == 0)
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()));
454 else
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));
460 hb_buffer_add_utf16(
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
465 // preference.
466 const char*const pHbShapers[] = { "graphite2", "ot", "fallback", nullptr };
467 bool ok = hb_shape_full(pHbFont, pHbBuffer, maFeatures.data(), maFeatures.size(), pHbShapers);
468 assert(ok);
469 (void) ok;
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.
483 if (!bRightToLeft)
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)
489 nCharCount = 0;
490 bInCluster = true;
492 else
494 // Find the next glyph with a different cluster, or the
495 // end of text.
496 int j = i;
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;
509 else
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)
515 nCharCount = 0;
516 bInCluster = true;
518 else
520 // Find the previous glyph with a different cluster, or
521 // the end of text.
522 int j = i;
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
537 if (!nGlyphIndex)
539 SetNeedFallback(rArgs, nCharPos, bRightToLeft);
540 if (SalLayoutFlags::ForFallback & rArgs.mnFlags)
541 continue;
544 GlyphItemFlags nGlyphFlags = GlyphItemFlags::NONE;
545 if (bRightToLeft)
546 nGlyphFlags |= GlyphItemFlags::IS_RTL_GLYPH;
548 if (bClusterStart)
549 nGlyphFlags |= GlyphItemFlags::IS_CLUSTER_START;
551 if (bInCluster)
552 nGlyphFlags |= GlyphItemFlags::IS_IN_CLUSTER;
554 sal_UCS4 aChar
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
578 // correct y offset.
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 );
588 else
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);
615 return true;
618 void GenericSalLayout::GetCharWidths(std::vector<DeviceCoordinate>& rCharWidths, const OUString& rStr) const
620 const int nCharCount = mnEndCharPos - mnMinCharPos;
622 rCharWidths.clear();
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)
631 continue;
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.
638 if (!xBreak.is())
639 xBreak = mxBreak.is() ? mxBreak : vcl::unohelper::CreateBreakIterator();
641 // Count grapheme clusters in the ligature.
642 sal_Int32 nDone;
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);
648 nGraphemeCount++;
652 if (nGraphemeCount > 1)
654 // More than one grapheme cluster, we want to distribute the glyph
655 // width over them.
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
667 // match, ignore it.
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.
672 double fScale = 0;
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
686 // order.
687 if (aGlyphItem.IsRTLGlyph())
688 std::reverse(aWidths.begin(), aWidths.end());
690 else
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
697 // ligature width.
698 aWidths[nGraphemeCount - 1] += aGlyphItem.newWidth() - (nWidth * nGraphemeCount);
701 // Set the width of each grapheme cluster.
702 sal_Int32 nDone;
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);
711 else
712 rCharWidths[aGlyphItem.charPos() - mnMinCharPos] += aGlyphItem.newWidth();
716 // - pDXArray: is the adjustments to glyph advances (usually due to
717 // justification).
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)
732 if (i == 0)
733 pNewCharWidths[i] = pDXArray[i];
734 else
735 pNewCharWidths[i] = pDXArray[i] - pDXArray[i - 1];
738 // Map of Kashida insertion points (in the glyph items vector) and the
739 // requested width.
740 std::map<size_t, std::pair<DeviceCoordinate, DeviceCoordinate>> pKashidas;
742 // The accumulated difference in X position.
743 double nDelta = 0;
745 // Apply the DX adjustments to glyph positions and widths.
746 size_t i = 0;
747 while (i < m_GlyphItems.size())
749 // Accumulate the width difference for all characters corresponding to
750 // this glyph.
751 int nCharPos = m_GlyphItems[i].charPos() - mnMinCharPos;
752 double nDiff = 0;
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
759 // the cluster.
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())
767 break;
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
774 // loop below.
775 i++;
777 else // RTL
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] };
795 i++;
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).
802 nDelta += nDiff;
805 // Insert Kashida glyphs.
806 if (pKashidas.empty())
807 return;
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");
816 return;
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.
828 int nCopies = 1;
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.
834 double nOverlap = 0;
835 double nShortfall = nTotalWidth - nKashidaWidth * nCopies;
836 if (nShortfall > 0)
838 ++nCopies;
839 double nExcess = nCopies * nKashidaWidth - nTotalWidth;
840 if (nExcess > 0)
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
848 // glyphs there.
849 aPos.adjustX(-nClusterWidth + pGlyphIter->origWidth());
850 while (nCopies--)
852 GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, nKashidaWidth, 0, 0);
853 pGlyphIter = m_GlyphItems.insert(pGlyphIter, aKashida);
854 aPos.adjustX(nKashidaWidth - nOverlap);
855 ++pGlyphIter;
856 ++nInserted;
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())
873 return false;
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)
878 return false;
880 // Lastly check if this position is kashida-safe.
881 return rNextGlyph->IsSafeToInsertKashida();
884 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */