/************************************************************** * * 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 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * *************************************************************/ // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_svgio.hxx" #include #include #include #include #include #include #include #include #include ////////////////////////////////////////////////////////////////////////////// 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::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; // decompose childs SvgNode::decomposeSvgNode(aSequence, bReferenced); if(aSequence.hasElements()) { if(getParent()) { // #122594# 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): // // "Note that the non-property definition also allows a percentage unit identifier. // The meaning of a percentage length value depends on the attribute for which the percentage // length value has been specified. Two common cases are: (a) when a percentage length value // represents a percentage of the viewport width or height (refer to the section that discusses // 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)." // 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) { // get width of enclosing svg and resolve percentage in x and width; double fWReference(0.0); bool bHasFoundWidth(false); seekReferenceWidth(fWReference, bHasFoundWidth); if (!bHasFoundWidth) { // 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(); } // referenced values are already in 'user unit' if (!bXIsAbsolute) { fX = getX().getNumber() * 0.01 * fWReference; } if (!bWidthIsAbsolute) { fW = (getWidth().isSet() ? getWidth().getNumber() *0.01 : 1.0) * fWReference; } } if ( !bYIsAbsolute || !bHeightIsAbsolute) { // 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(); } // referenced values are already in 'user unit' if (!bYIsAbsolute) { fY = getY().getNumber() * 0.01 * fHReference; } if (!bHeightIsAbsolute) { fH = (getHeight().isSet() ? getHeight().getNumber() *0.01 : 1.0) * fHReference; } } if(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 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 calculated above 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 // #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.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 // 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)) { 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 { 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 // 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 aSvgCanvasRange; // effective value depends on viewBox if(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 { // 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); } // 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 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)); 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 // 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) { return &basegfx::B2DRange(fX, fY, fX+fW, fY+fH); } else // try to resolve relative values { if (!bXIsAbsolute || !bWidthIsAbsolute) { // 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; } } if (!bYIsAbsolute || !bHeightIsAbsolute) { // 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; } } if (bXIsAbsolute && bYIsAbsolute && bWidthIsAbsolute && bHeightIsAbsolute) { return &basegfx::B2DRange(fX, fY, fX+fW, fY+fH); } else // relative values could not be resolved, there exists no fallback { return SvgNode::getCurrentViewPort(); } } } 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 { 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? } } } // end of namespace svgreader } // end of namespace svgio ////////////////////////////////////////////////////////////////////////////// // eof