1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <impglyphitem.hxx>
22 #include <vcl/glyphitemcache.hxx>
23 #include <vcl/vcllayout.hxx>
24 #include <tools/lazydelete.hxx>
25 #include <tools/stream.hxx>
26 #include <unotools/configmgr.hxx>
27 #include <TextLayoutCache.hxx>
28 #include <officecfg/Office/Common.hxx>
29 #include <o3tl/string_view.hxx>
31 #include <unicode/ubidi.h>
32 #include <unicode/uchar.h>
34 // These need being explicit because of SalLayoutGlyphsImpl being private in vcl.
35 SalLayoutGlyphs::SalLayoutGlyphs() {}
37 SalLayoutGlyphs::~SalLayoutGlyphs() {}
39 SalLayoutGlyphs::SalLayoutGlyphs(SalLayoutGlyphs
&& rOther
) noexcept
41 std::swap(m_pImpl
, rOther
.m_pImpl
);
42 std::swap(m_pExtraImpls
, rOther
.m_pExtraImpls
);
45 SalLayoutGlyphs
& SalLayoutGlyphs::operator=(SalLayoutGlyphs
&& rOther
) noexcept
49 std::swap(m_pImpl
, rOther
.m_pImpl
);
50 std::swap(m_pExtraImpls
, rOther
.m_pExtraImpls
);
55 bool SalLayoutGlyphs::IsValid() const
57 if (m_pImpl
== nullptr)
59 if (!m_pImpl
->IsValid())
62 for (std::unique_ptr
<SalLayoutGlyphsImpl
> const& impl
: *m_pExtraImpls
)
68 void SalLayoutGlyphs::Invalidate()
70 // Invalidating is in fact simply clearing.
72 m_pExtraImpls
.reset();
75 SalLayoutGlyphsImpl
* SalLayoutGlyphs::Impl(unsigned int nLevel
) const
79 if (m_pExtraImpls
!= nullptr && nLevel
- 1 < m_pExtraImpls
->size())
80 return (*m_pExtraImpls
)[nLevel
- 1].get();
84 void SalLayoutGlyphs::AppendImpl(SalLayoutGlyphsImpl
* pImpl
)
91 m_pExtraImpls
.reset(new std::vector
<std::unique_ptr
<SalLayoutGlyphsImpl
>>);
92 m_pExtraImpls
->emplace_back(pImpl
);
96 SalLayoutGlyphsImpl
* SalLayoutGlyphsImpl::clone() const { return new SalLayoutGlyphsImpl(*this); }
98 // Clone, but only glyphs in the given range in the original text string.
99 // It is possible the given range may not be cloned, in which case this returns nullptr.
100 SalLayoutGlyphsImpl
* SalLayoutGlyphsImpl::cloneCharRange(sal_Int32 index
, sal_Int32 length
) const
102 std::unique_ptr
<SalLayoutGlyphsImpl
> copy(new SalLayoutGlyphsImpl(*GetFont()));
103 copy
->SetFlags(GetFlags());
105 return copy
.release();
106 bool rtl
= front().IsRTLGlyph();
107 // Avoid mixing LTR/RTL or layouts that do not have it set explicitly (BiDiStrong). Otherwise
108 // the subset may not quite match what would a real layout call give (e.g. some characters with neutral
109 // direction such as space might have different LTR/RTL flag). It seems bailing out here mostly
110 // avoid relatively rare corner cases and doesn't matter for performance.
111 // This is also checked in SalLayoutGlyphsCache::GetLayoutGlyphs() below.
112 if (!(GetFlags() & SalLayoutFlags::BiDiStrong
)
113 || rtl
!= bool(GetFlags() & SalLayoutFlags::BiDiRtl
))
115 copy
->reserve(std::min
<size_t>(size(), length
));
116 sal_Int32 beginPos
= index
;
117 sal_Int32 endPos
= index
+ length
;
121 // Glyphs are in reverse order for RTL.
122 beginPos
= index
+ length
- 1;
124 // Skip glyphs that are in the string after the given index, i.e. are before the glyphs
126 pos
= std::partition_point(
127 begin(), end(), [beginPos
](const GlyphItem
& it
) { return it
.charPos() > beginPos
; });
131 // Skip glyphs that are in the string before the given index (glyphs are sorted by charPos()).
132 pos
= std::partition_point(
133 begin(), end(), [beginPos
](const GlyphItem
& it
) { return it
.charPos() < beginPos
; });
137 // Require a start at the exact position given, otherwise bail out.
138 if (pos
->charPos() != beginPos
)
140 // For RTL make sure we're not cutting in the middle of a multi-character glyph,
141 // or in the middle of a cluster
142 // (for non-RTL charPos is always the start of a multi-character glyph).
143 if (rtl
&& (pos
->charPos() + pos
->charCount() > beginPos
+ 1 || pos
->IsInCluster()))
145 if (!isSafeToBreak(pos
, rtl
))
147 // LinearPos needs adjusting to start at xOffset/yOffset for the first item,
148 // that's how it's computed in GenericSalLayout::LayoutText().
149 basegfx::B2DPoint zeroPoint
150 = pos
->linearPos() - basegfx::B2DPoint(pos
->xOffset(), pos
->yOffset());
151 // Add and adjust all glyphs until the given length.
152 // The check is written as 'charPos + charCount <= endPos' rather than 'charPos < endPos'
153 // (or similarly for RTL) to make sure we include complete glyphs. If a glyph is composed
154 // from several characters, we should not cut in the middle of those characters, so this
155 // checks the glyph is entirely in the given character range. If it is not, this will end
156 // the loop and the later 'pos->charPos() != endPos' check will fail and bail out.
157 // CppunitTest_sw_layoutwriter's testCombiningCharacterCursorPosition would fail without this.
159 && (rtl
? pos
->charPos() - pos
->charCount() >= endPos
160 : pos
->charPos() + pos
->charCount() <= endPos
))
162 if (pos
->IsRTLGlyph() != rtl
)
163 return nullptr; // Don't mix RTL and non-RTL runs.
164 // HACK: When running CppunitTest_sw_uiwriter3's testTdf104649 on Mac there's glyph
165 // with id 1232 that has 0 charCount, 0 origWidth and inconsistent xOffset (sometimes 0,
166 // but sometimes not). Possibly font or Harfbuzz bug? It's extremely rare, so simply bail out.
167 if (pos
->charCount() == 0 && pos
->origWidth() == 0)
169 copy
->push_back(*pos
);
170 copy
->back().setLinearPos(copy
->back().linearPos() - zeroPoint
);
175 // Fail if the next character is not at the expected past-end position. For RTL check
176 // that we're not cutting in the middle of a multi-character glyph.
177 if (rtl
? pos
->charPos() + pos
->charCount() != endPos
+ 1 : pos
->charPos() != endPos
)
179 if (!isSafeToBreak(pos
, rtl
))
182 return copy
.release();
185 bool SalLayoutGlyphsImpl::isSafeToBreak(const_iterator pos
, bool rtl
) const
189 // RTL is more complicated, because HB_GLYPH_FLAG_UNSAFE_TO_BREAK talks about beginning
190 // of a cluster, which refers to the text, not glyphs. This function is called
191 // for the first glyph of the subset and the first glyph after the subset, but since
192 // the glyphs are backwards, and we need the beginning of cluster at the start of the text
193 // and beginning of the cluster after the text, we need to check glyphs before this position.
198 // Don't create a subset if it's not safe to break at the beginning or end of the sequence
199 // (https://harfbuzz.github.io/harfbuzz-hb-buffer.html#hb-glyph-flags-t).
200 if (pos
->IsUnsafeToBreak() || (pos
->IsInCluster() && !pos
->IsClusterStart()))
206 bool SalLayoutGlyphsImpl::isLayoutEquivalent(const SalLayoutGlyphsImpl
* other
) const
208 if (!GetFont()->mxFontMetric
->CompareDeviceIndependentFontAttributes(
209 *other
->GetFont()->mxFontMetric
))
211 if (GetFlags() != other
->GetFlags())
213 if (empty() || other
->empty())
214 return empty() == other
->empty();
215 if (size() != other
->size())
217 for (size_t pos
= 0; pos
< size(); ++pos
)
219 if (!(*this)[pos
].isLayoutEquivalent((*other
)[pos
]))
226 bool SalLayoutGlyphsImpl::IsValid() const
228 if (!m_rFontInstance
.is())
233 void SalLayoutGlyphsCache::clear() { mCachedGlyphs
.clear(); }
235 SalLayoutGlyphsCache
* SalLayoutGlyphsCache::self()
237 static tools::DeleteOnDeinit
<SalLayoutGlyphsCache
> cache(
238 !comphelper::IsFuzzing() ? officecfg::Office::Common::Cache::Font::GlyphsCacheSize::get()
243 static UBiDiDirection
getBiDiDirection(std::u16string_view text
, sal_Int32 index
, sal_Int32 len
)
245 // Return whether all character are LTR, RTL, neutral or whether it's mixed.
246 // This is sort of ubidi_getBaseDirection() and ubidi_getDirection(),
247 // but it's meant to be fast but also check all characters.
248 sal_Int32 end
= index
+ len
;
249 UBiDiDirection direction
= UBIDI_NEUTRAL
;
252 switch (u_charDirection(o3tl::iterateCodePoints(text
, &index
)))
254 // Only characters with strong direction.
255 case U_LEFT_TO_RIGHT
:
256 if (direction
== UBIDI_RTL
)
258 direction
= UBIDI_LTR
;
260 case U_RIGHT_TO_LEFT
:
261 case U_RIGHT_TO_LEFT_ARABIC
:
262 if (direction
== UBIDI_LTR
)
264 direction
= UBIDI_RTL
;
273 static SalLayoutGlyphs
makeGlyphsSubset(const SalLayoutGlyphs
& source
,
274 const OutputDevice
* outputDevice
, std::u16string_view text
,
275 sal_Int32 index
, sal_Int32 len
)
277 // tdf#149264: We need to check if the text is LTR, RTL or mixed. Apparently
278 // harfbuzz doesn't give reproducible results (or possibly HB_GLYPH_FLAG_UNSAFE_TO_BREAK
279 // is not reliable?) when asked to lay out RTL text as LTR. So require that the whole
280 // subset ir either LTR or RTL.
281 UBiDiDirection direction
= getBiDiDirection(text
, index
, len
);
282 if (direction
== UBIDI_MIXED
)
283 return SalLayoutGlyphs();
285 for (int level
= 0;; ++level
)
287 const SalLayoutGlyphsImpl
* sourceLevel
= source
.Impl(level
);
288 if (sourceLevel
== nullptr)
290 bool sourceRtl
= bool(sourceLevel
->GetFlags() & SalLayoutFlags::BiDiRtl
);
291 if ((direction
== UBIDI_LTR
&& sourceRtl
) || (direction
== UBIDI_RTL
&& !sourceRtl
))
292 return SalLayoutGlyphs();
293 SalLayoutGlyphsImpl
* cloned
= sourceLevel
->cloneCharRange(index
, len
);
294 // If the glyphs range cannot be cloned, bail out.
295 if (cloned
== nullptr)
296 return SalLayoutGlyphs();
297 // If the entire string is mixed LTR/RTL but the subset is only LTR,
298 // then make sure the flags match that, otherwise checkGlyphsEqual()
299 // would assert on flags being different.
300 cloned
->SetFlags(cloned
->GetFlags()
301 | outputDevice
->GetBiDiLayoutFlags(text
, index
, index
+ len
));
302 ret
.AppendImpl(cloned
);
308 static void checkGlyphsEqual(const SalLayoutGlyphs
& g1
, const SalLayoutGlyphs
& g2
)
310 for (int level
= 0;; ++level
)
312 const SalLayoutGlyphsImpl
* l1
= g1
.Impl(level
);
313 const SalLayoutGlyphsImpl
* l2
= g2
.Impl(level
);
314 if (l1
== nullptr || l2
== nullptr)
319 assert(l1
->isLayoutEquivalent(l2
));
324 const SalLayoutGlyphs
* SalLayoutGlyphsCache::GetLayoutGlyphs(
325 const VclPtr
<const OutputDevice
>& outputDevice
, const OUString
& text
, sal_Int32 nIndex
,
326 sal_Int32 nLen
, tools::Long nLogicWidth
, const vcl::text::TextLayoutCache
* layoutCache
)
330 const CachedGlyphsKey
key(outputDevice
, text
, nIndex
, nLen
, nLogicWidth
);
331 GlyphsCache::const_iterator it
= mCachedGlyphs
.find(key
);
332 if (it
!= mCachedGlyphs
.end())
334 if (it
->second
.IsValid())
336 // Do not try to create the layout here. If a cache item exists, it's already
337 // been attempted and the layout was invalid (this happens with MultiSalLayout).
338 // So in that case this is a cached failure.
341 bool resetLastSubstringKey
= true;
342 const sal_Unicode nbSpace
= 0xa0; // non-breaking space
343 // SalLayoutGlyphsImpl::cloneCharRange() requires BiDiStrong, so if not set, do not even try.
344 bool skipGlyphSubsets
345 = !(outputDevice
->GetLayoutMode() & vcl::text::ComplexTextLayoutFlags::BiDiStrong
);
346 if ((nIndex
!= 0 || nLen
!= text
.getLength()) && !skipGlyphSubsets
)
348 // The glyphs functions are often called first for an entire string
349 // and then with an increasing starting index until the end of the string.
350 // Which means it's possible to get the glyphs faster by just copying
351 // a subset of the full glyphs and adjusting as necessary.
352 if (mLastTemporaryKey
.has_value() && mLastTemporaryKey
== key
)
353 return &mLastTemporaryGlyphs
;
354 const CachedGlyphsKey
keyWhole(outputDevice
, text
, 0, text
.getLength(), nLogicWidth
);
355 GlyphsCache::const_iterator itWhole
= mCachedGlyphs
.find(keyWhole
);
356 if (itWhole
== mCachedGlyphs
.end())
358 // This function may often be called repeatedly for segments of the same string,
359 // in which case it is more efficient to cache glyphs for the entire string
360 // and then return subsets of them. So if a second call either starts at the same
361 // position or starts at the end of the previous call, cache the entire string.
362 // This used to do this only for the first two segments of the string,
363 // but that missed the case when the font slightly changed e.g. because of the first
364 // part being underlined. Doing this for any two segments allows this optimization
365 // even when the prefix of the string would use a different font.
366 // TODO: Can those font differences be ignored?
368 // Shaping performance seems to scale poorly with respect to string length. Certain
369 // writing systems involve extremely long strings (for example, Tibetan: tdf#92064).
370 // In such cases, this optimization would be a net loss, and must be disabled.
371 constexpr sal_Int32 nOptLengthThreshold
= 20000;
372 bool bEnableOptimization
= (text
.getLength() < nOptLengthThreshold
);
374 // Writer layouts tests enable SAL_NON_APPLICATION_FONT_USE=abort in order
375 // to make PrintFontManager::Substitute() abort if font fallback happens. When
376 // laying out the entire string the chance this happens increases (e.g. testAbi11870
377 // normally calls this function only for a part of a string, but this optimization
378 // lays out the entire string and causes a fallback). Since this optimization
379 // does not change result of this function, simply disable it for those tests.
380 static const bool bAbortOnFontSubstitute
= [] {
381 const char* pEnv
= getenv("SAL_NON_APPLICATION_FONT_USE");
382 return pEnv
&& strcmp(pEnv
, "abort") == 0;
384 if (bEnableOptimization
&& mLastSubstringKey
.has_value() && !bAbortOnFontSubstitute
)
386 sal_Int32 pos
= nIndex
;
387 if (mLastSubstringKey
->len
< pos
&& text
[pos
- 1] == nbSpace
)
388 --pos
; // Writer skips a non-breaking space, so skip that character too.
389 if ((mLastSubstringKey
->len
== pos
|| mLastSubstringKey
->index
== nIndex
)
391 == CachedGlyphsKey(outputDevice
, text
, mLastSubstringKey
->index
,
392 mLastSubstringKey
->len
, nLogicWidth
))
394 GetLayoutGlyphs(outputDevice
, text
, 0, text
.getLength(), nLogicWidth
,
396 itWhole
= mCachedGlyphs
.find(keyWhole
);
399 mLastSubstringKey
.reset();
401 if (!mLastSubstringKey
.has_value())
403 mLastSubstringKey
= key
;
404 resetLastSubstringKey
= false;
407 if (itWhole
!= mCachedGlyphs
.end() && itWhole
->second
.IsValid())
409 mLastSubstringKey
.reset();
411 = makeGlyphsSubset(itWhole
->second
, outputDevice
, text
, nIndex
, nLen
);
412 if (mLastTemporaryGlyphs
.IsValid())
414 mLastTemporaryKey
= key
;
416 std::shared_ptr
<const vcl::text::TextLayoutCache
> tmpLayoutCache
;
417 if (layoutCache
== nullptr)
419 tmpLayoutCache
= vcl::text::TextLayoutCache::Create(text
);
420 layoutCache
= tmpLayoutCache
.get();
422 // Check if the subset result really matches what we would get normally,
423 // to make sure corner cases are handled well (see SalLayoutGlyphsImpl::cloneCharRange()).
424 std::unique_ptr
<SalLayout
> layout
425 = outputDevice
->ImplLayout(text
, nIndex
, nLen
, Point(0, 0), nLogicWidth
, {}, {},
426 SalLayoutFlags::GlyphItemsOnly
, layoutCache
);
428 checkGlyphsEqual(mLastTemporaryGlyphs
, layout
->GetGlyphs());
430 return &mLastTemporaryGlyphs
;
434 if (resetLastSubstringKey
)
436 // Writer does non-breaking space differently (not as part of the string), so in that
437 // case ignore that call and still allow finding two adjacent substrings that have
438 // the non-breaking space between them.
439 if (nLen
!= 1 || text
[nIndex
] != nbSpace
)
440 mLastSubstringKey
.reset();
443 std::shared_ptr
<const vcl::text::TextLayoutCache
> tmpLayoutCache
;
444 if (layoutCache
== nullptr)
446 tmpLayoutCache
= vcl::text::TextLayoutCache::Create(text
);
447 layoutCache
= tmpLayoutCache
.get();
449 std::unique_ptr
<SalLayout
> layout
450 = outputDevice
->ImplLayout(text
, nIndex
, nLen
, Point(0, 0), nLogicWidth
, {}, {},
451 SalLayoutFlags::GlyphItemsOnly
, layoutCache
);
454 SalLayoutGlyphs glyphs
= layout
->GetGlyphs();
455 if (glyphs
.IsValid())
457 // TODO: Fallbacks do not work reliably (fallback font not included in the key),
458 // so do not cache (but still return once, using the temporary without a key set).
459 if (!mbCacheGlyphsWhenDoingFallbackFonts
&& glyphs
.Impl(1) != nullptr)
461 mLastTemporaryGlyphs
= std::move(glyphs
);
462 mLastTemporaryKey
.reset();
463 return &mLastTemporaryGlyphs
;
465 mCachedGlyphs
.insert(std::make_pair(key
, std::move(glyphs
)));
466 assert(mCachedGlyphs
.find(key
)
467 == mCachedGlyphs
.begin()); // newly inserted item is first
468 return &mCachedGlyphs
.begin()->second
;
471 // Failure, cache it too as invalid glyphs.
472 mCachedGlyphs
.insert(std::make_pair(key
, SalLayoutGlyphs()));
476 const SalLayoutGlyphs
* SalLayoutGlyphsCache::GetLayoutGlyphs(
477 const VclPtr
<const OutputDevice
>& outputDevice
, const OUString
& text
, sal_Int32 nIndex
,
478 sal_Int32 nLen
, sal_Int32 nDrawMinCharPos
, sal_Int32 nDrawEndCharPos
, tools::Long nLogicWidth
,
479 const vcl::text::TextLayoutCache
* layoutCache
)
481 // This version is used by callers that need to draw a subset of a layout. In all ordinary uses
482 // this function will be called for successive glyph subsets, so should optimize for that case.
484 = GetLayoutGlyphs(outputDevice
, text
, nIndex
, nLen
, nLogicWidth
, layoutCache
);
485 if (nDrawMinCharPos
<= nIndex
&& nDrawEndCharPos
>= (nIndex
+ nLen
))
490 if (pWholeGlyphs
&& pWholeGlyphs
->IsValid())
492 mLastTemporaryKey
.reset();
493 mLastTemporaryGlyphs
= makeGlyphsSubset(*pWholeGlyphs
, outputDevice
, text
, nDrawMinCharPos
,
494 nDrawEndCharPos
- nDrawMinCharPos
);
495 if (mLastTemporaryGlyphs
.IsValid())
497 return &mLastTemporaryGlyphs
;
504 void SalLayoutGlyphsCache::SetCacheGlyphsWhenDoingFallbackFonts(bool bOK
)
506 mbCacheGlyphsWhenDoingFallbackFonts
= bOK
;
511 SalLayoutGlyphsCache::CachedGlyphsKey::CachedGlyphsKey(
512 const VclPtr
<const OutputDevice
>& outputDevice
, OUString t
, sal_Int32 i
, sal_Int32 l
,
518 // we also need to save things used in OutputDevice::ImplPrepareLayoutArgs(), in case they
519 // change in the output device, plus mapMode affects the sizes.
520 , fontMetric(outputDevice
->GetFontMetric())
521 // TODO It would be possible to get a better hit ratio if mapMode wasn't part of the key
522 // and results that differ only in mapmode would have coordinates adjusted based on that.
523 // That would occasionally lead to rounding errors (at least differences that would
524 // make checkGlyphsEqual() fail).
525 , mapMode(outputDevice
->GetMapMode())
526 , digitLanguage(outputDevice
->GetDigitLanguage())
527 , layoutMode(outputDevice
->GetLayoutMode())
528 , rtl(outputDevice
->IsRTLEnabled())
530 const LogicalFontInstance
* fi
= outputDevice
->GetFontInstance();
531 fi
->GetScale(&fontScaleX
, &fontScaleY
);
533 const vcl::font::FontSelectPattern
& rFSD
= fi
->GetFontSelectPattern();
534 disabledLigatures
= rFSD
.GetPitch() == PITCH_FIXED
;
535 artificialItalic
= fi
->NeedsArtificialItalic();
536 artificialBold
= fi
->NeedsArtificialBold();
539 o3tl::hash_combine(hashValue
, vcl::text::FirstCharsStringHash()(text
));
540 o3tl::hash_combine(hashValue
, index
);
541 o3tl::hash_combine(hashValue
, len
);
542 o3tl::hash_combine(hashValue
, logicWidth
);
543 o3tl::hash_combine(hashValue
, outputDevice
.get());
544 // Need to use IgnoreColor, because sometimes the color changes, but it's irrelevant
545 // for text layout (and also obsolete in vcl::Font).
546 o3tl::hash_combine(hashValue
, fontMetric
.GetHashValueIgnoreColor());
547 // For some reason font scale may differ even if vcl::Font is the same,
548 // so explicitly check it too.
549 o3tl::hash_combine(hashValue
, fontScaleX
);
550 o3tl::hash_combine(hashValue
, fontScaleY
);
551 o3tl::hash_combine(hashValue
, mapMode
.GetHashValue());
552 o3tl::hash_combine(hashValue
, rtl
);
553 o3tl::hash_combine(hashValue
, disabledLigatures
);
554 o3tl::hash_combine(hashValue
, artificialItalic
);
555 o3tl::hash_combine(hashValue
, artificialBold
);
556 o3tl::hash_combine(hashValue
, layoutMode
);
557 o3tl::hash_combine(hashValue
, digitLanguage
.get());
559 // In case the font name is the same, but the font family differs, then the font metric won't
560 // contain that custom font family, so explicitly include the font family from the output device
562 o3tl::hash_combine(hashValue
, outputDevice
->GetFont().GetFamilyType());
565 bool SalLayoutGlyphsCache::CachedGlyphsKey::operator==(const CachedGlyphsKey
& other
) const
567 return hashValue
== other
.hashValue
&& index
== other
.index
&& len
== other
.len
568 && logicWidth
== other
.logicWidth
&& mapMode
== other
.mapMode
&& rtl
== other
.rtl
569 && disabledLigatures
== other
.disabledLigatures
570 && artificialItalic
== other
.artificialItalic
&& artificialBold
== other
.artificialBold
571 && layoutMode
== other
.layoutMode
&& digitLanguage
== other
.digitLanguage
572 && fontScaleX
== other
.fontScaleX
&& fontScaleY
== other
.fontScaleY
573 && fontMetric
.EqualIgnoreColor(other
.fontMetric
)
574 && vcl::text::FastStringCompareEqual()(text
, other
.text
);
575 // Slower things last in the comparison.
578 size_t SalLayoutGlyphsCache::GlyphsCost::operator()(const SalLayoutGlyphs
& glyphs
) const
581 for (int level
= 0;; ++level
)
583 const SalLayoutGlyphsImpl
* impl
= glyphs
.Impl(level
);
586 // Count size in bytes, both the SalLayoutGlyphsImpl instance and contained GlyphItem's.
587 cost
+= sizeof(*impl
);
588 cost
+= impl
->size() * sizeof(impl
->front());
593 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */