Coverage Report

Created: 2026-04-09 11:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/gdi/impglyphitem.cxx
Line
Count
Source
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 <algorithm>
21
#include <utility>
22
#include <impglyphitem.hxx>
23
#include <vcl/glyphitemcache.hxx>
24
#include <vcl/outdev.hxx>
25
#include <vcl/vcllayout.hxx>
26
#include <tools/lazydelete.hxx>
27
#include <tools/stream.hxx>
28
#include <unotools/configmgr.hxx>
29
#include <TextLayoutCache.hxx>
30
#include <officecfg/Office/Common.hxx>
31
#include <o3tl/string_view.hxx>
32
33
#include <unicode/ubidi.h>
34
#include <unicode/uchar.h>
35
36
// These need being explicit because of SalLayoutGlyphsImpl being private in vcl.
37
5.33M
SalLayoutGlyphs::SalLayoutGlyphs() {}
38
39
14.5M
SalLayoutGlyphs::~SalLayoutGlyphs() {}
40
41
SalLayoutGlyphs::SalLayoutGlyphs(SalLayoutGlyphs&& rOther) noexcept
42
9.25M
{
43
9.25M
    std::swap(m_pImpl, rOther.m_pImpl);
44
9.25M
    std::swap(m_pExtraImpls, rOther.m_pExtraImpls);
45
9.25M
}
46
47
SalLayoutGlyphs& SalLayoutGlyphs::operator=(SalLayoutGlyphs&& rOther) noexcept
48
1.06M
{
49
1.06M
    if (this != &rOther)
50
1.06M
    {
51
1.06M
        std::swap(m_pImpl, rOther.m_pImpl);
52
1.06M
        std::swap(m_pExtraImpls, rOther.m_pExtraImpls);
53
1.06M
    }
54
1.06M
    return *this;
55
1.06M
}
56
57
bool SalLayoutGlyphs::IsValid() const
58
18.1M
{
59
18.1M
    if (m_pImpl == nullptr)
60
117k
        return false;
61
18.0M
    if (!m_pImpl->IsValid())
62
0
        return false;
63
64
18.0M
    return !m_pExtraImpls
65
0
           || std::ranges::all_of(*m_pExtraImpls, [](const auto& impl) { return impl->IsValid(); });
66
18.0M
}
67
68
void SalLayoutGlyphs::Invalidate()
69
0
{
70
    // Invalidating is in fact simply clearing.
71
0
    m_pImpl.reset();
72
0
    m_pExtraImpls.reset();
73
0
}
74
75
SalLayoutGlyphsImpl* SalLayoutGlyphs::Impl(unsigned int nLevel) const
76
27.7M
{
77
27.7M
    if (nLevel == 0)
78
16.4M
        return m_pImpl.get();
79
11.3M
    if (m_pExtraImpls != nullptr && nLevel - 1 < m_pExtraImpls->size())
80
0
        return (*m_pExtraImpls)[nLevel - 1].get();
81
11.3M
    return nullptr;
82
11.3M
}
83
84
void SalLayoutGlyphs::AppendImpl(SalLayoutGlyphsImpl* pImpl)
85
5.09M
{
86
5.09M
    if (!m_pImpl)
87
5.09M
        m_pImpl.reset(pImpl);
88
0
    else
89
0
    {
90
0
        if (!m_pExtraImpls)
91
0
            m_pExtraImpls.reset(new std::vector<std::unique_ptr<SalLayoutGlyphsImpl>>);
92
0
        m_pExtraImpls->emplace_back(pImpl);
93
0
    }
94
5.09M
}
95
96
4.15M
SalLayoutGlyphsImpl* SalLayoutGlyphsImpl::clone() const { return new SalLayoutGlyphsImpl(*this); }
97
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
101
1.00M
{
102
1.00M
    std::unique_ptr<SalLayoutGlyphsImpl> copy(new SalLayoutGlyphsImpl(*GetFont()));
103
1.00M
    copy->SetFlags(GetFlags());
104
1.00M
    if (empty())
105
0
        return copy.release();
106
1.00M
    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
1.00M
    if (!(GetFlags() & SalLayoutFlags::BiDiStrong)
113
1.00M
        || rtl != bool(GetFlags() & SalLayoutFlags::BiDiRtl))
114
6
        return nullptr;
115
1.00M
    copy->reserve(std::min<size_t>(size(), length));
116
1.00M
    sal_Int32 beginPos = index;
117
1.00M
    sal_Int32 endPos = index + length;
118
1.00M
    const_iterator pos;
119
1.00M
    if (rtl)
120
336
    {
121
        // Glyphs are in reverse order for RTL.
122
336
        beginPos = index + length - 1;
123
336
        endPos = index - 1;
124
        // Skip glyphs that are in the string after the given index, i.e. are before the glyphs
125
        // we want.
126
336
        pos = std::partition_point(
127
3.71k
            begin(), end(), [beginPos](const GlyphItem& it) { return it.charPos() > beginPos; });
128
336
    }
129
1.00M
    else
130
1.00M
    {
131
        // Skip glyphs that are in the string before the given index (glyphs are sorted by charPos()).
132
1.00M
        pos = std::partition_point(
133
6.85M
            begin(), end(), [beginPos](const GlyphItem& it) { return it.charPos() < beginPos; });
134
1.00M
    }
135
1.00M
    if (pos == end())
136
1.46k
        return nullptr;
137
    // Require a start at the exact position given, otherwise bail out.
138
1.00M
    if (pos->charPos() != beginPos)
139
41.2k
        return nullptr;
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
959k
    if (rtl && (pos->charPos() + pos->charCount() > beginPos + 1 || pos->IsInCluster()))
144
0
        return nullptr;
145
959k
    if (!isSafeToBreak(pos, rtl))
146
1.10k
        return nullptr;
147
    // LinearPos needs adjusting to start at xOffset/yOffset for the first item,
148
    // that's how it's computed in GenericSalLayout::LayoutText().
149
958k
    basegfx::B2DPoint zeroPoint
150
958k
        = 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.
158
62.4M
    while (pos != end()
159
61.9M
           && (rtl ? pos->charPos() - pos->charCount() >= endPos
160
61.9M
                   : pos->charPos() + pos->charCount() <= endPos))
161
61.4M
    {
162
61.4M
        if (pos->IsRTLGlyph() != rtl)
163
0
            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
61.4M
        if (pos->charCount() == 0 && pos->origWidth() == 0)
168
6.82k
            return nullptr;
169
61.4M
        copy->push_back(*pos);
170
61.4M
        copy->back().setLinearPos(copy->back().linearPos() - zeroPoint);
171
61.4M
        ++pos;
172
61.4M
    }
173
952k
    if (pos != end())
174
453k
    {
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
453k
        if (rtl ? pos->charPos() + pos->charCount() != endPos + 1 : pos->charPos() != endPos)
178
6.08k
            return nullptr;
179
447k
        if (!isSafeToBreak(pos, rtl))
180
1.94k
            return nullptr;
181
447k
    }
182
943k
    return copy.release();
183
952k
}
184
185
bool SalLayoutGlyphsImpl::isSafeToBreak(const_iterator pos, bool rtl) const
186
1.40M
{
187
1.40M
    if (rtl)
188
662
    {
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.
194
662
        if (pos == begin())
195
21
            return true;
196
641
        --pos;
197
641
    }
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
1.40M
    if (pos->IsUnsafeToBreak() || (pos->IsInCluster() && !pos->IsClusterStart()))
201
3.04k
        return false;
202
1.40M
    return true;
203
1.40M
}
204
205
#ifdef DBG_UTIL
206
bool SalLayoutGlyphsImpl::isLayoutEquivalent(const SalLayoutGlyphsImpl* other) const
207
{
208
    if (!GetFont()->mxFontMetric->CompareDeviceIndependentFontAttributes(
209
            *other->GetFont()->mxFontMetric))
210
        return false;
211
    if (GetFlags() != other->GetFlags())
212
        return false;
213
    if (empty() || other->empty())
214
        return empty() == other->empty();
215
    if (size() != other->size())
216
        return false;
217
    for (size_t pos = 0; pos < size(); ++pos)
218
    {
219
        if (!(*this)[pos].isLayoutEquivalent((*other)[pos]))
220
            return false;
221
    }
222
    return true;
223
}
224
#endif
225
226
bool SalLayoutGlyphsImpl::IsValid() const
227
33.5M
{
228
33.5M
    if (!m_rFontInstance.is())
229
0
        return false;
230
33.5M
    return true;
231
33.5M
}
232
233
3.93k
void SalLayoutGlyphsCache::clear() { mCachedGlyphs.clear(); }
234
235
SalLayoutGlyphsCache* SalLayoutGlyphsCache::self()
236
8.35M
{
237
8.35M
    static tools::DeleteOnDeinit<SalLayoutGlyphsCache> cache(
238
8.35M
        !comphelper::IsFuzzing() ? officecfg::Office::Common::Cache::Font::GlyphsCacheSize::get()
239
8.35M
                                 : 20000000);
240
8.35M
    return cache.get();
241
8.35M
}
242
243
static UBiDiDirection getBiDiDirection(std::u16string_view text, sal_Int32 index, sal_Int32 len)
244
1.06M
{
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
1.06M
    sal_Int32 end = index + len;
249
1.06M
    UBiDiDirection direction = UBIDI_NEUTRAL;
250
154M
    while (index < end)
251
153M
    {
252
153M
        switch (u_charDirection(o3tl::iterateCodePoints(text, &index)))
253
153M
        {
254
            // Only characters with strong direction.
255
49.8M
            case U_LEFT_TO_RIGHT:
256
49.8M
                if (direction == UBIDI_RTL)
257
0
                    return UBIDI_MIXED;
258
49.8M
                direction = UBIDI_LTR;
259
49.8M
                break;
260
26
            case U_RIGHT_TO_LEFT:
261
159
            case U_RIGHT_TO_LEFT_ARABIC:
262
159
                if (direction == UBIDI_LTR)
263
143
                    return UBIDI_MIXED;
264
16
                direction = UBIDI_RTL;
265
16
                break;
266
103M
            default:
267
103M
                break;
268
153M
        }
269
153M
    }
270
1.06M
    return direction;
271
1.06M
}
272
273
static SalLayoutGlyphs makeGlyphsSubset(const SalLayoutGlyphs& source,
274
                                        const OutputDevice* outputDevice, std::u16string_view text,
275
                                        sal_Int32 index, sal_Int32 len)
