Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/svgio/source/svgreader/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 128 kB image not shown  

Quelle  svgstyleattributes.cxx   Sprache: 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;
                defaultbreak;
            }

            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;
                defaultbreak;
            }

            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;
                defaultbreak;
            }

            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;
                defaultbreak;
            }

            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;
                defaultbreak;
            }

            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),
            maFontDirection(FontDirection::notset),
            maUnicodeBidi(UnicodeBidi::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(36, 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;
                }
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=94 H=96 G=94

¤ Dauer der Verarbeitung: 0.25 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.