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