276
1.06M
{
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
1.06M
    UBiDiDirection direction = getBiDiDirection(text, index, len);
282
1.06M
    if (direction == UBIDI_MIXED)
283
143
        return SalLayoutGlyphs();
284
1.06M
    SalLayoutGlyphs ret;
285
1.06M
    for (int level = 0;; ++level)
286
2.00M
    {
287
2.00M
        const SalLayoutGlyphsImpl* sourceLevel = source.Impl(level);
288
2.00M
        if (sourceLevel == nullptr)
289
943k
            break;
290
1.06M
        bool sourceRtl = bool(sourceLevel->GetFlags() & SalLayoutFlags::BiDiRtl);
291
1.06M
        if ((direction == UBIDI_LTR && sourceRtl) || (direction == UBIDI_RTL && !sourceRtl))
292
58.6k
            return SalLayoutGlyphs();
293
1.00M
        SalLayoutGlyphsImpl* cloned = sourceLevel->cloneCharRange(index, len);
294
        // If the glyphs range cannot be cloned, bail out.
295
1.00M
        if (cloned == nullptr)
296
58.7k
            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
943k
        cloned->SetFlags(cloned->GetFlags()
301
943k
                         | outputDevice->GetBiDiLayoutFlags(text, index, index + len));
302
943k
        ret.AppendImpl(cloned);
303
943k
    }
304
943k
    return ret;
305
1.06M
}
306
307
#ifdef DBG_UTIL
308
static void checkGlyphsEqual(const SalLayoutGlyphs& g1, const SalLayoutGlyphs& g2)
309
{
310
    for (int level = 0;; ++level)
311
    {
312
        const SalLayoutGlyphsImpl* l1 = g1.Impl(level);
313
        const SalLayoutGlyphsImpl* l2 = g2.Impl(level);
314
        if (l1 == nullptr || l2 == nullptr)
315
        {
316
            assert(l1 == l2);
317
            break;
318
        }
319
        assert(l1->isLayoutEquivalent(l2));
320
    }
321
}
322
#endif
323
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)
327
8.35M
{
328
8.35M
    if (nLen == 0)
329
27.2k
        return nullptr;
330
8.32M
    const CachedGlyphsKey key(outputDevice, text, nIndex, nLen, nLogicWidth);
331
8.32M
    GlyphsCache::const_iterator it = mCachedGlyphs.find(key);
332
8.32M
    if (it != mCachedGlyphs.end())
333
3.56M
    {
334
3.56M
        if (it->second.IsValid())
335
3.56M
            return &it->second;
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.
339
0
        return nullptr;
340
3.56M
    }
341
4.75M
    bool resetLastSubstringKey = true;
342
4.75M
    const sal_Unicode nbSpace = 0xa0; // non-breaking space
343
    // SalLayoutGlyphsImpl::cloneCharRange() requires BiDiStrong, so if not set, do not even try.
344
4.75M
    bool skipGlyphSubsets
345
4.75M
        = !(outputDevice->GetLayoutMode() & vcl::text::ComplexTextLayoutFlags::BiDiStrong);
346
4.75M
    if ((nIndex != 0 || nLen != text.getLength()) && !skipGlyphSubsets)
347
1.72M
    {
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
1.72M
        if (mLastTemporaryKey.has_value() && mLastTemporaryKey == key)
353
11.6k
            return &mLastTemporaryGlyphs;
354
1.71M
        const CachedGlyphsKey keyWhole(outputDevice, text, 0, text.getLength(), nLogicWidth);
355
1.71M
        GlyphsCache::const_iterator itWhole = mCachedGlyphs.find(keyWhole);
356
1.71M
        if (itWhole == mCachedGlyphs.end())
357
1.03M
        {
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?
367
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
1.03M
            constexpr sal_Int32 nOptLengthThreshold = 20000;
372
1.03M
            bool bEnableOptimization = (text.getLength() < nOptLengthThreshold);
373
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
1.03M
            static const bool bAbortOnFontSubstitute = [] {
381
14
                const char* pEnv = getenv("SAL_NON_APPLICATION_FONT_USE");
382
14
                return pEnv && strcmp(pEnv, "abort") == 0;
383
14
            }();
384
1.03M
            if (bEnableOptimization && mLastSubstringKey.has_value() && !bAbortOnFontSubstitute)
385
802k
            {
386
802k
                sal_Int32 pos = nIndex;
387
802k
                if (mLastSubstringKey->len < pos && text[pos - 1] == nbSpace)
388
4.61k
                    --pos; // Writer skips a non-breaking space, so skip that character too.
389
802k
                if ((mLastSubstringKey->len == pos || mLastSubstringKey->index == nIndex)
390
169k
                    && mLastSubstringKey
391
169k
                           == CachedGlyphsKey(outputDevice, text, mLastSubstringKey->index,
392
169k
                                              mLastSubstringKey->len, nLogicWidth))
393
5.04k
                {
394
5.04k
                    GetLayoutGlyphs(outputDevice, text, 0, text.getLength(), nLogicWidth,
395
5.04k
                                    layoutCache);
396
5.04k
                    itWhole = mCachedGlyphs.find(keyWhole);
397
5.04k
                }
398
797k
                else
399
797k
                    mLastSubstringKey.reset();
400
802k
            }
401
1.03M
            if (!mLastSubstringKey.has_value())
402
966k
            {
403
966k
                mLastSubstringKey = key;
404
966k
                resetLastSubstringKey = false;
405
966k
            }
406
1.03M
        }
407
1.71M
        if (itWhole != mCachedGlyphs.end() && itWhole->second.IsValid())
408
678k
        {
409
678k
            mLastSubstringKey.reset();
410
678k
            mLastTemporaryGlyphs
411
678k
                = makeGlyphsSubset(itWhole->second, outputDevice, text, nIndex, nLen);
412
678k
            if (mLastTemporaryGlyphs.IsValid())
413
590k
            {
414
590k
                mLastTemporaryKey = key;
415
#ifdef DBG_UTIL
416
                std::shared_ptr<const vcl::text::TextLayoutCache> tmpLayoutCache;
417
                if (layoutCache == nullptr)
418
                {
419
                    tmpLayoutCache = vcl::text::TextLayoutCache::Create(text);
420
                    layoutCache = tmpLayoutCache.get();
421
                }
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);
427
                assert(layout);
428
                checkGlyphsEqual(mLastTemporaryGlyphs, layout->GetGlyphs());
429
#endif
430
590k
                return &mLastTemporaryGlyphs;
431
590k
            }
432
678k
        }
433
1.71M
    }
