Version 5.4.3.2, tag libreoffice-5.4.3.2
[LibreOffice.git] / vcl / source / gdi / CommonSalLayout.cxx
blob54a8dc3123983d85b3cef5920041cacce20d99a4
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>
21 #include "CommonSalLayout.hxx"
23 #include <vcl/unohelp.hxx>
24 #include <scrptrun.h>
25 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
26 #include <i18nlangtag/mslangid.hxx>
27 #include <limits>
28 #include <salgdi.hxx>
29 #include <unicode/uchar.h>
31 #if defined(ANDROID)
32 namespace std
34 template<typename T>
35 T lround(T x)
37 return ::lround(x);
40 #endif
42 static hb_blob_t* getFontTable(hb_face_t* /*face*/, hb_tag_t nTableTag, void* pUserData)
44 char pTagName[5];
45 pTagName[0] = (char)(nTableTag >> 24);
46 pTagName[1] = (char)(nTableTag >> 16);
47 pTagName[2] = (char)(nTableTag >> 8);
48 pTagName[3] = (char)(nTableTag);
49 pTagName[4] = 0;
51 sal_uLong nLength = 0;
52 #if defined(_WIN32)
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);
69 if (nLength > 0)
71 pBuffer = new unsigned char[nLength];
72 pFont->GetFontTable(pTagName, pBuffer);
74 #else
75 const unsigned char* pBuffer = nullptr;
76 FreetypeFont* pFont = static_cast<FreetypeFont*>(pUserData);
77 pBuffer = pFont->GetTable(pTagName, &nLength);
78 #endif
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); });
85 #else
86 pBlob = hb_blob_create(reinterpret_cast<const char*>(pBuffer), nLength, HB_MEMORY_MODE_READONLY, nullptr, nullptr);
87 #endif
89 return pBlob;
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);
101 return pHbFont;
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);
110 #if defined(_WIN32)
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
114 // positions.
115 double nWidth(mrFontSelData.mnWidth ? mrFontSelData.mnWidth * mnAveWidthFactor : nHeight);
116 #else
117 double nWidth(mrFontSelData.mnWidth ? mrFontSelData.mnWidth : nHeight);
118 #endif
120 if (nYScale)
121 *nYScale = nHeight / nUPEM;
123 if (nXScale)
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*/,
132 void* /*user_data*/)
134 return 0;
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);
141 return ufuncs;
143 #endif
145 void CommonSalLayout::ParseFeatures(const OUString& aName)
147 if (aName.indexOf(FontSelectPatternAttributes::FEAT_PREFIX) < 0)
148 return;
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, '=');
160 else
162 hb_feature_t aFeature;
163 if (hb_feature_from_string(sToken.getStr(), sToken.getLength(), &aFeature))
164 maFeatures.push_back(aFeature);
167 while (nIndex >= 0);
170 #if defined(_WIN32)
171 CommonSalLayout::CommonSalLayout(HDC hDC, WinFontInstance& rWinFontInstance, const WinFontFace& rWinFontFace)
172 : mrFontSelData(rWinFontInstance.maFontSelData)
173 , mhDC(hDC)
174 , mhFont(static_cast<HFONT>(GetCurrentObject(hDC, OBJ_FONT)))
175 , mrWinFontInstance(rWinFontInstance)
176 , mnAveWidthFactor(1.0f)
177 , mpVertGlyphs(nullptr)
179 mpHbFont = rWinFontFace.GetHbFont();
180 if (!mpHbFont)
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));
193 LOGFONTW aLogFont;
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();
227 if (!mpHbFont)
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.
232 hb_face_t* pHbFace;
233 CTFontRef pCTFont = static_cast<CTFontRef>(CFDictionaryGetValue(rCoreTextStyle.GetStyleDict(), kCTFontAttributeName));
234 CGFontRef pCGFont = CTFontCopyGraphicsFont(pCTFont, nullptr);
235 if (pCGFont)
236 pHbFace = hb_coretext_face_create(pCGFont);
237 else
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);
246 #else
247 CommonSalLayout::CommonSalLayout(FreetypeFont& rFreetypeFont)
248 : mrFontSelData(rFreetypeFont.GetFontSelData())
249 , mrFreetypeFont(rFreetypeFont)
250 , mpVertGlyphs(nullptr)
252 mpHbFont = rFreetypeFont.GetHbFont();
253 if (!mpHbFont)
255 hb_face_t* pHbFace = hb_face_create_for_tables(getFontTable, &rFreetypeFont, nullptr);
257 mpHbFont = createHbFont(pHbFace);
258 mrFreetypeFont.SetHbFont(mpHbFont);
261 #endif
263 void CommonSalLayout::InitFont() const
265 #if defined(_WIN32)
266 SelectObject(mhDC, mhFont);
267 #endif
270 struct SubRun
272 int32_t mnMin;
273 int32_t mnEnd;
274 hb_script_t maScript;
275 hb_direction_t maDirection;
278 namespace vcl {
279 struct Run
281 int32_t nStart;
282 int32_t nEnd;
283 UScriptCode nCode;
284 Run(int32_t nStart_, int32_t nEnd_, UScriptCode nCode_)
285 : nStart(nStart_)
286 , nEnd(nEnd_)
287 , nCode(nCode_)
291 class TextLayoutCache
293 public:
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),
299 nEnd);
300 while (aScriptRun.next())
302 runs.push_back(Run(aScriptRun.getScriptStart(),
303 aScriptRun.getScriptEnd(), aScriptRun.getScriptCode()));
307 } // namespace vcl
309 namespace {
310 #include "VerticalOrientationData.cxx"
312 // These must match the values in the file included above.
313 enum class VerticalOrientation {
314 Upright = 0,
315 Rotated = 1,
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;
326 uint8_t nRet = 1;
328 if (cCh < 0x10000)
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)];
339 else
341 // Default value for unassigned
342 SAL_WARN("vcl.gdi", "Getting VerticalOrientation for codepoint outside Unicode range");
345 return VerticalOrientation(nRet);
348 } // namespace
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)
357 if (nCharPos < 0)
358 return;
360 using namespace ::com::sun::star;
362 if (!mxBreak.is())
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
369 //font
370 sal_Int32 nDone;
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);
385 if (rArgs.mpDXArray)
386 ApplyDXArray(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))
411 return false;
413 if (!mpVertGlyphs)
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!
459 else
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);
470 #endif
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);
480 double nXScale = 0;
481 double nYScale = 0;
482 getScale(&nXScale, &nYScale);
484 Point aCurrPos(0, 0);
485 while (true)
487 int nBidiMinRunPos, nBidiEndRunPos;
488 bool bRightToLeft;
489 if (!rArgs.GetNextRun(&nBidiMinRunPos, &nBidiEndRunPos, &bRightToLeft))
490 break;
492 // Find script subruns.
493 int nCurrentPos = nBidiMinRunPos;
494 std::vector<SubRun> aSubRuns;
495 size_t k = 0;
496 for (; k < pTextLayout->runs.size(); ++k)
498 vcl::Run const& rRun(pTextLayout->runs[k]);
499 if (rRun.nStart <= nCurrentPos && nCurrentPos < rRun.nEnd)
501 break;
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
513 // orientation.
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))
530 nIdx = nNextIdx;
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
540 // and then rotated.
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;
549 else
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 });
556 else
557 aSubRuns.back().mnEnd = nIdx;
560 else
562 aSubRuns.push_back({ nMinRunPos, nEndRunPos, aScript, aDirection });
565 nCurrentPos = nEndRunPos;
566 ++k;
569 // RTL subruns should be reversed to ensure that final glyph order is
570 // correct.
571 if (bRightToLeft)
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;
587 if (nMinRunPos == 0)
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);
596 hb_buffer_add_utf16(
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());
610 assert(ok);
611 (void) ok;
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
624 if (!nGlyphIndex)
626 SetNeedFallback(rArgs, nCharPos, bRightToLeft);
627 if (SalLayoutFlags::ForFallback & rArgs.mnFlags)
628 continue;
631 bool bInCluster = false;
632 if (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster)
633 bInCluster = true;
635 long nGlyphFlags = 0;
636 if (bRightToLeft)
637 nGlyphFlags |= GlyphItem::IS_RTL_GLYPH;
639 if (bInCluster)
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;
669 else
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,
682 nAdvance, nXOffset);
683 AppendGlyph(aGI);
685 aCurrPos.X() += nAdvance;
690 hb_buffer_destroy(pHbBuffer);
692 return true;
695 bool CommonSalLayout::GetCharWidths(DeviceCoordinate* pCharWidths) const
697 int nCharCount = mnEndCharPos - mnMinCharPos;
699 for (int i = 0; i < nCharCount; ++i)
700 pCharWidths[i] = 0;
702 for (auto const& aGlyphItem : m_GlyphItems)
703 pCharWidths[aGlyphItem.mnCharPos - mnMinCharPos] += aGlyphItem.mnNewWidth;
705 return true;
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
713 // desired amount.
715 // Writer eventually calls IsKashidaPosValid() to check whether it can insert a
716 // Kashida between two characters or not.
718 // Here we do:
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)
728 return;
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)
740 if (i == 0)
741 pNewCharWidths[i] = rArgs.mpDXArray[i];
742 else
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))
755 double nXScale = 0;
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
763 // requested width.
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.
770 size_t i = 0;
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.
780 size_t j = i;
781 while (j < m_GlyphItems.size())
783 if (m_GlyphItems[j].mnCharPos != m_GlyphItems[i].mnCharPos)
784 break;
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;
789 ++j;
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.
801 if (i > 0)
803 auto pGlyph = m_GlyphItems.begin() + i - 1;
804 while (pGlyph != m_GlyphItems.begin() && pGlyph->IsDiacritic())
806 pGlyph->maLinearPos.X() += nDiff;
807 --pGlyph;
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).
817 nDelta += nDiff;
818 i = j;
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.
833 int nCopies = 1;
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;
841 if (nShortfall > 0)
843 ++nCopies;
844 DeviceCoordinate nExcess = nCopies * nKashidaWidth - nTotalWidth;
845 if (nExcess > 0)
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;
852 while (nCopies--)
854 GlyphItem aKashida(nCharPos, nKashidaIndex, aPos, nFlags, nKashidaWidth);
855 pGlyphIter = m_GlyphItems.insert(pGlyphIter, aKashida);
856 aPos.X() += nKashidaWidth;
857 aPos.X() -= nOverlap;
858 ++pGlyphIter;
859 ++nInserted;
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())
876 return true;
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)
881 break;
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))
894 return true;
895 break;
901 return false;
904 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */