diff options
Diffstat (limited to 'svgio/source/svgreader/svgstyleattributes.cxx')
-rw-r--r-- | svgio/source/svgreader/svgstyleattributes.cxx | 2489 |
1 files changed, 2489 insertions, 0 deletions
diff --git a/svgio/source/svgreader/svgstyleattributes.cxx b/svgio/source/svgreader/svgstyleattributes.cxx new file mode 100644 index 000000000000..1a2755e52b1c --- /dev/null +++ b/svgio/source/svgreader/svgstyleattributes.cxx @@ -0,0 +1,2489 @@ +/* -*- 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 <svgio/svgreader/svgstyleattributes.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/polypolygonprimitive2d.hxx> +#include <svgio/svgreader/svgnode.hxx> +#include <svgio/svgreader/svgdocument.hxx> +#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx> +#include <svgio/svgreader/svggradientnode.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <basegfx/vector/b2enums.hxx> +#include <drawinglayer/processor2d/linegeometryextractor2d.hxx> +#include <drawinglayer/processor2d/textaspolygonextractor2d.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <svgio/svgreader/svgclippathnode.hxx> +#include <svgio/svgreader/svgmasknode.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <svgio/svgreader/svgmarkernode.hxx> +#include <basegfx/curve/b2dcubicbezier.hxx> +#include <svgio/svgreader/svgpatternnode.hxx> +#include <drawinglayer/primitive2d/patternfillprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace svgio +{ + namespace svgreader + { + 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; + } + + com::sun::star::drawing::LineCap StrokeLinecapToDrawingLineCap(StrokeLinecap aStrokeLinecap) + { + switch(aStrokeLinecap) + { + default: /* StrokeLinecap_notset, StrokeLinecap_butt */ + { + return com::sun::star::drawing::LineCap_BUTT; + break; + } + case StrokeLinecap_round: + { + return com::sun::star::drawing::LineCap_ROUND; + break; + } + case StrokeLinecap_square: + { + return com::sun::star::drawing::LineCap_SQUARE; + break; + } + } + } + + 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_100: aSource = FontWeight_200; break; + case FontWeight_200: aSource = FontWeight_300; break; + case FontWeight_300: aSource = FontWeight_400; break; + case FontWeight_400: aSource = FontWeight_500; break; + case FontWeight_500: aSource = FontWeight_600; break; + case FontWeight_600: aSource = FontWeight_700; break; + case FontWeight_700: aSource = FontWeight_800; break; + case FontWeight_800: aSource = FontWeight_900; break; + default: break; + } + + return aSource; + } + + FontWeight getLighter(FontWeight aSource) + { + switch(aSource) + { + case FontWeight_200: aSource = FontWeight_100; break; + case FontWeight_300: aSource = FontWeight_200; break; + case FontWeight_400: aSource = FontWeight_300; break; + case FontWeight_500: aSource = FontWeight_400; break; + case FontWeight_600: aSource = FontWeight_500; break; + case FontWeight_700: aSource = FontWeight_600; break; + case FontWeight_800: aSource = FontWeight_700; break; + case FontWeight_900: aSource = FontWeight_800; break; + default: break; + } + + return aSource; + } + + ::FontWeight getVclFontWeight(FontWeight aSource) + { + ::FontWeight nRetval(WEIGHT_NORMAL); + + switch(aSource) + { + case FontWeight_100: nRetval = WEIGHT_ULTRALIGHT; break; + case FontWeight_200: nRetval = WEIGHT_LIGHT; break; + case FontWeight_300: nRetval = WEIGHT_SEMILIGHT; break; + case FontWeight_400: nRetval = WEIGHT_NORMAL; break; + case FontWeight_500: nRetval = WEIGHT_MEDIUM; break; + case FontWeight_600: nRetval = WEIGHT_SEMIBOLD; break; + case FontWeight_700: nRetval = WEIGHT_BOLD; break; + case FontWeight_800: nRetval = WEIGHT_ULTRABOLD; break; + case FontWeight_900: nRetval = WEIGHT_BLACK; break; + default: break; + } + + return nRetval; + } + + void SvgStyleAttributes::readStyle(const rtl::OUString& rCandidate) + { + const sal_Int32 nLen(rCandidate.getLength()); + sal_Int32 nPos(0); + + while(nPos < nLen) + { + const sal_Int32 nInitPos(nPos); + skip_char(rCandidate, sal_Unicode(' '), nPos, nLen); + rtl::OUStringBuffer aTokenName; + copyString(rCandidate, nPos, aTokenName, nLen); + + if(aTokenName.getLength()) + { + skip_char(rCandidate, sal_Unicode(' '), sal_Unicode(':'), nPos, nLen); + rtl::OUStringBuffer aTokenValue; + copyToLimiter(rCandidate, sal_Unicode(';'), nPos, aTokenValue, nLen); + skip_char(rCandidate, sal_Unicode(' '), sal_Unicode(';'), nPos, nLen); + const rtl::OUString aOUTokenName(aTokenName.makeStringAndClear()); + const rtl::OUString aOUTokenValue(aTokenValue.makeStringAndClear()); + + parseStyleAttribute(aOUTokenName, StrToSVGToken(aOUTokenName), aOUTokenValue); + } + + if(nInitPos == nPos) + { + OSL_ENSURE(false, "Could not interpret on current position (!)"); + nPos++; + } + } + } + + void SvgStyleAttributes::checkForCssStyle(const rtl::OUString& rClassStr) const + { + if(!mpCssStyleParent) + { + const SvgDocument& rDocument = mrOwner.getDocument(); + const SvgStyleAttributes* pNew = 0; + + if(rDocument.hasSvgStyleAttributesById()) + { + if(mrOwner.getClass()) + { + rtl::OUString aId(rtl::OUString::createFromAscii(".")); + aId = aId + *mrOwner.getClass(); + pNew = rDocument.findSvgStyleAttributesById(aId); + + if(!pNew && rClassStr.getLength()) + { + aId = rClassStr + aId; + + pNew = rDocument.findSvgStyleAttributesById(aId); + } + } + + if(!pNew && mrOwner.getId()) + { + pNew = rDocument.findSvgStyleAttributesById(*mrOwner.getId()); + } + + if(!pNew && rClassStr.getLength()) + { + pNew = rDocument.findSvgStyleAttributesById(rClassStr); + } + + if(pNew) + { + // found css style, set as parent + const_cast< SvgStyleAttributes* >(this)->mpCssStyleParent = pNew; + } + } + } + } + + const SvgStyleAttributes* SvgStyleAttributes::getParentStyle() const + { + if(mpCssStyleParent) + { + return mpCssStyleParent; + } + + if(mrOwner.getParent()) + { + return mrOwner.getParent()->getSvgStyleAttributes(); + } + + return 0; + } + + void SvgStyleAttributes::add_text( + drawinglayer::primitive2d::Primitive2DSequence& rTarget, + drawinglayer::primitive2d::Primitive2DSequence& rSource) const + { + if(rSource.hasElements()) + { + // 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); + + // proccess + 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::tools::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 divrese 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 + drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, 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::Primitive2DSequence& 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()) + { + basegfx::B2DHomMatrix aGeoToUnit; + + if(rFillGradient.getGradientTransform()) + { + aGeoToUnit = *rFillGradient.getGradientTransform(); + } + + if(userSpaceOnUse == rFillGradient.getGradientUnits()) + { + aGeoToUnit.translate(-rGeoRange.getMinX(), -rGeoRange.getMinY()); + aGeoToUnit.scale(1.0 / rGeoRange.getWidth(), 1.0 / rGeoRange.getHeight()); + } + + if(SVGTokenLinearGradient == rFillGradient.getType()) + { + basegfx::B2DPoint aStart(0.0, 0.0); + basegfx::B2DPoint aEnd(1.0, 0.0); + + if(userSpaceOnUse == rFillGradient.getGradientUnits()) + { + // all possible units + aStart.setX(rFillGradient.getX1().solve(mrOwner, xcoordinate)); + aStart.setY(rFillGradient.getY1().solve(mrOwner, ycoordinate)); + aEnd.setX(rFillGradient.getX2().solve(mrOwner, xcoordinate)); + aEnd.setY(rFillGradient.getY2().solve(mrOwner, 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(Unit_percent == X1.getUnit() ? X1.getNumber() * 0.01 : X1.getNumber()); + aStart.setY(Unit_percent == Y1.getUnit() ? Y1.getNumber() * 0.01 : Y1.getNumber()); + aEnd.setX(Unit_percent == X2.getUnit() ? X2.getNumber() * 0.01 : X2.getNumber()); + aEnd.setY(Unit_percent == Y2.getUnit() ? Y2.getNumber() * 0.01 : Y2.getNumber()); + } + + if(!aGeoToUnit.isIdentity()) + { + aStart *= aGeoToUnit; + aEnd *= aGeoToUnit; + } + + drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence( + rTarget, + new drawinglayer::primitive2d::SvgLinearGradientPrimitive2D( + rPath, + aSvgGradientEntryVector, + aStart, + aEnd, + 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(userSpaceOnUse == rFillGradient.getGradientUnits()) + { + // all possible units + aStart.setX(rFillGradient.getCx().solve(mrOwner, xcoordinate)); + aStart.setY(rFillGradient.getCy().solve(mrOwner, ycoordinate)); + fRadius = rFillGradient.getR().solve(mrOwner, length); + + if(bFocal) + { + aFocal.setX(pFx ? pFx->solve(mrOwner, xcoordinate) : aStart.getX()); + aFocal.setY(pFy ? pFy->solve(mrOwner, 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(Unit_percent == Cx.getUnit() ? Cx.getNumber() * 0.01 : Cx.getNumber()); + aStart.setY(Unit_percent == Cy.getUnit() ? Cy.getNumber() * 0.01 : Cy.getNumber()); + fRadius = (Unit_percent == R.getUnit()) ? R.getNumber() * 0.01 : R.getNumber(); + + if(bFocal) + { + aFocal.setX(pFx ? (Unit_percent == pFx->getUnit() ? pFx->getNumber() * 0.01 : pFx->getNumber()) : aStart.getX()); + aFocal.setY(pFy ? (Unit_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; + } + } + + drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence( + rTarget, + new drawinglayer::primitive2d::SvgRadialGradientPrimitive2D( + rPath, + aSvgGradientEntryVector, + aStart, + fRadius, + rFillGradient.getSpreadMethod(), + bFocal ? &aFocal : 0)); + } + } + } + + void SvgStyleAttributes::add_fillPatternTransform( + const basegfx::B2DPolyPolygon& rPath, + drawinglayer::primitive2d::Primitive2DSequence& 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::Primitive2DSequence aNewTarget; + + aInv.invert(); + aPath.transform(aInv); + add_fillPattern(aPath, aNewTarget, rFillPattern, aPath.getB2DRange()); + + if(aNewTarget.hasElements()) + { + drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence( + rTarget, + new drawinglayer::primitive2d::TransformPrimitive2D( + *rFillPattern.getPatternTransform(), + aNewTarget)); + } + } + else + { + // no patternTransform, create fillPattern directly + add_fillPattern(rPath, rTarget, rFillPattern, rGeoRange); + } + } + + void SvgStyleAttributes::add_fillPattern( + const basegfx::B2DPolyPolygon& rPath, + drawinglayer::primitive2d::Primitive2DSequence& rTarget, + const SvgPatternNode& rFillPattern, + const basegfx::B2DRange& rGeoRange) const + { + // fill polyPolygon with given pattern + const drawinglayer::primitive2d::Primitive2DSequence& rPrimitives = rFillPattern.getPatternPrimitives(); + + if(rPrimitives.hasElements()) + { + double fTargetWidth(rGeoRange.getWidth()); + double fTargetHeight(rGeoRange.getHeight()); + + if(fTargetWidth > 0.0 && fTargetHeight > 0.0) + { + // 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) + { + // 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 = rRatio.createLinearMapping(aUnitRange, *pViewBox); + } + } + else + { + // use patternContentUnits + const SvgUnits aPatternContentUnits(rFillPattern.getPatternContentUnits() ? *rFillPattern.getPatternContentUnits() : userSpaceOnUse); + + if(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::Primitive2DSequence aPrimitives(rPrimitives); + + if(!aMapPrimitivesToUnitRange.isIdentity()) + { + const drawinglayer::primitive2d::Primitive2DReference xRef( + new drawinglayer::primitive2d::TransformPrimitive2D( + aMapPrimitivesToUnitRange, + aPrimitives)); + + aPrimitives = drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1); + } + + // embed in PatternFillPrimitive2D + drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence( + rTarget, + new drawinglayer::primitive2d::PatternFillPrimitive2D( + rPath, + aPrimitives, + aReferenceRange)); + } + } + } + } + + void SvgStyleAttributes::add_fill( + const basegfx::B2DPolyPolygon& rPath, + drawinglayer::primitive2d::Primitive2DSequence& rTarget, + const basegfx::B2DRange& rGeoRange) const + { + const basegfx::BColor* pFill = getFill(); + const SvgGradientNode* pFillGradient = getSvgGradientNodeFill(); + const SvgPatternNode* pFillPattern = getSvgPatternNodeFill(); + + if(pFill || pFillGradient || pFillPattern) + { + const double fFillOpacity(getFillOpacity().solve(mrOwner, length)); + + if(basegfx::fTools::more(fFillOpacity, 0.0)) + { + drawinglayer::primitive2d::Primitive2DSequence 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 + aNewFill.realloc(1); + aNewFill[0] = new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + rPath, + *pFill); + } + + if(aNewFill.hasElements()) + { + if(basegfx::fTools::less(fFillOpacity, 1.0)) + { + // embed in UnifiedTransparencePrimitive2D + drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence( + rTarget, + new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( + aNewFill, + 1.0 - fFillOpacity)); + } + else + { + // append + drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aNewFill); + } + } + } + } + } + + void SvgStyleAttributes::add_stroke( + const basegfx::B2DPolyPolygon& rPath, + drawinglayer::primitive2d::Primitive2DSequence& rTarget, + const basegfx::B2DRange& rGeoRange) const + { + const basegfx::BColor* pStroke = getStroke(); + const SvgGradientNode* pStrokeGradient = getSvgGradientNodeStroke(); + const SvgPatternNode* pStrokePattern = getSvgPatternNodeStroke(); + + if(pStroke || pStrokeGradient || pStrokePattern) + { + drawinglayer::primitive2d::Primitive2DSequence aNewStroke; + const double fStrokeOpacity(getStrokeOpacity().solve(mrOwner, length)); + + if(basegfx::fTools::more(fStrokeOpacity, 0.0)) + { + // 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, length) : 1.0); + + if(basegfx::fTools::more(fStrokeWidth, 0.0)) + { + // get LineJoin, LineCap and stroke array + const basegfx::B2DLineJoin aB2DLineJoin(StrokeLinejoinToB2DLineJoin(getStrokeLinejoin())); + const com::sun::star::drawing::LineCap aLineCap(StrokeLinecapToDrawingLineCap(getStrokeLinecap())); + ::std::vector< double > aDashArray; + + if(!getStrokeDasharray().empty()) + { + aDashArray = solveSvgNumberVector(getStrokeDasharray(), mrOwner, length); + } + + // todo: Handle getStrokeDashOffset() + + // prepare line attribute + drawinglayer::primitive2d::Primitive2DReference aNewLinePrimitive; + const drawinglayer::attribute::LineAttribute aLineAttribute( + pStroke ? *pStroke : basegfx::BColor(0.0, 0.0, 0.0), + fStrokeWidth, + aB2DLineJoin, + aLineCap); + + if(aDashArray.empty()) + { + aNewLinePrimitive = new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D( + rPath, + aLineAttribute); + } + else + { + const drawinglayer::attribute::StrokeAttribute aStrokeAttribute(aDashArray); + + aNewLinePrimitive = new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D( + rPath, + aLineAttribute, + aStrokeAttribute); + } + + if(pStrokeGradient || pStrokePattern) + { + // put primitive into Primitive2DReference and Primitive2DSequence + const drawinglayer::primitive2d::Primitive2DSequence aSeq(&aNewLinePrimitive, 1); + + // use neutral ViewInformation and create LineGeometryExtractor2D + const drawinglayer::geometry::ViewInformation2D aViewInformation2D; + drawinglayer::processor2d::LineGeometryExtractor2D aExtractor(aViewInformation2D); + + // proccess + aExtractor.process(aSeq); + + // check for fill rsults + const basegfx::B2DPolyPolygonVector& rLineFillVector(aExtractor.getExtractedLineFills()); + + if(!rLineFillVector.empty()) + { + const basegfx::B2DPolyPolygon aMergedArea( + basegfx::tools::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) + { + drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(aNewStroke, aNewLinePrimitive); + } + + if(aNewStroke.hasElements()) + { + if(basegfx::fTools::less(fStrokeOpacity, 1.0)) + { + // embed in UnifiedTransparencePrimitive2D + drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence( + rTarget, + new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( + aNewStroke, + 1.0 - fStrokeOpacity)); + } + else + { + // append + drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aNewStroke); + } + } + } + } + } + } + + double get_markerRotation( + const SvgMarkerNode& rMarker, + const basegfx::B2DPolygon& rPolygon, + const sal_uInt32 nIndex) + { + double fAngle(0.0); + const sal_uInt32 nPointCount(rPolygon.count()); + + if(nPointCount) + { + if(rMarker.getOrientAuto()) + { + const bool bPrev(rPolygon.isClosed() || nIndex > 0); + basegfx::B2DCubicBezier aSegment; + basegfx::B2DVector aPrev; + basegfx::B2DVector aNext; + + if(bPrev) + { + rPolygon.getBezierSegment((nIndex - 1) % nPointCount, aSegment); + aPrev = aSegment.getTangent(1.0); + } + + const bool bNext(rPolygon.isClosed() || nIndex + 1 < nPointCount); + + if(bNext) + { + rPolygon.getBezierSegment(nIndex % nPointCount, aSegment); + aNext = aSegment.getTangent(0.0); + } + + if(bPrev && bNext) + { + fAngle = atan2(aPrev.getY() + aNext.getY(), aPrev.getX() + aNext.getX()); + } + else if(bPrev) + { + fAngle = atan2(aPrev.getY(), aPrev.getX()); + } + else if(bNext) + { + fAngle = atan2(aNext.getY(), aNext.getX()); + } + } + else + { + fAngle = rMarker.getAngle(); + } + } + + return fAngle; + } + + bool SvgStyleAttributes::prepare_singleMarker( + drawinglayer::primitive2d::Primitive2DSequence& rMarkerPrimitives, + basegfx::B2DHomMatrix& rMarkerTransform, + basegfx::B2DRange& rClipRange, + const SvgMarkerNode& rMarker) const + { + // reset return values + rMarkerTransform.identity(); + rClipRange.reset(); + + // get marker primitive representation + rMarkerPrimitives = rMarker.getMarkerPrimitives(); + + if(rMarkerPrimitives.hasElements()) + { + 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, xcoordinate) : 3.0); + double fTargetHeight(rMarker.getMarkerHeight().isSet() ? rMarker.getMarkerHeight().solve(mrOwner, xcoordinate) : 3.0); + const bool bStrokeWidth(SvgMarkerNode::strokeWidth == rMarker.getMarkerUnits()); + const double fStrokeWidth(getStrokeWidth().isSet() ? getStrokeWidth().solve(mrOwner, length) : 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()) + { + // 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 = rRatio.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, xcoordinate) : 0.0, + rMarker.getRefY().isSet() ? rMarker.getRefY().solve(mrOwner, 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_singleMarker( + drawinglayer::primitive2d::Primitive2DSequence& rTarget, + const drawinglayer::primitive2d::Primitive2DSequence& rMarkerPrimitives, + const basegfx::B2DHomMatrix& rMarkerTransform, + const basegfx::B2DRange& rClipRange, + const SvgMarkerNode& rMarker, + const basegfx::B2DPolygon& rCandidate, + const sal_uInt32 nIndex) const + { + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount) + { + // get and apply rotation + basegfx::B2DHomMatrix aCombinedTransform(rMarkerTransform); + aCombinedTransform.rotate(get_markerRotation(rMarker, rCandidate, nIndex)); + + // get and apply target position + const basegfx::B2DPoint aPoint(rCandidate.getB2DPoint(nIndex % nPointCount)); + aCombinedTransform.translate(aPoint.getX(), aPoint.getY()); + + // prepare marker + drawinglayer::primitive2d::Primitive2DReference xMarker( + new drawinglayer::primitive2d::TransformPrimitive2D( + aCombinedTransform, + rMarkerPrimitives)); + + if(!rClipRange.isEmpty()) + { + // marker needs to be clipped, it's bigger as the mapping + basegfx::B2DPolyPolygon aClipPolygon(basegfx::tools::createPolygonFromRect(rClipRange)); + + aClipPolygon.transform(aCombinedTransform); + xMarker = new drawinglayer::primitive2d::MaskPrimitive2D( + aClipPolygon, + drawinglayer::primitive2d::Primitive2DSequence(&xMarker, 1)); + } + + // add marker + drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xMarker); + } + } + + void SvgStyleAttributes::add_markers( + const basegfx::B2DPolyPolygon& rPath, + drawinglayer::primitive2d::Primitive2DSequence& rTarget) const + { + // try to access linked markers + const SvgMarkerNode* pStart = accessMarkerStartXLink(); + const SvgMarkerNode* pMid = accessMarkerMidXLink(); + const SvgMarkerNode* pEnd = accessMarkerEndXLink(); + + if(pStart || pMid || pEnd) + { + const sal_uInt32 nCount(rPath.count()); + + for (sal_uInt32 a(0); a < nCount; a++) + { + const basegfx::B2DPolygon aCandidate(rPath.getB2DPolygon(a)); + const sal_uInt32 nPointCount(aCandidate.count()); + + if(nPointCount) + { + const sal_uInt32 nMarkerCount(aCandidate.isClosed() ? nPointCount + 1 : nPointCount); + drawinglayer::primitive2d::Primitive2DSequence aMarkerPrimitives; + basegfx::B2DHomMatrix aMarkerTransform; + basegfx::B2DRange aClipRange; + const SvgMarkerNode* pPrepared = 0; + + if(pStart) + { + if(prepare_singleMarker(aMarkerPrimitives, aMarkerTransform, aClipRange, *pStart)) + { + pPrepared = pStart; + add_singleMarker(rTarget, aMarkerPrimitives, aMarkerTransform, aClipRange, *pPrepared, aCandidate, 0); + } + } + + if(pMid && nMarkerCount > 2) + { + if(pMid == pPrepared || prepare_singleMarker(aMarkerPrimitives, aMarkerTransform, aClipRange, *pMid)) + { + pPrepared = pMid; + + for(sal_uInt32 b(1); b < nMarkerCount - 1; b++) + { + add_singleMarker(rTarget, aMarkerPrimitives, aMarkerTransform, aClipRange, *pPrepared, aCandidate, b); + } + } + } + + if(pEnd) + { + if(pEnd == pPrepared || prepare_singleMarker(aMarkerPrimitives, aMarkerTransform, aClipRange, *pEnd)) + { + pPrepared = pEnd; + add_singleMarker(rTarget, aMarkerPrimitives, aMarkerTransform, aClipRange, *pPrepared, aCandidate, nMarkerCount - 1); + } + } + } + } + } + } + + void SvgStyleAttributes::add_path( + const basegfx::B2DPolyPolygon& rPath, + drawinglayer::primitive2d::Primitive2DSequence& rTarget) const + { + const bool bIsLine(1 == rPath.count() + && !rPath.areControlPointsUsed() + && 2 == rPath.getB2DPolygon(0).count()); + + if(!rPath.count()) + { + return; + } + + const basegfx::B2DRange aGeoRange(rPath.getB2DRange()); + + if(aGeoRange.isEmpty()) + { + return; + } + + if(!bIsLine && // not for lines + (basegfx::fTools::equalZero(aGeoRange.getWidth()) + || basegfx::fTools::equalZero(aGeoRange.getHeight()))) + { + return; + } + + const double fOpacity(getOpacity().getNumber()); + + if(basegfx::fTools::equalZero(fOpacity)) + { + return; + } + + if(!bIsLine) + { + basegfx::B2DPolyPolygon aPath(rPath); + const bool bNeedToCheckClipRule(SVGTokenPath == mrOwner.getType() || SVGTokenPolygon == mrOwner.getType()); + const bool bClipPathIsNonzero(!bIsLine && bNeedToCheckClipRule && mbIsClipPathContent && mbClipRule); + const bool bFillRuleIsNonzero(!bIsLine && bNeedToCheckClipRule && !mbIsClipPathContent && getFillRule()); + + if(bClipPathIsNonzero || bFillRuleIsNonzero) + { + // nonzero is wanted, solve geometrically (see description on basegfx) + aPath = basegfx::tools::createNonzeroConform(aPath); + } + + add_fill(aPath, rTarget, aGeoRange); + } + + add_stroke(rPath, rTarget, aGeoRange); + + // Svg supports markers for path, polygon, polyline and line + if(SVGTokenPath == mrOwner.getType() || // path + SVGTokenPolygon == mrOwner.getType() || // polygon, polyline + SVGTokenLine == mrOwner.getType()) // line + { + // try to add markers + add_markers(rPath, rTarget); + } + } + + void SvgStyleAttributes::add_postProcess( + drawinglayer::primitive2d::Primitive2DSequence& rTarget, + const drawinglayer::primitive2d::Primitive2DSequence& rSource, + const basegfx::B2DHomMatrix* pTransform) const + { + if(rSource.hasElements()) + { + const double fOpacity(getOpacity().getNumber()); + + if(basegfx::fTools::equalZero(fOpacity)) + { + return; + } + + drawinglayer::primitive2d::Primitive2DSequence aSource(rSource); + + if(basegfx::fTools::less(fOpacity, 1.0)) + { + // embed in UnifiedTransparencePrimitive2D + const drawinglayer::primitive2d::Primitive2DReference xRef( + new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( + aSource, + 1.0 - fOpacity)); + + aSource = drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1); + } + + if(getClipPathXLink().getLength()) + { + // try to access linked ClipPath + const SvgClipPathNode* mpClip = dynamic_cast< const SvgClipPathNode* >(mrOwner.getDocument().findSvgNodeById(getClipPathXLink())); + + if(mpClip) + { + mpClip->apply(aSource); + } + } + + if(aSource.hasElements()) // test again, applied clipPath may have lead to empty geometry + { + if(getMaskXLink().getLength()) + { + // try to access linked Mask + const SvgMaskNode* mpMask = dynamic_cast< const SvgMaskNode* >(mrOwner.getDocument().findSvgNodeById(getMaskXLink())); + + if(mpMask) + { + mpMask->apply(aSource); + } + } + + if(aSource.hasElements()) // test again, applied mask may have lead to empty geometry + { + if(pTransform) + { + // create embedding group element with transformation + const drawinglayer::primitive2d::Primitive2DReference xRef( + new drawinglayer::primitive2d::TransformPrimitive2D( + *pTransform, + aSource)); + + aSource = drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1); + } + + // append to current target + drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aSource); + } + } + } + } + + SvgStyleAttributes::SvgStyleAttributes(SvgNode& rOwner) + : mrOwner(rOwner), + mpCssStyleParent(0), + maFill(), + maStroke(), + maStopColor(basegfx::BColor(0.0, 0.0, 0.0), true), + maStrokeWidth(), + maStopOpacity(), + mpSvgGradientNodeFill(0), + mpSvgGradientNodeStroke(0), + mpSvgPatternNodeFill(0), + mpSvgPatternNodeStroke(0), + maFillOpacity(), + maStrokeDasharray(), + maStrokeDashOffset(), + maStrokeLinecap(StrokeLinecap_notset), + maStrokeLinejoin(StrokeLinejoin_notset), + maStrokeMiterLimit(), + maStrokeOpacity(), + maFontFamily(), + maFontSize(), + maFontStretch(FontStretch_notset), + maFontStyle(FontStyle_notset), + maFontVariant(FontVariant_notset), + maFontWeight(FontWeight_notset), + maTextAlign(TextAlign_notset), + maTextDecoration(TextDecoration_notset), + maTextAnchor(TextAnchor_notset), + maColor(), + maOpacity(1.0), + maClipPathXLink(), + maMaskXLink(), + maMarkerStartXLink(), + mpMarkerStartXLink(0), + maMarkerMidXLink(), + mpMarkerMidXLink(0), + maMarkerEndXLink(), + mpMarkerEndXLink(0), + maFillRule(true), + maFillRuleSet(false), + mbIsClipPathContent(SVGTokenClipPathNode == mrOwner.getType()), + mbClipRule(true) + { + if(!mbIsClipPathContent) + { + const SvgStyleAttributes* pParentStyle = getParentStyle(); + + if(pParentStyle) + { + mbIsClipPathContent = pParentStyle->mbIsClipPathContent; + } + } + } + + SvgStyleAttributes::~SvgStyleAttributes() + { + } + + void SvgStyleAttributes::parseStyleAttribute(const rtl::OUString& /*rTokenName*/, SVGToken aSVGToken, const rtl::OUString& aContent) + { + switch(aSVGToken) + { + case SVGTokenFill: + { + SvgPaint aSvgPaint; + rtl::OUString aURL; + + if(readSvgPaint(aContent, aSvgPaint, aURL)) + { + setFill(aSvgPaint); + } + else if(aURL.getLength()) + { + const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(aURL); + + if(pNode) + { + if(SVGTokenLinearGradient == pNode->getType() || SVGTokenRadialGradient == pNode->getType()) + { + setSvgGradientNodeFill(static_cast< const SvgGradientNode* >(pNode)); + } + else if(SVGTokenPattern == pNode->getType()) + { + setSvgPatternNodeFill(static_cast< const SvgPatternNode* >(pNode)); + } + } + } + break; + } + case SVGTokenFillOpacity: + { + SvgNumber aNum; + + if(readSingleNumber(aContent, aNum)) + { + if(aNum.isPositive()) + { + setFillOpacity(aNum); + } + } + break; + } + case SVGTokenFillRule: + { + if(aContent.getLength()) + { + if(aContent.match(commonStrings::aStrNonzero)) + { + maFillRule = true; + maFillRuleSet = true; + } + else if(aContent.match(commonStrings::aStrEvenOdd)) + { + maFillRule = false; + maFillRuleSet = true; + } + } + break; + } + case SVGTokenStroke: + { + SvgPaint aSvgPaint; + rtl::OUString aURL; + + if(readSvgPaint(aContent, aSvgPaint, aURL)) + { + setStroke(aSvgPaint); + } + else if(aURL.getLength()) + { + const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(aURL); + + if(pNode) + { + if(SVGTokenLinearGradient == pNode->getType() || SVGTokenRadialGradient == pNode->getType()) + { + setSvgGradientNodeStroke(static_cast< const SvgGradientNode* >(pNode)); + } + else if(SVGTokenPattern == pNode->getType()) + { + setSvgPatternNodeStroke(static_cast< const SvgPatternNode* >(pNode)); + } + } + } + break; + } + case SVGTokenStrokeDasharray: + { + if(aContent.getLength()) + { + SvgNumberVector aVector; + + if(readSvgNumberVector(aContent, aVector)) + { + setStrokeDasharray(aVector); + } + } + break; + } + case SVGTokenStrokeDashoffset: + { + SvgNumber aNum; + + if(readSingleNumber(aContent, aNum)) + { + if(aNum.isPositive()) + { + setStrokeDashOffset(aNum); + } + } + break; + } + case SVGTokenStrokeLinecap: + { + if(aContent.getLength()) + { + static rtl::OUString aStrButt(rtl::OUString::createFromAscii("butt")); + static rtl::OUString aStrRound(rtl::OUString::createFromAscii("round")); + static rtl::OUString aStrSquare(rtl::OUString::createFromAscii("square")); + + if(aContent.match(aStrButt)) + { + setStrokeLinecap(StrokeLinecap_butt); + } + else if(aContent.match(aStrRound)) + { + setStrokeLinecap(StrokeLinecap_round); + } + else if(aContent.match(aStrSquare)) + { + setStrokeLinecap(StrokeLinecap_square); + } + } + break; + } + case SVGTokenStrokeLinejoin: + { + if(aContent.getLength()) + { + static rtl::OUString aStrMiter(rtl::OUString::createFromAscii("miter")); + static rtl::OUString aStrRound(rtl::OUString::createFromAscii("round")); + static rtl::OUString aStrBevel(rtl::OUString::createFromAscii("bevel")); + + if(aContent.match(aStrMiter)) + { + setStrokeLinejoin(StrokeLinejoin_miter); + } + else if(aContent.match(aStrRound)) + { + setStrokeLinejoin(StrokeLinejoin_round); + } + else if(aContent.match(aStrBevel)) + { + setStrokeLinejoin(StrokeLinejoin_bevel); + } + } + break; + } + case SVGTokenStrokeMiterlimit: + { + SvgNumber aNum; + + if(readSingleNumber(aContent, aNum)) + { + if(aNum.isPositive()) + { + setStrokeMiterLimit(aNum); + } + } + break; + } + case SVGTokenStrokeOpacity: + { + SvgNumber aNum; + + if(readSingleNumber(aContent, aNum)) + { + if(aNum.isPositive()) + { + setStrokeOpacity(aNum); + } + } + break; + } + case SVGTokenStrokeWidth: + { + SvgNumber aNum; + + if(readSingleNumber(aContent, aNum)) + { + if(aNum.isPositive()) + { + setStrokeWidth(aNum); + } + } + break; + } + case SVGTokenStopColor: + { + SvgPaint aSvgPaint; + rtl::OUString aURL; + + if(readSvgPaint(aContent, aSvgPaint, aURL)) + { + setStopColor(aSvgPaint); + } + break; + } + case SVGTokenStopOpacity: + { + SvgNumber aNum; + + if(readSingleNumber(aContent, aNum)) + { + if(aNum.isPositive()) + { + setStopOpacity(aNum); + } + } + break; + } + case SVGTokenFont: + { + break; + } + case SVGTokenFontFamily: + { + SvgStringVector aSvgStringVector; + + if(readSvgStringVector(aContent, aSvgStringVector)) + { + setFontFamily(aSvgStringVector); + } + break; + } + case SVGTokenFontSize: + { + SvgNumber aNum; + + if(readSingleNumber(aContent, aNum)) + { + setFontSize(aNum); + } + break; + } + case SVGTokenFontSizeAdjust: + { + break; + } + case SVGTokenFontStretch: + { + if(aContent.getLength()) + { + static rtl::OUString aStrNormal(rtl::OUString::createFromAscii("normal")); + static rtl::OUString aStrWider(rtl::OUString::createFromAscii("wider")); + static rtl::OUString aStrNarrower(rtl::OUString::createFromAscii("narrower")); + static rtl::OUString aStrUltra_condensed(rtl::OUString::createFromAscii("ultra-condensed")); + static rtl::OUString aStrExtra_condensed(rtl::OUString::createFromAscii("extra-condensed")); + static rtl::OUString aStrCondensed(rtl::OUString::createFromAscii("condensed")); + static rtl::OUString aStrSemi_condensed(rtl::OUString::createFromAscii("semi-condensed")); + static rtl::OUString aStrSemi_expanded(rtl::OUString::createFromAscii("semi-expanded")); + static rtl::OUString aStrExpanded(rtl::OUString::createFromAscii("expanded")); + static rtl::OUString aStrExtra_expanded(rtl::OUString::createFromAscii("extra-expanded")); + static rtl::OUString aStrUltra_expanded(rtl::OUString::createFromAscii("ultra-expanded")); + + if(aContent.match(aStrNormal)) + { + setFontStretch(FontStretch_normal); + } + else if(aContent.match(aStrWider)) + { + setFontStretch(FontStretch_wider); + } + else if(aContent.match(aStrNarrower)) + { + setFontStretch(FontStretch_narrower); + } + else if(aContent.match(aStrUltra_condensed)) + { + setFontStretch(FontStretch_ultra_condensed); + } + else if(aContent.match(aStrExtra_condensed)) + { + setFontStretch(FontStretch_extra_condensed); + } + else if(aContent.match(aStrCondensed)) + { + setFontStretch(FontStretch_condensed); + } + else if(aContent.match(aStrSemi_condensed)) + { + setFontStretch(FontStretch_semi_condensed); + } + else if(aContent.match(aStrSemi_expanded)) + { + setFontStretch(FontStretch_semi_expanded); + } + else if(aContent.match(aStrExpanded)) + { + setFontStretch(FontStretch_expanded); + } + else if(aContent.match(aStrExtra_expanded)) + { + setFontStretch(FontStretch_extra_expanded); + } + else if(aContent.match(aStrUltra_expanded)) + { + setFontStretch(FontStretch_ultra_expanded); + } + } + break; + } + case SVGTokenFontStyle: + { + if(aContent.getLength()) + { + static rtl::OUString aStrNormal(rtl::OUString::createFromAscii("normal")); + static rtl::OUString aStrItalic(rtl::OUString::createFromAscii("italic")); + static rtl::OUString aStrOblique(rtl::OUString::createFromAscii("oblique")); + + if(aContent.match(aStrNormal)) + { + setFontStyle(FontStyle_normal); + } + else if(aContent.match(aStrItalic)) + { + setFontStyle(FontStyle_italic); + } + else if(aContent.match(aStrOblique)) + { + setFontStyle(FontStyle_oblique); + } + } + break; + } + case SVGTokenFontVariant: + { + if(aContent.getLength()) + { + static rtl::OUString aStrNormal(rtl::OUString::createFromAscii("normal")); + static rtl::OUString aStrSmallCaps(rtl::OUString::createFromAscii("small-caps")); + + if(aContent.match(aStrNormal)) + { + setFontVariant(FontVariant_normal); + } + else if(aContent.match(aStrSmallCaps)) + { + setFontVariant(FontVariant_small_caps); + } + } + break; + } + case SVGTokenFontWeight: + { + if(aContent.getLength()) + { + static rtl::OUString aStrNormal(rtl::OUString::createFromAscii("normal")); + static rtl::OUString aStrBold(rtl::OUString::createFromAscii("bold")); + static rtl::OUString aStrBolder(rtl::OUString::createFromAscii("bolder")); + static rtl::OUString aStrLighter(rtl::OUString::createFromAscii("lighter")); + static rtl::OUString aStr100(rtl::OUString::createFromAscii("100")); + static rtl::OUString aStr200(rtl::OUString::createFromAscii("200")); + static rtl::OUString aStr300(rtl::OUString::createFromAscii("300")); + static rtl::OUString aStr400(rtl::OUString::createFromAscii("400")); + static rtl::OUString aStr500(rtl::OUString::createFromAscii("500")); + static rtl::OUString aStr600(rtl::OUString::createFromAscii("600")); + static rtl::OUString aStr700(rtl::OUString::createFromAscii("700")); + static rtl::OUString aStr800(rtl::OUString::createFromAscii("800")); + static rtl::OUString aStr900(rtl::OUString::createFromAscii("900")); + + if(aContent.match(aStr100)) + { + setFontWeight(FontWeight_100); + } + else if(aContent.match(aStr200)) + { + setFontWeight(FontWeight_200); + } + else if(aContent.match(aStr300)) + { + setFontWeight(FontWeight_300); + } + else if(aContent.match(aStr400) || aContent.match(aStrNormal)) + { + setFontWeight(FontWeight_400); + } + else if(aContent.match(aStr500)) + { + setFontWeight(FontWeight_500); + } + else if(aContent.match(aStr600)) + { + setFontWeight(FontWeight_600); + } + else if(aContent.match(aStr700) || aContent.match(aStrBold)) + { + setFontWeight(FontWeight_700); + } + else if(aContent.match(aStr800)) + { + setFontWeight(FontWeight_800); + } + else if(aContent.match(aStr900)) + { + setFontWeight(FontWeight_900); + } + else if(aContent.match(aStrBolder)) + { + setFontWeight(FontWeight_bolder); + } + else if(aContent.match(aStrLighter)) + { + setFontWeight(FontWeight_lighter); + } + } + break; + } + case SVGTokenDirection: + { + break; + } + case SVGTokenLetterSpacing: + { + break; + } + case SVGTokenTextDecoration: + { + if(aContent.getLength()) + { + static rtl::OUString aStrNone(rtl::OUString::createFromAscii("none")); + static rtl::OUString aStrUnderline(rtl::OUString::createFromAscii("underline")); + static rtl::OUString aStrOverline(rtl::OUString::createFromAscii("overline")); + static rtl::OUString aStrLineThrough(rtl::OUString::createFromAscii("line-through")); + static rtl::OUString aStrBlink(rtl::OUString::createFromAscii("blink")); + + if(aContent.match(aStrNone)) + { + setTextDecoration(TextDecoration_none); + } + else if(aContent.match(aStrUnderline)) + { + setTextDecoration(TextDecoration_underline); + } + else if(aContent.match(aStrOverline)) + { + setTextDecoration(TextDecoration_overline); + } + else if(aContent.match(aStrLineThrough)) + { + setTextDecoration(TextDecoration_line_through); + } + else if(aContent.match(aStrBlink)) + { + setTextDecoration(TextDecoration_blink); + } + } + break; + } + case SVGTokenUnicodeBidi: + { + break; + } + case SVGTokenWordSpacing: + { + break; + } + case SVGTokenTextAnchor: + { + if(aContent.getLength()) + { + static rtl::OUString aStrStart(rtl::OUString::createFromAscii("start")); + static rtl::OUString aStrMiddle(rtl::OUString::createFromAscii("middle")); + static rtl::OUString aStrEnd(rtl::OUString::createFromAscii("end")); + + if(aContent.match(aStrStart)) + { + setTextAnchor(TextAnchor_start); + } + else if(aContent.match(aStrMiddle)) + { + setTextAnchor(TextAnchor_middle); + } + else if(aContent.match(aStrEnd)) + { + setTextAnchor(TextAnchor_end); + } + } + break; + } + case SVGTokenTextAlign: + { + if(aContent.getLength()) + { + static rtl::OUString aStrLeft(rtl::OUString::createFromAscii("left")); + static rtl::OUString aStrRight(rtl::OUString::createFromAscii("right")); + static rtl::OUString aStrCenter(rtl::OUString::createFromAscii("center")); + static rtl::OUString aStrJustify(rtl::OUString::createFromAscii("justify")); + + if(aContent.match(aStrLeft)) + { + setTextAlign(TextAlign_left); + } + else if(aContent.match(aStrRight)) + { + setTextAlign(TextAlign_right); + } + else if(aContent.match(aStrCenter)) + { + setTextAlign(TextAlign_center); + } + else if(aContent.match(aStrJustify)) + { + setTextAlign(TextAlign_justify); + } + } + break; + } + case SVGTokenColor: + { + SvgPaint aSvgPaint; + rtl::OUString aURL; + + if(readSvgPaint(aContent, aSvgPaint, aURL)) + { + setColor(aSvgPaint); + } + break; + } + case SVGTokenOpacity: + { + SvgNumber aNum; + + if(readSingleNumber(aContent, aNum)) + { + setOpacity(SvgNumber(basegfx::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet())); + } + break; + } + case SVGTokenClipPathProperty: + { + readLocalUrl(aContent, maClipPathXLink); + break; + } + case SVGTokenMask: + { + readLocalUrl(aContent, maMaskXLink); + break; + } + case SVGTokenClipRule: + { + if(aContent.getLength()) + { + if(aContent.match(commonStrings::aStrNonzero)) + { + mbClipRule = true; + } + else if(aContent.match(commonStrings::aStrEvenOdd)) + { + mbClipRule = false; + } + } + break; + } + case SVGTokenMarker: + { + readLocalUrl(aContent, maMarkerEndXLink); + maMarkerStartXLink = maMarkerMidXLink = maMarkerEndXLink; + break; + } + case SVGTokenMarkerStart: + { + readLocalUrl(aContent, maMarkerStartXLink); + break; + } + case SVGTokenMarkerMid: + { + readLocalUrl(aContent, maMarkerMidXLink); + break; + } + case SVGTokenMarkerEnd: + { + readLocalUrl(aContent, maMarkerEndXLink); + break; + } + default: + { + break; + } + } + } + + const basegfx::BColor* SvgStyleAttributes::getFill() const + { + if(mbIsClipPathContent) + { + static basegfx::BColor aBlack(0.0, 0.0, 0.0); + + return &aBlack; + } + else if(maFill.isSet()) + { + if(maFill.isCurrent()) + { + return getColor(); + } + else if(maFill.isOn()) + { + return &maFill.getBColor(); + } + } + else + { + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getFill(); + } + } + + return 0; + } + + const basegfx::BColor* SvgStyleAttributes::getStroke() const + { + if(mbIsClipPathContent) + { + return 0; + } + else if(maStroke.isSet()) + { + if(maStroke.isCurrent()) + { + return getColor(); + } + else if(maStroke.isOn()) + { + return &maStroke.getBColor(); + } + } + else + { + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getStroke(); + } + } + + return 0; + } + + const basegfx::BColor& SvgStyleAttributes::getStopColor() const + { + if(maStopColor.isCurrent()) + { + return *getColor(); + } + else + { + return maStopColor.getBColor(); + } + } + + const SvgGradientNode* SvgStyleAttributes::getSvgGradientNodeFill() const + { + if(mbIsClipPathContent) + { + return 0; + } + else if(mpSvgGradientNodeFill) + { + return mpSvgGradientNodeFill; + } + else + { + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getSvgGradientNodeFill(); + } + } + + return 0; + } + + const SvgGradientNode* SvgStyleAttributes::getSvgGradientNodeStroke() const + { + if(mbIsClipPathContent) + { + return 0; + } + else if(mpSvgGradientNodeStroke) + { + return mpSvgGradientNodeStroke; + } + else + { + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getSvgGradientNodeStroke(); + } + } + + return 0; + } + + const SvgPatternNode* SvgStyleAttributes::getSvgPatternNodeFill() const + { + if(mbIsClipPathContent) + { + return 0; + } + else if(mpSvgPatternNodeFill) + { + return mpSvgPatternNodeFill; + } + else + { + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getSvgPatternNodeFill(); + } + } + + return 0; + } + + const SvgPatternNode* SvgStyleAttributes::getSvgPatternNodeStroke() const + { + if(mbIsClipPathContent) + { + return 0; + } + else if(mpSvgPatternNodeStroke) + { + return mpSvgPatternNodeStroke; + } + else + { + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getSvgPatternNodeStroke(); + } + } + + return 0; + } + + const SvgNumber SvgStyleAttributes::getStrokeWidth() const + { + if(mbIsClipPathContent) + { + return SvgNumber(0.0); + } + else if(maStrokeWidth.isSet()) + { + return maStrokeWidth; + } + + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getStrokeWidth(); + } + + // default is 1 + return SvgNumber(1.0); + } + + const SvgNumber SvgStyleAttributes::getStopOpacity() const + { + if(maStopOpacity.isSet()) + { + return maStopOpacity; + } + + // default is 1 + return SvgNumber(1.0); + } + + const SvgNumber SvgStyleAttributes::getFillOpacity() const + { + if(mbIsClipPathContent) + { + return SvgNumber(1.0); + } + else if(maFillOpacity.isSet()) + { + return maFillOpacity; + } + + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getFillOpacity(); + } + + // default is 1 + return SvgNumber(1.0); + } + + bool SvgStyleAttributes::getFillRule() const + { + if(maFillRuleSet) + { + return maFillRule; + } + + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getFillRule(); + } + + // default is NonZero + return true; + } + + void SvgStyleAttributes::setFillRule(const bool* pFillRule) + { + if(pFillRule) + { + maFillRuleSet = true; + maFillRule = *pFillRule; + } + else + { + maFillRuleSet = false; + } + } + + const SvgNumberVector& SvgStyleAttributes::getStrokeDasharray() const + { + if(!maStrokeDasharray.empty()) + { + return maStrokeDasharray; + } + + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getStrokeDasharray(); + } + + // default empty + return maStrokeDasharray; + } + + const SvgNumber SvgStyleAttributes::getStrokeDashOffset() const + { + if(maStrokeDashOffset.isSet()) + { + return maStrokeDashOffset; + } + + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getStrokeDashOffset(); + } + + // default is 0 + return SvgNumber(0.0); + } + + StrokeLinecap SvgStyleAttributes::getStrokeLinecap() const + { + if(maStrokeLinecap != StrokeLinecap_notset) + { + return maStrokeLinecap; + } + + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getStrokeLinecap(); + } + + // default is StrokeLinecap_butt + return StrokeLinecap_butt; + } + + StrokeLinejoin SvgStyleAttributes::getStrokeLinejoin() const + { + if(maStrokeLinejoin != StrokeLinejoin_notset) + { + return maStrokeLinejoin; + } + + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getStrokeLinejoin(); + } + + // default is StrokeLinejoin_butt + return StrokeLinejoin_miter; + } + + const SvgNumber SvgStyleAttributes::getStrokeMiterLimit() const + { + if(maStrokeMiterLimit.isSet()) + { + return maStrokeMiterLimit; + } + + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getStrokeMiterLimit(); + } + + // default is 4 + return SvgNumber(4.0); + } + + const SvgNumber SvgStyleAttributes::getStrokeOpacity() const + { + if(maStrokeOpacity.isSet()) + { + return maStrokeOpacity; + } + + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getStrokeOpacity(); + } + + // default is 1 + return SvgNumber(1.0); + } + + const SvgStringVector& SvgStyleAttributes::getFontFamily() const + { + if(!maFontFamily.empty()) + { + return maFontFamily; + } + + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getFontFamily(); + } + + // default is empty + return maFontFamily; + } + + const SvgNumber SvgStyleAttributes::getFontSize() const + { + if(maFontSize.isSet()) + { + return maFontSize; + } + + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getFontSize(); + } + + // default is 'medium' + return SvgNumber(12.0); + } + + FontStretch SvgStyleAttributes::getFontStretch() const + { + if(maFontStretch != FontStretch_notset) + { + if(FontStretch_wider != maFontStretch && FontStretch_narrower != maFontStretch) + { + return maFontStretch; + } + } + + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + FontStretch aInherited = pSvgStyleAttributes->getFontStretch(); + + 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 = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getFontStyle(); + } + + // 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 = getParentStyle(); + + if(pSvgStyleAttributes) + { + FontWeight aInherited = pSvgStyleAttributes->getFontWeight(); + + if(FontWeight_bolder == maFontWeight) + { + aInherited = getBolder(aInherited); + } + else if(FontWeight_lighter == maFontWeight) + { + aInherited = getLighter(aInherited); + } + + return aInherited; + } + + // default is FontWeight_400 (FontWeight_normal) + return FontWeight_400; + } + + TextAlign SvgStyleAttributes::getTextAlign() const + { + if(maTextAlign != TextAlign_notset) + { + return maTextAlign; + } + + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getTextAlign(); + } + + // default is TextAlign_left + return TextAlign_left; + } + + const SvgStyleAttributes* SvgStyleAttributes::getTextDecorationDefiningSvgStyleAttributes() const + { + if(maTextDecoration != TextDecoration_notset) + { + return this; + } + + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getTextDecorationDefiningSvgStyleAttributes(); + } + + // default is 0 + return 0; + } + + 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 = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getTextAnchor(); + } + + // 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 0; + } + else if(maColor.isOn()) + { + return &maColor.getBColor(); + } + } + else + { + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getColor(); + } + } + + return 0; + } + + const rtl::OUString SvgStyleAttributes::getMarkerStartXLink() const + { + if(maMarkerStartXLink.getLength()) + { + return maMarkerStartXLink; + } + + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getMarkerStartXLink(); + } + + return rtl::OUString(); + } + + const SvgMarkerNode* SvgStyleAttributes::accessMarkerStartXLink() const + { + if(!mpMarkerStartXLink) + { + const rtl::OUString aMarker(getMarkerStartXLink()); + + if(aMarker.getLength()) + { + const_cast< SvgStyleAttributes* >(this)->mpMarkerStartXLink = dynamic_cast< const SvgMarkerNode* >(mrOwner.getDocument().findSvgNodeById(getMarkerStartXLink())); + } + } + + return mpMarkerStartXLink; + } + + const rtl::OUString SvgStyleAttributes::getMarkerMidXLink() const + { + if(maMarkerMidXLink.getLength()) + { + return maMarkerMidXLink; + } + + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getMarkerMidXLink(); + } + + return rtl::OUString(); + } + + const SvgMarkerNode* SvgStyleAttributes::accessMarkerMidXLink() const + { + if(!mpMarkerMidXLink) + { + const rtl::OUString aMarker(getMarkerMidXLink()); + + if(aMarker.getLength()) + { + const_cast< SvgStyleAttributes* >(this)->mpMarkerMidXLink = dynamic_cast< const SvgMarkerNode* >(mrOwner.getDocument().findSvgNodeById(getMarkerMidXLink())); + } + } + + return mpMarkerMidXLink; + } + + const rtl::OUString SvgStyleAttributes::getMarkerEndXLink() const + { + if(maMarkerEndXLink.getLength()) + { + return maMarkerEndXLink; + } + + const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle(); + + if(pSvgStyleAttributes) + { + return pSvgStyleAttributes->getMarkerEndXLink(); + } + + return rtl::OUString(); + } + + const SvgMarkerNode* SvgStyleAttributes::accessMarkerEndXLink() const + { + if(!mpMarkerEndXLink) + { + const rtl::OUString aMarker(getMarkerEndXLink()); + + if(aMarker.getLength()) + { + const_cast< SvgStyleAttributes* >(this)->mpMarkerEndXLink = dynamic_cast< const SvgMarkerNode* >(mrOwner.getDocument().findSvgNodeById(getMarkerEndXLink())); + } + } + + return mpMarkerEndXLink; + } + + } // end of namespace svgreader +} // end of namespace svgio + +////////////////////////////////////////////////////////////////////////////// +// eof + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |