3286 lines
126 KiB
C++
3286 lines
126 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 <algorithm>
|
|
|
|
#include <svgstyleattributes.hxx>
|
|
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
|
|
#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
|
|
#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx>
|
|
#include <svgnode.hxx>
|
|
#include <svgdocument.hxx>
|
|
#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
|
|
#include <svggradientnode.hxx>
|
|
#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
|
|
#include <drawinglayer/primitive2d/PolyPolygonRGBAPrimitive2D.hxx>
|
|
#include <basegfx/vector/b2enums.hxx>
|
|
#include <drawinglayer/processor2d/linegeometryextractor2d.hxx>
|
|
#include <drawinglayer/processor2d/textaspolygonextractor2d.hxx>
|
|
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
|
|
#include <svgclippathnode.hxx>
|
|
#include <svgfilternode.hxx>
|
|
#include <svgmasknode.hxx>
|
|
#include <basegfx/polygon/b2dpolypolygontools.hxx>
|
|
#include <svgmarkernode.hxx>
|
|
#include <svgpatternnode.hxx>
|
|
#include <drawinglayer/primitive2d/patternfillprimitive2d.hxx>
|
|
#include <basegfx/polygon/b2dpolygontools.hxx>
|
|
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
|
|
#include <drawinglayer/primitive2d/pagehierarchyprimitive2d.hxx>
|
|
#include <o3tl/string_view.hxx>
|
|
#include <o3tl/unit_conversion.hxx>
|
|
|
|
const int nStyleDepthLimit = 1024;
|
|
|
|
namespace svgio::svgreader
|
|
{
|
|
static basegfx::B2DLineJoin StrokeLinejoinToB2DLineJoin(StrokeLinejoin aStrokeLinejoin)
|
|
{
|
|
if(StrokeLinejoin::round == aStrokeLinejoin)
|
|
{
|
|
return basegfx::B2DLineJoin::Round;
|
|
}
|
|
else if(StrokeLinejoin::bevel == aStrokeLinejoin)
|
|
{
|
|
return basegfx::B2DLineJoin::Bevel;
|
|
}
|
|
|
|
return basegfx::B2DLineJoin::Miter;
|
|
}
|
|
|
|
static css::drawing::LineCap StrokeLinecapToDrawingLineCap(StrokeLinecap aStrokeLinecap)
|
|
{
|
|
switch(aStrokeLinecap)
|
|
{
|
|
default: /* StrokeLinecap::notset, StrokeLinecap::butt */
|
|
{
|
|
return css::drawing::LineCap_BUTT;
|
|
}
|
|
case StrokeLinecap::round:
|
|
{
|
|
return css::drawing::LineCap_ROUND;
|
|
}
|
|
case StrokeLinecap::square:
|
|
{
|
|
return css::drawing::LineCap_SQUARE;
|
|
}
|
|
}
|
|
}
|
|
|
|
FontStretch getWider(FontStretch aSource)
|
|
{
|
|
switch(aSource)
|
|
{
|
|
case FontStretch::ultra_condensed: aSource = FontStretch::extra_condensed; break;
|
|
case FontStretch::extra_condensed: aSource = FontStretch::condensed; break;
|
|
case FontStretch::condensed: aSource = FontStretch::semi_condensed; break;
|
|
case FontStretch::semi_condensed: aSource = FontStretch::normal; break;
|
|
case FontStretch::normal: aSource = FontStretch::semi_expanded; break;
|
|
case FontStretch::semi_expanded: aSource = FontStretch::expanded; break;
|
|
case FontStretch::expanded: aSource = FontStretch::extra_expanded; break;
|
|
case FontStretch::extra_expanded: aSource = FontStretch::ultra_expanded; break;
|
|
default: break;
|
|
}
|
|
|
|
return aSource;
|
|
}
|
|
|
|
FontStretch getNarrower(FontStretch aSource)
|
|
{
|
|
switch(aSource)
|
|
{
|
|
case FontStretch::extra_condensed: aSource = FontStretch::ultra_condensed; break;
|
|
case FontStretch::condensed: aSource = FontStretch::extra_condensed; break;
|
|
case FontStretch::semi_condensed: aSource = FontStretch::condensed; break;
|
|
case FontStretch::normal: aSource = FontStretch::semi_condensed; break;
|
|
case FontStretch::semi_expanded: aSource = FontStretch::normal; break;
|
|
case FontStretch::expanded: aSource = FontStretch::semi_expanded; break;
|
|
case FontStretch::extra_expanded: aSource = FontStretch::expanded; break;
|
|
case FontStretch::ultra_expanded: aSource = FontStretch::extra_expanded; break;
|
|
default: break;
|
|
}
|
|
|
|
return aSource;
|
|
}
|
|
|
|
FontWeight getBolder(FontWeight aSource)
|
|
{
|
|
switch(aSource)
|
|
{
|
|
case FontWeight::N100: aSource = FontWeight::N200; break;
|
|
case FontWeight::N200: aSource = FontWeight::N300; break;
|
|
case FontWeight::N300: aSource = FontWeight::N400; break;
|
|
case FontWeight::N400: aSource = FontWeight::N500; break;
|
|
case FontWeight::N500: aSource = FontWeight::N600; break;
|
|
case FontWeight::N600: aSource = FontWeight::N700; break;
|
|
case FontWeight::N700: aSource = FontWeight::N800; break;
|
|
case FontWeight::N800: aSource = FontWeight::N900; break;
|
|
default: break;
|
|
}
|
|
|
|
return aSource;
|
|
}
|
|
|
|
FontWeight getLighter(FontWeight aSource)
|
|
{
|
|
switch(aSource)
|
|
{
|
|
case FontWeight::N200: aSource = FontWeight::N100; break;
|
|
case FontWeight::N300: aSource = FontWeight::N200; break;
|
|
case FontWeight::N400: aSource = FontWeight::N300; break;
|
|
case FontWeight::N500: aSource = FontWeight::N400; break;
|
|
case FontWeight::N600: aSource = FontWeight::N500; break;
|
|
case FontWeight::N700: aSource = FontWeight::N600; break;
|
|
case FontWeight::N800: aSource = FontWeight::N700; break;
|
|
case FontWeight::N900: aSource = FontWeight::N800; break;
|
|
default: break;
|
|
}
|
|
|
|
return aSource;
|
|
}
|
|
|
|
::FontWeight getVclFontWeight(FontWeight aSource)
|
|
{
|
|
::FontWeight nRetval(WEIGHT_NORMAL);
|
|
|
|
switch(aSource)
|
|
{
|
|
case FontWeight::N100: nRetval = WEIGHT_ULTRALIGHT; break;
|
|
case FontWeight::N200: nRetval = WEIGHT_LIGHT; break;
|
|
case FontWeight::N300: nRetval = WEIGHT_SEMILIGHT; break;
|
|
case FontWeight::N400: nRetval = WEIGHT_NORMAL; break;
|
|
case FontWeight::N500: nRetval = WEIGHT_MEDIUM; break;
|
|
case FontWeight::N600: nRetval = WEIGHT_SEMIBOLD; break;
|
|
case FontWeight::N700: nRetval = WEIGHT_BOLD; break;
|
|
case FontWeight::N800: nRetval = WEIGHT_ULTRABOLD; break;
|
|
case FontWeight::N900: nRetval = WEIGHT_BLACK; break;
|
|
default: break;
|
|
}
|
|
|
|
return nRetval;
|
|
}
|
|
|
|
void SvgStyleAttributes::readCssStyle(std::u16string_view rCandidate)
|
|
{
|
|
const sal_Int32 nLen(rCandidate.size());
|
|
sal_Int32 nPos(0);
|
|
|
|
while(nPos < nLen)
|
|
{
|
|
// get TokenName
|
|
OUStringBuffer aTokenName;
|
|
skip_char(rCandidate, u' ', nPos, nLen);
|
|
copyString(rCandidate, nPos, aTokenName, nLen);
|
|
|
|
if (aTokenName.isEmpty())
|
|
{
|
|
// if no TokenName advance one by force to avoid death loop, continue
|
|
OSL_ENSURE(false, "Could not interpret on current position, advancing one byte (!)");
|
|
nPos++;
|
|
continue;
|
|
}
|
|
|
|
// get TokenValue
|
|
OUStringBuffer aTokenValue;
|
|
skip_char(rCandidate, u' ', u':', nPos, nLen);
|
|
copyToLimiter(rCandidate, u';', nPos, aTokenValue, nLen);
|
|
skip_char(rCandidate, u' ', u';', nPos, nLen);
|
|
|
|
if (aTokenValue.isEmpty())
|
|
{
|
|
// no value - continue
|
|
continue;
|
|
}
|
|
|
|
// generate OUStrings
|
|
const OUString aOUTokenName(aTokenName.makeStringAndClear());
|
|
OUString aOUTokenValue(aTokenValue.makeStringAndClear());
|
|
|
|
// check for '!important' CssStyle mark, currently not supported
|
|
// but needs to be extracted for correct parsing
|
|
OUString aTokenImportant(u"!important"_ustr);
|
|
const sal_Int32 nIndexTokenImportant(aOUTokenValue.indexOf(aTokenImportant));
|
|
|
|
if(-1 != nIndexTokenImportant)
|
|
{
|
|
// if there currently just remove it and remove spaces to have the value only
|
|
OUString aNewOUTokenValue;
|
|
|
|
if(nIndexTokenImportant > 0)
|
|
{
|
|
// copy content before token
|
|
aNewOUTokenValue += aOUTokenValue.subView(0, nIndexTokenImportant);
|
|
}
|
|
|
|
if(aOUTokenValue.getLength() > nIndexTokenImportant + aTokenImportant.getLength())
|
|
{
|
|
// copy content after token
|
|
aNewOUTokenValue += aOUTokenValue.subView(nIndexTokenImportant + aTokenImportant.getLength());
|
|
}
|
|
|
|
// remove spaces
|
|
aOUTokenValue = aNewOUTokenValue.trim();
|
|
}
|
|
|
|
// valid token-value pair, parse it
|
|
parseStyleAttribute(StrToSVGToken(aOUTokenName, true), aOUTokenValue);
|
|
}
|
|
}
|
|
|
|
const SvgStyleAttributes* SvgStyleAttributes::getCssStyleOrParentStyle() const
|
|
{
|
|
if(const SvgStyleAttributes* pCssStyleParent = getCssStyle())
|
|
{
|
|
return pCssStyleParent;
|
|
}
|
|
|
|
if(mrOwner.supportsParentStyle() && mrOwner.getParent())
|
|
{
|
|
return mrOwner.getParent()->getSvgStyleAttributes();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const SvgMarkerNode* SvgStyleAttributes::getMarkerParentNode() const
|
|
{
|
|
if (SVGToken::Marker == mrOwner.getType())
|
|
return static_cast<const SvgMarkerNode *>(&mrOwner);
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
if (pSvgStyleAttributes && maResolvingParent[32] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[32];
|
|
const SvgMarkerNode* ret = pSvgStyleAttributes->getMarkerParentNode();
|
|
--maResolvingParent[32];
|
|
return ret;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void SvgStyleAttributes::add_text(
|
|
drawinglayer::primitive2d::Primitive2DContainer& rTarget,
|
|
drawinglayer::primitive2d::Primitive2DContainer&& rSource) const
|
|
{
|
|
if(rSource.empty())
|
|
return;
|
|
|
|
// at this point the primitives in rSource are of type TextSimplePortionPrimitive2D
|
|
// or TextDecoratedPortionPrimitive2D and have the Fill Color (pAttributes->getFill())
|
|
// set. When another fill is used and also evtl. stroke is set it gets necessary to
|
|
// dismantle to geometry and add needed primitives
|
|
const basegfx::BColor* pFill = getFill();
|
|
const SvgGradientNode* pFillGradient = getSvgGradientNodeFill();
|
|
const SvgPatternNode* pFillPattern = getSvgPatternNodeFill();
|
|
const basegfx::BColor* pStroke = getStroke();
|
|
const SvgGradientNode* pStrokeGradient = getSvgGradientNodeStroke();
|
|
const SvgPatternNode* pStrokePattern = getSvgPatternNodeStroke();
|
|
basegfx::B2DPolyPolygon aMergedArea;
|
|
|
|
if(pFillGradient || pFillPattern || pStroke || pStrokeGradient || pStrokePattern)
|
|
{
|
|
// text geometry is needed, create
|
|
// use neutral ViewInformation and create LineGeometryExtractor2D
|
|
const drawinglayer::geometry::ViewInformation2D aViewInformation2D;
|
|
drawinglayer::processor2d::TextAsPolygonExtractor2D aExtractor(aViewInformation2D);
|
|
|
|
// process
|
|
aExtractor.process(rSource);
|
|
|
|
// get results
|
|
const drawinglayer::processor2d::TextAsPolygonDataNodeVector& rResult = aExtractor.getTarget();
|
|
const sal_uInt32 nResultCount(rResult.size());
|
|
basegfx::B2DPolyPolygonVector aTextFillVector;
|
|
aTextFillVector.reserve(nResultCount);
|
|
|
|
for(sal_uInt32 a(0); a < nResultCount; a++)
|
|
{
|
|
const drawinglayer::processor2d::TextAsPolygonDataNode& rCandidate = rResult[a];
|
|
|
|
if(rCandidate.getIsFilled())
|
|
{
|
|
aTextFillVector.push_back(rCandidate.getB2DPolyPolygon());
|
|
}
|
|
}
|
|
|
|
if(!aTextFillVector.empty())
|
|
{
|
|
aMergedArea = basegfx::utils::mergeToSinglePolyPolygon(aTextFillVector);
|
|
}
|
|
}
|
|
|
|
const bool bStrokeUsed(pStroke || pStrokeGradient || pStrokePattern);
|
|
|
|
// add fill. Use geometry even for simple color fill when stroke
|
|
// is used, else text rendering and the geometry-based stroke will
|
|
// normally not really match optically due to diverse system text
|
|
// renderers
|
|
if(aMergedArea.count() && (pFillGradient || pFillPattern || bStrokeUsed))
|
|
{
|
|
// create text fill content based on geometry
|
|
add_fill(aMergedArea, rTarget, aMergedArea.getB2DRange());
|
|
}
|
|
else if(pFill)
|
|
{
|
|
// add the already prepared primitives for single color fill
|
|
rTarget.append(std::move(rSource));
|
|
}
|
|
|
|
// add stroke
|
|
if(aMergedArea.count() && bStrokeUsed)
|
|
{
|
|
// create text stroke content
|
|
add_stroke(aMergedArea, rTarget, aMergedArea.getB2DRange());
|
|
}
|
|
}
|
|
|
|
void SvgStyleAttributes::add_fillGradient(
|
|
const basegfx::B2DPolyPolygon& rPath,
|
|
drawinglayer::primitive2d::Primitive2DContainer& rTarget,
|
|
const SvgGradientNode& rFillGradient,
|
|
const basegfx::B2DRange& rGeoRange) const
|
|
{
|
|
// create fill content
|
|
drawinglayer::primitive2d::SvgGradientEntryVector aSvgGradientEntryVector;
|
|
|
|
// get the color stops
|
|
rFillGradient.collectGradientEntries(aSvgGradientEntryVector);
|
|
|
|
if(aSvgGradientEntryVector.empty())
|
|
return;
|
|
|
|
basegfx::B2DHomMatrix aGeoToUnit;
|
|
basegfx::B2DHomMatrix aGradientTransform;
|
|
|
|
if(rFillGradient.getGradientTransform())
|
|
{
|
|
aGradientTransform = *rFillGradient.getGradientTransform();
|
|
}
|
|
|
|
if (SvgUnits::userSpaceOnUse == rFillGradient.getGradientUnits())
|
|
{
|
|
aGeoToUnit.translate(-rGeoRange.getMinX(), -rGeoRange.getMinY());
|
|
aGeoToUnit.scale(1.0 / rGeoRange.getWidth(), 1.0 / rGeoRange.getHeight());
|
|
}
|
|
|
|
if(SVGToken::LinearGradient == rFillGradient.getType())
|
|
{
|
|
basegfx::B2DPoint aStart(0.0, 0.0);
|
|
basegfx::B2DPoint aEnd(1.0, 0.0);
|
|
|
|
if (SvgUnits::userSpaceOnUse == rFillGradient.getGradientUnits())
|
|
{
|
|
// all possible units
|
|
aStart.setX(rFillGradient.getX1().solve(mrOwner, NumberType::xcoordinate));
|
|
aStart.setY(rFillGradient.getY1().solve(mrOwner, NumberType::ycoordinate));
|
|
aEnd.setX(rFillGradient.getX2().solve(mrOwner, NumberType::xcoordinate));
|
|
aEnd.setY(rFillGradient.getY2().solve(mrOwner, NumberType::ycoordinate));
|
|
}
|
|
else
|
|
{
|
|
// fractions or percent relative to object bounds
|
|
const SvgNumber X1(rFillGradient.getX1());
|
|
const SvgNumber Y1(rFillGradient.getY1());
|
|
const SvgNumber X2(rFillGradient.getX2());
|
|
const SvgNumber Y2(rFillGradient.getY2());
|
|
|
|
aStart.setX(SvgUnit::percent == X1.getUnit() ? X1.getNumber() * 0.01 : X1.getNumber());
|
|
aStart.setY(SvgUnit::percent == Y1.getUnit() ? Y1.getNumber() * 0.01 : Y1.getNumber());
|
|
aEnd.setX(SvgUnit::percent == X2.getUnit() ? X2.getNumber() * 0.01 : X2.getNumber());
|
|
aEnd.setY(SvgUnit::percent == Y2.getUnit() ? Y2.getNumber() * 0.01 : Y2.getNumber());
|
|
}
|
|
|
|
if(!aGeoToUnit.isIdentity())
|
|
{
|
|
aStart *= aGeoToUnit;
|
|
aEnd *= aGeoToUnit;
|
|
}
|
|
|
|
rTarget.push_back(
|
|
new drawinglayer::primitive2d::SvgLinearGradientPrimitive2D(
|
|
aGradientTransform,
|
|
rPath,
|
|
std::move(aSvgGradientEntryVector),
|
|
aStart,
|
|
aEnd,
|
|
SvgUnits::userSpaceOnUse != rFillGradient.getGradientUnits(),
|
|
rFillGradient.getSpreadMethod()));
|
|
}
|
|
else
|
|
{
|
|
basegfx::B2DPoint aStart(0.5, 0.5);
|
|
basegfx::B2DPoint aFocal;
|
|
double fRadius(0.5);
|
|
const SvgNumber* pFx = rFillGradient.getFx();
|
|
const SvgNumber* pFy = rFillGradient.getFy();
|
|
const bool bFocal(pFx || pFy);
|
|
|
|
if (SvgUnits::userSpaceOnUse == rFillGradient.getGradientUnits())
|
|
{
|
|
// all possible units
|
|
aStart.setX(rFillGradient.getCx().solve(mrOwner, NumberType::xcoordinate));
|
|
aStart.setY(rFillGradient.getCy().solve(mrOwner, NumberType::ycoordinate));
|
|
fRadius = rFillGradient.getR().solve(mrOwner);
|
|
|
|
if(bFocal)
|
|
{
|
|
aFocal.setX(pFx ? pFx->solve(mrOwner, NumberType::xcoordinate) : aStart.getX());
|
|
aFocal.setY(pFy ? pFy->solve(mrOwner, NumberType::ycoordinate) : aStart.getY());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// fractions or percent relative to object bounds
|
|
const SvgNumber Cx(rFillGradient.getCx());
|
|
const SvgNumber Cy(rFillGradient.getCy());
|
|
const SvgNumber R(rFillGradient.getR());
|
|
|
|
aStart.setX(SvgUnit::percent == Cx.getUnit() ? Cx.getNumber() * 0.01 : Cx.getNumber());
|
|
aStart.setY(SvgUnit::percent == Cy.getUnit() ? Cy.getNumber() * 0.01 : Cy.getNumber());
|
|
fRadius = (SvgUnit::percent == R.getUnit()) ? R.getNumber() * 0.01 : R.getNumber();
|
|
|
|
if(bFocal)
|
|
{
|
|
aFocal.setX(pFx ? (SvgUnit::percent == pFx->getUnit() ? pFx->getNumber() * 0.01 : pFx->getNumber()) : aStart.getX());
|
|
aFocal.setY(pFy ? (SvgUnit::percent == pFy->getUnit() ? pFy->getNumber() * 0.01 : pFy->getNumber()) : aStart.getY());
|
|
}
|
|
}
|
|
|
|
if(!aGeoToUnit.isIdentity())
|
|
{
|
|
aStart *= aGeoToUnit;
|
|
fRadius = (aGeoToUnit * basegfx::B2DVector(fRadius, 0.0)).getLength();
|
|
|
|
if(bFocal)
|
|
{
|
|
aFocal *= aGeoToUnit;
|
|
}
|
|
}
|
|
|
|
rTarget.push_back(
|
|
new drawinglayer::primitive2d::SvgRadialGradientPrimitive2D(
|
|
aGradientTransform,
|
|
rPath,
|
|
std::move(aSvgGradientEntryVector),
|
|
aStart,
|
|
fRadius,
|
|
SvgUnits::userSpaceOnUse != rFillGradient.getGradientUnits(),
|
|
rFillGradient.getSpreadMethod(),
|
|
bFocal ? &aFocal : nullptr));
|
|
}
|
|
}
|
|
|
|
void SvgStyleAttributes::add_fillPatternTransform(
|
|
const basegfx::B2DPolyPolygon& rPath,
|
|
drawinglayer::primitive2d::Primitive2DContainer& rTarget,
|
|
const SvgPatternNode& rFillPattern,
|
|
const basegfx::B2DRange& rGeoRange) const
|
|
{
|
|
// prepare fill polyPolygon with given pattern, check for patternTransform
|
|
if(rFillPattern.getPatternTransform() && !rFillPattern.getPatternTransform()->isIdentity())
|
|
{
|
|
// PatternTransform is active; Handle by filling the inverse transformed
|
|
// path and back-transforming the result
|
|
basegfx::B2DPolyPolygon aPath(rPath);
|
|
basegfx::B2DHomMatrix aInv(*rFillPattern.getPatternTransform());
|
|
drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
|
|
|
|
aInv.invert();
|
|
aPath.transform(aInv);
|
|
add_fillPattern(aPath, aNewTarget, rFillPattern, aPath.getB2DRange());
|
|
|
|
if(!aNewTarget.empty())
|
|
{
|
|
rTarget.push_back(
|
|
new drawinglayer::primitive2d::TransformPrimitive2D(
|
|
*rFillPattern.getPatternTransform(),
|
|
std::move(aNewTarget)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no patternTransform, create fillPattern directly
|
|
add_fillPattern(rPath, rTarget, rFillPattern, rGeoRange);
|
|
}
|
|
}
|
|
|
|
void SvgStyleAttributes::add_fillPattern(
|
|
const basegfx::B2DPolyPolygon& rPath,
|
|
drawinglayer::primitive2d::Primitive2DContainer& rTarget,
|
|
const SvgPatternNode& rFillPattern,
|
|
const basegfx::B2DRange& rGeoRange) const
|
|
{
|
|
// fill polyPolygon with given pattern
|
|
const drawinglayer::primitive2d::Primitive2DContainer& rPrimitives = rFillPattern.getPatternPrimitives();
|
|
|
|
if(rPrimitives.empty())
|
|
return;
|
|
|
|
double fTargetWidth(rGeoRange.getWidth());
|
|
double fTargetHeight(rGeoRange.getHeight());
|
|
|
|
if(fTargetWidth <= 0.0 || fTargetHeight <= 0.0)
|
|
return;
|
|
|
|
// get relative values from pattern
|
|
double fX(0.0);
|
|
double fY(0.0);
|
|
double fW(0.0);
|
|
double fH(0.0);
|
|
|
|
rFillPattern.getValuesRelative(fX, fY, fW, fH, rGeoRange, mrOwner);
|
|
|
|
if(fW <= 0.0 || fH <= 0.0)
|
|
return;
|
|
|
|
// build the reference range relative to the rGeoRange
|
|
const basegfx::B2DRange aReferenceRange(fX, fY, fX + fW, fY + fH);
|
|
|
|
// find out how the content is mapped to the reference range
|
|
basegfx::B2DHomMatrix aMapPrimitivesToUnitRange;
|
|
const basegfx::B2DRange* pViewBox = rFillPattern.getViewBox();
|
|
|
|
if(pViewBox)
|
|
{
|
|
// use viewBox/preserveAspectRatio
|
|
const SvgAspectRatio& rRatio = rFillPattern.getSvgAspectRatio();
|
|
const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
|
|
|
|
if(rRatio.isSet())
|
|
{
|
|
// let mapping be created from SvgAspectRatio
|
|
aMapPrimitivesToUnitRange = rRatio.createMapping(aUnitRange, *pViewBox);
|
|
}
|
|
else
|
|
{
|
|
// choose default mapping
|
|
aMapPrimitivesToUnitRange = SvgAspectRatio::createLinearMapping(aUnitRange, *pViewBox);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// use patternContentUnits
|
|
const SvgUnits aPatternContentUnits(rFillPattern.getPatternContentUnits() ? *rFillPattern.getPatternContentUnits() : SvgUnits::userSpaceOnUse);
|
|
|
|
if (SvgUnits::userSpaceOnUse == aPatternContentUnits)
|
|
{
|
|
// create relative mapping to unit coordinates
|
|
aMapPrimitivesToUnitRange.scale(1.0 / (fW * fTargetWidth), 1.0 / (fH * fTargetHeight));
|
|
}
|
|
else
|
|
{
|
|
aMapPrimitivesToUnitRange.scale(1.0 / fW, 1.0 / fH);
|
|
}
|
|
}
|
|
|
|
// apply aMapPrimitivesToUnitRange to content when used
|
|
drawinglayer::primitive2d::Primitive2DContainer aPrimitives(rPrimitives);
|
|
|
|
if(!aMapPrimitivesToUnitRange.isIdentity())
|
|
{
|
|
const drawinglayer::primitive2d::Primitive2DReference xRef(
|
|
new drawinglayer::primitive2d::TransformPrimitive2D(
|
|
aMapPrimitivesToUnitRange,
|
|
std::move(aPrimitives)));
|
|
|
|
aPrimitives = drawinglayer::primitive2d::Primitive2DContainer { xRef };
|
|
}
|
|
|
|
// embed in PatternFillPrimitive2D
|
|
rTarget.push_back(
|
|
new drawinglayer::primitive2d::PatternFillPrimitive2D(
|
|
rPath,
|
|
std::move(aPrimitives),
|
|
aReferenceRange));
|
|
}
|
|
|
|
void SvgStyleAttributes::add_fill(
|
|
const basegfx::B2DPolyPolygon& rPath,
|
|
drawinglayer::primitive2d::Primitive2DContainer& rTarget,
|
|
const basegfx::B2DRange& rGeoRange) const
|
|
{
|
|
const basegfx::BColor* pFill = nullptr;
|
|
const SvgGradientNode* pFillGradient = nullptr;
|
|
const SvgPatternNode* pFillPattern = nullptr;
|
|
|
|
if (mbUseFillFromContextFill)
|
|
{
|
|
if (const SvgMarkerNode* pMarker = getMarkerParentNode())
|
|
{
|
|
const SvgStyleAttributes* pStyle = pMarker->getContextStyleAttributes();
|
|
pFill = pStyle->getFill();
|
|
pFillGradient = pStyle->getSvgGradientNodeFill();
|
|
pFillPattern = pStyle->getSvgPatternNodeFill();
|
|
}
|
|
}
|
|
else if (mbUseFillFromContextStroke)
|
|
{
|
|
if (const SvgMarkerNode* pMarker = getMarkerParentNode())
|
|
{
|
|
const SvgStyleAttributes* pStyle = pMarker->getContextStyleAttributes();
|
|
pFill = pStyle->getStroke();
|
|
pFillGradient = pStyle->getSvgGradientNodeStroke();
|
|
pFillPattern = pStyle->getSvgPatternNodeStroke();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pFill = getFill();
|
|
pFillGradient = getSvgGradientNodeFill();
|
|
pFillPattern = getSvgPatternNodeFill();
|
|
}
|
|
|
|
if(!(pFill || pFillGradient || pFillPattern))
|
|
return;
|
|
|
|
double fFillOpacity(getFillOpacity().solve(mrOwner));
|
|
|
|
if (fFillOpacity <= 0.0 || basegfx::fTools::equalZero(fFillOpacity))
|
|
return;
|
|
|
|
drawinglayer::primitive2d::Primitive2DContainer aNewFill;
|
|
|
|
if(pFillGradient)
|
|
{
|
|
// create fill content with SVG gradient primitive
|
|
add_fillGradient(rPath, aNewFill, *pFillGradient, rGeoRange);
|
|
}
|
|
else if(pFillPattern)
|
|
{
|
|
// create fill content with SVG pattern primitive
|
|
add_fillPatternTransform(rPath, aNewFill, *pFillPattern, rGeoRange);
|
|
}
|
|
else // if(pFill)
|
|
{
|
|
// create fill content
|
|
if(basegfx::fTools::moreOrEqual(fFillOpacity, 1.0))
|
|
{
|
|
// no transparence
|
|
aNewFill.push_back(
|
|
new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
|
|
rPath, *pFill));
|
|
}
|
|
else
|
|
{
|
|
// transparence
|
|
aNewFill.push_back(
|
|
new drawinglayer::primitive2d::PolyPolygonRGBAPrimitive2D(
|
|
rPath, *pFill, 1.0 - fFillOpacity));
|
|
|
|
// do not embed again below
|
|
fFillOpacity = 1.0;
|
|
}
|
|
}
|
|
|
|
if(aNewFill.empty())
|
|
return;
|
|
|
|
if(basegfx::fTools::less(fFillOpacity, 1.0))
|
|
{
|
|
// embed in UnifiedTransparencePrimitive2D
|
|
rTarget.push_back(
|
|
new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
|
|
std::move(aNewFill),
|
|
1.0 - fFillOpacity));
|
|
}
|
|
else
|
|
{
|
|
// append
|
|
rTarget.append(aNewFill);
|
|
}
|
|
}
|
|
|
|
void SvgStyleAttributes::add_stroke(
|
|
const basegfx::B2DPolyPolygon& rPath,
|
|
drawinglayer::primitive2d::Primitive2DContainer& rTarget,
|
|
const basegfx::B2DRange& rGeoRange) const
|
|
{
|
|
const basegfx::BColor* pStroke = nullptr;
|
|
const SvgGradientNode* pStrokeGradient = nullptr;
|
|
const SvgPatternNode* pStrokePattern = nullptr;
|
|
|
|
if(mbUseStrokeFromContextFill)
|
|
{
|
|
if (const SvgMarkerNode* pMarker = getMarkerParentNode())
|
|
{
|
|
const SvgStyleAttributes* pStyle = pMarker->getContextStyleAttributes();
|
|
pStroke = pStyle->getFill();
|
|
pStrokeGradient = pStyle->getSvgGradientNodeFill();
|
|
pStrokePattern = pStyle->getSvgPatternNodeFill();
|
|
}
|
|
}
|
|
else if(mbUseStrokeFromContextStroke)
|
|
{
|
|
if (const SvgMarkerNode* pMarker = getMarkerParentNode())
|
|
{
|
|
const SvgStyleAttributes* pStyle = pMarker->getContextStyleAttributes();
|
|
pStroke = pStyle->getStroke();
|
|
pStrokeGradient = pStyle->getSvgGradientNodeStroke();
|
|
pStrokePattern = pStyle->getSvgPatternNodeStroke();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pStroke = getStroke();
|
|
pStrokeGradient = getSvgGradientNodeStroke();
|
|
pStrokePattern = getSvgPatternNodeStroke();
|
|
}
|
|
|
|
if(!(pStroke || pStrokeGradient || pStrokePattern))
|
|
return;
|
|
|
|
drawinglayer::primitive2d::Primitive2DContainer aNewStroke;
|
|
const double fStrokeOpacity(getStrokeOpacity().solve(mrOwner));
|
|
|
|
if (fStrokeOpacity <= 0.0 || basegfx::fTools::equalZero(fStrokeOpacity))
|
|
return;
|
|
|
|
// get stroke width; SVG does not use 0.0 == hairline, so 0.0 is no line at all
|
|
const double fStrokeWidth(getStrokeWidth().isSet() ? getStrokeWidth().solve(mrOwner) : 1.0);
|
|
|
|
if (fStrokeWidth <= 0.0 || basegfx::fTools::equalZero(fStrokeWidth))
|
|
return;
|
|
|
|
if (fStrokeWidth > std::numeric_limits<sal_Int32>::max())
|
|
{
|
|
SAL_WARN("svgio", "ignoring ludicrous stroke width: " << fStrokeWidth);
|
|
return;
|
|
}
|
|
|
|
drawinglayer::primitive2d::Primitive2DReference aNewLinePrimitive;
|
|
|
|
// if we have a line with two identical points it is not really a line,
|
|
// but used by SVG sometimes to paint a single dot.In that case, create
|
|
// the geometry for a single dot
|
|
if(1 == rPath.count())
|
|
{
|
|
const basegfx::B2DPolygon& aSingle(rPath.getB2DPolygon(0));
|
|
|
|
if(2 == aSingle.count() && aSingle.getB2DPoint(0).equal(aSingle.getB2DPoint(1)))
|
|
{
|
|
aNewLinePrimitive = new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
|
|
basegfx::B2DPolyPolygon(
|
|
basegfx::utils::createPolygonFromCircle(
|
|
aSingle.getB2DPoint(0),
|
|
fStrokeWidth * (1.44 * 0.5))),
|
|
pStroke ? *pStroke : basegfx::BColor(0.0, 0.0, 0.0));
|
|
}
|
|
}
|
|
|
|
if(!aNewLinePrimitive.is())
|
|
{
|
|
// get LineJoin, LineCap and stroke array
|
|
const basegfx::B2DLineJoin aB2DLineJoin(StrokeLinejoinToB2DLineJoin(getStrokeLinejoin()));
|
|
const css::drawing::LineCap aLineCap(StrokeLinecapToDrawingLineCap(getStrokeLinecap()));
|
|
::std::vector< double > aDashArray;
|
|
|
|
if(!getStrokeDasharray().empty())
|
|
{
|
|
aDashArray = solveSvgNumberVector(getStrokeDasharray(), mrOwner);
|
|
}
|
|
|
|
// convert svg:stroke-miterlimit to LineAttrute:mfMiterMinimumAngle
|
|
// The default needs to be set explicitly, because svg default <> Draw default
|
|
double fMiterMinimumAngle;
|
|
if (getStrokeMiterLimit().isSet())
|
|
{
|
|
fMiterMinimumAngle = 2.0 * asin(1.0/getStrokeMiterLimit().getNumber());
|
|
}
|
|
else
|
|
{
|
|
fMiterMinimumAngle = 2.0 * asin(0.25); // 1.0/default 4.0
|
|
}
|
|
|
|
// todo: Handle getStrokeDashOffset()
|
|
|
|
// prepare line attribute
|
|
const drawinglayer::attribute::LineAttribute aLineAttribute(
|
|
pStroke ? *pStroke : basegfx::BColor(0.0, 0.0, 0.0),
|
|
fStrokeWidth,
|
|
aB2DLineJoin,
|
|
aLineCap,
|
|
fMiterMinimumAngle);
|
|
|
|
if(aDashArray.empty())
|
|
{
|
|
aNewLinePrimitive = new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
|
|
rPath,
|
|
aLineAttribute);
|
|
}
|
|
else
|
|
{
|
|
drawinglayer::attribute::StrokeAttribute aStrokeAttribute(std::move(aDashArray));
|
|
|
|
aNewLinePrimitive = new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
|
|
rPath,
|
|
aLineAttribute,
|
|
std::move(aStrokeAttribute));
|
|
}
|
|
}
|
|
|
|
if(pStrokeGradient || pStrokePattern)
|
|
{
|
|
// put primitive into Primitive2DReference and Primitive2DSequence
|
|
const drawinglayer::primitive2d::Primitive2DContainer aSeq { aNewLinePrimitive };
|
|
|
|
// use neutral ViewInformation and create LineGeometryExtractor2D
|
|
const drawinglayer::geometry::ViewInformation2D aViewInformation2D;
|
|
drawinglayer::processor2d::LineGeometryExtractor2D aExtractor(aViewInformation2D);
|
|
|
|
// process
|
|
aExtractor.process(aSeq);
|
|
|
|
// check for fill rsults
|
|
const basegfx::B2DPolyPolygonVector& rLineFillVector(aExtractor.getExtractedLineFills());
|
|
|
|
if(!rLineFillVector.empty())
|
|
{
|
|
const basegfx::B2DPolyPolygon aMergedArea(
|
|
basegfx::utils::mergeToSinglePolyPolygon(
|
|
rLineFillVector));
|
|
|
|
if(aMergedArea.count())
|
|
{
|
|
if(pStrokeGradient)
|
|
{
|
|
// create fill content with SVG gradient primitive. Use original GeoRange,
|
|
// e.g. from circle without LineWidth
|
|
add_fillGradient(aMergedArea, aNewStroke, *pStrokeGradient, rGeoRange);
|
|
}
|
|
else // if(pStrokePattern)
|
|
{
|
|
// create fill content with SVG pattern primitive. Use GeoRange
|
|
// from the expanded data, e.g. circle with extended geo by half linewidth
|
|
add_fillPatternTransform(aMergedArea, aNewStroke, *pStrokePattern, aMergedArea.getB2DRange());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // if(pStroke)
|
|
{
|
|
aNewStroke.push_back(aNewLinePrimitive);
|
|
}
|
|
|
|
if(aNewStroke.empty())
|
|
return;
|
|
|
|
if(basegfx::fTools::less(fStrokeOpacity, 1.0))
|
|
{
|
|
// embed in UnifiedTransparencePrimitive2D
|
|
rTarget.push_back(
|
|
new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
|
|
std::move(aNewStroke),
|
|
1.0 - fStrokeOpacity));
|
|
}
|
|
else
|
|
{
|
|
// append
|
|
rTarget.append(aNewStroke);
|
|
}
|
|
}
|
|
|
|
bool SvgStyleAttributes::prepare_singleMarker(
|
|
drawinglayer::primitive2d::Primitive2DContainer& rMarkerPrimitives,
|
|
basegfx::B2DHomMatrix& rMarkerTransform,
|
|
basegfx::B2DRange& rClipRange,
|
|
const SvgMarkerNode& rMarker) const
|
|
{
|
|
// reset return values
|
|
rMarkerTransform.identity();
|
|
rClipRange.reset();
|
|
|
|
// Set the current style attributes to the marker before calling getMarkerPrimitives,
|
|
// which calls decomposeSvgNode to decompose the children of the marker.
|
|
// If any children uses 'context-fill' or 'context-stroke',
|
|
// then these style attributes will be used in add_fill or add_stroke
|
|
const_cast<SvgMarkerNode&>(rMarker).setContextStyleAttributes(this);
|
|
|
|
// get marker primitive representation
|
|
rMarkerPrimitives = rMarker.getMarkerPrimitives();
|
|
|
|
if(!rMarkerPrimitives.empty())
|
|
{
|
|
basegfx::B2DRange aPrimitiveRange(0.0, 0.0, 1.0, 1.0);
|
|
const basegfx::B2DRange* pViewBox = rMarker.getViewBox();
|
|
|
|
if(pViewBox)
|
|
{
|
|
aPrimitiveRange = *pViewBox;
|
|
}
|
|
|
|
if(aPrimitiveRange.getWidth() > 0.0 && aPrimitiveRange.getHeight() > 0.0)
|
|
{
|
|
double fTargetWidth(rMarker.getMarkerWidth().isSet() ? rMarker.getMarkerWidth().solve(mrOwner, NumberType::xcoordinate) : 3.0);
|
|
double fTargetHeight(rMarker.getMarkerHeight().isSet() ? rMarker.getMarkerHeight().solve(mrOwner, NumberType::xcoordinate) : 3.0);
|
|
const bool bStrokeWidth(SvgMarkerNode::MarkerUnits::strokeWidth == rMarker.getMarkerUnits());
|
|
const double fStrokeWidth(getStrokeWidth().isSet() ? getStrokeWidth().solve(mrOwner) : 1.0);
|
|
|
|
if(bStrokeWidth)
|
|
{
|
|
// relative to strokeWidth
|
|
fTargetWidth *= fStrokeWidth;
|
|
fTargetHeight *= fStrokeWidth;
|
|
}
|
|
|
|
if(fTargetWidth > 0.0 && fTargetHeight > 0.0)
|
|
{
|
|
// create mapping
|
|
const basegfx::B2DRange aTargetRange(0.0, 0.0, fTargetWidth, fTargetHeight);
|
|
const SvgAspectRatio& rRatio = rMarker.getSvgAspectRatio();
|
|
|
|
|
|
if(rRatio.isSet() && Overflow::visible != rMarker.getSvgStyleAttributes()->getOverflow())
|
|
{
|
|
// let mapping be created from SvgAspectRatio
|
|
rMarkerTransform = rRatio.createMapping(aTargetRange, aPrimitiveRange);
|
|
|
|
if(rRatio.isMeetOrSlice())
|
|
{
|
|
// need to clip
|
|
rClipRange = aPrimitiveRange;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!pViewBox)
|
|
{
|
|
if(bStrokeWidth)
|
|
{
|
|
// adapt to strokewidth if needed
|
|
rMarkerTransform.scale(fStrokeWidth, fStrokeWidth);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// choose default mapping
|
|
rMarkerTransform = SvgAspectRatio::createLinearMapping(aTargetRange, aPrimitiveRange);
|
|
}
|
|
}
|
|
|
|
// get and apply reference point. Initially it's in marker local coordinate system
|
|
basegfx::B2DPoint aRefPoint(
|
|
rMarker.getRefX().isSet() ? rMarker.getRefX().solve(mrOwner, NumberType::xcoordinate) : 0.0,
|
|
rMarker.getRefY().isSet() ? rMarker.getRefY().solve(mrOwner, NumberType::ycoordinate) : 0.0);
|
|
|
|
// apply MarkerTransform to have it in mapped coordinates
|
|
aRefPoint *= rMarkerTransform;
|
|
|
|
// apply by moving RepPoint to (0.0)
|
|
rMarkerTransform.translate(-aRefPoint.getX(), -aRefPoint.getY());
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void SvgStyleAttributes::add_markers(
|
|
const basegfx::B2DPolyPolygon& rPath,
|
|
drawinglayer::primitive2d::Primitive2DContainer& rTarget,
|
|
const basegfx::utils::PointIndexSet* pHelpPointIndices) const
|
|
{
|
|
// try to access linked markers
|
|
const SvgMarkerNode* pStart = accessMarkerStartXLink();
|
|
const SvgMarkerNode* pMid = accessMarkerMidXLink();
|
|
const SvgMarkerNode* pEnd = accessMarkerEndXLink();
|
|
|
|
if(!(pStart || pMid || pEnd))
|
|
return;
|
|
|
|
const sal_uInt32 nSubPathCount(rPath.count());
|
|
|
|
if(!nSubPathCount)
|
|
return;
|
|
|
|
// remember prepared marker; pStart, pMid and pEnd may all be equal when
|
|
// only 'marker' was used instead of 'marker-start', 'marker-mid' or 'marker-end',
|
|
// see 'case SVGToken::Marker' in this file; thus in this case only one common
|
|
// marker in primitive form will be prepared
|
|
const SvgMarkerNode* pPrepared = nullptr;
|
|
|
|
// values for the prepared marker, results of prepare_singleMarker
|
|
drawinglayer::primitive2d::Primitive2DContainer aPreparedMarkerPrimitives;
|
|
basegfx::B2DHomMatrix aPreparedMarkerTransform;
|
|
basegfx::B2DRange aPreparedMarkerClipRange;
|
|
|
|
for (sal_uInt32 a(0); a < nSubPathCount; a++)
|
|
{
|
|
// iterate over sub-paths
|
|
const basegfx::B2DPolygon& aSubPolygonPath(rPath.getB2DPolygon(a));
|
|
const sal_uInt32 nSubPolygonPointCount(aSubPolygonPath.count());
|
|
const bool bSubPolygonPathIsClosed(aSubPolygonPath.isClosed());
|
|
|
|
if(nSubPolygonPointCount)
|
|
{
|
|
// for each sub-path, create one marker per point (when closed, two markers
|
|
// need to pe created for the 1st point)
|
|
const sal_uInt32 nTargetMarkerCount(bSubPolygonPathIsClosed ? nSubPolygonPointCount + 1 : nSubPolygonPointCount);
|
|
|
|
for (sal_uInt32 b(0); b < nTargetMarkerCount; b++)
|
|
{
|
|
const bool bIsFirstMarker(!a && !b);
|
|
const bool bIsLastMarker(nSubPathCount - 1 == a && nTargetMarkerCount - 1 == b);
|
|
const SvgMarkerNode* pNeeded = nullptr;
|
|
|
|
if(bIsFirstMarker)
|
|
{
|
|
// 1st point in 1st sub-polygon, use pStart
|
|
pNeeded = pStart;
|
|
}
|
|
else if(bIsLastMarker)
|
|
{
|
|
// last point in last sub-polygon, use pEnd
|
|
pNeeded = pEnd;
|
|
}
|
|
else
|
|
{
|
|
// anything in-between, use pMid
|
|
pNeeded = pMid;
|
|
}
|
|
|
|
if(pHelpPointIndices && !pHelpPointIndices->empty())
|
|
{
|
|
const basegfx::utils::PointIndexSet::const_iterator aFound(
|
|
pHelpPointIndices->find(basegfx::utils::PointIndex(a, b)));
|
|
|
|
if(aFound != pHelpPointIndices->end())
|
|
{
|
|
// this point is a pure helper point; do not create a marker for it
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if(!pNeeded)
|
|
{
|
|
// no marker needs to be created for this point
|
|
continue;
|
|
}
|
|
|
|
if(pPrepared != pNeeded)
|
|
{
|
|
// if needed marker is not yet prepared, do it now
|
|
if(prepare_singleMarker(aPreparedMarkerPrimitives, aPreparedMarkerTransform, aPreparedMarkerClipRange, *pNeeded))
|
|
{
|
|
pPrepared = pNeeded;
|
|
}
|
|
else
|
|
{
|
|
// error: could not prepare given marker
|
|
OSL_ENSURE(false, "OOps, could not prepare given marker as primitives (!)");
|
|
pPrepared = nullptr;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// prepare complete transform
|
|
basegfx::B2DHomMatrix aCombinedTransform(aPreparedMarkerTransform);
|
|
|
|
// get rotation
|
|
if(pPrepared->getMarkerOrient() == SvgMarkerNode::MarkerOrient::auto_start ||
|
|
pPrepared->getMarkerOrient() == SvgMarkerNode::MarkerOrient::auto_start_reverse)
|
|
{
|
|
const sal_uInt32 nPointIndex(b % nSubPolygonPointCount);
|
|
|
|
// get entering and leaving tangents; this will search backward/forward
|
|
// in the polygon to find tangents unequal to zero, skipping empty edges
|
|
// see basegfx descriptions)
|
|
// Hint: Mozilla, Inkscape and others use only leaving tangent for start marker
|
|
// and entering tangent for end marker. To achieve this (if wanted) it is possible
|
|
// to make the fetch of aEntering/aLeaving dependent on bIsFirstMarker/bIsLastMarker.
|
|
// This is not done here, see comment 14 in task #1232379#
|
|
// or http://www.w3.org/TR/SVG/painting.html#OrientAttribute
|
|
basegfx::B2DVector aEntering(
|
|
basegfx::utils::getTangentEnteringPoint(
|
|
aSubPolygonPath,
|
|
nPointIndex));
|
|
basegfx::B2DVector aLeaving(
|
|
basegfx::utils::getTangentLeavingPoint(
|
|
aSubPolygonPath,
|
|
nPointIndex));
|
|
const bool bEntering(!aEntering.equalZero());
|
|
const bool bLeaving(!aLeaving.equalZero());
|
|
|
|
if(bEntering || bLeaving)
|
|
{
|
|
basegfx::B2DVector aSum(0.0, 0.0);
|
|
|
|
if(bEntering)
|
|
{
|
|
if(bIsFirstMarker && pPrepared->getMarkerOrient() == SvgMarkerNode::MarkerOrient::auto_start_reverse)
|
|
aSum -= aEntering.normalize();
|
|
else
|
|
aSum += aEntering.normalize();
|
|
}
|
|
|
|
if(bLeaving)
|
|
{
|
|
if(bIsFirstMarker && pPrepared->getMarkerOrient() == SvgMarkerNode::MarkerOrient::auto_start_reverse)
|
|
aSum -= aLeaving.normalize();
|
|
else
|
|
aSum += aLeaving.normalize();
|
|
}
|
|
|
|
if(!aSum.equalZero())
|
|
{
|
|
const double fAngle(atan2(aSum.getY(), aSum.getX()));
|
|
|
|
// apply rotation
|
|
aCombinedTransform.rotate(fAngle);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// apply rotation
|
|
aCombinedTransform.rotate(pPrepared->getAngle());
|
|
}
|
|
|
|
// get and apply target position
|
|
const basegfx::B2DPoint aPoint(aSubPolygonPath.getB2DPoint(b % nSubPolygonPointCount));
|
|
|
|
aCombinedTransform.translate(aPoint.getX(), aPoint.getY());
|
|
|
|
// prepare marker
|
|
drawinglayer::primitive2d::Primitive2DReference xMarker(
|
|
new drawinglayer::primitive2d::TransformPrimitive2D(
|
|
aCombinedTransform,
|
|
drawinglayer::primitive2d::Primitive2DContainer(aPreparedMarkerPrimitives)));
|
|
|
|
if(!aPreparedMarkerClipRange.isEmpty())
|
|
{
|
|
// marker needs to be clipped, it's bigger as the mapping
|
|
basegfx::B2DPolyPolygon aClipPolygon(basegfx::utils::createPolygonFromRect(aPreparedMarkerClipRange));
|
|
|
|
aClipPolygon.transform(aCombinedTransform);
|
|
xMarker = new drawinglayer::primitive2d::MaskPrimitive2D(
|
|
std::move(aClipPolygon),
|
|
drawinglayer::primitive2d::Primitive2DContainer { xMarker });
|
|
}
|
|
|
|
// add marker
|
|
rTarget.push_back(xMarker);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SvgStyleAttributes::add_path(
|
|
const basegfx::B2DPolyPolygon& rPath,
|
|
drawinglayer::primitive2d::Primitive2DContainer& rTarget,
|
|
const basegfx::utils::PointIndexSet* pHelpPointIndices) const
|
|
{
|
|
if(!rPath.count())
|
|
{
|
|
// no geometry at all
|
|
return;
|
|
}
|
|
|
|
const basegfx::B2DRange aGeoRange(rPath.getB2DRange());
|
|
|
|
if(aGeoRange.isEmpty())
|
|
{
|
|
// no geometry range
|
|
return;
|
|
}
|
|
|
|
const double fOpacity(getOpacity().solve(mrOwner));
|
|
|
|
if(basegfx::fTools::equalZero(fOpacity))
|
|
{
|
|
// not visible
|
|
return;
|
|
}
|
|
|
|
// check if it's a line
|
|
const bool bNoWidth(basegfx::fTools::equalZero(aGeoRange.getWidth()));
|
|
const bool bNoHeight(basegfx::fTools::equalZero(aGeoRange.getHeight()));
|
|
const bool bIsTwoPointLine(1 == rPath.count()
|
|
&& !rPath.areControlPointsUsed()
|
|
&& 2 == rPath.getB2DPolygon(0).count());
|
|
const bool bIsLine(bIsTwoPointLine || bNoWidth || bNoHeight);
|
|
|
|
if(!bIsLine)
|
|
{
|
|
// create fill
|
|
basegfx::B2DPolyPolygon aPath(rPath);
|
|
|
|
if(SVGToken::Path == mrOwner.getType() || SVGToken::Polygon == mrOwner.getType())
|
|
{
|
|
if(FillRule::evenodd != getClipRule() && FillRule::evenodd != getFillRule())
|
|
{
|
|
if(getFill() || getSvgGradientNodeFill() || getSvgPatternNodeFill())
|
|
{
|
|
// nonzero is wanted, solve geometrically (see description on basegfx)
|
|
// basegfx::utils::createNonzeroConform() is expensive for huge paths
|
|
// and is only needed if path will be filled later on
|
|
aPath = basegfx::utils::createNonzeroConform(aPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
add_fill(aPath, rTarget, aGeoRange);
|
|
}
|
|
|
|
// create stroke
|
|
add_stroke(rPath, rTarget, aGeoRange);
|
|
|
|
// Svg supports markers for path, polygon, polyline and line
|
|
if(SVGToken::Path == mrOwner.getType() || // path
|
|
SVGToken::Polygon == mrOwner.getType() || // polygon
|
|
SVGToken::Polyline == mrOwner.getType() || // polyline
|
|
SVGToken::Line == mrOwner.getType() || // line
|
|
SVGToken::Style == mrOwner.getType()) // tdf#150323
|
|
{
|
|
// try to add markers
|
|
add_markers(rPath, rTarget, pHelpPointIndices);
|
|
}
|
|
}
|
|
|
|
void SvgStyleAttributes::add_postProcess(
|
|
drawinglayer::primitive2d::Primitive2DContainer& rTarget,
|
|
drawinglayer::primitive2d::Primitive2DContainer&& rSource,
|
|
const std::optional<basegfx::B2DHomMatrix>& pTransform) const
|
|
{
|
|
// default is 1
|
|
double fOpacity(1.0);
|
|
|
|
if(maOpacity.isSet())
|
|
{
|
|
fOpacity = maOpacity.solve(mrOwner);
|
|
}
|
|
else
|
|
{
|
|
// if opacity is not set, check the css style
|
|
if (const SvgStyleAttributes* pSvgStyleAttributes = getCssStyle())
|
|
{
|
|
if (pSvgStyleAttributes->maOpacity.isSet())
|
|
{
|
|
fOpacity = pSvgStyleAttributes->maOpacity.solve(mrOwner);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(basegfx::fTools::equalZero(fOpacity))
|
|
{
|
|
return;
|
|
}
|
|
|
|
drawinglayer::primitive2d::Primitive2DContainer aSource(std::move(rSource));
|
|
|
|
if(basegfx::fTools::less(fOpacity, 1.0))
|
|
{
|
|
// embed in UnifiedTransparencePrimitive2D
|
|
const drawinglayer::primitive2d::Primitive2DReference xRef(
|
|
new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
|
|
std::move(aSource),
|
|
1.0 - fOpacity));
|
|
|
|
aSource = drawinglayer::primitive2d::Primitive2DContainer { xRef };
|
|
}
|
|
|
|
if(pTransform)
|
|
{
|
|
// create embedding group element with transformation. This applies the given
|
|
// transformation to the graphical content, but *not* to mask and/or clip (as needed)
|
|
const drawinglayer::primitive2d::Primitive2DReference xRef(
|
|
new drawinglayer::primitive2d::TransformPrimitive2D(
|
|
*pTransform,
|
|
std::move(aSource)));
|
|
|
|
aSource = drawinglayer::primitive2d::Primitive2DContainer { xRef };
|
|
}
|
|
|
|
const SvgClipPathNode* pClip = accessClipPathXLink();
|
|
while(pClip)
|
|
{
|
|
// #i124852# transform may be needed when SvgUnits::userSpaceOnUse
|
|
pClip->apply(aSource, pTransform);
|
|
pClip = pClip->getSvgStyleAttributes()->accessClipPathXLink();
|
|
}
|
|
|
|
if(!aSource.empty()) // test again, applied clipPath may have lead to empty geometry
|
|
{
|
|
const SvgFilterNode* pFilter = accessFilterXLink();
|
|
if(pFilter)
|
|
{
|
|
pFilter->apply(aSource, nullptr);
|
|
}
|
|
}
|
|
|
|
if(!aSource.empty()) // test again, applied filter may have lead to empty geometry
|
|
{
|
|
|
|
const SvgMaskNode* pMask = accessMaskXLink();
|
|
if(pMask)
|
|
{
|
|
// #i124852# transform may be needed when SvgUnits::userSpaceOnUse
|
|
pMask->apply(aSource, pTransform);
|
|
}
|
|
}
|
|
|
|
// This is part of the SVG import of self-written SVGs from
|
|
// Draw/Impress containing multiple Slides/Pages. To be able
|
|
// to later 'break' these to multiple Pages if wanted, embed
|
|
// each Page-Content in an identifiable Primitive Grouping
|
|
// Object.
|
|
// This is the case when the current Node is a GroupNode, has
|
|
// class="Page" set, has a parent that also is a GroupNode
|
|
// at which class="Slide" is set.
|
|
// Multiple Slides/Pages are possible for Draw and Impress.
|
|
if(SVGToken::G == mrOwner.getType() && mrOwner.getClass())
|
|
{
|
|
const OUString aOwnerClass(*mrOwner.getClass());
|
|
|
|
if("Page" == aOwnerClass)
|
|
{
|
|
const SvgNode* pParent(mrOwner.getParent());
|
|
|
|
if(nullptr != pParent && SVGToken::G == pParent->getType() && pParent->getClass())
|
|
{
|
|
const OUString aParentClass(*pParent->getClass());
|
|
|
|
if("Slide" == aParentClass)
|
|
{
|
|
// embed to grouping primitive to identify the
|
|
// Slide/Page information
|
|
const drawinglayer::primitive2d::Primitive2DReference xRef(
|
|
new drawinglayer::primitive2d::PageHierarchyPrimitive2D(
|
|
std::move(aSource)));
|
|
|
|
aSource = drawinglayer::primitive2d::Primitive2DContainer { xRef };
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!aSource.empty()) // test again, applied mask may have lead to empty geometry
|
|
{
|
|
// append to current target
|
|
rTarget.append(aSource);
|
|
}
|
|
}
|
|
|
|
SvgStyleAttributes::SvgStyleAttributes(SvgNode& rOwner)
|
|
: mrOwner(rOwner),
|
|
mpCssStyle(nullptr),
|
|
maStopColor(basegfx::BColor(0.0, 0.0, 0.0), true),
|
|
maStrokeLinecap(StrokeLinecap::notset),
|
|
maStrokeLinejoin(StrokeLinejoin::notset),
|
|
maFontSize(),
|
|
maFontStretch(FontStretch::notset),
|
|
maFontStyle(FontStyle::notset),
|
|
maFontWeight(FontWeight::notset),
|
|
maTextAlign(TextAlign::notset),
|
|
maTextDecoration(TextDecoration::notset),
|
|
maTextAnchor(TextAnchor::notset),
|
|
maOverflow(Overflow::notset),
|
|
maVisibility(Visibility::notset),
|
|
maFillRule(FillRule::notset),
|
|
maClipRule(FillRule::notset),
|
|
maBaselineShift(BaselineShift::Baseline),
|
|
maBaselineShiftNumber(0),
|
|
maDominantBaseline(DominantBaseline::Auto),
|
|
maResolvingParent(34, 0),
|
|
mbStrokeDasharraySet(false),
|
|
mbUseFillFromContextFill(false),
|
|
mbUseFillFromContextStroke(false),
|
|
mbUseStrokeFromContextFill(false),
|
|
mbUseStrokeFromContextStroke(false)
|
|
{
|
|
}
|
|
|
|
SvgStyleAttributes::~SvgStyleAttributes()
|
|
{
|
|
}
|
|
|
|
void SvgStyleAttributes::parseStyleAttribute(
|
|
SVGToken aSVGToken,
|
|
const OUString& aContent)
|
|
{
|
|
switch(aSVGToken)
|
|
{
|
|
case SVGToken::Fill:
|
|
{
|
|
SvgPaint aSvgPaint;
|
|
OUString aURL;
|
|
SvgNumber aOpacity;
|
|
|
|
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"context-fill"))
|
|
{
|
|
mbUseFillFromContextFill = true;
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"context-stroke"))
|
|
{
|
|
mbUseFillFromContextStroke = true;
|
|
}
|
|
else if(readSvgPaint(aContent, aSvgPaint, aURL, aOpacity))
|
|
{
|
|
setFill(aSvgPaint);
|
|
if(aOpacity.isSet())
|
|
{
|
|
setOpacity(SvgNumber(std::clamp(aOpacity.getNumber(), 0.0, 1.0)));
|
|
}
|
|
}
|
|
else if(!aURL.isEmpty())
|
|
{
|
|
maNodeFillURL = aURL;
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::FillOpacity:
|
|
{
|
|
SvgNumber aNum;
|
|
|
|
if(readSingleNumber(aContent, aNum))
|
|
{
|
|
maFillOpacity = SvgNumber(std::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet());
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::FillRule:
|
|
{
|
|
if(!aContent.isEmpty())
|
|
{
|
|
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrNonzero))
|
|
{
|
|
maFillRule = FillRule::nonzero;
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrEvenOdd))
|
|
{
|
|
maFillRule = FillRule::evenodd;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::Stroke:
|
|
{
|
|
SvgPaint aSvgPaint;
|
|
OUString aURL;
|
|
SvgNumber aOpacity;
|
|
|
|
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"context-stroke"))
|
|
{
|
|
mbUseStrokeFromContextStroke = true;
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"context-fill"))
|
|
{
|
|
mbUseStrokeFromContextFill = true;
|
|
}
|
|
else if(readSvgPaint(aContent, aSvgPaint, aURL, aOpacity))
|
|
{
|
|
maStroke = aSvgPaint;
|
|
if(aOpacity.isSet())
|
|
{
|
|
setOpacity(SvgNumber(std::clamp(aOpacity.getNumber(), 0.0, 1.0)));
|
|
}
|
|
}
|
|
else if(!aURL.isEmpty())
|
|
{
|
|
maNodeStrokeURL = aURL;
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::StrokeDasharray:
|
|
{
|
|
if(!aContent.isEmpty())
|
|
{
|
|
SvgNumberVector aVector;
|
|
|
|
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"none"))
|
|
{
|
|
// #121221# The special value 'none' needs to be handled
|
|
// in the sense that *when* it is set, the parent shall not
|
|
// be used. Before this was only dependent on the array being
|
|
// empty
|
|
mbStrokeDasharraySet = true;
|
|
}
|
|
else if(readSvgNumberVector(aContent, aVector))
|
|
{
|
|
maStrokeDasharray = std::move(aVector);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::StrokeDashoffset:
|
|
{
|
|
SvgNumber aNum;
|
|
|
|
if(readSingleNumber(aContent, aNum))
|
|
{
|
|
if(aNum.isPositive())
|
|
{
|
|
maStrokeDashOffset = aNum;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::StrokeLinecap:
|
|
{
|
|
if(!aContent.isEmpty())
|
|
{
|
|
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"butt"))
|
|
{
|
|
setStrokeLinecap(StrokeLinecap::butt);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"round"))
|
|
{
|
|
setStrokeLinecap(StrokeLinecap::round);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"square"))
|
|
{
|
|
setStrokeLinecap(StrokeLinecap::square);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::StrokeLinejoin:
|
|
{
|
|
if(!aContent.isEmpty())
|
|
{
|
|
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"miter"))
|
|
{
|
|
setStrokeLinejoin(StrokeLinejoin::miter);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"round"))
|
|
{
|
|
setStrokeLinejoin(StrokeLinejoin::round);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"bevel"))
|
|
{
|
|
setStrokeLinejoin(StrokeLinejoin::bevel);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::StrokeMiterlimit:
|
|
{
|
|
SvgNumber aNum;
|
|
|
|
if(readSingleNumber(aContent, aNum))
|
|
{
|
|
if(basegfx::fTools::moreOrEqual(aNum.getNumber(), 1.0))
|
|
{ //readSingleNumber sets SvgUnit::px as default, if unit is missing. Correct it here.
|
|
maStrokeMiterLimit = SvgNumber(aNum.getNumber(), SvgUnit::none);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::StrokeOpacity:
|
|
{
|
|
|
|
SvgNumber aNum;
|
|
|
|
if(readSingleNumber(aContent, aNum))
|
|
{
|
|
maStrokeOpacity = SvgNumber(std::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet());
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::StrokeWidth:
|
|
{
|
|
SvgNumber aNum;
|
|
|
|
if(readSingleNumber(aContent, aNum))
|
|
{
|
|
if(aNum.isPositive())
|
|
{
|
|
maStrokeWidth = aNum;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::StopColor:
|
|
{
|
|
SvgPaint aSvgPaint;
|
|
OUString aURL;
|
|
SvgNumber aOpacity;
|
|
|
|
if(readSvgPaint(aContent, aSvgPaint, aURL, aOpacity))
|
|
{
|
|
maStopColor = aSvgPaint;
|
|
if(aOpacity.isSet())
|
|
{
|
|
setOpacity(SvgNumber(std::clamp(aOpacity.getNumber(), 0.0, 1.0)));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::StopOpacity:
|
|
{
|
|
SvgNumber aNum;
|
|
|
|
if(readSingleNumber(aContent, aNum))
|
|
{
|
|
if(aNum.isPositive())
|
|
{
|
|
maStopOpacity = aNum;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::Font:
|
|
{
|
|
break;
|
|
}
|
|
case SVGToken::FontFamily:
|
|
{
|
|
SvgStringVector aSvgStringVector;
|
|
|
|
if(readSvgStringVector(aContent, aSvgStringVector, ','))
|
|
{
|
|
maFontFamily = std::move(aSvgStringVector);
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::FontSize:
|
|
{
|
|
if(!aContent.isEmpty())
|
|
{
|
|
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"xx-small"))
|
|
{
|
|
setFontSize(FontSize::xx_small);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"x-small"))
|
|
{
|
|
setFontSize(FontSize::x_small);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"small"))
|
|
{
|
|
setFontSize(FontSize::small);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"smaller"))
|
|
{
|
|
setFontSize(FontSize::smaller);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"medium"))
|
|
{
|
|
setFontSize(FontSize::medium);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"larger"))
|
|
{
|
|
setFontSize(FontSize::larger);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"large"))
|
|
{
|
|
setFontSize(FontSize::large);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"x-large"))
|
|
{
|
|
setFontSize(FontSize::x_large);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"xx-large"))
|
|
{
|
|
setFontSize(FontSize::xx_large);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"initial"))
|
|
{
|
|
setFontSize(FontSize::initial);
|
|
}
|
|
else
|
|
{
|
|
SvgNumber aNum;
|
|
|
|
if(readSingleNumber(aContent, aNum))
|
|
{
|
|
maFontSizeNumber = aNum;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::FontSizeAdjust:
|
|
{
|
|
break;
|
|
}
|
|
case SVGToken::FontStretch:
|
|
{
|
|
if(!aContent.isEmpty())
|
|
{
|
|
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"normal"))
|
|
{
|
|
setFontStretch(FontStretch::normal);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"wider"))
|
|
{
|
|
setFontStretch(FontStretch::wider);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"narrower"))
|
|
{
|
|
setFontStretch(FontStretch::narrower);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"ultra-condensed"))
|
|
{
|
|
setFontStretch(FontStretch::ultra_condensed);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"extra-condensed"))
|
|
{
|
|
setFontStretch(FontStretch::extra_condensed);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"condensed"))
|
|
{
|
|
setFontStretch(FontStretch::condensed);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"semi-condensed"))
|
|
{
|
|
setFontStretch(FontStretch::semi_condensed);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"semi-expanded"))
|
|
{
|
|
setFontStretch(FontStretch::semi_expanded);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"expanded"))
|
|
{
|
|
setFontStretch(FontStretch::expanded);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"extra-expanded"))
|
|
{
|
|
setFontStretch(FontStretch::extra_expanded);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"ultra-expanded"))
|
|
{
|
|
setFontStretch(FontStretch::ultra_expanded);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::FontStyle:
|
|
{
|
|
if(!aContent.isEmpty())
|
|
{
|
|
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"normal"))
|
|
{
|
|
setFontStyle(FontStyle::normal);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"italic"))
|
|
{
|
|
setFontStyle(FontStyle::italic);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"oblique"))
|
|
{
|
|
setFontStyle(FontStyle::oblique);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::FontVariant:
|
|
{
|
|
break;
|
|
}
|
|
case SVGToken::FontWeight:
|
|
{
|
|
if(!aContent.isEmpty())
|
|
{
|
|
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"100"))
|
|
{
|
|
setFontWeight(FontWeight::N100);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"200"))
|
|
{
|
|
setFontWeight(FontWeight::N200);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"300"))
|
|
{
|
|
setFontWeight(FontWeight::N300);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"400") || o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"normal"))
|
|
{
|
|
setFontWeight(FontWeight::N400);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"500"))
|
|
{
|
|
setFontWeight(FontWeight::N500);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"600"))
|
|
{
|
|
setFontWeight(FontWeight::N600);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"700") || o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"bold"))
|
|
{
|
|
setFontWeight(FontWeight::N700);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"800"))
|
|
{
|
|
setFontWeight(FontWeight::N800);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"900"))
|
|
{
|
|
setFontWeight(FontWeight::N900);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"bolder"))
|
|
{
|
|
setFontWeight(FontWeight::bolder);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"lighter"))
|
|
{
|
|
setFontWeight(FontWeight::lighter);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::Direction:
|
|
{
|
|
break;
|
|
}
|
|
case SVGToken::LetterSpacing:
|
|
{
|
|
break;
|
|
}
|
|
case SVGToken::TextDecoration:
|
|
{
|
|
if(!aContent.isEmpty())
|
|
{
|
|
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"none"))
|
|
{
|
|
setTextDecoration(TextDecoration::none);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"underline"))
|
|
{
|
|
setTextDecoration(TextDecoration::underline);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"overline"))
|
|
{
|
|
setTextDecoration(TextDecoration::overline);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"line-through"))
|
|
{
|
|
setTextDecoration(TextDecoration::line_through);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"blink"))
|
|
{
|
|
setTextDecoration(TextDecoration::blink);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::UnicodeBidi:
|
|
{
|
|
break;
|
|
}
|
|
case SVGToken::WordSpacing:
|
|
{
|
|
break;
|
|
}
|
|
case SVGToken::TextAnchor:
|
|
{
|
|
if(!aContent.isEmpty())
|
|
{
|
|
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"start"))
|
|
{
|
|
setTextAnchor(TextAnchor::start);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"middle"))
|
|
{
|
|
setTextAnchor(TextAnchor::middle);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"end"))
|
|
{
|
|
setTextAnchor(TextAnchor::end);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::TextAlign:
|
|
{
|
|
if(!aContent.isEmpty())
|
|
{
|
|
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"left"))
|
|
{
|
|
setTextAlign(TextAlign::left);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"right"))
|
|
{
|
|
setTextAlign(TextAlign::right);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"center"))
|
|
{
|
|
setTextAlign(TextAlign::center);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"justify"))
|
|
{
|
|
setTextAlign(TextAlign::justify);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::Color:
|
|
{
|
|
SvgPaint aSvgPaint;
|
|
OUString aURL;
|
|
SvgNumber aOpacity;
|
|
|
|
if(readSvgPaint(aContent, aSvgPaint, aURL, aOpacity))
|
|
{
|
|
maColor = aSvgPaint;
|
|
if(aOpacity.isSet())
|
|
{
|
|
setOpacity(SvgNumber(std::clamp(aOpacity.getNumber(), 0.0, 1.0)));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::Opacity:
|
|
{
|
|
SvgNumber aNum;
|
|
|
|
if(readSingleNumber(aContent, aNum))
|
|
{
|
|
setOpacity(SvgNumber(std::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet()));
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::Overflow:
|
|
{
|
|
if(!aContent.isEmpty())
|
|
{
|
|
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"visible"))
|
|
{
|
|
setOverflow(Overflow::visible);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"hidden"))
|
|
{
|
|
setOverflow(Overflow::hidden);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::Visibility:
|
|
{
|
|
if(!aContent.isEmpty())
|
|
{
|
|
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"visible"))
|
|
{
|
|
setVisibility(Visibility::visible);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"hidden"))
|
|
{
|
|
setVisibility(Visibility::hidden);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"collapse"))
|
|
{
|
|
setVisibility(Visibility::collapse);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"inherit"))
|
|
{
|
|
setVisibility(Visibility::inherit);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::Title:
|
|
{
|
|
maTitle = aContent;
|
|
break;
|
|
}
|
|
case SVGToken::Desc:
|
|
{
|
|
maDesc = aContent;
|
|
break;
|
|
}
|
|
case SVGToken::ClipPathProperty:
|
|
{
|
|
readLocalUrl(aContent, maClipPathXLink);
|
|
break;
|
|
}
|
|
case SVGToken::Filter:
|
|
{
|
|
readLocalUrl(aContent, maFilterXLink);
|
|
break;
|
|
}
|
|
case SVGToken::Mask:
|
|
{
|
|
readLocalUrl(aContent, maMaskXLink);
|
|
break;
|
|
}
|
|
case SVGToken::ClipRule:
|
|
{
|
|
if(!aContent.isEmpty())
|
|
{
|
|
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrNonzero))
|
|
{
|
|
maClipRule = FillRule::nonzero;
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrEvenOdd))
|
|
{
|
|
maClipRule = FillRule::evenodd;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::Marker:
|
|
{
|
|
// tdf#155819: Using the marker property from a style sheet is equivalent to using all three (start, mid, end).
|
|
if(mrOwner.getType() == SVGToken::Style)
|
|
{
|
|
readLocalUrl(aContent, maMarkerEndXLink);
|
|
maMarkerStartXLink = maMarkerMidXLink = maMarkerEndXLink;
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::MarkerStart:
|
|
{
|
|
readLocalUrl(aContent, maMarkerStartXLink);
|
|
break;
|
|
}
|
|
case SVGToken::MarkerMid:
|
|
{
|
|
readLocalUrl(aContent, maMarkerMidXLink);
|
|
break;
|
|
}
|
|
case SVGToken::MarkerEnd:
|
|
{
|
|
readLocalUrl(aContent, maMarkerEndXLink);
|
|
break;
|
|
}
|
|
case SVGToken::Display:
|
|
{
|
|
// There may be display:none statements inside of style defines, e.g. the following line:
|
|
// style="display:none"
|
|
// taken from a svg example; this needs to be parsed and set at the owning node. Do not call
|
|
// mrOwner.parseAttribute(...) here, this would lead to a recursion
|
|
if(!aContent.isEmpty())
|
|
{
|
|
mrOwner.setDisplay(getDisplayFromContent(aContent));
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::BaselineShift:
|
|
{
|
|
if(!aContent.isEmpty())
|
|
{
|
|
SvgNumber aNum;
|
|
|
|
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"sub"))
|
|
{
|
|
setBaselineShift(BaselineShift::Sub);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"super"))
|
|
{
|
|
setBaselineShift(BaselineShift::Super);
|
|
}
|
|
else if(readSingleNumber(aContent, aNum))
|
|
{
|
|
maBaselineShiftNumber = aNum;
|
|
|
|
if(SvgUnit::percent == aNum.getUnit())
|
|
{
|
|
setBaselineShift(BaselineShift::Percentage);
|
|
}
|
|
else
|
|
{
|
|
setBaselineShift(BaselineShift::Length);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no BaselineShift or inherit (which is automatically)
|
|
setBaselineShift(BaselineShift::Baseline);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SVGToken::DominantBaseline:
|
|
{
|
|
if(!aContent.isEmpty())
|
|
{
|
|
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"middle"))
|
|
{
|
|
setDominantBaseline(DominantBaseline::Middle);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"hanging"))
|
|
{
|
|
setDominantBaseline(DominantBaseline::Hanging);
|
|
}
|
|
else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"central"))
|
|
{
|
|
setDominantBaseline(DominantBaseline::Central);
|
|
}
|
|
else
|
|
{
|
|
// no DominantBaseline
|
|
setDominantBaseline(DominantBaseline::Auto);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SvgStyleAttributes::isClipPathContent() const
|
|
{
|
|
if (SVGToken::ClipPathNode == mrOwner.getType())
|
|
return true;
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
if (pSvgStyleAttributes && maResolvingParent[31] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[31];
|
|
bool ret = pSvgStyleAttributes->isClipPathContent();
|
|
--maResolvingParent[31];
|
|
return ret;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// #i125258# ask if fill is a direct hard attribute (no hierarchy)
|
|
bool SvgStyleAttributes::isFillSet() const
|
|
{
|
|
if(isClipPathContent())
|
|
{
|
|
return false;
|
|
}
|
|
else if(maFill.isSet())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const basegfx::BColor* SvgStyleAttributes::getCurrentColor() const
|
|
{
|
|
static basegfx::BColor aBlack(0.0, 0.0, 0.0);
|
|
const basegfx::BColor *aColor = getColor();
|
|
if( aColor )
|
|
return aColor;
|
|
else
|
|
return &aBlack;
|
|
}
|
|
|
|
const basegfx::BColor* SvgStyleAttributes::getFill() const
|
|
{
|
|
if(maFill.isSet())
|
|
{
|
|
if(maFill.isCurrent())
|
|
{
|
|
return getCurrentColor();
|
|
}
|
|
else if(maFill.isOn())
|
|
{
|
|
return &maFill.getBColor();
|
|
}
|
|
else if(isClipPathContent())
|
|
{
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[0] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[0];
|
|
const basegfx::BColor* pFill = pSvgStyleAttributes->getFill();
|
|
--maResolvingParent[0];
|
|
|
|
return pFill;
|
|
}
|
|
}
|
|
}
|
|
else if (maNodeFillURL.isEmpty())
|
|
{
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[0] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[0];
|
|
const basegfx::BColor* pFill = pSvgStyleAttributes->getFill();
|
|
--maResolvingParent[0];
|
|
|
|
if(isClipPathContent())
|
|
{
|
|
if (pFill)
|
|
{
|
|
return pFill;
|
|
}
|
|
else
|
|
{
|
|
static basegfx::BColor aBlack(0.0, 0.0, 0.0);
|
|
return &aBlack;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return pFill;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const basegfx::BColor* SvgStyleAttributes::getStroke() const
|
|
{
|
|
if(maStroke.isSet())
|
|
{
|
|
if(maStroke.isCurrent())
|
|
{
|
|
return getCurrentColor();
|
|
}
|
|
else if(maStroke.isOn())
|
|
{
|
|
return &maStroke.getBColor();
|
|
}
|
|
}
|
|
else if (maNodeStrokeURL.isEmpty())
|
|
{
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[1] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[1];
|
|
auto ret = pSvgStyleAttributes->getStroke();
|
|
--maResolvingParent[1];
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const basegfx::BColor& SvgStyleAttributes::getStopColor() const
|
|
{
|
|
if(maStopColor.isCurrent())
|
|
{
|
|
return *getCurrentColor();
|
|
}
|
|
else
|
|
{
|
|
return maStopColor.getBColor();
|
|
}
|
|
}
|
|
|
|
const SvgGradientNode* SvgStyleAttributes::getSvgGradientNodeFill() const
|
|
{
|
|
if (!maFill.isSet())
|
|
{
|
|
if (!maNodeFillURL.isEmpty())
|
|
{
|
|
const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(maNodeFillURL);
|
|
|
|
if(pNode)
|
|
{
|
|
if(SVGToken::LinearGradient == pNode->getType() || SVGToken::RadialGradient == pNode->getType())
|
|
{
|
|
return static_cast< const SvgGradientNode* >(pNode);
|
|
}
|
|
}
|
|
}
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[2] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[2];
|
|
auto ret = pSvgStyleAttributes->getSvgGradientNodeFill();
|
|
--maResolvingParent[2];
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const SvgGradientNode* SvgStyleAttributes::getSvgGradientNodeStroke() const
|
|
{
|
|
if (!maStroke.isSet())
|
|
{
|
|
if(!maNodeStrokeURL.isEmpty())
|
|
{
|
|
const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(maNodeStrokeURL);
|
|
|
|
if(pNode)
|
|
{
|
|
if(SVGToken::LinearGradient == pNode->getType() || SVGToken::RadialGradient == pNode->getType())
|
|
{
|
|
return static_cast< const SvgGradientNode* >(pNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[3] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[3];
|
|
auto ret = pSvgStyleAttributes->getSvgGradientNodeStroke();
|
|
--maResolvingParent[3];
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const SvgPatternNode* SvgStyleAttributes::getSvgPatternNodeFill() const
|
|
{
|
|
if (!maFill.isSet())
|
|
{
|
|
if (!maNodeFillURL.isEmpty())
|
|
{
|
|
const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(maNodeFillURL);
|
|
|
|
if(pNode)
|
|
{
|
|
if(SVGToken::Pattern == pNode->getType())
|
|
{
|
|
return static_cast< const SvgPatternNode* >(pNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[4] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[4];
|
|
auto ret = pSvgStyleAttributes->getSvgPatternNodeFill();
|
|
--maResolvingParent[4];
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const SvgPatternNode* SvgStyleAttributes::getSvgPatternNodeStroke() const
|
|
{
|
|
if (!maStroke.isSet())
|
|
{
|
|
if(!maNodeStrokeURL.isEmpty())
|
|
{
|
|
const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(maNodeStrokeURL);
|
|
|
|
if(pNode)
|
|
{
|
|
if(SVGToken::Pattern == pNode->getType())
|
|
{
|
|
return static_cast< const SvgPatternNode* >(pNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[5] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[5];
|
|
auto ret = pSvgStyleAttributes->getSvgPatternNodeStroke();
|
|
--maResolvingParent[5];
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SvgNumber SvgStyleAttributes::getStrokeWidth() const
|
|
{
|
|
if(maStrokeWidth.isSet())
|
|
{
|
|
return maStrokeWidth;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[6] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[6];
|
|
auto ret = pSvgStyleAttributes->getStrokeWidth();
|
|
--maResolvingParent[6];
|
|
return ret;
|
|
}
|
|
|
|
if(isClipPathContent())
|
|
{
|
|
return SvgNumber(0.0);
|
|
}
|
|
|
|
// default is 1
|
|
return SvgNumber(1.0);
|
|
}
|
|
|
|
SvgNumber SvgStyleAttributes::getStopOpacity() const
|
|
{
|
|
if(maStopOpacity.isSet())
|
|
{
|
|
return maStopOpacity;
|
|
}
|
|
|
|
// default is 1
|
|
return SvgNumber(1.0);
|
|
}
|
|
|
|
SvgNumber SvgStyleAttributes::getFillOpacity() const
|
|
{
|
|
if(maFillOpacity.isSet())
|
|
{
|
|
return maFillOpacity;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[7] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[7];
|
|
auto ret = pSvgStyleAttributes->getFillOpacity();
|
|
--maResolvingParent[7];
|
|
return ret;
|
|
}
|
|
|
|
// default is 1
|
|
return SvgNumber(1.0);
|
|
}
|
|
|
|
SvgNumber SvgStyleAttributes::getOpacity() const
|
|
{
|
|
if(maOpacity.isSet())
|
|
{
|
|
return maOpacity;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[33] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[33];
|
|
auto ret = pSvgStyleAttributes->getOpacity();
|
|
--maResolvingParent[33];
|
|
return ret;
|
|
}
|
|
|
|
// default is 1
|
|
return SvgNumber(1.0);
|
|
}
|
|
|
|
Overflow SvgStyleAttributes::getOverflow() const
|
|
{
|
|
if(Overflow::notset != maOverflow)
|
|
{
|
|
return maOverflow;
|
|
}
|
|
|
|
if (const SvgStyleAttributes* pSvgStyleAttributes = getCssStyle())
|
|
{
|
|
return pSvgStyleAttributes->getOverflow();
|
|
}
|
|
|
|
return Overflow::hidden;
|
|
}
|
|
|
|
Visibility SvgStyleAttributes::getVisibility() const
|
|
{
|
|
if(Visibility::notset == maVisibility || Visibility::inherit == maVisibility)
|
|
{
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[9] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[9];
|
|
auto ret = pSvgStyleAttributes->getVisibility();
|
|
--maResolvingParent[9];
|
|
return ret;
|
|
}
|
|
//default is Visible
|
|
return Visibility::visible;
|
|
}
|
|
|
|
// Visibility correction/exception for self-exported SVGs:
|
|
// When Impress exports single or multi-page SVGs, it puts the
|
|
// single slides into <g visibility="hidden">. Not sure why
|
|
// this happens, but this leads (correctly) to empty imported
|
|
// Graphics.
|
|
// Thus, if Visibility::hidden is active and owner is a SVGToken::G
|
|
// and it's parent is also a SVGToken::G and it has a Class 'SlideGroup'
|
|
// set, check if we are an Impress export.
|
|
// We are an Impress export if an SVG-Node titled 'ooo:meta_slides'
|
|
// exists.
|
|
// All together gives:
|
|
if(Visibility::hidden == maVisibility
|
|
&& SVGToken::G == mrOwner.getType()
|
|
&& nullptr != mrOwner.getDocument().findSvgNodeById(u"ooo:meta_slides"_ustr))
|
|
{
|
|
const SvgNode* pParent(mrOwner.getParent());
|
|
|
|
if(nullptr != pParent && SVGToken::G == pParent->getType() && pParent->getClass())
|
|
{
|
|
const OUString aClass(*pParent->getClass());
|
|
|
|
if("SlideGroup" == aClass)
|
|
{
|
|
// if we detect this exception,
|
|
// override Visibility::hidden -> Visibility::visible
|
|
return Visibility::visible;
|
|
}
|
|
}
|
|
}
|
|
|
|
return maVisibility;
|
|
}
|
|
|
|
FillRule SvgStyleAttributes::getFillRule() const
|
|
{
|
|
if(FillRule::notset != maFillRule)
|
|
{
|
|
return maFillRule;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[10] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[10];
|
|
auto ret = pSvgStyleAttributes->getFillRule();
|
|
--maResolvingParent[10];
|
|
return ret;
|
|
}
|
|
|
|
// default is NonZero
|
|
return FillRule::nonzero;
|
|
}
|
|
|
|
FillRule SvgStyleAttributes::getClipRule() const
|
|
{
|
|
if(FillRule::notset != maClipRule)
|
|
{
|
|
return maClipRule;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[25] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[25];
|
|
auto ret = pSvgStyleAttributes->getClipRule();
|
|
--maResolvingParent[25];
|
|
return ret;
|
|
}
|
|
|
|
// default is NonZero
|
|
return FillRule::nonzero;
|
|
}
|
|
|
|
const SvgNumberVector& SvgStyleAttributes::getStrokeDasharray() const
|
|
{
|
|
if(!maStrokeDasharray.empty())
|
|
{
|
|
return maStrokeDasharray;
|
|
}
|
|
else if(mbStrokeDasharraySet)
|
|
{
|
|
// #121221# is set to empty *by purpose*, do not visit parent styles
|
|
return maStrokeDasharray;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[11] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[11];
|
|
const SvgNumberVector& ret = pSvgStyleAttributes->getStrokeDasharray();
|
|
--maResolvingParent[11];
|
|
return ret;
|
|
}
|
|
|
|
// default empty
|
|
return maStrokeDasharray;
|
|
}
|
|
|
|
SvgNumber SvgStyleAttributes::getStrokeDashOffset() const
|
|
{
|
|
if(maStrokeDashOffset.isSet())
|
|
{
|
|
return maStrokeDashOffset;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[12] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[12];
|
|
auto ret = pSvgStyleAttributes->getStrokeDashOffset();
|
|
--maResolvingParent[12];
|
|
return ret;
|
|
}
|
|
|
|
// default is 0
|
|
return SvgNumber(0.0);
|
|
}
|
|
|
|
StrokeLinecap SvgStyleAttributes::getStrokeLinecap() const
|
|
{
|
|
if(maStrokeLinecap != StrokeLinecap::notset)
|
|
{
|
|
return maStrokeLinecap;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[13] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[13];
|
|
auto ret = pSvgStyleAttributes->getStrokeLinecap();
|
|
--maResolvingParent[13];
|
|
return ret;
|
|
}
|
|
|
|
// default is StrokeLinecap::butt
|
|
return StrokeLinecap::butt;
|
|
}
|
|
|
|
StrokeLinejoin SvgStyleAttributes::getStrokeLinejoin() const
|
|
{
|
|
if(maStrokeLinejoin != StrokeLinejoin::notset)
|
|
{
|
|
return maStrokeLinejoin;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[14] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[14];
|
|
auto ret = pSvgStyleAttributes->getStrokeLinejoin();
|
|
--maResolvingParent[14];
|
|
return ret;
|
|
}
|
|
|
|
// default is StrokeLinejoin::butt
|
|
return StrokeLinejoin::miter;
|
|
}
|
|
|
|
SvgNumber SvgStyleAttributes::getStrokeMiterLimit() const
|
|
{
|
|
if(maStrokeMiterLimit.isSet())
|
|
{
|
|
return maStrokeMiterLimit;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[15] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[15];
|
|
auto ret = pSvgStyleAttributes->getStrokeMiterLimit();
|
|
--maResolvingParent[15];
|
|
return ret;
|
|
}
|
|
|
|
// default is 4
|
|
return SvgNumber(4.0, SvgUnit::none);
|
|
}
|
|
|
|
SvgNumber SvgStyleAttributes::getStrokeOpacity() const
|
|
{
|
|
if(maStrokeOpacity.isSet())
|
|
{
|
|
return maStrokeOpacity;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[16] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[16];
|
|
auto ret = pSvgStyleAttributes->getStrokeOpacity();
|
|
--maResolvingParent[16];
|
|
return ret;
|
|
}
|
|
|
|
// default is 1
|
|
return SvgNumber(1.0);
|
|
}
|
|
|
|
const SvgStringVector& SvgStyleAttributes::getFontFamily() const
|
|
{
|
|
if(!maFontFamily.empty() && !o3tl::equalsIgnoreAsciiCase(o3tl::trim(maFontFamily[0]), u"inherit"))
|
|
{
|
|
return maFontFamily;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[17] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[17];
|
|
const SvgStringVector& ret = pSvgStyleAttributes->getFontFamily();
|
|
--maResolvingParent[17];
|
|
return ret;
|
|
}
|
|
|
|
// default is empty
|
|
return maFontFamily;
|
|
}
|
|
|
|
SvgNumber SvgStyleAttributes::getFontSizeNumber() const
|
|
{
|
|
// default size is 'medium', i.e. 12 pt, or 16px
|
|
constexpr double aDefaultSize = o3tl::convert(12.0, o3tl::Length::pt, o3tl::Length::px);
|
|
|
|
if(maFontSizeNumber.isSet())
|
|
{
|
|
if(!maFontSizeNumber.isPositive())
|
|
return aDefaultSize;
|
|
|
|
// #122524# Handle SvgUnit::percent relative to parent FontSize (see SVG1.1
|
|
// spec 10.10 Font selection properties \91font-size\92, lastline (click 'normative
|
|
// definition of the property')
|
|
if(SvgUnit::percent == maFontSizeNumber.getUnit())
|
|
{
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if(pSvgStyleAttributes)
|
|
{
|
|
const SvgNumber aParentNumber = pSvgStyleAttributes->getFontSizeNumber();
|
|
|
|
return SvgNumber(
|
|
aParentNumber.getNumber() * maFontSizeNumber.getNumber() * 0.01,
|
|
aParentNumber.getUnit(),
|
|
true);
|
|
}
|
|
// if there's no parent style, set the font size based on the default size
|
|
// 100% = 16px
|
|
return SvgNumber(
|
|
maFontSizeNumber.getNumber() * aDefaultSize / 100.0, SvgUnit::px, true);
|
|
}
|
|
else if((SvgUnit::em == maFontSizeNumber.getUnit()) || (SvgUnit::ex == maFontSizeNumber.getUnit()))
|
|
{
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if(pSvgStyleAttributes)
|
|
{
|
|
const SvgNumber aParentNumber = pSvgStyleAttributes->getFontSizeNumber();
|
|
double n = aParentNumber.getNumber() * maFontSizeNumber.getNumber();
|
|
if (SvgUnit::ex == maFontSizeNumber.getUnit())
|
|
n *= 0.5; // FIXME: use "x-height of the first available font"
|
|
|
|
return SvgNumber(n, aParentNumber.getUnit());
|
|
}
|
|
}
|
|
|
|
return maFontSizeNumber;
|
|
}
|
|
|
|
//In CSS2, the suggested scaling factor between adjacent indexes is 1.2
|
|
switch(maFontSize)
|
|
{
|
|
case FontSize::notset:
|
|
break;
|
|
case FontSize::xx_small:
|
|
{
|
|
return SvgNumber(aDefaultSize / 1.728);
|
|
}
|
|
case FontSize::x_small:
|
|
{
|
|
return SvgNumber(aDefaultSize / 1.44);
|
|
}
|
|
case FontSize::small:
|
|
{
|
|
return SvgNumber(aDefaultSize / 1.2);
|
|
}
|
|
case FontSize::smaller:
|
|
{
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
if(pSvgStyleAttributes)
|
|
{
|
|
const SvgNumber aParentNumber = pSvgStyleAttributes->getFontSizeNumber();
|
|
return SvgNumber(aParentNumber.getNumber() / 1.2, aParentNumber.getUnit());
|
|
}
|
|
[[fallthrough]];
|
|
}
|
|
case FontSize::medium:
|
|
case FontSize::initial:
|
|
{
|
|
return SvgNumber(aDefaultSize);
|
|
}
|
|
case FontSize::large:
|
|
{
|
|
return SvgNumber(aDefaultSize * 1.2);
|
|
}
|
|
case FontSize::larger:
|
|
{
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
if(pSvgStyleAttributes)
|
|
{
|
|
const SvgNumber aParentNumber = pSvgStyleAttributes->getFontSizeNumber();
|
|
return SvgNumber(aParentNumber.getNumber() * 1.2, aParentNumber.getUnit());
|
|
}
|
|
[[fallthrough]];
|
|
}
|
|
case FontSize::x_large:
|
|
{
|
|
return SvgNumber(aDefaultSize * 1.44);
|
|
}
|
|
case FontSize::xx_large:
|
|
{
|
|
return SvgNumber(aDefaultSize * 1.728);
|
|
}
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if(pSvgStyleAttributes)
|
|
{
|
|
return pSvgStyleAttributes->getFontSizeNumber();
|
|
}
|
|
|
|
return SvgNumber(aDefaultSize);
|
|
}
|
|
|
|
FontStretch SvgStyleAttributes::getFontStretch() const
|
|
{
|
|
if(maFontStretch != FontStretch::notset)
|
|
{
|
|
if(FontStretch::wider != maFontStretch && FontStretch::narrower != maFontStretch)
|
|
{
|
|
return maFontStretch;
|
|
}
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[18] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[18];
|
|
FontStretch aInherited = pSvgStyleAttributes->getFontStretch();
|
|
--maResolvingParent[18];
|
|
|
|
if(FontStretch::wider == maFontStretch)
|
|
{
|
|
aInherited = getWider(aInherited);
|
|
}
|
|
else if(FontStretch::narrower == maFontStretch)
|
|
{
|
|
aInherited = getNarrower(aInherited);
|
|
}
|
|
|
|
return aInherited;
|
|
}
|
|
|
|
// default is FontStretch::normal
|
|
return FontStretch::normal;
|
|
}
|
|
|
|
FontStyle SvgStyleAttributes::getFontStyle() const
|
|
{
|
|
if(maFontStyle != FontStyle::notset)
|
|
{
|
|
return maFontStyle;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[19] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[19];
|
|
auto ret = pSvgStyleAttributes->getFontStyle();
|
|
--maResolvingParent[19];
|
|
return ret;
|
|
}
|
|
|
|
// default is FontStyle::normal
|
|
return FontStyle::normal;
|
|
}
|
|
|
|
FontWeight SvgStyleAttributes::getFontWeight() const
|
|
{
|
|
if(maFontWeight != FontWeight::notset)
|
|
{
|
|
if(FontWeight::bolder != maFontWeight && FontWeight::lighter != maFontWeight)
|
|
{
|
|
return maFontWeight;
|
|
}
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[20] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[20];
|
|
FontWeight aInherited = pSvgStyleAttributes->getFontWeight();
|
|
--maResolvingParent[20];
|
|
|
|
if(FontWeight::bolder == maFontWeight)
|
|
{
|
|
aInherited = getBolder(aInherited);
|
|
}
|
|
else if(FontWeight::lighter == maFontWeight)
|
|
{
|
|
aInherited = getLighter(aInherited);
|
|
}
|
|
|
|
return aInherited;
|
|
}
|
|
|
|
// default is FontWeight::N400 (FontWeight::normal)
|
|
return FontWeight::N400;
|
|
}
|
|
|
|
TextAlign SvgStyleAttributes::getTextAlign() const
|
|
{
|
|
if(maTextAlign != TextAlign::notset)
|
|
{
|
|
return maTextAlign;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[21] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[21];
|
|
auto ret = pSvgStyleAttributes->getTextAlign();
|
|
--maResolvingParent[21];
|
|
return ret;
|
|
}
|
|
|
|
// default is TextAlign::left
|
|
return TextAlign::left;
|
|
}
|
|
|
|
const SvgStyleAttributes* SvgStyleAttributes::getTextDecorationDefiningSvgStyleAttributes() const
|
|
{
|
|
if(maTextDecoration != TextDecoration::notset)
|
|
{
|
|
return this;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[22] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[22];
|
|
auto ret = pSvgStyleAttributes->getTextDecorationDefiningSvgStyleAttributes();
|
|
--maResolvingParent[22];
|
|
return ret;
|
|
}
|
|
|
|
// default is 0
|
|
return nullptr;
|
|
}
|
|
|
|
TextDecoration SvgStyleAttributes::getTextDecoration() const
|
|
{
|
|
const SvgStyleAttributes* pDefining = getTextDecorationDefiningSvgStyleAttributes();
|
|
|
|
if(pDefining)
|
|
{
|
|
return pDefining->maTextDecoration;
|
|
}
|
|
else
|
|
{
|
|
// default is TextDecoration::none
|
|
return TextDecoration::none;
|
|
}
|
|
}
|
|
|
|
TextAnchor SvgStyleAttributes::getTextAnchor() const
|
|
{
|
|
if(maTextAnchor != TextAnchor::notset)
|
|
{
|
|
return maTextAnchor;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[23] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[23];
|
|
auto ret = pSvgStyleAttributes->getTextAnchor();
|
|
--maResolvingParent[23];
|
|
return ret;
|
|
}
|
|
|
|
// default is TextAnchor::start
|
|
return TextAnchor::start;
|
|
}
|
|
|
|
const basegfx::BColor* SvgStyleAttributes::getColor() const
|
|
{
|
|
if(maColor.isSet())
|
|
{
|
|
if(maColor.isCurrent())
|
|
{
|
|
OSL_ENSURE(false, "Svg error: current color uses current color (!)");
|
|
return nullptr;
|
|
}
|
|
else if(maColor.isOn())
|
|
{
|
|
return &maColor.getBColor();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[24] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[24];
|
|
auto ret = pSvgStyleAttributes->getColor();
|
|
--maResolvingParent[24];
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
OUString SvgStyleAttributes::getClipPathXLink() const
|
|
{
|
|
if(!maClipPathXLink.isEmpty())
|
|
{
|
|
return maClipPathXLink;
|
|
}
|
|
|
|
// This is called from add_postProcess so only check if it has a css style
|
|
if (const SvgStyleAttributes* pSvgStyleAttributes = getCssStyle())
|
|
{
|
|
return pSvgStyleAttributes->maClipPathXLink;
|
|
}
|
|
|
|
return OUString();
|
|
}
|
|
|
|
const SvgClipPathNode* SvgStyleAttributes::accessClipPathXLink() const
|
|
{
|
|
const OUString aClipPath(getClipPathXLink());
|
|
|
|
if(!aClipPath.isEmpty())
|
|
{
|
|
return dynamic_cast< const SvgClipPathNode* >(mrOwner.getDocument().findSvgNodeById(aClipPath));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
OUString SvgStyleAttributes::getFilterXLink() const
|
|
{
|
|
if(!maFilterXLink.isEmpty())
|
|
{
|
|
return maFilterXLink;
|
|
}
|
|
|
|
// This is called from add_postProcess so only check if it has a css style
|
|
if (const SvgStyleAttributes* pSvgStyleAttributes = getCssStyle())
|
|
{
|
|
return pSvgStyleAttributes->maFilterXLink;
|
|
}
|
|
|
|
return OUString();
|
|
}
|
|
|
|
const SvgFilterNode* SvgStyleAttributes::accessFilterXLink() const
|
|
{
|
|
const OUString aFilter(getFilterXLink());
|
|
|
|
if(!aFilter.isEmpty())
|
|
{
|
|
return dynamic_cast< const SvgFilterNode* >(mrOwner.getDocument().findSvgNodeById(aFilter));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
OUString SvgStyleAttributes::getMaskXLink() const
|
|
{
|
|
if(!maMaskXLink.isEmpty())
|
|
{
|
|
return maMaskXLink;
|
|
}
|
|
|
|
// This is called from add_postProcess so only check if it has a css style
|
|
if (const SvgStyleAttributes* pSvgStyleAttributes = getCssStyle())
|
|
{
|
|
return pSvgStyleAttributes->maMaskXLink;
|
|
}
|
|
|
|
return OUString();
|
|
}
|
|
|
|
const SvgMaskNode* SvgStyleAttributes::accessMaskXLink() const
|
|
{
|
|
const OUString aMask(getMaskXLink());
|
|
|
|
if(!aMask.isEmpty())
|
|
{
|
|
return dynamic_cast< const SvgMaskNode* >(mrOwner.getDocument().findSvgNodeById(aMask));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
OUString SvgStyleAttributes::getMarkerStartXLink() const
|
|
{
|
|
if(!maMarkerStartXLink.isEmpty())
|
|
{
|
|
return maMarkerStartXLink;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[26] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[26];
|
|
auto ret = pSvgStyleAttributes->getMarkerStartXLink();
|
|
--maResolvingParent[26];
|
|
return ret;
|
|
}
|
|
|
|
return OUString();
|
|
}
|
|
|
|
const SvgMarkerNode* SvgStyleAttributes::accessMarkerStartXLink() const
|
|
{
|
|
const OUString aMarker(getMarkerStartXLink());
|
|
|
|
if(!aMarker.isEmpty())
|
|
{
|
|
return dynamic_cast< const SvgMarkerNode* >(mrOwner.getDocument().findSvgNodeById(getMarkerStartXLink()));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
OUString SvgStyleAttributes::getMarkerMidXLink() const
|
|
{
|
|
if(!maMarkerMidXLink.isEmpty())
|
|
{
|
|
return maMarkerMidXLink;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[27] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[27];
|
|
auto ret = pSvgStyleAttributes->getMarkerMidXLink();
|
|
--maResolvingParent[27];
|
|
return ret;
|
|
}
|
|
|
|
return OUString();
|
|
}
|
|
|
|
const SvgMarkerNode* SvgStyleAttributes::accessMarkerMidXLink() const
|
|
{
|
|
const OUString aMarker(getMarkerMidXLink());
|
|
|
|
if(!aMarker.isEmpty())
|
|
{
|
|
return dynamic_cast< const SvgMarkerNode* >(mrOwner.getDocument().findSvgNodeById(getMarkerMidXLink()));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
OUString SvgStyleAttributes::getMarkerEndXLink() const
|
|
{
|
|
if(!maMarkerEndXLink.isEmpty())
|
|
{
|
|
return maMarkerEndXLink;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[28] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[28];
|
|
auto ret = pSvgStyleAttributes->getMarkerEndXLink();
|
|
--maResolvingParent[28];
|
|
return ret;
|
|
}
|
|
|
|
return OUString();
|
|
}
|
|
|
|
const SvgMarkerNode* SvgStyleAttributes::accessMarkerEndXLink() const
|
|
{
|
|
const OUString aMarker(getMarkerEndXLink());
|
|
|
|
if(!aMarker.isEmpty())
|
|
{
|
|
return dynamic_cast< const SvgMarkerNode* >(mrOwner.getDocument().findSvgNodeById(getMarkerEndXLink()));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SvgNumber SvgStyleAttributes::getBaselineShiftNumber() const
|
|
{
|
|
// #122524# Handle SvgUnit::percent relative to parent BaselineShift
|
|
if(SvgUnit::percent == maBaselineShiftNumber.getUnit())
|
|
{
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[8] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[8];
|
|
const SvgNumber aParentNumber = pSvgStyleAttributes->getBaselineShiftNumber();
|
|
--maResolvingParent[8];
|
|
|
|
return SvgNumber(
|
|
aParentNumber.getNumber() * maBaselineShiftNumber.getNumber() * 0.01,
|
|
aParentNumber.getUnit(),
|
|
true);
|
|
}
|
|
}
|
|
|
|
return maBaselineShiftNumber;
|
|
}
|
|
|
|
BaselineShift SvgStyleAttributes::getBaselineShift() const
|
|
{
|
|
if(maBaselineShift != BaselineShift::Baseline)
|
|
{
|
|
return maBaselineShift;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[29] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[29];
|
|
auto ret = pSvgStyleAttributes->getBaselineShift();
|
|
--maResolvingParent[29];
|
|
return ret;
|
|
}
|
|
|
|
return BaselineShift::Baseline;
|
|
}
|
|
|
|
DominantBaseline SvgStyleAttributes::getDominantBaseline() const
|
|
{
|
|
if(maDominantBaseline != DominantBaseline::Auto)
|
|
{
|
|
return maDominantBaseline;
|
|
}
|
|
|
|
const SvgStyleAttributes* pSvgStyleAttributes = getCssStyleOrParentStyle();
|
|
|
|
if (pSvgStyleAttributes && maResolvingParent[30] < nStyleDepthLimit)
|
|
{
|
|
++maResolvingParent[30];
|
|
auto ret = pSvgStyleAttributes->getDominantBaseline();
|
|
--maResolvingParent[30];
|
|
return ret;
|
|
}
|
|
|
|
return DominantBaseline::Auto;
|
|
}
|
|
} // end of namespace svgio
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
|
|