summaryrefslogtreecommitdiffstats
path: root/vcl/source/gdi/sallayout.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /vcl/source/gdi/sallayout.cxx
parentInitial commit. (diff)
downloadlibreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.tar.xz
libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.zip
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source/gdi/sallayout.cxx')
-rw-r--r--vcl/source/gdi/sallayout.cxx1585
1 files changed, 1585 insertions, 0 deletions
diff --git a/vcl/source/gdi/sallayout.cxx b/vcl/source/gdi/sallayout.cxx
new file mode 100644
index 000000000..eaa016ed1
--- /dev/null
+++ b/vcl/source/gdi/sallayout.cxx
@@ -0,0 +1,1585 @@
+/* -*- 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 <iostream>
+#include <iomanip>
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <cstdio>
+
+#include <math.h>
+
+#include <salgdi.hxx>
+#include <sallayout.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+#include <i18nlangtag/lang.h>
+
+#include <vcl/svapp.hxx>
+
+#include <unicode/ubidi.h>
+#include <unicode/uchar.h>
+
+#include <algorithm>
+#include <memory>
+
+#include <impglyphitem.hxx>
+
+// Glyph Flags
+#define GF_FONTMASK 0xF0000000
+#define GF_FONTSHIFT 28
+
+
+std::ostream &operator <<(std::ostream& s, 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) {
+ s << "[";
+ int count = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
+ lim = count;
+ if (lim > 10)
+ lim = 7;
+ for (int i = 0; i < lim; i++) {
+ s << rArgs.mpDXArray[i];
+ if (i < lim-1)
+ s << ",";
+ }
+ if (count > lim) {
+ if (count > lim + 1)
+ s << "...";
+ s << rArgs.mpDXArray[count-1];
+ }
+ s << "]";
+ } else
+ s << "NULL";
+
+ s << ",LayoutWidth=" << rArgs.mnLayoutWidth;
+
+ s << "}";
+
+#endif
+ return s;
+}
+
+sal_UCS4 GetMirroredChar( sal_UCS4 nChar )
+{
+ nChar = u_charMirror( nChar );
+ return nChar;
+}
+
+sal_UCS4 GetLocalizedChar( sal_UCS4 nChar, LanguageType eLang )
+{
+ // currently only conversion from ASCII digits is interesting
+ if( (nChar < '0') || ('9' < nChar) )
+ return nChar;
+
+ int nOffset;
+ // eLang & LANGUAGE_MASK_PRIMARY catches language independent of region.
+ // CAVEAT! To some like Mongolian MS assigned the same primary language
+ // although the script type is different!
+ LanguageType pri = primary(eLang);
+ if( pri == primary(LANGUAGE_ARABIC_SAUDI_ARABIA) )
+ nOffset = 0x0660 - '0'; // arabic-indic digits
+ else if ( pri.anyOf(
+ primary(LANGUAGE_FARSI),
+ primary(LANGUAGE_URDU_PAKISTAN),
+ primary(LANGUAGE_PUNJABI), //???
+ primary(LANGUAGE_SINDHI)))
+ nOffset = 0x06F0 - '0'; // eastern arabic-indic digits
+ else if ( pri == primary(LANGUAGE_BENGALI) )
+ nOffset = 0x09E6 - '0'; // bengali
+ else if ( pri == primary(LANGUAGE_HINDI) )
+ nOffset = 0x0966 - '0'; // devanagari
+ else if ( pri.anyOf(
+ primary(LANGUAGE_AMHARIC_ETHIOPIA),
+ primary(LANGUAGE_TIGRIGNA_ETHIOPIA)))
+ // TODO case:
+ nOffset = 0x1369 - '0'; // ethiopic
+ else if ( pri == primary(LANGUAGE_GUJARATI) )
+ nOffset = 0x0AE6 - '0'; // gujarati
+#ifdef LANGUAGE_GURMUKHI // TODO case:
+ else if ( pri == primary(LANGUAGE_GURMUKHI) )
+ nOffset = 0x0A66 - '0'; // gurmukhi
+#endif
+ else if ( pri == primary(LANGUAGE_KANNADA) )
+ nOffset = 0x0CE6 - '0'; // kannada
+ else if ( pri == primary(LANGUAGE_KHMER))
+ nOffset = 0x17E0 - '0'; // khmer
+ else if ( pri == primary(LANGUAGE_LAO) )
+ nOffset = 0x0ED0 - '0'; // lao
+ else if ( pri == primary(LANGUAGE_MALAYALAM) )
+ nOffset = 0x0D66 - '0'; // malayalam
+ else if ( pri == primary(LANGUAGE_MONGOLIAN_MONGOLIAN_LSO))
+ {
+ if (eLang.anyOf(
+ LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA,
+ LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA,
+ LANGUAGE_MONGOLIAN_MONGOLIAN_LSO))
+ nOffset = 0x1810 - '0'; // mongolian
+ else
+ nOffset = 0; // mongolian cyrillic
+ }
+ else if ( pri == primary(LANGUAGE_BURMESE) )
+ nOffset = 0x1040 - '0'; // myanmar
+ else if ( pri == primary(LANGUAGE_ODIA) )
+ nOffset = 0x0B66 - '0'; // odia
+ else if ( pri == primary(LANGUAGE_TAMIL) )
+ nOffset = 0x0BE7 - '0'; // tamil
+ else if ( pri == primary(LANGUAGE_TELUGU) )
+ nOffset = 0x0C66 - '0'; // telugu
+ else if ( pri == primary(LANGUAGE_THAI) )
+ nOffset = 0x0E50 - '0'; // thai
+ else if ( pri == primary(LANGUAGE_TIBETAN) )
+ nOffset = 0x0F20 - '0'; // tibetan
+ else
+ {
+ nOffset = 0;
+ }
+
+ nChar += nOffset;
+ return nChar;
+}
+
+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;
+ return false;
+}
+
+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;
+}
+
+ImplLayoutArgs::ImplLayoutArgs(const OUString& rStr,
+ int nMinCharPos, int nEndCharPos, SalLayoutFlags nFlags, const LanguageTag& rLanguageTag,
+ vcl::TextLayoutCache const*const pLayoutCache)
+:
+ maLanguageTag( rLanguageTag ),
+ mnFlags( nFlags ),
+ mrStr( rStr ),
+ mnMinCharPos( nMinCharPos ),
+ mnEndCharPos( nEndCharPos ),
+ m_pTextLayoutCache(pLayoutCache),
+ mpDXArray( 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 = mrStr.getLength();
+ UBiDi* pParaBidi = ubidi_openSized(nLength, 0, &rcI18n);
+ if( !pParaBidi )
+ return;
+ ubidi_setPara(pParaBidi, reinterpret_cast<const UChar *>(mrStr.getStr()), nLength, nLevel, nullptr, &rcI18n);
+
+ UBiDi* pLineBidi = pParaBidi;
+ int nSubLength = mnEndCharPos - mnMinCharPos;
+ if (nSubLength != nLength)
+ {
+ pLineBidi = ubidi_openSized( nSubLength, 0, &rcI18n );
+ ubidi_setLine( pParaBidi, mnMinCharPos, mnEndCharPos, pLineBidi, &rcI18n );
+ }
+
+ // run BiDi algorithm
+ const int nRunCount = ubidi_countRuns( pLineBidi, &rcI18n );
+ //maRuns.resize( 2 * nRunCount );
+ for( int i = 0; i < nRunCount; ++i )
+ {
+ int32_t nMinPos, nRunLength;
+ const UBiDiDirection nDir = ubidi_getVisualRun( pLineBidi, 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
+ if( pLineBidi != pParaBidi )
+ ubidi_close( pLineBidi );
+ ubidi_close( pParaBidi );
+ }
+
+ // prepare calls to GetNextPos/GetNextRun
+ maRuns.ResetPos();
+}
+
+// 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()
+{
+ // 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;
+}
+
+SalLayout::SalLayout()
+: mnMinCharPos( -1 ),
+ mnEndCharPos( -1 ),
+ mnUnitsPerPixel( 1 ),
+ mnOrientation( 0 ),
+ maDrawOffset( 0, 0 )
+{}
+
+SalLayout::~SalLayout()
+{}
+
+void SalLayout::AdjustLayout( ImplLayoutArgs& rArgs )
+{
+ mnMinCharPos = rArgs.mnMinCharPos;
+ mnEndCharPos = rArgs.mnEndCharPos;
+ mnOrientation = rArgs.mnOrientation;
+}
+
+Point SalLayout::GetDrawPosition( const Point& rRelative ) const
+{
+ Point aPos = maDrawBase;
+ Point aOfs = rRelative + maDrawOffset;
+
+ if( mnOrientation == 0 )
+ aPos += aOfs;
+ else
+ {
+ // cache trigonometric results
+ static int nOldOrientation = 0;
+ static double fCos = 1.0, fSin = 0.0;
+ if( nOldOrientation != mnOrientation )
+ {
+ nOldOrientation = mnOrientation;
+ double fRad = mnOrientation * (M_PI / 1800.0);
+ fCos = cos( fRad );
+ fSin = sin( fRad );
+ }
+
+ double fX = aOfs.X();
+ double fY = aOfs.Y();
+ long nX = static_cast<long>( +fCos * fX + fSin * fY );
+ long nY = static_cast<long>( +fCos * fY - fSin * fX );
+ aPos += Point( nX, nY );
+ }
+
+ return aPos;
+}
+
+bool SalLayout::GetOutline(basegfx::B2DPolyPolygonVector& rVector) const
+{
+ bool bAllOk = true;
+ bool bOneOk = false;
+
+ basegfx::B2DPolyPolygon aGlyphOutline;
+
+ Point aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ // get outline of individual glyph, ignoring "empty" glyphs
+ bool bSuccess = pGlyph->GetGlyphOutline(aGlyphOutline);
+ bAllOk &= bSuccess;
+ bOneOk |= bSuccess;
+ // only add non-empty outlines
+ if( bSuccess && (aGlyphOutline.count() > 0) )
+ {
+ if( aPos.X() || aPos.Y() )
+ {
+ aGlyphOutline.transform(basegfx::utils::createTranslateB2DHomMatrix(aPos.X(), aPos.Y()));
+ }
+
+ // insert outline at correct position
+ rVector.push_back( aGlyphOutline );
+ }
+ }
+
+ return (bAllOk && bOneOk);
+}
+
+bool SalLayout::GetBoundRect(tools::Rectangle& rRect) const
+{
+ bool bRet = false;
+ rRect.SetEmpty();
+
+ tools::Rectangle aRectangle;
+
+ Point aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ // get bounding rectangle of individual glyph
+ if (pGlyph->GetGlyphBoundRect(aRectangle))
+ {
+ // merge rectangle
+ aRectangle += aPos;
+ if (rRect.IsEmpty())
+ rRect = aRectangle;
+ else
+ rRect.Union(aRectangle);
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+DeviceCoordinate GenericSalLayout::FillDXArray( DeviceCoordinate* pCharWidths ) const
+{
+ if (pCharWidths)
+ GetCharWidths(pCharWidths);
+
+ return GetTextWidth();
+}
+
+// the text width is the maximum logical extent of all glyphs
+DeviceCoordinate GenericSalLayout::GetTextWidth() const
+{
+ if (!m_GlyphItems.IsValid())
+ return 0;
+
+ // initialize the extent
+ DeviceCoordinate nMinPos = 0;
+ DeviceCoordinate nMaxPos = 0;
+
+ for (auto const& aGlyphItem : *m_GlyphItems.Impl())
+ {
+ // update the text extent with the glyph extent
+ DeviceCoordinate nXPos = aGlyphItem.m_aLinearPos.getX();
+ if( nMinPos > nXPos )
+ nMinPos = nXPos;
+ nXPos += aGlyphItem.m_nNewWidth - aGlyphItem.xOffset();
+ if( nMaxPos < nXPos )
+ nMaxPos = nXPos;
+ }
+
+ DeviceCoordinate nWidth = nMaxPos - nMinPos;
+ return nWidth;
+}
+
+void GenericSalLayout::Justify( DeviceCoordinate nNewWidth )
+{
+ nNewWidth *= mnUnitsPerPixel;
+ DeviceCoordinate nOldWidth = GetTextWidth();
+ if( !nOldWidth || nNewWidth==nOldWidth )
+ return;
+
+ if (!m_GlyphItems.IsValid())
+ {
+ return;
+ }
+ // find rightmost glyph, it won't get stretched
+ std::vector<GlyphItem>::iterator pGlyphIterRight = m_GlyphItems.Impl()->begin();
+ pGlyphIterRight += m_GlyphItems.Impl()->size() - 1;
+ std::vector<GlyphItem>::iterator pGlyphIter;
+ // count stretchable glyphs
+ int nStretchable = 0;
+ int nMaxGlyphWidth = 0;
+ for(pGlyphIter = m_GlyphItems.Impl()->begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter)
+ {
+ if( !pGlyphIter->IsDiacritic() )
+ ++nStretchable;
+ if (nMaxGlyphWidth < pGlyphIter->origWidth())
+ nMaxGlyphWidth = pGlyphIter->origWidth();
+ }
+
+ // move rightmost glyph to requested position
+ nOldWidth -= pGlyphIterRight->origWidth();
+ if( nOldWidth <= 0 )
+ return;
+ if( nNewWidth < nMaxGlyphWidth)
+ nNewWidth = nMaxGlyphWidth;
+ nNewWidth -= pGlyphIterRight->origWidth();
+ pGlyphIterRight->m_aLinearPos.setX( nNewWidth );
+
+ // justify glyph widths and positions
+ int nDiffWidth = nNewWidth - nOldWidth;
+ if( nDiffWidth >= 0) // expanded case
+ {
+ // expand width by distributing space between glyphs evenly
+ int nDeltaSum = 0;
+ for( pGlyphIter = m_GlyphItems.Impl()->begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter )
+ {
+ // move glyph to justified position
+ pGlyphIter->m_aLinearPos.AdjustX(nDeltaSum );
+
+ // do not stretch non-stretchable glyphs
+ if( pGlyphIter->IsDiacritic() || (nStretchable <= 0) )
+ continue;
+
+ // distribute extra space equally to stretchable glyphs
+ int nDeltaWidth = nDiffWidth / nStretchable--;
+ nDiffWidth -= nDeltaWidth;
+ pGlyphIter->m_nNewWidth += nDeltaWidth;
+ nDeltaSum += nDeltaWidth;
+ }
+ }
+ else // condensed case
+ {
+ // squeeze width by moving glyphs proportionally
+ double fSqueeze = static_cast<double>(nNewWidth) / nOldWidth;
+ if(m_GlyphItems.Impl()->size() > 1)
+ {
+ for( pGlyphIter = m_GlyphItems.Impl()->begin(); ++pGlyphIter != pGlyphIterRight;)
+ {
+ int nX = pGlyphIter->m_aLinearPos.getX();
+ nX = static_cast<int>(nX * fSqueeze);
+ pGlyphIter->m_aLinearPos.setX( nX );
+ }
+ }
+ // adjust glyph widths to new positions
+ for( pGlyphIter = m_GlyphItems.Impl()->begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter )
+ pGlyphIter->m_nNewWidth = pGlyphIter[1].m_aLinearPos.getX() - pGlyphIter[0].m_aLinearPos.getX();
+ }
+}
+
+// returns asian kerning values in quarter of character width units
+// to enable automatic halfwidth substitution for fullwidth punctuation
+// return value is negative for l, positive for r, zero for neutral
+// TODO: handle vertical layout as proposed in commit 43bf2ad49c2b3989bbbe893e4fee2e032a3920f5?
+static int lcl_CalcAsianKerning(sal_UCS4 c, bool bLeft)
+{
+ // http://www.asahi-net.or.jp/~sd5a-ucd/freetexts/jis/x4051/1995/appendix.html
+ static const signed char nTable[0x30] =
+ {
+ 0, -2, -2, 0, 0, 0, 0, 0, +2, -2, +2, -2, +2, -2, +2, -2,
+ +2, -2, 0, 0, +2, -2, +2, -2, 0, 0, 0, 0, 0, +2, -2, -2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -2, +2, +2, -2, -2
+ };
+
+ int nResult = 0;
+ if( (c >= 0x3000) && (c < 0x3030) )
+ nResult = nTable[ c - 0x3000 ];
+ else switch( c )
+ {
+ case 0x30FB:
+ nResult = bLeft ? -1 : +1; // 25% left/right/top/bottom
+ break;
+ case 0x2019: case 0x201D:
+ case 0xFF01: case 0xFF09: case 0xFF0C:
+ case 0xFF1A: case 0xFF1B:
+ nResult = -2;
+ break;
+ case 0x2018: case 0x201C:
+ case 0xFF08:
+ nResult = +2;
+ break;
+ default:
+ break;
+ }
+
+ return nResult;
+}
+
+static bool lcl_CanApplyAsianKerning(sal_Unicode cp)
+{
+ return (0x3000 == (cp & 0xFF00)) || (0xFF00 == (cp & 0xFF00)) || (0x2010 == (cp & 0xFFF0));
+}
+
+void GenericSalLayout::ApplyAsianKerning(const OUString& rStr)
+{
+ const int nLength = rStr.getLength();
+ long nOffset = 0;
+
+ for (std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.Impl()->begin(),
+ pGlyphIterEnd = m_GlyphItems.Impl()->end();
+ pGlyphIter != pGlyphIterEnd; ++pGlyphIter)
+ {
+ const int n = pGlyphIter->charPos();
+ if (n < nLength - 1)
+ {
+ // ignore code ranges that are not affected by asian punctuation compression
+ const sal_Unicode cCurrent = rStr[n];
+ if (!lcl_CanApplyAsianKerning(cCurrent))
+ continue;
+ const sal_Unicode cNext = rStr[n + 1];
+ if (!lcl_CanApplyAsianKerning(cNext))
+ continue;
+
+ // calculate compression values
+ const int nKernCurrent = +lcl_CalcAsianKerning(cCurrent, true);
+ if (nKernCurrent == 0)
+ continue;
+ const int nKernNext = -lcl_CalcAsianKerning(cNext, false);
+ if (nKernNext == 0)
+ continue;
+
+ // apply punctuation compression to logical glyph widths
+ int nDelta = (nKernCurrent < nKernNext) ? nKernCurrent : nKernNext;
+ if (nDelta < 0)
+ {
+ nDelta = (nDelta * pGlyphIter->origWidth() + 2) / 4;
+ if( pGlyphIter+1 == pGlyphIterEnd )
+ pGlyphIter->m_nNewWidth += nDelta;
+ nOffset += nDelta;
+ }
+ }
+
+ // adjust the glyph positions to the new glyph widths
+ if( pGlyphIter+1 != pGlyphIterEnd )
+ pGlyphIter->m_aLinearPos.AdjustX(nOffset);
+ }
+}
+
+void GenericSalLayout::GetCaretPositions( int nMaxIndex, long* pCaretXArray ) const
+{
+ // initialize result array
+ for (int i = 0; i < nMaxIndex; ++i)
+ pCaretXArray[i] = -1;
+
+ // calculate caret positions using glyph array
+ for (auto const& aGlyphItem : *m_GlyphItems.Impl())
+ {
+ long nXPos = aGlyphItem.m_aLinearPos.getX();
+ long nXRight = nXPos + aGlyphItem.origWidth();
+ int n = aGlyphItem.charPos();
+ int nCurrIdx = 2 * (n - mnMinCharPos);
+ // tdf#86399 if this is not the start of a cluster, don't overwrite the caret bounds of the cluster start
+ if (aGlyphItem.IsInCluster() && pCaretXArray[nCurrIdx] != -1)
+ continue;
+ if (!aGlyphItem.IsRTLGlyph() )
+ {
+ // normal positions for LTR case
+ pCaretXArray[ nCurrIdx ] = nXPos;
+ pCaretXArray[ nCurrIdx+1 ] = nXRight;
+ }
+ else
+ {
+ // reverse positions for RTL case
+ pCaretXArray[ nCurrIdx ] = nXRight;
+ pCaretXArray[ nCurrIdx+1 ] = nXPos;
+ }
+ }
+}
+
+sal_Int32 GenericSalLayout::GetTextBreak( DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor ) const
+{
+ int nCharCapacity = mnEndCharPos - mnMinCharPos;
+ std::unique_ptr<DeviceCoordinate[]> const pCharWidths(new DeviceCoordinate[nCharCapacity]);
+ GetCharWidths(pCharWidths.get());
+
+ DeviceCoordinate nWidth = 0;
+ for( int i = mnMinCharPos; i < mnEndCharPos; ++i )
+ {
+ nWidth += pCharWidths[ i - mnMinCharPos ] * nFactor;
+ if( nWidth > nMaxWidth )
+ return i;
+ nWidth += nCharExtra;
+ }
+
+ return -1;
+}
+
+bool GenericSalLayout::GetNextGlyph(const GlyphItem** pGlyph,
+ Point& rPos, int& nStart,
+ const PhysicalFontFace**, int* const pFallbackLevel) const
+{
+ std::vector<GlyphItem>::const_iterator pGlyphIter = m_GlyphItems.Impl()->begin();
+ std::vector<GlyphItem>::const_iterator pGlyphIterEnd = m_GlyphItems.Impl()->end();
+ pGlyphIter += nStart;
+
+ // find next glyph in substring
+ for(; pGlyphIter != pGlyphIterEnd; ++nStart, ++pGlyphIter )
+ {
+ int n = pGlyphIter->charPos();
+ if( (mnMinCharPos <= n) && (n < mnEndCharPos) )
+ break;
+ }
+
+ // return zero if no more glyph found
+ if( nStart >= static_cast<int>(m_GlyphItems.Impl()->size()) )
+ return false;
+
+ if( pGlyphIter == pGlyphIterEnd )
+ return false;
+
+ // update return data with glyph info
+ *pGlyph = &(*pGlyphIter);
+ if (pFallbackLevel)
+ *pFallbackLevel = 0;
+ ++nStart;
+
+ // calculate absolute position in pixel units
+ Point aRelativePos = pGlyphIter->m_aLinearPos;
+
+ aRelativePos.setX( aRelativePos.X() / mnUnitsPerPixel );
+ aRelativePos.setY( aRelativePos.Y() / mnUnitsPerPixel );
+ rPos = GetDrawPosition( aRelativePos );
+
+ return true;
+}
+
+void GenericSalLayout::MoveGlyph( int nStart, long nNewXPos )
+{
+ if( nStart >= static_cast<int>(m_GlyphItems.Impl()->size()) )
+ return;
+
+ std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.Impl()->begin();
+ pGlyphIter += nStart;
+
+ // the nNewXPos argument determines the new cell position
+ // as RTL-glyphs are right justified in their cell
+ // the cell position needs to be adjusted to the glyph position
+ if( pGlyphIter->IsRTLGlyph() )
+ nNewXPos += pGlyphIter->m_nNewWidth - pGlyphIter->origWidth();
+ // calculate the x-offset to the old position
+ long nXDelta = nNewXPos - pGlyphIter->m_aLinearPos.getX();
+ // adjust all following glyph positions if needed
+ if( nXDelta != 0 )
+ {
+ for( std::vector<GlyphItem>::iterator pGlyphIterEnd = m_GlyphItems.Impl()->end(); pGlyphIter != pGlyphIterEnd; ++pGlyphIter )
+ {
+ pGlyphIter->m_aLinearPos.AdjustX(nXDelta );
+ }
+ }
+}
+
+void GenericSalLayout::DropGlyph( int nStart )
+{
+ if( nStart >= static_cast<int>(m_GlyphItems.Impl()->size()))
+ return;
+
+ std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.Impl()->begin();
+ pGlyphIter += nStart;
+ pGlyphIter->dropGlyph();
+}
+
+void GenericSalLayout::Simplify( bool bIsBase )
+{
+ // remove dropped glyphs inplace
+ size_t j = 0;
+ for(size_t i = 0; i < m_GlyphItems.Impl()->size(); i++ )
+ {
+ if (bIsBase && (*m_GlyphItems.Impl())[i].IsDropped())
+ continue;
+ if (!bIsBase && (*m_GlyphItems.Impl())[i].glyphId() == 0)
+ continue;
+
+ if( i != j )
+ {
+ (*m_GlyphItems.Impl())[j] = (*m_GlyphItems.Impl())[i];
+ }
+ j += 1;
+ }
+ m_GlyphItems.Impl()->erase(m_GlyphItems.Impl()->begin() + j, m_GlyphItems.Impl()->end());
+}
+
+MultiSalLayout::MultiSalLayout( std::unique_ptr<SalLayout> pBaseLayout )
+: SalLayout()
+, mnLevel( 1 )
+, mbIncomplete( false )
+{
+ assert(dynamic_cast<GenericSalLayout*>(pBaseLayout.get()));
+
+ mpLayouts[ 0 ].reset(static_cast<GenericSalLayout*>(pBaseLayout.release()));
+ mnUnitsPerPixel = mpLayouts[ 0 ]->GetUnitsPerPixel();
+}
+
+void MultiSalLayout::SetIncomplete(bool bIncomplete)
+{
+ mbIncomplete = bIncomplete;
+ maFallbackRuns[mnLevel-1] = ImplLayoutRuns();
+}
+
+MultiSalLayout::~MultiSalLayout()
+{
+}
+
+void MultiSalLayout::AddFallback( std::unique_ptr<SalLayout> pFallback,
+ ImplLayoutRuns const & rFallbackRuns)
+{
+ assert(dynamic_cast<GenericSalLayout*>(pFallback.get()));
+ if( mnLevel >= MAX_FALLBACK )
+ return;
+
+ mpLayouts[ mnLevel ].reset(static_cast<GenericSalLayout*>(pFallback.release()));
+ maFallbackRuns[ mnLevel-1 ] = rFallbackRuns;
+ ++mnLevel;
+}
+
+bool MultiSalLayout::LayoutText( ImplLayoutArgs& rArgs, const SalLayoutGlyphs* )
+{
+ if( mnLevel <= 1 )
+ return false;
+ if (!mbIncomplete)
+ maFallbackRuns[ mnLevel-1 ] = rArgs.maRuns;
+ return true;
+}
+
+void MultiSalLayout::AdjustLayout( ImplLayoutArgs& rArgs )
+{
+ SalLayout::AdjustLayout( rArgs );
+ ImplLayoutArgs aMultiArgs = rArgs;
+ std::unique_ptr<DeviceCoordinate[]> pJustificationArray;
+
+ if( !rArgs.mpDXArray && rArgs.mnLayoutWidth )
+ {
+ // for stretched text in a MultiSalLayout the target width needs to be
+ // distributed by individually adjusting its virtual character widths
+ DeviceCoordinate nTargetWidth = aMultiArgs.mnLayoutWidth;
+ nTargetWidth *= mnUnitsPerPixel; // convert target width to base font units
+ aMultiArgs.mnLayoutWidth = 0;
+
+ // we need to get the original unmodified layouts ready
+ for( int n = 0; n < mnLevel; ++n )
+ mpLayouts[n]->SalLayout::AdjustLayout( aMultiArgs );
+ // then we can measure the unmodified metrics
+ int nCharCount = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
+ pJustificationArray.reset(new DeviceCoordinate[nCharCount]);
+ FillDXArray( pJustificationArray.get() );
+ // #i17359# multilayout is not simplified yet, so calculating the
+ // unjustified width needs handholding; also count the number of
+ // stretchable virtual char widths
+ DeviceCoordinate nOrigWidth = 0;
+ int nStretchable = 0;
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ // convert array from widths to sum of widths
+ nOrigWidth += pJustificationArray[i];
+ if( pJustificationArray[i] > 0 )
+ ++nStretchable;
+ }
+
+ // now we are able to distribute the extra width over the virtual char widths
+ if( nOrigWidth && (nTargetWidth != nOrigWidth) )
+ {
+ DeviceCoordinate nDiffWidth = nTargetWidth - nOrigWidth;
+ DeviceCoordinate nWidthSum = 0;
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ DeviceCoordinate nJustWidth = pJustificationArray[i];
+ if( (nJustWidth > 0) && (nStretchable > 0) )
+ {
+ DeviceCoordinate nDeltaWidth = nDiffWidth / nStretchable;
+ nJustWidth += nDeltaWidth;
+ nDiffWidth -= nDeltaWidth;
+ --nStretchable;
+ }
+ nWidthSum += nJustWidth;
+ pJustificationArray[i] = nWidthSum;
+ }
+ if( nWidthSum != nTargetWidth )
+ pJustificationArray[ nCharCount-1 ] = nTargetWidth;
+
+ // the justification array is still in base level units
+ // => convert it to pixel units
+ if( mnUnitsPerPixel > 1 )
+ {
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ DeviceCoordinate nVal = pJustificationArray[ i ];
+ nVal += (mnUnitsPerPixel + 1) / 2;
+ pJustificationArray[ i ] = nVal / mnUnitsPerPixel;
+ }
+ }
+
+ // change the mpDXArray temporarily (just for the justification)
+ aMultiArgs.mpDXArray = pJustificationArray.get();
+ }
+ }
+
+ // Compute rtl flags, since in some scripts glyphs/char order can be
+ // reversed for a few character sequences e.g. Myanmar
+ std::vector<bool> vRtl(rArgs.mnEndCharPos - rArgs.mnMinCharPos, false);
+ rArgs.ResetPos();
+ bool bRtl;
+ int nRunStart, nRunEnd;
+ while (rArgs.GetNextRun(&nRunStart, &nRunEnd, &bRtl))
+ {
+ if (bRtl) std::fill(vRtl.begin() + (nRunStart - rArgs.mnMinCharPos),
+ vRtl.begin() + (nRunEnd - rArgs.mnMinCharPos), true);
+ }
+ rArgs.ResetPos();
+
+ // prepare "merge sort"
+ int nStartOld[ MAX_FALLBACK ];
+ int nStartNew[ MAX_FALLBACK ];
+ const GlyphItem* pGlyphs[MAX_FALLBACK];
+ bool bValid[MAX_FALLBACK] = { false };
+
+ Point aPos;
+ int nLevel = 0, n;
+ for( n = 0; n < mnLevel; ++n )
+ {
+ // now adjust the individual components
+ if( n > 0 )
+ {
+ aMultiArgs.maRuns = maFallbackRuns[ n-1 ];
+ aMultiArgs.mnFlags |= SalLayoutFlags::ForFallback;
+ }
+ mpLayouts[n]->AdjustLayout( aMultiArgs );
+
+ // remove unused parts of component
+ if( n > 0 )
+ {
+ if (mbIncomplete && (n == mnLevel-1))
+ mpLayouts[n]->Simplify( true );
+ else
+ mpLayouts[n]->Simplify( false );
+ }
+
+ // prepare merging components
+ nStartNew[ nLevel ] = nStartOld[ nLevel ] = 0;
+ bValid[nLevel] = mpLayouts[n]->GetNextGlyph(&pGlyphs[nLevel], aPos, nStartNew[nLevel]);
+
+ if( (n > 0) && !bValid[ nLevel ] )
+ {
+ // an empty fallback layout can be released
+ mpLayouts[n].reset();
+ }
+ else
+ {
+ // reshuffle used fallbacks if needed
+ if( nLevel != n )
+ {
+ mpLayouts[ nLevel ] = std::move(mpLayouts[ n ]);
+ maFallbackRuns[ nLevel ] = maFallbackRuns[ n ];
+ }
+ ++nLevel;
+ }
+ }
+ mnLevel = nLevel;
+
+ // prepare merge the fallback levels
+ long nXPos = 0;
+ double fUnitMul = 1.0;
+ for( n = 0; n < nLevel; ++n )
+ maFallbackRuns[n].ResetPos();
+
+ int nFirstValid = -1;
+ for( n = 0; n < nLevel; ++n )
+ {
+ if(bValid[n])
+ {
+ nFirstValid = n;
+ break;
+ }
+ }
+ assert(nFirstValid >= 0);
+
+ // get the next codepoint index that needs fallback
+ int nActiveCharPos = pGlyphs[nFirstValid]->charPos();
+ int nActiveCharIndex = nActiveCharPos - mnMinCharPos;
+ // get the end index of the active run
+ int nLastRunEndChar = (nActiveCharIndex >= 0 && vRtl[nActiveCharIndex]) ?
+ rArgs.mnEndCharPos : rArgs.mnMinCharPos - 1;
+ int nRunVisibleEndChar = pGlyphs[nFirstValid]->charPos();
+ // merge the fallback levels
+ while( bValid[nFirstValid] && (nLevel > 0))
+ {
+ // find best fallback level
+ for( n = 0; n < nLevel; ++n )
+ if( bValid[n] && !maFallbackRuns[n].PosIsInAnyRun( nActiveCharPos ) )
+ // fallback level n wins when it requested no further fallback
+ break;
+ int nFBLevel = n;
+
+ if( n < nLevel )
+ {
+ // use base(n==0) or fallback(n>=1) level
+ fUnitMul = mnUnitsPerPixel;
+ fUnitMul /= mpLayouts[n]->GetUnitsPerPixel();
+ long nNewPos = static_cast<long>(nXPos/fUnitMul + 0.5);
+ mpLayouts[n]->MoveGlyph( nStartOld[n], nNewPos );
+ }
+ else
+ {
+ n = 0; // keep NotDef in base level
+ fUnitMul = 1.0;
+ }
+
+ if( n > 0 )
+ {
+ // drop the NotDef glyphs in the base layout run if a fallback run exists
+ while (
+ (maFallbackRuns[n-1].PosIsInRun(pGlyphs[nFirstValid]->charPos())) &&
+ (!maFallbackRuns[n].PosIsInAnyRun(pGlyphs[nFirstValid]->charPos()))
+ )
+ {
+ mpLayouts[0]->DropGlyph( nStartOld[0] );
+ nStartOld[0] = nStartNew[0];
+ bValid[nFirstValid] = mpLayouts[0]->GetNextGlyph(&pGlyphs[nFirstValid], aPos, nStartNew[0]);
+
+ if( !bValid[nFirstValid] )
+ break;
+ }
+ }
+
+ // skip to end of layout run and calculate its advance width
+ DeviceCoordinate nRunAdvance = 0;
+ bool bKeepNotDef = (nFBLevel >= nLevel);
+ for(;;)
+ {
+ nRunAdvance += pGlyphs[n]->m_nNewWidth;
+
+ // proceed to next glyph
+ nStartOld[n] = nStartNew[n];
+ int nOrigCharPos = pGlyphs[n]->charPos();
+ bValid[n] = mpLayouts[n]->GetNextGlyph(&pGlyphs[n], aPos, nStartNew[n]);
+ // break after last glyph of active layout
+ if( !bValid[n] )
+ {
+ // performance optimization (when a fallback layout is no longer needed)
+ if( n >= nLevel-1 )
+ --nLevel;
+ break;
+ }
+
+ //If the next character is one which belongs to the next level, then we
+ //are finished here for now, and we'll pick up after the next level has
+ //been processed
+ if ((n+1 < nLevel) && (pGlyphs[n]->charPos() != nOrigCharPos))
+ {
+ if (nOrigCharPos < pGlyphs[n]->charPos())
+ {
+ if (pGlyphs[n+1]->charPos() > nOrigCharPos && (pGlyphs[n+1]->charPos() < pGlyphs[n]->charPos()))
+ break;
+ }
+ else if (nOrigCharPos > pGlyphs[n]->charPos())
+ {
+ if (pGlyphs[n+1]->charPos() > pGlyphs[n]->charPos() && (pGlyphs[n+1]->charPos() < nOrigCharPos))
+ break;
+ }
+ }
+
+ // break at end of layout run
+ if( n > 0 )
+ {
+ // skip until end of fallback run
+ if (!maFallbackRuns[n-1].PosIsInRun(pGlyphs[n]->charPos()))
+ break;
+ }
+ else
+ {
+ // break when a fallback is needed and available
+ bool bNeedFallback = maFallbackRuns[0].PosIsInRun(pGlyphs[nFirstValid]->charPos());
+ if( bNeedFallback )
+ if (!maFallbackRuns[nLevel-1].PosIsInRun(pGlyphs[nFirstValid]->charPos()))
+ break;
+ // break when change from resolved to unresolved base layout run
+ if( bKeepNotDef && !bNeedFallback )
+ { maFallbackRuns[0].NextRun(); break; }
+ bKeepNotDef = bNeedFallback;
+ }
+ // check for reordered glyphs
+ if (aMultiArgs.mpDXArray &&
+ nRunVisibleEndChar < mnEndCharPos &&
+ nRunVisibleEndChar >= mnMinCharPos &&
+ pGlyphs[n]->charPos() < mnEndCharPos &&
+ pGlyphs[n]->charPos() >= mnMinCharPos)
+ {
+ if (vRtl[nActiveCharPos - mnMinCharPos])
+ {
+ if (aMultiArgs.mpDXArray[nRunVisibleEndChar-mnMinCharPos]
+ >= aMultiArgs.mpDXArray[pGlyphs[n]->charPos() - mnMinCharPos])
+ {
+ nRunVisibleEndChar = pGlyphs[n]->charPos();
+ }
+ }
+ else if (aMultiArgs.mpDXArray[nRunVisibleEndChar-mnMinCharPos]
+ <= aMultiArgs.mpDXArray[pGlyphs[n]->charPos() - mnMinCharPos])
+ {
+ nRunVisibleEndChar = pGlyphs[n]->charPos();
+ }
+ }
+ }
+
+ // if a justification array is available
+ // => use it directly to calculate the corresponding run width
+ if( aMultiArgs.mpDXArray )
+ {
+ // the run advance is the width from the first char
+ // in the run to the first char in the next run
+ nRunAdvance = 0;
+ nActiveCharIndex = nActiveCharPos - mnMinCharPos;
+ if (nActiveCharIndex >= 0 && vRtl[nActiveCharIndex])
+ {
+ if (nRunVisibleEndChar > mnMinCharPos && nRunVisibleEndChar <= mnEndCharPos)
+ nRunAdvance -= aMultiArgs.mpDXArray[nRunVisibleEndChar - 1 - mnMinCharPos];
+ if (nLastRunEndChar > mnMinCharPos && nLastRunEndChar <= mnEndCharPos)
+ nRunAdvance += aMultiArgs.mpDXArray[nLastRunEndChar - 1 - mnMinCharPos];
+ }
+ else
+ {
+ if (nRunVisibleEndChar >= mnMinCharPos)
+ nRunAdvance += aMultiArgs.mpDXArray[nRunVisibleEndChar - mnMinCharPos];
+ if (nLastRunEndChar >= mnMinCharPos)
+ nRunAdvance -= aMultiArgs.mpDXArray[nLastRunEndChar - mnMinCharPos];
+ }
+ nLastRunEndChar = nRunVisibleEndChar;
+ nRunVisibleEndChar = pGlyphs[nFirstValid]->charPos();
+ // the requested width is still in pixel units
+ // => convert it to base level font units
+ nRunAdvance *= mnUnitsPerPixel;
+ }
+ else
+ {
+ // the measured width is still in fallback font units
+ // => convert it to base level font units
+ if( n > 0 ) // optimization: because (fUnitMul==1.0) for (n==0)
+ nRunAdvance = static_cast<long>(nRunAdvance*fUnitMul + 0.5);
+ }
+
+ // calculate new x position (in base level units)
+ nXPos += nRunAdvance;
+
+ // prepare for next fallback run
+ nActiveCharPos = pGlyphs[nFirstValid]->charPos();
+ // it essential that the runs don't get ahead of themselves and in the
+ // if( bKeepNotDef && !bNeedFallback ) statement above, the next run may
+ // have already been reached on the base level
+ for( int i = nFBLevel; --i >= 0;)
+ {
+ if (maFallbackRuns[i].GetRun(&nRunStart, &nRunEnd, &bRtl))
+ {
+ if (bRtl)
+ {
+ if (nRunStart > nActiveCharPos)
+ maFallbackRuns[i].NextRun();
+ }
+ else
+ {
+ if (nRunEnd <= nActiveCharPos)
+ maFallbackRuns[i].NextRun();
+ }
+ }
+ }
+ }
+
+ mpLayouts[0]->Simplify( true );
+}
+
+void MultiSalLayout::InitFont() const
+{
+ if( mnLevel > 0 )
+ mpLayouts[0]->InitFont();
+}
+
+void MultiSalLayout::DrawText( SalGraphics& rGraphics ) const
+{
+ for( int i = mnLevel; --i >= 0; )
+ {
+ SalLayout& rLayout = *mpLayouts[ i ];
+ rLayout.DrawBase() += maDrawBase;
+ rLayout.DrawOffset() += maDrawOffset;
+ rLayout.InitFont();
+ rLayout.DrawText( rGraphics );
+ rLayout.DrawOffset() -= maDrawOffset;
+ rLayout.DrawBase() -= maDrawBase;
+ }
+ // NOTE: now the baselevel font is active again
+}
+
+sal_Int32 MultiSalLayout::GetTextBreak( DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor ) const
+{
+ if( mnLevel <= 0 )
+ return -1;
+ if( mnLevel == 1 )
+ return mpLayouts[0]->GetTextBreak( nMaxWidth, nCharExtra, nFactor );
+
+ int nCharCount = mnEndCharPos - mnMinCharPos;
+ std::unique_ptr<DeviceCoordinate[]> const pCharWidths(new DeviceCoordinate[nCharCount]);
+ std::unique_ptr<DeviceCoordinate[]> const pFallbackCharWidths(new DeviceCoordinate[nCharCount]);
+ mpLayouts[0]->FillDXArray( pCharWidths.get() );
+
+ for( int n = 1; n < mnLevel; ++n )
+ {
+ SalLayout& rLayout = *mpLayouts[ n ];
+ rLayout.FillDXArray( pFallbackCharWidths.get() );
+ double fUnitMul = mnUnitsPerPixel;
+ fUnitMul /= rLayout.GetUnitsPerPixel();
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ if( pCharWidths[ i ] == 0 )
+ {
+ DeviceCoordinate w = pFallbackCharWidths[i];
+ w = static_cast<DeviceCoordinate>(w * fUnitMul + 0.5);
+ pCharWidths[ i ] = w;
+ }
+ }
+ }
+
+ DeviceCoordinate nWidth = 0;
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ nWidth += pCharWidths[ i ] * nFactor;
+ if( nWidth > nMaxWidth )
+ return (i + mnMinCharPos);
+ nWidth += nCharExtra;
+ }
+
+ return -1;
+}
+
+DeviceCoordinate MultiSalLayout::FillDXArray( DeviceCoordinate* pCharWidths ) const
+{
+ DeviceCoordinate nMaxWidth = 0;
+
+ // prepare merging of fallback levels
+ std::unique_ptr<DeviceCoordinate[]> pTempWidths;
+ const int nCharCount = mnEndCharPos - mnMinCharPos;
+ if( pCharWidths )
+ {
+ for( int i = 0; i < nCharCount; ++i )
+ pCharWidths[i] = 0;
+ pTempWidths.reset(new DeviceCoordinate[nCharCount]);
+ }
+
+ for( int n = mnLevel; --n >= 0; )
+ {
+ // query every fallback level
+ DeviceCoordinate nTextWidth = mpLayouts[n]->FillDXArray( pTempWidths.get() );
+ if( !nTextWidth )
+ continue;
+ // merge results from current level
+ double fUnitMul = mnUnitsPerPixel;
+ fUnitMul /= mpLayouts[n]->GetUnitsPerPixel();
+ nTextWidth = static_cast<DeviceCoordinate>(nTextWidth * fUnitMul + 0.5);
+ if( nMaxWidth < nTextWidth )
+ nMaxWidth = nTextWidth;
+ if( !pCharWidths )
+ continue;
+ // calculate virtual char widths using most probable fallback layout
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ // #i17359# restriction:
+ // one char cannot be resolved from different fallbacks
+ if( pCharWidths[i] != 0 )
+ continue;
+ DeviceCoordinate nCharWidth = pTempWidths[i];
+ if( !nCharWidth )
+ continue;
+ nCharWidth = static_cast<DeviceCoordinate>(nCharWidth * fUnitMul + 0.5);
+ pCharWidths[i] = nCharWidth;
+ }
+ }
+
+ return nMaxWidth;
+}
+
+void MultiSalLayout::GetCaretPositions( int nMaxIndex, long* pCaretXArray ) const
+{
+ SalLayout& rLayout = *mpLayouts[ 0 ];
+ rLayout.GetCaretPositions( nMaxIndex, pCaretXArray );
+
+ if( mnLevel > 1 )
+ {
+ std::unique_ptr<long[]> const pTempPos(new long[nMaxIndex]);
+ for( int n = 1; n < mnLevel; ++n )
+ {
+ mpLayouts[ n ]->GetCaretPositions( nMaxIndex, pTempPos.get() );
+ double fUnitMul = mnUnitsPerPixel;
+ fUnitMul /= mpLayouts[n]->GetUnitsPerPixel();
+ for( int i = 0; i < nMaxIndex; ++i )
+ if( pTempPos[i] >= 0 )
+ {
+ long w = pTempPos[i];
+ w = static_cast<long>(w*fUnitMul + 0.5);
+ pCaretXArray[i] = w;
+ }
+ }
+ }
+}
+
+bool MultiSalLayout::GetNextGlyph(const GlyphItem** pGlyph,
+ Point& rPos, int& nStart,
+ const PhysicalFontFace** pFallbackFont,
+ int* const pFallbackLevel) const
+{
+ // NOTE: nStart is tagged with current font index
+ int nLevel = static_cast<unsigned>(nStart) >> GF_FONTSHIFT;
+ nStart &= ~GF_FONTMASK;
+ for(; nLevel < mnLevel; ++nLevel, nStart=0 )
+ {
+ GenericSalLayout& rLayout = *mpLayouts[ nLevel ];
+ rLayout.InitFont();
+ const PhysicalFontFace* pFontFace = rLayout.GetFont().GetFontFace();
+ if (rLayout.GetNextGlyph(pGlyph, rPos, nStart))
+ {
+ int nFontTag = nLevel << GF_FONTSHIFT;
+ nStart |= nFontTag;
+ if (pFallbackFont)
+ *pFallbackFont = pFontFace;
+ if (pFallbackLevel)
+ *pFallbackLevel = nLevel;
+ rPos += maDrawBase;
+ rPos += maDrawOffset;
+ return true;
+ }
+ }
+
+ // #111016# reset to base level font when done
+ mpLayouts[0]->InitFont();
+ return false;
+}
+
+bool MultiSalLayout::GetOutline(basegfx::B2DPolyPolygonVector& rPPV) const
+{
+ bool bRet = false;
+
+ for( int i = mnLevel; --i >= 0; )
+ {
+ SalLayout& rLayout = *mpLayouts[ i ];
+ rLayout.DrawBase() = maDrawBase;
+ rLayout.DrawOffset() += maDrawOffset;
+ rLayout.InitFont();
+ bRet |= rLayout.GetOutline(rPPV);
+ rLayout.DrawOffset() -= maDrawOffset;
+ }
+
+ return bRet;
+}
+
+bool MultiSalLayout::IsKashidaPosValid(int nCharPos) const
+{
+ // Check the base layout
+ bool bValid = mpLayouts[0]->IsKashidaPosValid(nCharPos);
+
+ // If base layout returned false, it might be because the character was not
+ // supported there, so we check fallback layouts.
+ if (!bValid)
+ {
+ for (int i = 1; i < mnLevel; ++i)
+ {
+ // - 1 because there is no fallback run for the base layout, IIUC.
+ if (maFallbackRuns[i - 1].PosIsInAnyRun(nCharPos))
+ {
+ bValid = mpLayouts[i]->IsKashidaPosValid(nCharPos);
+ break;
+ }
+ }
+ }
+
+ return bValid;
+}
+
+const SalLayoutGlyphs* SalLayout::GetGlyphs() const
+{
+ // No access to the glyphs by default.
+ return nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */