blob: 11bf9dfb4d3101f5ef58ecf265472dbf0c36e2ba [file] [log] [blame]
/**************************************************************
*
* 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
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_svgio.hxx"
#include <svgio/svgreader/svgcharacternode.hxx>
#include <svgio/svgreader/svgstyleattributes.hxx>
#include <drawinglayer/attribute/fontattribute.hxx>
#include <drawinglayer/primitive2d/textprimitive2d.hxx>
#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
#include <drawinglayer/primitive2d/textbreakuphelper.hxx>
#include <drawinglayer/primitive2d/groupprimitive2d.hxx>
#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
//////////////////////////////////////////////////////////////////////////////
namespace svgio
{
namespace svgreader
{
SvgTextPositions::SvgTextPositions()
: maX(),
maY(),
maDx(),
maDy(),
maRotate(),
maTextLength(),
mbLengthAdjust(true)
{
}
void SvgTextPositions::parseTextPositionAttributes(const rtl::OUString& /*rTokenName*/, SVGToken aSVGToken, const rtl::OUString& aContent)
{
// parse own
switch(aSVGToken)
{
case SVGTokenX:
{
if(aContent.getLength())
{
SvgNumberVector aVector;
if(readSvgNumberVector(aContent, aVector))
{
setX(aVector);
}
}
break;
}
case SVGTokenY:
{
if(aContent.getLength())
{
SvgNumberVector aVector;
if(readSvgNumberVector(aContent, aVector))
{
setY(aVector);
}
}
break;
}
case SVGTokenDx:
{
if(aContent.getLength())
{
SvgNumberVector aVector;
if(readSvgNumberVector(aContent, aVector))
{
setDx(aVector);
}
}
break;
}
case SVGTokenDy:
{
if(aContent.getLength())
{
SvgNumberVector aVector;
if(readSvgNumberVector(aContent, aVector))
{
setDy(aVector);
}
}
break;
}
case SVGTokenRotate:
{
if(aContent.getLength())
{
SvgNumberVector aVector;
if(readSvgNumberVector(aContent, aVector))
{
setRotate(aVector);
}
}
break;
}
case SVGTokenTextLength:
{
SvgNumber aNum;
if(readSingleNumber(aContent, aNum))
{
if(aNum.isPositive())
{
setTextLength(aNum);
}
}
break;
}
case SVGTokenLengthAdjust:
{
if(aContent.getLength())
{
static rtl::OUString aStrSpacing(rtl::OUString::createFromAscii("spacing"));
static rtl::OUString aStrSpacingAndGlyphs(rtl::OUString::createFromAscii("spacingAndGlyphs"));
if(aContent.match(aStrSpacing))
{
setLengthAdjust(true);
}
else if(aContent.match(aStrSpacingAndGlyphs))
{
setLengthAdjust(false);
}
}
break;
}
default:
{
break;
}
}
}
} // end of namespace svgreader
} // end of namespace svgio
//////////////////////////////////////////////////////////////////////////////
namespace svgio
{
namespace svgreader
{
class localTextBreakupHelper : public drawinglayer::primitive2d::TextBreakupHelper
{
private:
SvgTextPosition& mrSvgTextPosition;
protected:
/// allow user callback to allow changes to the new TextTransformation. Default
/// does nothing.
virtual bool allowChange(sal_uInt32 nCount, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength);
public:
localTextBreakupHelper(
const drawinglayer::primitive2d::TextSimplePortionPrimitive2D& rSource,
SvgTextPosition& rSvgTextPosition)
: drawinglayer::primitive2d::TextBreakupHelper(rSource),
mrSvgTextPosition(rSvgTextPosition)
{
}
};
bool localTextBreakupHelper::allowChange(sal_uInt32 /*nCount*/, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 /*nIndex*/, sal_uInt32 /*nLength*/)
{
const double fRotation(mrSvgTextPosition.consumeRotation());
if(0.0 != fRotation)
{
const basegfx::B2DPoint aBasePoint(rNewTransform * basegfx::B2DPoint(0.0, 0.0));
rNewTransform.translate(-aBasePoint.getX(), -aBasePoint.getY());
rNewTransform.rotate(fRotation);
rNewTransform.translate(aBasePoint.getX(), aBasePoint.getY());
}
return true;
}
} // end of namespace svgreader
} // end of namespace svgio
//////////////////////////////////////////////////////////////////////////////
namespace svgio
{
namespace svgreader
{
SvgCharacterNode::SvgCharacterNode(
SvgDocument& rDocument,
SvgNode* pParent,
const rtl::OUString& rText)
: SvgNode(SVGTokenCharacter, rDocument, pParent),
maText(rText)
{
}
SvgCharacterNode::~SvgCharacterNode()
{
}
const SvgStyleAttributes* SvgCharacterNode::getSvgStyleAttributes() const
{
// no own style, use parent's
if(getParent())
{
return getParent()->getSvgStyleAttributes();
}
else
{
return 0;
}
}
drawinglayer::primitive2d::TextSimplePortionPrimitive2D* SvgCharacterNode::createSimpleTextPrimitive(
SvgTextPosition& rSvgTextPosition,
const SvgStyleAttributes& rSvgStyleAttributes) const
{
// prepare retval, index and length
drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pRetval = 0;
sal_uInt32 nIndex(0);
sal_uInt32 nLength(getText().getLength());
if(nLength)
{
// prepare FontAttribute
rtl::OUString aFontFamily = rSvgStyleAttributes.getFontFamily().empty() ?
rtl::OUString(rtl::OUString::createFromAscii("Times New Roman")) :
rSvgStyleAttributes.getFontFamily()[0];
// #122324# if the FontFamily name ends on ' embedded' it is probably a re-import
// of a SVG export with fiont embedding. Remove this to make font matching work. This
// is pretty safe since there should be no font family names ending on ' embedded'.
// Remove again when FontEmbedding is implemented in SVG import
if(aFontFamily.endsWithAsciiL(" embedded", 9))
{
aFontFamily = aFontFamily.copy(0, aFontFamily.getLength() - 9);
}
const ::FontWeight nFontWeight(getVclFontWeight(rSvgStyleAttributes.getFontWeight()));
bool bSymbol(false);
bool bVertical(false);
bool bItalic(FontStyle_italic == rSvgStyleAttributes.getFontStyle() || FontStyle_oblique == rSvgStyleAttributes.getFontStyle());
bool bMonospaced(false);
bool bOutline(false);
bool bRTL(false);
bool bBiDiStrong(false);
const drawinglayer::attribute::FontAttribute aFontAttribute(
aFontFamily,
rtl::OUString(),
nFontWeight,
bSymbol,
bVertical,
bItalic,
bMonospaced,
bOutline,
bRTL,
bBiDiStrong);
// prepare FontSize
double fFontWidth(rSvgStyleAttributes.getFontSize().solve(*this, length));
double fFontHeight(fFontWidth);
// prepare locale
::com::sun::star::lang::Locale aLocale;
// prepare TextLayouterDevice
drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
aTextLayouterDevice.setFontAttribute(aFontAttribute, fFontWidth, fFontHeight, aLocale);
// prepare TextArray
::std::vector< double > aTextArray(rSvgTextPosition.getX());
if(!aTextArray.empty() && aTextArray.size() < nLength)
{
const sal_uInt32 nArray(aTextArray.size());
if(nArray < nLength)
{
double fStartX(0.0);
if(rSvgTextPosition.getParent() && rSvgTextPosition.getParent()->getAbsoluteX())
{
fStartX = rSvgTextPosition.getParent()->getPosition().getX();
}
else
{
fStartX = aTextArray[nArray - 1];
}
::std::vector< double > aExtendArray(aTextLayouterDevice.getTextArray(getText(), nArray, nLength - nArray));
aTextArray.reserve(nLength);
for(sal_uInt32 a(0); a < aExtendArray.size(); a++)
{
aTextArray.push_back(aExtendArray[a] + fStartX);
}
}
}
// get current TextPosition and TextWidth in units
basegfx::B2DPoint aPosition(rSvgTextPosition.getPosition());
double fTextWidth(aTextLayouterDevice.getTextWidth(getText(), nIndex, nLength));
// check for user-given TextLength
if(0.0 != rSvgTextPosition.getTextLength()
&& !basegfx::fTools::equal(fTextWidth, rSvgTextPosition.getTextLength()))
{
const double fFactor(rSvgTextPosition.getTextLength() / fTextWidth);
if(rSvgTextPosition.getLengthAdjust())
{
// spacing, need to create and expand TextArray
if(aTextArray.empty())
{
aTextArray = aTextLayouterDevice.getTextArray(getText(), nIndex, nLength);
}
for(sal_uInt32 a(0); a < aTextArray.size(); a++)
{
aTextArray[a] *= fFactor;
}
}
else
{
// spacing and glyphs, just apply to FontWidth
fFontWidth *= fFactor;
}
fTextWidth = rSvgTextPosition.getTextLength();
}
// get TextAlign
TextAlign aTextAlign(rSvgStyleAttributes.getTextAlign());
// map TextAnchor to TextAlign, there seems not to be a difference
if(TextAnchor_notset != rSvgStyleAttributes.getTextAnchor())
{
switch(rSvgStyleAttributes.getTextAnchor())
{
case TextAnchor_start:
{
aTextAlign = TextAlign_left;
break;
}
case TextAnchor_middle:
{
aTextAlign = TextAlign_center;
break;
}
case TextAnchor_end:
{
aTextAlign = TextAlign_right;
break;
}
default:
{
break;
}
}
}
// apply TextAlign
switch(aTextAlign)
{
case TextAlign_right:
{
aPosition.setX(aPosition.getX() - fTextWidth);
break;
}
case TextAlign_center:
{
aPosition.setX(aPosition.getX() - (fTextWidth * 0.5));
break;
}
case TextAlign_notset:
case TextAlign_left:
case TextAlign_justify:
{
// TextAlign_notset, TextAlign_left: nothing to do
// TextAlign_justify is not clear currently; handle as TextAlign_left
break;
}
}
// get BaselineShift
const BaselineShift aBaselineShift(rSvgStyleAttributes.getBaselineShift());
// apply BaselineShift
switch(aBaselineShift)
{
case BaselineShift_Sub:
{
aPosition.setY(aPosition.getY() + aTextLayouterDevice.getUnderlineOffset());
break;
}
case BaselineShift_Super:
{
aPosition.setY(aPosition.getY() + aTextLayouterDevice.getOverlineOffset());
break;
}
case BaselineShift_Percentage:
case BaselineShift_Length:
{
const SvgNumber aNumber(rSvgStyleAttributes.getBaselineShiftNumber());
const double mfBaselineShift(aNumber.solve(*this, length));
aPosition.setY(aPosition.getY() + mfBaselineShift);
break;
}
default: // BaselineShift_Baseline
{
// nothing to do
break;
}
}
// get fill color
const basegfx::BColor aFill(rSvgStyleAttributes.getFill()
? *rSvgStyleAttributes.getFill()
: basegfx::BColor(0.0, 0.0, 0.0));
// prepare TextTransformation
basegfx::B2DHomMatrix aTextTransform;
aTextTransform.scale(fFontWidth, fFontHeight);
aTextTransform.translate(aPosition.getX(), aPosition.getY());
// check TextDecoration and if TextDecoratedPortionPrimitive2D is needed
const TextDecoration aDeco(rSvgStyleAttributes.getTextDecoration());
if(TextDecoration_underline == aDeco
|| TextDecoration_overline == aDeco
|| TextDecoration_line_through == aDeco)
{
// get the fill for decroation as described by SVG. We cannot
// have different stroke colors/definitions for those, though
const SvgStyleAttributes* pDecoDef = rSvgStyleAttributes.getTextDecorationDefiningSvgStyleAttributes();
const basegfx::BColor aDecoColor(pDecoDef && pDecoDef->getFill() ? *pDecoDef->getFill() : aFill);
// create decorated text primitive
pRetval = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
aTextTransform,
getText(),
nIndex,
nLength,
aTextArray,
aFontAttribute,
aLocale,
aFill,
// extra props for decorated
aDecoColor,
aDecoColor,
TextDecoration_overline == aDeco ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE,
TextDecoration_underline == aDeco ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE,
false,
TextDecoration_line_through == aDeco ? drawinglayer::primitive2d::TEXT_STRIKEOUT_SINGLE : drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE,
false,
drawinglayer::primitive2d::TEXT_EMPHASISMARK_NONE,
true,
false,
drawinglayer::primitive2d::TEXT_RELIEF_NONE,
false);
}
else
{
// create text primitive
pRetval = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
aTextTransform,
getText(),
nIndex,
nLength,
aTextArray,
aFontAttribute,
aLocale,
aFill);
}
// advance current TextPosition
rSvgTextPosition.setPosition(rSvgTextPosition.getPosition() + basegfx::B2DVector(fTextWidth, 0.0));
}
return pRetval;
}
void SvgCharacterNode::decomposeTextWithStyle(
drawinglayer::primitive2d::Primitive2DSequence& rTarget,
SvgTextPosition& rSvgTextPosition,
const SvgStyleAttributes& rSvgStyleAttributes) const
{
const drawinglayer::primitive2d::Primitive2DReference xRef(
createSimpleTextPrimitive(
rSvgTextPosition,
rSvgStyleAttributes));
if(xRef.is())
{
if(!rSvgTextPosition.isRotated())
{
drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xRef);
}
else
{
// need to apply rotations to each character as given
const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pCandidate =
dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(xRef.get());
if(pCandidate)
{
const localTextBreakupHelper alocalTextBreakupHelper(*pCandidate, rSvgTextPosition);
const drawinglayer::primitive2d::Primitive2DSequence aResult(
alocalTextBreakupHelper.getResult(drawinglayer::primitive2d::BreakupUnit_character));
if(aResult.hasElements())
{
drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aResult);
}
// also consume for the implied single space
rSvgTextPosition.consumeRotation();
}
else
{
OSL_ENSURE(false, "Used primitive is not a text primitive (!)");
}
}
}
}
void SvgCharacterNode::whiteSpaceHandling()
{
if(XmlSpace_default == getXmlSpace())
{
maText = whiteSpaceHandlingDefault(maText);
}
else
{
maText = whiteSpaceHandlingPreserve(maText);
}
}
void SvgCharacterNode::addGap()
{
maText += rtl::OUString(sal_Unicode(' '));
}
void SvgCharacterNode::concatenate(const rtl::OUString& rText)
{
maText += rText;
}
void SvgCharacterNode::decomposeText(drawinglayer::primitive2d::Primitive2DSequence& rTarget, SvgTextPosition& rSvgTextPosition) const
{
if(getText().getLength())
{
const SvgStyleAttributes* pSvgStyleAttributes = getSvgStyleAttributes();
if(pSvgStyleAttributes)
{
decomposeTextWithStyle(rTarget, rSvgTextPosition, *pSvgStyleAttributes);
}
}
}
} // end of namespace svgreader
} // end of namespace svgio
//////////////////////////////////////////////////////////////////////////////
namespace svgio
{
namespace svgreader
{
SvgTextPosition::SvgTextPosition(
SvgTextPosition* pParent,
const InfoProvider& rInfoProvider,
const SvgTextPositions& rSvgTextPositions)
: mpParent(pParent),
maX(), // computed below
maY(), // computed below
maRotate(solveSvgNumberVector(rSvgTextPositions.getRotate(), rInfoProvider, length)),
mfTextLength(0.0),
maPosition(), // computed below
mnRotationIndex(0),
mbLengthAdjust(rSvgTextPositions.getLengthAdjust()),
mbAbsoluteX(false),
mbAbsoluteY(false)
{
// get TextLength if provided
if(rSvgTextPositions.getTextLength().isSet())
{
mfTextLength = rSvgTextPositions.getTextLength().solve(rInfoProvider, length);
}
// SVG does not really define in which units a ‘rotate’ for Text/TSpan is given,
// but it seems to be degrees. Convert here to radians
if(!maRotate.empty())
{
const double fFactor(F_PI / 180.0);
for(sal_uInt32 a(0); a < maRotate.size(); a++)
{
maRotate[a] *= fFactor;
}
}
// get text positions X
const sal_uInt32 nSizeX(rSvgTextPositions.getX().size());
if(nSizeX)
{
// we have absolute positions, get first one as current text position X
maPosition.setX(rSvgTextPositions.getX()[0].solve(rInfoProvider, xcoordinate));
mbAbsoluteX = true;
if(nSizeX > 1)
{
// fill deltas to maX
maX.reserve(nSizeX);
for(sal_uInt32 a(1); a < nSizeX; a++)
{
maX.push_back(rSvgTextPositions.getX()[a].solve(rInfoProvider, xcoordinate) - maPosition.getX());
}
}
}
else
{
// no absolute position, get from parent
if(pParent)
{
maPosition.setX(pParent->getPosition().getX());
}
const sal_uInt32 nSizeDx(rSvgTextPositions.getDx().size());
if(nSizeDx)
{
// relative positions given, translate position derived from parent
maPosition.setX(maPosition.getX() + rSvgTextPositions.getDx()[0].solve(rInfoProvider, xcoordinate));
if(nSizeDx > 1)
{
// fill deltas to maX
maX.reserve(nSizeDx);
for(sal_uInt32 a(1); a < nSizeDx; a++)
{
maX.push_back(rSvgTextPositions.getDx()[a].solve(rInfoProvider, xcoordinate));
}
}
}
}
// get text positions Y
const sal_uInt32 nSizeY(rSvgTextPositions.getY().size());
if(nSizeY)
{
// we have absolute positions, get first one as current text position Y
maPosition.setY(rSvgTextPositions.getY()[0].solve(rInfoProvider, ycoordinate));
mbAbsoluteX = true;
if(nSizeY > 1)
{
// fill deltas to maY
maY.reserve(nSizeY);
for(sal_uInt32 a(1); a < nSizeY; a++)
{
maY.push_back(rSvgTextPositions.getY()[a].solve(rInfoProvider, ycoordinate) - maPosition.getY());
}
}
}
else
{
// no absolute position, get from parent
if(pParent)
{
maPosition.setY(pParent->getPosition().getY());
}
const sal_uInt32 nSizeDy(rSvgTextPositions.getDy().size());
if(nSizeDy)
{
// relative positions given, translate position derived from parent
maPosition.setY(maPosition.getY() + rSvgTextPositions.getDy()[0].solve(rInfoProvider, ycoordinate));
if(nSizeDy > 1)
{
// fill deltas to maY
maY.reserve(nSizeDy);
for(sal_uInt32 a(1); a < nSizeDy; a++)
{
maY.push_back(rSvgTextPositions.getDy()[a].solve(rInfoProvider, ycoordinate));
}
}
}
}
}
bool SvgTextPosition::isRotated() const
{
if(maRotate.empty())
{
if(getParent())
{
return getParent()->isRotated();
}
else
{
return false;
}
}
else
{
return true;
}
}
double SvgTextPosition::consumeRotation()
{
double fRetval(0.0);
if(maRotate.empty())
{
if(getParent())
{
fRetval = mpParent->consumeRotation();
}
else
{
fRetval = 0.0;
}
}
else
{
const sal_uInt32 nSize(maRotate.size());
if(mnRotationIndex < nSize)
{
fRetval = maRotate[mnRotationIndex++];
}
else
{
fRetval = maRotate[nSize - 1];
}
}
return fRetval;
}
} // end of namespace svgreader
} // end of namespace svgio
//////////////////////////////////////////////////////////////////////////////
// eof