1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 // MARKER(update_precomp.py): autogen include statement, do not remove
23 #include "precompiled_svgio.hxx"
24 
25 #include <svgio/svgreader/svgsvgnode.hxx>
26 #include <drawinglayer/geometry/viewinformation2d.hxx>
27 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
28 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
29 #include <basegfx/polygon/b2dpolygontools.hxx>
30 #include <basegfx/polygon/b2dpolygon.hxx>
31 #include <basegfx/matrix/b2dhommatrixtools.hxx>
32 #include <drawinglayer/primitive2d/polygonprimitive2d.hxx>
33 #include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx>
34 
35 //////////////////////////////////////////////////////////////////////////////
36 
37 namespace svgio
38 {
39     namespace svgreader
40     {
41         SvgSvgNode::SvgSvgNode(
42             SvgDocument& rDocument,
43             SvgNode* pParent)
44         :   SvgNode(SVGTokenSvg, rDocument, pParent),
45             maSvgStyleAttributes(*this),
46             mpViewBox(0),
47             maSvgAspectRatio(),
48             maX(),
49             maY(),
50             maWidth(),
51             maHeight(),
52             maVersion()
53         {
54             if(!getParent())
55             {
56                 // initial fill is black
57                 maSvgStyleAttributes.setFill(SvgPaint(basegfx::BColor(0.0, 0.0, 0.0), true, true));
58             }
59         }
60 
61         SvgSvgNode::~SvgSvgNode()
62         {
63             if(mpViewBox) delete mpViewBox;
64         }
65 
66         const SvgStyleAttributes* SvgSvgNode::getSvgStyleAttributes() const
67         {
68             return &maSvgStyleAttributes;
69         }
70 
71         void SvgSvgNode::parseAttribute(const rtl::OUString& rTokenName, SVGToken aSVGToken, const rtl::OUString& aContent)
72         {
73             // call parent
74             SvgNode::parseAttribute(rTokenName, aSVGToken, aContent);
75 
76             // read style attributes
77             maSvgStyleAttributes.parseStyleAttribute(rTokenName, aSVGToken, aContent);
78 
79             // parse own
80             switch(aSVGToken)
81             {
82                 case SVGTokenStyle:
83                 {
84                     maSvgStyleAttributes.readStyle(aContent);
85                     break;
86                 }
87                 case SVGTokenViewBox:
88                 {
89                     const basegfx::B2DRange aRange(readViewBox(aContent, *this));
90 
91                     if(!aRange.isEmpty())
92                     {
93                         setViewBox(&aRange);
94                     }
95                     break;
96                 }
97                 case SVGTokenPreserveAspectRatio:
98                 {
99                     setSvgAspectRatio(readSvgAspectRatio(aContent));
100                     break;
101                 }
102                 case SVGTokenX:
103                 {
104                     SvgNumber aNum;
105 
106                     if(readSingleNumber(aContent, aNum))
107                     {
108                         setX(aNum);
109                     }
110                     break;
111                 }
112                 case SVGTokenY:
113                 {
114                     SvgNumber aNum;
115 
116                     if(readSingleNumber(aContent, aNum))
117                     {
118                         setY(aNum);
119                     }
120                     break;
121                 }
122                 case SVGTokenWidth:
123                 {
124                     SvgNumber aNum;
125 
126                     if(readSingleNumber(aContent, aNum))
127                     {
128                         if(aNum.isPositive())
129                         {
130                             setWidth(aNum);
131                         }
132                     }
133                     break;
134                 }
135                 case SVGTokenHeight:
136                 {
137                     SvgNumber aNum;
138 
139                     if(readSingleNumber(aContent, aNum))
140                     {
141                         if(aNum.isPositive())
142                         {
143                             setHeight(aNum);
144                         }
145                     }
146                     break;
147                 }
148                 case SVGTokenVersion:
149                 {
150                     SvgNumber aNum;
151 
152                     if(readSingleNumber(aContent, aNum))
153                     {
154                         setVersion(aNum);
155                     }
156                     break;
157                 }
158                 default:
159                 {
160                     break;
161                 }
162             }
163         }
164 
165         void SvgSvgNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DSequence& rTarget, bool bReferenced) const
166         {
167             drawinglayer::primitive2d::Primitive2DSequence aSequence;
168 
169             // decompose childs
170             SvgNode::decomposeSvgNode(aSequence, bReferenced);
171 
172             if(aSequence.hasElements())
173             {
174                 if(getParent())
175                 {
176                     if(getViewBox())
177                     {
178                         // Svg defines that with no width or no height the viewBox content is empty,
179                         // so both need to exist
180                         if(!basegfx::fTools::equalZero(getViewBox()->getWidth()) && !basegfx::fTools::equalZero(getViewBox()->getHeight()))
181                         {
182                             // create target range homing x,y, width and height as given
183                             const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0);
184                             const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0);
185                             const bool bWidthIsRelative(!getWidth().isSet() || Unit_percent == getWidth().getUnit());
186                             const bool bHeightIsRelative(!getWidth().isSet() || Unit_percent == getWidth().getUnit());
187                             const SvgSvgNode* pParentSvgSvgNode = 0;
188                             double fW(0.0);
189                             double fH(0.0);
190 
191                             // #122594# if width/height is not given, it's 100% (see 5.1.2 The �svg� element in SVG1.1 spec).
192                             // If it is relative, the question is to what. The previous implementatin assumed relative to the
193                             // local ViewBox which is implied by (4.2 Basic data types):
194                             //
195                             // "Note that the non-property <length> definition also allows a percentage unit identifier.
196                             // The meaning of a percentage length value depends on the attribute for which the percentage
197                             // length value has been specified. Two common cases are: (a) when a percentage length value
198                             // represents a percentage of the viewport width or height (refer to the section that discusses
199                             // units in general), and (b) when a percentage length value represents a percentage of the
200                             // bounding box width or height on a given object (refer to the section that describes object
201                             // bounding box units)."
202                             //
203                             // This is not closer specified for the SVG element itself as non-outmost element, but comparisons
204                             // with common browsers shows that it's mostly interpreted relative to the viewBox of the parent.
205                             // Adding code to search the parent SVG element and calculating width/height relative to it's
206                             // viewBox width/height (and no longer to the local viewBox).
207                             if(bWidthIsRelative || bHeightIsRelative)
208                             {
209                                 for(const SvgNode* pParent = getParent(); pParent && !pParentSvgSvgNode; pParent = pParent->getParent())
210                                 {
211                                     pParentSvgSvgNode = dynamic_cast< const SvgSvgNode* >(pParent);
212                                 }
213                             }
214 
215                             if(bWidthIsRelative)
216                             {
217                                 fW = getWidth().isSet() ? getWidth().getNumber() * 0.01 : 1.0;
218 
219                                 if(pParentSvgSvgNode)
220                                 {
221                                     fW *= pParentSvgSvgNode->getViewBox()->getWidth();
222                                 }
223                             }
224                             else
225                             {
226                                 fW = getWidth().solve(*this, xcoordinate);
227                             }
228 
229                             if(bHeightIsRelative)
230                             {
231                                 fH = getHeight().isSet() ? getHeight().getNumber() * 0.01 : 1.0;
232 
233                                 if(pParentSvgSvgNode)
234                                 {
235                                     fH *= pParentSvgSvgNode->getViewBox()->getHeight();
236                                 }
237                             }
238                             else
239                             {
240                                 fH = getHeight().solve(*this, ycoordinate);
241                             }
242 
243                             const basegfx::B2DRange aTarget(fX, fY, fX + fW, fY + fH);
244 
245                             if(aTarget.equal(*getViewBox()))
246                             {
247                                 // no mapping needed, append
248                                 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aSequence);
249                             }
250                             else
251                             {
252                                 // create mapping
253                                 const SvgAspectRatio& rRatio = getSvgAspectRatio();
254 
255                                 if(rRatio.isSet())
256                                 {
257                                     // let mapping be created from SvgAspectRatio
258                                     const basegfx::B2DHomMatrix aEmbeddingTransform(
259                                         rRatio.createMapping(aTarget, *getViewBox()));
260 
261                                     // prepare embedding in transformation
262                                     const drawinglayer::primitive2d::Primitive2DReference xRef(
263                                         new drawinglayer::primitive2d::TransformPrimitive2D(
264                                             aEmbeddingTransform,
265                                             aSequence));
266 
267                                     if(rRatio.isMeetOrSlice())
268                                     {
269                                         // embed in transformation
270                                         drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xRef);
271                                     }
272                                     else
273                                     {
274                                         // need to embed in MaskPrimitive2D, too
275                                         const drawinglayer::primitive2d::Primitive2DReference xMask(
276                                             new drawinglayer::primitive2d::MaskPrimitive2D(
277                                                 basegfx::B2DPolyPolygon(basegfx::tools::createPolygonFromRect(aTarget)),
278                                                 drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1)));
279 
280                                         drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xMask);
281                                     }
282                                 }
283                                 else
284                                 {
285                                     // choose default mapping
286                                     const basegfx::B2DHomMatrix aEmbeddingTransform(
287                                         rRatio.createLinearMapping(
288                                             aTarget, *getViewBox()));
289 
290                                     // embed in transformation
291                                     const drawinglayer::primitive2d::Primitive2DReference xTransform(
292                                         new drawinglayer::primitive2d::TransformPrimitive2D(
293                                             aEmbeddingTransform,
294                                             aSequence));
295 
296                                     drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xTransform);
297                                 }
298                             }
299                         }
300                     }
301                     else
302                     {
303                         // check if we have a size
304                         const double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : 0.0);
305                         const double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : 0.0);
306 
307                         // Svg defines that a negative value is an error and that 0.0 disables rendering
308                         if(basegfx::fTools::more(fW, 0.0) && basegfx::fTools::more(fH, 0.0))
309                         {
310                             // check if we have a x,y position
311                             const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0);
312                             const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0);
313 
314                             if(!basegfx::fTools::equalZero(fX) || !basegfx::fTools::equalZero(fY))
315                             {
316                                 // embed in transform
317                                 const drawinglayer::primitive2d::Primitive2DReference xRef(
318                                     new drawinglayer::primitive2d::TransformPrimitive2D(
319                                         basegfx::tools::createTranslateB2DHomMatrix(fX, fY),
320                                         aSequence));
321 
322                                 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1);
323                             }
324 
325                             // embed in MaskPrimitive2D to clip
326                             const drawinglayer::primitive2d::Primitive2DReference xMask(
327                                 new drawinglayer::primitive2d::MaskPrimitive2D(
328                                     basegfx::B2DPolyPolygon(
329                                         basegfx::tools::createPolygonFromRect(
330                                             basegfx::B2DRange(fX, fY, fX + fW, fY + fH))),
331                                     aSequence));
332 
333                             // append
334                             drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xMask);
335                         }
336                     }
337                 }
338                 else
339                 {
340                     // Outermost SVG element; create target range homing width and height as given.
341                     // SVG defines that x,y has no meanig for the outermost SVG element. Use a fallback
342                     // width and height of din A 4 (21 x 29,7 cm)
343                     double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : (210.0 * 3.543307));
344                     double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : (297.0 * 3.543307));
345 
346                     // Svg defines that a negative value is an error and that 0.0 disables rendering
347                     if(basegfx::fTools::more(fW, 0.0) && basegfx::fTools::more(fH, 0.0))
348                     {
349                         const basegfx::B2DRange aSvgCanvasRange(0.0, 0.0, fW, fH);
350 
351                         if(getViewBox())
352                         {
353                             if(!basegfx::fTools::equalZero(getViewBox()->getWidth()) && !basegfx::fTools::equalZero(getViewBox()->getHeight()))
354                             {
355                                 // create mapping
356                                 const SvgAspectRatio& rRatio = getSvgAspectRatio();
357                                 basegfx::B2DHomMatrix aViewBoxMapping;
358 
359                                 if(rRatio.isSet())
360                                 {
361                                     // let mapping be created from SvgAspectRatio
362                                     aViewBoxMapping = rRatio.createMapping(aSvgCanvasRange, *getViewBox());
363 
364                                     // no need to check ratio here for slice, the outermost Svg will
365                                     // be clipped anyways (see below)
366                                 }
367                                 else
368                                 {
369                                     // choose default mapping
370                                     aViewBoxMapping = rRatio.createLinearMapping(aSvgCanvasRange, *getViewBox());
371                                 }
372 
373                                 // scale content to viewBox definitions
374                                 const drawinglayer::primitive2d::Primitive2DReference xTransform(
375                                     new drawinglayer::primitive2d::TransformPrimitive2D(
376                                         aViewBoxMapping,
377                                         aSequence));
378 
379                                 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1);
380                             }
381                         }
382 
383                         // to be completely correct in Svg sense it is necessary to clip
384                         // the whole content to the given canvas. I choose here to do this
385                         // initially despite I found various examples of Svg files out there
386                         // which have no correct values for this clipping. It's correct
387                         // due to the Svg spec.
388                         bool bDoCorrectCanvasClipping(true);
389 
390                         if(bDoCorrectCanvasClipping)
391                         {
392                             // different from Svg we have the possibility with primitives to get
393                             // a correct bounding box for the geometry. Get it for evtl. taking action
394                             const basegfx::B2DRange aContentRange(
395                                 drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence(
396                                     aSequence,
397                                     drawinglayer::geometry::ViewInformation2D()));
398 
399                             if(aSvgCanvasRange.isInside(aContentRange))
400                             {
401                                 // no clip needed, but an invisible HiddenGeometryPrimitive2D
402                                 // to allow getting the full Svg range using the primitive mechanisms.
403                                 // This is needed since e.g. an SdrObject using this as graphic will
404                                 // create a mapping transformation to exactly map the content to it's
405                                 // real life size
406                                 const drawinglayer::primitive2d::Primitive2DReference xLine(
407                                     new drawinglayer::primitive2d::PolygonHairlinePrimitive2D(
408                                         basegfx::tools::createPolygonFromRect(
409                                             aSvgCanvasRange),
410                                         basegfx::BColor(0.0, 0.0, 0.0)));
411                                 const drawinglayer::primitive2d::Primitive2DReference xHidden(
412                                     new drawinglayer::primitive2d::HiddenGeometryPrimitive2D(
413                                         drawinglayer::primitive2d::Primitive2DSequence(&xLine, 1)));
414 
415                                 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(aSequence, xHidden);
416                             }
417                             else if(aSvgCanvasRange.overlaps(aContentRange))
418                             {
419                                 // Clip is necessary. This will make Svg images evtl. smaller
420                                 // than wanted from Svg (the free space which may be around it is
421                                 // conform to the Svg spec), but avoids an expensive and unneccessary
422                                 // clip. Keep the full Svg range here to get the correct mappings
423                                 // to objects using this. Optimizations can be done in the processors
424                                 const drawinglayer::primitive2d::Primitive2DReference xMask(
425                                     new drawinglayer::primitive2d::MaskPrimitive2D(
426                                         basegfx::B2DPolyPolygon(
427                                             basegfx::tools::createPolygonFromRect(
428                                                 aSvgCanvasRange)),
429                                         aSequence));
430 
431                                 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xMask, 1);
432                             }
433                             else
434                             {
435                                 // not inside, no overlap. Empty Svg
436                                 aSequence.realloc(0);
437                             }
438                         }
439 
440                         if(aSequence.hasElements())
441                         {
442                             // embed in transform primitive to scale to 1/100th mm
443                             // where 1 mm == 3.543307 px to get from Svg coordinates to
444                             // drawinglayer ones
445                             const double fScaleTo100thmm(100.0 / 3.543307);
446                             const basegfx::B2DHomMatrix aTransform(
447                                 basegfx::tools::createScaleB2DHomMatrix(
448                                     fScaleTo100thmm,
449                                     fScaleTo100thmm));
450 
451                             const drawinglayer::primitive2d::Primitive2DReference xTransform(
452                                 new drawinglayer::primitive2d::TransformPrimitive2D(
453                                     aTransform,
454                                     aSequence));
455 
456                             aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1);
457 
458                             // append to result
459                             drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aSequence);
460                         }
461                     }
462                 }
463             }
464         }
465 
466         const basegfx::B2DRange* SvgSvgNode::getCurrentViewPort() const
467         {
468             if(getViewBox())
469             {
470                 return getViewBox();
471             }
472             else
473             {
474                 return SvgNode::getCurrentViewPort();
475             }
476         }
477 
478     } // end of namespace svgreader
479 } // end of namespace svgio
480 
481 //////////////////////////////////////////////////////////////////////////////
482 // eof
483