Coverage Report

Created: 2026-03-31 11:00

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