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