434
4.15M
    if (resetLastSubstringKey)
435
3.19M
    {
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
3.19M
        if (nLen != 1 || text[nIndex] != nbSpace)
440
3.19M
            mLastSubstringKey.reset();
441
3.19M
    }
442
443
4.15M
    std::shared_ptr<const vcl::text::TextLayoutCache> tmpLayoutCache;
444
4.15M
    if (layoutCache == nullptr)
445
3.79M
    {
446
3.79M
        tmpLayoutCache = vcl::text::TextLayoutCache::Create(text);
447
3.79M
        layoutCache = tmpLayoutCache.get();
448
3.79M
    }
449
4.15M
    std::unique_ptr<SalLayout> layout
450
4.15M
        = outputDevice->ImplLayout(text, nIndex, nLen, Point(0, 0), nLogicWidth, {}, {},
451
4.15M
                                   SalLayoutFlags::GlyphItemsOnly, layoutCache);
452
4.15M
    if (layout)
453
4.15M
    {
454
4.15M
        SalLayoutGlyphs glyphs = layout->GetGlyphs();
455
4.15M
        if (glyphs.IsValid())
456
4.15M
        {
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
4.15M
            if (!mbCacheGlyphsWhenDoingFallbackFonts && glyphs.Impl(1) != nullptr)
460
0
            {
461
0
                mLastTemporaryGlyphs = std::move(glyphs);
462
0
                mLastTemporaryKey.reset();
463
0
                return &mLastTemporaryGlyphs;
464
0
            }
465
4.15M
            mCachedGlyphs.insert(std::make_pair(key, std::move(glyphs)));
466
4.15M
            assert(mCachedGlyphs.find(key)
467
4.15M
                   == mCachedGlyphs.begin()); // newly inserted item is first
468
4.15M
            return &mCachedGlyphs.begin()->second;
469
4.15M
        }
470
4.15M
    }
