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             }
157         }
158 
159         void SvgSvgNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DSequence& rTarget, bool bReferenced) const
160         {
161             drawinglayer::primitive2d::Primitive2DSequence aSequence;
162 
163             // decompose childs
164             SvgNode::decomposeSvgNode(aSequence, bReferenced);
165 
166             if(aSequence.hasElements())
167             {
168                 if(getParent())
169                 {
170                     if(getViewBox())
171                     {
172                         // Svg defines that with no width or no height the viewBox content is empty,
173                         // so both need to exist
174                         if(!basegfx::fTools::equalZero(getViewBox()->getWidth()) && !basegfx::fTools::equalZero(getViewBox()->getHeight()))
175                         {
176                             // create target range homing x,y, width and height as given
177                             const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0);
178                             const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0);
179                             const double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : getViewBox()->getWidth());
180                             const double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : getViewBox()->getHeight());
181                             const basegfx::B2DRange aTarget(fX, fY, fX + fW, fY + fH);
182 
183                             if(aTarget.equal(*getViewBox()))
184                             {
185                                 // no mapping needed, append
186                                 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aSequence);
187                             }
188                             else
189                             {
190                                 // create mapping
191                                 const SvgAspectRatio& rRatio = getSvgAspectRatio();
192 
193                                 if(rRatio.isSet())
194                                 {
195                                     // let mapping be created from SvgAspectRatio
196                                     const basegfx::B2DHomMatrix aEmbeddingTransform(
197                                         rRatio.createMapping(aTarget, *getViewBox()));
198 
199                                     // prepare embedding in transformation
200                                     const drawinglayer::primitive2d::Primitive2DReference xRef(
201                                         new drawinglayer::primitive2d::TransformPrimitive2D(
202                                             aEmbeddingTransform,
203                                             aSequence));
204 
205                                     if(rRatio.isMeetOrSlice())
206                                     {
207                                         // embed in transformation
208                                         drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xRef);
209                                     }
210                                     else
211                                     {
212                                         // need to embed in MaskPrimitive2D, too
213                                         const drawinglayer::primitive2d::Primitive2DReference xMask(
214                                             new drawinglayer::primitive2d::MaskPrimitive2D(
215                                                 basegfx::B2DPolyPolygon(basegfx::tools::createPolygonFromRect(aTarget)),
216                                                 drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1)));
217 
218                                         drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xMask);
219                                     }
220                                 }
221                                 else
222                                 {
223                                     // choose default mapping
224                                     const basegfx::B2DHomMatrix aEmbeddingTransform(
225                                         rRatio.createLinearMapping(
226                                             aTarget, *getViewBox()));
227 
228                                     // embed in transformation
229                                     const drawinglayer::primitive2d::Primitive2DReference xTransform(
230                                         new drawinglayer::primitive2d::TransformPrimitive2D(
231                                             aEmbeddingTransform,
232                                             aSequence));
233 
234                                     drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xTransform);
235                                 }
236                             }
237                         }
238                     }
239                     else
240                     {
241                         // check if we have a size
242                         const double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : 0.0);
243                         const double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : 0.0);
244 
245                         // Svg defines that a negative value is an error and that 0.0 disables rendering
246                         if(basegfx::fTools::more(fW, 0.0) && basegfx::fTools::more(fH, 0.0))
247                         {
248                             // check if we have a x,y position
249                             const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0);
250                             const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0);
251 
252                             if(!basegfx::fTools::equalZero(fX) || !basegfx::fTools::equalZero(fY))
253                             {
254                                 // embed in transform
255                                 const drawinglayer::primitive2d::Primitive2DReference xRef(
256                                     new drawinglayer::primitive2d::TransformPrimitive2D(
257                                         basegfx::tools::createTranslateB2DHomMatrix(fX, fY),
258                                         aSequence));
259 
260                                 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1);
261                             }
262 
263                             // embed in MaskPrimitive2D to clip
264                             const drawinglayer::primitive2d::Primitive2DReference xMask(
265                                 new drawinglayer::primitive2d::MaskPrimitive2D(
266                                     basegfx::B2DPolyPolygon(
267                                         basegfx::tools::createPolygonFromRect(
268                                             basegfx::B2DRange(fX, fY, fX + fW, fY + fH))),
269                                     aSequence));
270 
271                             // append
272                             drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xMask);
273                         }
274                     }
275                 }
276                 else
277                 {
278                     // Outermost SVG element; create target range homing width and height as given.
279                     // SVG defines that x,y has no meanig for the outermost SVG element. Use a fallback
280                     // width and height of din A 4 (21 x 29,7 cm)
281                     double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : (210.0 * 3.543307));
282                     double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : (297.0 * 3.543307));
283 
284                     // Svg defines that a negative value is an error and that 0.0 disables rendering
285                     if(basegfx::fTools::more(fW, 0.0) && basegfx::fTools::more(fH, 0.0))
286                     {
287                         const basegfx::B2DRange aSvgCanvasRange(0.0, 0.0, fW, fH);
288 
289                         if(getViewBox())
290                         {
291                             if(!basegfx::fTools::equalZero(getViewBox()->getWidth()) && !basegfx::fTools::equalZero(getViewBox()->getHeight()))
292                             {
293                                 // create mapping
294                                 const SvgAspectRatio& rRatio = getSvgAspectRatio();
295                                 basegfx::B2DHomMatrix aViewBoxMapping;
296 
297                                 if(rRatio.isSet())
298                                 {
299                                     // let mapping be created from SvgAspectRatio
300                                     aViewBoxMapping = rRatio.createMapping(aSvgCanvasRange, *getViewBox());
301 
302                                     // no need to check ratio here for slice, the outermost Svg will
303                                     // be clipped anyways (see below)
304                                 }
305                                 else
306                                 {
307                                     // choose default mapping
308                                     aViewBoxMapping = rRatio.createLinearMapping(aSvgCanvasRange, *getViewBox());
309                                 }
310 
311                                 // scale content to viewBox definitions
312                                 const drawinglayer::primitive2d::Primitive2DReference xTransform(
313                                     new drawinglayer::primitive2d::TransformPrimitive2D(
314                                         aViewBoxMapping,
315                                         aSequence));
316 
317                                 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1);
318                             }
319                         }
320 
321                         // to be completely correct in Svg sense it is necessary to clip
322                         // the whole content to the given canvas. I choose here to do this
323                         // initially despite I found various examples of Svg files out there
324                         // which have no correct values for this clipping. It's correct
325                         // due to the Svg spec.
326                         bool bDoCorrectCanvasClipping(true);
327 
328                         if(bDoCorrectCanvasClipping)
329                         {
330                             // different from Svg we have the possibility with primitives to get
331                             // a correct bounding box for the geometry, thhus I will allow to
332                             // only clip if necessary. This will make Svg images evtl. smaller
333                             // than wanted from Svg (the free space which may be around it is
334                             // conform to the Svg spec), but avoids an expensive and unneccessary
335                             // clip.
336                             const basegfx::B2DRange aContentRange(
337                                 drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence(
338                                     aSequence,
339                                     drawinglayer::geometry::ViewInformation2D()));
340 
341                             if(!aSvgCanvasRange.isInside(aContentRange))
342                             {
343                                 const drawinglayer::primitive2d::Primitive2DReference xMask(
344                                     new drawinglayer::primitive2d::MaskPrimitive2D(
345                                         basegfx::B2DPolyPolygon(
346                                             basegfx::tools::createPolygonFromRect(
347                                                 aSvgCanvasRange)),
348                                         aSequence));
349 
350                                 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xMask, 1);
351                             }
352                         }
353 
354                         {
355                             // embed in transform primitive to scale to 1/100th mm
356                             // where 1 mm == 3.543307 px to get from Svg coordinates to
357                             // drawinglayer ones
358                             const double fScaleTo100thmm(100.0 / 3.543307);
359                             const basegfx::B2DHomMatrix aTransform(
360                                 basegfx::tools::createScaleB2DHomMatrix(
361                                     fScaleTo100thmm,
362                                     fScaleTo100thmm));
363 
364                             const drawinglayer::primitive2d::Primitive2DReference xTransform(
365                                 new drawinglayer::primitive2d::TransformPrimitive2D(
366                                     aTransform,
367                                     aSequence));
368 
369                             aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1);
370                         }
371 
372                         // append
373                         drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aSequence);
374                     }
375                 }
376             }
377         }
378 
379         const basegfx::B2DRange* SvgSvgNode::getCurrentViewPort() const
380         {
381             if(getViewBox())
382             {
383                 return getViewBox();
384             }
385             else
386             {
387                 return SvgNode::getCurrentViewPort();
388             }
389         }
390 
391     } // end of namespace svgreader
392 } // end of namespace svgio
393 
394 //////////////////////////////////////////////////////////////////////////////
395 // eof
396