/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: */ |