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