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/svgimagenode.hxx>
26 #include <svgio/svgreader/svgdocument.hxx>
27 #include <sax/tools/converter.hxx>
28 #include <tools/stream.hxx>
29 #include <vcl/bitmapex.hxx>
30 #include <svtools/filter.hxx>
31 #include <basegfx/matrix/b2dhommatrixtools.hxx>
32 #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
33 #include <drawinglayer/primitive2d/groupprimitive2d.hxx>
34 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
35 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
36 #include <basegfx/polygon/b2dpolygontools.hxx>
37 #include <basegfx/polygon/b2dpolygon.hxx>
38 #include <rtl/uri.hxx>
39 #include <drawinglayer/geometry/viewinformation2d.hxx>
40 
41 //////////////////////////////////////////////////////////////////////////////
42 
43 namespace svgio
44 {
45     namespace svgreader
46     {
47         SvgImageNode::SvgImageNode(
48             SvgDocument& rDocument,
49             SvgNode* pParent)
50         :   SvgNode(SVGTokenRect, rDocument, pParent),
51             maSvgStyleAttributes(*this),
52             maSvgAspectRatio(),
53             mpaTransform(0),
54             maX(0),
55             maY(0),
56             maWidth(0),
57             maHeight(0),
58             maXLink(),
59             maUrl(),
60             maMimeType(),
61             maData()
62         {
63         }
64 
65         SvgImageNode::~SvgImageNode()
66         {
67             if(mpaTransform) delete mpaTransform;
68         }
69 
70         const SvgStyleAttributes* SvgImageNode::getSvgStyleAttributes() const
71         {
72             static rtl::OUString aClassStr(rtl::OUString::createFromAscii("image"));
73 
74             return checkForCssStyle(aClassStr, maSvgStyleAttributes);
75         }
76 
77         void SvgImageNode::parseAttribute(const rtl::OUString& rTokenName, SVGToken aSVGToken, const rtl::OUString& aContent)
78         {
79             // call parent
80             SvgNode::parseAttribute(rTokenName, aSVGToken, aContent);
81 
82             // read style attributes
83             maSvgStyleAttributes.parseStyleAttribute(rTokenName, aSVGToken, aContent);
84 
85             // parse own
86             switch(aSVGToken)
87             {
88                 case SVGTokenStyle:
89                 {
90                     maSvgStyleAttributes.readStyle(aContent);
91                     break;
92                 }
93                 case SVGTokenPreserveAspectRatio:
94                 {
95                     setSvgAspectRatio(readSvgAspectRatio(aContent));
96                     break;
97                 }
98                 case SVGTokenTransform:
99                 {
100                     const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
101 
102                     if(!aMatrix.isIdentity())
103                     {
104                         setTransform(&aMatrix);
105                     }
106                     break;
107                 }
108                 case SVGTokenX:
109                 {
110                     SvgNumber aNum;
111 
112                     if(readSingleNumber(aContent, aNum))
113                     {
114                         setX(aNum);
115                     }
116                     break;
117                 }
118                 case SVGTokenY:
119                 {
120                     SvgNumber aNum;
121 
122                     if(readSingleNumber(aContent, aNum))
123                     {
124                         setY(aNum);
125                     }
126                     break;
127                 }
128                 case SVGTokenWidth:
129                 {
130                     SvgNumber aNum;
131 
132                     if(readSingleNumber(aContent, aNum))
133                     {
134                         if(aNum.isPositive())
135                         {
136                             setWidth(aNum);
137                         }
138                     }
139                     break;
140                 }
141                 case SVGTokenHeight:
142                 {
143                     SvgNumber aNum;
144 
145                     if(readSingleNumber(aContent, aNum))
146                     {
147                         if(aNum.isPositive())
148                         {
149                             setHeight(aNum);
150                         }
151                     }
152                     break;
153                 }
154                 case SVGTokenXlinkHref:
155                 {
156                     const sal_Int32 nLen(aContent.getLength());
157 
158                     if(nLen)
159                     {
160                         readImageLink(aContent, maXLink, maUrl, maMimeType, maData);
161                     }
162                     break;
163                 }
164                 default:
165                 {
166                     break;
167                 }
168             }
169         }
170 
171         void extractFromGraphic(
172             const Graphic& rGraphic,
173             drawinglayer::primitive2d::Primitive2DSequence& rEmbedded,
174             basegfx::B2DRange& rViewBox,
175             BitmapEx& rBitmapEx)
176         {
177             if(GRAPHIC_BITMAP == rGraphic.GetType())
178             {
179                 if(rGraphic.getSvgData().get())
180                 {
181                     // embedded Svg
182                     rEmbedded = rGraphic.getSvgData()->getPrimitive2DSequence();
183 
184                     // fill aViewBox
185                     rViewBox = rGraphic.getSvgData()->getRange();
186                 }
187                 else
188                 {
189                     // get bitmap
190                     rBitmapEx = rGraphic.GetBitmapEx();
191                 }
192             }
193             else
194             {
195                 // evtl. convert to bitmap
196                 rBitmapEx = rGraphic.GetBitmapEx();
197             }
198         }
199 
200         void SvgImageNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DSequence& rTarget, bool /*bReferenced*/) const
201         {
202             // get size range and create path
203             const SvgStyleAttributes* pStyle = getSvgStyleAttributes();
204 
205             if(pStyle && getWidth().isSet() && getHeight().isSet())
206             {
207                 const double fWidth(getWidth().solve(*this, xcoordinate));
208                 const double fHeight(getHeight().solve(*this, ycoordinate));
209 
210                 if(fWidth > 0.0 && fHeight > 0.0)
211                 {
212                     BitmapEx aBitmapEx;
213                     drawinglayer::primitive2d::Primitive2DSequence aNewTarget;
214 
215                     // prepare Target and ViewBox for evtl. AspectRatio mappings
216                     const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0);
217                     const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0);
218                     const basegfx::B2DRange aTarget(fX, fY, fX + fWidth, fY + fHeight);
219                     basegfx::B2DRange aViewBox(aTarget);
220 
221                     if(maMimeType.getLength() && maData.getLength())
222                     {
223                         // use embedded base64 encoded data
224                         ::com::sun::star::uno::Sequence< sal_Int8 > aPass;
225                         ::sax::Converter::decodeBase64(aPass, maData);
226 
227                         if(aPass.hasElements())
228                         {
229                             SvMemoryStream aStream(aPass.getArray(), aPass.getLength(), STREAM_READ);
230                             Graphic aGraphic;
231 
232                             if(GRFILTER_OK == GraphicFilter::GetGraphicFilter()->ImportGraphic(
233                                 aGraphic,
234                                 String(),
235                                 aStream))
236                             {
237                                 extractFromGraphic(aGraphic, aNewTarget, aViewBox, aBitmapEx);
238                             }
239                         }
240                     }
241                     else if(maUrl.getLength())
242                     {
243                         const rtl::OUString& rPath = getDocument().getAbsolutePath();
244 
245                         if(rPath.getLength())
246                         {
247                             const rtl::OUString aAbsUrl(rtl::Uri::convertRelToAbs(rPath, maUrl));
248 
249                             if(aAbsUrl.getLength())
250                             {
251                                 SvFileStream aStream(aAbsUrl, STREAM_STD_READ);
252                                 Graphic aGraphic;
253 
254                                 if(GRFILTER_OK == GraphicFilter::GetGraphicFilter()->ImportGraphic(
255                                     aGraphic,
256                                     aAbsUrl,
257                                     aStream))
258                                 {
259                                     extractFromGraphic(aGraphic, aNewTarget, aViewBox, aBitmapEx);
260                                 }
261                             }
262                         }
263                         else
264                         {
265                             // #123042# detect missing path and assert - content will be missing. The
266                             // absolute path to itself needs to be set to correctly import linked
267                             // content in a SVG file
268                             OSL_ENSURE(false, "SVG graphic with internal links is interpreted, but local AbsolutePath is not set: linked content will be missing (!)");
269                         }
270                     }
271                     else if(maXLink.getLength())
272                     {
273                         const SvgNode* mpXLink = getDocument().findSvgNodeById(maXLink);
274 
275                         if(mpXLink && Display_none != mpXLink->getDisplay())
276                         {
277                             mpXLink->decomposeSvgNode(aNewTarget, true);
278 
279                             if(aNewTarget.hasElements())
280                             {
281                                 aViewBox = drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence(
282                                     aNewTarget,
283                                     drawinglayer::geometry::ViewInformation2D());
284                             }
285                         }
286                     }
287 
288                     if(!aBitmapEx.IsEmpty())
289                     {
290                         // create content from created bitmap
291                         aNewTarget.realloc(1);
292                         aNewTarget[0] = new drawinglayer::primitive2d::BitmapPrimitive2D(
293                             aBitmapEx,
294                             basegfx::B2DHomMatrix());
295 
296                         // fill aViewBox. No size set yet, use unit size
297                         aViewBox = basegfx::B2DRange(0.0, 0.0, 1.0, 1.0);
298                     }
299 
300                     if(aNewTarget.hasElements())
301                     {
302                         if(aTarget.equal(aViewBox))
303                         {
304                             // just add to rTarget
305                             drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aNewTarget);
306                         }
307                         else
308                         {
309                             // create mapping
310                             const SvgAspectRatio& rRatio = getSvgAspectRatio();
311 
312                             if(rRatio.isSet())
313                             {
314                                 // let mapping be created from SvgAspectRatio
315                                 const basegfx::B2DHomMatrix aEmbeddingTransform(rRatio.createMapping(aTarget, aViewBox));
316 
317                                 if(!aEmbeddingTransform.isIdentity())
318                                 {
319                                     const drawinglayer::primitive2d::Primitive2DReference xRef(
320                                         new drawinglayer::primitive2d::TransformPrimitive2D(
321                                             aEmbeddingTransform,
322                                             aNewTarget));
323 
324                                     aNewTarget = drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1);
325                                 }
326 
327                                 if(!rRatio.isMeetOrSlice())
328                                 {
329                                     // need to embed in MaskPrimitive2D to ensure clipping
330                                     const drawinglayer::primitive2d::Primitive2DReference xMask(
331                                         new drawinglayer::primitive2d::MaskPrimitive2D(
332                                             basegfx::B2DPolyPolygon(
333                                                 basegfx::tools::createPolygonFromRect(aTarget)),
334                                             aNewTarget));
335 
336                                     aNewTarget = drawinglayer::primitive2d::Primitive2DSequence(&xMask, 1);
337                                 }
338                             }
339                             else
340                             {
341                                 // choose default mapping
342                                 const basegfx::B2DHomMatrix aEmbeddingTransform(rRatio.createLinearMapping(aTarget, aViewBox));
343 
344                                 if(!aEmbeddingTransform.isIdentity())
345                                 {
346                                     const drawinglayer::primitive2d::Primitive2DReference xRef(
347                                         new drawinglayer::primitive2d::TransformPrimitive2D(
348                                             aEmbeddingTransform,
349                                             aNewTarget));
350 
351                                     aNewTarget = drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1);
352                                 }
353                             }
354 
355                             // embed and add to rTarget, take local extra-transform into account
356                             pStyle->add_postProcess(rTarget, aNewTarget, getTransform());
357                         }
358                     }
359                 }
360             }
361         }
362 
363     } // end of namespace svgreader
364 } // end of namespace svgio
365 
366 //////////////////////////////////////////////////////////////////////////////
367 // eof
368