LibreOffice Module vcl (master)  1
CommonSalLayout.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <sal/config.h>
21 
22 #include <sal/log.hxx>
23 #include <unotools/configmgr.hxx>
24 #include <o3tl/temporary.hxx>
25 
26 #include <vcl/unohelp.hxx>
27 #include <vcl/font/Feature.hxx>
29 
30 #include <ImplLayoutArgs.hxx>
31 #include <TextLayoutCache.hxx>
33 #include <salgdi.hxx>
34 #include <sallayout.hxx>
35 
36 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
37 
38 #include <unicode/uchar.h>
39 #include <hb-ot.h>
40 #include <hb-graphite2.h>
41 
42 #include <memory>
43 
44 #if !HB_VERSION_ATLEAST(1, 1, 0)
45 // Disabled Unicode compatibility decomposition, see fdo#66715
46 static unsigned int unicodeDecomposeCompatibility(hb_unicode_funcs_t* /*ufuncs*/,
47  hb_codepoint_t /*u*/,
48  hb_codepoint_t* /*decomposed*/,
49  void* /*user_data*/)
50 {
51  return 0;
52 }
53 
54 static hb_unicode_funcs_t* getUnicodeFuncs()
55 {
56  static hb_unicode_funcs_t* ufuncs = hb_unicode_funcs_create(hb_icu_get_unicode_funcs());
57  hb_unicode_funcs_set_decompose_compatibility_func(ufuncs, unicodeDecomposeCompatibility, nullptr, nullptr);
58  return ufuncs;
59 }
60 #endif
61 
63  : m_GlyphItems(rFont)
64  , mpVertGlyphs(nullptr)
65  , mbFuzzing(utl::ConfigManager::IsFuzzing())
66 {
67 }
68 
70 {
71 }
72 
73 void GenericSalLayout::ParseFeatures(const OUString& aName)
74 {
75  vcl::font::FeatureParser aParser(aName);
76  const OUString& sLanguage = aParser.getLanguage();
77  if (!sLanguage.isEmpty())
78  msLanguage = OUStringToOString(sLanguage, RTL_TEXTENCODING_ASCII_US);
79 
80  for (auto const &rFeat : aParser.getFeatures())
81  {
82  hb_feature_t aFeature { rFeat.m_nTag, rFeat.m_nValue, rFeat.m_nStart, rFeat.m_nEnd };
83  maFeatures.push_back(aFeature);
84  }
85 }
86 
87 namespace {
88 
89 struct SubRun
90 {
91  int32_t mnMin;
92  int32_t mnEnd;
93  hb_script_t maScript;
94  hb_direction_t maDirection;
95 };
96 
97 }
98 
99 namespace {
100 #if U_ICU_VERSION_MAJOR_NUM >= 63
101  enum class VerticalOrientation {
102  Upright = U_VO_UPRIGHT,
103  Rotated = U_VO_ROTATED,
104  TransformedUpright = U_VO_TRANSFORMED_UPRIGHT,
105  TransformedRotated = U_VO_TRANSFORMED_ROTATED
106  };
107 #else
108  #include "VerticalOrientationData.cxx"
109 
110  // These must match the values in the file included above.
111  enum class VerticalOrientation {
112  Upright = 0,
113  Rotated = 1,
114  TransformedUpright = 2,
115  TransformedRotated = 3
116  };
117 #endif
118 
119  VerticalOrientation GetVerticalOrientation(sal_UCS4 cCh, const LanguageTag& rTag)
120  {
121  // Override orientation of fullwidth colon , semi-colon,
122  // and Bopomofo tonal marks.
123  if ((cCh == 0xff1a || cCh == 0xff1b
124  || cCh == 0x2ca || cCh == 0x2cb || cCh == 0x2c7 || cCh == 0x2d9)
125  && rTag.getLanguage() == "zh")
126  return VerticalOrientation::TransformedUpright;
127 
128 #if U_ICU_VERSION_MAJOR_NUM >= 63
129  int32_t nRet = u_getIntPropertyValue(cCh, UCHAR_VERTICAL_ORIENTATION);
130 #else
131  uint8_t nRet = 1;
132 
133  if (cCh < 0x10000)
134  {
136  [cCh & ((1 << kVerticalOrientationCharBits) - 1)];
137  }
138  else if (cCh < (kVerticalOrientationMaxPlane + 1) * 0x10000)
139  {
141  [(cCh & 0xffff) >> kVerticalOrientationCharBits]]
142  [cCh & ((1 << kVerticalOrientationCharBits) - 1)];
143  }
144  else
145  {
146  // Default value for unassigned
147  SAL_WARN("vcl.gdi", "Getting VerticalOrientation for codepoint outside Unicode range");
148  }
149 #endif
150 
151  return VerticalOrientation(nRet);
152  }
153 
154 } // namespace
155 
156 std::shared_ptr<vcl::text::TextLayoutCache> GenericSalLayout::CreateTextLayoutCache(OUString const& rString)
157 {
158  return std::make_shared<vcl::text::TextLayoutCache>(rString.getStr(), rString.getLength());
159 }
160 
162 {
163  SalLayoutGlyphs glyphs;
164  glyphs.AppendImpl(m_GlyphItems.clone());
165  return glyphs;
166 }
167 
168 void GenericSalLayout::SetNeedFallback(vcl::text::ImplLayoutArgs& rArgs, sal_Int32 nCharPos, bool bRightToLeft)
169 {
170  if (nCharPos < 0 || mbFuzzing)
171  return;
172 
173  using namespace ::com::sun::star;
174 
175  if (!mxBreak.is())
177 
178  lang::Locale aLocale(rArgs.maLanguageTag.getLocale());
179 
180  //if position nCharPos is missing in the font, grab the entire grapheme and
181  //mark all glyphs as missing so the whole thing is rendered with the same
182  //font
183  sal_Int32 nDone;
184  sal_Int32 nGraphemeEndPos =
185  mxBreak->nextCharacters(rArgs.mrStr, nCharPos, aLocale,
186  i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
187  // Safely advance nCharPos in case it is a non-BMP character.
188  rArgs.mrStr.iterateCodePoints(&nCharPos);
189  sal_Int32 nGraphemeStartPos =
190  mxBreak->previousCharacters(rArgs.mrStr, nCharPos, aLocale,
191  i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
192 
193  rArgs.AddFallbackRun(nGraphemeStartPos, nGraphemeEndPos, bRightToLeft);
194 }
195 
197 {
199 
200  if (rArgs.mpAltNaturalDXArray) // Used when "TextRenderModeForResolutionIndependentLayout" is set
202  else if (rArgs.mpDXArray) // Normal case
203  ApplyDXArray(rArgs.mpDXArray, rArgs.mnFlags);
204  else if (rArgs.mnLayoutWidth)
205  Justify(rArgs.mnLayoutWidth);
206  // apply asian kerning if the glyphs are not already formatted
207  else if ((rArgs.mnFlags & SalLayoutFlags::KerningAsian)
208  && !(rArgs.mnFlags & SalLayoutFlags::Vertical))
209  ApplyAsianKerning(rArgs.mrStr);
210 }
211 
212 void GenericSalLayout::DrawText(SalGraphics& rSalGraphics) const
213 {
214  //call platform dependent DrawText functions
215  rSalGraphics.DrawTextLayout( *this );
216 }
217 
218 // Find if the nominal glyph of the character is an input to “vert” feature.
219 // We don’t check for a specific script or language as it shouldn’t matter
220 // here; if the glyph would be the result from applying “vert” for any
221 // script/language then we want to always treat it as upright glyph.
223 {
224  hb_codepoint_t nGlyphIndex = 0;
225  hb_font_t *pHbFont = GetFont().GetHbFont();
226  if (!hb_font_get_glyph(pHbFont, aChar, aVariationSelector, &nGlyphIndex))
227  return false;
228 
229  if (!mpVertGlyphs)
230  {
231  hb_face_t* pHbFace = hb_font_get_face(pHbFont);
232  mpVertGlyphs = hb_set_create();
233 
234  // Find all GSUB lookups for “vert” feature.
235  hb_set_t* pLookups = hb_set_create();
236  hb_tag_t const pFeatures[] = { HB_TAG('v','e','r','t'), HB_TAG_NONE };
237  hb_ot_layout_collect_lookups(pHbFace, HB_OT_TAG_GSUB, nullptr, nullptr, pFeatures, pLookups);
238  if (!hb_set_is_empty(pLookups))
239  {
240  // Find the output glyphs in each lookup (i.e. the glyphs that
241  // would result from applying this lookup).
242  hb_codepoint_t nIdx = HB_SET_VALUE_INVALID;
243  while (hb_set_next(pLookups, &nIdx))
244  {
245  hb_set_t* pGlyphs = hb_set_create();
246  hb_ot_layout_lookup_collect_glyphs(pHbFace, HB_OT_TAG_GSUB, nIdx,
247  nullptr, // glyphs before
248  pGlyphs, // glyphs input
249  nullptr, // glyphs after
250  nullptr); // glyphs out
251  hb_set_union(mpVertGlyphs, pGlyphs);
252  }
253  }
254  }
255 
256  return hb_set_has(mpVertGlyphs, nGlyphIndex) != 0;
257 }
258 
260 {
261  // No need to touch m_GlyphItems at all for an empty string.
262  if (rArgs.mnEndCharPos - rArgs.mnMinCharPos <= 0)
263  return true;
264 
265  if (pGlyphs)
266  {
267  // Work with pre-computed glyph items.
268  m_GlyphItems = *pGlyphs;
269  for(const GlyphItem& item : m_GlyphItems)
270  if(!item.glyphId())
271  SetNeedFallback(rArgs, item.charPos(), item.IsRTLGlyph());
272  // Some flags are set as a side effect of text layout, restore them here.
273  rArgs.mnFlags |= pGlyphs->GetFlags();
274  return true;
275  }
276 
277  hb_font_t *pHbFont = GetFont().GetHbFont();
278  bool isGraphite = GetFont().IsGraphiteFont();
279 
280  int nGlyphCapacity = 2 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos);
281  m_GlyphItems.reserve(nGlyphCapacity);
282 
283  const int nLength = rArgs.mrStr.getLength();
284  const sal_Unicode *pStr = rArgs.mrStr.getStr();
285 
286  std::optional<vcl::text::TextLayoutCache> oNewScriptRun;
287  vcl::text::TextLayoutCache const* pTextLayout;
288  if (rArgs.m_pTextLayoutCache)
289  {
290  pTextLayout = rArgs.m_pTextLayoutCache; // use cache!
291  }
292  else
293  {
294  oNewScriptRun.emplace(pStr, rArgs.mnEndCharPos);
295  pTextLayout = &*oNewScriptRun;
296  }
297 
298  // nBaseOffset is used to align vertical text to the center of rotated
299  // horizontal text. That is the offset from original baseline to
300  // the center of EM box. Maybe we can use OpenType base table to improve this
301  // in the future.
302  DeviceCoordinate nBaseOffset = 0;
303  if (rArgs.mnFlags & SalLayoutFlags::Vertical)
304  {
305  hb_font_extents_t extents;
306  if (hb_font_get_h_extents(pHbFont, &extents))
307  nBaseOffset = ( extents.ascender + extents.descender ) / 2;
308  }
309 
310  hb_buffer_t* pHbBuffer = hb_buffer_create();
311  hb_buffer_pre_allocate(pHbBuffer, nGlyphCapacity);
312 #if !HB_VERSION_ATLEAST(1, 1, 0)
313  static hb_unicode_funcs_t* pHbUnicodeFuncs = getUnicodeFuncs();
314  hb_buffer_set_unicode_funcs(pHbBuffer, pHbUnicodeFuncs);
315 #endif
316 
319  {
320  SAL_INFO("vcl.harfbuzz", "Disabling kerning for font: " << rFontSelData.maTargetName);
321  maFeatures.push_back({ HB_TAG('k','e','r','n'), 0, 0, static_cast<unsigned int>(-1) });
322  }
323 
324  if (rFontSelData.GetPitch() == PITCH_FIXED)
325  {
326  SAL_INFO("vcl.harfbuzz", "Disabling ligatures for font: " << rFontSelData.maTargetName);
327  maFeatures.push_back({ HB_TAG('l','i','g','a'), 0, 0, static_cast<unsigned int>(-1) });
328  }
329 
330  ParseFeatures(rFontSelData.maTargetName);
331 
332  double nXScale = 0;
333  double nYScale = 0;
334  GetFont().GetScale(&nXScale, &nYScale);
335 
336  DevicePoint aCurrPos(0, 0);
337  while (true)
338  {
339  int nBidiMinRunPos, nBidiEndRunPos;
340  bool bRightToLeft;
341  if (!rArgs.GetNextRun(&nBidiMinRunPos, &nBidiEndRunPos, &bRightToLeft))
342  break;
343 
344  // Find script subruns.
345  std::vector<SubRun> aSubRuns;
346  int nCurrentPos = nBidiMinRunPos;
347  size_t k = 0;
348  for (; k < pTextLayout->runs.size(); ++k)
349  {
350  vcl::text::Run const& rRun(pTextLayout->runs[k]);
351  if (rRun.nStart <= nCurrentPos && nCurrentPos < rRun.nEnd)
352  {
353  break;
354  }
355  }
356 
357  if (isGraphite)
358  {
359  hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode);
360  aSubRuns.push_back({ nBidiMinRunPos, nBidiEndRunPos, aScript, bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR });
361  }
362  else
363  {
364  while (nCurrentPos < nBidiEndRunPos && k < pTextLayout->runs.size())
365  {
366  int32_t nMinRunPos = nCurrentPos;
367  int32_t nEndRunPos = std::min(pTextLayout->runs[k].nEnd, nBidiEndRunPos);
368  hb_direction_t aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
369  hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode);
370  // For vertical text, further divide the runs based on character
371  // orientation.
372  if (rArgs.mnFlags & SalLayoutFlags::Vertical)
373  {
374  sal_Int32 nIdx = nMinRunPos;
375  while (nIdx < nEndRunPos)
376  {
377  sal_Int32 nPrevIdx = nIdx;
378  sal_UCS4 aChar = rArgs.mrStr.iterateCodePoints(&nIdx);
379  VerticalOrientation aVo = GetVerticalOrientation(aChar, rArgs.maLanguageTag);
380 
381  sal_UCS4 aVariationSelector = 0;
382  if (nIdx < nEndRunPos)
383  {
384  sal_Int32 nNextIdx = nIdx;
385  sal_UCS4 aNextChar = rArgs.mrStr.iterateCodePoints(&nNextIdx);
386  if (u_hasBinaryProperty(aNextChar, UCHAR_VARIATION_SELECTOR))
387  {
388  nIdx = nNextIdx;
389  aVariationSelector = aNextChar;
390  }
391  }
392 
393  // Charters with U and Tu vertical orientation should
394  // be shaped in vertical direction. But characters
395  // with Tr should be shaped in vertical direction
396  // only if they have vertical alternates, otherwise
397  // they should be shaped in horizontal direction
398  // and then rotated.
399  // See http://unicode.org/reports/tr50/#vo
400  if (aVo == VerticalOrientation::Upright ||
401  aVo == VerticalOrientation::TransformedUpright ||
402  (aVo == VerticalOrientation::TransformedRotated &&
403  HasVerticalAlternate(aChar, aVariationSelector)))
404  {
405  aDirection = HB_DIRECTION_TTB;
406  }
407  else
408  {
409  aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
410  }
411 
412  if (aSubRuns.empty() || aSubRuns.back().maDirection != aDirection)
413  aSubRuns.push_back({ nPrevIdx, nIdx, aScript, aDirection });
414  else
415  aSubRuns.back().mnEnd = nIdx;
416  }
417  }
418  else
419  {
420  aSubRuns.push_back({ nMinRunPos, nEndRunPos, aScript, aDirection });
421  }
422 
423  nCurrentPos = nEndRunPos;
424  ++k;
425  }
426  }
427 
428  // RTL subruns should be reversed to ensure that final glyph order is
429  // correct.
430  if (bRightToLeft)
431  std::reverse(aSubRuns.begin(), aSubRuns.end());
432 
433  for (const auto& aSubRun : aSubRuns)
434  {
435  hb_buffer_clear_contents(pHbBuffer);
436 
437  const int nMinRunPos = aSubRun.mnMin;
438  const int nEndRunPos = aSubRun.mnEnd;
439  const int nRunLen = nEndRunPos - nMinRunPos;
440 
441  OString sLanguage = msLanguage;
442  if (sLanguage.isEmpty())
443  sLanguage = OUStringToOString(rArgs.maLanguageTag.getBcp47(), RTL_TEXTENCODING_ASCII_US);
444 
445  int nHbFlags = HB_BUFFER_FLAGS_DEFAULT;
446  if (nMinRunPos == 0)
447  nHbFlags |= HB_BUFFER_FLAG_BOT; /* Beginning-of-text */
448  if (nEndRunPos == nLength)
449  nHbFlags |= HB_BUFFER_FLAG_EOT; /* End-of-text */
450 
451  hb_buffer_set_direction(pHbBuffer, aSubRun.maDirection);
452  hb_buffer_set_script(pHbBuffer, aSubRun.maScript);
453  hb_buffer_set_language(pHbBuffer, hb_language_from_string(sLanguage.getStr(), -1));
454  hb_buffer_set_flags(pHbBuffer, static_cast<hb_buffer_flags_t>(nHbFlags));
455  hb_buffer_add_utf16(
456  pHbBuffer, reinterpret_cast<uint16_t const *>(pStr), nLength,
457  nMinRunPos, nRunLen);
458  hb_buffer_set_cluster_level(pHbBuffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
459 
460  // The shapers that we want HarfBuzz to use, in the order of
461  // preference. The coretext_aat shaper is available only on macOS,
462  // but there is no harm in always including it, HarfBuzz will
463  // ignore unavailable shapers.
464  const char*const pHbShapers[] = { "graphite2", "coretext_aat", "ot", "fallback", nullptr };
465  bool ok = hb_shape_full(pHbFont, pHbBuffer, maFeatures.data(), maFeatures.size(), pHbShapers);
466  assert(ok);
467  (void) ok;
468 
469  int nRunGlyphCount = hb_buffer_get_length(pHbBuffer);
470  hb_glyph_info_t *pHbGlyphInfos = hb_buffer_get_glyph_infos(pHbBuffer, nullptr);
471  hb_glyph_position_t *pHbPositions = hb_buffer_get_glyph_positions(pHbBuffer, nullptr);
472 
473  for (int i = 0; i < nRunGlyphCount; ++i) {
474  int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint;
475  int32_t nCharPos = pHbGlyphInfos[i].cluster;
476  int32_t nCharCount = 0;
477  bool bInCluster = false;
478  bool bClusterStart = false;
479 
480  // Find the number of characters that make up this glyph.
481  if (!bRightToLeft)
482  {
483  // If the cluster is the same as previous glyph, then this
484  // already consumed, skip.
485  if (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster)
486  {
487  nCharCount = 0;
488  bInCluster = true;
489  }
490  else
491  {
492  // Find the next glyph with a different cluster, or the
493  // end of text.
494  int j = i;
495  int32_t nNextCharPos = nCharPos;
496  while (nNextCharPos == nCharPos && j < nRunGlyphCount)
497  nNextCharPos = pHbGlyphInfos[j++].cluster;
498 
499  if (nNextCharPos == nCharPos)
500  nNextCharPos = nEndRunPos;
501  nCharCount = nNextCharPos - nCharPos;
502  if ((i == 0 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i - 1].cluster) &&
503  (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster))
504  bClusterStart = true;
505  }
506  }
507  else
508  {
509  // If the cluster is the same as previous glyph, then this
510  // will be consumed later, skip.
511  if (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster)
512  {
513  nCharCount = 0;
514  bInCluster = true;
515  }
516  else
517  {
518  // Find the previous glyph with a different cluster, or
519  // the end of text.
520  int j = i;
521  int32_t nNextCharPos = nCharPos;
522  while (nNextCharPos == nCharPos && j >= 0)
523  nNextCharPos = pHbGlyphInfos[j--].cluster;
524 
525  if (nNextCharPos == nCharPos)
526  nNextCharPos = nEndRunPos;
527  nCharCount = nNextCharPos - nCharPos;
528  if ((i == nRunGlyphCount - 1 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i + 1].cluster) &&
529  (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster))
530  bClusterStart = true;
531  }
532  }
533 
534  // if needed request glyph fallback by updating LayoutArgs
535  if (!nGlyphIndex)
536  {
537  SetNeedFallback(rArgs, nCharPos, bRightToLeft);
539  continue;
540  }
541 
542  GlyphItemFlags nGlyphFlags = GlyphItemFlags::NONE;
543  if (bRightToLeft)
544  nGlyphFlags |= GlyphItemFlags::IS_RTL_GLYPH;
545 
546  if (bClusterStart)
547  nGlyphFlags |= GlyphItemFlags::IS_CLUSTER_START;
548 
549  if (bInCluster)
550  nGlyphFlags |= GlyphItemFlags::IS_IN_CLUSTER;
551 
552  sal_UCS4 aChar
553  = rArgs.mrStr.iterateCodePoints(&o3tl::temporary(sal_Int32(nCharPos)), 0);
554 
555  if (u_getIntPropertyValue(aChar, UCHAR_GENERAL_CATEGORY) == U_NON_SPACING_MARK)
556  nGlyphFlags |= GlyphItemFlags::IS_DIACRITIC;
557 
558  if (u_isUWhiteSpace(aChar))
559  nGlyphFlags |= GlyphItemFlags::IS_SPACING;
560 
561  if (aSubRun.maScript == HB_SCRIPT_ARABIC &&
562  HB_DIRECTION_IS_BACKWARD(aSubRun.maDirection) &&
563  !(nGlyphFlags & GlyphItemFlags::IS_SPACING))
564  {
565  nGlyphFlags |= GlyphItemFlags::ALLOW_KASHIDA;
567  }
568 
569  DeviceCoordinate nAdvance, nXOffset, nYOffset;
570  if (aSubRun.maDirection == HB_DIRECTION_TTB)
571  {
572  nGlyphFlags |= GlyphItemFlags::IS_VERTICAL;
573 
574  nAdvance = -pHbPositions[i].y_advance;
575  nXOffset = -pHbPositions[i].y_offset;
576  nYOffset = -pHbPositions[i].x_offset - nBaseOffset;
577  }
578  else
579  {
580  nAdvance = pHbPositions[i].x_advance;
581  nXOffset = pHbPositions[i].x_offset;
582  nYOffset = -pHbPositions[i].y_offset;
583  }
584 
585  nAdvance = std::lround(nAdvance * nXScale);
586  nXOffset = std::lround(nXOffset * nXScale);
587  nYOffset = std::lround(nYOffset * nYScale);
588 
589  DevicePoint aNewPos(aCurrPos.getX() + nXOffset, aCurrPos.getY() + nYOffset);
590  const GlyphItem aGI(nCharPos, nCharCount, nGlyphIndex, aNewPos, nGlyphFlags,
591  nAdvance, nXOffset);
592  m_GlyphItems.push_back(aGI);
593 
594  aCurrPos.adjustX(nAdvance);
595  }
596  }
597  }
598 
599  hb_buffer_destroy(pHbBuffer);
600 
601  // Some flags are set as a side effect of text layout, save them here.
604 
605  return true;
606 }
607 
608 void GenericSalLayout::GetCharWidths(std::vector<DeviceCoordinate>& rCharWidths) const
609 {
610  const int nCharCount = mnEndCharPos - mnMinCharPos;
611 
612  rCharWidths.clear();
613  rCharWidths.resize(nCharCount, 0);
614 
615  for (auto const& aGlyphItem : m_GlyphItems)
616  {
617  const int nIndex = aGlyphItem.charPos() - mnMinCharPos;
618  if (nIndex >= nCharCount)
619  continue;
620  rCharWidths[nIndex] += aGlyphItem.m_nNewWidth;
621  }
622 }
623 
624 // A note on how Kashida justification is implemented (because it took me 5
625 // years to figure it out):
626 // The decision to insert Kashidas, where and how much is taken by Writer.
627 // This decision is communicated to us in a very indirect way; by increasing
628 // the width of the character after which Kashidas should be inserted by the
629 // desired amount.
630 //
631 // Writer eventually calls IsKashidaPosValid() to check whether it can insert a
632 // Kashida between two characters or not.
633 //
634 // Here we do:
635 // - In LayoutText() set KashidaJustification flag based on text script.
636 // - In ApplyDXArray():
637 // * Check the above flag to decide whether to insert Kashidas or not.
638 // * For any RTL glyph that has DX adjustment, insert enough Kashidas to
639 // fill in the added space.
640 template<typename DC>
641 void GenericSalLayout::ApplyDXArray(const DC* pDXArray, SalLayoutFlags nLayoutFlags)
642 {
643  int nCharCount = mnEndCharPos - mnMinCharPos;
644  std::vector<DeviceCoordinate> aOldCharWidths;
645  std::unique_ptr<DC[]> const pNewCharWidths(new DC[nCharCount]);
646 
647  // Get the natural character widths (i.e. before applying DX adjustments).
648  GetCharWidths(aOldCharWidths);
649 
650  // Calculate the character widths after DX adjustments.
651  for (int i = 0; i < nCharCount; ++i)
652  {
653  if (i == 0)
654  pNewCharWidths[i] = pDXArray[i];
655  else
656  pNewCharWidths[i] = pDXArray[i] - pDXArray[i - 1];
657  }
658 
659  bool bKashidaJustify = false;
660  DeviceCoordinate nKashidaWidth = 0;
661  hb_codepoint_t nKashidaIndex = 0;
662  if (nLayoutFlags & SalLayoutFlags::KashidaJustification)
663  {
664  hb_font_t *pHbFont = GetFont().GetHbFont();
665  // Find Kashida glyph width and index.
666  if (hb_font_get_glyph(pHbFont, 0x0640, 0, &nKashidaIndex))
667  nKashidaWidth = GetFont().GetKashidaWidth();
668  bKashidaJustify = nKashidaWidth != 0;
669  }
670 
671  // Map of Kashida insertion points (in the glyph items vector) and the
672  // requested width.
673  std::map<size_t, DeviceCoordinate> pKashidas;
674 
675  // The accumulated difference in X position.
676  DC nDelta = 0;
677 
678  // Apply the DX adjustments to glyph positions and widths.
679  size_t i = 0;
680  while (i < m_GlyphItems.size())
681  {
682  // Accumulate the width difference for all characters corresponding to
683  // this glyph.
684  int nCharPos = m_GlyphItems[i].charPos() - mnMinCharPos;
685  DC nDiff = 0;
686  for (int j = 0; j < m_GlyphItems[i].charCount(); j++)
687  nDiff += pNewCharWidths[nCharPos + j] - aOldCharWidths[nCharPos + j];
688 
689  if (!m_GlyphItems[i].IsRTLGlyph())
690  {
691  // Adjust the width and position of the first (leftmost) glyph in
692  // the cluster.
693  m_GlyphItems[i].m_nNewWidth += nDiff;
694  m_GlyphItems[i].m_aLinearPos.adjustX(nDelta);
695 
696  // Adjust the position of the rest of the glyphs in the cluster.
697  while (++i < m_GlyphItems.size())
698  {
699  if (!m_GlyphItems[i].IsInCluster())
700  break;
701  m_GlyphItems[i].m_aLinearPos.adjustX(nDelta);
702  }
703  }
704  else if (m_GlyphItems[i].IsInCluster())
705  {
706  // RTL glyph in the middle of the cluster, will be handled in the
707  // loop below.
708  i++;
709  }
710  else
711  {
712  // Adjust the width and position of the first (rightmost) glyph in
713  // the cluster.
714  // For RTL, we put all the adjustment to the left of the glyph.
715  m_GlyphItems[i].m_nNewWidth += nDiff;
716  m_GlyphItems[i].m_aLinearPos.adjustX(nDelta + nDiff);
717 
718  // Adjust the X position of all glyphs in the cluster.
719  size_t j = i;
720  while (j > 0)
721  {
722  --j;
723  if (!m_GlyphItems[j].IsInCluster())
724  break;
725  m_GlyphItems[j].m_aLinearPos.adjustX(nDelta + nDiff);
726  }
727 
728  // If this glyph is Kashida-justifiable, then mark this as a
729  // Kashida position. Since this must be a RTL glyph, we mark the
730  // last glyph in the cluster not the first as this would be the
731  // base glyph.
732  if (bKashidaJustify && m_GlyphItems[i].AllowKashida() &&
733  nDiff > m_GlyphItems[i].charCount()) // Rounding errors, 1 pixel per character!
734  {
735  pKashidas[i] = nDiff;
736  // Move any non-spacing marks attached to this cluster as well.
737  // Looping backward because this is RTL glyph.
738  while (j > 0)
739  {
740  if (!m_GlyphItems[j].IsDiacritic())
741  break;
742  m_GlyphItems[j--].m_aLinearPos.adjustX(nDiff);
743  }
744  }
745  i++;
746  }
747 
748  // Increment the delta, the loop above makes sure we do so only once
749  // for every character (cluster) not for every glyph (otherwise we
750  // would apply it multiple times for each glyphs belonging to the same
751  // character which is wrong since DX adjustments are character based).
752  nDelta += nDiff;
753  }
754 
755  // Insert Kashida glyphs.
756  if (!bKashidaJustify || pKashidas.empty())
757  return;
758 
759  size_t nInserted = 0;
760  for (auto const& pKashida : pKashidas)
761  {
762  auto pGlyphIter = m_GlyphItems.begin() + nInserted + pKashida.first;
763 
764  // The total Kashida width.
765  DeviceCoordinate nTotalWidth = pKashida.second;
766 
767  // Number of times to repeat each Kashida.
768  int nCopies = 1;
769  if (nTotalWidth > nKashidaWidth)
770  nCopies = nTotalWidth / nKashidaWidth;
771 
772  // See if we can improve the fit by adding an extra Kashidas and
773  // squeezing them together a bit.
774  DeviceCoordinate nOverlap = 0;
775  DeviceCoordinate nShortfall = nTotalWidth - nKashidaWidth * nCopies;
776  if (nShortfall > 0)
777  {
778  ++nCopies;
779  DeviceCoordinate nExcess = nCopies * nKashidaWidth - nTotalWidth;
780  if (nExcess > 0)
781  nOverlap = nExcess / (nCopies - 1);
782  }
783 
784  DevicePoint aPos(pGlyphIter->m_aLinearPos.getX() - nTotalWidth, 0);
785  int nCharPos = pGlyphIter->charPos();
787  while (nCopies--)
788  {
789  GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, nKashidaWidth, 0);
790  pGlyphIter = m_GlyphItems.insert(pGlyphIter, aKashida);
791  aPos.adjustX(nKashidaWidth - nOverlap);
792  ++pGlyphIter;
793  ++nInserted;
794  }
795  }
796 }
797 
798 bool GenericSalLayout::IsKashidaPosValid(int nCharPos) const
799 {
800  for (auto pIter = m_GlyphItems.begin(); pIter != m_GlyphItems.end(); ++pIter)
801  {
802  if (pIter->charPos() == nCharPos)
803  {
804  // The position is the first glyph, this would happen if we
805  // changed the text styling in the middle of a word. Since we don’t
806  // do ligatures across layout engine instances, this can’t be a
807  // ligature so it should be fine.
808  if (pIter == m_GlyphItems.begin())
809  return true;
810 
811  // If the character is not supported by this layout, return false
812  // so that fallback layouts would be checked for it.
813  if (pIter->glyphId() == 0)
814  break;
815 
816  // Search backwards for previous glyph belonging to a different
817  // character. We are looking backwards because we are dealing with
818  // RTL glyphs, which will be in visual order.
819  for (auto pPrev = pIter - 1; pPrev != m_GlyphItems.begin(); --pPrev)
820  {
821  if (pPrev->charPos() != nCharPos)
822  {
823  // Check if the found glyph belongs to the next character,
824  // otherwise the current glyph will be a ligature which is
825  // invalid kashida position.
826  if (pPrev->charPos() == (nCharPos + 1))
827  return true;
828  break;
829  }
830  }
831  }
832  }
833 
834  return false;
835 }
836 
837 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
GenericSalLayout(LogicalFontInstance &)
OUString const & getLanguage() const
double getY() const
sal_Int32 nIndex
void GetCharWidths(std::vector< DeviceCoordinate > &rCharWidths) const
static hb_unicode_funcs_t * getUnicodeFuncs()
vcl::text::TextLayoutCache const * m_pTextLayoutCache
std::vector< vcl::text::Run > runs
virtual void DrawTextLayout(const GenericSalLayout &)=0
hb_set_t * mpVertGlyphs
Definition: sallayout.hxx:159
sal_uInt32 sal_UCS4
Definition: vclenum.hxx:192
size_t mnEnd
static const uint8_t sVerticalOrientationValues[34][128]
bool LayoutText(vcl::text::ImplLayoutArgs &, const SalLayoutGlyphsImpl *) final override
const OUString & getBcp47(bool bResolveSystem=true) const
void Justify(DeviceCoordinate nNewWidth)
Definition: sallayout.cxx:292
hb_font_t * GetHbFont()
void SetFlags(SalLayoutFlags flags)
sal_Int32 DeviceCoordinate
bool GetNextRun(int *nMinRunPos, int *nEndRunPos, bool *bRTL)
LogicalFontInstance & GetFont() const
Definition: sallayout.hxx:121
const css::lang::Locale & getLocale(bool bResolveSystem=true) const
void AppendImpl(SalLayoutGlyphsImpl *pImpl)
const DeviceCoordinate * mpDXArray
GlyphItemFlags
sal_uInt16 sal_Unicode
bool HasVerticalAlternate(sal_UCS4 aChar, sal_UCS4 aNextChar)
OUString getLanguage() const
const vcl::font::FontSelectPattern & GetFontSelectPattern() const
void GetScale(double *nXScale, double *nYScale)
~GenericSalLayout() override
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
void ApplyAsianKerning(const OUString &rStr)
Definition: sallayout.cxx:411
VerticalOrientation
SalLayoutGlyphsImpl m_GlyphItems
Definition: sallayout.hxx:154
virtual void AdjustLayout(vcl::text::ImplLayoutArgs &)
Definition: sallayout.cxx:145
int mnEndCharPos
Definition: vcllayout.hxx:116
int i
css::drawing::Direction3D aDirection
SalLayoutFlags GetFlags() const
PITCH_FIXED
OString msLanguage
Definition: sallayout.hxx:156
static const uint8_t sVerticalOrientationPages[4][512]
static std::shared_ptr< vcl::text::TextLayoutCache > CreateTextLayoutCache(OUString const &)
static const uint8_t sVerticalOrientationPlanes[16]
std::vector< FeatureSetting > const & getFeatures() const
static unsigned int unicodeDecomposeCompatibility(hb_unicode_funcs_t *, hb_codepoint_t, hb_codepoint_t *, void *)
void ApplyDXArray(const DC *, SalLayoutFlags nLayoutFlags)
const double * mpAltNaturalDXArray
constexpr T & temporary(T &&x)
void AdjustLayout(vcl::text::ImplLayoutArgs &) final override
SalLayoutGlyphs GetGlyphs() const final override
std::vector< hb_feature_t > maFeatures
Definition: sallayout.hxx:157
const bool mbFuzzing
Definition: sallayout.hxx:160
#define kVerticalOrientationMaxPlane
#define SAL_INFO(area, stream)
void SetNeedFallback(vcl::text::ImplLayoutArgs &, sal_Int32, bool)
void DrawText(SalGraphics &) const final override
css::uno::Reference< css::i18n::XBreakIterator > mxBreak
Definition: sallayout.hxx:152
bool IsKashidaPosValid(int nCharPos) const final override
#define SAL_WARN(area, stream)
double getX() const
void AddFallbackRun(int nMinRunPos, int nEndRunPos, bool bRTL)
int mnMinCharPos
Definition: vcllayout.hxx:115
sal_Int32 nLength
FontPitch GetPitch() const
VCL_DLLPUBLIC css::uno::Reference< css::i18n::XBreakIterator > CreateBreakIterator()
Definition: unohelp.cxx:37
SalLayoutGlyphsImpl * clone() const
DeviceCoordinate mnLayoutWidth
void ParseFeatures(const OUString &name)
#define kVerticalOrientationCharBits
void adjustX(double fX)
SalLayoutFlags
typedef void(CALLTYPE *GetFuncDataPtr)(sal_uInt16 &nNo