summaryrefslogtreecommitdiffstats
path: root/vcl/source/text
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/source/text')
-rw-r--r--vcl/source/text/ImplLayoutArgs.cxx343
-rw-r--r--vcl/source/text/ImplLayoutRuns.cxx179
-rw-r--r--vcl/source/text/TextLayoutCache.cxx75
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: */