diff options
Diffstat (limited to 'svgio/source/svgreader/svgsvgnode.cxx')
-rw-r--r-- | svgio/source/svgreader/svgsvgnode.cxx | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/svgio/source/svgreader/svgsvgnode.cxx b/svgio/source/svgreader/svgsvgnode.cxx new file mode 100644 index 000000000000..ecf74d9bb4a6 --- /dev/null +++ b/svgio/source/svgreader/svgsvgnode.cxx @@ -0,0 +1,423 @@ +/* -*- 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/svgsvgnode.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace svgio +{ + namespace svgreader + { + SvgSvgNode::SvgSvgNode( + SvgDocument& rDocument, + SvgNode* pParent) + : SvgNode(SVGTokenSvg, rDocument, pParent), + maSvgStyleAttributes(*this), + mpViewBox(0), + maSvgAspectRatio(), + maX(), + maY(), + maWidth(), + maHeight(), + maVersion() + { + if(!getParent()) + { + // initial fill is black + maSvgStyleAttributes.setFill(SvgPaint(basegfx::BColor(0.0, 0.0, 0.0), true, true)); + } + } + + SvgSvgNode::~SvgSvgNode() + { + if(mpViewBox) delete mpViewBox; + } + + const SvgStyleAttributes* SvgSvgNode::getSvgStyleAttributes() const + { + return &maSvgStyleAttributes; + } + + void SvgSvgNode::parseAttribute(const rtl::OUString& rTokenName, SVGToken aSVGToken, const rtl::OUString& aContent) + { + // call parent + SvgNode::parseAttribute(rTokenName, aSVGToken, aContent); + + // read style attributes + maSvgStyleAttributes.parseStyleAttribute(rTokenName, aSVGToken, aContent); + + // parse own + switch(aSVGToken) + { + case SVGTokenStyle: + { + maSvgStyleAttributes.readStyle(aContent); + break; + } + case SVGTokenViewBox: + { + const basegfx::B2DRange aRange(readViewBox(aContent, *this)); + + if(!aRange.isEmpty()) + { + setViewBox(&aRange); + } + break; + } + case SVGTokenPreserveAspectRatio: + { + setSvgAspectRatio(readSvgAspectRatio(aContent)); + break; + } + case SVGTokenX: + { + SvgNumber aNum; + + if(readSingleNumber(aContent, aNum)) + { + setX(aNum); + } + break; + } + case SVGTokenY: + { + SvgNumber aNum; + + if(readSingleNumber(aContent, aNum)) + { + setY(aNum); + } + break; + } + case SVGTokenWidth: + { + SvgNumber aNum; + + if(readSingleNumber(aContent, aNum)) + { + if(aNum.isPositive()) + { + setWidth(aNum); + } + } + break; + } + case SVGTokenHeight: + { + SvgNumber aNum; + + if(readSingleNumber(aContent, aNum)) + { + if(aNum.isPositive()) + { + setHeight(aNum); + } + } + break; + } + case SVGTokenVersion: + { + SvgNumber aNum; + + if(readSingleNumber(aContent, aNum)) + { + setVersion(aNum); + } + break; + } + default: + { + break; + } + } + } + + void SvgSvgNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DSequence& rTarget, bool bReferenced) const + { + drawinglayer::primitive2d::Primitive2DSequence aSequence; + + // decompose childs + SvgNode::decomposeSvgNode(aSequence, bReferenced); + + if(aSequence.hasElements()) + { + if(getParent()) + { + if(getViewBox()) + { + // Svg defines that with no width or no height the viewBox content is empty, + // so both need to exist + if(!basegfx::fTools::equalZero(getViewBox()->getWidth()) && !basegfx::fTools::equalZero(getViewBox()->getHeight())) + { + // create target range homing x,y, width and height as given + const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0); + const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0); + const double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : getViewBox()->getWidth()); + const double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : getViewBox()->getHeight()); + const basegfx::B2DRange aTarget(fX, fY, fX + fW, fY + fH); + + if(aTarget.equal(*getViewBox())) + { + // no mapping needed, append + drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aSequence); + } + else + { + // create mapping + const SvgAspectRatio& rRatio = getSvgAspectRatio(); + + if(rRatio.isSet()) + { + // let mapping be created from SvgAspectRatio + const basegfx::B2DHomMatrix aEmbeddingTransform( + rRatio.createMapping(aTarget, *getViewBox())); + + // prepare embedding in transformation + const drawinglayer::primitive2d::Primitive2DReference xRef( + new drawinglayer::primitive2d::TransformPrimitive2D( + aEmbeddingTransform, + aSequence)); + + if(rRatio.isMeetOrSlice()) + { + // embed in transformation + drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xRef); + } + else + { + // need to embed in MaskPrimitive2D, too + const drawinglayer::primitive2d::Primitive2DReference xMask( + new drawinglayer::primitive2d::MaskPrimitive2D( + basegfx::B2DPolyPolygon(basegfx::tools::createPolygonFromRect(aTarget)), + drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1))); + + drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xMask); + } + } + else + { + // choose default mapping + const basegfx::B2DHomMatrix aEmbeddingTransform( + rRatio.createLinearMapping( + aTarget, *getViewBox())); + + // embed in transformation + const drawinglayer::primitive2d::Primitive2DReference xTransform( + new drawinglayer::primitive2d::TransformPrimitive2D( + aEmbeddingTransform, + aSequence)); + + drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xTransform); + } + } + } + } + else + { + // check if we have a size + const double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : 0.0); + const double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : 0.0); + + // Svg defines that a negative value is an error and that 0.0 disables rendering + if(basegfx::fTools::more(fW, 0.0) && basegfx::fTools::more(fH, 0.0)) + { + // check if we have a x,y position + const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0); + const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0); + + if(!basegfx::fTools::equalZero(fX) || !basegfx::fTools::equalZero(fY)) + { + // embed in transform + const drawinglayer::primitive2d::Primitive2DReference xRef( + new drawinglayer::primitive2d::TransformPrimitive2D( + basegfx::tools::createTranslateB2DHomMatrix(fX, fY), + aSequence)); + + aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1); + } + + // embed in MaskPrimitive2D to clip + const drawinglayer::primitive2d::Primitive2DReference xMask( + new drawinglayer::primitive2d::MaskPrimitive2D( + basegfx::B2DPolyPolygon( + basegfx::tools::createPolygonFromRect( + basegfx::B2DRange(fX, fY, fX + fW, fY + fH))), + aSequence)); + + // append + drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xMask); + } + } + } + else + { + // Outermost SVG element; create target range homing width and height as given. + // SVG defines that x,y has no meanig for the outermost SVG element. Use a fallback + // width and height of din A 4 (21 x 29,7 cm) + double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : (210.0 * 3.543307)); + double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : (297.0 * 3.543307)); + + // Svg defines that a negative value is an error and that 0.0 disables rendering + if(basegfx::fTools::more(fW, 0.0) && basegfx::fTools::more(fH, 0.0)) + { + const basegfx::B2DRange aSvgCanvasRange(0.0, 0.0, fW, fH); + + if(getViewBox()) + { + if(!basegfx::fTools::equalZero(getViewBox()->getWidth()) && !basegfx::fTools::equalZero(getViewBox()->getHeight())) + { + // create mapping + const SvgAspectRatio& rRatio = getSvgAspectRatio(); + basegfx::B2DHomMatrix aViewBoxMapping; + + if(rRatio.isSet()) + { + // let mapping be created from SvgAspectRatio + aViewBoxMapping = rRatio.createMapping(aSvgCanvasRange, *getViewBox()); + + // no need to check ratio here for slice, the outermost Svg will + // be clipped anyways (see below) + } + else + { + // choose default mapping + aViewBoxMapping = rRatio.createLinearMapping(aSvgCanvasRange, *getViewBox()); + } + + // scale content to viewBox definitions + const drawinglayer::primitive2d::Primitive2DReference xTransform( + new drawinglayer::primitive2d::TransformPrimitive2D( + aViewBoxMapping, + aSequence)); + + aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1); + } + } + + // to be completely correct in Svg sense it is necessary to clip + // the whole content to the given canvas. I choose here to do this + // initially despite I found various examples of Svg files out there + // which have no correct values for this clipping. It's correct + // due to the Svg spec. + bool bDoCorrectCanvasClipping(true); + + if(bDoCorrectCanvasClipping) + { + // different from Svg we have the possibility with primitives to get + // a correct bounding box for the geometry. Get it for evtl. taking action + const basegfx::B2DRange aContentRange( + drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence( + aSequence, + drawinglayer::geometry::ViewInformation2D())); + + if(aSvgCanvasRange.isInside(aContentRange)) + { + // no clip needed, but an invisible HiddenGeometryPrimitive2D + // to allow getting the full Svg range using the primitive mechanisms. + // This is needed since e.g. an SdrObject using this as graphic will + // create a mapping transformation to exactly map the content to it's + // real life size + const drawinglayer::primitive2d::Primitive2DReference xLine( + new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( + basegfx::tools::createPolygonFromRect( + aSvgCanvasRange), + basegfx::BColor(0.0, 0.0, 0.0))); + const drawinglayer::primitive2d::Primitive2DReference xHidden( + new drawinglayer::primitive2d::HiddenGeometryPrimitive2D( + drawinglayer::primitive2d::Primitive2DSequence(&xLine, 1))); + + drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(aSequence, xHidden); + } + else if(aSvgCanvasRange.overlaps(aContentRange)) + { + // Clip is necessary. This will make Svg images evtl. smaller + // than wanted from Svg (the free space which may be around it is + // conform to the Svg spec), but avoids an expensive and unneccessary + // clip. Keep the full Svg range here to get the correct mappings + // to objects using this. Optimizations can be done in the processors + const drawinglayer::primitive2d::Primitive2DReference xMask( + new drawinglayer::primitive2d::MaskPrimitive2D( + basegfx::B2DPolyPolygon( + basegfx::tools::createPolygonFromRect( + aSvgCanvasRange)), + aSequence)); + + aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xMask, 1); + } + else + { + // not inside, no overlap. Empty Svg + aSequence.realloc(0); + } + } + + if(aSequence.hasElements()) + { + // embed in transform primitive to scale to 1/100th mm + // where 1 mm == 3.543307 px to get from Svg coordinates to + // drawinglayer ones + const double fScaleTo100thmm(100.0 / 3.543307); + const basegfx::B2DHomMatrix aTransform( + basegfx::tools::createScaleB2DHomMatrix( + fScaleTo100thmm, + fScaleTo100thmm)); + + const drawinglayer::primitive2d::Primitive2DReference xTransform( + new drawinglayer::primitive2d::TransformPrimitive2D( + aTransform, + aSequence)); + + aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1); + + // append to result + drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aSequence); + } + } + } + } + } + + const basegfx::B2DRange* SvgSvgNode::getCurrentViewPort() const + { + if(getViewBox()) + { + return getViewBox(); + } + else + { + return SvgNode::getCurrentViewPort(); + } + } + + } // end of namespace svgreader +} // end of namespace svgio + +////////////////////////////////////////////////////////////////////////////// +// eof + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |