diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/source/text | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source/text')
-rw-r--r-- | vcl/source/text/ImplLayoutArgs.cxx | 343 | ||||
-rw-r--r-- | vcl/source/text/ImplLayoutRuns.cxx | 179 | ||||
-rw-r--r-- | vcl/source/text/TextLayoutCache.cxx | 75 |
3 files changed, 597 insertions, 0 deletions
diff --git a/vcl/source/text/ImplLayoutArgs.cxx b/vcl/source/text/ImplLayoutArgs.cxx new file mode 100644 index 000000000..dbdc6e18f --- /dev/null +++ b/vcl/source/text/ImplLayoutArgs.cxx @@ -0,0 +1,343 @@ +/* -*- 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 <ImplLayoutArgs.hxx> + +#include <unicode/ubidi.h> +#include <unicode/uchar.h> + +#include <algorithm> +#include <memory> + +namespace vcl::text +{ +ImplLayoutArgs::ImplLayoutArgs(const OUString& rStr, int nMinCharPos, int nEndCharPos, + SalLayoutFlags nFlags, const LanguageTag& rLanguageTag, + vcl::text::TextLayoutCache const* const pLayoutCache) + : maLanguageTag(rLanguageTag) + , mnFlags(nFlags) + , mrStr(rStr) + , mnMinCharPos(nMinCharPos) + , mnEndCharPos(nEndCharPos) + , m_pTextLayoutCache(pLayoutCache) + , mpDXArray(nullptr) + , mpAltNaturalDXArray(nullptr) + , mnLayoutWidth(0) + , mnOrientation(0) +{ + if (mnFlags & SalLayoutFlags::BiDiStrong) + { + // handle strong BiDi mode + + // do not bother to BiDi analyze strong LTR/RTL + // TODO: can we assume these strings do not have unicode control chars? + // if not remove the control characters from the runs + bool bRTL(mnFlags & SalLayoutFlags::BiDiRtl); + AddRun(mnMinCharPos, mnEndCharPos, bRTL); + } + else + { + // handle weak BiDi mode + UBiDiLevel nLevel = (mnFlags & SalLayoutFlags::BiDiRtl) ? 1 : 0; + + // prepare substring for BiDi analysis + // TODO: reuse allocated pParaBidi + UErrorCode rcI18n = U_ZERO_ERROR; + const int nLength = mnEndCharPos - mnMinCharPos; + UBiDi* pParaBidi = ubidi_openSized(nLength, 0, &rcI18n); + if (!pParaBidi) + return; + ubidi_setPara(pParaBidi, reinterpret_cast<const UChar*>(mrStr.getStr()) + mnMinCharPos, + nLength, nLevel, nullptr, &rcI18n); + + // run BiDi algorithm + const int nRunCount = ubidi_countRuns(pParaBidi, &rcI18n); + for (int i = 0; i < nRunCount; ++i) + { + int32_t nMinPos, nRunLength; + const UBiDiDirection nDir = ubidi_getVisualRun(pParaBidi, i, &nMinPos, &nRunLength); + const int nPos0 = nMinPos + mnMinCharPos; + const int nPos1 = nPos0 + nRunLength; + + const bool bRTL = (nDir == UBIDI_RTL); + AddRun(nPos0, nPos1, bRTL); + } + + // cleanup BiDi engine + ubidi_close(pParaBidi); + } + + // prepare calls to GetNextPos/GetNextRun + maRuns.ResetPos(); +} + +void ImplLayoutArgs::SetLayoutWidth(DeviceCoordinate nWidth) { mnLayoutWidth = nWidth; } + +void ImplLayoutArgs::SetDXArray(DeviceCoordinate const* pDXArray) { mpDXArray = pDXArray; } + +void ImplLayoutArgs::SetAltNaturalDXArray(double const* pDXArray) +{ + mpAltNaturalDXArray = pDXArray; +} + +void ImplLayoutArgs::SetOrientation(Degree10 nOrientation) { mnOrientation = nOrientation; } + +void ImplLayoutArgs::ResetPos() { maRuns.ResetPos(); } + +bool ImplLayoutArgs::GetNextPos(int* nCharPos, bool* bRTL) +{ + return maRuns.GetNextPos(nCharPos, bRTL); +} + +void ImplLayoutArgs::AddFallbackRun(int nMinRunPos, int nEndRunPos, bool bRTL) +{ + maFallbackRuns.AddRun(nMinRunPos, nEndRunPos, bRTL); +} + +bool ImplLayoutArgs::HasFallbackRun() const { return !maFallbackRuns.IsEmpty(); } + +static bool IsControlChar(sal_UCS4 cChar) +{ + // C0 control characters + if ((0x0001 <= cChar) && (cChar <= 0x001F)) + return true; + // formatting characters + if ((0x200E <= cChar) && (cChar <= 0x200F)) + return true; + if ((0x2028 <= cChar) && (cChar <= 0x202E)) + return true; + // deprecated formatting characters + if ((0x206A <= cChar) && (cChar <= 0x206F)) + return true; + if (0x2060 == cChar) + return true; + // byte order markers and invalid unicode + if ((cChar == 0xFEFF) || (cChar == 0xFFFE) || (cChar == 0xFFFF)) + return true; + // drop null character too, broken documents may contain it (ofz34898-1.doc) + if (cChar == 0) + return true; + return false; +} + +// add a run after splitting it up to get rid of control chars +void ImplLayoutArgs::AddRun(int nCharPos0, int nCharPos1, bool bRTL) +{ + SAL_WARN_IF(nCharPos0 > nCharPos1, "vcl", "ImplLayoutArgs::AddRun() nCharPos0>=nCharPos1"); + + // remove control characters from runs by splitting them up + if (!bRTL) + { + for (int i = nCharPos0; i < nCharPos1; ++i) + if (IsControlChar(mrStr[i])) + { + // add run until control char + maRuns.AddRun(nCharPos0, i, bRTL); + nCharPos0 = i + 1; + } + } + else + { + for (int i = nCharPos1; --i >= nCharPos0;) + if (IsControlChar(mrStr[i])) + { + // add run until control char + maRuns.AddRun(i + 1, nCharPos1, bRTL); + nCharPos1 = i; + } + } + + // add remainder of run + maRuns.AddRun(nCharPos0, nCharPos1, bRTL); +} + +bool ImplLayoutArgs::PrepareFallback(const SalLayoutGlyphsImpl* pGlyphsImpl) +{ + // Generate runs with pre-calculated glyph items instead maFallbackRuns. + if (pGlyphsImpl != nullptr) + { + maRuns.Clear(); + maFallbackRuns.Clear(); + + for (auto const& aGlyphItem : *pGlyphsImpl) + { + for (int i = aGlyphItem.charPos(); i < aGlyphItem.charPos() + aGlyphItem.charCount(); + ++i) + maRuns.AddPos(i, aGlyphItem.IsRTLGlyph()); + } + + return !maRuns.IsEmpty(); + } + + // short circuit if no fallback is needed + if (maFallbackRuns.IsEmpty()) + { + maRuns.Clear(); + return false; + } + + // convert the fallback requests to layout requests + bool bRTL; + int nMin, nEnd; + + // get the individual fallback requests + std::vector<int> aPosVector; + aPosVector.reserve(mrStr.getLength()); + maFallbackRuns.ResetPos(); + for (; maFallbackRuns.GetRun(&nMin, &nEnd, &bRTL); maFallbackRuns.NextRun()) + for (int i = nMin; i < nEnd; ++i) + aPosVector.push_back(i); + maFallbackRuns.Clear(); + + // sort the individual fallback requests + std::sort(aPosVector.begin(), aPosVector.end()); + + // adjust fallback runs to have the same order and limits of the original runs + ImplLayoutRuns aNewRuns; + maRuns.ResetPos(); + for (; maRuns.GetRun(&nMin, &nEnd, &bRTL); maRuns.NextRun()) + { + if (!bRTL) + { + auto it = std::lower_bound(aPosVector.begin(), aPosVector.end(), nMin); + for (; (it != aPosVector.end()) && (*it < nEnd); ++it) + aNewRuns.AddPos(*it, bRTL); + } + else + { + auto it = std::upper_bound(aPosVector.begin(), aPosVector.end(), nEnd); + while ((it != aPosVector.begin()) && (*--it >= nMin)) + aNewRuns.AddPos(*it, bRTL); + } + } + + maRuns = aNewRuns; // TODO: use vector<>::swap() + maRuns.ResetPos(); + return true; +} + +bool ImplLayoutArgs::GetNextRun(int* nMinRunPos, int* nEndRunPos, bool* bRTL) +{ + bool bValid = maRuns.GetRun(nMinRunPos, nEndRunPos, bRTL); + maRuns.NextRun(); + return bValid; +} +} + +std::ostream& operator<<(std::ostream& s, vcl::text::ImplLayoutArgs const& rArgs) +{ +#ifndef SAL_LOG_INFO + (void)rArgs; +#else + s << "ImplLayoutArgs{"; + + s << "Flags="; + if (rArgs.mnFlags == SalLayoutFlags::NONE) + s << 0; + else + { + bool need_or = false; + s << "{"; +#define TEST(x) \ + if (rArgs.mnFlags & SalLayoutFlags::x) \ + { \ + if (need_or) \ + s << "|"; \ + s << #x; \ + need_or = true; \ + } + TEST(BiDiRtl); + TEST(BiDiStrong); + TEST(RightAlign); + TEST(DisableKerning); + TEST(KerningAsian); + TEST(Vertical); + TEST(KashidaJustification); + TEST(ForFallback); +#undef TEST + s << "}"; + } + + const int nLength = rArgs.mrStr.getLength(); + + s << ",Length=" << nLength; + s << ",MinCharPos=" << rArgs.mnMinCharPos; + s << ",EndCharPos=" << rArgs.mnEndCharPos; + + s << ",Str=\""; + int lim = nLength; + if (lim > 10) + lim = 7; + for (int i = 0; i < lim; i++) + { + if (rArgs.mrStr[i] == '\n') + s << "\\n"; + else if (rArgs.mrStr[i] < ' ' || (rArgs.mrStr[i] >= 0x7F && rArgs.mrStr[i] <= 0xFF)) + s << "\\0x" << std::hex << std::setw(2) << std::setfill('0') + << static_cast<int>(rArgs.mrStr[i]) << std::setfill(' ') << std::setw(1) << std::dec; + else if (rArgs.mrStr[i] < 0x7F) + s << static_cast<char>(rArgs.mrStr[i]); + else + s << "\\u" << std::hex << std::setw(4) << std::setfill('0') + << static_cast<int>(rArgs.mrStr[i]) << std::setfill(' ') << std::setw(1) << std::dec; + } + if (nLength > lim) + s << "..."; + s << "\""; + + s << ",DXArray="; + if (rArgs.mpDXArray || rArgs.mpAltNaturalDXArray) + { + s << "["; + int count = rArgs.mnEndCharPos - rArgs.mnMinCharPos; + lim = count; + if (lim > 10) + lim = 7; + for (int i = 0; i < lim; i++) + { + if (rArgs.mpDXArray) + s << rArgs.mpDXArray[i]; + else + s << rArgs.mpAltNaturalDXArray[i]; + if (i < lim - 1) + s << ","; + } + if (count > lim) + { + if (count > lim + 1) + s << "..."; + if (rArgs.mpDXArray) + s << rArgs.mpDXArray[count - 1]; + else + s << rArgs.mpAltNaturalDXArray[count - 1]; + } + s << "]"; + } + else + s << "NULL"; + + s << ",LayoutWidth=" << rArgs.mnLayoutWidth; + + s << "}"; + +#endif + return s; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/text/ImplLayoutRuns.cxx b/vcl/source/text/ImplLayoutRuns.cxx new file mode 100644 index 000000000..a560e17e1 --- /dev/null +++ b/vcl/source/text/ImplLayoutRuns.cxx @@ -0,0 +1,179 @@ +/* -*- 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 <ImplLayoutRuns.hxx> + +void ImplLayoutRuns::AddPos( int nCharPos, bool bRTL ) +{ + // check if charpos could extend current run + int nIndex = maRuns.size(); + if( nIndex >= 2 ) + { + int nRunPos0 = maRuns[ nIndex-2 ]; + int nRunPos1 = maRuns[ nIndex-1 ]; + if( ((nCharPos + int(bRTL)) == nRunPos1) && ((nRunPos0 > nRunPos1) == bRTL) ) + { + // extend current run by new charpos + maRuns[ nIndex-1 ] = nCharPos + int(!bRTL); + return; + } + // ignore new charpos when it is in current run + if( (nRunPos0 <= nCharPos) && (nCharPos < nRunPos1) ) + return; + if( (nRunPos1 <= nCharPos) && (nCharPos < nRunPos0) ) + return; + } + + // else append a new run consisting of the new charpos + maRuns.push_back( nCharPos + (bRTL ? 1 : 0) ); + maRuns.push_back( nCharPos + (bRTL ? 0 : 1) ); +} + +void ImplLayoutRuns::AddRun( int nCharPos0, int nCharPos1, bool bRTL ) +{ + if( nCharPos0 == nCharPos1 ) + return; + + // swap if needed + if( bRTL == (nCharPos0 < nCharPos1) ) + { + int nTemp = nCharPos0; + nCharPos0 = nCharPos1; + nCharPos1 = nTemp; + } + + if (maRuns.size() >= 2 && nCharPos0 == maRuns[maRuns.size() - 2] && nCharPos1 == maRuns[maRuns.size() - 1]) + { + //this run is the same as the last + return; + } + + // append new run + maRuns.push_back( nCharPos0 ); + maRuns.push_back( nCharPos1 ); +} + +bool ImplLayoutRuns::PosIsInRun( int nCharPos ) const +{ + if( mnRunIndex >= static_cast<int>(maRuns.size()) ) + return false; + + int nMinCharPos = maRuns[ mnRunIndex+0 ]; + int nEndCharPos = maRuns[ mnRunIndex+1 ]; + if( nMinCharPos > nEndCharPos ) // reversed in RTL case + { + int nTemp = nMinCharPos; + nMinCharPos = nEndCharPos; + nEndCharPos = nTemp; + } + + if( nCharPos < nMinCharPos ) + return false; + if( nCharPos >= nEndCharPos ) + return false; + return true; +} + +bool ImplLayoutRuns::PosIsInAnyRun( int nCharPos ) const +{ + bool bRet = false; + int nRunIndex = mnRunIndex; + + ImplLayoutRuns *pThis = const_cast<ImplLayoutRuns*>(this); + + pThis->ResetPos(); + + for (size_t i = 0; i < maRuns.size(); i+=2) + { + bRet = PosIsInRun( nCharPos ); + if( bRet ) + break; + pThis->NextRun(); + } + + pThis->mnRunIndex = nRunIndex; + return bRet; +} + +bool ImplLayoutRuns::GetNextPos( int* nCharPos, bool* bRightToLeft ) +{ + // negative nCharPos => reset to first run + if( *nCharPos < 0 ) + mnRunIndex = 0; + + // return false when all runs completed + if( mnRunIndex >= static_cast<int>(maRuns.size()) ) + return false; + + int nRunPos0 = maRuns[ mnRunIndex+0 ]; + int nRunPos1 = maRuns[ mnRunIndex+1 ]; + *bRightToLeft = (nRunPos0 > nRunPos1); + + if( *nCharPos < 0 ) + { + // get first valid nCharPos in run + *nCharPos = nRunPos0; + } + else + { + // advance to next nCharPos for LTR case + if( !*bRightToLeft ) + ++(*nCharPos); + + // advance to next run if current run is completed + if( *nCharPos == nRunPos1 ) + { + if( (mnRunIndex += 2) >= static_cast<int>(maRuns.size()) ) + return false; + nRunPos0 = maRuns[ mnRunIndex+0 ]; + nRunPos1 = maRuns[ mnRunIndex+1 ]; + *bRightToLeft = (nRunPos0 > nRunPos1); + *nCharPos = nRunPos0; + } + } + + // advance to next nCharPos for RTL case + if( *bRightToLeft ) + --(*nCharPos); + + return true; +} + +bool ImplLayoutRuns::GetRun( int* nMinRunPos, int* nEndRunPos, bool* bRightToLeft ) const +{ + if( mnRunIndex >= static_cast<int>(maRuns.size()) ) + return false; + + int nRunPos0 = maRuns[ mnRunIndex+0 ]; + int nRunPos1 = maRuns[ mnRunIndex+1 ]; + *bRightToLeft = (nRunPos1 < nRunPos0) ; + if( !*bRightToLeft ) + { + *nMinRunPos = nRunPos0; + *nEndRunPos = nRunPos1; + } + else + { + *nMinRunPos = nRunPos1; + *nEndRunPos = nRunPos0; + } + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/text/TextLayoutCache.cxx b/vcl/source/text/TextLayoutCache.cxx new file mode 100644 index 000000000..1d3e8e504 --- /dev/null +++ b/vcl/source/text/TextLayoutCache.cxx @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 <TextLayoutCache.hxx> + +#include <scrptrun.h> + +#include <o3tl/hash_combine.hxx> +#include <o3tl/lru_map.hxx> +#include <unotools/configmgr.hxx> +#include <vcl/lazydelete.hxx> +#include <officecfg/Office/Common.hxx> + +namespace vcl::text +{ +TextLayoutCache::TextLayoutCache(sal_Unicode const* pStr, sal_Int32 const nEnd) +{ + vcl::ScriptRun aScriptRun(reinterpret_cast<const UChar*>(pStr), nEnd); + while (aScriptRun.next()) + { + runs.emplace_back(aScriptRun.getScriptStart(), aScriptRun.getScriptEnd(), + aScriptRun.getScriptCode()); + } +} + +namespace +{ +struct TextLayoutCacheCost +{ + size_t operator()(const std::shared_ptr<const TextLayoutCache>& item) const + { + return item->runs.size() * sizeof(item->runs.front()); + } +}; +} // namespace + +std::shared_ptr<const TextLayoutCache> TextLayoutCache::Create(OUString const& rString) +{ + typedef o3tl::lru_map<OUString, std::shared_ptr<const TextLayoutCache>, FirstCharsStringHash, + FastStringCompareEqual, TextLayoutCacheCost> + Cache; + static vcl::DeleteOnDeinit<Cache> cache( + !utl::ConfigManager::IsFuzzing() + ? officecfg::Office::Common::Cache::Font::TextRunsCacheSize::get() + : 100); + if (Cache* map = cache.get()) + { + auto it = map->find(rString); + if (it != map->end()) + return it->second; + auto ret = std::make_shared<const TextLayoutCache>(rString.getStr(), rString.getLength()); + map->insert({ rString, ret }); + return ret; + } + return std::make_shared<const TextLayoutCache>(rString.getStr(), rString.getLength()); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ |