summaryrefslogtreecommitdiffstats
path: root/vcl/source/gdi/impglyphitem.cxx
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--vcl/source/gdi/impglyphitem.cxx560
1 files changed, 560 insertions, 0 deletions
diff --git a/vcl/source/gdi/impglyphitem.cxx b/vcl/source/gdi/impglyphitem.cxx
new file mode 100644
index 000000000..8ec137434
--- /dev/null
+++ b/vcl/source/gdi/impglyphitem.cxx
@@ -0,0 +1,560 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <impglyphitem.hxx>
+#include <vcl/glyphitemcache.hxx>
+#include <vcl/vcllayout.hxx>
+#include <vcl/lazydelete.hxx>
+#include <tools/stream.hxx>
+#include <unotools/configmgr.hxx>
+#include <TextLayoutCache.hxx>
+#include <officecfg/Office/Common.hxx>
+
+#include <unicode/ubidi.h>
+#include <unicode/uchar.h>
+
+// These need being explicit because of SalLayoutGlyphsImpl being private in vcl.
+SalLayoutGlyphs::SalLayoutGlyphs() {}
+
+SalLayoutGlyphs::~SalLayoutGlyphs() {}
+
+SalLayoutGlyphs::SalLayoutGlyphs(SalLayoutGlyphs&& rOther) noexcept
+{
+ std::swap(m_pImpl, rOther.m_pImpl);
+ std::swap(m_pExtraImpls, rOther.m_pExtraImpls);
+}
+
+SalLayoutGlyphs& SalLayoutGlyphs::operator=(SalLayoutGlyphs&& rOther) noexcept
+{
+ if (this != &rOther)
+ {
+ std::swap(m_pImpl, rOther.m_pImpl);
+ std::swap(m_pExtraImpls, rOther.m_pExtraImpls);
+ }
+ return *this;
+}
+
+bool SalLayoutGlyphs::IsValid() const
+{
+ if (m_pImpl == nullptr)
+ return false;
+ if (!m_pImpl->IsValid())
+ return false;
+ if (m_pExtraImpls)
+ for (std::unique_ptr<SalLayoutGlyphsImpl> const& impl : *m_pExtraImpls)
+ if (!impl->IsValid())
+ return false;
+ return true;
+}
+
+void SalLayoutGlyphs::Invalidate()
+{
+ // Invalidating is in fact simply clearing.
+ m_pImpl.reset();
+ m_pExtraImpls.reset();
+}
+
+SalLayoutGlyphsImpl* SalLayoutGlyphs::Impl(unsigned int nLevel) const
+{
+ if (nLevel == 0)
+ return m_pImpl.get();
+ if (m_pExtraImpls != nullptr && nLevel - 1 < m_pExtraImpls->size())
+ return (*m_pExtraImpls)[nLevel - 1].get();
+ return nullptr;
+}
+
+void SalLayoutGlyphs::AppendImpl(SalLayoutGlyphsImpl* pImpl)
+{
+ if (!m_pImpl)
+ m_pImpl.reset(pImpl);
+ else
+ {
+ if (!m_pExtraImpls)
+ m_pExtraImpls.reset(new std::vector<std::unique_ptr<SalLayoutGlyphsImpl>>);
+ m_pExtraImpls->emplace_back(pImpl);
+ }
+}
+
+SalLayoutGlyphsImpl* SalLayoutGlyphsImpl::clone() const { return new SalLayoutGlyphsImpl(*this); }
+
+// Clone, but only glyphs in the given range in the original text string.
+// It is possible the given range may not be cloned, in which case this returns nullptr.
+SalLayoutGlyphsImpl* SalLayoutGlyphsImpl::cloneCharRange(sal_Int32 index, sal_Int32 length) const
+{
+ std::unique_ptr<SalLayoutGlyphsImpl> copy(new SalLayoutGlyphsImpl(*GetFont()));
+ copy->SetFlags(GetFlags());
+ if (empty())
+ return copy.release();
+ bool rtl = front().IsRTLGlyph();
+ // Avoid mixing LTR/RTL or layouts that do not have it set explicitly (BiDiStrong). Otherwise
+ // the subset may not quite match what would a real layout call give (e.g. some characters with neutral
+ // direction such as space might have different LTR/RTL flag). It seems bailing out here mostly
+ // avoid relatively rare corner cases and doesn't matter for performance.
+ // This is also checked in SalLayoutGlyphsCache::GetLayoutGlyphs() below.
+ if (!(GetFlags() & SalLayoutFlags::BiDiStrong)
+ || rtl != bool(GetFlags() & SalLayoutFlags::BiDiRtl))
+ return nullptr;
+ copy->reserve(std::min<size_t>(size(), length));
+ sal_Int32 beginPos = index;
+ sal_Int32 endPos = index + length;
+ const_iterator pos;
+ if (rtl)
+ {
+ // Glyphs are in reverse order for RTL.
+ beginPos = index + length - 1;
+ endPos = index - 1;
+ // Skip glyphs that are in the string after the given index, i.e. are before the glyphs
+ // we want.
+ pos = std::partition_point(
+ begin(), end(), [beginPos](const GlyphItem& it) { return it.charPos() > beginPos; });
+ }
+ else
+ {
+ // Skip glyphs that are in the string before the given index (glyphs are sorted by charPos()).
+ pos = std::partition_point(
+ begin(), end(), [beginPos](const GlyphItem& it) { return it.charPos() < beginPos; });
+ }
+ if (pos == end())
+ return nullptr;
+ // Require a start at the exact position given, otherwise bail out.
+ if (pos->charPos() != beginPos)
+ return nullptr;
+ // For RTL make sure we're not cutting in the middle of a multi-character glyph
+ // (for non-RTL charPos is always the start of a multi-character glyph).
+ if (rtl && pos->charPos() + pos->charCount() > beginPos + 1)
+ return nullptr;
+ if (!isSafeToBreak(pos, rtl))
+ return nullptr;
+ // LinearPos needs adjusting to start at xOffset/yOffset for the first item,
+ // that's how it's computed in GenericSalLayout::LayoutText().
+ DevicePoint zeroPoint = pos->linearPos() - DevicePoint(pos->xOffset(), pos->yOffset());
+ // Add and adjust all glyphs until the given length.
+ // The check is written as 'charPos + charCount <= endPos' rather than 'charPos < endPos'
+ // (or similarly for RTL) to make sure we include complete glyphs. If a glyph is composed
+ // from several characters, we should not cut in the middle of those characters, so this
+ // checks the glyph is entirely in the given character range. If it is not, this will end
+ // the loop and the later 'pos->charPos() != endPos' check will fail and bail out.
+ // CppunitTest_sw_layoutwriter's testCombiningCharacterCursorPosition would fail without this.
+ while (pos != end()
+ && (rtl ? pos->charPos() - pos->charCount() >= endPos
+ : pos->charPos() + pos->charCount() <= endPos))
+ {
+ if (pos->IsRTLGlyph() != rtl)
+ return nullptr; // Don't mix RTL and non-RTL runs.
+ // HACK: When running CppunitTest_sw_uiwriter3's testTdf104649 on Mac there's glyph
+ // with id 1232 that has 0 charCount, 0 origWidth and inconsistent xOffset (sometimes 0,
+ // but sometimes not). Possibly font or Harfbuzz bug? It's extremely rare, so simply bail out.
+ if (pos->charCount() == 0 && pos->origWidth() == 0)
+ return nullptr;
+ copy->push_back(*pos);
+ copy->back().setLinearPos(copy->back().linearPos() - zeroPoint);
+ ++pos;
+ }
+ if (pos != end())
+ {
+ // Fail if the next character is not at the expected past-end position. For RTL check
+ // that we're not cutting in the middle of a multi-character glyph.
+ if (rtl ? pos->charPos() + pos->charCount() != endPos + 1 : pos->charPos() != endPos)
+ return nullptr;
+ if (!isSafeToBreak(pos, rtl))
+ return nullptr;
+ }
+ return copy.release();
+}
+
+bool SalLayoutGlyphsImpl::isSafeToBreak(const_iterator pos, bool rtl) const
+{
+ if (rtl)
+ {
+ // RTL is more complicated, because HB_GLYPH_FLAG_UNSAFE_TO_BREAK talks about beginning
+ // of a cluster, which refers to the text, not glyphs. This function is called
+ // for the first glyph of the subset and the first glyph after the subset, but since
+ // the glyphs are backwards, and we need the beginning of cluster at the start of the text
+ // and beginning of the cluster after the text, we need to check glyphs before this position.
+ if (pos == begin())
+ return true;
+ --pos;
+ }
+ // Don't create a subset if it's not safe to break at the beginning or end of the sequence
+ // (https://harfbuzz.github.io/harfbuzz-hb-buffer.html#hb-glyph-flags-t).
+ if (pos->IsUnsafeToBreak() || (pos->IsInCluster() && !pos->IsClusterStart()))
+ return false;
+ return true;
+}
+
+#ifdef DBG_UTIL
+bool SalLayoutGlyphsImpl::isEqual(const SalLayoutGlyphsImpl* other) const
+{
+ if (!GetFont()->mxFontMetric->CompareDeviceIndependentFontAttributes(
+ *other->GetFont()->mxFontMetric))
+ return false;
+ if (GetFlags() != other->GetFlags())
+ return false;
+ if (empty() || other->empty())
+ return empty() == other->empty();
+ if (size() != other->size())
+ return false;
+ for (size_t pos = 0; pos < size(); ++pos)
+ {
+ if ((*this)[pos] != (*other)[pos])
+ return false;
+ }
+ return true;
+}
+#endif
+
+bool SalLayoutGlyphsImpl::IsValid() const
+{
+ if (!m_rFontInstance.is())
+ return false;
+ return true;
+}
+
+void SalLayoutGlyphsCache::clear() { mCachedGlyphs.clear(); }
+
+SalLayoutGlyphsCache* SalLayoutGlyphsCache::self()
+{
+ static vcl::DeleteOnDeinit<SalLayoutGlyphsCache> cache(
+ !utl::ConfigManager::IsFuzzing()
+ ? officecfg::Office::Common::Cache::Font::GlyphsCacheSize::get()
+ : 20000);
+ return cache.get();
+}
+
+static UBiDiDirection getBiDiDirection(const OUString& text, sal_Int32 index, sal_Int32 len)
+{
+ // Return whether all character are LTR, RTL, neutral or whether it's mixed.
+ // This is sort of ubidi_getBaseDirection() and ubidi_getDirection(),
+ // but it's meant to be fast but also check all characters.
+ sal_Int32 end = index + len;
+ UBiDiDirection direction = UBIDI_NEUTRAL;
+ while (index < end)
+ {
+ switch (u_charDirection(text.iterateCodePoints(&index)))
+ {
+ // Only characters with strong direction.
+ case U_LEFT_TO_RIGHT:
+ if (direction == UBIDI_RTL)
+ return UBIDI_MIXED;
+ direction = UBIDI_LTR;
+ break;
+ case U_RIGHT_TO_LEFT:
+ case U_RIGHT_TO_LEFT_ARABIC:
+ if (direction == UBIDI_LTR)
+ return UBIDI_MIXED;
+ direction = UBIDI_RTL;
+ break;
+ default:
+ break;
+ }
+ }
+ return direction;
+}
+
+static SalLayoutGlyphs makeGlyphsSubset(const SalLayoutGlyphs& source,
+ const OutputDevice* outputDevice, const OUString& text,
+ sal_Int32 index, sal_Int32 len)
+{
+ // tdf#149264: We need to check if the text is LTR, RTL or mixed. Apparently
+ // harfbuzz doesn't give reproducible results (or possibly HB_GLYPH_FLAG_UNSAFE_TO_BREAK
+ // is not reliable?) when asked to lay out RTL text as LTR. So require that the whole
+ // subset ir either LTR or RTL.
+ UBiDiDirection direction = getBiDiDirection(text, index, len);
+ if (direction == UBIDI_MIXED)
+ return SalLayoutGlyphs();
+ SalLayoutGlyphs ret;
+ for (int level = 0;; ++level)
+ {
+ const SalLayoutGlyphsImpl* sourceLevel = source.Impl(level);
+ if (sourceLevel == nullptr)
+ break;
+ bool sourceRtl = bool(sourceLevel->GetFlags() & SalLayoutFlags::BiDiRtl);
+ if ((direction == UBIDI_LTR && sourceRtl) || (direction == UBIDI_RTL && !sourceRtl))
+ return SalLayoutGlyphs();
+ SalLayoutGlyphsImpl* cloned = sourceLevel->cloneCharRange(index, len);
+ // If the glyphs range cannot be cloned, bail out.
+ if (cloned == nullptr)
+ return SalLayoutGlyphs();
+ // If the entire string is mixed LTR/RTL but the subset is only LTR,
+ // then make sure the flags match that, otherwise checkGlyphsEqual()
+ // would assert on flags being different.
+ cloned->SetFlags(cloned->GetFlags()
+ | outputDevice->GetBiDiLayoutFlags(text, index, index + len));
+ // SalLayoutFlags::KashidaJustification is set only if any glyph
+ // in the range has GlyphItemFlags::ALLOW_KASHIDA (otherwise unset it).
+ if (cloned->GetFlags() & SalLayoutFlags::KashidaJustification)
+ {
+ bool hasKashida = false;
+ for (const GlyphItem& item : *cloned)
+ {
+ if (item.AllowKashida())
+ {
+ hasKashida = true;
+ break;
+ }
+ }
+ if (!hasKashida)
+ cloned->SetFlags(cloned->GetFlags() & ~SalLayoutFlags::KashidaJustification);
+ }
+#ifdef DBG_UTIL
+ else
+ for (const GlyphItem& item : *cloned)
+ assert(!item.AllowKashida());
+#endif
+ ret.AppendImpl(cloned);
+ }
+ return ret;
+}
+
+#ifdef DBG_UTIL
+static void checkGlyphsEqual(const SalLayoutGlyphs& g1, const SalLayoutGlyphs& g2)
+{
+ for (int level = 0;; ++level)
+ {
+ const SalLayoutGlyphsImpl* l1 = g1.Impl(level);
+ const SalLayoutGlyphsImpl* l2 = g2.Impl(level);
+ if (l1 == nullptr || l2 == nullptr)
+ {
+ assert(l1 == l2);
+ break;
+ }
+ assert(l1->isEqual(l2));
+ }
+}
+#endif
+
+const SalLayoutGlyphs*
+SalLayoutGlyphsCache::GetLayoutGlyphs(VclPtr<const OutputDevice> outputDevice, const OUString& text,
+ sal_Int32 nIndex, sal_Int32 nLen, tools::Long nLogicWidth,
+ const vcl::text::TextLayoutCache* layoutCache)
+{
+ if (nLen == 0)
+ return nullptr;
+ const CachedGlyphsKey key(outputDevice, text, nIndex, nLen, nLogicWidth);
+ GlyphsCache::const_iterator it = mCachedGlyphs.find(key);
+ if (it != mCachedGlyphs.end())
+ {
+ if (it->second.IsValid())
+ return &it->second;
+ // Do not try to create the layout here. If a cache item exists, it's already
+ // been attempted and the layout was invalid (this happens with MultiSalLayout).
+ // So in that case this is a cached failure.
+ return nullptr;
+ }
+ bool resetLastSubstringKey = true;
+ const sal_Unicode nbSpace = 0xa0; // non-breaking space
+ // SalLayoutGlyphsImpl::cloneCharRange() requires BiDiStrong, so if not set, do not even try.
+ bool skipGlyphSubsets
+ = !(outputDevice->GetLayoutMode() & vcl::text::ComplexTextLayoutFlags::BiDiStrong);
+ if ((nIndex != 0 || nLen != text.getLength()) && !skipGlyphSubsets)
+ {
+ // The glyphs functions are often called first for an entire string
+ // and then with an increasing starting index until the end of the string.
+ // Which means it's possible to get the glyphs faster by just copying
+ // a subset of the full glyphs and adjusting as necessary.
+ if (mLastTemporaryKey.has_value() && mLastTemporaryKey == key)
+ return &mLastTemporaryGlyphs;
+ const CachedGlyphsKey keyWhole(outputDevice, text, 0, text.getLength(), nLogicWidth);
+ GlyphsCache::const_iterator itWhole = mCachedGlyphs.find(keyWhole);
+ if (itWhole == mCachedGlyphs.end())
+ {
+ // This function may often be called repeatedly for segments of the same string,
+ // in which case it is more efficient to cache glyphs for the entire string
+ // and then return subsets of them. So if a second call either starts at the same
+ // position or starts at the end of the previous call, cache the entire string.
+ // This used to do this only for the first two segments of the string,
+ // but that missed the case when the font slightly changed e.g. because of the first
+ // part being underlined. Doing this for any two segments allows this optimization
+ // even when the prefix of the string would use a different font.
+ // TODO: Can those font differences be ignored?
+ // Writer layouts tests enable SAL_ABORT_ON_NON_APPLICATION_FONT_USE in order
+ // to make PrintFontManager::Substitute() abort if font fallback happens. When
+ // laying out the entire string the chance this happens increases (e.g. testAbi11870
+ // normally calls this function only for a part of a string, but this optimization
+ // lays out the entire string and causes a fallback). Since this optimization
+ // does not change result of this function, simply disable it for those tests.
+ static bool bAbortOnFontSubstitute
+ = getenv("SAL_ABORT_ON_NON_APPLICATION_FONT_USE") != nullptr;
+ if (mLastSubstringKey.has_value() && !bAbortOnFontSubstitute)
+ {
+ sal_Int32 pos = nIndex;
+ if (mLastSubstringKey->len < pos && text[pos - 1] == nbSpace)
+ --pos; // Writer skips a non-breaking space, so skip that character too.
+ if ((mLastSubstringKey->len == pos || mLastSubstringKey->index == nIndex)
+ && mLastSubstringKey
+ == CachedGlyphsKey(outputDevice, text, mLastSubstringKey->index,
+ mLastSubstringKey->len, nLogicWidth))
+ {
+ GetLayoutGlyphs(outputDevice, text, 0, text.getLength(), nLogicWidth,
+ layoutCache);
+ itWhole = mCachedGlyphs.find(keyWhole);
+ }
+ else
+ mLastSubstringKey.reset();
+ }
+ if (!mLastSubstringKey.has_value())
+ {
+ mLastSubstringKey = key;
+ resetLastSubstringKey = false;
+ }
+ }
+ if (itWhole != mCachedGlyphs.end() && itWhole->second.IsValid())
+ {
+ mLastSubstringKey.reset();
+ mLastTemporaryGlyphs
+ = makeGlyphsSubset(itWhole->second, outputDevice, text, nIndex, nLen);
+ if (mLastTemporaryGlyphs.IsValid())
+ {
+ mLastTemporaryKey = std::move(key);
+#ifdef DBG_UTIL
+ std::shared_ptr<const vcl::text::TextLayoutCache> tmpLayoutCache;
+ if (layoutCache == nullptr)
+ {
+ tmpLayoutCache = vcl::text::TextLayoutCache::Create(text);
+ layoutCache = tmpLayoutCache.get();
+ }
+ // Check if the subset result really matches what we would get normally,
+ // to make sure corner cases are handled well (see SalLayoutGlyphsImpl::cloneCharRange()).
+ std::unique_ptr<SalLayout> layout
+ = outputDevice->ImplLayout(text, nIndex, nLen, Point(0, 0), nLogicWidth, {},
+ SalLayoutFlags::GlyphItemsOnly, layoutCache);
+ assert(layout);
+ checkGlyphsEqual(mLastTemporaryGlyphs, layout->GetGlyphs());
+#endif
+ return &mLastTemporaryGlyphs;
+ }
+ }
+ }
+ if (resetLastSubstringKey)
+ {
+ // Writer does non-breaking space differently (not as part of the string), so in that
+ // case ignore that call and still allow finding two adjacent substrings that have
+ // the non-breaking space between them.
+ if (nLen != 1 || text[nIndex] != nbSpace)
+ mLastSubstringKey.reset();
+ }
+
+ std::shared_ptr<const vcl::text::TextLayoutCache> tmpLayoutCache;
+ if (layoutCache == nullptr)
+ {
+ tmpLayoutCache = vcl::text::TextLayoutCache::Create(text);
+ layoutCache = tmpLayoutCache.get();
+ }
+ std::unique_ptr<SalLayout> layout
+ = outputDevice->ImplLayout(text, nIndex, nLen, Point(0, 0), nLogicWidth, {},
+ SalLayoutFlags::GlyphItemsOnly, layoutCache);
+ if (layout)
+ {
+ SalLayoutGlyphs glyphs = layout->GetGlyphs();
+ if (glyphs.IsValid())
+ {
+ // TODO: Fallbacks do not work reliably (fallback font not included in the key),
+ // so do not cache (but still return once, using the temporary without a key set).
+ if (glyphs.Impl(1) != nullptr)
+ {
+ mLastTemporaryGlyphs = std::move(glyphs);
+ mLastTemporaryKey.reset();
+ return &mLastTemporaryGlyphs;
+ }
+ mCachedGlyphs.insert(std::make_pair(key, layout->GetGlyphs()));
+ assert(mCachedGlyphs.find(key)
+ == mCachedGlyphs.begin()); // newly inserted item is first
+ return &mCachedGlyphs.begin()->second;
+ }
+ }
+ // Failure, cache it too as invalid glyphs.
+ mCachedGlyphs.insert(std::make_pair(key, SalLayoutGlyphs()));
+ return nullptr;
+}
+
+SalLayoutGlyphsCache::CachedGlyphsKey::CachedGlyphsKey(
+ const VclPtr<const OutputDevice>& outputDevice, const OUString& t, sal_Int32 i, sal_Int32 l,
+ tools::Long w)
+ : text(t)
+ , index(i)
+ , len(l)
+ , logicWidth(w)
+ // we also need to save things used in OutputDevice::ImplPrepareLayoutArgs(), in case they
+ // change in the output device, plus mapMode affects the sizes.
+ , fontMetric(outputDevice->GetFontMetric())
+ // TODO It would be possible to get a better hit ratio if mapMode wasn't part of the key
+ // and results that differ only in mapmode would have coordinates adjusted based on that.
+ // That would occasionally lead to rounding errors (at least differences that would
+ // make checkGlyphsEqual() fail).
+ , mapMode(outputDevice->GetMapMode())
+ , rtl(outputDevice->IsRTLEnabled())
+ , layoutMode(outputDevice->GetLayoutMode())
+ , digitLanguage(outputDevice->GetDigitLanguage())
+{
+ const LogicalFontInstance* fi = outputDevice->GetFontInstance();
+ fi->GetScale(&fontScaleX, &fontScaleY);
+
+ const vcl::font::FontSelectPattern& rFSD = fi->GetFontSelectPattern();
+ artificialItalic = rFSD.GetItalic() != ITALIC_NONE && fontMetric.GetItalic() == ITALIC_NONE;
+ artificialBold = rFSD.GetWeight() > WEIGHT_MEDIUM && fontMetric.GetWeight() <= WEIGHT_MEDIUM;
+
+ hashValue = 0;
+ o3tl::hash_combine(hashValue, vcl::text::FirstCharsStringHash()(text));
+ o3tl::hash_combine(hashValue, index);
+ o3tl::hash_combine(hashValue, len);
+ o3tl::hash_combine(hashValue, logicWidth);
+ o3tl::hash_combine(hashValue, outputDevice.get());
+ // Need to use IgnoreColor, because sometimes the color changes, but it's irrelevant
+ // for text layout (and also obsolete in vcl::Font).
+ o3tl::hash_combine(hashValue, fontMetric.GetHashValueIgnoreColor());
+ // For some reason font scale may differ even if vcl::Font is the same,
+ // so explicitly check it too.
+ o3tl::hash_combine(hashValue, fontScaleX);
+ o3tl::hash_combine(hashValue, fontScaleY);
+ o3tl::hash_combine(hashValue, mapMode.GetHashValue());
+ o3tl::hash_combine(hashValue, rtl);
+ o3tl::hash_combine(hashValue, artificialItalic);
+ o3tl::hash_combine(hashValue, artificialBold);
+ o3tl::hash_combine(hashValue, layoutMode);
+ o3tl::hash_combine(hashValue, digitLanguage.get());
+}
+
+inline bool SalLayoutGlyphsCache::CachedGlyphsKey::operator==(const CachedGlyphsKey& other) const
+{
+ return hashValue == other.hashValue && index == other.index && len == other.len
+ && logicWidth == other.logicWidth && mapMode == other.mapMode && rtl == other.rtl
+ && artificialItalic == other.artificialItalic && artificialBold == other.artificialBold
+ && layoutMode == other.layoutMode && digitLanguage == other.digitLanguage
+ && fontScaleX == other.fontScaleX && fontScaleY == other.fontScaleY
+ && fontMetric.EqualIgnoreColor(other.fontMetric)
+ && vcl::text::FastStringCompareEqual()(text, other.text);
+ // Slower things last in the comparison.
+}
+
+size_t SalLayoutGlyphsCache::GlyphsCost::operator()(const SalLayoutGlyphs& glyphs) const
+{
+ size_t cost = 0;
+ for (int level = 0;; ++level)
+ {
+ const SalLayoutGlyphsImpl* impl = glyphs.Impl(level);
+ if (impl == nullptr)
+ break;
+ // Count size in bytes, both the SalLayoutGlyphsImpl instance and contained GlyphItem's.
+ cost += sizeof(*impl);
+ cost += impl->size() * sizeof(impl->front());
+ }
+ return cost;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */