LibreOffice Module vcl (master)  1
impglyphitem.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 <impglyphitem.hxx>
21 #include <vcl/glyphitemcache.hxx>
22 #include <vcl/vcllayout.hxx>
23 #include <vcl/lazydelete.hxx>
24 #include <tools/stream.hxx>
25 #include <unotools/configmgr.hxx>
26 #include <TextLayoutCache.hxx>
27 #include <officecfg/Office/Common.hxx>
28 
29 // These need being explicit because of SalLayoutGlyphsImpl being private in vcl.
31 
33 
35 {
36  std::swap(m_pImpl, rOther.m_pImpl);
37  std::swap(m_pExtraImpls, rOther.m_pExtraImpls);
38 }
39 
41 {
42  if (this != &rOther)
43  {
44  std::swap(m_pImpl, rOther.m_pImpl);
45  std::swap(m_pExtraImpls, rOther.m_pExtraImpls);
46  }
47  return *this;
48 }
49 
51 {
52  if (m_pImpl == nullptr)
53  return false;
54  if (!m_pImpl->IsValid())
55  return false;
56  if (m_pExtraImpls)
57  for (std::unique_ptr<SalLayoutGlyphsImpl> const& impl : *m_pExtraImpls)
58  if (!impl->IsValid())
59  return false;
60  return true;
61 }
62 
64 {
65  // Invalidating is in fact simply clearing.
66  m_pImpl.reset();
67  m_pExtraImpls.reset();
68 }
69 
70 SalLayoutGlyphsImpl* SalLayoutGlyphs::Impl(unsigned int nLevel) const
71 {
72  if (nLevel == 0)
73  return m_pImpl.get();
74  if (m_pExtraImpls != nullptr && nLevel - 1 < m_pExtraImpls->size())
75  return (*m_pExtraImpls)[nLevel - 1].get();
76  return nullptr;
77 }
78 
80 {
81  if (!m_pImpl)
82  m_pImpl.reset(pImpl);
83  else
84  {
85  if (!m_pExtraImpls)
86  m_pExtraImpls.reset(new std::vector<std::unique_ptr<SalLayoutGlyphsImpl>>);
87  m_pExtraImpls->emplace_back(pImpl);
88  }
89 }
90 
92 
93 // Clone, but only glyphs in the given range in the original text string.
94 // It is possible the given range may not be cloned, in which case this returns nullptr.
95 SalLayoutGlyphsImpl* SalLayoutGlyphsImpl::cloneCharRange(sal_Int32 index, sal_Int32 length) const
96 {
97  std::unique_ptr<SalLayoutGlyphsImpl> copy(new SalLayoutGlyphsImpl(*GetFont()));
98  copy->SetFlags(GetFlags());
99  if (empty())
100  return copy.release();
101  copy->reserve(std::min<size_t>(size(), length));
102  sal_Int32 beginPos = index;
103  sal_Int32 endPos = index + length;
104  const_iterator pos;
105  bool rtl = front().IsRTLGlyph();
106  if (rtl)
107  {
108  // Glyphs are in reverse order for RTL.
109  beginPos = index + length - 1;
110  endPos = index - 1;
111  // Skip glyphs that are in the string after the given index, i.e. are before the glyphs
112  // we want.
113  pos = std::partition_point(
114  begin(), end(), [beginPos](const GlyphItem& it) { return it.charPos() > beginPos; });
115  }
116  else
117  {
118  // Skip glyphs that are in the string before the given index (glyphs are sorted by charPos()).
119  pos = std::partition_point(
120  begin(), end(), [beginPos](const GlyphItem& it) { return it.charPos() < beginPos; });
121  }
122  if (pos == end())
123  return nullptr;
124  // Require a start at the exact position given, otherwise bail out.
125  if (pos->charPos() != beginPos)
126  return nullptr;
127  // For RTL make sure we're not cutting in the middle of a multi-character glyph
128  // (for non-RTL charPos is always the start of a multi-character glyph).
129  if (rtl && pos->charPos() + pos->charCount() > beginPos + 1)
130  return nullptr;
131  if (!isSafeToBreak(pos, rtl))
132  return nullptr;
133  // LinearPos needs adjusting to start at xOffset/yOffset for the first item,
134  // that's how it's computed in GenericSalLayout::LayoutText().
135  DevicePoint zeroPoint = pos->linearPos() - DevicePoint(pos->xOffset(), pos->yOffset());
136  // Add and adjust all glyphs until the given length.
137  // The check is written as 'charPos + charCount <= endPos' rather than 'charPos < endPos'
138  // (or similarly for RTL) to make sure we include complete glyphs. If a glyph is composed
139  // from several characters, we should not cut in the middle of those characters, so this
140  // checks the glyph is entirely in the given character range. If it is not, this will end
141  // the loop and the later 'pos->charPos() != endPos' check will fail and bail out.
142  // CppunitTest_sw_layoutwriter's testCombiningCharacterCursorPosition would fail without this.
143  while (pos != end()
144  && (rtl ? pos->charPos() - pos->charCount() >= endPos
145  : pos->charPos() + pos->charCount() <= endPos))
146  {
147  if (pos->IsRTLGlyph() != rtl)
148  return nullptr; // Don't mix RTL and non-RTL runs.
149  // HACK: When running CppunitTest_sw_uiwriter3's testTdf104649 on Mac there's glyph
150  // with id 1232 that has 0 charCount, 0 origWidth and inconsistent xOffset (sometimes 0,
151  // but sometimes not). Possibly font or Harfbuzz bug? It's extremely rare, so simply bail out.
152  if (pos->charCount() == 0 && pos->origWidth() == 0)
153  return nullptr;
154  copy->push_back(*pos);
155  copy->back().setLinearPos(copy->back().linearPos() - zeroPoint);
156  ++pos;
157  }
158  if (pos != end())
159  {
160  // Fail if the next character is not at the expected past-end position. For RTL check
161  // that we're not cutting in the middle of a multi-character glyph.
162  if (rtl ? pos->charPos() + pos->charCount() != endPos + 1 : pos->charPos() != endPos)
163  return nullptr;
164  if (!isSafeToBreak(pos, rtl))
165  return nullptr;
166  }
167  // HACK: If mode is se to be RTL, but the last glyph is a non-RTL space,
168  // then making a subset would give a different result than the actual layout,
169  // because the weak BiDi mode code in ImplLayoutArgs ctor would interpret
170  // the string subset ending with space as the space being RTL, but it would
171  // treat it as non-RTL for the whole string if there would be more non-RTL
172  // characters after the space. So bail out.
173  if (GetFlags() & SalLayoutFlags::BiDiRtl && !rtl && !copy->empty() && copy->back().IsSpacing())
174  {
175  return nullptr;
176  }
177  return copy.release();
178 }
179 
180 bool SalLayoutGlyphsImpl::isSafeToBreak(const_iterator pos, bool rtl) const
181 {
182  if (rtl)
183  {
184  // RTL is more complicated, because HB_GLYPH_FLAG_UNSAFE_TO_BREAK talks about beginning
185  // of a cluster, which refers to the text, not glyphs. This function is called
186  // for the first glyph of the subset and the first glyph after the subset, but since
187  // the glyphs are backwards, and we need the beginning of cluster at the start of the text
188  // and beginning of the cluster after the text, we need to check glyphs before this position.
189  if (pos == begin())
190  return true;
191  --pos;
192  while (pos >= begin() && (pos->IsInCluster() && !pos->IsClusterStart()))
193  --pos;
194  if (pos < begin())
195  return true;
196  }
197  // Don't create a subset if it's not safe to break at the beginning or end of the sequence
198  // (https://harfbuzz.github.io/harfbuzz-hb-buffer.html#hb-glyph-flags-t).
199  if (pos->IsUnsafeToBreak() || (pos->IsInCluster() && !pos->IsClusterStart()))
200  return false;
201  return true;
202 }
203 
204 #ifdef DBG_UTIL
206 {
207  if (!GetFont()->mxFontMetric->CompareDeviceIndependentFontAttributes(
208  *other->GetFont()->mxFontMetric))
209  return false;
210  if (GetFlags() != other->GetFlags())
211  return false;
212  if (empty() || other->empty())
213  return empty() == other->empty();
214  if (size() != other->size())
215  return false;
216  for (size_t pos = 0; pos < size(); ++pos)
217  {
218  if ((*this)[pos] != (*other)[pos])
219  return false;
220  }
221  return true;
222 }
223 #endif
224 
226 {
227  if (!m_rFontInstance.is())
228  return false;
229  return true;
230 }
231 
233 {
236  ? officecfg::Office::Common::Cache::Font::GlyphsCacheSize::get()
237  : 20000);
238  return cache.get();
239 }
240 
242  const OutputDevice* outputDevice, std::u16string_view text,
243  sal_Int32 index, sal_Int32 len)
244 {
245  SalLayoutGlyphs ret;
246  for (int level = 0;; ++level)
247  {
248  const SalLayoutGlyphsImpl* sourceLevel = source.Impl(level);
249  if (sourceLevel == nullptr)
250  break;
251  SalLayoutGlyphsImpl* cloned = sourceLevel->cloneCharRange(index, len);
252  // If the glyphs range cannot be cloned, bail out.
253  if (cloned == nullptr)
254  return SalLayoutGlyphs();
255  // If the entire string is mixed LTR/RTL but the subset is only LTR,
256  // then make sure the flags match that, otherwise checkGlyphsEqual()
257  // would assert on flags being different.
258  cloned->SetFlags(cloned->GetFlags()
259  | outputDevice->GetBiDiLayoutFlags(text, index, index + len));
260  // SalLayoutFlags::KashidaJustification is set only if any glyph
261  // in the range has GlyphItemFlags::ALLOW_KASHIDA (otherwise unset it).
263  {
264  bool hasKashida = false;
265  for (const GlyphItem& item : *cloned)
266  {
267  if (item.AllowKashida())
268  {
269  hasKashida = true;
270  break;
271  }
272  }
273  if (!hasKashida)
274  cloned->SetFlags(cloned->GetFlags() & ~SalLayoutFlags::KashidaJustification);
275  }
276 #ifdef DBG_UTIL
277  else
278  for (const GlyphItem& item : *cloned)
279  assert(!item.AllowKashida());
280 #endif
281  ret.AppendImpl(cloned);
282  }
283  return ret;
284 }
285 
286 #ifdef DBG_UTIL
287 static void checkGlyphsEqual(const SalLayoutGlyphs& g1, const SalLayoutGlyphs& g2)
288 {
289  for (int level = 0;; ++level)
290  {
291  const SalLayoutGlyphsImpl* l1 = g1.Impl(level);
292  const SalLayoutGlyphsImpl* l2 = g2.Impl(level);
293  if (l1 == nullptr || l2 == nullptr)
294  {
295  assert(l1 == l2);
296  break;
297  }
298  assert(l1->isEqual(l2));
299  }
300 }
301 #endif
302 
303 const SalLayoutGlyphs*
305  sal_Int32 nIndex, sal_Int32 nLen, tools::Long nLogicWidth,
306  const vcl::text::TextLayoutCache* layoutCache)
307 {
308  if (nLen == 0)
309  return nullptr;
310  const CachedGlyphsKey key(outputDevice, text, nIndex, nLen, nLogicWidth);
312  if (it != mCachedGlyphs.end())
313  {
314  if (it->second.IsValid())
315  return &it->second;
316  // Do not try to create the layout here. If a cache item exists, it's already
317  // been attempted and the layout was invalid (this happens with MultiSalLayout).
318  // So in that case this is a cached failure.
319  return nullptr;
320  }
321  const SalLayoutFlags glyphItemsOnlyLayout = SalLayoutFlags::GlyphItemsOnly;
322  bool resetLastSubstringKey = true;
323  const sal_Unicode nbSpace = 0xa0; // non-breaking space
324  if (nIndex != 0 || nLen != text.getLength())
325  {
326  // The glyphs functions are often called first for an entire string
327  // and then with an increasing starting index until the end of the string.
328  // Which means it's possible to get the glyphs faster by just copying
329  // a subset of the full glyphs and adjusting as necessary.
330  if (mLastTemporaryKey.has_value() && mLastTemporaryKey == key)
331  return &mLastTemporaryGlyphs;
332  const CachedGlyphsKey keyWhole(outputDevice, text, 0, text.getLength(), nLogicWidth);
333  GlyphsCache::const_iterator itWhole = mCachedGlyphs.find(keyWhole);
334  if (itWhole == mCachedGlyphs.end())
335  {
336  // This function may often be called repeatedly for segments of the same string,
337  // in which case it is more efficient to cache glyphs for the entire string
338  // and then return subsets of them. So if a second call either starts at the same
339  // position or starts at the end of the previous call, cache the entire string.
340  // This used to do this only for the first two segments of the string,
341  // but that missed the case when the font slightly changed e.g. because of the first
342  // part being underlined. Doing this for any two segments allows this optimization
343  // even when the prefix of the string would use a different font.
344  // TODO: Can those font differences be ignored?
345  // Writer layouts tests enable SAL_ABORT_ON_NON_APPLICATION_FONT_USE in order
346  // to make PrintFontManager::Substitute() abort if font fallback happens. When
347  // laying out the entire string the chance this happens increases (e.g. testAbi11870
348  // normally calls this function only for a part of a string, but this optimization
349  // lays out the entire string and causes a fallback). Since this optimization
350  // does not change result of this function, simply disable it for those tests.
351  static bool bAbortOnFontSubstitute
352  = getenv("SAL_ABORT_ON_NON_APPLICATION_FONT_USE") != nullptr;
353  if (mLastSubstringKey.has_value() && !bAbortOnFontSubstitute)
354  {
355  sal_Int32 pos = nIndex;
356  if (mLastSubstringKey->len < pos && text[pos - 1] == nbSpace)
357  --pos; // Writer skips a non-breaking space, so skip that character too.
358  if ((mLastSubstringKey->len == pos || mLastSubstringKey->index == nIndex)
360  == CachedGlyphsKey(outputDevice, text, mLastSubstringKey->index,
361  mLastSubstringKey->len, nLogicWidth))
362  {
363  GetLayoutGlyphs(outputDevice, text, 0, text.getLength(), nLogicWidth,
364  layoutCache);
365  itWhole = mCachedGlyphs.find(keyWhole);
366  }
367  else
368  mLastSubstringKey.reset();
369  }
370  if (!mLastSubstringKey.has_value())
371  {
372  mLastSubstringKey = key;
373  resetLastSubstringKey = false;
374  }
375  }
376  if (itWhole != mCachedGlyphs.end() && itWhole->second.IsValid())
377  {
378  mLastSubstringKey.reset();
380  = makeGlyphsSubset(itWhole->second, outputDevice, text, nIndex, nLen);
382  {
383  mLastTemporaryKey = std::move(key);
384 #ifdef DBG_UTIL
385  std::shared_ptr<const vcl::text::TextLayoutCache> tmpLayoutCache;
386  if (layoutCache == nullptr)
387  {
388  tmpLayoutCache = vcl::text::TextLayoutCache::Create(text);
389  layoutCache = tmpLayoutCache.get();
390  }
391  // Check if the subset result really matches what we would get normally,
392  // to make sure corner cases are handled well (see SalLayoutGlyphsImpl::cloneCharRange()).
393  std::unique_ptr<SalLayout> layout
394  = outputDevice->ImplLayout(text, nIndex, nLen, Point(0, 0), nLogicWidth, {},
395  glyphItemsOnlyLayout, layoutCache);
396  assert(layout);
397  checkGlyphsEqual(mLastTemporaryGlyphs, layout->GetGlyphs());
398 #endif
399  return &mLastTemporaryGlyphs;
400  }
401  }
402  }
403  if (resetLastSubstringKey)
404  {
405  // Writer does non-breaking space differently (not as part of the string), so in that
406  // case ignore that call and still allow finding two adjacent substrings that have
407  // the non-breaking space between them.
408  if (nLen != 1 || text[nIndex] != nbSpace)
409  mLastSubstringKey.reset();
410  }
411 
412  std::shared_ptr<const vcl::text::TextLayoutCache> tmpLayoutCache;
413  if (layoutCache == nullptr)
414  {
415  tmpLayoutCache = vcl::text::TextLayoutCache::Create(text);
416  layoutCache = tmpLayoutCache.get();
417  }
418  std::unique_ptr<SalLayout> layout = outputDevice->ImplLayout(
419  text, nIndex, nLen, Point(0, 0), nLogicWidth, {}, glyphItemsOnlyLayout, layoutCache);
420  if (layout)
421  {
422  SalLayoutGlyphs glyphs = layout->GetGlyphs();
423  if (glyphs.IsValid())
424  {
425  // TODO: Fallbacks do not work reliably (fallback font not included in the key),
426  // so do not cache (but still return once, using the temporary without a key set).
427  if (glyphs.Impl(1) != nullptr)
428  {
429  mLastTemporaryGlyphs = std::move(glyphs);
430  mLastTemporaryKey.reset();
431  return &mLastTemporaryGlyphs;
432  }
433  mCachedGlyphs.insert(std::make_pair(key, layout->GetGlyphs()));
434  assert(mCachedGlyphs.find(key)
435  == mCachedGlyphs.begin()); // newly inserted item is first
436  return &mCachedGlyphs.begin()->second;
437  }
438  }
439  // Failure, cache it too as invalid glyphs.
440  mCachedGlyphs.insert(std::make_pair(key, SalLayoutGlyphs()));
441  return nullptr;
442 }
443 
445  const VclPtr<const OutputDevice>& outputDevice, const OUString& t, sal_Int32 i, sal_Int32 l,
446  tools::Long w)
447  : text(t)
448  , index(i)
449  , len(l)
450  , logicWidth(w)
451  // we also need to save things used in OutputDevice::ImplPrepareLayoutArgs(), in case they
452  // change in the output device, plus mapMode affects the sizes.
453  , fontMetric(outputDevice->GetFontMetric())
454  // TODO It would be possible to get a better hit ratio if mapMode wasn't part of the key
455  // and results that differ only in mapmode would have coordinates adjusted based on that.
456  // That would occasionally lead to rounding errors (at least differences that would
457  // make checkGlyphsEqual() fail).
458  , mapMode(outputDevice->GetMapMode())
459  , rtl(outputDevice->IsRTLEnabled())
460  , layoutMode(outputDevice->GetLayoutMode())
461  , digitLanguage(outputDevice->GetDigitLanguage())
462 {
463  const LogicalFontInstance* fi = outputDevice->GetFontInstance();
465 
466  hashValue = 0;
471  o3tl::hash_combine(hashValue, outputDevice.get());
472  // Need to use IgnoreColor, because sometimes the color changes, but it's irrelevant
473  // for text layout (and also obsolete in vcl::Font).
475  // For some reason font scale may differ even if vcl::Font is the same,
476  // so explicitly check it too.
483 }
484 
486 {
487  return hashValue == other.hashValue && index == other.index && len == other.len
488  && logicWidth == other.logicWidth && mapMode == other.mapMode && rtl == other.rtl
489  && layoutMode == other.layoutMode && digitLanguage == other.digitLanguage
490  && fontScaleX == other.fontScaleX && fontScaleY == other.fontScaleY
491  && fontMetric.EqualIgnoreColor(other.fontMetric)
493  // Slower things last in the comparison.
494 }
495 
497 {
498  size_t cost = 0;
499  for (int level = 0;; ++level)
500  {
501  const SalLayoutGlyphsImpl* impl = glyphs.Impl(level);
502  if (impl == nullptr)
503  break;
504  // Count size in bytes, both the SalLayoutGlyphsImpl instance and contained GlyphItem's.
505  cost += sizeof(*impl);
506  cost += impl->size() * sizeof(impl->front());
507  }
508  return cost;
509 }
510 
511 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
const SalLayoutGlyphs * GetLayoutGlyphs(VclPtr< const OutputDevice > outputDevice, const OUString &text, const vcl::text::TextLayoutCache *layoutCache=nullptr)
void insert(key_value_pair_t &rPair)
SalLayoutGlyphsImpl * cloneCharRange(sal_Int32 index, sal_Int32 length) const
long Long
static void checkGlyphsEqual(const SalLayoutGlyphs &g1, const SalLayoutGlyphs &g2)
bool isEqual(const SalLayoutGlyphsImpl *other) const
size_t GetHashValue() const
Definition: mapmod.cxx:139
void SetFlags(SalLayoutFlags flags)
std::unique_ptr< std::vector< std::unique_ptr< SalLayoutGlyphsImpl > > > m_pExtraImpls
Definition: glyphitem.hxx:37
SAL_DLLPRIVATE const LogicalFontInstance * GetFontInstance() const
SAL_DLLPRIVATE SalLayoutFlags GetBiDiLayoutFlags(std::u16string_view rStr, const sal_Int32 nMinIndex, const sal_Int32 nEndIndex) const
Definition: text.cxx:1227
bool IsValid() const
void AppendImpl(SalLayoutGlyphsImpl *pImpl)
list_const_iterator_t find(const Key &key)
sal_uInt16 sal_Unicode
enumrange< T >::Iterator begin(enumrange< T >)
size_t pos
static bool IsFuzzing()
void GetScale(double *nXScale, double *nYScale) const
bool IsValid() const
bool isSafeToBreak(const_iterator pos, bool rtl) const
exports com.sun.star. text
UNDERLYING_TYPE get() const
size_t GetHashValueIgnoreColor() const
Definition: fontmetric.cxx:132
static SalLayoutGlyphsCache * self()
SalLayoutFlags GetFlags() const
std::optional< CachedGlyphsKey > mLastTemporaryKey
Some things multiple-inherit from VclAbstractDialog and OutputDevice, so we need to use virtual inher...
Definition: outdev.hxx:175
SalLayoutGlyphsImpl(LogicalFontInstance &rFontInstance)
size
std::enable_if_t<(sizeof(N)==4)> hash_combine(N &nSeed, T const *pValue, size_t nCount)
tuple index
SalLayoutGlyphs mLastTemporaryGlyphs
rtl::Reference< LogicalFontInstance > m_rFontInstance
const rtl::Reference< LogicalFontInstance > & GetFont() const
std::optional< CachedGlyphsKey > mLastSubstringKey
enumrange< T >::Iterator end(enumrange< T >)
int charPos() const
CachedGlyphsKey(const VclPtr< const OutputDevice > &dev, const OUString &t, sal_Int32 i, sal_Int32 l, tools::Long w)
basegfx::B2DPoint DevicePoint
SalLayoutGlyphs & operator=(const SalLayoutGlyphs &)=delete
SalLayoutGlyphsImpl * Impl(unsigned int nLevel) const
static SalLayoutGlyphs makeGlyphsSubset(const SalLayoutGlyphs &source, const OutputDevice *outputDevice, std::u16string_view text, sal_Int32 index, sal_Int32 len)
list_const_iterator_t end() const
void copy(const fs::path &src, const fs::path &dest)
bool operator==(const CachedGlyphsKey &other) const
reference_type * get() const
Get the body.
Definition: vclptr.hxx:143
std::unique_ptr< SalLayout > ImplLayout(const OUString &, sal_Int32 nIndex, sal_Int32 nLen, const Point &rLogicPos=Point(0, 0), tools::Long nLogicWidth=0, o3tl::span< const sal_Int32 > pLogicDXArray={}, SalLayoutFlags flags=SalLayoutFlags::NONE, vcl::text::TextLayoutCache const *=nullptr, const SalLayoutGlyphs *pGlyphs=nullptr) const
Definition: text.cxx:1308
list_const_iterator_t begin() const
static std::shared_ptr< const vcl::text::TextLayoutCache > Create(OUString const &)
A cache for SalLayoutGlyphs objects.
size_t operator()(const SalLayoutGlyphs &) const
vcl::text::ComplexTextLayoutFlags layoutMode
SalLayoutGlyphsImpl * clone() const
std::unique_ptr< SalLayoutGlyphsImpl > m_pImpl
Definition: glyphitem.hxx:33
SalLayoutFlags