Coverage Report

Created: 2025-12-08 09:28

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 <impglyphitem.hxx>
21
#include <utility>
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>
30
31
#include <unicode/ubidi.h>
32
#include <unicode/uchar.h>
33
34
// These need being explicit because of SalLayoutGlyphsImpl being private in vcl.
35
6.74M
SalLayoutGlyphs::SalLayoutGlyphs() {}
36
37
18.6M
SalLayoutGlyphs::~SalLayoutGlyphs() {}
38
39
SalLayoutGlyphs::SalLayoutGlyphs(SalLayoutGlyphs&& rOther) noexcept
40
11.8M
{
41
11.8M
    std::swap(m_pImpl, rOther.m_pImpl);
42
11.8M
    std::swap(m_pExtraImpls, rOther.m_pExtraImpls);
43
11.8M
}
44
45
SalLayoutGlyphs& SalLayoutGlyphs::operator=(SalLayoutGlyphs&& rOther) noexcept
46
1.24M
{
47
1.24M
    if (this != &rOther)
48
1.24M
    {
49
1.24M
        std::swap(m_pImpl, rOther.m_pImpl);
50
1.24M
        std::swap(m_pExtraImpls, rOther.m_pExtraImpls);
51
1.24M
    }
52
1.24M
    return *this;
53
1.24M
}
54
55
bool SalLayoutGlyphs::IsValid() const
56
21.9M
{
57
21.9M
    if (m_pImpl == nullptr)
58
124k
        return false;
59
21.8M
    if (!m_pImpl->IsValid())
60
0
        return false;
61
21.8M
    if (m_pExtraImpls)
62
0
        for (std::unique_ptr<SalLayoutGlyphsImpl> const& impl : *m_pExtraImpls)
63
0
            if (!impl->IsValid())
64
0
                return false;
65
21.8M
    return true;
66
21.8M
}
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
33.8M
{
77
33.8M
    if (nLevel == 0)
78
20.0M
        return m_pImpl.get();
79
13.8M
    if (m_pExtraImpls != nullptr && nLevel - 1 < m_pExtraImpls->size())
80
0
        return (*m_pExtraImpls)[nLevel - 1].get();
81
13.8M
    return nullptr;
82
13.8M
}
83
84
void SalLayoutGlyphs::AppendImpl(SalLayoutGlyphsImpl* pImpl)
85
6.49M
{
86
6.49M
    if (!m_pImpl)
87
6.49M
        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
6.49M
}
95
96
5.36M
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.18M
{
102
1.18M
    std::unique_ptr<SalLayoutGlyphsImpl> copy(new SalLayoutGlyphsImpl(*GetFont()));
103
1.18M
    copy->SetFlags(GetFlags());
104
1.18M
    if (empty())
105
0
        return copy.release();
106
1.18M
    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.18M
    if (!(GetFlags() & SalLayoutFlags::BiDiStrong)
113
1.18M
        || rtl != bool(GetFlags() & SalLayoutFlags::BiDiRtl))
114
0
        return nullptr;
115
1.18M
    copy->reserve(std::min<size_t>(size(), length));
116
1.18M
    sal_Int32 beginPos = index;
117
1.18M
    sal_Int32 endPos = index + length;
118
1.18M
    const_iterator pos;
119
1.18M
    if (rtl)
120
392
    {
121
        // Glyphs are in reverse order for RTL.
122
392
        beginPos = index + length - 1;
123
392
        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
392
        pos = std::partition_point(
127
3.99k
            begin(), end(), [beginPos](const GlyphItem& it) { return it.charPos() > beginPos; });
128
392
    }
129
1.18M
    else
130
1.18M
    {
131
        // Skip glyphs that are in the string before the given index (glyphs are sorted by charPos()).
132
1.18M
        pos = std::partition_point(
133
7.98M
            begin(), end(), [beginPos](const GlyphItem& it) { return it.charPos() < beginPos; });
134
1.18M
    }
135
1.18M
    if (pos == end())
136
1.37k
        return nullptr;
137
    // Require a start at the exact position given, otherwise bail out.
138
1.18M
    if (pos->charPos() != beginPos)
139
40.7k
        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
1.14M
    if (rtl && (pos->charPos() + pos->charCount() > beginPos + 1 || pos->IsInCluster()))
144
0
        return nullptr;
145
1.14M
    if (!isSafeToBreak(pos, rtl))
146
1.28k
        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
1.14M
    basegfx::B2DPoint zeroPoint
150
1.14M
        = 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
63.2M
    while (pos != end()
159
62.7M
           && (rtl ? pos->charPos() - pos->charCount() >= endPos
160
62.7M
                   : pos->charPos() + pos->charCount() <= endPos))
161
62.1M
    {
162
62.1M
        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
62.1M
        if (pos->charCount() == 0 && pos->origWidth() == 0)
168
8.58k
            return nullptr;
169
62.1M
        copy->push_back(*pos);
170
62.1M
        copy->back().setLinearPos(copy->back().linearPos() - zeroPoint);
171
62.1M
        ++pos;
172
62.1M
    }
173
1.13M
    if (pos != end())
174
649k
    {
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
649k
        if (rtl ? pos->charPos() + pos->charCount() != endPos + 1 : pos->charPos() != endPos)
178
6.64k
            return nullptr;
179
642k
        if (!isSafeToBreak(pos, rtl))
180
1.52k
            return nullptr;
181
642k
    }
182
1.12M
    return copy.release();
183
1.13M
}
184
185
bool SalLayoutGlyphsImpl::isSafeToBreak(const_iterator pos, bool rtl) const
186
1.78M
{
187
1.78M
    if (rtl)
188
623
    {
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
623
        if (pos == begin())
195
26
            return true;
196
597
        --pos;
197
597
    }
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.78M
    if (pos->IsUnsafeToBreak() || (pos->IsInCluster() && !pos->IsClusterStart()))
201
2.81k
        return false;
202
1.78M
    return true;
203
1.78M
}
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
41.6M
{
228
41.6M
    if (!m_rFontInstance.is())
229
0
        return false;
230
41.6M
    return true;
231
41.6M
}
232
233
4.26k
void SalLayoutGlyphsCache::clear() { mCachedGlyphs.clear(); }
234
235
SalLayoutGlyphsCache* SalLayoutGlyphsCache::self()
236
10.0M
{
237
10.0M
    static tools::DeleteOnDeinit<SalLayoutGlyphsCache> cache(
238
10.0M
        !comphelper::IsFuzzing() ? officecfg::Office::Common::Cache::Font::GlyphsCacheSize::get()
239
10.0M
                                 : 20000000);
240
10.0M
    return cache.get();
241
10.0M
}
242
243
static UBiDiDirection getBiDiDirection(std::u16string_view text, sal_Int32 index, sal_Int32 len)
244
1.24M
{
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.24M
    sal_Int32 end = index + len;
249
1.24M
    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
50.8M
            case U_LEFT_TO_RIGHT:
256
50.8M
                if (direction == UBIDI_RTL)
257
2
                    return UBIDI_MIXED;
258
50.8M
                direction = UBIDI_LTR;
259
50.8M
                break;
260
47
            case U_RIGHT_TO_LEFT:
261
830
            case U_RIGHT_TO_LEFT_ARABIC:
262
830
                if (direction == UBIDI_LTR)
263
739
                    return UBIDI_MIXED;
264
91
                direction = UBIDI_RTL;
265
91
                break;
266
102M
            default:
267
102M
                break;
268
153M
        }