471
    // Failure, cache it too as invalid glyphs.
472
0
    mCachedGlyphs.insert(std::make_pair(key, SalLayoutGlyphs()));
473
0
    return nullptr;
474
4.15M
}
475
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)
480
686k
{
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.
483
686k
    auto* pWholeGlyphs
484
686k
        = GetLayoutGlyphs(outputDevice, text, nIndex, nLen, nLogicWidth, layoutCache);
485
686k
    if (nDrawMinCharPos <= nIndex && nDrawEndCharPos >= (nIndex + nLen))
486
303k
    {
487
303k
        return pWholeGlyphs;
488
303k
    }
489
490
382k
    if (pWholeGlyphs && pWholeGlyphs->IsValid())
491
382k
    {
492
382k
        mLastTemporaryKey.reset();
493
382k
        mLastTemporaryGlyphs = makeGlyphsSubset(*pWholeGlyphs, outputDevice, text, nDrawMinCharPos,
494
382k
                                                nDrawEndCharPos - nDrawMinCharPos);
495
382k
        if (mLastTemporaryGlyphs.IsValid())
496
353k
        {
497
353k
            return &mLastTemporaryGlyphs;
498
353k
        }
499
382k
    }
500
501
29.1k
    return nullptr;
502
382k
}
503
504
void SalLayoutGlyphsCache::SetCacheGlyphsWhenDoingFallbackFonts(bool bOK)
505
7.87k
{
506
7.87k
    mbCacheGlyphsWhenDoingFallbackFonts = bOK;
507
7.87k
    if (!bOK)
508
3.93k
        clear();
509
7.87k
}
510
511
SalLayoutGlyphsCache::CachedGlyphsKey::CachedGlyphsKey(
512
    const VclPtr<const OutputDevice>& outputDevice, OUString t, sal_Int32 i, sal_Int32 l,
513
    tools::Long w)
514
10.2M
    : text(std::move(t))
515
10.2M
    , index(i)
516
10.2M
    , len(l)
517
10.2M
    , logicWidth(w)
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
10.2M
    , 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
10.2M
    , mapMode(outputDevice->GetMapMode())
526
10.2M
    , digitLanguage(outputDevice->GetDigitLanguage())
527
10.2M
    , layoutMode(outputDevice->GetLayoutMode())
528
10.2M
    , rtl(outputDevice->IsRTLEnabled())
529
10.2M
{
530
10.2M
    const LogicalFontInstance* fi = outputDevice->GetFontInstance();
531
10.2M
    fi->GetScale(&fontScaleX, &fontScaleY);
532
533
10.2M
    const vcl::font::FontSelectPattern& rFSD = fi->GetFontSelectPattern();
534
10.2M
    disabledLigatures = rFSD.GetPitch() == PITCH_FIXED;
535
10.2M
    artificialItalic = fi->NeedsArtificialItalic();
536
10.2M
    artificialBold = fi->NeedsArtificialBold();
537
538
10.2M
    hashValue = 0;
539
10.2M
    o3tl::hash_combine(hashValue, vcl::text::FirstCharsStringHash()(text));
540
10.2M
    o3tl::hash_combine(hashValue, index);
541
10.2M
    o3tl::hash_combine(hashValue, len);
542
10.2M
    o3tl::hash_combine(hashValue, logicWidth);
543
10.2M
    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
10.2M
    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
10.2M
    o3tl::hash_combine(hashValue, fontScaleX);
550
10.2M
    o3tl::hash_combine(hashValue, fontScaleY);
551
10.2M
    o3tl::hash_combine(hashValue, mapMode.GetHashValue());
552
10.2M
    o3tl::hash_combine(hashValue, rtl);
553
10.2M
    o3tl::hash_combine(hashValue, disabledLigatures);
554
10.2M
    o3tl::hash_combine(hashValue, artificialItalic);
555
10.2M
    o3tl::hash_combine(hashValue, artificialBold);
556
10.2M
    o3tl::hash_combine(hashValue, layoutMode);
557
10.2M
    o3tl::hash_combine(hashValue, digitLanguage.get());
558
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
561
    // font.
562
10.2M
    o3tl::hash_combine(hashValue, outputDevice->GetFont().GetFamilyType());
563
10.2M
}
564
565
bool SalLayoutGlyphsCache::CachedGlyphsKey::operator==(const CachedGlyphsKey& other) const
566
8.30M
{
567
8.30M
    return hashValue == other.hashValue && index == other.index && len == other.len
568
7.35M
           && logicWidth == other.logicWidth && mapMode == other.mapMode && rtl == other.rtl
569
7.35M
           && disabledLigatures == other.disabledLigatures
570
7.35M
           && artificialItalic == other.artificialItalic && artificialBold == other.artificialBold
571
7.35M
           && layoutMode == other.layoutMode && digitLanguage == other.digitLanguage
572
7.35M
           && fontScaleX == other.fontScaleX && fontScaleY == other.fontScaleY
573
7.35M
           && fontMetric.EqualIgnoreColor(other.fontMetric)
574
7.35M
           && vcl::text::FastStringCompareEqual()(text, other.text);
575
    // Slower things last in the comparison.
576
8.30M
}
577
578
size_t SalLayoutGlyphsCache::GlyphsCost::operator()(const SalLayoutGlyphs& glyphs) const
579
7.06M
{
580
7.06M
    size_t cost = 0;
581
7.06M
    for (int level = 0;; ++level)
582
14.1M
    {
583
14.1M
        const SalLayoutGlyphsImpl* impl = glyphs.Impl(level);
584
14.1M
        if (impl == nullptr)
585
7.06M
            break;
586
        // Count size in bytes, both the SalLayoutGlyphsImpl instance and contained GlyphItem's.
587
7.06M
        cost += sizeof(*impl);
588
7.06M
        cost += impl->size() * sizeof(impl->front());
589
7.06M
    }
590
7.06M
    return cost;
591
7.06M
}
592
593
0
OUString SalLayoutGlyphsCache::getCacheName() const { return "SalLayoutGlyphsCache"; }
594
595
bool SalLayoutGlyphsCache::dropCaches()
596
0
{
597
0
    clear();
598
0
    return true;
599
0
}
600
601
void SalLayoutGlyphsCache::dumpState(rtl::OStringBuffer& rState)
602
0
{
603
0
    rState.append("\nSalLayoutGlyphsCache:\t");
604
0
    rState.append(static_cast<sal_Int32>(mCachedGlyphs.size()));
605
0
}
606
607
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */