/* -*- 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 .
*/
// insert outline at correct position
rVector.push_back( aGlyphOutline );
}
}
return (bAllOk && bOneOk);
}
// No need to expand to the next pixel, when the character only covers its tiny fraction staticdouble trimInsignificant(double n)
{ return std::abs(n) >= 0x1p53 ? n : std::round(n * 1e5) / 1e5;
}
// 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? staticint lcl_CalcAsianKerning(sal_UCS4 c, bool bLeft)
{ // http://www.asahi-net.or.jp/~sd5a-ucd/freetexts/jis/x4051/1995/appendix.html staticconstsignedchar 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 ]; elseswitch( 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;
}
// calculate caret positions using glyph array for (autoconst& aGlyphItem : m_GlyphItems)
{ auto nCurrX = aGlyphItem.linearPos().getX() - aGlyphItem.xOffset(); auto nCharStart = aGlyphItem.charPos(); auto nCharEnd = nCharStart + aGlyphItem.charCount() - 1; if (!aGlyphItem.IsRTLGlyph())
{ // unchanged positions for LTR case for (int i = nCharStart; i <= nCharEnd; i++)
{ int n = i - mnMinCharPos; int nCurrIdx = 2 * n;
auto nLeft = nCurrX;
nCurrX += aCharWidths[n]; auto nRight = nCurrX;
rCaretPositions[nCurrIdx] = nLeft;
rCaretPositions[nCurrIdx + 1] = nRight;
}
} else
{ // reverse positions for RTL case for (int i = nCharEnd; i >= nCharStart; i--)
{ int n = i - mnMinCharPos; int nCurrIdx = 2 * n;
auto nRight = nCurrX;
nCurrX += aCharWidths[n]; auto nLeft = nCurrX;
// 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->newWidth() - pGlyphIter->origWidth(); // calculate the x-offset to the old position double nXDelta = nNewXPos - pGlyphIter->linearPos().getX() + pGlyphIter->xOffset(); // adjust all following glyph positions if needed if( nXDelta != 0 )
{ for( std::vector<GlyphItem>::iterator pGlyphIterEnd = m_GlyphItems.end(); pGlyphIter != pGlyphIterEnd; ++pGlyphIter )
{
pGlyphIter->adjustLinearPosX(nXDelta);
}
}
}
if (!rArgs.mstJustification.empty() && rArgs.mnLayoutWidth)
{ // for stretched text in a MultiSalLayout the target width needs to be // distributed by individually adjusting its virtual character widths double nTargetWidth = aMultiArgs.mnLayoutWidth;
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;
FillDXArray( &aJustificationArray, {} ); // #i17359# multilayout is not simplified yet, so calculating the // unjustified width needs handholding; also count the number of // stretchable virtual char widths double nOrigWidth = 0; int nStretchable = 0; for( int i = 0; i < nCharCount; ++i )
{ // convert array from widths to sum of widths
nOrigWidth += aJustificationArray[i]; if( aJustificationArray[i] > 0 )
++nStretchable;
}
// now we are able to distribute the extra width over the virtual char widths if( nOrigWidth && (nTargetWidth != nOrigWidth) )
{ double nDiffWidth = nTargetWidth - nOrigWidth; double nWidthSum = 0; for( int i = 0; i < nCharCount; ++i )
{ double nJustWidth = aJustificationArray[i]; if( (nJustWidth > 0) && (nStretchable > 0) )
{ double nDeltaWidth = nDiffWidth / nStretchable;
nJustWidth += nDeltaWidth;
nDiffWidth -= nDeltaWidth;
--nStretchable;
}
nWidthSum += nJustWidth;
aJustificationArray[i] = nWidthSum;
} if( nWidthSum != nTargetWidth )
aJustificationArray[ nCharCount-1 ] = nTargetWidth;
// change the DXArray temporarily (just for the justification)
JustificationData stJustData{ rArgs.mnMinCharPos, nCharCount }; for (sal_Int32 i = 0; i < nCharCount; ++i)
{
stJustData.SetTotalAdvance(rArgs.mnMinCharPos + i, aJustificationArray[i]);
}
void MultiSalLayout::ImplAdjustMultiLayout(vcl::text::ImplLayoutArgs& rArgs,
vcl::text::ImplLayoutArgs& rMultiArgs, const JustificationData& rstJustification)
{ // 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();
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 double nXPos = 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
mpLayouts[n]->MoveGlyph( nStartOld[n], nXPos );
} else
{
n = 0; // keep NotDef in base level
}
if( n > 0 )
{ // drop the NotDef glyphs in the base layout run if a fallback run exists // // tdf#163761: The whole algorithm in this outer loop works by advancing through // all of the glyphs and runs in lock-step. The current glyph in the base layout // must not outpace the fallback runs. The following loop does this by breaking // at the end of the current fallback run (which comes from the previous level). 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 double nRunAdvance = 0; bool bKeepNotDef = (nFBLevel >= nLevel); for(;;)
{ // check for reordered glyphs // tdf#154104: Moved this up in the loop body to handle the case of single-glyph // runs that start on a reordered glyph. if (!rstJustification.empty())
{ if (vRtl[nActiveCharPos - mnMinCharPos])
{ if (rstJustification.GetTotalAdvance(nRunVisibleEndChar)
>= rstJustification.GetTotalAdvance(pGlyphs[n]->charPos()))
{
nRunVisibleEndChar = pGlyphs[n]->charPos();
}
} elseif (rstJustification.GetTotalAdvance(nRunVisibleEndChar)
<= rstJustification.GetTotalAdvance(pGlyphs[n]->charPos()))
{
nRunVisibleEndChar = pGlyphs[n]->charPos();
}
}
nRunAdvance += pGlyphs[n]->newWidth();
// 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;
} elseif (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;
}
}
// if a justification array is available // => use it directly to calculate the corresponding run width if (!rstJustification.empty())
{ // 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])
{
nRunAdvance -= rstJustification.GetTotalAdvance(nRunVisibleEndChar - 1);
nRunAdvance += rstJustification.GetTotalAdvance(nLastRunEndChar - 1);
} else
{
nRunAdvance += rstJustification.GetTotalAdvance(nRunVisibleEndChar);
nRunAdvance -= rstJustification.GetTotalAdvance(nLastRunEndChar);
}
nLastRunEndChar = nRunVisibleEndChar;
nRunVisibleEndChar = pGlyphs[nFirstValid]->charPos();
}
// calculate new x position
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))
{ // tdf#165510: Need to use the direction of the current character, // not the direction of the fallback run.
nActiveCharIndex = nActiveCharPos - mnMinCharPos; if (nActiveCharIndex >= 0)
{
bRtl = vRtl[nActiveCharIndex];
}
if (bRtl)
{ if (nRunStart > nActiveCharPos)
maFallbackRuns[i].NextRun();
} else
{ if (nRunEnd <= nActiveCharPos)
maFallbackRuns[i].NextRun();
}
}
}
}
mpLayouts[0]->Simplify( true );
}
void MultiSalLayout::DrawText( SalGraphics& rGraphics ) const
{ for( int i = mnLevel; --i >= 0; )
{
SalLayout& rLayout = *mpLayouts[ i ];
rLayout.DrawBase() += maDrawBase;
rLayout.DrawOffset() += maDrawOffset;
rLayout.DrawText( rGraphics );
rLayout.DrawOffset() -= maDrawOffset;
rLayout.DrawBase() -= maDrawBase;
} // NOTE: now the baselevel font is active again
}
for( int n = 1; n < mnLevel; ++n )
{
SalLayout& rLayout = *mpLayouts[ n ];
rLayout.FillDXArray( &aFallbackCharWidths, {} ); for( int i = 0; i < nCharCount; ++i ) if( aCharWidths[ i ] == 0 )
aCharWidths[i] = aFallbackCharWidths[i];
}
double nWidth = 0; for( int i = 0; i < nCharCount; ++i )
{
nWidth += aCharWidths[ i ] * nFactor; if( nWidth > nMaxWidth ) return (i + mnMinCharPos);
nWidth += nCharExtra;
}
return -1;
}
double MultiSalLayout::GetTextWidth() const
{ // Measure text width. There might be holes in each SalLayout due to // missing chars, so we use GetNextGlyph() to get the glyphs across all // layouts. int nStart = 0;
basegfx::B2DPoint aPos; const GlyphItem* pGlyphItem;
double MultiSalLayout::GetPartialTextWidth(sal_Int32 skipStart, sal_Int32 amt) const
{ // Measure text width. There might be holes in each SalLayout due to // missing chars, so we use GetNextGlyph() to get the glyphs across all // layouts. int nStart = 0;
basegfx::B2DPoint aPos; const GlyphItem* pGlyphItem;
auto skipEnd = skipStart + amt; double nWidth = 0; while (GetNextGlyph(&pGlyphItem, aPos, nStart))
{ auto cpos = pGlyphItem->charPos(); if (cpos >= skipStart && cpos < skipEnd)
{
nWidth += pGlyphItem->newWidth();
}
}
for (int n = mnLevel; --n >= 0;)
{ // query every fallback level
mpLayouts[n]->FillDXArray(&aTempWidths, rStr);
// 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; double nCharWidth = aTempWidths[i]; if (!nCharWidth) continue;
(*pCharWidths)[i] = nCharWidth;
}
}
}
for (int n = mnLevel; --n >= 0;)
{ // query every fallback level
mpLayouts[n]->GetCaretPositions(aTempPos, rStr);
// calculate virtual char widths using most probable fallback layout for (int i = 0; i < nCaretPositions; ++i)
{ // one char cannot be resolved from different fallbacks if (rCaretPositions[i] != -1) continue; if (aTempPos[i] >= 0)
rCaretPositions[i] = aTempPos[i];
}
}
}
bool MultiSalLayout::GetNextGlyph(const GlyphItem** pGlyph,
basegfx::B2DPoint& rPos, int& nStart, const LogicalFontInstance** ppGlyphFont) 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 ]; if (rLayout.GetNextGlyph(pGlyph, rPos, nStart, ppGlyphFont))
{ int nFontTag = nLevel << GF_FONTSHIFT;
nStart |= nFontTag;
rPos += maDrawBase + maDrawOffset; returntrue;
}
}
for( int i = mnLevel; --i >= 0; )
{
SalLayout& rLayout = *mpLayouts[ i ];
rLayout.DrawBase() = maDrawBase;
rLayout.DrawOffset() += maDrawOffset;
bRet |= rLayout.GetOutline(rPPV);
rLayout.DrawOffset() -= maDrawOffset;
}
return bRet;
}
bool MultiSalLayout::HasFontKashidaPositions() const
{ // tdf#163215: VCL cannot suggest valid kashida positions for certain fonts (e.g. AAT). // In order to strictly validate kashida positions, all fallback fonts must allow it. for (int n = 0; n < mnLevel; ++n)
{ if (!mpLayouts[n]->HasFontKashidaPositions())
{ returnfalse;
}
}
returntrue;
}
bool MultiSalLayout::IsKashidaPosValid(int nCharPos, int nNextCharPos) const
{ // Check the base layout bool bValid = mpLayouts[0]->IsKashidaPosValid(nCharPos, nNextCharPos);
// 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) &&
maFallbackRuns[i - 1].PosIsInAnyRun(nNextCharPos))
{
bValid = mpLayouts[i]->IsKashidaPosValid(nCharPos, nNextCharPos); break;
}
}
}
return bValid;
}
SalLayoutGlyphs MultiSalLayout::GetGlyphs() const
{
SalLayoutGlyphs glyphs; for( int n = 0; n < mnLevel; ++n )
glyphs.AppendImpl(mpLayouts[n]->GlyphsImpl().clone()); return glyphs;
}
void MultiSalLayout::drawSalLayout(void* pSurface, const basegfx::BColor& rTextColor, bool bAntiAliased) const
{ for( int i = mnLevel; --i >= 0; )
{
Application::GetDefaultDevice()->GetGraphics()->DrawSalLayout(*mpLayouts[ i ], pSurface, rTextColor, bAntiAliased);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.