3901e029bd
Communicate Kashida insertion positions in an explicit way. Rest of LibreOffice communicate adjustments to character widths (e.g. for justification or spacing) using so-called DX array. DX array is an array of absolute character positions (e.g. DX[n] is the position after character n from the start of the lines, and its widths is DX[n] - DX[n-1]). This DX array is modified also when Kashidas are inserted after a given character for Arabic justification, by expanding its width. VCL would use this to know where to insert the Kashidas and how many ones. But because DX array is used for both widths adjustments and kashida insertion, this turns out to be a source of bugs since VCL has tosecond guess the DX array to find which is pure width adjustment and which also involves Kashida insertion, and the heuristics it uses are fragile. This change adds a second array of booleans that records where Kashida is inserted and communicates it all the way from where Kashida insertion is decoded in Writer and down to VCL layout. This change passes the Kashida array only when it seems necessary (e.g. during drawing but not when measuring text since the DX array is enough in this case). Hopefully no places where Kashida insertion needs to be passed down were missed. A couple of glyph and layout flags that were used for old heuristics and no longer needed and are removed. This also fixes: tdf#87731 tdf#106309 tdf#108604 tdf#112849 tdf#114257 tdf#127176 tdf#145647 tdf#146199 Change-Id: I4ed0850ef2fdc3e9143341afac649e7e7d463c39 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/138068 Tested-by: Jenkins Reviewed-by: Caolán McNamara <caolanm@redhat.com>
748 lines
32 KiB
C++
748 lines
32 KiB
C++
/* -*- 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 <sal/config.h>
|
|
|
|
#include <o3tl/safeint.hxx>
|
|
#include <svx/svdotext.hxx>
|
|
#include <svx/svdoutl.hxx>
|
|
#include <basegfx/vector/b2dvector.hxx>
|
|
#include <sdr/primitive2d/sdrtextprimitive2d.hxx>
|
|
#include <basegfx/polygon/b2dpolygontools.hxx>
|
|
#include <basegfx/polygon/b2dpolygon.hxx>
|
|
#include <algorithm>
|
|
#include <com/sun/star/i18n/BreakIterator.hpp>
|
|
#include <comphelper/processfactory.hxx>
|
|
#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
|
|
#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
|
|
#include <drawinglayer/primitive2d/textprimitive2d.hxx>
|
|
#include <basegfx/color/bcolor.hxx>
|
|
|
|
// primitive decomposition helpers
|
|
#include <drawinglayer/attribute/strokeattribute.hxx>
|
|
#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
|
|
#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
|
|
#include <svx/unoapi.hxx>
|
|
#include <drawinglayer/geometry/viewinformation2d.hxx>
|
|
#include <sdr/attribute/sdrformtextoutlineattribute.hxx>
|
|
#include <utility>
|
|
|
|
using namespace com::sun::star;
|
|
|
|
// PathTextPortion helper
|
|
|
|
namespace
|
|
{
|
|
class impPathTextPortion
|
|
{
|
|
basegfx::B2DVector maOffset;
|
|
OUString maText;
|
|
sal_Int32 mnTextStart;
|
|
sal_Int32 mnTextLength;
|
|
sal_Int32 mnParagraph;
|
|
SvxFont maFont;
|
|
::std::vector< double > maDblDXArray; // double DXArray, font size independent -> unit coordinate system
|
|
::std::vector< sal_Bool > maKashidaArray;
|
|
lang::Locale maLocale;
|
|
|
|
bool mbRTL : 1;
|
|
|
|
public:
|
|
explicit impPathTextPortion(const DrawPortionInfo& rInfo)
|
|
: maOffset(rInfo.mrStartPos.X(), rInfo.mrStartPos.Y()),
|
|
maText(rInfo.maText),
|
|
mnTextStart(rInfo.mnTextStart),
|
|
mnTextLength(rInfo.mnTextLen),
|
|
mnParagraph(rInfo.mnPara),
|
|
maFont(rInfo.mrFont),
|
|
maKashidaArray(rInfo.mpKashidaArray.begin(), rInfo.mpKashidaArray.end()),
|
|
maLocale(rInfo.mpLocale ? *rInfo.mpLocale : lang::Locale()),
|
|
mbRTL(!rInfo.mrFont.IsVertical() && rInfo.IsRTL())
|
|
{
|
|
if(mnTextLength && !rInfo.mpDXArray.empty())
|
|
{
|
|
maDblDXArray.reserve(mnTextLength);
|
|
|
|
for(sal_Int32 a=0; a < mnTextLength; a++)
|
|
{
|
|
maDblDXArray.push_back(static_cast<double>(rInfo.mpDXArray[a]));
|
|
}
|
|
}
|
|
}
|
|
|
|
// for ::std::sort
|
|
bool operator<(const impPathTextPortion& rComp) const
|
|
{
|
|
if(mnParagraph < rComp.mnParagraph)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if(maOffset.getX() < rComp.maOffset.getX())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return (maOffset.getY() < rComp.maOffset.getY());
|
|
}
|
|
|
|
const OUString& getText() const { return maText; }
|
|
sal_Int32 getTextStart() const { return mnTextStart; }
|
|
sal_Int32 getTextLength() const { return mnTextLength; }
|
|
sal_Int32 getParagraph() const { return mnParagraph; }
|
|
const SvxFont& getFont() const { return maFont; }
|
|
bool isRTL() const { return mbRTL; }
|
|
const ::std::vector< double >& getDoubleDXArray() const { return maDblDXArray; }
|
|
const ::std::vector< sal_Bool >& getKashidaArray() const { return maKashidaArray; }
|
|
const lang::Locale& getLocale() const { return maLocale; }
|
|
|
|
sal_Int32 getPortionIndex(sal_Int32 nIndex, sal_Int32 nLength) const
|
|
{
|
|
if(mbRTL)
|
|
{
|
|
return (mnTextStart + (mnTextLength - (nIndex + nLength)));
|
|
}
|
|
else
|
|
{
|
|
return (mnTextStart + nIndex);
|
|
}
|
|
}
|
|
|
|
double getDisplayLength(sal_Int32 nIndex, sal_Int32 nLength) const
|
|
{
|
|
drawinglayer::primitive2d::TextLayouterDevice aTextLayouter;
|
|
double fRetval(0.0);
|
|
|
|
if(maFont.IsVertical())
|
|
{
|
|
fRetval = aTextLayouter.getTextHeight() * static_cast<double>(nLength);
|
|
}
|
|
else
|
|
{
|
|
fRetval = aTextLayouter.getTextWidth(maText, getPortionIndex(nIndex, nLength), nLength);
|
|
}
|
|
|
|
return fRetval;
|
|
}
|
|
};
|
|
} // end of anonymous namespace
|
|
|
|
|
|
// TextBreakup helper
|
|
|
|
namespace
|
|
{
|
|
class impTextBreakupHandler
|
|
{
|
|
SdrOutliner& mrOutliner;
|
|
::std::vector< impPathTextPortion > maPathTextPortions;
|
|
|
|
DECL_LINK(decompositionPathTextPrimitive, DrawPortionInfo*, void );
|
|
|
|
public:
|
|
explicit impTextBreakupHandler(SdrOutliner& rOutliner)
|
|
: mrOutliner(rOutliner)
|
|
{
|
|
}
|
|
|
|
const ::std::vector< impPathTextPortion >& decompositionPathTextPrimitive()
|
|
{
|
|
// strip portions to maPathTextPortions
|
|
mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decompositionPathTextPrimitive));
|
|
mrOutliner.StripPortions();
|
|
|
|
if(!maPathTextPortions.empty())
|
|
{
|
|
// sort portions by paragraph, x and y
|
|
::std::sort(maPathTextPortions.begin(), maPathTextPortions.end());
|
|
}
|
|
|
|
return maPathTextPortions;
|
|
}
|
|
};
|
|
|
|
IMPL_LINK(impTextBreakupHandler, decompositionPathTextPrimitive, DrawPortionInfo*, pInfo, void)
|
|
{
|
|
maPathTextPortions.emplace_back(*pInfo);
|
|
}
|
|
} // end of anonymous namespace
|
|
|
|
|
|
// TextBreakup one poly and one paragraph helper
|
|
|
|
namespace
|
|
{
|
|
class impPolygonParagraphHandler
|
|
{
|
|
const drawinglayer::attribute::SdrFormTextAttribute maSdrFormTextAttribute; // FormText parameters
|
|
drawinglayer::primitive2d::Primitive2DContainer& mrDecomposition; // destination primitive list
|
|
drawinglayer::primitive2d::Primitive2DContainer& mrShadowDecomposition; // destination primitive list for shadow
|
|
uno::Reference<i18n::XBreakIterator> mxBreak; // break iterator
|
|
|
|
static double getParagraphTextLength(const ::std::vector< const impPathTextPortion* >& rTextPortions)
|
|
{
|
|
drawinglayer::primitive2d::TextLayouterDevice aTextLayouter;
|
|
double fRetval(0.0);
|
|
|
|
for(const impPathTextPortion* pCandidate : rTextPortions)
|
|
{
|
|
if(pCandidate && pCandidate->getTextLength())
|
|
{
|
|
aTextLayouter.setFont(pCandidate->getFont());
|
|
fRetval += pCandidate->getDisplayLength(0, pCandidate->getTextLength());
|
|
}
|
|
}
|
|
|
|
return fRetval;
|
|
}
|
|
|
|
sal_Int32 getNextGlyphLen(const impPathTextPortion* pCandidate, sal_Int32 nPosition, const lang::Locale& rFontLocale)
|
|
{
|
|
sal_Int32 nNextGlyphLen(1);
|
|
|
|
if(mxBreak.is())
|
|
{
|
|
sal_Int32 nDone(0);
|
|
nNextGlyphLen = mxBreak->nextCharacters(pCandidate->getText(), nPosition,
|
|
rFontLocale, i18n::CharacterIteratorMode::SKIPCELL, 1, nDone) - nPosition;
|
|
}
|
|
|
|
return nNextGlyphLen;
|
|
}
|
|
|
|
public:
|
|
impPolygonParagraphHandler(
|
|
drawinglayer::attribute::SdrFormTextAttribute aSdrFormTextAttribute,
|
|
drawinglayer::primitive2d::Primitive2DContainer& rDecomposition,
|
|
drawinglayer::primitive2d::Primitive2DContainer& rShadowDecomposition)
|
|
: maSdrFormTextAttribute(std::move(aSdrFormTextAttribute)),
|
|
mrDecomposition(rDecomposition),
|
|
mrShadowDecomposition(rShadowDecomposition)
|
|
{
|
|
// prepare BreakIterator
|
|
uno::Reference<uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext();
|
|
mxBreak = i18n::BreakIterator::create(xContext);
|
|
}
|
|
|
|
void HandlePair(const basegfx::B2DPolygon& rPolygonCandidate, const ::std::vector< const impPathTextPortion* >& rTextPortions)
|
|
{
|
|
// prepare polygon geometry, take into account as many parameters as possible
|
|
basegfx::B2DPolygon aPolygonCandidate(rPolygonCandidate);
|
|
const double fPolyLength(basegfx::utils::getLength(aPolygonCandidate));
|
|
double fPolyEnd(fPolyLength);
|
|
double fPolyStart(0.0);
|
|
double fAutosizeScaleFactor(1.0);
|
|
bool bAutosizeScale(false);
|
|
|
|
if(maSdrFormTextAttribute.getFormTextMirror())
|
|
{
|
|
aPolygonCandidate.flip();
|
|
}
|
|
|
|
if(maSdrFormTextAttribute.getFormTextStart()
|
|
&& (XFormTextAdjust::Left == maSdrFormTextAttribute.getFormTextAdjust()
|
|
|| XFormTextAdjust::Right == maSdrFormTextAttribute.getFormTextAdjust()))
|
|
{
|
|
if(XFormTextAdjust::Left == maSdrFormTextAttribute.getFormTextAdjust())
|
|
{
|
|
fPolyStart += maSdrFormTextAttribute.getFormTextStart();
|
|
|
|
if(fPolyStart > fPolyEnd)
|
|
{
|
|
fPolyStart = fPolyEnd;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fPolyEnd -= maSdrFormTextAttribute.getFormTextStart();
|
|
|
|
if(fPolyEnd < fPolyStart)
|
|
{
|
|
fPolyEnd = fPolyStart;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(XFormTextAdjust::Left != maSdrFormTextAttribute.getFormTextAdjust())
|
|
{
|
|
// calculate total text length of this paragraph, some layout needs to be done
|
|
const double fParagraphTextLength(getParagraphTextLength(rTextPortions));
|
|
|
|
// check if text is too long for paragraph. If yes, handle as if left aligned (default),
|
|
// but still take care of XFormTextAdjust::AutoSize in that case
|
|
const bool bTextTooLong(fParagraphTextLength > (fPolyEnd - fPolyStart));
|
|
|
|
if(XFormTextAdjust::Right == maSdrFormTextAttribute.getFormTextAdjust())
|
|
{
|
|
if(!bTextTooLong)
|
|
{
|
|
// if right aligned, add difference to polygon start
|
|
fPolyStart += ((fPolyEnd - fPolyStart) - fParagraphTextLength);
|
|
}
|
|
}
|
|
else if(XFormTextAdjust::Center == maSdrFormTextAttribute.getFormTextAdjust())
|
|
{
|
|
if(!bTextTooLong)
|
|
{
|
|
// if centered, add half of difference to polygon start
|
|
fPolyStart += ((fPolyEnd - fPolyStart) - fParagraphTextLength) / 2.0;
|
|
}
|
|
}
|
|
else if(XFormTextAdjust::AutoSize == maSdrFormTextAttribute.getFormTextAdjust())
|
|
{
|
|
// if scale, prepare scale factor between curve length and text length
|
|
if(0.0 != fParagraphTextLength)
|
|
{
|
|
fAutosizeScaleFactor = (fPolyEnd - fPolyStart) / fParagraphTextLength;
|
|
bAutosizeScale = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// handle text portions for this paragraph
|
|
for(auto a = rTextPortions.begin(); a != rTextPortions.end() && fPolyStart < fPolyEnd; ++a)
|
|
{
|
|
const impPathTextPortion* pCandidate = *a;
|
|
basegfx::B2DVector aFontScaling;
|
|
|
|
if(pCandidate && pCandidate->getTextLength())
|
|
{
|
|
const drawinglayer::attribute::FontAttribute aCandidateFontAttribute(
|
|
drawinglayer::primitive2d::getFontAttributeFromVclFont(
|
|
aFontScaling,
|
|
pCandidate->getFont(),
|
|
pCandidate->isRTL(),
|
|
false));
|
|
|
|
drawinglayer::primitive2d::TextLayouterDevice aTextLayouter;
|
|
aTextLayouter.setFont(pCandidate->getFont());
|
|
sal_Int32 nUsedTextLength(0);
|
|
|
|
while(nUsedTextLength < pCandidate->getTextLength() && fPolyStart < fPolyEnd)
|
|
{
|
|
sal_Int32 nNextGlyphLen(getNextGlyphLen(pCandidate, pCandidate->getTextStart() + nUsedTextLength, pCandidate->getLocale()));
|
|
|
|
// prepare portion length. Takes RTL sections into account.
|
|
double fPortionLength(pCandidate->getDisplayLength(nUsedTextLength, nNextGlyphLen));
|
|
|
|
if(bAutosizeScale)
|
|
{
|
|
// when autosize scaling, expand portion length
|
|
fPortionLength *= fAutosizeScaleFactor;
|
|
}
|
|
|
|
// create transformation
|
|
basegfx::B2DHomMatrix aNewTransformA, aNewTransformB, aNewShadowTransform;
|
|
basegfx::B2DPoint aStartPos(basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart, fPolyLength));
|
|
basegfx::B2DPoint aEndPos(aStartPos);
|
|
|
|
// add font scaling
|
|
aNewTransformA.scale(aFontScaling.getX(), aFontScaling.getY());
|
|
|
|
// prepare scaling of text primitive
|
|
if(bAutosizeScale)
|
|
{
|
|
// when autosize scaling, expand text primitive scaling to it
|
|
aNewTransformA.scale(fAutosizeScaleFactor, fAutosizeScaleFactor);
|
|
}
|
|
|
|
// eventually create shadow primitives from aDecomposition and add to rDecomposition
|
|
const bool bShadow(XFormTextShadow::NONE != maSdrFormTextAttribute.getFormTextShadow());
|
|
|
|
if(bShadow)
|
|
{
|
|
if(XFormTextShadow::Normal == maSdrFormTextAttribute.getFormTextShadow())
|
|
{
|
|
aNewShadowTransform.translate(
|
|
maSdrFormTextAttribute.getFormTextShdwXVal(),
|
|
-maSdrFormTextAttribute.getFormTextShdwYVal());
|
|
}
|
|
else // XFormTextShadow::Slant
|
|
{
|
|
double fScaleValue(maSdrFormTextAttribute.getFormTextShdwYVal() / 100.0);
|
|
double fShearValue(-basegfx::deg2rad<10>(maSdrFormTextAttribute.getFormTextShdwXVal()));
|
|
|
|
aNewShadowTransform.scale(1.0, fScaleValue);
|
|
aNewShadowTransform.shearX(sin(fShearValue));
|
|
aNewShadowTransform.scale(1.0, cos(fShearValue));
|
|
}
|
|
}
|
|
|
|
switch(maSdrFormTextAttribute.getFormTextStyle())
|
|
{
|
|
case XFormTextStyle::Rotate :
|
|
{
|
|
aEndPos = basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart + fPortionLength, fPolyLength);
|
|
const basegfx::B2DVector aDirection(aEndPos - aStartPos);
|
|
aNewTransformB.rotate(atan2(aDirection.getY(), aDirection.getX()));
|
|
aNewTransformB.translate(aStartPos.getX(), aStartPos.getY());
|
|
|
|
break;
|
|
}
|
|
case XFormTextStyle::Upright :
|
|
{
|
|
aNewTransformB.translate(aStartPos.getX() - (fPortionLength / 2.0), aStartPos.getY());
|
|
|
|
break;
|
|
}
|
|
case XFormTextStyle::SlantX :
|
|
{
|
|
aEndPos = basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart + fPortionLength, fPolyLength);
|
|
const basegfx::B2DVector aDirection(aEndPos - aStartPos);
|
|
const double fShearValue(atan2(aDirection.getY(), aDirection.getX()));
|
|
const double fSin(sin(fShearValue));
|
|
const double fCos(cos(fShearValue));
|
|
|
|
aNewTransformB.shearX(-fSin);
|
|
|
|
// Scale may lead to objects without height since fCos == 0.0 is possible.
|
|
// Renderers need to handle that, it's not a forbidden value and does not
|
|
// need to be avoided
|
|
aNewTransformB.scale(1.0, fCos);
|
|
aNewTransformB.translate(aStartPos.getX() - (fPortionLength / 2.0), aStartPos.getY());
|
|
|
|
break;
|
|
}
|
|
case XFormTextStyle::SlantY :
|
|
{
|
|
aEndPos = basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart + fPortionLength, fPolyLength);
|
|
const basegfx::B2DVector aDirection(aEndPos - aStartPos);
|
|
const double fShearValue(atan2(aDirection.getY(), aDirection.getX()));
|
|
const double fCos(cos(fShearValue));
|
|
const double fTan(tan(fShearValue));
|
|
|
|
// shear to 'stand' on the curve
|
|
aNewTransformB.shearY(fTan);
|
|
|
|
// scale in X to make as tight as needed. As with XFT_SLANT_X, this may
|
|
// lead to primitives without width which the renderers will handle
|
|
aNewTransformA.scale(fCos, 1.0);
|
|
|
|
aNewTransformB.translate(aStartPos.getX(), aStartPos.getY());
|
|
|
|
break;
|
|
}
|
|
default : break; // XFormTextStyle::NONE
|
|
}
|
|
|
|
// distance from path?
|
|
if(maSdrFormTextAttribute.getFormTextDistance())
|
|
{
|
|
if(aEndPos.equal(aStartPos))
|
|
{
|
|
aEndPos = basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart + fPortionLength, fPolyLength);
|
|
}
|
|
|
|
// use back vector (aStartPos - aEndPos) here to get mirrored perpendicular as in old stuff
|
|
const basegfx::B2DVector aPerpendicular(
|
|
basegfx::getNormalizedPerpendicular(aStartPos - aEndPos) *
|
|
maSdrFormTextAttribute.getFormTextDistance());
|
|
aNewTransformB.translate(aPerpendicular.getX(), aPerpendicular.getY());
|
|
}
|
|
|
|
if(!pCandidate->getText().isEmpty() && nNextGlyphLen)
|
|
{
|
|
const sal_Int32 nPortionIndex(pCandidate->getPortionIndex(nUsedTextLength, nNextGlyphLen));
|
|
::std::vector< double > aNewDXArray;
|
|
|
|
if(nNextGlyphLen > 1 && !pCandidate->getDoubleDXArray().empty())
|
|
{
|
|
// copy DXArray for portion
|
|
aNewDXArray.insert(
|
|
aNewDXArray.begin(),
|
|
pCandidate->getDoubleDXArray().begin() + nPortionIndex,
|
|
pCandidate->getDoubleDXArray().begin() + (nPortionIndex + nNextGlyphLen));
|
|
|
|
if(nPortionIndex > 0)
|
|
{
|
|
// adapt to portion start
|
|
double fDXOffset= *(pCandidate->getDoubleDXArray().begin() + (nPortionIndex - 1));
|
|
::std::transform(
|
|
aNewDXArray.begin(), aNewDXArray.end(),
|
|
aNewDXArray.begin(), [fDXOffset](double x) { return x - fDXOffset; });
|
|
}
|
|
|
|
if(bAutosizeScale)
|
|
{
|
|
// when autosize scaling, adapt to DXArray, too
|
|
::std::transform(
|
|
aNewDXArray.begin(), aNewDXArray.end(),
|
|
aNewDXArray.begin(), [fAutosizeScaleFactor](double x) { return x * fAutosizeScaleFactor; });
|
|
}
|
|
}
|
|
|
|
if(bShadow)
|
|
{
|
|
// shadow primitive creation
|
|
const Color aShadowColor(maSdrFormTextAttribute.getFormTextShdwColor());
|
|
const basegfx::BColor aRGBShadowColor(aShadowColor.getBColor());
|
|
|
|
mrShadowDecomposition.push_back(
|
|
new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
|
|
aNewTransformB * aNewShadowTransform * aNewTransformA,
|
|
pCandidate->getText(),
|
|
nPortionIndex,
|
|
nNextGlyphLen,
|
|
std::vector(aNewDXArray),
|
|
std::vector(pCandidate->getKashidaArray()),
|
|
aCandidateFontAttribute,
|
|
pCandidate->getLocale(),
|
|
aRGBShadowColor) );
|
|
}
|
|
|
|
{
|
|
// primitive creation
|
|
const Color aColor(pCandidate->getFont().GetColor());
|
|
const basegfx::BColor aRGBColor(aColor.getBColor());
|
|
|
|
mrDecomposition.push_back(
|
|
new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
|
|
aNewTransformB * aNewTransformA,
|
|
pCandidate->getText(),
|
|
nPortionIndex,
|
|
nNextGlyphLen,
|
|
std::move(aNewDXArray),
|
|
std::vector(pCandidate->getKashidaArray()),
|
|
aCandidateFontAttribute,
|
|
pCandidate->getLocale(),
|
|
aRGBColor) );
|
|
}
|
|
}
|
|
|
|
// consume from portion
|
|
nUsedTextLength += nNextGlyphLen;
|
|
|
|
// consume from polygon
|
|
fPolyStart += fPortionLength;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
} // end of anonymous namespace
|
|
|
|
|
|
// primitive decomposition helpers
|
|
|
|
namespace
|
|
{
|
|
void impAddPolygonStrokePrimitives(
|
|
const basegfx::B2DPolyPolygonVector& rB2DPolyPolyVector,
|
|
const basegfx::B2DHomMatrix& rTransform,
|
|
const drawinglayer::attribute::LineAttribute& rLineAttribute,
|
|
const drawinglayer::attribute::StrokeAttribute& rStrokeAttribute,
|
|
drawinglayer::primitive2d::Primitive2DContainer& rTarget)
|
|
{
|
|
for(const auto& rB2DPolyPolygon : rB2DPolyPolyVector)
|
|
{
|
|
// prepare PolyPolygons
|
|
basegfx::B2DPolyPolygon aB2DPolyPolygon = rB2DPolyPolygon;
|
|
aB2DPolyPolygon.transform(rTransform);
|
|
|
|
for(auto const& rPolygon : std::as_const(aB2DPolyPolygon))
|
|
{
|
|
// create one primitive per polygon
|
|
rTarget.push_back(
|
|
new drawinglayer::primitive2d::PolygonStrokePrimitive2D(
|
|
rPolygon, rLineAttribute, rStrokeAttribute) );
|
|
}
|
|
}
|
|
}
|
|
|
|
drawinglayer::primitive2d::Primitive2DContainer impAddPathTextOutlines(
|
|
const drawinglayer::primitive2d::Primitive2DContainer& rSource,
|
|
const drawinglayer::attribute::SdrFormTextOutlineAttribute& rOutlineAttribute)
|
|
{
|
|
drawinglayer::primitive2d::Primitive2DContainer aNewPrimitives;
|
|
|
|
for(const drawinglayer::primitive2d::Primitive2DReference& a : rSource)
|
|
{
|
|
const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pTextCandidate = dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(a.get());
|
|
|
|
if(pTextCandidate)
|
|
{
|
|
basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
|
|
basegfx::B2DHomMatrix aPolygonTransform;
|
|
|
|
// get text outlines and their object transformation
|
|
pTextCandidate->getTextOutlinesAndTransformation(aB2DPolyPolyVector, aPolygonTransform);
|
|
|
|
if(!aB2DPolyPolyVector.empty())
|
|
{
|
|
// create stroke primitives
|
|
drawinglayer::primitive2d::Primitive2DContainer aStrokePrimitives;
|
|
impAddPolygonStrokePrimitives(
|
|
aB2DPolyPolyVector,
|
|
aPolygonTransform,
|
|
rOutlineAttribute.getLineAttribute(),
|
|
rOutlineAttribute.getStrokeAttribute(),
|
|
aStrokePrimitives);
|
|
const sal_uInt32 nStrokeCount(aStrokePrimitives.size());
|
|
|
|
if(nStrokeCount)
|
|
{
|
|
if(rOutlineAttribute.getTransparence())
|
|
{
|
|
// create UnifiedTransparencePrimitive2D
|
|
aNewPrimitives.push_back(
|
|
new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
|
|
std::move(aStrokePrimitives),
|
|
static_cast<double>(rOutlineAttribute.getTransparence()) / 100.0) );
|
|
}
|
|
else
|
|
{
|
|
// add polygons to rDecomposition as polygonStrokePrimitives
|
|
aNewPrimitives.append( std::move(aStrokePrimitives) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return aNewPrimitives;
|
|
}
|
|
} // end of anonymous namespace
|
|
|
|
|
|
// primitive decomposition
|
|
|
|
void SdrTextObj::impDecomposePathTextPrimitive(
|
|
drawinglayer::primitive2d::Primitive2DContainer& rTarget,
|
|
const drawinglayer::primitive2d::SdrPathTextPrimitive2D& rSdrPathTextPrimitive,
|
|
const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
|
|
{
|
|
drawinglayer::primitive2d::Primitive2DContainer aRetvalA;
|
|
drawinglayer::primitive2d::Primitive2DContainer aRetvalB;
|
|
|
|
// prepare outliner
|
|
SdrOutliner& rOutliner = ImpGetDrawOutliner();
|
|
rOutliner.SetUpdateLayout(true);
|
|
rOutliner.Clear();
|
|
rOutliner.SetPaperSize(Size(LONG_MAX,LONG_MAX));
|
|
rOutliner.SetText(rSdrPathTextPrimitive.getOutlinerParaObject());
|
|
|
|
// set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
|
|
rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
|
|
|
|
// now break up to text portions
|
|
impTextBreakupHandler aConverter(rOutliner);
|
|
const ::std::vector< impPathTextPortion > rPathTextPortions = aConverter.decompositionPathTextPrimitive();
|
|
|
|
if(!rPathTextPortions.empty())
|
|
{
|
|
// get FormText and polygon values
|
|
const drawinglayer::attribute::SdrFormTextAttribute& rFormTextAttribute = rSdrPathTextPrimitive.getSdrFormTextAttribute();
|
|
const basegfx::B2DPolyPolygon& rPathPolyPolygon(rSdrPathTextPrimitive.getPathPolyPolygon());
|
|
|
|
// get loop count
|
|
sal_uInt32 nLoopCount(rPathPolyPolygon.count());
|
|
|
|
if(o3tl::make_unsigned(rOutliner.GetParagraphCount()) < nLoopCount)
|
|
{
|
|
nLoopCount = rOutliner.GetParagraphCount();
|
|
}
|
|
|
|
if(nLoopCount)
|
|
{
|
|
// prepare common decomposition stuff
|
|
drawinglayer::primitive2d::Primitive2DContainer aRegularDecomposition;
|
|
drawinglayer::primitive2d::Primitive2DContainer aShadowDecomposition;
|
|
impPolygonParagraphHandler aPolygonParagraphHandler(
|
|
rFormTextAttribute,
|
|
aRegularDecomposition,
|
|
aShadowDecomposition);
|
|
sal_uInt32 a;
|
|
|
|
for(a = 0; a < nLoopCount; a++)
|
|
{
|
|
// filter text portions for this paragraph
|
|
::std::vector< const impPathTextPortion* > aParagraphTextPortions;
|
|
|
|
for(const auto & rCandidate : rPathTextPortions)
|
|
{
|
|
if(static_cast<sal_uInt32>(rCandidate.getParagraph()) == a)
|
|
{
|
|
aParagraphTextPortions.push_back(&rCandidate);
|
|
}
|
|
}
|
|
|
|
// handle data pair polygon/ParagraphTextPortions
|
|
if(!aParagraphTextPortions.empty())
|
|
{
|
|
aPolygonParagraphHandler.HandlePair(rPathPolyPolygon.getB2DPolygon(a), aParagraphTextPortions);
|
|
}
|
|
}
|
|
|
|
const sal_uInt32 nShadowCount(aShadowDecomposition.size());
|
|
const sal_uInt32 nRegularCount(aRegularDecomposition.size());
|
|
|
|
if(nShadowCount)
|
|
{
|
|
// add shadow primitives to decomposition
|
|
|
|
// if necessary, add shadow outlines
|
|
if(rFormTextAttribute.getFormTextOutline()
|
|
&& !rFormTextAttribute.getShadowOutline().isDefault())
|
|
{
|
|
aRetvalA = aShadowDecomposition;
|
|
const drawinglayer::primitive2d::Primitive2DContainer aOutlines(
|
|
impAddPathTextOutlines(
|
|
aShadowDecomposition,
|
|
rFormTextAttribute.getShadowOutline()));
|
|
|
|
aRetvalA.append(aOutlines);
|
|
}
|
|
else
|
|
aRetvalA = std::move(aShadowDecomposition);
|
|
}
|
|
|
|
if(nRegularCount)
|
|
{
|
|
// add normal primitives to decomposition
|
|
|
|
// if necessary, add outlines
|
|
if(rFormTextAttribute.getFormTextOutline()
|
|
&& !rFormTextAttribute.getOutline().isDefault())
|
|
{
|
|
aRetvalB = aRegularDecomposition;
|
|
const drawinglayer::primitive2d::Primitive2DContainer aOutlines(
|
|
impAddPathTextOutlines(
|
|
aRegularDecomposition,
|
|
rFormTextAttribute.getOutline()));
|
|
|
|
aRetvalB.append(aOutlines);
|
|
}
|
|
else
|
|
aRetvalB = std::move(aRegularDecomposition);
|
|
}
|
|
}
|
|
}
|
|
|
|
// clean up outliner
|
|
rOutliner.SetDrawPortionHdl(Link<DrawPortionInfo*,void>());
|
|
rOutliner.Clear();
|
|
rOutliner.setVisualizedPage(nullptr);
|
|
|
|
// concatenate all results
|
|
rTarget.append(std::move(aRetvalA));
|
|
rTarget.append(std::move(aRetvalB));
|
|
}
|
|
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|