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 
33 //////////////////////////////////////////////////////////////////////////////
34 
35 namespace svgio
36 {
37     namespace svgreader
38     {
39         SvgSvgNode::SvgSvgNode(
40             SvgDocument& rDocument,
41             SvgNode* pParent)
42         :   SvgNode(SVGTokenSvg, rDocument, pParent),
43             maSvgStyleAttributes(*this),
44             mpViewBox(0),
45             maSvgAspectRatio(),
46             maX(),
47             maY(),
48             maWidth(),
49             maHeight(),
50             maVersion()
51         {
52             if(!getParent())
53             {
54                 // initial fill is black
55                 maSvgStyleAttributes.setFill(SvgPaint(basegfx::BColor(0.0, 0.0, 0.0), true, true));
56             }
57         }
58 
59         SvgSvgNode::~SvgSvgNode()
60         {
61             if(mpViewBox) delete mpViewBox;
62         }
63 
64         const SvgStyleAttributes* SvgSvgNode::getSvgStyleAttributes() const
65         {
66             return &maSvgStyleAttributes;
67         }
68 
69         void SvgSvgNode::parseAttribute(const rtl::OUString& rTokenName, SVGToken aSVGToken, const rtl::OUString& aContent)
70         {
71             // call parent
72             SvgNode::parseAttribute(rTokenName, aSVGToken, aContent);
73 
74             // read style attributes
75             maSvgStyleAttributes.parseStyleAttribute(rTokenName, aSVGToken, aContent);
76 
77             // parse own
78             switch(aSVGToken)
79             {
80                 case SVGTokenStyle:
81                 {
82                     maSvgStyleAttributes.readStyle(aContent);
83                     break;
84                 }
85                 case SVGTokenViewBox:
86                 {
87                     const basegfx::B2DRange aRange(readViewBox(aContent, *this));
88 
89                     if(!aRange.isEmpty())
90                     {
91                         setViewBox(&aRange);
92                     }
93                     break;
94                 }
95                 case SVGTokenPreserveAspectRatio:
96                 {
97                     setSvgAspectRatio(readSvgAspectRatio(aContent));
98                     break;
99                 }
100                 case SVGTokenX:
101                 {
102                     SvgNumber aNum;
103 
104                     if(readSingleNumber(aContent, aNum))
105                     {
106                         setX(aNum);
107                     }
108                     break;
109                 }
110                 case SVGTokenY:
111                 {
112                     SvgNumber aNum;
113 
114                     if(readSingleNumber(aContent, aNum))
115                     {
116                         setY(aNum);
117                     }
118                     break;
119                 }
120                 case SVGTokenWidth:
121                 {
122                     SvgNumber aNum;
123 
124                     if(readSingleNumber(aContent, aNum))
125                     {
126                         if(aNum.isPositive())
127                         {
128                             setWidth(aNum);
129                         }
130                     }
131                     break;
132                 }
133                 case SVGTokenHeight:
134                 {
135                     SvgNumber aNum;
136 
137                     if(readSingleNumber(aContent, aNum))
138                     {
139                         if(aNum.isPositive())
140                         {
141                             setHeight(aNum);
142                         }
143                     }
144                     break;
145                 }
146                 case SVGTokenVersion:
147                 {
148                     SvgNumber aNum;
149 
150                     if(readSingleNumber(aContent, aNum))
151                     {
152                         setVersion(aNum);
153                     }
154                     break;
155                 }
156                 default:
157                 {
158                     break;
159                 }
160             }
161         }
162 
163         void SvgSvgNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DSequence& rTarget, bool bReferenced) const
164         {
165             drawinglayer::primitive2d::Primitive2DSequence aSequence;
166 
167             // decompose childs
168             SvgNode::decomposeSvgNode(aSequence, bReferenced);
169 
170             if(aSequence.hasElements())
171             {
172                 if(getParent())
173                 {
174                     if(getViewBox())
175                     {
176                         // Svg defines that with no width or no height the viewBox content is empty,
177                         // so both need to exist
178                         if(!basegfx::fTools::equalZero(getViewBox()->getWidth()) && !basegfx::fTools::equalZero(getViewBox()->getHeight()))
179                         {
180                             // create target range homing x,y, width and height as given
181                             const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0);
182                             const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0);
183                             const double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : getViewBox()->getWidth());
184                             const double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : getViewBox()->getHeight());
185                             const basegfx::B2DRange aTarget(fX, fY, fX + fW, fY + fH);
186 
187                             if(aTarget.equal(*getViewBox()))
188                             {
189                                 // no mapping needed, append
190                                 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aSequence);
191                             }
192                             else
193                             {
194                                 // create mapping
195                                 const SvgAspectRatio& rRatio = getSvgAspectRatio();
196 
197                                 if(rRatio.isSet())
198                                 {
199                                     // let mapping be created from SvgAspectRatio
200                                     const basegfx::B2DHomMatrix aEmbeddingTransform(
201                                         rRatio.createMapping(aTarget, *getViewBox()));
202 
203                                     // prepare embedding in transformation
204                                     const drawinglayer::primitive2d::Primitive2DReference xRef(
205                                         new drawinglayer::primitive2d::TransformPrimitive2D(
206                                             aEmbeddingTransform,
207                                             aSequence));
208 
209                                     if(rRatio.isMeetOrSlice())
210                                     {
211                                         // embed in transformation
212                                         drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xRef);
213                                     }
214                                     else
215                                     {
216                                         // need to embed in MaskPrimitive2D, too
217                                         const drawinglayer::primitive2d::Primitive2DReference xMask(
218                                             new drawinglayer::primitive2d::MaskPrimitive2D(
219                                                 basegfx::B2DPolyPolygon(basegfx::tools::createPolygonFromRect(aTarget)),
220                                                 drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1)));
221 
222                                         drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xMask);
223                                     }
224                                 }
225                                 else
226                                 {
227                                     // choose default mapping
228                                     const basegfx::B2DHomMatrix aEmbeddingTransform(
229                                         rRatio.createLinearMapping(
230                                             aTarget, *getViewBox()));
231 
232                                     // embed in transformation
233                                     const drawinglayer::primitive2d::Primitive2DReference xTransform(
234                                         new drawinglayer::primitive2d::TransformPrimitive2D(
235                                             aEmbeddingTransform,
236                                             aSequence));
237 
238                                     drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xTransform);
239                                 }
240                             }
241                         }
242                     }
243                     else
244                     {
245                         // check if we have a size
246                         const double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : 0.0);
247                         const double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : 0.0);
248 
249                         // Svg defines that a negative value is an error and that 0.0 disables rendering
250                         if(basegfx::fTools::more(fW, 0.0) && basegfx::fTools::more(fH, 0.0))
251                         {
252                             // check if we have a x,y position
253                             const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0);
254                             const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0);
255 
256                             if(!basegfx::fTools::equalZero(fX) || !basegfx::fTools::equalZero(fY))
257                             {
258                                 // embed in transform
259                                 const drawinglayer::primitive2d::Primitive2DReference xRef(
260                                     new drawinglayer::primitive2d::TransformPrimitive2D(
261                                         basegfx::tools::createTranslateB2DHomMatrix(fX, fY),
262                                         aSequence));
263 
264                                 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1);
265                             }
266 
267                             // embed in MaskPrimitive2D to clip
268                             const drawinglayer::primitive2d::Primitive2DReference xMask(
269                                 new drawinglayer::primitive2d::MaskPrimitive2D(
270                                     basegfx::B2DPolyPolygon(
271                                         basegfx::tools::createPolygonFromRect(
272                                             basegfx::B2DRange(fX, fY, fX + fW, fY + fH))),
273                                     aSequence));
274 
275                             // append
276                             drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xMask);
277                         }
278                     }
279                 }
280                 else
281                 {
282                     // Outermost SVG element; create target range homing width and height as given.
283                     // SVG defines that x,y has no meanig for the outermost SVG element. Use a fallback
284                     // width and height of din A 4 (21 x 29,7 cm)
285                     double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : (210.0 * 3.543307));
286                     double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : (297.0 * 3.543307));
287 
288                     // Svg defines that a negative value is an error and that 0.0 disables rendering
289                     if(basegfx::fTools::more(fW, 0.0) && basegfx::fTools::more(fH, 0.0))
290                     {
291                         const basegfx::B2DRange aSvgCanvasRange(0.0, 0.0, fW, fH);
292 
293                         if(getViewBox())
294                         {
295                             if(!basegfx::fTools::equalZero(getViewBox()->getWidth()) && !basegfx::fTools::equalZero(getViewBox()->getHeight()))
296                             {
297                                 // create mapping
298                                 const SvgAspectRatio& rRatio = getSvgAspectRatio();
299                                 basegfx::B2DHomMatrix aViewBoxMapping;
300 
301                                 if(rRatio.isSet())
302                                 {
303                                     // let mapping be created from SvgAspectRatio
304                                     aViewBoxMapping = rRatio.createMapping(aSvgCanvasRange, *getViewBox());
305 
306                                     // no need to check ratio here for slice, the outermost Svg will
307                                     // be clipped anyways (see below)
308                                 }
309                                 else
310                                 {
311                                     // choose default mapping
312                                     aViewBoxMapping = rRatio.createLinearMapping(aSvgCanvasRange, *getViewBox());
313                                 }
314 
315                                 // scale content to viewBox definitions
316                                 const drawinglayer::primitive2d::Primitive2DReference xTransform(
317                                     new drawinglayer::primitive2d::TransformPrimitive2D(
318                                         aViewBoxMapping,
319                                         aSequence));
320 
321                                 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1);
322                             }
323                         }
324 
325                         // to be completely correct in Svg sense it is necessary to clip
326                         // the whole content to the given canvas. I choose here to do this
327                         // initially despite I found various examples of Svg files out there
328                         // which have no correct values for this clipping. It's correct
329                         // due to the Svg spec.
330                         bool bDoCorrectCanvasClipping(true);
331 
332                         if(bDoCorrectCanvasClipping)
333                         {
334                             // different from Svg we have the possibility with primitives to get
335                             // a correct bounding box for the geometry, thhus I will allow to
336                             // only clip if necessary. This will make Svg images evtl. smaller
337                             // than wanted from Svg (the free space which may be around it is
338                             // conform to the Svg spec), but avoids an expensive and unneccessary
339                             // clip.
340                             const basegfx::B2DRange aContentRange(
341                                 drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence(
342                                     aSequence,
343                                     drawinglayer::geometry::ViewInformation2D()));
344 
345                             if(!aSvgCanvasRange.isInside(aContentRange))
346                             {
347                                 const drawinglayer::primitive2d::Primitive2DReference xMask(
348                                     new drawinglayer::primitive2d::MaskPrimitive2D(
349                                         basegfx::B2DPolyPolygon(
350                                             basegfx::tools::createPolygonFromRect(
351                                                 aSvgCanvasRange)),
352                                         aSequence));
353 
354                                 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xMask, 1);
355                             }
356                         }
357 
358                         {
359                             // embed in transform primitive to scale to 1/100th mm
360                             // where 1 mm == 3.543307 px to get from Svg coordinates to
361                             // drawinglayer ones
362                             const double fScaleTo100thmm(100.0 / 3.543307);
363                             const basegfx::B2DHomMatrix aTransform(
364                                 basegfx::tools::createScaleB2DHomMatrix(
365                                     fScaleTo100thmm,
366                                     fScaleTo100thmm));
367 
368                             const drawinglayer::primitive2d::Primitive2DReference xTransform(
369                                 new drawinglayer::primitive2d::TransformPrimitive2D(
370                                     aTransform,
371                                     aSequence));
372 
373                             aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1);
374                         }
375 
376                         // append
377                         drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aSequence);
378                     }
379                 }
380             }
381         }
382 
383         const basegfx::B2DRange* SvgSvgNode::getCurrentViewPort() const
384         {
385             if(getViewBox())
386             {
387                 return getViewBox();
388             }
389             else
390             {
391                 return SvgNode::getCurrentViewPort();
392             }
393         }
394 
395     } // end of namespace svgreader
396 } // end of namespace svgio
397 
398 //////////////////////////////////////////////////////////////////////////////
399 // eof
400