269
153M
    }
270
1.24M
    return direction;
271
1.24M
}
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.24M
{
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.24M
    UBiDiDirection direction = getBiDiDirection(text, index, len);
282
1.24M
    if (direction == UBIDI_MIXED)
283
741
        return SalLayoutGlyphs();
284
1.24M
    SalLayoutGlyphs ret;
285
1.24M
    for (int level = 0;; ++level)
286
2.37M
    {
287
2.37M
        const SalLayoutGlyphsImpl* sourceLevel = source.Impl(level);
288
2.37M
        if (sourceLevel == nullptr)
289
1.12M
            break;
290
1.24M
        bool sourceRtl = bool(sourceLevel->GetFlags() & SalLayoutFlags::BiDiRtl);
291
1.24M
        if ((direction == UBIDI_LTR && sourceRtl) || (direction == UBIDI_RTL && !sourceRtl))
292
63.0k
            return SalLayoutGlyphs();
293
1.18M
        SalLayoutGlyphsImpl* cloned = sourceLevel->cloneCharRange(index, len);
294
        // If the glyphs range cannot be cloned, bail out.
295
1.18M
        if (cloned == nullptr)
296
60.1k
            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
1.12M
        cloned->SetFlags(cloned->GetFlags()
301
1.12M
                         | outputDevice->GetBiDiLayoutFlags(text, index, index + len));
302
1.12M
        ret.AppendImpl(cloned);
303
1.12M
    }
304
1.12M
    return ret;
305
1.24M
}
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
10.0M
{
328
10.0M
    if (nLen == 0)
329
47.8k
        return nullptr;
330
10.0M
    const CachedGlyphsKey key(outputDevice, text, nIndex, nLen, nLogicWidth);
331
10.0M
    GlyphsCache::const_iterator it = mCachedGlyphs.find(key);
332
10.0M
    if (it != mCachedGlyphs.end())
333
4.07M
    {
334
4.07M
        if (it->second.IsValid())
335
4.07M
            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
4.07M
    }
341
5.96M
    bool resetLastSubstringKey = true;
342
5.96M
    const sal_Unicode nbSpace = 0xa0; // non-breaking space
343
    // SalLayoutGlyphsImpl::cloneCharRange() requires BiDiStrong, so if not set, do not even try.
344
5.96M
    bool skipGlyphSubsets
345
5.96M
        = !(outputDevice->GetLayoutMode() & vcl::text::ComplexTextLayoutFlags::BiDiStrong);
346
5.96M
    if ((nIndex != 0 || nLen != text.getLength()) && !skipGlyphSubsets)
347
2.49M
    {
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
2.49M
        if (mLastTemporaryKey.has_value() && mLastTemporaryKey == key)
353
11.3k
            return &mLastTemporaryGlyphs;
354
2.48M
        const CachedGlyphsKey keyWhole(outputDevice, text, 0, text.getLength(), nLogicWidth);
355
2.48M
        GlyphsCache::const_iterator itWhole = mCachedGlyphs.find(keyWhole);
356
2.48M
        if (itWhole == mCachedGlyphs.end())
357
1.83M
        {
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.83M
            constexpr sal_Int32 nOptLengthThreshold = 20000;
372
1.83M
            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.83M
            static const bool bAbortOnFontSubstitute = [] {
381
13
                const char* pEnv = getenv("SAL_NON_APPLICATION_FONT_USE");
382
13
                return pEnv && strcmp(pEnv, "abort") == 0;
383
13
            }();
384
1.83M
            if (bEnableOptimization && mLastSubstringKey.has_value() && !bAbortOnFontSubstitute)
385
1.76M
            {
386
1.76M
                sal_Int32 pos = nIndex;
387
1.76M
                if (mLastSubstringKey->len < pos && text[pos - 1] == nbSpace)
388
745
                    --pos; // Writer skips a non-breaking space, so skip that character too.
389
1.76M
                if ((mLastSubstringKey->len == pos || mLastSubstringKey->index == nIndex)
390
479k
                    && mLastSubstringKey
391
479k
                           == CachedGlyphsKey(outputDevice, text, mLastSubstringKey->index,
392
479k
                                              mLastSubstringKey->len, nLogicWidth))
393
5.18k
                {
394
5.18k
                    GetLayoutGlyphs(outputDevice, text, 0, text.getLength(), nLogicWidth,
395
5.18k
                                    layoutCache);
396
5.18k
                    itWhole = mCachedGlyphs.find(keyWhole);
397
5.18k
                }
398
1.76M
                else
399
1.76M
                    mLastSubstringKey.reset();
400
1.76M
            }
401
1.83M
            if (!mLastSubstringKey.has_value())
402
1.83M
            {
403
1.83M
                mLastSubstringKey = key;
404
1.83M
                resetLastSubstringKey = false;
405
1.83M
            }
406
1.83M
        }
407
2.48M
        if (itWhole != mCachedGlyphs.end() && itWhole->second.IsValid())
408
661k
        {
409
661k
            mLastSubstringKey.reset();
410
661k
            mLastTemporaryGlyphs
411
661k
                = makeGlyphsSubset(itWhole->second, outputDevice, text, nIndex, nLen);
412
661k
            if (mLastTemporaryGlyphs.IsValid())
413
583k
            {
414
583k
                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
583k
                return &mLastTemporaryGlyphs;
431
583k
            }
432
661k
        }
433
2.48M
    }
434
5.36M
    if (resetLastSubstringKey)
435
3.53M
    {
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.53M
        if (nLen != 1 || text[nIndex] != nbSpace)
440
3.53M
            mLastSubstringKey.reset();
441
3.53M
    }
442
443
5.36M
    std::shared_ptr<const vcl::text::TextLayoutCache> tmpLayoutCache;
444
5.36M
    if (layoutCache == nullptr)
445
4.82M
    {
446
4.82M
        tmpLayoutCache = vcl::text::TextLayoutCache::Create(text);
447
4.82M
        layoutCache = tmpLayoutCache.get();
448
4.82M
    }
449
5.36M
    std::unique_ptr<SalLayout> layout
450
5.36M
        = outputDevice->ImplLayout(text, nIndex, nLen, Point(0, 0), nLogicWidth, {}, {},
451
5.36M
                                   SalLayoutFlags::GlyphItemsOnly, layoutCache);
452
5.36M
    if (layout)
453
5.36M
    {
454
5.36M
        SalLayoutGlyphs glyphs = layout->GetGlyphs();
455
5.36M
        if (glyphs.IsValid())
456
5.36M
        {
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
5.36M
            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
5.36M
            mCachedGlyphs.insert(std::make_pair(key, std::move(glyphs)));
466
5.36M
            assert(mCachedGlyphs.find(key)
467
5.36M
                   == mCachedGlyphs.begin()); // newly inserted item is first
468
5.36M
            return &mCachedGlyphs.begin()->second;
469
5.36M
        }
470
5.36M
    }
471
    // Failure, cache it too as invalid glyphs.
472
0
    mCachedGlyphs.insert(std::make_pair(key, SalLayoutGlyphs()));
473
0
    return nullptr;
474
5.36M
}
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
890k
{
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
890k
    auto* pWholeGlyphs
484
890k
        = GetLayoutGlyphs(outputDevice, text, nIndex, nLen, nLogicWidth, layoutCache);
485
890k
    if (nDrawMinCharPos <= nIndex && nDrawEndCharPos >= (nIndex + nLen))
486
302k
    {
487
302k
        return pWholeGlyphs;
488
302k
    }
489
490
588k
    if (pWholeGlyphs && pWholeGlyphs->IsValid())
491
588k
    {
492
588k
        mLastTemporaryKey.reset();
493
588k
        mLastTemporaryGlyphs = makeGlyphsSubset(*pWholeGlyphs, outputDevice, text, nDrawMinCharPos,
494
588k
                                                nDrawEndCharPos - nDrawMinCharPos);
495
588k
        if (mLastTemporaryGlyphs.IsValid())
496
541k
        {
497
541k
            return &mLastTemporaryGlyphs;
498
541k
        }
499
588k
    }
500
501
46.6k
    return nullptr;
502
588k
}
503
504
void SalLayoutGlyphsCache::SetCacheGlyphsWhenDoingFallbackFonts(bool bOK)
505
8.52k
{
506
8.52k
    mbCacheGlyphsWhenDoingFallbackFonts = bOK;
507
8.52k
    if (!bOK)
508
4.26k
        clear();
509
8.52k
}
510
511
SalLayoutGlyphsCache::CachedGlyphsKey::CachedGlyphsKey(
512
    const VclPtr<const OutputDevice>& outputDevice, OUString t, sal_Int32 i, sal_Int32 l,
513
    tools::Long w)
