1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <sal/config.h>
24 #include <svgstyleattributes.hxx>
25 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
26 #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
27 #include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx>
28 #include <svgnode.hxx>
29 #include <svgdocument.hxx>
30 #include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
31 #include <svggradientnode.hxx>
32 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
33 #include <basegfx/vector/b2enums.hxx>
34 #include <drawinglayer/processor2d/linegeometryextractor2d.hxx>
35 #include <drawinglayer/processor2d/textaspolygonextractor2d.hxx>
36 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
37 #include <svgclippathnode.hxx>
38 #include <svgmasknode.hxx>
39 #include <basegfx/polygon/b2dpolypolygontools.hxx>
40 #include <svgmarkernode.hxx>
41 #include <svgpatternnode.hxx>
42 #include <drawinglayer/primitive2d/patternfillprimitive2d.hxx>
43 #include <basegfx/polygon/b2dpolygontools.hxx>
44 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
45 #include <drawinglayer/primitive2d/pagehierarchyprimitive2d.hxx>
46 #include <o3tl/string_view.hxx>
47 #include <o3tl/unit_conversion.hxx>
49 const int nStyleDepthLimit
= 1024;
51 namespace svgio::svgreader
53 static basegfx::B2DLineJoin
StrokeLinejoinToB2DLineJoin(StrokeLinejoin aStrokeLinejoin
)
55 if(StrokeLinejoin::round
== aStrokeLinejoin
)
57 return basegfx::B2DLineJoin::Round
;
59 else if(StrokeLinejoin::bevel
== aStrokeLinejoin
)
61 return basegfx::B2DLineJoin::Bevel
;
64 return basegfx::B2DLineJoin::Miter
;
67 static css::drawing::LineCap
StrokeLinecapToDrawingLineCap(StrokeLinecap aStrokeLinecap
)
69 switch(aStrokeLinecap
)
71 default: /* StrokeLinecap::notset, StrokeLinecap::butt */
73 return css::drawing::LineCap_BUTT
;
75 case StrokeLinecap::round
:
77 return css::drawing::LineCap_ROUND
;
79 case StrokeLinecap::square
:
81 return css::drawing::LineCap_SQUARE
;
86 FontStretch
getWider(FontStretch aSource
)
90 case FontStretch::ultra_condensed
: aSource
= FontStretch::extra_condensed
; break;
91 case FontStretch::extra_condensed
: aSource
= FontStretch::condensed
; break;
92 case FontStretch::condensed
: aSource
= FontStretch::semi_condensed
; break;
93 case FontStretch::semi_condensed
: aSource
= FontStretch::normal
; break;
94 case FontStretch::normal
: aSource
= FontStretch::semi_expanded
; break;
95 case FontStretch::semi_expanded
: aSource
= FontStretch::expanded
; break;
96 case FontStretch::expanded
: aSource
= FontStretch::extra_expanded
; break;
97 case FontStretch::extra_expanded
: aSource
= FontStretch::ultra_expanded
; break;
104 FontStretch
getNarrower(FontStretch aSource
)
108 case FontStretch::extra_condensed
: aSource
= FontStretch::ultra_condensed
; break;
109 case FontStretch::condensed
: aSource
= FontStretch::extra_condensed
; break;
110 case FontStretch::semi_condensed
: aSource
= FontStretch::condensed
; break;
111 case FontStretch::normal
: aSource
= FontStretch::semi_condensed
; break;
112 case FontStretch::semi_expanded
: aSource
= FontStretch::normal
; break;
113 case FontStretch::expanded
: aSource
= FontStretch::semi_expanded
; break;
114 case FontStretch::extra_expanded
: aSource
= FontStretch::expanded
; break;
115 case FontStretch::ultra_expanded
: aSource
= FontStretch::extra_expanded
; break;
122 FontWeight
getBolder(FontWeight aSource
)
126 case FontWeight::N100
: aSource
= FontWeight::N200
; break;
127 case FontWeight::N200
: aSource
= FontWeight::N300
; break;
128 case FontWeight::N300
: aSource
= FontWeight::N400
; break;
129 case FontWeight::N400
: aSource
= FontWeight::N500
; break;
130 case FontWeight::N500
: aSource
= FontWeight::N600
; break;
131 case FontWeight::N600
: aSource
= FontWeight::N700
; break;
132 case FontWeight::N700
: aSource
= FontWeight::N800
; break;
133 case FontWeight::N800
: aSource
= FontWeight::N900
; break;
140 FontWeight
getLighter(FontWeight aSource
)
144 case FontWeight::N200
: aSource
= FontWeight::N100
; break;
145 case FontWeight::N300
: aSource
= FontWeight::N200
; break;
146 case FontWeight::N400
: aSource
= FontWeight::N300
; break;
147 case FontWeight::N500
: aSource
= FontWeight::N400
; break;
148 case FontWeight::N600
: aSource
= FontWeight::N500
; break;
149 case FontWeight::N700
: aSource
= FontWeight::N600
; break;
150 case FontWeight::N800
: aSource
= FontWeight::N700
; break;
151 case FontWeight::N900
: aSource
= FontWeight::N800
; break;
158 ::FontWeight
getVclFontWeight(FontWeight aSource
)
160 ::FontWeight
nRetval(WEIGHT_NORMAL
);
164 case FontWeight::N100
: nRetval
= WEIGHT_ULTRALIGHT
; break;
165 case FontWeight::N200
: nRetval
= WEIGHT_LIGHT
; break;
166 case FontWeight::N300
: nRetval
= WEIGHT_SEMILIGHT
; break;
167 case FontWeight::N400
: nRetval
= WEIGHT_NORMAL
; break;
168 case FontWeight::N500
: nRetval
= WEIGHT_MEDIUM
; break;
169 case FontWeight::N600
: nRetval
= WEIGHT_SEMIBOLD
; break;
170 case FontWeight::N700
: nRetval
= WEIGHT_BOLD
; break;
171 case FontWeight::N800
: nRetval
= WEIGHT_ULTRABOLD
; break;
172 case FontWeight::N900
: nRetval
= WEIGHT_BLACK
; break;
179 void SvgStyleAttributes::readCssStyle(std::u16string_view rCandidate
)
181 const sal_Int32
nLen(rCandidate
.size());
187 OUStringBuffer aTokenName
;
188 skip_char(rCandidate
, u
' ', nPos
, nLen
);
189 copyString(rCandidate
, nPos
, aTokenName
, nLen
);
191 if (aTokenName
.isEmpty())
193 // if no TokenName advance one by force to avoid death loop, continue
194 OSL_ENSURE(false, "Could not interpret on current position, advancing one byte (!)");
200 OUStringBuffer aTokenValue
;
201 skip_char(rCandidate
, u
' ', u
':', nPos
, nLen
);
202 copyToLimiter(rCandidate
, u
';', nPos
, aTokenValue
, nLen
);
203 skip_char(rCandidate
, u
' ', u
';', nPos
, nLen
);
205 if (aTokenValue
.isEmpty())
207 // no value - continue
211 // generate OUStrings
212 const OUString
aOUTokenName(aTokenName
.makeStringAndClear());
213 OUString
aOUTokenValue(aTokenValue
.makeStringAndClear());
215 // check for '!important' CssStyle mark, currently not supported
216 // but needs to be extracted for correct parsing
217 OUString
aTokenImportant("!important");
218 const sal_Int32
nIndexTokenImportant(aOUTokenValue
.indexOf(aTokenImportant
));
220 if(-1 != nIndexTokenImportant
)
222 // if there currently just remove it and remove spaces to have the value only
223 OUString aNewOUTokenValue
;
225 if(nIndexTokenImportant
> 0)
227 // copy content before token
228 aNewOUTokenValue
+= aOUTokenValue
.subView(0, nIndexTokenImportant
);
231 if(aOUTokenValue
.getLength() > nIndexTokenImportant
+ aTokenImportant
.getLength())
233 // copy content after token
234 aNewOUTokenValue
+= aOUTokenValue
.subView(nIndexTokenImportant
+ aTokenImportant
.getLength());
238 aOUTokenValue
= aNewOUTokenValue
.trim();
241 // valid token-value pair, parse it
242 parseStyleAttribute(StrToSVGToken(aOUTokenName
, true), aOUTokenValue
);
246 const SvgStyleAttributes
* SvgStyleAttributes::getParentStyle() const
248 if(getCssStyleParent())
250 return getCssStyleParent();
253 if(mrOwner
.supportsParentStyle() && mrOwner
.getParent())
255 return mrOwner
.getParent()->getSvgStyleAttributes();
261 void SvgStyleAttributes::add_text(
262 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
263 drawinglayer::primitive2d::Primitive2DContainer
&& rSource
) const
268 // at this point the primitives in rSource are of type TextSimplePortionPrimitive2D
269 // or TextDecoratedPortionPrimitive2D and have the Fill Color (pAttributes->getFill())
270 // set. When another fill is used and also evtl. stroke is set it gets necessary to
271 // dismantle to geometry and add needed primitives
272 const basegfx::BColor
* pFill
= getFill();
273 const SvgGradientNode
* pFillGradient
= getSvgGradientNodeFill();
274 const SvgPatternNode
* pFillPattern
= getSvgPatternNodeFill();
275 const basegfx::BColor
* pStroke
= getStroke();
276 const SvgGradientNode
* pStrokeGradient
= getSvgGradientNodeStroke();
277 const SvgPatternNode
* pStrokePattern
= getSvgPatternNodeStroke();
278 basegfx::B2DPolyPolygon aMergedArea
;
280 if(pFillGradient
|| pFillPattern
|| pStroke
|| pStrokeGradient
|| pStrokePattern
)
282 // text geometry is needed, create
283 // use neutral ViewInformation and create LineGeometryExtractor2D
284 const drawinglayer::geometry::ViewInformation2D aViewInformation2D
;
285 drawinglayer::processor2d::TextAsPolygonExtractor2D
aExtractor(aViewInformation2D
);
288 aExtractor
.process(rSource
);
291 const drawinglayer::processor2d::TextAsPolygonDataNodeVector
& rResult
= aExtractor
.getTarget();
292 const sal_uInt32
nResultCount(rResult
.size());
293 basegfx::B2DPolyPolygonVector aTextFillVector
;
294 aTextFillVector
.reserve(nResultCount
);
296 for(sal_uInt32
a(0); a
< nResultCount
; a
++)
298 const drawinglayer::processor2d::TextAsPolygonDataNode
& rCandidate
= rResult
[a
];
300 if(rCandidate
.getIsFilled())
302 aTextFillVector
.push_back(rCandidate
.getB2DPolyPolygon());
306 if(!aTextFillVector
.empty())
308 aMergedArea
= basegfx::utils::mergeToSinglePolyPolygon(aTextFillVector
);
312 const bool bStrokeUsed(pStroke
|| pStrokeGradient
|| pStrokePattern
);
314 // add fill. Use geometry even for simple color fill when stroke
315 // is used, else text rendering and the geometry-based stroke will
316 // normally not really match optically due to diverse system text
318 if(aMergedArea
.count() && (pFillGradient
|| pFillPattern
|| bStrokeUsed
))
320 // create text fill content based on geometry
321 add_fill(aMergedArea
, rTarget
, aMergedArea
.getB2DRange());
325 // add the already prepared primitives for single color fill
326 rTarget
.append(std::move(rSource
));
330 if(aMergedArea
.count() && bStrokeUsed
)
332 // create text stroke content
333 add_stroke(aMergedArea
, rTarget
, aMergedArea
.getB2DRange());
337 void SvgStyleAttributes::add_fillGradient(
338 const basegfx::B2DPolyPolygon
& rPath
,
339 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
340 const SvgGradientNode
& rFillGradient
,
341 const basegfx::B2DRange
& rGeoRange
) const
343 // create fill content
344 drawinglayer::primitive2d::SvgGradientEntryVector aSvgGradientEntryVector
;
346 // get the color stops
347 rFillGradient
.collectGradientEntries(aSvgGradientEntryVector
);
349 if(aSvgGradientEntryVector
.empty())
352 basegfx::B2DHomMatrix aGeoToUnit
;
353 basegfx::B2DHomMatrix aGradientTransform
;
355 if(rFillGradient
.getGradientTransform())
357 aGradientTransform
= *rFillGradient
.getGradientTransform();
360 if (SvgUnits::userSpaceOnUse
== rFillGradient
.getGradientUnits())
362 aGeoToUnit
.translate(-rGeoRange
.getMinX(), -rGeoRange
.getMinY());
363 aGeoToUnit
.scale(1.0 / rGeoRange
.getWidth(), 1.0 / rGeoRange
.getHeight());
366 if(SVGToken::LinearGradient
== rFillGradient
.getType())
368 basegfx::B2DPoint
aStart(0.0, 0.0);
369 basegfx::B2DPoint
aEnd(1.0, 0.0);
371 if (SvgUnits::userSpaceOnUse
== rFillGradient
.getGradientUnits())
373 // all possible units
374 aStart
.setX(rFillGradient
.getX1().solve(mrOwner
, NumberType::xcoordinate
));
375 aStart
.setY(rFillGradient
.getY1().solve(mrOwner
, NumberType::ycoordinate
));
376 aEnd
.setX(rFillGradient
.getX2().solve(mrOwner
, NumberType::xcoordinate
));
377 aEnd
.setY(rFillGradient
.getY2().solve(mrOwner
, NumberType::ycoordinate
));
381 // fractions or percent relative to object bounds
382 const SvgNumber
X1(rFillGradient
.getX1());
383 const SvgNumber
Y1(rFillGradient
.getY1());
384 const SvgNumber
X2(rFillGradient
.getX2());
385 const SvgNumber
Y2(rFillGradient
.getY2());
387 aStart
.setX(SvgUnit::percent
== X1
.getUnit() ? X1
.getNumber() * 0.01 : X1
.getNumber());
388 aStart
.setY(SvgUnit::percent
== Y1
.getUnit() ? Y1
.getNumber() * 0.01 : Y1
.getNumber());
389 aEnd
.setX(SvgUnit::percent
== X2
.getUnit() ? X2
.getNumber() * 0.01 : X2
.getNumber());
390 aEnd
.setY(SvgUnit::percent
== Y2
.getUnit() ? Y2
.getNumber() * 0.01 : Y2
.getNumber());
393 if(!aGeoToUnit
.isIdentity())
395 aStart
*= aGeoToUnit
;
400 new drawinglayer::primitive2d::SvgLinearGradientPrimitive2D(
403 std::move(aSvgGradientEntryVector
),
406 SvgUnits::userSpaceOnUse
!= rFillGradient
.getGradientUnits(),
407 rFillGradient
.getSpreadMethod()));
411 basegfx::B2DPoint
aStart(0.5, 0.5);
412 basegfx::B2DPoint aFocal
;
414 const SvgNumber
* pFx
= rFillGradient
.getFx();
415 const SvgNumber
* pFy
= rFillGradient
.getFy();
416 const bool bFocal(pFx
|| pFy
);
418 if (SvgUnits::userSpaceOnUse
== rFillGradient
.getGradientUnits())
420 // all possible units
421 aStart
.setX(rFillGradient
.getCx().solve(mrOwner
, NumberType::xcoordinate
));
422 aStart
.setY(rFillGradient
.getCy().solve(mrOwner
, NumberType::ycoordinate
));
423 fRadius
= rFillGradient
.getR().solve(mrOwner
);
427 aFocal
.setX(pFx
? pFx
->solve(mrOwner
, NumberType::xcoordinate
) : aStart
.getX());
428 aFocal
.setY(pFy
? pFy
->solve(mrOwner
, NumberType::ycoordinate
) : aStart
.getY());
433 // fractions or percent relative to object bounds
434 const SvgNumber
Cx(rFillGradient
.getCx());
435 const SvgNumber
Cy(rFillGradient
.getCy());
436 const SvgNumber
R(rFillGradient
.getR());
438 aStart
.setX(SvgUnit::percent
== Cx
.getUnit() ? Cx
.getNumber() * 0.01 : Cx
.getNumber());
439 aStart
.setY(SvgUnit::percent
== Cy
.getUnit() ? Cy
.getNumber() * 0.01 : Cy
.getNumber());
440 fRadius
= (SvgUnit::percent
== R
.getUnit()) ? R
.getNumber() * 0.01 : R
.getNumber();
444 aFocal
.setX(pFx
? (SvgUnit::percent
== pFx
->getUnit() ? pFx
->getNumber() * 0.01 : pFx
->getNumber()) : aStart
.getX());
445 aFocal
.setY(pFy
? (SvgUnit::percent
== pFy
->getUnit() ? pFy
->getNumber() * 0.01 : pFy
->getNumber()) : aStart
.getY());
449 if(!aGeoToUnit
.isIdentity())
451 aStart
*= aGeoToUnit
;
452 fRadius
= (aGeoToUnit
* basegfx::B2DVector(fRadius
, 0.0)).getLength();
456 aFocal
*= aGeoToUnit
;
461 new drawinglayer::primitive2d::SvgRadialGradientPrimitive2D(
464 std::move(aSvgGradientEntryVector
),
467 SvgUnits::userSpaceOnUse
!= rFillGradient
.getGradientUnits(),
468 rFillGradient
.getSpreadMethod(),
469 bFocal
? &aFocal
: nullptr));
473 void SvgStyleAttributes::add_fillPatternTransform(
474 const basegfx::B2DPolyPolygon
& rPath
,
475 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
476 const SvgPatternNode
& rFillPattern
,
477 const basegfx::B2DRange
& rGeoRange
) const
479 // prepare fill polyPolygon with given pattern, check for patternTransform
480 if(rFillPattern
.getPatternTransform() && !rFillPattern
.getPatternTransform()->isIdentity())
482 // PatternTransform is active; Handle by filling the inverse transformed
483 // path and back-transforming the result
484 basegfx::B2DPolyPolygon
aPath(rPath
);
485 basegfx::B2DHomMatrix
aInv(*rFillPattern
.getPatternTransform());
486 drawinglayer::primitive2d::Primitive2DContainer aNewTarget
;
489 aPath
.transform(aInv
);
490 add_fillPattern(aPath
, aNewTarget
, rFillPattern
, aPath
.getB2DRange());
492 if(!aNewTarget
.empty())
495 new drawinglayer::primitive2d::TransformPrimitive2D(
496 *rFillPattern
.getPatternTransform(),
497 std::move(aNewTarget
)));
502 // no patternTransform, create fillPattern directly
503 add_fillPattern(rPath
, rTarget
, rFillPattern
, rGeoRange
);
507 void SvgStyleAttributes::add_fillPattern(
508 const basegfx::B2DPolyPolygon
& rPath
,
509 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
510 const SvgPatternNode
& rFillPattern
,
511 const basegfx::B2DRange
& rGeoRange
) const
513 // fill polyPolygon with given pattern
514 const drawinglayer::primitive2d::Primitive2DContainer
& rPrimitives
= rFillPattern
.getPatternPrimitives();
516 if(rPrimitives
.empty())
519 double fTargetWidth(rGeoRange
.getWidth());
520 double fTargetHeight(rGeoRange
.getHeight());
522 if(fTargetWidth
<= 0.0 || fTargetHeight
<= 0.0)
525 // get relative values from pattern
531 rFillPattern
.getValuesRelative(fX
, fY
, fW
, fH
, rGeoRange
, mrOwner
);
533 if(fW
<= 0.0 || fH
<= 0.0)
536 // build the reference range relative to the rGeoRange
537 const basegfx::B2DRange
aReferenceRange(fX
, fY
, fX
+ fW
, fY
+ fH
);
539 // find out how the content is mapped to the reference range
540 basegfx::B2DHomMatrix aMapPrimitivesToUnitRange
;
541 const basegfx::B2DRange
* pViewBox
= rFillPattern
.getViewBox();
545 // use viewBox/preserveAspectRatio
546 const SvgAspectRatio
& rRatio
= rFillPattern
.getSvgAspectRatio();
547 const basegfx::B2DRange
aUnitRange(0.0, 0.0, 1.0, 1.0);
551 // let mapping be created from SvgAspectRatio
552 aMapPrimitivesToUnitRange
= rRatio
.createMapping(aUnitRange
, *pViewBox
);
556 // choose default mapping
557 aMapPrimitivesToUnitRange
= SvgAspectRatio::createLinearMapping(aUnitRange
, *pViewBox
);
562 // use patternContentUnits
563 const SvgUnits
aPatternContentUnits(rFillPattern
.getPatternContentUnits() ? *rFillPattern
.getPatternContentUnits() : SvgUnits::userSpaceOnUse
);
565 if (SvgUnits::userSpaceOnUse
== aPatternContentUnits
)
567 // create relative mapping to unit coordinates
568 aMapPrimitivesToUnitRange
.scale(1.0 / (fW
* fTargetWidth
), 1.0 / (fH
* fTargetHeight
));
572 aMapPrimitivesToUnitRange
.scale(1.0 / fW
, 1.0 / fH
);
576 // apply aMapPrimitivesToUnitRange to content when used
577 drawinglayer::primitive2d::Primitive2DContainer
aPrimitives(rPrimitives
);
579 if(!aMapPrimitivesToUnitRange
.isIdentity())
581 const drawinglayer::primitive2d::Primitive2DReference
xRef(
582 new drawinglayer::primitive2d::TransformPrimitive2D(
583 aMapPrimitivesToUnitRange
,
584 std::move(aPrimitives
)));
586 aPrimitives
= drawinglayer::primitive2d::Primitive2DContainer
{ xRef
};
589 // embed in PatternFillPrimitive2D
591 new drawinglayer::primitive2d::PatternFillPrimitive2D(
593 std::move(aPrimitives
),
597 void SvgStyleAttributes::add_fill(
598 const basegfx::B2DPolyPolygon
& rPath
,
599 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
600 const basegfx::B2DRange
& rGeoRange
) const
602 const basegfx::BColor
* pFill
= getFill();
603 const SvgGradientNode
* pFillGradient
= getSvgGradientNodeFill();
604 const SvgPatternNode
* pFillPattern
= getSvgPatternNodeFill();
606 if(!(pFill
|| pFillGradient
|| pFillPattern
))
609 const double fFillOpacity(getFillOpacity().solve(mrOwner
));
611 if(!basegfx::fTools::more(fFillOpacity
, 0.0))
614 drawinglayer::primitive2d::Primitive2DContainer aNewFill
;
618 // create fill content with SVG gradient primitive
619 add_fillGradient(rPath
, aNewFill
, *pFillGradient
, rGeoRange
);
621 else if(pFillPattern
)
623 // create fill content with SVG pattern primitive
624 add_fillPatternTransform(rPath
, aNewFill
, *pFillPattern
, rGeoRange
);
628 // create fill content
630 aNewFill
[0] = new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
638 if(basegfx::fTools::less(fFillOpacity
, 1.0))
640 // embed in UnifiedTransparencePrimitive2D
642 new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
644 1.0 - fFillOpacity
));
649 rTarget
.append(aNewFill
);
653 void SvgStyleAttributes::add_stroke(
654 const basegfx::B2DPolyPolygon
& rPath
,
655 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
656 const basegfx::B2DRange
& rGeoRange
) const
658 const basegfx::BColor
* pStroke
= getStroke();
659 const SvgGradientNode
* pStrokeGradient
= getSvgGradientNodeStroke();
660 const SvgPatternNode
* pStrokePattern
= getSvgPatternNodeStroke();
662 if(!(pStroke
|| pStrokeGradient
|| pStrokePattern
))
665 drawinglayer::primitive2d::Primitive2DContainer aNewStroke
;
666 const double fStrokeOpacity(getStrokeOpacity().solve(mrOwner
));
668 if(!basegfx::fTools::more(fStrokeOpacity
, 0.0))
671 // get stroke width; SVG does not use 0.0 == hairline, so 0.0 is no line at all
672 const double fStrokeWidth(getStrokeWidth().isSet() ? getStrokeWidth().solve(mrOwner
) : 1.0);
674 if(!basegfx::fTools::more(fStrokeWidth
, 0.0))
677 drawinglayer::primitive2d::Primitive2DReference aNewLinePrimitive
;
679 // if we have a line with two identical points it is not really a line,
680 // but used by SVG sometimes to paint a single dot.In that case, create
681 // the geometry for a single dot
682 if(1 == rPath
.count())
684 const basegfx::B2DPolygon
& aSingle(rPath
.getB2DPolygon(0));
686 if(2 == aSingle
.count() && aSingle
.getB2DPoint(0).equal(aSingle
.getB2DPoint(1)))
688 aNewLinePrimitive
= new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
689 basegfx::B2DPolyPolygon(
690 basegfx::utils::createPolygonFromCircle(
691 aSingle
.getB2DPoint(0),
692 fStrokeWidth
* (1.44 * 0.5))),
693 pStroke
? *pStroke
: basegfx::BColor(0.0, 0.0, 0.0));
697 if(!aNewLinePrimitive
.is())
699 // get LineJoin, LineCap and stroke array
700 const basegfx::B2DLineJoin
aB2DLineJoin(StrokeLinejoinToB2DLineJoin(getStrokeLinejoin()));
701 const css::drawing::LineCap
aLineCap(StrokeLinecapToDrawingLineCap(getStrokeLinecap()));
702 ::std::vector
< double > aDashArray
;
704 if(!getStrokeDasharray().empty())
706 aDashArray
= solveSvgNumberVector(getStrokeDasharray(), mrOwner
);
709 // convert svg:stroke-miterlimit to LineAttrute:mfMiterMinimumAngle
710 // The default needs to be set explicitly, because svg default <> Draw default
711 double fMiterMinimumAngle
;
712 if (getStrokeMiterLimit().isSet())
714 fMiterMinimumAngle
= 2.0 * asin(1.0/getStrokeMiterLimit().getNumber());
718 fMiterMinimumAngle
= 2.0 * asin(0.25); // 1.0/default 4.0
721 // todo: Handle getStrokeDashOffset()
723 // prepare line attribute
724 const drawinglayer::attribute::LineAttribute
aLineAttribute(
725 pStroke
? *pStroke
: basegfx::BColor(0.0, 0.0, 0.0),
731 if(aDashArray
.empty())
733 aNewLinePrimitive
= new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
739 drawinglayer::attribute::StrokeAttribute
aStrokeAttribute(std::move(aDashArray
));
741 aNewLinePrimitive
= new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
744 std::move(aStrokeAttribute
));
748 if(pStrokeGradient
|| pStrokePattern
)
750 // put primitive into Primitive2DReference and Primitive2DSequence
751 const drawinglayer::primitive2d::Primitive2DContainer aSeq
{ aNewLinePrimitive
};
753 // use neutral ViewInformation and create LineGeometryExtractor2D
754 const drawinglayer::geometry::ViewInformation2D aViewInformation2D
;
755 drawinglayer::processor2d::LineGeometryExtractor2D
aExtractor(aViewInformation2D
);
758 aExtractor
.process(aSeq
);
760 // check for fill rsults
761 const basegfx::B2DPolyPolygonVector
& rLineFillVector(aExtractor
.getExtractedLineFills());
763 if(!rLineFillVector
.empty())
765 const basegfx::B2DPolyPolygon
aMergedArea(
766 basegfx::utils::mergeToSinglePolyPolygon(
769 if(aMergedArea
.count())
773 // create fill content with SVG gradient primitive. Use original GeoRange,
774 // e.g. from circle without LineWidth
775 add_fillGradient(aMergedArea
, aNewStroke
, *pStrokeGradient
, rGeoRange
);
777 else // if(pStrokePattern)
779 // create fill content with SVG pattern primitive. Use GeoRange
780 // from the expanded data, e.g. circle with extended geo by half linewidth
781 add_fillPatternTransform(aMergedArea
, aNewStroke
, *pStrokePattern
, aMergedArea
.getB2DRange());
788 aNewStroke
.push_back(aNewLinePrimitive
);
791 if(aNewStroke
.empty())
794 if(basegfx::fTools::less(fStrokeOpacity
, 1.0))
796 // embed in UnifiedTransparencePrimitive2D
798 new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
799 std::move(aNewStroke
),
800 1.0 - fStrokeOpacity
));
805 rTarget
.append(aNewStroke
);
809 bool SvgStyleAttributes::prepare_singleMarker(
810 drawinglayer::primitive2d::Primitive2DContainer
& rMarkerPrimitives
,
811 basegfx::B2DHomMatrix
& rMarkerTransform
,
812 basegfx::B2DRange
& rClipRange
,
813 const SvgMarkerNode
& rMarker
) const
815 // reset return values
816 rMarkerTransform
.identity();
819 // get marker primitive representation
820 rMarkerPrimitives
= rMarker
.getMarkerPrimitives();
822 if(!rMarkerPrimitives
.empty())
824 basegfx::B2DRange
aPrimitiveRange(0.0, 0.0, 1.0, 1.0);
825 const basegfx::B2DRange
* pViewBox
= rMarker
.getViewBox();
829 aPrimitiveRange
= *pViewBox
;
832 if(aPrimitiveRange
.getWidth() > 0.0 && aPrimitiveRange
.getHeight() > 0.0)
834 double fTargetWidth(rMarker
.getMarkerWidth().isSet() ? rMarker
.getMarkerWidth().solve(mrOwner
, NumberType::xcoordinate
) : 3.0);
835 double fTargetHeight(rMarker
.getMarkerHeight().isSet() ? rMarker
.getMarkerHeight().solve(mrOwner
, NumberType::xcoordinate
) : 3.0);
836 const bool bStrokeWidth(SvgMarkerNode::MarkerUnits::strokeWidth
== rMarker
.getMarkerUnits());
837 const double fStrokeWidth(getStrokeWidth().isSet() ? getStrokeWidth().solve(mrOwner
) : 1.0);
841 // relative to strokeWidth
842 fTargetWidth
*= fStrokeWidth
;
843 fTargetHeight
*= fStrokeWidth
;
846 if(fTargetWidth
> 0.0 && fTargetHeight
> 0.0)
849 const basegfx::B2DRange
aTargetRange(0.0, 0.0, fTargetWidth
, fTargetHeight
);
850 const SvgAspectRatio
& rRatio
= rMarker
.getSvgAspectRatio();
854 // let mapping be created from SvgAspectRatio
855 rMarkerTransform
= rRatio
.createMapping(aTargetRange
, aPrimitiveRange
);
857 if(rRatio
.isMeetOrSlice())
860 rClipRange
= aPrimitiveRange
;
869 // adapt to strokewidth if needed
870 rMarkerTransform
.scale(fStrokeWidth
, fStrokeWidth
);
875 // choose default mapping
876 rMarkerTransform
= SvgAspectRatio::createLinearMapping(aTargetRange
, aPrimitiveRange
);
880 // get and apply reference point. Initially it's in marker local coordinate system
881 basegfx::B2DPoint
aRefPoint(
882 rMarker
.getRefX().isSet() ? rMarker
.getRefX().solve(mrOwner
, NumberType::xcoordinate
) : 0.0,
883 rMarker
.getRefY().isSet() ? rMarker
.getRefY().solve(mrOwner
, NumberType::ycoordinate
) : 0.0);
885 // apply MarkerTransform to have it in mapped coordinates
886 aRefPoint
*= rMarkerTransform
;
888 // apply by moving RepPoint to (0.0)
889 rMarkerTransform
.translate(-aRefPoint
.getX(), -aRefPoint
.getY());
899 void SvgStyleAttributes::add_markers(
900 const basegfx::B2DPolyPolygon
& rPath
,
901 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
902 const basegfx::utils::PointIndexSet
* pHelpPointIndices
) const
904 // try to access linked markers
905 const SvgMarkerNode
* pStart
= accessMarkerStartXLink();
906 const SvgMarkerNode
* pMid
= accessMarkerMidXLink();
907 const SvgMarkerNode
* pEnd
= accessMarkerEndXLink();
909 if(!(pStart
|| pMid
|| pEnd
))
912 const sal_uInt32
nSubPathCount(rPath
.count());
917 // remember prepared marker; pStart, pMid and pEnd may all be equal when
918 // only 'marker' was used instead of 'marker-start', 'marker-mid' or 'marker-end',
919 // see 'case SVGToken::Marker' in this file; thus in this case only one common
920 // marker in primitive form will be prepared
921 const SvgMarkerNode
* pPrepared
= nullptr;
923 // values for the prepared marker, results of prepare_singleMarker
924 drawinglayer::primitive2d::Primitive2DContainer aPreparedMarkerPrimitives
;
925 basegfx::B2DHomMatrix aPreparedMarkerTransform
;
926 basegfx::B2DRange aPreparedMarkerClipRange
;
928 for (sal_uInt32
a(0); a
< nSubPathCount
; a
++)
930 // iterate over sub-paths
931 const basegfx::B2DPolygon
& aSubPolygonPath(rPath
.getB2DPolygon(a
));
932 const sal_uInt32
nSubPolygonPointCount(aSubPolygonPath
.count());
933 const bool bSubPolygonPathIsClosed(aSubPolygonPath
.isClosed());
935 if(nSubPolygonPointCount
)
937 // for each sub-path, create one marker per point (when closed, two markers
938 // need to pe created for the 1st point)
939 const sal_uInt32
nTargetMarkerCount(bSubPolygonPathIsClosed
? nSubPolygonPointCount
+ 1 : nSubPolygonPointCount
);
941 for (sal_uInt32
b(0); b
< nTargetMarkerCount
; b
++)
943 const bool bIsFirstMarker(!a
&& !b
);
944 const bool bIsLastMarker(nSubPathCount
- 1 == a
&& nTargetMarkerCount
- 1 == b
);
945 const SvgMarkerNode
* pNeeded
= nullptr;
949 // 1st point in 1st sub-polygon, use pStart
952 else if(bIsLastMarker
)
954 // last point in last sub-polygon, use pEnd
959 // anything in-between, use pMid
963 if(pHelpPointIndices
&& !pHelpPointIndices
->empty())
965 const basegfx::utils::PointIndexSet::const_iterator
aFound(
966 pHelpPointIndices
->find(basegfx::utils::PointIndex(a
, b
)));
968 if(aFound
!= pHelpPointIndices
->end())
970 // this point is a pure helper point; do not create a marker for it
977 // no marker needs to be created for this point
981 if(pPrepared
!= pNeeded
)
983 // if needed marker is not yet prepared, do it now
984 if(prepare_singleMarker(aPreparedMarkerPrimitives
, aPreparedMarkerTransform
, aPreparedMarkerClipRange
, *pNeeded
))
990 // error: could not prepare given marker
991 OSL_ENSURE(false, "OOps, could not prepare given marker as primitives (!)");
997 // prepare complete transform
998 basegfx::B2DHomMatrix
aCombinedTransform(aPreparedMarkerTransform
);
1001 if(pPrepared
->getMarkerOrient() == SvgMarkerNode::MarkerOrient::auto_start
||
1002 pPrepared
->getMarkerOrient() == SvgMarkerNode::MarkerOrient::auto_start_reverse
)
1004 const sal_uInt32
nPointIndex(b
% nSubPolygonPointCount
);
1006 // get entering and leaving tangents; this will search backward/forward
1007 // in the polygon to find tangents unequal to zero, skipping empty edges
1008 // see basegfx descriptions)
1009 // Hint: Mozilla, Inkscape and others use only leaving tangent for start marker
1010 // and entering tangent for end marker. To achieve this (if wanted) it is possible
1011 // to make the fetch of aEntering/aLeaving dependent on bIsFirstMarker/bIsLastMarker.
1012 // This is not done here, see comment 14 in task #1232379#
1013 // or http://www.w3.org/TR/SVG/painting.html#OrientAttribute
1014 basegfx::B2DVector
aEntering(
1015 basegfx::utils::getTangentEnteringPoint(
1018 basegfx::B2DVector
aLeaving(
1019 basegfx::utils::getTangentLeavingPoint(
1022 const bool bEntering(!aEntering
.equalZero());
1023 const bool bLeaving(!aLeaving
.equalZero());
1025 if(bEntering
|| bLeaving
)
1027 basegfx::B2DVector
aSum(0.0, 0.0);
1031 if(bIsFirstMarker
&& pPrepared
->getMarkerOrient() == SvgMarkerNode::MarkerOrient::auto_start_reverse
)
1032 aSum
-= aEntering
.normalize();
1034 aSum
+= aEntering
.normalize();
1039 if(bIsFirstMarker
&& pPrepared
->getMarkerOrient() == SvgMarkerNode::MarkerOrient::auto_start_reverse
)
1040 aSum
-= aLeaving
.normalize();
1042 aSum
+= aLeaving
.normalize();
1045 if(!aSum
.equalZero())
1047 const double fAngle(atan2(aSum
.getY(), aSum
.getX()));
1050 aCombinedTransform
.rotate(fAngle
);
1057 aCombinedTransform
.rotate(pPrepared
->getAngle());
1060 // get and apply target position
1061 const basegfx::B2DPoint
aPoint(aSubPolygonPath
.getB2DPoint(b
% nSubPolygonPointCount
));
1063 aCombinedTransform
.translate(aPoint
.getX(), aPoint
.getY());
1066 drawinglayer::primitive2d::Primitive2DReference
xMarker(
1067 new drawinglayer::primitive2d::TransformPrimitive2D(
1069 drawinglayer::primitive2d::Primitive2DContainer(aPreparedMarkerPrimitives
)));
1071 if(!aPreparedMarkerClipRange
.isEmpty())
1073 // marker needs to be clipped, it's bigger as the mapping
1074 basegfx::B2DPolyPolygon
aClipPolygon(basegfx::utils::createPolygonFromRect(aPreparedMarkerClipRange
));
1076 aClipPolygon
.transform(aCombinedTransform
);
1077 xMarker
= new drawinglayer::primitive2d::MaskPrimitive2D(
1078 std::move(aClipPolygon
),
1079 drawinglayer::primitive2d::Primitive2DContainer
{ xMarker
});
1083 rTarget
.push_back(xMarker
);
1089 void SvgStyleAttributes::add_path(
1090 const basegfx::B2DPolyPolygon
& rPath
,
1091 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
1092 const basegfx::utils::PointIndexSet
* pHelpPointIndices
) const
1096 // no geometry at all
1100 const basegfx::B2DRange
aGeoRange(rPath
.getB2DRange());
1102 if(aGeoRange
.isEmpty())
1104 // no geometry range
1108 const double fOpacity(getOpacity().solve(mrOwner
));
1110 if(basegfx::fTools::equalZero(fOpacity
))
1116 // check if it's a line
1117 const bool bNoWidth(basegfx::fTools::equalZero(aGeoRange
.getWidth()));
1118 const bool bNoHeight(basegfx::fTools::equalZero(aGeoRange
.getHeight()));
1119 const bool bIsTwoPointLine(1 == rPath
.count()
1120 && !rPath
.areControlPointsUsed()
1121 && 2 == rPath
.getB2DPolygon(0).count());
1122 const bool bIsLine(bIsTwoPointLine
|| bNoWidth
|| bNoHeight
);
1127 basegfx::B2DPolyPolygon
aPath(rPath
);
1129 if(SVGToken::Path
== mrOwner
.getType() || SVGToken::Polygon
== mrOwner
.getType())
1131 if(FillRule::evenodd
!= getClipRule() && FillRule::evenodd
!= getFillRule())
1133 if(getFill() || getSvgGradientNodeFill() || getSvgPatternNodeFill())
1135 // nonzero is wanted, solve geometrically (see description on basegfx)
1136 // basegfx::utils::createNonzeroConform() is expensive for huge paths
1137 // and is only needed if path will be filled later on
1138 aPath
= basegfx::utils::createNonzeroConform(aPath
);
1143 add_fill(aPath
, rTarget
, aGeoRange
);
1147 add_stroke(rPath
, rTarget
, aGeoRange
);
1149 // Svg supports markers for path, polygon, polyline and line
1150 if(SVGToken::Path
== mrOwner
.getType() || // path
1151 SVGToken::Polygon
== mrOwner
.getType() || // polygon, polyline
1152 SVGToken::Line
== mrOwner
.getType() || // line
1153 SVGToken::Style
== mrOwner
.getType()) // tdf#150323
1155 // try to add markers
1156 add_markers(rPath
, rTarget
, pHelpPointIndices
);
1160 void SvgStyleAttributes::add_postProcess(
1161 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
1162 drawinglayer::primitive2d::Primitive2DContainer
&& rSource
,
1163 const std::optional
<basegfx::B2DHomMatrix
>& pTransform
) const
1168 const double fOpacity(getOpacity().solve(mrOwner
));
1170 if(basegfx::fTools::equalZero(fOpacity
))
1175 drawinglayer::primitive2d::Primitive2DContainer
aSource(std::move(rSource
));
1177 if(basegfx::fTools::less(fOpacity
, 1.0))
1179 // embed in UnifiedTransparencePrimitive2D
1180 const drawinglayer::primitive2d::Primitive2DReference
xRef(
1181 new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
1185 aSource
= drawinglayer::primitive2d::Primitive2DContainer
{ xRef
};
1190 // create embedding group element with transformation. This applies the given
1191 // transformation to the graphical content, but *not* to mask and/or clip (as needed)
1192 const drawinglayer::primitive2d::Primitive2DReference
xRef(
1193 new drawinglayer::primitive2d::TransformPrimitive2D(
1195 std::move(aSource
)));
1197 aSource
= drawinglayer::primitive2d::Primitive2DContainer
{ xRef
};
1200 const SvgClipPathNode
* pClip
= accessClipPathXLink();
1203 // #i124852# transform may be needed when SvgUnits::userSpaceOnUse
1204 pClip
->apply(aSource
, pTransform
);
1205 pClip
= pClip
->getSvgStyleAttributes()->accessClipPathXLink();
1208 if(!aSource
.empty()) // test again, applied clipPath may have lead to empty geometry
1210 const SvgMaskNode
* pMask
= accessMaskXLink();
1213 // #i124852# transform may be needed when SvgUnits::userSpaceOnUse
1214 pMask
->apply(aSource
, pTransform
);
1218 // This is part of the SVG import of self-written SVGs from
1219 // Draw/Impress containing multiple Slides/Pages. To be able
1220 // to later 'break' these to multiple Pages if wanted, embed
1221 // each Page-Content in an identifiable Primitive Grouping
1223 // This is the case when the current Node is a GroupNode, has
1224 // class="Page" set, has a parent that also is a GroupNode
1225 // at which class="Slide" is set.
1226 // Multiple Slides/Pages are possible for Draw and Impress.
1227 if(SVGToken::G
== mrOwner
.getType() && mrOwner
.getClass())
1229 const OUString
aOwnerClass(*mrOwner
.getClass());
1231 if("Page" == aOwnerClass
)
1233 const SvgNode
* pParent(mrOwner
.getParent());
1235 if(nullptr != pParent
&& SVGToken::G
== pParent
->getType() && pParent
->getClass())
1237 const OUString
aParentClass(*pParent
->getClass());
1239 if("Slide" == aParentClass
)
1241 // embed to grouping primitive to identify the
1242 // Slide/Page information
1243 const drawinglayer::primitive2d::Primitive2DReference
xRef(
1244 new drawinglayer::primitive2d::PageHierarchyPrimitive2D(
1245 std::move(aSource
)));
1247 aSource
= drawinglayer::primitive2d::Primitive2DContainer
{ xRef
};
1253 if(!aSource
.empty()) // test again, applied mask may have lead to empty geometry
1255 // append to current target
1256 rTarget
.append(aSource
);
1260 SvgStyleAttributes::SvgStyleAttributes(SvgNode
& rOwner
)
1262 mpCssStyleParent(nullptr),
1263 maStopColor(basegfx::BColor(0.0, 0.0, 0.0), true),
1264 maStrokeLinecap(StrokeLinecap::notset
),
1265 maStrokeLinejoin(StrokeLinejoin::notset
),
1267 maFontStretch(FontStretch::notset
),
1268 maFontStyle(FontStyle::notset
),
1269 maFontWeight(FontWeight::notset
),
1270 maTextAlign(TextAlign::notset
),
1271 maTextDecoration(TextDecoration::notset
),
1272 maTextAnchor(TextAnchor::notset
),
1273 maVisibility(Visibility::notset
),
1274 mpClipPathXLink(nullptr),
1275 mpMaskXLink(nullptr),
1276 mpMarkerStartXLink(nullptr),
1277 mpMarkerMidXLink(nullptr),
1278 mpMarkerEndXLink(nullptr),
1279 maFillRule(FillRule::notset
),
1280 maClipRule(FillRule::notset
),
1281 maBaselineShift(BaselineShift::Baseline
),
1282 maBaselineShiftNumber(0),
1283 maResolvingParent(32, 0),
1284 mbIsClipPathContent(SVGToken::ClipPathNode
== mrOwner
.getType()),
1285 mbStrokeDasharraySet(false)
1287 const SvgStyleAttributes
* pParentStyle
= getParentStyle();
1288 if(!mbIsClipPathContent
)
1292 mbIsClipPathContent
= pParentStyle
->mbIsClipPathContent
;
1297 SvgStyleAttributes::~SvgStyleAttributes()
1301 void SvgStyleAttributes::parseStyleAttribute(
1303 const OUString
& aContent
)
1307 case SVGToken::Fill
:
1313 if(readSvgPaint(aContent
, aSvgPaint
, aURL
, aOpacity
))
1316 if(aOpacity
.isSet())
1318 setOpacity(SvgNumber(std::clamp(aOpacity
.getNumber(), 0.0, 1.0)));
1321 else if(!aURL
.isEmpty())
1323 maNodeFillURL
= aURL
;
1327 case SVGToken::FillOpacity
:
1331 if(readSingleNumber(aContent
, aNum
))
1333 maFillOpacity
= SvgNumber(std::clamp(aNum
.getNumber(), 0.0, 1.0), aNum
.getUnit(), aNum
.isSet());
1337 case SVGToken::FillRule
:
1339 if(!aContent
.isEmpty())
1341 if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), commonStrings::aStrNonzero
))
1343 maFillRule
= FillRule::nonzero
;
1345 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), commonStrings::aStrEvenOdd
))
1347 maFillRule
= FillRule::evenodd
;
1352 case SVGToken::Stroke
:
1358 if(readSvgPaint(aContent
, aSvgPaint
, aURL
, aOpacity
))
1360 maStroke
= aSvgPaint
;
1361 if(aOpacity
.isSet())
1363 setOpacity(SvgNumber(std::clamp(aOpacity
.getNumber(), 0.0, 1.0)));
1366 else if(!aURL
.isEmpty())
1368 maNodeStrokeURL
= aURL
;
1372 case SVGToken::StrokeDasharray
:
1374 if(!aContent
.isEmpty())
1376 SvgNumberVector aVector
;
1378 if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"none"))
1380 // #121221# The special value 'none' needs to be handled
1381 // in the sense that *when* it is set, the parent shall not
1382 // be used. Before this was only dependent on the array being
1384 mbStrokeDasharraySet
= true;
1386 else if(readSvgNumberVector(aContent
, aVector
))
1388 maStrokeDasharray
= aVector
;
1393 case SVGToken::StrokeDashoffset
:
1397 if(readSingleNumber(aContent
, aNum
))
1399 if(aNum
.isPositive())
1401 maStrokeDashOffset
= aNum
;
1406 case SVGToken::StrokeLinecap
:
1408 if(!aContent
.isEmpty())
1410 if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"butt"))
1412 setStrokeLinecap(StrokeLinecap::butt
);
1414 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"round"))
1416 setStrokeLinecap(StrokeLinecap::round
);
1418 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"square"))
1420 setStrokeLinecap(StrokeLinecap::square
);
1425 case SVGToken::StrokeLinejoin
:
1427 if(!aContent
.isEmpty())
1429 if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"miter"))
1431 setStrokeLinejoin(StrokeLinejoin::miter
);
1433 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"round"))
1435 setStrokeLinejoin(StrokeLinejoin::round
);
1437 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"bevel"))
1439 setStrokeLinejoin(StrokeLinejoin::bevel
);
1444 case SVGToken::StrokeMiterlimit
:
1448 if(readSingleNumber(aContent
, aNum
))
1450 if(basegfx::fTools::moreOrEqual(aNum
.getNumber(), 1.0))
1451 { //readSingleNumber sets SvgUnit::px as default, if unit is missing. Correct it here.
1452 maStrokeMiterLimit
= SvgNumber(aNum
.getNumber(), SvgUnit::none
);
1457 case SVGToken::StrokeOpacity
:
1462 if(readSingleNumber(aContent
, aNum
))
1464 maStrokeOpacity
= SvgNumber(std::clamp(aNum
.getNumber(), 0.0, 1.0), aNum
.getUnit(), aNum
.isSet());
1468 case SVGToken::StrokeWidth
:
1472 if(readSingleNumber(aContent
, aNum
))
1474 if(aNum
.isPositive())
1476 maStrokeWidth
= aNum
;
1481 case SVGToken::StopColor
:
1487 if(readSvgPaint(aContent
, aSvgPaint
, aURL
, aOpacity
))
1489 maStopColor
= aSvgPaint
;
1490 if(aOpacity
.isSet())
1492 setOpacity(SvgNumber(std::clamp(aOpacity
.getNumber(), 0.0, 1.0)));
1497 case SVGToken::StopOpacity
:
1501 if(readSingleNumber(aContent
, aNum
))
1503 if(aNum
.isPositive())
1505 maStopOpacity
= aNum
;
1510 case SVGToken::Font
:
1514 case SVGToken::FontFamily
:
1516 SvgStringVector aSvgStringVector
;
1518 if(readSvgStringVector(aContent
, aSvgStringVector
))
1520 maFontFamily
= aSvgStringVector
;
1524 case SVGToken::FontSize
:
1526 if(!aContent
.isEmpty())
1528 if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"xx-small"))
1530 setFontSize(FontSize::xx_small
);
1532 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"x-small"))
1534 setFontSize(FontSize::x_small
);
1536 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"small"))
1538 setFontSize(FontSize::small
);
1540 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"smaller"))
1542 setFontSize(FontSize::smaller
);
1544 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"medium"))
1546 setFontSize(FontSize::medium
);
1548 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"larger"))
1550 setFontSize(FontSize::larger
);
1552 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"large"))
1554 setFontSize(FontSize::large
);
1556 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"x-large"))
1558 setFontSize(FontSize::x_large
);
1560 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"xx-large"))
1562 setFontSize(FontSize::xx_large
);
1564 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"initial"))
1566 setFontSize(FontSize::initial
);
1572 if(readSingleNumber(aContent
, aNum
))
1574 maFontSizeNumber
= aNum
;
1580 case SVGToken::FontSizeAdjust
:
1584 case SVGToken::FontStretch
:
1586 if(!aContent
.isEmpty())
1588 if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"normal"))
1590 setFontStretch(FontStretch::normal
);
1592 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"wider"))
1594 setFontStretch(FontStretch::wider
);
1596 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"narrower"))
1598 setFontStretch(FontStretch::narrower
);
1600 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"ultra-condensed"))
1602 setFontStretch(FontStretch::ultra_condensed
);
1604 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"extra-condensed"))
1606 setFontStretch(FontStretch::extra_condensed
);
1608 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"condensed"))
1610 setFontStretch(FontStretch::condensed
);
1612 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"semi-condensed"))
1614 setFontStretch(FontStretch::semi_condensed
);
1616 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"semi-expanded"))
1618 setFontStretch(FontStretch::semi_expanded
);
1620 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"expanded"))
1622 setFontStretch(FontStretch::expanded
);
1624 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"extra-expanded"))
1626 setFontStretch(FontStretch::extra_expanded
);
1628 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"ultra-expanded"))
1630 setFontStretch(FontStretch::ultra_expanded
);
1635 case SVGToken::FontStyle
:
1637 if(!aContent
.isEmpty())
1639 if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"normal"))
1641 setFontStyle(FontStyle::normal
);
1643 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"italic"))
1645 setFontStyle(FontStyle::italic
);
1647 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"oblique"))
1649 setFontStyle(FontStyle::oblique
);
1654 case SVGToken::FontVariant
:
1658 case SVGToken::FontWeight
:
1660 if(!aContent
.isEmpty())
1662 if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"100"))
1664 setFontWeight(FontWeight::N100
);
1666 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"200"))
1668 setFontWeight(FontWeight::N200
);
1670 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"300"))
1672 setFontWeight(FontWeight::N300
);
1674 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"400") || o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"normal"))
1676 setFontWeight(FontWeight::N400
);
1678 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"500"))
1680 setFontWeight(FontWeight::N500
);
1682 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"600"))
1684 setFontWeight(FontWeight::N600
);
1686 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"700") || o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"bold"))
1688 setFontWeight(FontWeight::N700
);
1690 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"800"))
1692 setFontWeight(FontWeight::N800
);
1694 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"900"))
1696 setFontWeight(FontWeight::N900
);
1698 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"bolder"))
1700 setFontWeight(FontWeight::bolder
);
1702 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"lighter"))
1704 setFontWeight(FontWeight::lighter
);
1709 case SVGToken::Direction
:
1713 case SVGToken::LetterSpacing
:
1717 case SVGToken::TextDecoration
:
1719 if(!aContent
.isEmpty())
1721 if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"none"))
1723 setTextDecoration(TextDecoration::none
);
1725 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"underline"))
1727 setTextDecoration(TextDecoration::underline
);
1729 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"overline"))
1731 setTextDecoration(TextDecoration::overline
);
1733 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"line-through"))
1735 setTextDecoration(TextDecoration::line_through
);
1737 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"blink"))
1739 setTextDecoration(TextDecoration::blink
);
1744 case SVGToken::UnicodeBidi
:
1748 case SVGToken::WordSpacing
:
1752 case SVGToken::TextAnchor
:
1754 if(!aContent
.isEmpty())
1756 if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"start"))
1758 setTextAnchor(TextAnchor::start
);
1760 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"middle"))
1762 setTextAnchor(TextAnchor::middle
);
1764 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"end"))
1766 setTextAnchor(TextAnchor::end
);
1771 case SVGToken::TextAlign
:
1773 if(!aContent
.isEmpty())
1775 if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"left"))
1777 setTextAlign(TextAlign::left
);
1779 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"right"))
1781 setTextAlign(TextAlign::right
);
1783 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"center"))
1785 setTextAlign(TextAlign::center
);
1787 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"justify"))
1789 setTextAlign(TextAlign::justify
);
1794 case SVGToken::Color
:
1800 if(readSvgPaint(aContent
, aSvgPaint
, aURL
, aOpacity
))
1802 maColor
= aSvgPaint
;
1803 if(aOpacity
.isSet())
1805 setOpacity(SvgNumber(std::clamp(aOpacity
.getNumber(), 0.0, 1.0)));
1810 case SVGToken::Opacity
:
1814 if(readSingleNumber(aContent
, aNum
))
1816 setOpacity(SvgNumber(std::clamp(aNum
.getNumber(), 0.0, 1.0), aNum
.getUnit(), aNum
.isSet()));
1820 case SVGToken::Visibility
:
1822 if(!aContent
.isEmpty())
1824 if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"visible"))
1826 setVisibility(Visibility::visible
);
1828 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"hidden"))
1830 setVisibility(Visibility::hidden
);
1832 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"collapse"))
1834 setVisibility(Visibility::collapse
);
1836 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"inherit"))
1838 setVisibility(Visibility::inherit
);
1843 case SVGToken::Title
:
1848 case SVGToken::Desc
:
1853 case SVGToken::ClipPathProperty
:
1855 readLocalUrl(aContent
, maClipPathXLink
);
1858 case SVGToken::Mask
:
1860 readLocalUrl(aContent
, maMaskXLink
);
1863 case SVGToken::ClipRule
:
1865 if(!aContent
.isEmpty())
1867 if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), commonStrings::aStrNonzero
))
1869 maClipRule
= FillRule::nonzero
;
1871 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), commonStrings::aStrEvenOdd
))
1873 maClipRule
= FillRule::evenodd
;
1878 case SVGToken::Marker
:
1880 // tdf#155819: Using the marker property from a style sheet is equivalent to using all three (start, mid, end).
1881 if(mrOwner
.getType() == SVGToken::Style
)
1883 readLocalUrl(aContent
, maMarkerEndXLink
);
1884 maMarkerStartXLink
= maMarkerMidXLink
= maMarkerEndXLink
;
1888 case SVGToken::MarkerStart
:
1890 readLocalUrl(aContent
, maMarkerStartXLink
);
1893 case SVGToken::MarkerMid
:
1895 readLocalUrl(aContent
, maMarkerMidXLink
);
1898 case SVGToken::MarkerEnd
:
1900 readLocalUrl(aContent
, maMarkerEndXLink
);
1903 case SVGToken::Display
:
1905 // There may be display:none statements inside of style defines, e.g. the following line:
1906 // style="display:none"
1907 // taken from a svg example; this needs to be parsed and set at the owning node. Do not call
1908 // mrOwner.parseAttribute(...) here, this would lead to a recursion
1909 if(!aContent
.isEmpty())
1911 mrOwner
.setDisplay(getDisplayFromContent(aContent
));
1915 case SVGToken::BaselineShift
:
1917 if(!aContent
.isEmpty())
1921 if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"sub"))
1923 setBaselineShift(BaselineShift::Sub
);
1925 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"super"))
1927 setBaselineShift(BaselineShift::Super
);
1929 else if(readSingleNumber(aContent
, aNum
))
1931 maBaselineShiftNumber
= aNum
;
1933 if(SvgUnit::percent
== aNum
.getUnit())
1935 setBaselineShift(BaselineShift::Percentage
);
1939 setBaselineShift(BaselineShift::Length
);
1944 // no BaselineShift or inherit (which is automatically)
1945 setBaselineShift(BaselineShift::Baseline
);
1957 // #i125258# ask if fill is a direct hard attribute (no hierarchy)
1958 bool SvgStyleAttributes::isFillSet() const
1960 if(mbIsClipPathContent
)
1964 else if(maFill
.isSet())
1972 const basegfx::BColor
* SvgStyleAttributes::getCurrentColor() const
1974 static basegfx::BColor
aBlack(0.0, 0.0, 0.0);
1975 const basegfx::BColor
*aColor
= getColor();
1982 const basegfx::BColor
* SvgStyleAttributes::getFill() const
1986 if(maFill
.isCurrent())
1988 return getCurrentColor();
1990 else if(maFill
.isOn())
1992 return &maFill
.getBColor();
1994 else if(mbIsClipPathContent
)
1996 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
1998 if (pSvgStyleAttributes
&& maResolvingParent
[0] < nStyleDepthLimit
)
2000 ++maResolvingParent
[0];
2001 const basegfx::BColor
* pFill
= pSvgStyleAttributes
->getFill();
2002 --maResolvingParent
[0];
2008 else if (maNodeFillURL
.isEmpty())
2010 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2012 if (pSvgStyleAttributes
&& maResolvingParent
[0] < nStyleDepthLimit
)
2014 ++maResolvingParent
[0];
2015 const basegfx::BColor
* pFill
= pSvgStyleAttributes
->getFill();
2016 --maResolvingParent
[0];
2018 if(mbIsClipPathContent
)
2026 static basegfx::BColor
aBlack(0.0, 0.0, 0.0);
2040 const basegfx::BColor
* SvgStyleAttributes::getStroke() const
2042 if(maStroke
.isSet())
2044 if(maStroke
.isCurrent())
2046 return getCurrentColor();
2048 else if(maStroke
.isOn())
2050 return &maStroke
.getBColor();
2053 else if (maNodeStrokeURL
.isEmpty())
2055 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2057 if (pSvgStyleAttributes
&& maResolvingParent
[1] < nStyleDepthLimit
)
2059 ++maResolvingParent
[1];
2060 auto ret
= pSvgStyleAttributes
->getStroke();
2061 --maResolvingParent
[1];
2069 const basegfx::BColor
& SvgStyleAttributes::getStopColor() const
2071 if(maStopColor
.isCurrent())
2073 return *getCurrentColor();
2077 return maStopColor
.getBColor();
2081 const SvgGradientNode
* SvgStyleAttributes::getSvgGradientNodeFill() const
2083 if (!maFill
.isSet())
2085 if (!maNodeFillURL
.isEmpty())
2087 const SvgNode
* pNode
= mrOwner
.getDocument().findSvgNodeById(maNodeFillURL
);
2091 if(SVGToken::LinearGradient
== pNode
->getType() || SVGToken::RadialGradient
== pNode
->getType())
2093 return static_cast< const SvgGradientNode
* >(pNode
);
2097 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2099 if (pSvgStyleAttributes
&& maResolvingParent
[2] < nStyleDepthLimit
)
2101 ++maResolvingParent
[2];
2102 auto ret
= pSvgStyleAttributes
->getSvgGradientNodeFill();
2103 --maResolvingParent
[2];
2111 const SvgGradientNode
* SvgStyleAttributes::getSvgGradientNodeStroke() const
2113 if (!maStroke
.isSet())
2115 if(!maNodeStrokeURL
.isEmpty())
2117 const SvgNode
* pNode
= mrOwner
.getDocument().findSvgNodeById(maNodeStrokeURL
);
2121 if(SVGToken::LinearGradient
== pNode
->getType() || SVGToken::RadialGradient
== pNode
->getType())
2123 return static_cast< const SvgGradientNode
* >(pNode
);
2128 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2130 if (pSvgStyleAttributes
&& maResolvingParent
[3] < nStyleDepthLimit
)
2132 ++maResolvingParent
[3];
2133 auto ret
= pSvgStyleAttributes
->getSvgGradientNodeStroke();
2134 --maResolvingParent
[3];
2142 const SvgPatternNode
* SvgStyleAttributes::getSvgPatternNodeFill() const
2144 if (!maFill
.isSet())
2146 if (!maNodeFillURL
.isEmpty())
2148 const SvgNode
* pNode
= mrOwner
.getDocument().findSvgNodeById(maNodeFillURL
);
2152 if(SVGToken::Pattern
== pNode
->getType())
2154 return static_cast< const SvgPatternNode
* >(pNode
);
2159 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2161 if (pSvgStyleAttributes
&& maResolvingParent
[4] < nStyleDepthLimit
)
2163 ++maResolvingParent
[4];
2164 auto ret
= pSvgStyleAttributes
->getSvgPatternNodeFill();
2165 --maResolvingParent
[4];
2173 const SvgPatternNode
* SvgStyleAttributes::getSvgPatternNodeStroke() const
2175 if (!maStroke
.isSet())
2177 if(!maNodeStrokeURL
.isEmpty())
2179 const SvgNode
* pNode
= mrOwner
.getDocument().findSvgNodeById(maNodeStrokeURL
);
2183 if(SVGToken::Pattern
== pNode
->getType())
2185 return static_cast< const SvgPatternNode
* >(pNode
);
2190 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2192 if (pSvgStyleAttributes
&& maResolvingParent
[5] < nStyleDepthLimit
)
2194 ++maResolvingParent
[5];
2195 auto ret
= pSvgStyleAttributes
->getSvgPatternNodeStroke();
2196 --maResolvingParent
[5];
2204 SvgNumber
SvgStyleAttributes::getStrokeWidth() const
2206 if(maStrokeWidth
.isSet())
2208 return maStrokeWidth
;
2211 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2213 if (pSvgStyleAttributes
&& maResolvingParent
[6] < nStyleDepthLimit
)
2215 ++maResolvingParent
[6];
2216 auto ret
= pSvgStyleAttributes
->getStrokeWidth();
2217 --maResolvingParent
[6];
2221 if(mbIsClipPathContent
)
2223 return SvgNumber(0.0);
2227 return SvgNumber(1.0);
2230 SvgNumber
SvgStyleAttributes::getStopOpacity() const
2232 if(maStopOpacity
.isSet())
2234 return maStopOpacity
;
2238 return SvgNumber(1.0);
2241 SvgNumber
SvgStyleAttributes::getFillOpacity() const
2243 if(maFillOpacity
.isSet())
2245 return maFillOpacity
;
2248 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2250 if (pSvgStyleAttributes
&& maResolvingParent
[7] < nStyleDepthLimit
)
2252 ++maResolvingParent
[7];
2253 auto ret
= pSvgStyleAttributes
->getFillOpacity();
2254 --maResolvingParent
[7];
2259 return SvgNumber(1.0);
2262 SvgNumber
SvgStyleAttributes::getOpacity() const
2264 if(maOpacity
.isSet())
2269 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2271 if (pSvgStyleAttributes
&& maResolvingParent
[8] < nStyleDepthLimit
)
2273 ++maResolvingParent
[8];
2274 auto ret
= pSvgStyleAttributes
->getOpacity();
2275 --maResolvingParent
[8];
2280 return SvgNumber(1.0);
2283 Visibility
SvgStyleAttributes::getVisibility() const
2285 if(Visibility::notset
== maVisibility
|| Visibility::inherit
== maVisibility
)
2287 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2289 if (pSvgStyleAttributes
&& maResolvingParent
[9] < nStyleDepthLimit
)
2291 ++maResolvingParent
[9];
2292 auto ret
= pSvgStyleAttributes
->getVisibility();
2293 --maResolvingParent
[9];
2296 //default is Visible
2297 return Visibility::visible
;
2300 // Visibility correction/exception for self-exported SVGs:
2301 // When Impress exports single or multi-page SVGs, it puts the
2302 // single slides into <g visibility="hidden">. Not sure why
2303 // this happens, but this leads (correctly) to empty imported
2305 // Thus, if Visibility::hidden is active and owner is a SVGToken::G
2306 // and it's parent is also a SVGToken::G and it has a Class 'SlideGroup'
2307 // set, check if we are an Impress export.
2308 // We are an Impress export if an SVG-Node titled 'ooo:meta_slides'
2310 // All together gives:
2311 if(Visibility::hidden
== maVisibility
2312 && SVGToken::G
== mrOwner
.getType()
2313 && nullptr != mrOwner
.getDocument().findSvgNodeById("ooo:meta_slides"))
2315 const SvgNode
* pParent(mrOwner
.getParent());
2317 if(nullptr != pParent
&& SVGToken::G
== pParent
->getType() && pParent
->getClass())
2319 const OUString
aClass(*pParent
->getClass());
2321 if("SlideGroup" == aClass
)
2323 // if we detect this exception,
2324 // override Visibility::hidden -> Visibility::visible
2325 return Visibility::visible
;
2330 return maVisibility
;
2333 FillRule
SvgStyleAttributes::getFillRule() const
2335 if(FillRule::notset
!= maFillRule
)
2340 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2342 if (pSvgStyleAttributes
&& maResolvingParent
[10] < nStyleDepthLimit
)
2344 ++maResolvingParent
[10];
2345 auto ret
= pSvgStyleAttributes
->getFillRule();
2346 --maResolvingParent
[10];
2350 // default is NonZero
2351 return FillRule::nonzero
;
2354 FillRule
SvgStyleAttributes::getClipRule() const
2356 if(FillRule::notset
!= maClipRule
)
2361 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2363 if (pSvgStyleAttributes
&& maResolvingParent
[31] < nStyleDepthLimit
)
2365 ++maResolvingParent
[31];
2366 auto ret
= pSvgStyleAttributes
->getClipRule();
2367 --maResolvingParent
[31];
2371 // default is NonZero
2372 return FillRule::nonzero
;
2375 const SvgNumberVector
& SvgStyleAttributes::getStrokeDasharray() const
2377 if(!maStrokeDasharray
.empty())
2379 return maStrokeDasharray
;
2381 else if(mbStrokeDasharraySet
)
2383 // #121221# is set to empty *by purpose*, do not visit parent styles
2384 return maStrokeDasharray
;
2387 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2389 if (pSvgStyleAttributes
&& maResolvingParent
[11] < nStyleDepthLimit
)
2391 ++maResolvingParent
[11];
2392 const SvgNumberVector
& ret
= pSvgStyleAttributes
->getStrokeDasharray();
2393 --maResolvingParent
[11];
2398 return maStrokeDasharray
;
2401 SvgNumber
SvgStyleAttributes::getStrokeDashOffset() const
2403 if(maStrokeDashOffset
.isSet())
2405 return maStrokeDashOffset
;
2408 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2410 if (pSvgStyleAttributes
&& maResolvingParent
[12] < nStyleDepthLimit
)
2412 ++maResolvingParent
[12];
2413 auto ret
= pSvgStyleAttributes
->getStrokeDashOffset();
2414 --maResolvingParent
[12];
2419 return SvgNumber(0.0);
2422 StrokeLinecap
SvgStyleAttributes::getStrokeLinecap() const
2424 if(maStrokeLinecap
!= StrokeLinecap::notset
)
2426 return maStrokeLinecap
;
2429 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2431 if (pSvgStyleAttributes
&& maResolvingParent
[13] < nStyleDepthLimit
)
2433 ++maResolvingParent
[13];
2434 auto ret
= pSvgStyleAttributes
->getStrokeLinecap();
2435 --maResolvingParent
[13];
2439 // default is StrokeLinecap::butt
2440 return StrokeLinecap::butt
;
2443 StrokeLinejoin
SvgStyleAttributes::getStrokeLinejoin() const
2445 if(maStrokeLinejoin
!= StrokeLinejoin::notset
)
2447 return maStrokeLinejoin
;
2450 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2452 if (pSvgStyleAttributes
&& maResolvingParent
[14] < nStyleDepthLimit
)
2454 ++maResolvingParent
[14];
2455 auto ret
= pSvgStyleAttributes
->getStrokeLinejoin();
2456 --maResolvingParent
[14];
2460 // default is StrokeLinejoin::butt
2461 return StrokeLinejoin::miter
;
2464 SvgNumber
SvgStyleAttributes::getStrokeMiterLimit() const
2466 if(maStrokeMiterLimit
.isSet())
2468 return maStrokeMiterLimit
;
2471 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2473 if (pSvgStyleAttributes
&& maResolvingParent
[15] < nStyleDepthLimit
)
2475 ++maResolvingParent
[15];
2476 auto ret
= pSvgStyleAttributes
->getStrokeMiterLimit();
2477 --maResolvingParent
[15];
2482 return SvgNumber(4.0, SvgUnit::none
);
2485 SvgNumber
SvgStyleAttributes::getStrokeOpacity() const
2487 if(maStrokeOpacity
.isSet())
2489 return maStrokeOpacity
;
2492 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2494 if (pSvgStyleAttributes
&& maResolvingParent
[16] < nStyleDepthLimit
)
2496 ++maResolvingParent
[16];
2497 auto ret
= pSvgStyleAttributes
->getStrokeOpacity();
2498 --maResolvingParent
[16];
2503 return SvgNumber(1.0);
2506 const SvgStringVector
& SvgStyleAttributes::getFontFamily() const
2508 if(!maFontFamily
.empty() && !o3tl::equalsIgnoreAsciiCase(o3tl::trim(maFontFamily
[0]), u
"inherit"))
2510 return maFontFamily
;
2513 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2515 if (pSvgStyleAttributes
&& maResolvingParent
[17] < nStyleDepthLimit
)
2517 ++maResolvingParent
[17];
2518 const SvgStringVector
& ret
= pSvgStyleAttributes
->getFontFamily();
2519 --maResolvingParent
[17];
2524 return maFontFamily
;
2527 SvgNumber
SvgStyleAttributes::getFontSizeNumber() const
2529 // default size is 'medium', i.e. 12 pt, or 16px
2530 constexpr double aDefaultSize
= o3tl::convert(12.0, o3tl::Length::pt
, o3tl::Length::px
);
2532 if(maFontSizeNumber
.isSet())
2534 if(!maFontSizeNumber
.isPositive())
2535 return aDefaultSize
;
2537 // #122524# Handle SvgUnit::percent relative to parent FontSize (see SVG1.1
2538 // spec 10.10 Font selection properties \91font-size\92, lastline (click 'normative
2539 // definition of the property')
2540 if(SvgUnit::percent
== maFontSizeNumber
.getUnit())
2542 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2544 if(pSvgStyleAttributes
)
2546 const SvgNumber aParentNumber
= pSvgStyleAttributes
->getFontSizeNumber();
2549 aParentNumber
.getNumber() * maFontSizeNumber
.getNumber() * 0.01,
2550 aParentNumber
.getUnit(),
2553 // if there's no parent style, set the font size based on the default size
2556 maFontSizeNumber
.getNumber() * aDefaultSize
/ 100.0, SvgUnit::px
, true);
2558 else if((SvgUnit::em
== maFontSizeNumber
.getUnit()) || (SvgUnit::ex
== maFontSizeNumber
.getUnit()))
2560 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2562 if(pSvgStyleAttributes
)
2564 const SvgNumber aParentNumber
= pSvgStyleAttributes
->getFontSizeNumber();
2567 aParentNumber
.getNumber() * maFontSizeNumber
.getNumber(),
2568 aParentNumber
.getUnit(),
2573 return maFontSizeNumber
;
2576 //In CSS2, the suggested scaling factor between adjacent indexes is 1.2
2579 case FontSize::notset
:
2581 case FontSize::xx_small
:
2583 return SvgNumber(aDefaultSize
/ 1.728);
2585 case FontSize::x_small
:
2587 return SvgNumber(aDefaultSize
/ 1.44);
2589 case FontSize::small
:
2591 return SvgNumber(aDefaultSize
/ 1.2);
2593 case FontSize::smaller
:
2595 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2596 if(pSvgStyleAttributes
)
2598 const SvgNumber aParentNumber
= pSvgStyleAttributes
->getFontSizeNumber();
2599 return SvgNumber(aParentNumber
.getNumber() / 1.2, aParentNumber
.getUnit());
2603 case FontSize::medium
:
2604 case FontSize::initial
:
2606 return SvgNumber(aDefaultSize
);
2608 case FontSize::large
:
2610 return SvgNumber(aDefaultSize
* 1.2);
2612 case FontSize::larger
:
2614 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2615 if(pSvgStyleAttributes
)
2617 const SvgNumber aParentNumber
= pSvgStyleAttributes
->getFontSizeNumber();
2618 return SvgNumber(aParentNumber
.getNumber() * 1.2, aParentNumber
.getUnit());
2622 case FontSize::x_large
:
2624 return SvgNumber(aDefaultSize
* 1.44);
2626 case FontSize::xx_large
:
2628 return SvgNumber(aDefaultSize
* 1.728);
2632 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2634 if(pSvgStyleAttributes
)
2636 return pSvgStyleAttributes
->getFontSizeNumber();
2639 return SvgNumber(aDefaultSize
);
2642 FontStretch
SvgStyleAttributes::getFontStretch() const
2644 if(maFontStretch
!= FontStretch::notset
)
2646 if(FontStretch::wider
!= maFontStretch
&& FontStretch::narrower
!= maFontStretch
)
2648 return maFontStretch
;
2652 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2654 if (pSvgStyleAttributes
&& maResolvingParent
[18] < nStyleDepthLimit
)
2656 ++maResolvingParent
[18];
2657 FontStretch aInherited
= pSvgStyleAttributes
->getFontStretch();
2658 --maResolvingParent
[18];
2660 if(FontStretch::wider
== maFontStretch
)
2662 aInherited
= getWider(aInherited
);
2664 else if(FontStretch::narrower
== maFontStretch
)
2666 aInherited
= getNarrower(aInherited
);
2672 // default is FontStretch::normal
2673 return FontStretch::normal
;
2676 FontStyle
SvgStyleAttributes::getFontStyle() const
2678 if(maFontStyle
!= FontStyle::notset
)
2683 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2685 if (pSvgStyleAttributes
&& maResolvingParent
[19] < nStyleDepthLimit
)
2687 ++maResolvingParent
[19];
2688 auto ret
= pSvgStyleAttributes
->getFontStyle();
2689 --maResolvingParent
[19];
2693 // default is FontStyle::normal
2694 return FontStyle::normal
;
2697 FontWeight
SvgStyleAttributes::getFontWeight() const
2699 if(maFontWeight
!= FontWeight::notset
)
2701 if(FontWeight::bolder
!= maFontWeight
&& FontWeight::lighter
!= maFontWeight
)
2703 return maFontWeight
;
2707 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2709 if (pSvgStyleAttributes
&& maResolvingParent
[20] < nStyleDepthLimit
)
2711 ++maResolvingParent
[20];
2712 FontWeight aInherited
= pSvgStyleAttributes
->getFontWeight();
2713 --maResolvingParent
[20];
2715 if(FontWeight::bolder
== maFontWeight
)
2717 aInherited
= getBolder(aInherited
);
2719 else if(FontWeight::lighter
== maFontWeight
)
2721 aInherited
= getLighter(aInherited
);
2727 // default is FontWeight::N400 (FontWeight::normal)
2728 return FontWeight::N400
;
2731 TextAlign
SvgStyleAttributes::getTextAlign() const
2733 if(maTextAlign
!= TextAlign::notset
)
2738 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2740 if (pSvgStyleAttributes
&& maResolvingParent
[21] < nStyleDepthLimit
)
2742 ++maResolvingParent
[21];
2743 auto ret
= pSvgStyleAttributes
->getTextAlign();
2744 --maResolvingParent
[21];
2748 // default is TextAlign::left
2749 return TextAlign::left
;
2752 const SvgStyleAttributes
* SvgStyleAttributes::getTextDecorationDefiningSvgStyleAttributes() const
2754 if(maTextDecoration
!= TextDecoration::notset
)
2759 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2761 if (pSvgStyleAttributes
&& maResolvingParent
[22] < nStyleDepthLimit
)
2763 ++maResolvingParent
[22];
2764 auto ret
= pSvgStyleAttributes
->getTextDecorationDefiningSvgStyleAttributes();
2765 --maResolvingParent
[22];
2773 TextDecoration
SvgStyleAttributes::getTextDecoration() const
2775 const SvgStyleAttributes
* pDefining
= getTextDecorationDefiningSvgStyleAttributes();
2779 return pDefining
->maTextDecoration
;
2783 // default is TextDecoration::none
2784 return TextDecoration::none
;
2788 TextAnchor
SvgStyleAttributes::getTextAnchor() const
2790 if(maTextAnchor
!= TextAnchor::notset
)
2792 return maTextAnchor
;
2795 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2797 if (pSvgStyleAttributes
&& maResolvingParent
[23] < nStyleDepthLimit
)
2799 ++maResolvingParent
[23];
2800 auto ret
= pSvgStyleAttributes
->getTextAnchor();
2801 --maResolvingParent
[23];
2805 // default is TextAnchor::start
2806 return TextAnchor::start
;
2809 const basegfx::BColor
* SvgStyleAttributes::getColor() const
2813 if(maColor
.isCurrent())
2815 OSL_ENSURE(false, "Svg error: current color uses current color (!)");
2818 else if(maColor
.isOn())
2820 return &maColor
.getBColor();
2825 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2827 if (pSvgStyleAttributes
&& maResolvingParent
[24] < nStyleDepthLimit
)
2829 ++maResolvingParent
[24];
2830 auto ret
= pSvgStyleAttributes
->getColor();
2831 --maResolvingParent
[24];
2839 OUString
SvgStyleAttributes::getClipPathXLink() const
2841 if(!maClipPathXLink
.isEmpty())
2843 return maClipPathXLink
;
2846 if(getCssStyleParent())
2848 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2850 if (pSvgStyleAttributes
&& maResolvingParent
[30] < nStyleDepthLimit
)
2852 ++maResolvingParent
[30];
2853 auto ret
= pSvgStyleAttributes
->getClipPathXLink();
2854 --maResolvingParent
[30];
2862 const SvgClipPathNode
* SvgStyleAttributes::accessClipPathXLink() const
2864 if(!mpClipPathXLink
)
2866 const OUString
aClipPath(getClipPathXLink());
2868 if(!aClipPath
.isEmpty())
2870 return dynamic_cast< const SvgClipPathNode
* >(mrOwner
.getDocument().findSvgNodeById(aClipPath
));
2874 return mpClipPathXLink
;
2877 OUString
SvgStyleAttributes::getMaskXLink() const
2879 if(!maMaskXLink
.isEmpty())
2884 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2886 if (pSvgStyleAttributes
&& maResolvingParent
[25] < nStyleDepthLimit
)
2888 ++maResolvingParent
[25];
2889 auto ret
= pSvgStyleAttributes
->getMaskXLink();
2890 --maResolvingParent
[25];
2897 const SvgMaskNode
* SvgStyleAttributes::accessMaskXLink() const
2901 const OUString
aMask(getMaskXLink());
2903 if(!aMask
.isEmpty())
2905 return dynamic_cast< const SvgMaskNode
* >(mrOwner
.getDocument().findSvgNodeById(aMask
));
2912 OUString
SvgStyleAttributes::getMarkerStartXLink() const
2914 if(!maMarkerStartXLink
.isEmpty())
2916 return maMarkerStartXLink
;
2919 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2921 if (pSvgStyleAttributes
&& maResolvingParent
[26] < nStyleDepthLimit
)
2923 ++maResolvingParent
[26];
2924 auto ret
= pSvgStyleAttributes
->getMarkerStartXLink();
2925 --maResolvingParent
[26];
2932 const SvgMarkerNode
* SvgStyleAttributes::accessMarkerStartXLink() const
2934 if(!mpMarkerStartXLink
)
2936 const OUString
aMarker(getMarkerStartXLink());
2938 if(!aMarker
.isEmpty())
2940 return dynamic_cast< const SvgMarkerNode
* >(mrOwner
.getDocument().findSvgNodeById(getMarkerStartXLink()));
2944 return mpMarkerStartXLink
;
2947 OUString
SvgStyleAttributes::getMarkerMidXLink() const
2949 if(!maMarkerMidXLink
.isEmpty())
2951 return maMarkerMidXLink
;
2954 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2956 if (pSvgStyleAttributes
&& maResolvingParent
[27] < nStyleDepthLimit
)
2958 ++maResolvingParent
[27];
2959 auto ret
= pSvgStyleAttributes
->getMarkerMidXLink();
2960 --maResolvingParent
[27];
2967 const SvgMarkerNode
* SvgStyleAttributes::accessMarkerMidXLink() const
2969 if(!mpMarkerMidXLink
)
2971 const OUString
aMarker(getMarkerMidXLink());
2973 if(!aMarker
.isEmpty())
2975 return dynamic_cast< const SvgMarkerNode
* >(mrOwner
.getDocument().findSvgNodeById(getMarkerMidXLink()));
2979 return mpMarkerMidXLink
;
2982 OUString
SvgStyleAttributes::getMarkerEndXLink() const
2984 if(!maMarkerEndXLink
.isEmpty())
2986 return maMarkerEndXLink
;
2989 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2991 if (pSvgStyleAttributes
&& maResolvingParent
[28] < nStyleDepthLimit
)
2993 ++maResolvingParent
[28];
2994 auto ret
= pSvgStyleAttributes
->getMarkerEndXLink();
2995 --maResolvingParent
[28];
3002 const SvgMarkerNode
* SvgStyleAttributes::accessMarkerEndXLink() const
3004 if(!mpMarkerEndXLink
)
3006 const OUString
aMarker(getMarkerEndXLink());
3008 if(!aMarker
.isEmpty())
3010 return dynamic_cast< const SvgMarkerNode
* >(mrOwner
.getDocument().findSvgNodeById(getMarkerEndXLink()));
3014 return mpMarkerEndXLink
;
3017 SvgNumber
SvgStyleAttributes::getBaselineShiftNumber() const
3019 // #122524# Handle SvgUnit::percent relative to parent BaselineShift
3020 if(SvgUnit::percent
== maBaselineShiftNumber
.getUnit())
3022 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
3024 if (pSvgStyleAttributes
&& maResolvingParent
[29] < nStyleDepthLimit
)
3026 ++maResolvingParent
[29];
3027 const SvgNumber aParentNumber
= pSvgStyleAttributes
->getBaselineShiftNumber();
3028 --maResolvingParent
[29];
3031 aParentNumber
.getNumber() * maBaselineShiftNumber
.getNumber() * 0.01,
3032 aParentNumber
.getUnit(),
3037 return maBaselineShiftNumber
;
3039 } // end of namespace svgio
3041 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */