Coverage Report

Created: 2025-07-07 10:01

/src/libreoffice/svgio/source/svgreader/svgimagenode.cxx
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <svgimagenode.hxx>
21
#include <svgdocument.hxx>
22
#include <tools/stream.hxx>
23
#include <vcl/bitmapex.hxx>
24
#include <vcl/graphicfilter.hxx>
25
#include <basegfx/matrix/b2dhommatrixtools.hxx>
26
#include <drawinglayer/primitive2d/bitmapprimitive2d.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 <rtl/uri.hxx>
32
#include <sal/log.hxx>
33
#include <drawinglayer/geometry/viewinformation2d.hxx>
34
#include <comphelper/base64.hxx>
35
#include <toolkit/helper/vclunohelper.hxx>
36
37
namespace svgio::svgreader
38
{
39
        SvgImageNode::SvgImageNode(
40
            SvgDocument& rDocument,
41
            SvgNode* pParent)
42
0
        :   SvgNode(SVGToken::Rect, rDocument, pParent),
43
0
            maSvgStyleAttributes(*this),
44
0
            maX(0),
45
0
            maY(0),
46
0
            maWidth(0),
47
0
            maHeight(0)
48
0
        {
49
0
        }
50
51
        SvgImageNode::~SvgImageNode()
52
0
        {
53
0
        }
54
55
        const SvgStyleAttributes* SvgImageNode::getSvgStyleAttributes() const
56
0
        {
57
0
            return checkForCssStyle(maSvgStyleAttributes);
58
0
        }
59
60
        void SvgImageNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
61
0
        {
62
            // call parent
63
0
            SvgNode::parseAttribute(aSVGToken, aContent);
64
65
            // read style attributes
66
0
            maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
67
68
            // parse own
69
0
            switch(aSVGToken)
70
0
            {
71
0
                case SVGToken::Style:
72
0
                {
73
0
                    readLocalCssStyle(aContent);
74
0
                    break;
75
0
                }
76
0
                case SVGToken::PreserveAspectRatio:
77
0
                {
78
0
                    maSvgAspectRatio = readSvgAspectRatio(aContent);
79
0
                    break;
80
0
                }
81
0
                case SVGToken::Transform:
82
0
                {
83
0
                    const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
84
85
0
                    if(!aMatrix.isIdentity())
86
0
                    {
87
0
                        setTransform(aMatrix);
88
0
                    }
89
0
                    break;
90
0
                }
91
0
                case SVGToken::X:
92
0
                {
93
0
                    SvgNumber aNum;
94
95
0
                    if(readSingleNumber(aContent, aNum))
96
0
                    {
97
0
                        maX = aNum;
98
0
                    }
99
0
                    break;
100
0
                }
101
0
                case SVGToken::Y:
102
0
                {
103
0
                    SvgNumber aNum;
104
105
0
                    if(readSingleNumber(aContent, aNum))
106
0
                    {
107
0
                        maY = aNum;
108
0
                    }
109
0
                    break;
110
0
                }
111
0
                case SVGToken::Width:
112
0
                {
113
0
                    SvgNumber aNum;
114
115
0
                    if(readSingleNumber(aContent, aNum))
116
0
                    {
117
0
                        if(aNum.isPositive())
118
0
                        {
119
0
                            maWidth = aNum;
120
0
                        }
121
0
                    }
122
0
                    break;
123
0
                }
124
0
                case SVGToken::Height:
125
0
                {
126
0
                    SvgNumber aNum;
127
128
0
                    if(readSingleNumber(aContent, aNum))
129
0
                    {
130
0
                        if(aNum.isPositive())
131
0
                        {
132
0
                            maHeight = aNum;
133
0
                        }
134
0
                    }
135
0
                    break;
136
0
                }
137
0
                case SVGToken::Href:
138
0
                case SVGToken::XlinkHref:
139
0
                {
140
0
                    const sal_Int32 nLen(aContent.getLength());
141
142
0
                    if(nLen)
143
0
                    {
144
0
                        readImageLink(aContent, maXLink, maUrl, maData);
145
0
                    }
146
0
                    break;
147
0
                }
148
0
                default:
149
0
                {
150
0
                    break;
151
0
                }
152
0
            }
153
0
        }
154
155
        static void extractFromGraphic(
156
            const Graphic& rGraphic,
157
            drawinglayer::primitive2d::Primitive2DContainer& rEmbedded,
158
            basegfx::B2DRange& rViewBox,
159
            BitmapEx& rBitmapEx)
160
0
        {
161
0
            if(GraphicType::Bitmap == rGraphic.GetType())
162
0
            {
163
0
                if(rGraphic.getVectorGraphicData())
164
0
                {
165
                    // embedded Svg
166
0
                    rEmbedded = rGraphic.getVectorGraphicData()->getPrimitive2DSequence();
167
168
                    // fill aViewBox
169
0
                    rViewBox = rGraphic.getVectorGraphicData()->getRange();
170
0
                }
171
0
                else
172
0
                {
173
                    // get bitmap
174
0
                    rBitmapEx = rGraphic.GetBitmapEx();
175
0
                }
176
0
            }
177
0
            else
178
0
            {
179
                // evtl. convert to bitmap
180
0
                rBitmapEx = rGraphic.GetBitmapEx();
181
0
            }
182
0
        }
183
184
        void SvgImageNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool /*bReferenced*/) const
