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