diff options
author | Armin Le Grand <alg@apache.org> | 2013-08-14 11:44:08 +0100 |
---|---|---|
committer | Caolán McNamara <caolanm@redhat.com> | 2013-08-14 12:07:51 +0100 |
commit | b1ac43d1ab416d334a0feb93244941eee447abcf (patch) | |
tree | 72cd01bd4fd07f6a3541038a9703c985fe671e64 /svgio | |
parent | f0b9a27703db603a6a2fcf1d6f4db87e0bf4823b (diff) |
Resolves: fdo#67047 fdo#64125 #i122600# svgio improvements
(list see in task itself) and a regression/crash
Patch by: regina
Review by: ALG
(cherry picked from commit 8a9164f5f853db66b4453fc6fb798acf0a88a238)
Conflicts:
svgio/source/svgreader/svgsvgnode.cxx
Change-Id: I1c29073a3a18d2e645378e64a9fb28e73fc7e9ab
Diffstat (limited to 'svgio')
-rw-r--r-- | svgio/inc/svgio/svgreader/svgsvgnode.hxx | 10 | ||||
-rw-r--r-- | svgio/inc/svgio/svgreader/svgtools.hxx | 8 | ||||
-rw-r--r-- | svgio/source/svgreader/svgcirclenode.cxx | 2 | ||||
-rw-r--r-- | svgio/source/svgreader/svgsvgnode.cxx | 614 | ||||
-rw-r--r-- | svgio/source/svgreader/svgtools.cxx | 65 |
5 files changed, 497 insertions, 202 deletions
diff --git a/svgio/inc/svgio/svgreader/svgsvgnode.hxx b/svgio/inc/svgio/svgreader/svgsvgnode.hxx index 02f7e50e25c9..7226d7fcfc27 100644 --- a/svgio/inc/svgio/svgreader/svgsvgnode.hxx +++ b/svgio/inc/svgio/svgreader/svgsvgnode.hxx @@ -53,7 +53,15 @@ namespace svgio virtual void parseAttribute(const OUString& rTokenName, SVGToken aSVGToken, const OUString& aContent); virtual void decomposeSvgNode(drawinglayer::primitive2d::Primitive2DSequence& rTarget, bool bReferenced) const; - /// InfoProvider support for % values + /// Seeks width and height of viewport, which is current before the new viewport is set. + // needed for percentage unit in x, y, width or height + virtual void seekReferenceWidth(double& fWidth, bool& bHasFound) const; + virtual void seekReferenceHeight(double& fHeight, bool& bHasFound) const; + + /// InfoProvider support for % values in childs + // The returned 'CurrentViewPort' is the viewport as it is set by this svg element + // and as it is needed to resolve relative values in childs + // The method does not check for invalid width and height virtual const basegfx::B2DRange* getCurrentViewPort() const; /// viewBox content diff --git a/svgio/inc/svgio/svgreader/svgtools.hxx b/svgio/inc/svgio/svgreader/svgtools.hxx index 138a06546a0b..8a808b29b976 100644 --- a/svgio/inc/svgio/svgreader/svgtools.hxx +++ b/svgio/inc/svgio/svgreader/svgtools.hxx @@ -37,6 +37,9 @@ namespace svgio void myAssert(const OUString& rMessage); #endif +// recommended value for this devise dependend unit, see CSS2 section 4.3.2 Lenghts +#define F_SVG_PIXEL_PER_INCH 90.0 + // common non-token strings struct commonStrings { @@ -126,7 +129,12 @@ namespace svgio bool isPositive() const; + // Only usable in cases, when the unit is not Unit_percent, otherwise use method solve + double solveNonPercentage(const InfoProvider& rInfoProvider) const; + double solve(const InfoProvider& rInfoProvider, NumberType aNumberType = length) const; + + }; typedef ::std::vector< SvgNumber > SvgNumberVector; diff --git a/svgio/source/svgreader/svgcirclenode.cxx b/svgio/source/svgreader/svgcirclenode.cxx index 6dc269ca867a..c742a41f9989 100644 --- a/svgio/source/svgreader/svgcirclenode.cxx +++ b/svgio/source/svgreader/svgcirclenode.cxx @@ -122,7 +122,7 @@ namespace svgio if(pStyle && getR().isSet()) { - const double fR(getR().solve(*this, xcoordinate)); + const double fR(getR().solve(*this, length)); if(fR > 0.0) { diff --git a/svgio/source/svgreader/svgsvgnode.cxx b/svgio/source/svgreader/svgsvgnode.cxx index a8daee667ebe..8524a016c3e5 100644 --- a/svgio/source/svgreader/svgsvgnode.cxx +++ b/svgio/source/svgreader/svgsvgnode.cxx @@ -39,7 +39,7 @@ namespace svgio : SvgNode(SVGTokenSvg, rDocument, pParent), maSvgStyleAttributes(*this), mpViewBox(0), - maSvgAspectRatio(SvgAspectRatio(Align_xMidYMid, false, true)), + maSvgAspectRatio(), maX(), maY(), maWidth(), @@ -157,6 +157,91 @@ namespace svgio } } + void SvgSvgNode::seekReferenceWidth(double& fWidth, bool& bHasFound) const + { + if (!getParent() || bHasFound) + { + return; + } + const SvgSvgNode* pParentSvgSvgNode = 0; + // enclosing svg might have relative width, need to cumulate them till they are + // resolved somewhere up in the node tree + double fPercentage(1.0); + for(const SvgNode* pParent = getParent(); pParent && !bHasFound; pParent = pParent->getParent()) + { + // dynamic_cast results Null-pointer for not SvgSvgNode and so skips them in if condition + pParentSvgSvgNode = dynamic_cast< const SvgSvgNode* >(pParent); + if (pParentSvgSvgNode) + { + if (pParentSvgSvgNode->getViewBox()) + { + // viewbox values are already in 'user unit'. + fWidth = pParentSvgSvgNode->getViewBox()->getWidth() * fPercentage; + bHasFound = true; + } + else + { + // take absolute value or cummulate percentage + if (pParentSvgSvgNode->getWidth().isSet()) + { + if (Unit_percent == pParentSvgSvgNode->getWidth().getUnit()) + { + fPercentage *= pParentSvgSvgNode->getWidth().getNumber() * 0.01; + } + else + { + fWidth = pParentSvgSvgNode->getWidth().solveNonPercentage(*pParentSvgSvgNode) * fPercentage; + bHasFound = true; + } + } // not set => width=100% => factor 1, no need for else + } + } + } + } + + void SvgSvgNode::seekReferenceHeight(double& fHeight, bool& bHasFound) const + { + if (!getParent() || bHasFound) + { + return; + } + const SvgSvgNode* pParentSvgSvgNode = 0; + // enclosing svg might have relative width and height, need to cumulate them till they are + // resolved somewhere up in the node tree + double fPercentage(1.0); + for(const SvgNode* pParent = getParent(); pParent && !bHasFound; pParent = pParent->getParent()) + { + // dynamic_cast results Null-pointer for not SvgSvgNode and so skips them in if condition + pParentSvgSvgNode = dynamic_cast< const SvgSvgNode* >(pParent); + if (pParentSvgSvgNode) + { + if (pParentSvgSvgNode->getViewBox()) + { + // viewbox values are already in 'user unit'. + fHeight = pParentSvgSvgNode->getViewBox()->getHeight() * fPercentage; + bHasFound = true; + } + else + { + // take absolute value or cummulate percentage + if (pParentSvgSvgNode->getHeight().isSet()) + { + if (Unit_percent == pParentSvgSvgNode->getHeight().getUnit()) + { + fPercentage *= pParentSvgSvgNode->getHeight().getNumber() * 0.01; + } + else + { + fHeight = pParentSvgSvgNode->getHeight().solveNonPercentage(*pParentSvgSvgNode) * fPercentage; + bHasFound = true; + } + } // not set => height=100% => factor 1, no need for else + } + } + } + } + +// ToDo: Consider attribute overflow in method decomposeSvgNode void SvgSvgNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DSequence& rTarget, bool bReferenced) const { drawinglayer::primitive2d::Primitive2DSequence aSequence; @@ -168,13 +253,7 @@ namespace svgio { if(getParent()) { - const bool bWidthIsRelative(!getWidth().isSet() || Unit_percent == getWidth().getUnit()); - const bool bHeightIsRelative(!getWidth().isSet() || Unit_percent == getWidth().getUnit()); - const SvgSvgNode* pParentSvgSvgNode = 0; - double fW(0.0); - double fH(0.0); - - // #i122594# if width/height is not given, it's 100% (see 5.1.2 The svg element in SVG1.1 spec). + // #i122594# if width/height is not given, it's 100% (see 5.1.2 The 'svg' element in SVG1.1 spec). // If it is relative, the question is to what. The previous implementatin assumed relative to the // local ViewBox which is implied by (4.2 Basic data types): // @@ -185,56 +264,92 @@ namespace svgio // units in general), and (b) when a percentage length value represents a percentage of the // bounding box width or height on a given object (refer to the section that describes object // bounding box units)." - // - // This is not closer specified for the SVG element itself as non-outmost element, but comparisons - // with common browsers shows that it's mostly interpreted relative to the viewBox of the parent. - // Adding code to search the parent SVG element and calculating width/height relative to it's - // viewBox width/height (and no longer to the local viewBox). - if(bWidthIsRelative || bHeightIsRelative) + + // Comparisons with commom browsers show, that it's mostly interpreted relative to the viewport + // of the parent, and so does the new implementation. + + // Extract known viewport data + // bXXXIsAbsolute tracks whether relative values could be resolved to absolute values + + // If width or height is not provided, the default 100% is used, see SVG 1.1 section 5.1.2 + // value 0.0 here is only to initialize variable + bool bWidthIsAbsolute(getWidth().isSet() && Unit_percent != getWidth().getUnit()); + double fW( bWidthIsAbsolute ? getWidth().solveNonPercentage(*this) : 0.0); + + bool bHeightIsAbsolute(getHeight().isSet() && Unit_percent != getHeight().getUnit()); + double fH( bHeightIsAbsolute ? getHeight().solveNonPercentage(*this) : 0.0); + + // If x or y not provided, then default 0.0 is used, see SVG 1.1 Section 5.1.2 + bool bXIsAbsolute((getX().isSet() && Unit_percent != getX().getUnit()) || !getX().isSet()); + double fX( bXIsAbsolute && getX().isSet() ? getX().solveNonPercentage(*this) : 0.0); + + bool bYIsAbsolute((getY().isSet() && Unit_percent != getY().getUnit()) || !getY().isSet()); + double fY( bYIsAbsolute && getY().isSet() ? getY().solveNonPercentage(*this) : 0.0); + + if ( !bXIsAbsolute || !bWidthIsAbsolute) { - for(const SvgNode* pParent = getParent(); pParent && !pParentSvgSvgNode; pParent = pParent->getParent()) + // get width of enclosing svg and resolve percentage in x and width; + double fWReference(0.0); + bool bHasFoundWidth(false); + seekReferenceWidth(fWReference, bHasFoundWidth); + if (!bHasFoundWidth) { - pParentSvgSvgNode = dynamic_cast< const SvgSvgNode* >(pParent); + // Even outermost svg has not all information to resolve relative values, + // I use content itself as fallback to set missing values for viewport + // Any better idea for such ill structures svg documents? + const basegfx::B2DRange aChildRange( + drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence( + aSequence, + drawinglayer::geometry::ViewInformation2D())); + fWReference = aChildRange.getWidth(); } - } - - if(bWidthIsRelative) - { - fW = getWidth().isSet() ? getWidth().getNumber() * 0.01 : 1.0; - - if(pParentSvgSvgNode) + // referenced values are already in 'user unit' + if (!bXIsAbsolute) { - fW *= pParentSvgSvgNode->getViewBox()->getWidth(); + fX = getX().getNumber() * 0.01 * fWReference; + } + if (!bWidthIsAbsolute) + { + fW = (getWidth().isSet() ? getWidth().getNumber() *0.01 : 1.0) * fWReference; } - } - else - { - fW = getWidth().solve(*this, xcoordinate); } - if(bHeightIsRelative) + if ( !bYIsAbsolute || !bHeightIsAbsolute) { - fH = getHeight().isSet() ? getHeight().getNumber() * 0.01 : 1.0; + // get height of enclosing svg and resolve percentage in y and height + double fHReference(0.0); + bool bHasFoundHeight(false); + seekReferenceHeight(fHReference, bHasFoundHeight); + if (!bHasFoundHeight) + { + // Even outermost svg has not all information to resolve relative values, + // I use content itself as fallback to set missing values for viewport + // Any better idea for such ill structures svg documents? + const basegfx::B2DRange aChildRange( + drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence( + aSequence, + drawinglayer::geometry::ViewInformation2D())); + fHReference = aChildRange.getHeight(); + } - if(pParentSvgSvgNode) + // referenced values are already in 'user unit' + if (!bYIsAbsolute) { - fH *= pParentSvgSvgNode->getViewBox()->getHeight(); + fY = getY().getNumber() * 0.01 * fHReference; + } + if (!bHeightIsAbsolute) + { + fH = (getHeight().isSet() ? getHeight().getNumber() *0.01 : 1.0) * fHReference; } - } - else - { - fH = getHeight().solve(*this, ycoordinate); } 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())) + // SVG 1.1 defines in section 7.7 that a negative value for width or height + // in viewBox is an error and that 0.0 disables rendering + if(basegfx::fTools::more(getViewBox()->getWidth(),0.0) && basegfx::fTools::more(getViewBox()->getHeight(),0.0)) { - // 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); + // create target range homing x,y, width and height as calculated above const basegfx::B2DRange aTarget(fX, fY, fX + fW, fY + fH); if(aTarget.equal(*getViewBox())) @@ -245,63 +360,44 @@ namespace svgio else { // create mapping - const SvgAspectRatio& rRatio = getSvgAspectRatio(); + // #i122610 SVG 1.1 defines in section 5.1.2 that if the attribute perserveAspectRatio is not specified, + // then the effect is as if a value of 'xMidYMid meet' were specified. + SvgAspectRatio aRatioDefault(Align_xMidYMid,false,true); + const SvgAspectRatio& rRatio = getSvgAspectRatio().isSet()? getSvgAspectRatio() : aRatioDefault; + + // 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.isSet()) + if(rRatio.isMeetOrSlice()) { - // 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); - } + // embed in transformation + drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xRef); } 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)); + // 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, xTransform); + drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xMask); } } } } - else + else // no viewBox attribute { // 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 @@ -326,140 +422,284 @@ namespace svgio } } } - else + else // Outermost SVG element { - // Outermost SVG element; create target range homing width and height as given. - // SVG defines that x,y has no meaning for the outermost SVG element. Use a fallback - // width and height of DIN A 4 (21 x 29,7 cm) - double fViewPortW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : (210.0 * 3.543307)); - double fViewPortH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : (297.0 * 3.543307)); - basegfx::B2DRange aSvgCanvasRange(0.0, 0.0, 0.0, 0.0); + double fW = 0.0; // effective value depends on viewBox + double fH = 0.0; // Svg defines that a negative value is an error and that 0.0 disables rendering - if(basegfx::fTools::more(fViewPortW, 0.0) && basegfx::fTools::more(fViewPortH, 0.0)) + // isPositive() not usable because it allows 0.0 in contrast to mathematical definition of 'positive' + const bool bWidthInvalid(getWidth().isSet() && basegfx::fTools::lessOrEqual(getWidth().getNumber(), 0.0)); + const bool bHeightInvalid(getHeight().isSet() && basegfx::fTools::lessOrEqual(getHeight().getNumber(), 0.0)); + if(!bWidthInvalid && !bHeightInvalid) { - basegfx::B2DRange aContentRange; // ViewBox or bounding box + basegfx::B2DRange aSvgCanvasRange; // effective value depends on viewBox if(getViewBox()) { - aContentRange = *getViewBox(); + // SVG 1.1 defines in section 7.7 that a negative value for width or height + // in viewBox is an error and that 0.0 disables rendering + const double fViewBoxWidth = getViewBox()->getWidth(); + const double fViewBoxHeight = getViewBox()->getHeight(); + if(basegfx::fTools::more(fViewBoxWidth,0.0) && basegfx::fTools::more(fViewBoxHeight,0.0)) + { + // The intrinsic aspect ratio of the svg element is given by absolute values of both width and height + // or if one or both of them is relative by the width and height of the viewBox + // see SVG 1.1 section 7.12 + const bool bWidthIsAbsolute(getWidth().isSet() && Unit_percent != getWidth().getUnit()); + const bool bHeightIsAbsolute(getHeight().isSet() && Unit_percent != getHeight().getUnit()); + if(bWidthIsAbsolute && bHeightIsAbsolute) + { + fW =getWidth().solveNonPercentage(*this); + fH =getHeight().solveNonPercentage(*this); + } + else if (bWidthIsAbsolute) + { + fW = getWidth().solveNonPercentage(*this); + fH = fW * fViewBoxWidth / fViewBoxHeight ; + } + else if (bHeightIsAbsolute) + { + fH = getHeight().solveNonPercentage(*this); + fW = fH * fViewBoxWidth / fViewBoxHeight ; + } + else + { + fW = fViewBoxWidth; + fH = fViewBoxHeight; + } + // SVG 1.1 defines in section 5.1.2 that x,y has no meanig for the outermost SVG element. + aSvgCanvasRange = basegfx::B2DRange(0.0, 0.0, fW, fH); + + // create mapping + // SVG 1.1 defines in section 5.1.2 that if the attribute perserveAspectRatio is not specified, + // then the effect is as if a value of 'xMidYMid meet' were specified. + SvgAspectRatio aRatioDefault(Align_xMidYMid,false,true); + const SvgAspectRatio& rRatio = getSvgAspectRatio().isSet()? getSvgAspectRatio() : aRatioDefault; + + basegfx::B2DHomMatrix aViewBoxMapping; + aViewBoxMapping = rRatio.createMapping(aSvgCanvasRange, *getViewBox()); + // no need to check ratio here for slice, the outermost Svg will + // be clipped anyways (see below) + + // scale content to viewBox definitions + const drawinglayer::primitive2d::Primitive2DReference xTransform( + new drawinglayer::primitive2d::TransformPrimitive2D( + aViewBoxMapping, + aSequence)); + + aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1); + } } - else // no ViewBox given -> get bounding box TODO: ignore preserveAspectRatio i.e. set to default? + else // no viewbox { - aContentRange = drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence(aSequence, drawinglayer::geometry::ViewInformation2D()); + // There exists no parent to resolve relative width or height. + // Use child size as fallback. + const bool bWidthIsAbsolute(getWidth().isSet() && Unit_percent != getWidth().getUnit()); + const bool bHeightIsAbsolute(getHeight().isSet() && Unit_percent != getHeight().getUnit()); + if (bWidthIsAbsolute && bHeightIsAbsolute) + { + fW =getWidth().solveNonPercentage(*this); + fH =getHeight().solveNonPercentage(*this); + + } + else + { + const basegfx::B2DRange aChildRange( + drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence( + aSequence, + drawinglayer::geometry::ViewInformation2D())); + const double fChildWidth(aChildRange.getWidth()); + const double fChildHeight(aChildRange.getHeight()); + fW = bWidthIsAbsolute ? getWidth().solveNonPercentage(*this) : fChildWidth; + fH = bHeightIsAbsolute ? getHeight().solveNonPercentage(*this) : fChildHeight; + } + // SVG 1.1 defines in section 5.1.2 that x,y has no meanig for the outermost SVG element. + aSvgCanvasRange = basegfx::B2DRange(0.0, 0.0, fW, fH); } - double fW(aContentRange.getWidth()); - double fH(aContentRange.getHeight()); - if(!basegfx::fTools::equalZero(fW) && !basegfx::fTools::equalZero(fH)) + // 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) { - const SvgAspectRatio& rRatio = getSvgAspectRatio(); - double fScale = std::min(fViewPortW/fW, fViewPortH/fH); - fW *= fScale; - fH *= fScale; - aSvgCanvasRange = basegfx::B2DRange(0.0, 0.0, fW, fH); + // 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)); - // let mapping be created from SvgAspectRatio - basegfx::B2DHomMatrix aViewBoxMapping; - aViewBoxMapping = rRatio.createMapping(aSvgCanvasRange, aContentRange); + aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xMask, 1); + } + else + { + // not inside, no overlap. Empty Svg + aSequence.realloc(0); + } + } - // no need to check ratio here for slice, the outermost Svg will - // be clipped anyways (see below) + if(aSequence.hasElements()) + { + // embed in transform primitive to scale to 1/100th mm + // where 1 inch == 25.4 mm to get from Svg coordinates (px) to + // drawinglayer coordinates + const double fScaleTo100thmm(25.4 * 100.0 / F_SVG_PIXEL_PER_INCH); + const basegfx::B2DHomMatrix aTransform( + basegfx::tools::createScaleB2DHomMatrix( + fScaleTo100thmm, + fScaleTo100thmm)); - // scale content to viewBox definitions const drawinglayer::primitive2d::Primitive2DReference xTransform( - new drawinglayer::primitive2d::TransformPrimitive2D( - aViewBoxMapping, - aSequence)); + new drawinglayer::primitive2d::TransformPrimitive2D( + aTransform, + aSequence)); aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1); + + // append to result + drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aSequence); } } - // 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) + } + } + } + + const basegfx::B2DRange* SvgSvgNode::getCurrentViewPort() const + { + if(getViewBox()) + { + return getViewBox(); + } + else // viewport should be given by x, y, width, and height + { + // Extract known viewport data + // bXXXIsAbsolute tracks whether relative values could be resolved to absolute values + if (getParent()) + { + // If width or height is not provided, the default 100% is used, see SVG 1.1 section 5.1.2 + // value 0.0 here is only to initialize variable + bool bWidthIsAbsolute(getWidth().isSet() && Unit_percent != getWidth().getUnit()); + double fW( bWidthIsAbsolute ? getWidth().solveNonPercentage(*this) : 0.0); + bool bHeightIsAbsolute(getHeight().isSet() && Unit_percent != getHeight().getUnit()); + double fH( bHeightIsAbsolute ? getHeight().solveNonPercentage(*this) : 0.0); + + // If x or y not provided, then default 0.0 is used, see SVG 1.1 Section 5.1.2 + bool bXIsAbsolute((getX().isSet() && Unit_percent != getX().getUnit()) || !getX().isSet()); + double fX( bXIsAbsolute && getX().isSet() ? getX().solveNonPercentage(*this) : 0.0); + + bool bYIsAbsolute((getY().isSet() && Unit_percent != getY().getUnit()) || !getY().isSet()); + double fY( bYIsAbsolute && getY().isSet() ? getY().solveNonPercentage(*this) : 0.0); + + if (bXIsAbsolute && bYIsAbsolute && bWidthIsAbsolute && bHeightIsAbsolute) { - // 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)) + return &basegfx::B2DRange(fX, fY, fX+fW, fY+fH); + } + else // try to resolve relative values + { + if (!bXIsAbsolute || !bWidthIsAbsolute) { - // 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); + // get width of enclosing svg and resolve percentage in x and width + double fWReference(0.0); + bool bHasFoundWidth(false); + seekReferenceWidth(fWReference, bHasFoundWidth); + // referenced values are already in 'user unit' + if (!bXIsAbsolute && bHasFoundWidth) + { + fX = getX().getNumber() * 0.01 * fWReference; + bXIsAbsolute = true; + } + if (!bWidthIsAbsolute && bHasFoundWidth) + { + fW = (getWidth().isSet() ? getWidth().getNumber() *0.01 : 1.0) * fWReference; + bWidthIsAbsolute = true; + } } - else if(aSvgCanvasRange.overlaps(aContentRange)) + if (!bYIsAbsolute || !bHeightIsAbsolute) { - // 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)); + // get height of enclosing svg and resolve percentage in y and height + double fHReference(0.0); + bool bHasFoundHeight(false); + seekReferenceHeight(fHReference, bHasFoundHeight); + // referenced values are already in 'user unit' + if (!bYIsAbsolute && bHasFoundHeight) + { + fY = getY().getNumber() * 0.01 * fHReference; + bYIsAbsolute = true; + } + if (!bHeightIsAbsolute && bHasFoundHeight) + { + fH = (getHeight().isSet() ? getHeight().getNumber() *0.01 : 1.0) * fHReference; + bHeightIsAbsolute = true; + } + } - aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xMask, 1); + if (bXIsAbsolute && bYIsAbsolute && bWidthIsAbsolute && bHeightIsAbsolute) + { + return &basegfx::B2DRange(fX, fY, fX+fW, fY+fH); } - else + else // relative values could not be resolved, there exists no fallback { - // not inside, no overlap. Empty Svg - aSequence.realloc(0); + return SvgNode::getCurrentViewPort(); } } - - if(aSequence.hasElements()) + } + else //outermost svg + { + // If width or height is not provided, the default would be 100%, see SVG 1.1 section 5.1.2 + // But here it cannot be resolved and no fallback exists. + // SVG 1.1 defines in section 5.1.2 that x,y has no meanig for the outermost SVG element. + bool bWidthIsAbsolute(getWidth().isSet() && Unit_percent != getWidth().getUnit()); + double fW( bWidthIsAbsolute ? getWidth().solveNonPercentage(*this) : 0.0); + bool bHeightIsAbsolute(getHeight().isSet() && Unit_percent != getHeight().getUnit()); + double fH( bHeightIsAbsolute ? getHeight().solveNonPercentage(*this) : 0.0); + if (bWidthIsAbsolute && bHeightIsAbsolute) + { + return &basegfx::B2DRange(0.0, 0.0, fW, fH); + } + else // no fallback exists { - // 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); + return SvgNode::getCurrentViewPort(); } } - } - } +// ToDo: Is it possible to decompose and use the bounding box of the childs, if even the +// outermost svg has no information to resolve percentage? Is it worth, how expensive is it? - const basegfx::B2DRange* SvgSvgNode::getCurrentViewPort() const - { - if(getViewBox()) - { - return getViewBox(); - } - else - { - return SvgNode::getCurrentViewPort(); } } diff --git a/svgio/source/svgreader/svgtools.cxx b/svgio/source/svgreader/svgtools.cxx index 47b6e49a350d..ec74902475c9 100644 --- a/svgio/source/svgreader/svgtools.cxx +++ b/svgio/source/svgreader/svgtools.cxx @@ -151,7 +151,7 @@ namespace svgio return aRetval; } - double SvgNumber::solve(const InfoProvider& rInfoProvider, NumberType aNumberType) const + double SvgNumber::solveNonPercentage(const InfoProvider& rInfoProvider) const { if(isSet()) { @@ -179,17 +179,53 @@ namespace svgio switch(meUnit) { - case Unit_pt: fRetval *= 1.25; break; - case Unit_pc: fRetval *= 15.0; break; - case Unit_cm: fRetval *= 35.43307; break; - case Unit_mm: fRetval *= 3.543307; break; - case Unit_in: fRetval *= 90.0; break; + case Unit_pt: fRetval *= F_SVG_PIXEL_PER_INCH / 72.0; break; + case Unit_pc: fRetval *= F_SVG_PIXEL_PER_INCH / 6.0; break; + case Unit_cm: fRetval *= F_SVG_PIXEL_PER_INCH / 2.54; break; + case Unit_mm: fRetval *= 0.1 * F_SVG_PIXEL_PER_INCH / 2.54; break; + case Unit_in: fRetval *= F_SVG_PIXEL_PER_INCH; break; default: break; } return fRetval; break; } + default: + { + OSL_ENSURE(false, "Do not use with percentage! "); + return 0.0; + break; + } + } + } + + /// not set + OSL_ENSURE(false, "SvgNumber not set (!)"); + return 0.0; + } + + double SvgNumber::solve(const InfoProvider& rInfoProvider, NumberType aNumberType) const + { + if(isSet()) + { + switch(meUnit) + { + case Unit_px: + { + return mfNumber; + break; + } + case Unit_pt: + case Unit_pc: + case Unit_cm: + case Unit_mm: + case Unit_in: + case Unit_em: + case Unit_ex: + { + return solveNonPercentage( rInfoProvider); + break; + } case Unit_percent: { double fRetval(mfNumber * 0.01); @@ -197,12 +233,15 @@ namespace svgio if(!pViewPort) { +#ifdef DBG_UTIL + myAssert(rtl::OUString::createFromAscii("Design error, this case should have been handled in the caller")); +#endif // no viewPort, assume a normal page size (A4) static basegfx::B2DRange aDinA4Range( 0.0, 0.0, - 210.0 * 3.543307, - 297.0 * 3.543307); + 210.0 * F_SVG_PIXEL_PER_INCH / 2.54, + 297.0 * F_SVG_PIXEL_PER_INCH / 2.54); pViewPort = &aDinA4Range; } @@ -920,11 +959,11 @@ namespace svgio if(readNumberAndUnit(rCandidate, nPos, aHeight, nLen)) { - return basegfx::B2DRange( - aMinX.solve(rInfoProvider, xcoordinate), - aMinY.solve(rInfoProvider, ycoordinate), - aWidth.solve(rInfoProvider, xcoordinate), - aHeight.solve(rInfoProvider, ycoordinate)); + double fX(aMinX.solve(rInfoProvider, xcoordinate)); + double fY(aMinY.solve(rInfoProvider, ycoordinate)); + double fW(aWidth.solve(rInfoProvider,xcoordinate)); + double fH(aHeight.solve(rInfoProvider,ycoordinate)); + return basegfx::B2DRange(fX,fY,fX+fW,fY+fH); } } } |