514
13.0M
    : text(std::move(t))
515
13.0M
    , index(i)
516
13.0M
    , len(l)
517
13.0M
    , 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
13.0M
    , 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
13.0M
    , mapMode(outputDevice->GetMapMode())
526
13.0M
    , digitLanguage(outputDevice->GetDigitLanguage())
527
13.0M
    , layoutMode(outputDevice->GetLayoutMode())
528
13.0M
    , rtl(outputDevice->IsRTLEnabled())
529
13.0M
{
530
13.0M
    const LogicalFontInstance* fi = outputDevice->GetFontInstance();
531
13.0M
    fi->GetScale(&fontScaleX, &fontScaleY);
532
533
13.0M
    const vcl::font::FontSelectPattern& rFSD = fi->GetFontSelectPattern();
534
13.0M
    disabledLigatures = rFSD.GetPitch() == PITCH_FIXED;
535
13.0M
    artificialItalic = fi->NeedsArtificialItalic();
536
13.0M
    artificialBold = fi->NeedsArtificialBold();
537
538
13.0M
    hashValue = 0;
539
13.0M
    o3tl::hash_combine(hashValue, vcl::text::FirstCharsStringHash()(text));
540
13.0M
    o3tl::hash_combine(hashValue, index);
541
13.0M
    o3tl::hash_combine(hashValue, len);
542
13.0M
    o3tl::hash_combine(hashValue, logicWidth);
543
13.0M
    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
13.0M
    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
13.0M
    o3tl::hash_combine(hashValue, fontScaleX);
550
13.0M
    o3tl::hash_combine(hashValue, fontScaleY);
551
13.0M
    o3tl::hash_combine(hashValue, mapMode.GetHashValue());
552
13.0M
    o3tl::hash_combine(hashValue, rtl);
553
13.0M
    o3tl::hash_combine(hashValue, disabledLigatures);
554
13.0M
    o3tl::hash_combine(hashValue, artificialItalic);
555
13.0M
    o3tl::hash_combine(hashValue, artificialBold);
556
13.0M
    o3tl::hash_combine(hashValue, layoutMode);
557
13.0M
    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
13.0M
    o3tl::hash_combine(hashValue, outputDevice->GetFont().GetFamilyType());
563
13.0M
}
564
565
bool SalLayoutGlyphsCache::CachedGlyphsKey::operator==(const CachedGlyphsKey& other) const
566
10.2M
{
567
10.2M
    return hashValue == other.hashValue && index == other.index && len == other.len
568
8.90M
           && logicWidth == other.logicWidth && mapMode == other.mapMode && rtl == other.rtl
569
8.90M
           && disabledLigatures == other.disabledLigatures
570
8.90M
           && artificialItalic == other.artificialItalic && artificialBold == other.artificialBold
571
8.90M
           && layoutMode == other.layoutMode && digitLanguage == other.digitLanguage
572
8.90M
           && fontScaleX == other.fontScaleX && fontScaleY == other.fontScaleY
573
8.90M
           && fontMetric.EqualIgnoreColor(other.fontMetric)
574
8.90M
           && vcl::text::FastStringCompareEqual()(text, other.text);
575
    // Slower things last in the comparison.
576
10.2M
}
577
578
size_t SalLayoutGlyphsCache::GlyphsCost::operator()(const SalLayoutGlyphs& glyphs) const
579
8.76M
{
580
8.76M
    size_t cost = 0;
581
8.76M
    for (int level = 0;; ++level)
582
17.5M
    {
583
17.5M
        const SalLayoutGlyphsImpl* impl = glyphs.Impl(level);
584
17.5M
        if (impl == nullptr)
585
8.76M
            break;
586
        // Count size in bytes, both the SalLayoutGlyphsImpl instance and contained GlyphItem's.
587
8.76M
        cost += sizeof(*impl);
588
8.76M
        cost += impl->size() * sizeof(impl->front());
589
8.76M
    }
590
8.76M
    return cost;
591
8.76M
}
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: */