185
0
        {
186
            // get size range and create path
187
0
            const SvgStyleAttributes* pStyle = getSvgStyleAttributes();
188
189
0
            if(!(pStyle && getWidth().isSet() && getHeight().isSet()))
190
0
                return;
191
192
0
            const double fWidth(getWidth().solve(*this, NumberType::xcoordinate));
193
0
            const double fHeight(getHeight().solve(*this, NumberType::ycoordinate));
194
195
0
            if(fWidth <= 0.0 || fHeight <= 0.0)
196
0
                return;
197
198
0
            BitmapEx aBitmapEx;
199
0
            drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
200
201
            // prepare Target and ViewBox for evtl. AspectRatio mappings
202
0
            const double fX(getX().isSet() ? getX().solve(*this, NumberType::xcoordinate) : 0.0);
203
0
            const double fY(getY().isSet() ? getY().solve(*this, NumberType::ycoordinate) : 0.0);
204
0
            const basegfx::B2DRange aTarget(fX, fY, fX + fWidth, fY + fHeight);
205
0
            basegfx::B2DRange aViewBox(aTarget);
206
207
0
            if(!maData.isEmpty())
208
0
            {
209
                // use embedded base64 encoded data
210
0
                css::uno::Sequence< sal_Int8 > aPass;
211
0
                ::comphelper::Base64::decode(aPass, maData);
212
213
0
                if(aPass.hasElements())
214
0
                {
215
0
                    SvMemoryStream aStream(aPass.getArray(), aPass.getLength(), StreamMode::READ);
216
0
                    Graphic aGraphic;
217
218
0
                    if(ERRCODE_NONE == GraphicFilter::GetGraphicFilter().ImportGraphic(
219
0
                        aGraphic,
220
0
                        u"",
221
0
                        aStream))
222
0
                    {
223
0
                        extractFromGraphic(aGraphic, aNewTarget, aViewBox, aBitmapEx);
224
0
                    }
225
0
                }
226
0
            }
227
0
            else if(!maUrl.isEmpty())
228
0
            {
229
0
                const OUString& rPath = getDocument().getAbsolutePath();
230
0
                OUString aAbsUrl;
231
0
                try {
232
0
                    aAbsUrl = rtl::Uri::convertRelToAbs(rPath, maUrl);
233
0
                } catch (rtl::MalformedUriException & e) {
234
0
                    SAL_WARN(
235
0
                        "svg",
236
0
                        "caught rtl::MalformedUriException \""
237
0
                            << e.getMessage() << "\"");
238
0
                }
239
240
0
                if (!aAbsUrl.isEmpty() && rPath != aAbsUrl)
241
0
                {
242
0
                    SvFileStream aStream(aAbsUrl, StreamMode::STD_READ);
243
0
                    Graphic aGraphic;
244
245
0
                    if(ERRCODE_NONE == GraphicFilter::GetGraphicFilter().ImportGraphic(
246
0
                           aGraphic,
247
0
                           aAbsUrl,
248
0
                           aStream))
249
0
                    {
250
0
                        extractFromGraphic(aGraphic, aNewTarget, aViewBox, aBitmapEx);
251
0
                    }
252
0
                }
253
0
            }
254
0
            else if(!maXLink.isEmpty())
255
0
            {
256
0
                const SvgNode* pXLink = getDocument().findSvgNodeById(maXLink);
257
258
0
                if(pXLink && Display::None != pXLink->getDisplay())
259
0
                {
260
0
                    pXLink->decomposeSvgNode(aNewTarget, true);
261
262
0
                    if(!aNewTarget.empty())
263
0
                    {
264
0
                        aViewBox = aNewTarget.getB2DRange(drawinglayer::geometry::ViewInformation2D());
265
0
                    }
266
0
                }
267
0
            }
268
269
0
            if(!aBitmapEx.IsEmpty() && 0 != aBitmapEx.GetSizePixel().Width()  && 0 != aBitmapEx.GetSizePixel().Height())
270
0
            {
271
                // calculate centered unit size
272
0
                const double fAspectRatio = static_cast<double>(aBitmapEx.GetSizePixel().Width()) / static_cast<double>(aBitmapEx.GetSizePixel().Height());
273
274
0
                if (basegfx::fTools::equalZero(fAspectRatio))
275
0
                {
276
                    // use unit range
277
0
                    aViewBox = basegfx::B2DRange(0.0, 0.0, 1.0, 1.0);
278
0
                }
279
0
                else if (fAspectRatio > 0.0)
280
0
                {
281
                    // width bigger height
282
0
                    const double fHalfHeight((1.0 / fAspectRatio) * 0.5);
283
0
                    aViewBox = basegfx::B2DRange(
284
0
                        0.0,
285
0
                        0.5 - fHalfHeight,
286
0
                        1.0,
287
0
                        0.5 + fHalfHeight);
288
0
                }
289
0
                else
290
0
                {
291
                    // height bigger width
292
0
                    const double fHalfWidth(fAspectRatio * 0.5);
293
0
                    aViewBox = basegfx::B2DRange(
294
0
                        0.5 - fHalfWidth,
295
0
                        0.0,
296
0
                        0.5 + fHalfWidth,
297
0
                        1.0);
298
0
                }
299
300
                // create content from created bitmap, use calculated unit range size
301
                // as transformation to map the picture data correctly
302
0
                aNewTarget.resize(1);
303
0
                aNewTarget[0] = new drawinglayer::primitive2d::BitmapPrimitive2D(
304
0
                    aBitmapEx,
305
0
                    basegfx::utils::createScaleTranslateB2DHomMatrix(
306
0
                        aViewBox.getRange(),
307
0
                        aViewBox.getMinimum()));
308
0
            }
309
310
0
            if(aNewTarget.empty())
311
0
                return;
312
313
            // create mapping
314
0
            const SvgAspectRatio& rRatio = maSvgAspectRatio;
315
316
            // even when ratio is not set, use the defaults
317
            // let mapping be created from SvgAspectRatio
318
0
            const basegfx::B2DHomMatrix aEmbeddingTransform(rRatio.createMapping(aTarget, aViewBox));
319
320
0
            if(!aEmbeddingTransform.isIdentity())
321
0
            {
322
0
                const drawinglayer::primitive2d::Primitive2DReference xRef(
323
0
                    new drawinglayer::primitive2d::TransformPrimitive2D(
324
0
                        aEmbeddingTransform,
325
0
                        std::move(aNewTarget)));
326
327
0
                aNewTarget = drawinglayer::primitive2d::Primitive2DContainer { xRef };
328
0
            }
329
330
0
            if(!rRatio.isMeetOrSlice())
331
0
            {
332
                // need to embed in MaskPrimitive2D to ensure clipping
333
0
                const drawinglayer::primitive2d::Primitive2DReference xMask(
334
0
                    new drawinglayer::primitive2d::MaskPrimitive2D(
335
0
                        basegfx::B2DPolyPolygon(
336
0
                            basegfx::utils::createPolygonFromRect(aTarget)),
337
0
                        std::move(aNewTarget)));
338
339
0
                aNewTarget = drawinglayer::primitive2d::Primitive2DContainer { xMask };
340
0
            }
341
342
            // embed and add to rTarget, take local extra-transform into account
343
0
            pStyle->add_postProcess(rTarget, std::move(aNewTarget), getTransform());
344
0
        }
345
346
} // end of namespace svgio::svgreader
347
348
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */