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>
47 const int nStyleDepthLimit
= 1024;
49 namespace svgio::svgreader
51 static basegfx::B2DLineJoin
StrokeLinejoinToB2DLineJoin(StrokeLinejoin aStrokeLinejoin
)
53 if(StrokeLinejoin::round
== aStrokeLinejoin
)
55 return basegfx::B2DLineJoin::Round
;
57 else if(StrokeLinejoin::bevel
== aStrokeLinejoin
)
59 return basegfx::B2DLineJoin::Bevel
;
62 return basegfx::B2DLineJoin::Miter
;
65 static css::drawing::LineCap
StrokeLinecapToDrawingLineCap(StrokeLinecap aStrokeLinecap
)
67 switch(aStrokeLinecap
)
69 default: /* StrokeLinecap::notset, StrokeLinecap::butt */
71 return css::drawing::LineCap_BUTT
;
73 case StrokeLinecap::round
:
75 return css::drawing::LineCap_ROUND
;
77 case StrokeLinecap::square
:
79 return css::drawing::LineCap_SQUARE
;
84 FontStretch
getWider(FontStretch aSource
)
88 case FontStretch::ultra_condensed
: aSource
= FontStretch::extra_condensed
; break;
89 case FontStretch::extra_condensed
: aSource
= FontStretch::condensed
; break;
90 case FontStretch::condensed
: aSource
= FontStretch::semi_condensed
; break;
91 case FontStretch::semi_condensed
: aSource
= FontStretch::normal
; break;
92 case FontStretch::normal
: aSource
= FontStretch::semi_expanded
; break;
93 case FontStretch::semi_expanded
: aSource
= FontStretch::expanded
; break;
94 case FontStretch::expanded
: aSource
= FontStretch::extra_expanded
; break;
95 case FontStretch::extra_expanded
: aSource
= FontStretch::ultra_expanded
; break;
102 FontStretch
getNarrower(FontStretch aSource
)
106 case FontStretch::extra_condensed
: aSource
= FontStretch::ultra_condensed
; break;
107 case FontStretch::condensed
: aSource
= FontStretch::extra_condensed
; break;
108 case FontStretch::semi_condensed
: aSource
= FontStretch::condensed
; break;
109 case FontStretch::normal
: aSource
= FontStretch::semi_condensed
; break;
110 case FontStretch::semi_expanded
: aSource
= FontStretch::normal
; break;
111 case FontStretch::expanded
: aSource
= FontStretch::semi_expanded
; break;
112 case FontStretch::extra_expanded
: aSource
= FontStretch::expanded
; break;
113 case FontStretch::ultra_expanded
: aSource
= FontStretch::extra_expanded
; break;
120 FontWeight
getBolder(FontWeight aSource
)
124 case FontWeight::N100
: aSource
= FontWeight::N200
; break;
125 case FontWeight::N200
: aSource
= FontWeight::N300
; break;
126 case FontWeight::N300
: aSource
= FontWeight::N400
; break;
127 case FontWeight::N400
: aSource
= FontWeight::N500
; break;
128 case FontWeight::N500
: aSource
= FontWeight::N600
; break;
129 case FontWeight::N600
: aSource
= FontWeight::N700
; break;
130 case FontWeight::N700
: aSource
= FontWeight::N800
; break;
131 case FontWeight::N800
: aSource
= FontWeight::N900
; break;
138 FontWeight
getLighter(FontWeight aSource
)
142 case FontWeight::N200
: aSource
= FontWeight::N100
; break;
143 case FontWeight::N300
: aSource
= FontWeight::N200
; break;
144 case FontWeight::N400
: aSource
= FontWeight::N300
; break;
145 case FontWeight::N500
: aSource
= FontWeight::N400
; break;
146 case FontWeight::N600
: aSource
= FontWeight::N500
; break;
147 case FontWeight::N700
: aSource
= FontWeight::N600
; break;
148 case FontWeight::N800
: aSource
= FontWeight::N700
; break;
149 case FontWeight::N900
: aSource
= FontWeight::N800
; break;
156 ::FontWeight
getVclFontWeight(FontWeight aSource
)
158 ::FontWeight
nRetval(WEIGHT_NORMAL
);
162 case FontWeight::N100
: nRetval
= WEIGHT_ULTRALIGHT
; break;
163 case FontWeight::N200
: nRetval
= WEIGHT_LIGHT
; break;
164 case FontWeight::N300
: nRetval
= WEIGHT_SEMILIGHT
; break;
165 case FontWeight::N400
: nRetval
= WEIGHT_NORMAL
; break;
166 case FontWeight::N500
: nRetval
= WEIGHT_MEDIUM
; break;
167 case FontWeight::N600
: nRetval
= WEIGHT_SEMIBOLD
; break;
168 case FontWeight::N700
: nRetval
= WEIGHT_BOLD
; break;
169 case FontWeight::N800
: nRetval
= WEIGHT_ULTRABOLD
; break;
170 case FontWeight::N900
: nRetval
= WEIGHT_BLACK
; break;
177 void SvgStyleAttributes::readCssStyle(const OUString
& rCandidate
)
179 const sal_Int32
nLen(rCandidate
.getLength());
185 OUStringBuffer aTokenName
;
186 skip_char(rCandidate
, u
' ', nPos
, nLen
);
187 copyString(rCandidate
, nPos
, aTokenName
, nLen
);
189 if (aTokenName
.isEmpty())
191 // if no TokenName advance one by force to avoid death loop, continue
192 OSL_ENSURE(false, "Could not interpret on current position, advancing one byte (!)");
198 OUStringBuffer aTokenValue
;
199 skip_char(rCandidate
, u
' ', u
':', nPos
, nLen
);
200 copyToLimiter(rCandidate
, u
';', nPos
, aTokenValue
, nLen
);
201 skip_char(rCandidate
, u
' ', u
';', nPos
, nLen
);
203 if (aTokenValue
.isEmpty())
205 // no value - continue
209 // generate OUStrings
210 const OUString
aOUTokenName(aTokenName
.makeStringAndClear());
211 OUString
aOUTokenValue(aTokenValue
.makeStringAndClear());
213 // check for '!important' CssStyle mark, currently not supported
214 // but needs to be extracted for correct parsing
215 OUString
aTokenImportant("!important");
216 const sal_Int32
nIndexTokenImportant(aOUTokenValue
.indexOf(aTokenImportant
));
218 if(-1 != nIndexTokenImportant
)
220 // if there currently just remove it and remove spaces to have the value only
221 OUString aNewOUTokenValue
;
223 if(nIndexTokenImportant
> 0)
225 // copy content before token
226 aNewOUTokenValue
+= aOUTokenValue
.subView(0, nIndexTokenImportant
);
229 if(aOUTokenValue
.getLength() > nIndexTokenImportant
+ aTokenImportant
.getLength())
231 // copy content after token
232 aNewOUTokenValue
+= aOUTokenValue
.subView(nIndexTokenImportant
+ aTokenImportant
.getLength());
236 aOUTokenValue
= aNewOUTokenValue
.trim();
239 // valid token-value pair, parse it
240 parseStyleAttribute(StrToSVGToken(aOUTokenName
, true), aOUTokenValue
, true);
244 const SvgStyleAttributes
* SvgStyleAttributes::getParentStyle() const
246 if(getCssStyleParent())
248 return getCssStyleParent();
251 if(mrOwner
.supportsParentStyle() && mrOwner
.getParent())
253 return mrOwner
.getParent()->getSvgStyleAttributes();
259 void SvgStyleAttributes::add_text(
260 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
261 drawinglayer::primitive2d::Primitive2DContainer
const & rSource
) const
266 // at this point the primitives in rSource are of type TextSimplePortionPrimitive2D
267 // or TextDecoratedPortionPrimitive2D and have the Fill Color (pAttributes->getFill())
268 // set. When another fill is used and also evtl. stroke is set it gets necessary to
269 // dismantle to geometry and add needed primitives
270 const basegfx::BColor
* pFill
= getFill();
271 const SvgGradientNode
* pFillGradient
= getSvgGradientNodeFill();
272 const SvgPatternNode
* pFillPattern
= getSvgPatternNodeFill();
273 const basegfx::BColor
* pStroke
= getStroke();
274 const SvgGradientNode
* pStrokeGradient
= getSvgGradientNodeStroke();
275 const SvgPatternNode
* pStrokePattern
= getSvgPatternNodeStroke();
276 basegfx::B2DPolyPolygon aMergedArea
;
278 if(pFillGradient
|| pFillPattern
|| pStroke
|| pStrokeGradient
|| pStrokePattern
)
280 // text geometry is needed, create
281 // use neutral ViewInformation and create LineGeometryExtractor2D
282 const drawinglayer::geometry::ViewInformation2D aViewInformation2D
;
283 drawinglayer::processor2d::TextAsPolygonExtractor2D
aExtractor(aViewInformation2D
);
286 aExtractor
.process(rSource
);
289 const drawinglayer::processor2d::TextAsPolygonDataNodeVector
& rResult
= aExtractor
.getTarget();
290 const sal_uInt32
nResultCount(rResult
.size());
291 basegfx::B2DPolyPolygonVector aTextFillVector
;
292 aTextFillVector
.reserve(nResultCount
);
294 for(sal_uInt32
a(0); a
< nResultCount
; a
++)
296 const drawinglayer::processor2d::TextAsPolygonDataNode
& rCandidate
= rResult
[a
];
298 if(rCandidate
.getIsFilled())
300 aTextFillVector
.push_back(rCandidate
.getB2DPolyPolygon());
304 if(!aTextFillVector
.empty())
306 aMergedArea
= basegfx::utils::mergeToSinglePolyPolygon(aTextFillVector
);
310 const bool bStrokeUsed(pStroke
|| pStrokeGradient
|| pStrokePattern
);
312 // add fill. Use geometry even for simple color fill when stroke
313 // is used, else text rendering and the geometry-based stroke will
314 // normally not really match optically due to diverse system text
316 if(aMergedArea
.count() && (pFillGradient
|| pFillPattern
|| bStrokeUsed
))
318 // create text fill content based on geometry
319 add_fill(aMergedArea
, rTarget
, aMergedArea
.getB2DRange());
323 // add the already prepared primitives for single color fill
324 rTarget
.append(rSource
);
328 if(aMergedArea
.count() && bStrokeUsed
)
330 // create text stroke content
331 add_stroke(aMergedArea
, rTarget
, aMergedArea
.getB2DRange());
335 void SvgStyleAttributes::add_fillGradient(
336 const basegfx::B2DPolyPolygon
& rPath
,
337 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
338 const SvgGradientNode
& rFillGradient
,
339 const basegfx::B2DRange
& rGeoRange
) const
341 // create fill content
342 drawinglayer::primitive2d::SvgGradientEntryVector aSvgGradientEntryVector
;
344 // get the color stops
345 rFillGradient
.collectGradientEntries(aSvgGradientEntryVector
);
347 if(aSvgGradientEntryVector
.empty())
350 basegfx::B2DHomMatrix aGeoToUnit
;
351 basegfx::B2DHomMatrix aGradientTransform
;
353 if(rFillGradient
.getGradientTransform())
355 aGradientTransform
= *rFillGradient
.getGradientTransform();
358 if (SvgUnits::userSpaceOnUse
== rFillGradient
.getGradientUnits())
360 aGeoToUnit
.translate(-rGeoRange
.getMinX(), -rGeoRange
.getMinY());
361 aGeoToUnit
.scale(1.0 / rGeoRange
.getWidth(), 1.0 / rGeoRange
.getHeight());
364 if(SVGToken::LinearGradient
== rFillGradient
.getType())
366 basegfx::B2DPoint
aStart(0.0, 0.0);
367 basegfx::B2DPoint
aEnd(1.0, 0.0);
369 if (SvgUnits::userSpaceOnUse
== rFillGradient
.getGradientUnits())
371 // all possible units
372 aStart
.setX(rFillGradient
.getX1().solve(mrOwner
, NumberType::xcoordinate
));
373 aStart
.setY(rFillGradient
.getY1().solve(mrOwner
, NumberType::ycoordinate
));
374 aEnd
.setX(rFillGradient
.getX2().solve(mrOwner
, NumberType::xcoordinate
));
375 aEnd
.setY(rFillGradient
.getY2().solve(mrOwner
, NumberType::ycoordinate
));
379 // fractions or percent relative to object bounds
380 const SvgNumber
X1(rFillGradient
.getX1());
381 const SvgNumber
Y1(rFillGradient
.getY1());
382 const SvgNumber
X2(rFillGradient
.getX2());
383 const SvgNumber
Y2(rFillGradient
.getY2());
385 aStart
.setX(SvgUnit::percent
== X1
.getUnit() ? X1
.getNumber() * 0.01 : X1
.getNumber());
386 aStart
.setY(SvgUnit::percent
== Y1
.getUnit() ? Y1
.getNumber() * 0.01 : Y1
.getNumber());
387 aEnd
.setX(SvgUnit::percent
== X2
.getUnit() ? X2
.getNumber() * 0.01 : X2
.getNumber());
388 aEnd
.setY(SvgUnit::percent
== Y2
.getUnit() ? Y2
.getNumber() * 0.01 : Y2
.getNumber());
391 if(!aGeoToUnit
.isIdentity())
393 aStart
*= aGeoToUnit
;
398 new drawinglayer::primitive2d::SvgLinearGradientPrimitive2D(
401 aSvgGradientEntryVector
,
404 SvgUnits::userSpaceOnUse
!= rFillGradient
.getGradientUnits(),
405 rFillGradient
.getSpreadMethod()));
409 basegfx::B2DPoint
aStart(0.5, 0.5);
410 basegfx::B2DPoint aFocal
;
412 const SvgNumber
* pFx
= rFillGradient
.getFx();
413 const SvgNumber
* pFy
= rFillGradient
.getFy();
414 const bool bFocal(pFx
|| pFy
);
416 if (SvgUnits::userSpaceOnUse
== rFillGradient
.getGradientUnits())
418 // all possible units
419 aStart
.setX(rFillGradient
.getCx().solve(mrOwner
, NumberType::xcoordinate
));
420 aStart
.setY(rFillGradient
.getCy().solve(mrOwner
, NumberType::ycoordinate
));
421 fRadius
= rFillGradient
.getR().solve(mrOwner
);
425 aFocal
.setX(pFx
? pFx
->solve(mrOwner
, NumberType::xcoordinate
) : aStart
.getX());
426 aFocal
.setY(pFy
? pFy
->solve(mrOwner
, NumberType::ycoordinate
) : aStart
.getY());
431 // fractions or percent relative to object bounds
432 const SvgNumber
Cx(rFillGradient
.getCx());
433 const SvgNumber
Cy(rFillGradient
.getCy());
434 const SvgNumber
R(rFillGradient
.getR());
436 aStart
.setX(SvgUnit::percent
== Cx
.getUnit() ? Cx
.getNumber() * 0.01 : Cx
.getNumber());
437 aStart
.setY(SvgUnit::percent
== Cy
.getUnit() ? Cy
.getNumber() * 0.01 : Cy
.getNumber());
438 fRadius
= (SvgUnit::percent
== R
.getUnit()) ? R
.getNumber() * 0.01 : R
.getNumber();
442 aFocal
.setX(pFx
? (SvgUnit::percent
== pFx
->getUnit() ? pFx
->getNumber() * 0.01 : pFx
->getNumber()) : aStart
.getX());
443 aFocal
.setY(pFy
? (SvgUnit::percent
== pFy
->getUnit() ? pFy
->getNumber() * 0.01 : pFy
->getNumber()) : aStart
.getY());
447 if(!aGeoToUnit
.isIdentity())
449 aStart
*= aGeoToUnit
;
450 fRadius
= (aGeoToUnit
* basegfx::B2DVector(fRadius
, 0.0)).getLength();
454 aFocal
*= aGeoToUnit
;
459 new drawinglayer::primitive2d::SvgRadialGradientPrimitive2D(
462 aSvgGradientEntryVector
,
465 SvgUnits::userSpaceOnUse
!= rFillGradient
.getGradientUnits(),
466 rFillGradient
.getSpreadMethod(),
467 bFocal
? &aFocal
: nullptr));
471 void SvgStyleAttributes::add_fillPatternTransform(
472 const basegfx::B2DPolyPolygon
& rPath
,
473 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
474 const SvgPatternNode
& rFillPattern
,
475 const basegfx::B2DRange
& rGeoRange
) const
477 // prepare fill polyPolygon with given pattern, check for patternTransform
478 if(rFillPattern
.getPatternTransform() && !rFillPattern
.getPatternTransform()->isIdentity())
480 // PatternTransform is active; Handle by filling the inverse transformed
481 // path and back-transforming the result
482 basegfx::B2DPolyPolygon
aPath(rPath
);
483 basegfx::B2DHomMatrix
aInv(*rFillPattern
.getPatternTransform());
484 drawinglayer::primitive2d::Primitive2DContainer aNewTarget
;
487 aPath
.transform(aInv
);
488 add_fillPattern(aPath
, aNewTarget
, rFillPattern
, aPath
.getB2DRange());
490 if(!aNewTarget
.empty())
493 new drawinglayer::primitive2d::TransformPrimitive2D(
494 *rFillPattern
.getPatternTransform(),
500 // no patternTransform, create fillPattern directly
501 add_fillPattern(rPath
, rTarget
, rFillPattern
, rGeoRange
);
505 void SvgStyleAttributes::add_fillPattern(
506 const basegfx::B2DPolyPolygon
& rPath
,
507 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
508 const SvgPatternNode
& rFillPattern
,
509 const basegfx::B2DRange
& rGeoRange
) const
511 // fill polyPolygon with given pattern
512 const drawinglayer::primitive2d::Primitive2DContainer
& rPrimitives
= rFillPattern
.getPatternPrimitives();
514 if(rPrimitives
.empty())
517 double fTargetWidth(rGeoRange
.getWidth());
518 double fTargetHeight(rGeoRange
.getHeight());
520 if(fTargetWidth
<= 0.0 || fTargetHeight
<= 0.0)
523 // get relative values from pattern
529 rFillPattern
.getValuesRelative(fX
, fY
, fW
, fH
, rGeoRange
, mrOwner
);
531 if(fW
<= 0.0 || fH
<= 0.0)
534 // build the reference range relative to the rGeoRange
535 const basegfx::B2DRange
aReferenceRange(fX
, fY
, fX
+ fW
, fY
+ fH
);
537 // find out how the content is mapped to the reference range
538 basegfx::B2DHomMatrix aMapPrimitivesToUnitRange
;
539 const basegfx::B2DRange
* pViewBox
= rFillPattern
.getViewBox();
543 // use viewBox/preserveAspectRatio
544 const SvgAspectRatio
& rRatio
= rFillPattern
.getSvgAspectRatio();
545 const basegfx::B2DRange
aUnitRange(0.0, 0.0, 1.0, 1.0);
549 // let mapping be created from SvgAspectRatio
550 aMapPrimitivesToUnitRange
= rRatio
.createMapping(aUnitRange
, *pViewBox
);
554 // choose default mapping
555 aMapPrimitivesToUnitRange
= SvgAspectRatio::createLinearMapping(aUnitRange
, *pViewBox
);
560 // use patternContentUnits
561 const SvgUnits
aPatternContentUnits(rFillPattern
.getPatternContentUnits() ? *rFillPattern
.getPatternContentUnits() : SvgUnits::userSpaceOnUse
);
563 if (SvgUnits::userSpaceOnUse
== aPatternContentUnits
)
565 // create relative mapping to unit coordinates
566 aMapPrimitivesToUnitRange
.scale(1.0 / (fW
* fTargetWidth
), 1.0 / (fH
* fTargetHeight
));
570 aMapPrimitivesToUnitRange
.scale(1.0 / fW
, 1.0 / fH
);
574 // apply aMapPrimitivesToUnitRange to content when used
575 drawinglayer::primitive2d::Primitive2DContainer
aPrimitives(rPrimitives
);
577 if(!aMapPrimitivesToUnitRange
.isIdentity())
579 const drawinglayer::primitive2d::Primitive2DReference
xRef(
580 new drawinglayer::primitive2d::TransformPrimitive2D(
581 aMapPrimitivesToUnitRange
,
584 aPrimitives
= drawinglayer::primitive2d::Primitive2DContainer
{ xRef
};
587 // embed in PatternFillPrimitive2D
589 new drawinglayer::primitive2d::PatternFillPrimitive2D(
595 void SvgStyleAttributes::add_fill(
596 const basegfx::B2DPolyPolygon
& rPath
,
597 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
598 const basegfx::B2DRange
& rGeoRange
) const
600 const basegfx::BColor
* pFill
= getFill();
601 const SvgGradientNode
* pFillGradient
= getSvgGradientNodeFill();
602 const SvgPatternNode
* pFillPattern
= getSvgPatternNodeFill();
604 if(!(pFill
|| pFillGradient
|| pFillPattern
))
607 const double fFillOpacity(getFillOpacity().solve(mrOwner
));
609 if(!basegfx::fTools::more(fFillOpacity
, 0.0))
612 drawinglayer::primitive2d::Primitive2DContainer aNewFill
;
616 // create fill content with SVG gradient primitive
617 add_fillGradient(rPath
, aNewFill
, *pFillGradient
, rGeoRange
);
619 else if(pFillPattern
)
621 // create fill content with SVG pattern primitive
622 add_fillPatternTransform(rPath
, aNewFill
, *pFillPattern
, rGeoRange
);
626 // create fill content
628 aNewFill
[0] = new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
636 if(basegfx::fTools::less(fFillOpacity
, 1.0))
638 // embed in UnifiedTransparencePrimitive2D
640 new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
642 1.0 - fFillOpacity
));
647 rTarget
.append(aNewFill
);
651 void SvgStyleAttributes::add_stroke(
652 const basegfx::B2DPolyPolygon
& rPath
,
653 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
654 const basegfx::B2DRange
& rGeoRange
) const
656 const basegfx::BColor
* pStroke
= getStroke();
657 const SvgGradientNode
* pStrokeGradient
= getSvgGradientNodeStroke();
658 const SvgPatternNode
* pStrokePattern
= getSvgPatternNodeStroke();
660 if(!(pStroke
|| pStrokeGradient
|| pStrokePattern
))
663 drawinglayer::primitive2d::Primitive2DContainer aNewStroke
;
664 const double fStrokeOpacity(getStrokeOpacity().solve(mrOwner
));
666 if(!basegfx::fTools::more(fStrokeOpacity
, 0.0))
669 // get stroke width; SVG does not use 0.0 == hairline, so 0.0 is no line at all
670 const double fStrokeWidth(getStrokeWidth().isSet() ? getStrokeWidth().solve(mrOwner
) : 1.0);
672 if(!basegfx::fTools::more(fStrokeWidth
, 0.0))
675 drawinglayer::primitive2d::Primitive2DReference aNewLinePrimitive
;
677 // if we have a line with two identical points it is not really a line,
678 // but used by SVG sometimes to paint a single dot.In that case, create
679 // the geometry for a single dot
680 if(1 == rPath
.count())
682 const basegfx::B2DPolygon
& aSingle(rPath
.getB2DPolygon(0));
684 if(2 == aSingle
.count() && aSingle
.getB2DPoint(0).equal(aSingle
.getB2DPoint(1)))
686 aNewLinePrimitive
= new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
687 basegfx::B2DPolyPolygon(
688 basegfx::utils::createPolygonFromCircle(
689 aSingle
.getB2DPoint(0),
690 fStrokeWidth
* (1.44 * 0.5))),
691 pStroke
? *pStroke
: basegfx::BColor(0.0, 0.0, 0.0));
695 if(!aNewLinePrimitive
.is())
697 // get LineJoin, LineCap and stroke array
698 const basegfx::B2DLineJoin
aB2DLineJoin(StrokeLinejoinToB2DLineJoin(getStrokeLinejoin()));
699 const css::drawing::LineCap
aLineCap(StrokeLinecapToDrawingLineCap(getStrokeLinecap()));
700 ::std::vector
< double > aDashArray
;
702 if(!getStrokeDasharray().empty())
704 aDashArray
= solveSvgNumberVector(getStrokeDasharray(), mrOwner
);
707 // convert svg:stroke-miterlimit to LineAttrute:mfMiterMinimumAngle
708 // The default needs to be set explicitly, because svg default <> Draw default
709 double fMiterMinimumAngle
;
710 if (getStrokeMiterLimit().isSet())
712 fMiterMinimumAngle
= 2.0 * asin(1.0/getStrokeMiterLimit().getNumber());
716 fMiterMinimumAngle
= 2.0 * asin(0.25); // 1.0/default 4.0
719 // todo: Handle getStrokeDashOffset()
721 // prepare line attribute
722 const drawinglayer::attribute::LineAttribute
aLineAttribute(
723 pStroke
? *pStroke
: basegfx::BColor(0.0, 0.0, 0.0),
729 if(aDashArray
.empty())
731 aNewLinePrimitive
= new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
737 const drawinglayer::attribute::StrokeAttribute
aStrokeAttribute(aDashArray
);
739 aNewLinePrimitive
= new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
746 if(pStrokeGradient
|| pStrokePattern
)
748 // put primitive into Primitive2DReference and Primitive2DSequence
749 const drawinglayer::primitive2d::Primitive2DContainer aSeq
{ aNewLinePrimitive
};
751 // use neutral ViewInformation and create LineGeometryExtractor2D
752 const drawinglayer::geometry::ViewInformation2D aViewInformation2D
;
753 drawinglayer::processor2d::LineGeometryExtractor2D
aExtractor(aViewInformation2D
);
756 aExtractor
.process(aSeq
);
758 // check for fill rsults
759 const basegfx::B2DPolyPolygonVector
& rLineFillVector(aExtractor
.getExtractedLineFills());
761 if(!rLineFillVector
.empty())
763 const basegfx::B2DPolyPolygon
aMergedArea(
764 basegfx::utils::mergeToSinglePolyPolygon(
767 if(aMergedArea
.count())
771 // create fill content with SVG gradient primitive. Use original GeoRange,
772 // e.g. from circle without LineWidth
773 add_fillGradient(aMergedArea
, aNewStroke
, *pStrokeGradient
, rGeoRange
);
775 else // if(pStrokePattern)
777 // create fill content with SVG pattern primitive. Use GeoRange
778 // from the expanded data, e.g. circle with extended geo by half linewidth
779 add_fillPatternTransform(aMergedArea
, aNewStroke
, *pStrokePattern
, aMergedArea
.getB2DRange());
786 aNewStroke
.push_back(aNewLinePrimitive
);
789 if(aNewStroke
.empty())
792 if(basegfx::fTools::less(fStrokeOpacity
, 1.0))
794 // embed in UnifiedTransparencePrimitive2D
796 new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
798 1.0 - fStrokeOpacity
));
803 rTarget
.append(aNewStroke
);
807 bool SvgStyleAttributes::prepare_singleMarker(
808 drawinglayer::primitive2d::Primitive2DContainer
& rMarkerPrimitives
,
809 basegfx::B2DHomMatrix
& rMarkerTransform
,
810 basegfx::B2DRange
& rClipRange
,
811 const SvgMarkerNode
& rMarker
) const
813 // reset return values
814 rMarkerTransform
.identity();
817 // get marker primitive representation
818 rMarkerPrimitives
= rMarker
.getMarkerPrimitives();
820 if(!rMarkerPrimitives
.empty())
822 basegfx::B2DRange
aPrimitiveRange(0.0, 0.0, 1.0, 1.0);
823 const basegfx::B2DRange
* pViewBox
= rMarker
.getViewBox();
827 aPrimitiveRange
= *pViewBox
;
830 if(aPrimitiveRange
.getWidth() > 0.0 && aPrimitiveRange
.getHeight() > 0.0)
832 double fTargetWidth(rMarker
.getMarkerWidth().isSet() ? rMarker
.getMarkerWidth().solve(mrOwner
, NumberType::xcoordinate
) : 3.0);
833 double fTargetHeight(rMarker
.getMarkerHeight().isSet() ? rMarker
.getMarkerHeight().solve(mrOwner
, NumberType::xcoordinate
) : 3.0);
834 const bool bStrokeWidth(SvgMarkerNode::MarkerUnits::strokeWidth
== rMarker
.getMarkerUnits());
835 const double fStrokeWidth(getStrokeWidth().isSet() ? getStrokeWidth().solve(mrOwner
) : 1.0);
839 // relative to strokeWidth
840 fTargetWidth
*= fStrokeWidth
;
841 fTargetHeight
*= fStrokeWidth
;
844 if(fTargetWidth
> 0.0 && fTargetHeight
> 0.0)
847 const basegfx::B2DRange
aTargetRange(0.0, 0.0, fTargetWidth
, fTargetHeight
);
848 const SvgAspectRatio
& rRatio
= rMarker
.getSvgAspectRatio();
852 // let mapping be created from SvgAspectRatio
853 rMarkerTransform
= rRatio
.createMapping(aTargetRange
, aPrimitiveRange
);
855 if(rRatio
.isMeetOrSlice())
858 rClipRange
= aPrimitiveRange
;
867 // adapt to strokewidth if needed
868 rMarkerTransform
.scale(fStrokeWidth
, fStrokeWidth
);
873 // choose default mapping
874 rMarkerTransform
= SvgAspectRatio::createLinearMapping(aTargetRange
, aPrimitiveRange
);
878 // get and apply reference point. Initially it's in marker local coordinate system
879 basegfx::B2DPoint
aRefPoint(
880 rMarker
.getRefX().isSet() ? rMarker
.getRefX().solve(mrOwner
, NumberType::xcoordinate
) : 0.0,
881 rMarker
.getRefY().isSet() ? rMarker
.getRefY().solve(mrOwner
, NumberType::ycoordinate
) : 0.0);
883 // apply MarkerTransform to have it in mapped coordinates
884 aRefPoint
*= rMarkerTransform
;
886 // apply by moving RepPoint to (0.0)
887 rMarkerTransform
.translate(-aRefPoint
.getX(), -aRefPoint
.getY());
897 void SvgStyleAttributes::add_markers(
898 const basegfx::B2DPolyPolygon
& rPath
,
899 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
900 const basegfx::utils::PointIndexSet
* pHelpPointIndices
) const
902 // try to access linked markers
903 const SvgMarkerNode
* pStart
= accessMarkerStartXLink();
904 const SvgMarkerNode
* pMid
= accessMarkerMidXLink();
905 const SvgMarkerNode
* pEnd
= accessMarkerEndXLink();
907 if(!(pStart
|| pMid
|| pEnd
))
910 const sal_uInt32
nSubPathCount(rPath
.count());
915 // remember prepared marker; pStart, pMid and pEnd may all be equal when
916 // only 'marker' was used instead of 'marker-start', 'marker-mid' or 'marker-end',
917 // see 'case SVGToken::Marker' in this file; thus in this case only one common
918 // marker in primitive form will be prepared
919 const SvgMarkerNode
* pPrepared
= nullptr;
921 // values for the prepared marker, results of prepare_singleMarker
922 drawinglayer::primitive2d::Primitive2DContainer aPreparedMarkerPrimitives
;
923 basegfx::B2DHomMatrix aPreparedMarkerTransform
;
924 basegfx::B2DRange aPreparedMarkerClipRange
;
926 for (sal_uInt32
a(0); a
< nSubPathCount
; a
++)
928 // iterate over sub-paths
929 const basegfx::B2DPolygon
& aSubPolygonPath(rPath
.getB2DPolygon(a
));
930 const sal_uInt32
nSubPolygonPointCount(aSubPolygonPath
.count());
931 const bool bSubPolygonPathIsClosed(aSubPolygonPath
.isClosed());
933 if(nSubPolygonPointCount
)
935 // for each sub-path, create one marker per point (when closed, two markers
936 // need to pe created for the 1st point)
937 const sal_uInt32
nTargetMarkerCount(bSubPolygonPathIsClosed
? nSubPolygonPointCount
+ 1 : nSubPolygonPointCount
);
939 for (sal_uInt32
b(0); b
< nTargetMarkerCount
; b
++)
941 const bool bIsFirstMarker(!a
&& !b
);
942 const bool bIsLastMarker(nSubPathCount
- 1 == a
&& nTargetMarkerCount
- 1 == b
);
943 const SvgMarkerNode
* pNeeded
= nullptr;
947 // 1st point in 1st sub-polygon, use pStart
950 else if(bIsLastMarker
)
952 // last point in last sub-polygon, use pEnd
957 // anything in-between, use pMid
961 if(pHelpPointIndices
&& !pHelpPointIndices
->empty())
963 const basegfx::utils::PointIndexSet::const_iterator
aFound(
964 pHelpPointIndices
->find(basegfx::utils::PointIndex(a
, b
)));
966 if(aFound
!= pHelpPointIndices
->end())
968 // this point is a pure helper point; do not create a marker for it
975 // no marker needs to be created for this point
979 if(pPrepared
!= pNeeded
)
981 // if needed marker is not yet prepared, do it now
982 if(prepare_singleMarker(aPreparedMarkerPrimitives
, aPreparedMarkerTransform
, aPreparedMarkerClipRange
, *pNeeded
))
988 // error: could not prepare given marker
989 OSL_ENSURE(false, "OOps, could not prepare given marker as primitives (!)");
995 // prepare complete transform
996 basegfx::B2DHomMatrix
aCombinedTransform(aPreparedMarkerTransform
);
999 if(pPrepared
->getOrientAuto())
1001 const sal_uInt32
nPointIndex(b
% nSubPolygonPointCount
);
1003 // get entering and leaving tangents; this will search backward/forward
1004 // in the polygon to find tangents unequal to zero, skipping empty edges
1005 // see basegfx descriptions)
1006 // Hint: Mozilla, Inkscape and others use only leaving tangent for start marker
1007 // and entering tangent for end marker. To achieve this (if wanted) it is possible
1008 // to make the fetch of aEntering/aLeaving dependent on bIsFirstMarker/bIsLastMarker.
1009 // This is not done here, see comment 14 in task #1232379#
1010 // or http://www.w3.org/TR/SVG/painting.html#OrientAttribute
1011 basegfx::B2DVector
aEntering(
1012 basegfx::utils::getTangentEnteringPoint(
1015 basegfx::B2DVector
aLeaving(
1016 basegfx::utils::getTangentLeavingPoint(
1019 const bool bEntering(!aEntering
.equalZero());
1020 const bool bLeaving(!aLeaving
.equalZero());
1022 if(bEntering
|| bLeaving
)
1024 basegfx::B2DVector
aSum(0.0, 0.0);
1028 aSum
+= aEntering
.normalize();
1033 aSum
+= aLeaving
.normalize();
1036 if(!aSum
.equalZero())
1038 const double fAngle(atan2(aSum
.getY(), aSum
.getX()));
1041 aCombinedTransform
.rotate(fAngle
);
1048 aCombinedTransform
.rotate(pPrepared
->getAngle());
1051 // get and apply target position
1052 const basegfx::B2DPoint
aPoint(aSubPolygonPath
.getB2DPoint(b
% nSubPolygonPointCount
));
1054 aCombinedTransform
.translate(aPoint
.getX(), aPoint
.getY());
1057 drawinglayer::primitive2d::Primitive2DReference
xMarker(
1058 new drawinglayer::primitive2d::TransformPrimitive2D(
1060 aPreparedMarkerPrimitives
));
1062 if(!aPreparedMarkerClipRange
.isEmpty())
1064 // marker needs to be clipped, it's bigger as the mapping
1065 basegfx::B2DPolyPolygon
aClipPolygon(basegfx::utils::createPolygonFromRect(aPreparedMarkerClipRange
));
1067 aClipPolygon
.transform(aCombinedTransform
);
1068 xMarker
= new drawinglayer::primitive2d::MaskPrimitive2D(
1070 drawinglayer::primitive2d::Primitive2DContainer
{ xMarker
});
1074 rTarget
.push_back(xMarker
);
1080 void SvgStyleAttributes::add_path(
1081 const basegfx::B2DPolyPolygon
& rPath
,
1082 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
1083 const basegfx::utils::PointIndexSet
* pHelpPointIndices
) const
1087 // no geometry at all
1091 const basegfx::B2DRange
aGeoRange(rPath
.getB2DRange());
1093 if(aGeoRange
.isEmpty())
1095 // no geometry range
1099 const double fOpacity(getOpacity().solve(mrOwner
));
1101 if(basegfx::fTools::equalZero(fOpacity
))
1107 // check if it's a line
1108 const bool bNoWidth(basegfx::fTools::equalZero(aGeoRange
.getWidth()));
1109 const bool bNoHeight(basegfx::fTools::equalZero(aGeoRange
.getHeight()));
1110 const bool bIsTwoPointLine(1 == rPath
.count()
1111 && !rPath
.areControlPointsUsed()
1112 && 2 == rPath
.getB2DPolygon(0).count());
1113 const bool bIsLine(bIsTwoPointLine
|| bNoWidth
|| bNoHeight
);
1118 basegfx::B2DPolyPolygon
aPath(rPath
);
1119 const bool bNeedToCheckClipRule(SVGToken::Path
== mrOwner
.getType() || SVGToken::Polygon
== mrOwner
.getType());
1120 const bool bClipPathIsNonzero(bNeedToCheckClipRule
&& mbIsClipPathContent
&& FillRule::nonzero
== maClipRule
);
1121 const bool bFillRuleIsNonzero(bNeedToCheckClipRule
&& !mbIsClipPathContent
&& FillRule::nonzero
== getFillRule());
1123 if(bClipPathIsNonzero
|| bFillRuleIsNonzero
)
1125 if(getFill() || getSvgGradientNodeFill() || getSvgPatternNodeFill()) {
1126 // nonzero is wanted, solve geometrically (see description on basegfx)
1127 // basegfx::utils::createNonzeroConform() is expensive for huge paths
1128 // and is only needed if path will be filled later on
1129 aPath
= basegfx::utils::createNonzeroConform(aPath
);
1133 add_fill(aPath
, rTarget
, aGeoRange
);
1137 add_stroke(rPath
, rTarget
, aGeoRange
);
1139 // Svg supports markers for path, polygon, polyline and line
1140 if(SVGToken::Path
== mrOwner
.getType() || // path
1141 SVGToken::Polygon
== mrOwner
.getType() || // polygon, polyline
1142 SVGToken::Line
== mrOwner
.getType()) // line
1144 // try to add markers
1145 add_markers(rPath
, rTarget
, pHelpPointIndices
);
1149 void SvgStyleAttributes::add_postProcess(
1150 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
1151 const drawinglayer::primitive2d::Primitive2DContainer
& rSource
,
1152 const basegfx::B2DHomMatrix
* pTransform
) const
1157 const double fOpacity(getOpacity().solve(mrOwner
));
1159 if(basegfx::fTools::equalZero(fOpacity
))
1164 drawinglayer::primitive2d::Primitive2DContainer
aSource(rSource
);
1166 if(basegfx::fTools::less(fOpacity
, 1.0))
1168 // embed in UnifiedTransparencePrimitive2D
1169 const drawinglayer::primitive2d::Primitive2DReference
xRef(
1170 new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
1174 aSource
= drawinglayer::primitive2d::Primitive2DContainer
{ xRef
};
1179 // create embedding group element with transformation. This applies the given
1180 // transformation to the graphical content, but *not* to mask and/or clip (as needed)
1181 const drawinglayer::primitive2d::Primitive2DReference
xRef(
1182 new drawinglayer::primitive2d::TransformPrimitive2D(
1186 aSource
= drawinglayer::primitive2d::Primitive2DContainer
{ xRef
};
1189 const SvgClipPathNode
* pClip
= accessClipPathXLink();
1192 // #i124852# transform may be needed when SvgUnits::userSpaceOnUse
1193 pClip
->apply(aSource
, pTransform
);
1194 pClip
= pClip
->getSvgStyleAttributes()->accessClipPathXLink();
1197 if(!aSource
.empty()) // test again, applied clipPath may have lead to empty geometry
1199 const SvgMaskNode
* pMask
= accessMaskXLink();
1202 // #i124852# transform may be needed when SvgUnits::userSpaceOnUse
1203 pMask
->apply(aSource
, pTransform
);
1207 // This is part of the SVG import of self-written SVGs from
1208 // Draw/Impress containing multiple Slides/Pages. To be able
1209 // to later 'break' these to multiple Pages if wanted, embed
1210 // each Page-Content in an identifiable Primitive Grouping
1212 // This is the case when the current Node is a GroupNode, has
1213 // class="Page" set, has a parent that also is a GroupNode
1214 // at which class="Slide" is set.
1215 // Multiple Slides/Pages are possible for Draw and Impress.
1216 if(SVGToken::G
== mrOwner
.getType() && mrOwner
.getClass())
1218 const OUString
aOwnerClass(*mrOwner
.getClass());
1220 if("Page" == aOwnerClass
)
1222 const SvgNode
* pParent(mrOwner
.getParent());
1224 if(nullptr != pParent
&& SVGToken::G
== pParent
->getType() && pParent
->getClass())
1226 const OUString
aParentClass(*pParent
->getClass());
1228 if("Slide" == aParentClass
)
1230 // embed to grouping primitive to identify the
1231 // Slide/Page information
1232 const drawinglayer::primitive2d::Primitive2DReference
xRef(
1233 new drawinglayer::primitive2d::PageHierarchyPrimitive2D(
1236 aSource
= drawinglayer::primitive2d::Primitive2DContainer
{ xRef
};
1242 if(!aSource
.empty()) // test again, applied mask may have lead to empty geometry
1244 // append to current target
1245 rTarget
.append(aSource
);
1249 SvgStyleAttributes::SvgStyleAttributes(SvgNode
& rOwner
)
1251 mpCssStyleParent(nullptr),
1254 maStopColor(basegfx::BColor(0.0, 0.0, 0.0), true),
1258 maStrokeDasharray(),
1259 maStrokeDashOffset(),
1260 maStrokeLinecap(StrokeLinecap::notset
),
1261 maStrokeLinejoin(StrokeLinejoin::notset
),
1262 maStrokeMiterLimit(),
1267 maFontStretch(FontStretch::notset
),
1268 maFontStyle(FontStyle::notset
),
1269 maFontWeight(FontWeight::notset
),
1270 maTextAlign(TextAlign::notset
),
1271 maTextDecoration(TextDecoration::notset
),
1272 maTextAnchor(TextAnchor::notset
),
1275 maVisibility(Visibility::notset
),
1279 mpClipPathXLink(nullptr),
1281 mpMaskXLink(nullptr),
1282 maMarkerStartXLink(),
1283 mpMarkerStartXLink(nullptr),
1285 mpMarkerMidXLink(nullptr),
1287 mpMarkerEndXLink(nullptr),
1288 maFillRule(FillRule::notset
),
1289 maClipRule(FillRule::nonzero
),
1290 maBaselineShift(BaselineShift::Baseline
),
1291 maBaselineShiftNumber(0),
1292 maResolvingParent(30, 0),
1293 mbIsClipPathContent(SVGToken::ClipPathNode
== mrOwner
.getType()),
1294 mbStrokeDasharraySet(false)
1296 const SvgStyleAttributes
* pParentStyle
= getParentStyle();
1297 if(!mbIsClipPathContent
)
1301 mbIsClipPathContent
= pParentStyle
->mbIsClipPathContent
;
1306 SvgStyleAttributes::~SvgStyleAttributes()
1310 void SvgStyleAttributes::parseStyleAttribute(
1312 const OUString
& aContent
,
1313 bool bCaseIndependent
)
1317 case SVGToken::Fill
:
1323 if(readSvgPaint(aContent
, aSvgPaint
, aURL
, bCaseIndependent
, aOpacity
))
1326 if(aOpacity
.isSet())
1328 setOpacity(SvgNumber(std::clamp(aOpacity
.getNumber(), 0.0, 1.0)));
1331 else if(!aURL
.isEmpty())
1333 maNodeFillURL
= aURL
;
1337 case SVGToken::FillOpacity
:
1341 if(readSingleNumber(aContent
, aNum
))
1343 maFillOpacity
= SvgNumber(std::clamp(aNum
.getNumber(), 0.0, 1.0), aNum
.getUnit(), aNum
.isSet());
1347 case SVGToken::FillRule
:
1349 if(!aContent
.isEmpty())
1351 if(aContent
.match(commonStrings::aStrNonzero
))
1353 maFillRule
= FillRule::nonzero
;
1355 else if(aContent
.match(commonStrings::aStrEvenOdd
))
1357 maFillRule
= FillRule::evenodd
;
1362 case SVGToken::Stroke
:
1368 if(readSvgPaint(aContent
, aSvgPaint
, aURL
, bCaseIndependent
, aOpacity
))
1370 maStroke
= aSvgPaint
;
1371 if(aOpacity
.isSet())
1373 setOpacity(SvgNumber(std::clamp(aOpacity
.getNumber(), 0.0, 1.0)));
1376 else if(!aURL
.isEmpty())
1378 maNodeStrokeURL
= aURL
;
1382 case SVGToken::StrokeDasharray
:
1384 if(!aContent
.isEmpty())
1386 SvgNumberVector aVector
;
1388 if(aContent
.startsWith("none"))
1390 // #121221# The special value 'none' needs to be handled
1391 // in the sense that *when* it is set, the parent shall not
1392 // be used. Before this was only dependent on the array being
1394 mbStrokeDasharraySet
= true;
1396 else if(readSvgNumberVector(aContent
, aVector
))
1398 maStrokeDasharray
= aVector
;
1403 case SVGToken::StrokeDashoffset
:
1407 if(readSingleNumber(aContent
, aNum
))
1409 if(aNum
.isPositive())
1411 maStrokeDashOffset
= aNum
;
1416 case SVGToken::StrokeLinecap
:
1418 if(!aContent
.isEmpty())
1420 if(aContent
.startsWith("butt"))
1422 setStrokeLinecap(StrokeLinecap::butt
);
1424 else if(aContent
.startsWith("round"))
1426 setStrokeLinecap(StrokeLinecap::round
);
1428 else if(aContent
.startsWith("square"))
1430 setStrokeLinecap(StrokeLinecap::square
);
1435 case SVGToken::StrokeLinejoin
:
1437 if(!aContent
.isEmpty())
1439 if(aContent
.startsWith("miter"))
1441 setStrokeLinejoin(StrokeLinejoin::miter
);
1443 else if(aContent
.startsWith("round"))
1445 setStrokeLinejoin(StrokeLinejoin::round
);
1447 else if(aContent
.startsWith("bevel"))
1449 setStrokeLinejoin(StrokeLinejoin::bevel
);
1454 case SVGToken::StrokeMiterlimit
:
1458 if(readSingleNumber(aContent
, aNum
))
1460 if(basegfx::fTools::moreOrEqual(aNum
.getNumber(), 1.0))
1461 { //readSingleNumber sets SvgUnit::px as default, if unit is missing. Correct it here.
1462 maStrokeMiterLimit
= SvgNumber(aNum
.getNumber(), SvgUnit::none
);
1467 case SVGToken::StrokeOpacity
:
1472 if(readSingleNumber(aContent
, aNum
))
1474 maStrokeOpacity
= SvgNumber(std::clamp(aNum
.getNumber(), 0.0, 1.0), aNum
.getUnit(), aNum
.isSet());
1478 case SVGToken::StrokeWidth
:
1482 if(readSingleNumber(aContent
, aNum
))
1484 if(aNum
.isPositive())
1486 maStrokeWidth
= aNum
;
1491 case SVGToken::StopColor
:
1497 if(readSvgPaint(aContent
, aSvgPaint
, aURL
, bCaseIndependent
, aOpacity
))
1499 maStopColor
= aSvgPaint
;
1500 if(aOpacity
.isSet())
1502 setOpacity(SvgNumber(std::clamp(aOpacity
.getNumber(), 0.0, 1.0)));
1507 case SVGToken::StopOpacity
:
1511 if(readSingleNumber(aContent
, aNum
))
1513 if(aNum
.isPositive())
1515 maStopOpacity
= aNum
;
1520 case SVGToken::Font
:
1524 case SVGToken::FontFamily
:
1526 SvgStringVector aSvgStringVector
;
1528 if(readSvgStringVector(aContent
, aSvgStringVector
))
1530 maFontFamily
= aSvgStringVector
;
1534 case SVGToken::FontSize
:
1536 if(!aContent
.isEmpty())
1538 if(aContent
.startsWith("xx-small"))
1540 setFontSize(FontSize::xx_small
);
1542 else if(aContent
.startsWith("x-small"))
1544 setFontSize(FontSize::x_small
);
1546 else if(aContent
.startsWith("small"))
1548 setFontSize(FontSize::small
);
1550 else if(aContent
.startsWith("smaller"))
1552 setFontSize(FontSize::smaller
);
1554 else if(aContent
.startsWith("medium"))
1556 setFontSize(FontSize::medium
);
1558 else if(aContent
.startsWith("larger"))
1560 setFontSize(FontSize::larger
);
1562 else if(aContent
.startsWith("large"))
1564 setFontSize(FontSize::large
);
1566 else if(aContent
.startsWith("x-large"))
1568 setFontSize(FontSize::x_large
);
1570 else if(aContent
.startsWith("xx-large"))
1572 setFontSize(FontSize::xx_large
);
1574 else if(aContent
.startsWith("initial"))
1576 setFontSize(FontSize::initial
);
1582 if(readSingleNumber(aContent
, aNum
))
1584 maFontSizeNumber
= aNum
;
1590 case SVGToken::FontSizeAdjust
:
1594 case SVGToken::FontStretch
:
1596 if(!aContent
.isEmpty())
1598 if(aContent
.startsWith("normal"))
1600 setFontStretch(FontStretch::normal
);
1602 else if(aContent
.startsWith("wider"))
1604 setFontStretch(FontStretch::wider
);
1606 else if(aContent
.startsWith("narrower"))
1608 setFontStretch(FontStretch::narrower
);
1610 else if(aContent
.startsWith("ultra-condensed"))
1612 setFontStretch(FontStretch::ultra_condensed
);
1614 else if(aContent
.startsWith("extra-condensed"))
1616 setFontStretch(FontStretch::extra_condensed
);
1618 else if(aContent
.startsWith("condensed"))
1620 setFontStretch(FontStretch::condensed
);
1622 else if(aContent
.startsWith("semi-condensed"))
1624 setFontStretch(FontStretch::semi_condensed
);
1626 else if(aContent
.startsWith("semi-expanded"))
1628 setFontStretch(FontStretch::semi_expanded
);
1630 else if(aContent
.startsWith("expanded"))
1632 setFontStretch(FontStretch::expanded
);
1634 else if(aContent
.startsWith("extra-expanded"))
1636 setFontStretch(FontStretch::extra_expanded
);
1638 else if(aContent
.startsWith("ultra-expanded"))
1640 setFontStretch(FontStretch::ultra_expanded
);
1645 case SVGToken::FontStyle
:
1647 if(!aContent
.isEmpty())
1649 if(aContent
.startsWith("normal"))
1651 setFontStyle(FontStyle::normal
);
1653 else if(aContent
.startsWith("italic"))
1655 setFontStyle(FontStyle::italic
);
1657 else if(aContent
.startsWith("oblique"))
1659 setFontStyle(FontStyle::oblique
);
1664 case SVGToken::FontVariant
:
1668 case SVGToken::FontWeight
:
1670 if(!aContent
.isEmpty())
1672 if(aContent
.startsWith("100"))
1674 setFontWeight(FontWeight::N100
);
1676 else if(aContent
.startsWith("200"))
1678 setFontWeight(FontWeight::N200
);
1680 else if(aContent
.startsWith("300"))
1682 setFontWeight(FontWeight::N300
);
1684 else if(aContent
.startsWith("400") || aContent
.startsWith("normal"))
1686 setFontWeight(FontWeight::N400
);
1688 else if(aContent
.startsWith("500"))
1690 setFontWeight(FontWeight::N500
);
1692 else if(aContent
.startsWith("600"))
1694 setFontWeight(FontWeight::N600
);
1696 else if(aContent
.startsWith("700") || aContent
.startsWith("bold"))
1698 setFontWeight(FontWeight::N700
);
1700 else if(aContent
.startsWith("800"))
1702 setFontWeight(FontWeight::N800
);
1704 else if(aContent
.startsWith("900"))
1706 setFontWeight(FontWeight::N900
);
1708 else if(aContent
.startsWith("bolder"))
1710 setFontWeight(FontWeight::bolder
);
1712 else if(aContent
.startsWith("lighter"))
1714 setFontWeight(FontWeight::lighter
);
1719 case SVGToken::Direction
:
1723 case SVGToken::LetterSpacing
:
1727 case SVGToken::TextDecoration
:
1729 if(!aContent
.isEmpty())
1731 if(aContent
.startsWith("none"))
1733 setTextDecoration(TextDecoration::none
);
1735 else if(aContent
.startsWith("underline"))
1737 setTextDecoration(TextDecoration::underline
);
1739 else if(aContent
.startsWith("overline"))
1741 setTextDecoration(TextDecoration::overline
);
1743 else if(aContent
.startsWith("line-through"))
1745 setTextDecoration(TextDecoration::line_through
);
1747 else if(aContent
.startsWith("blink"))
1749 setTextDecoration(TextDecoration::blink
);
1754 case SVGToken::UnicodeBidi
:
1758 case SVGToken::WordSpacing
:
1762 case SVGToken::TextAnchor
:
1764 if(!aContent
.isEmpty())
1766 if(aContent
.startsWith("start"))
1768 setTextAnchor(TextAnchor::start
);
1770 else if(aContent
.startsWith("middle"))
1772 setTextAnchor(TextAnchor::middle
);
1774 else if(aContent
.startsWith("end"))
1776 setTextAnchor(TextAnchor::end
);
1781 case SVGToken::TextAlign
:
1783 if(!aContent
.isEmpty())
1785 if(aContent
.startsWith("left"))
1787 setTextAlign(TextAlign::left
);
1789 else if(aContent
.startsWith("right"))
1791 setTextAlign(TextAlign::right
);
1793 else if(aContent
.startsWith("center"))
1795 setTextAlign(TextAlign::center
);
1797 else if(aContent
.startsWith("justify"))
1799 setTextAlign(TextAlign::justify
);
1804 case SVGToken::Color
:
1810 if(readSvgPaint(aContent
, aSvgPaint
, aURL
, bCaseIndependent
, aOpacity
))
1812 maColor
= aSvgPaint
;
1813 if(aOpacity
.isSet())
1815 setOpacity(SvgNumber(std::clamp(aOpacity
.getNumber(), 0.0, 1.0)));
1820 case SVGToken::Opacity
:
1824 if(readSingleNumber(aContent
, aNum
))
1826 setOpacity(SvgNumber(std::clamp(aNum
.getNumber(), 0.0, 1.0), aNum
.getUnit(), aNum
.isSet()));
1830 case SVGToken::Visibility
:
1832 if(!aContent
.isEmpty())
1834 if(aContent
.startsWith("visible"))
1836 setVisibility(Visibility::visible
);
1838 else if(aContent
.startsWith("hidden"))
1840 setVisibility(Visibility::hidden
);
1842 else if(aContent
.startsWith("collapse"))
1844 setVisibility(Visibility::collapse
);
1846 else if(aContent
.startsWith("inherit"))
1848 setVisibility(Visibility::inherit
);
1853 case SVGToken::Title
:
1858 case SVGToken::Desc
:
1863 case SVGToken::ClipPathProperty
:
1865 readLocalUrl(aContent
, maClipPathXLink
);
1868 case SVGToken::Mask
:
1870 readLocalUrl(aContent
, maMaskXLink
);
1873 case SVGToken::ClipRule
:
1875 if(!aContent
.isEmpty())
1877 if(aContent
.match(commonStrings::aStrNonzero
))
1879 maClipRule
= FillRule::nonzero
;
1881 else if(aContent
.match(commonStrings::aStrEvenOdd
))
1883 maClipRule
= FillRule::evenodd
;
1888 case SVGToken::Marker
:
1890 if(bCaseIndependent
)
1892 readLocalUrl(aContent
, maMarkerEndXLink
);
1893 maMarkerStartXLink
= maMarkerMidXLink
= maMarkerEndXLink
;
1897 case SVGToken::MarkerStart
:
1899 readLocalUrl(aContent
, maMarkerStartXLink
);
1902 case SVGToken::MarkerMid
:
1904 readLocalUrl(aContent
, maMarkerMidXLink
);
1907 case SVGToken::MarkerEnd
:
1909 readLocalUrl(aContent
, maMarkerEndXLink
);
1912 case SVGToken::Display
:
1914 // There may be display:none statements inside of style defines, e.g. the following line:
1915 // style="display:none"
1916 // taken from a svg example; this needs to be parsed and set at the owning node. Do not call
1917 // mrOwner.parseAttribute(...) here, this would lead to a recursion
1918 if(!aContent
.isEmpty())
1920 mrOwner
.setDisplay(getDisplayFromContent(aContent
));
1924 case SVGToken::BaselineShift
:
1926 if(!aContent
.isEmpty())
1930 if(aContent
.startsWith("sub"))
1932 setBaselineShift(BaselineShift::Sub
);
1934 else if(aContent
.startsWith("super"))
1936 setBaselineShift(BaselineShift::Super
);
1938 else if(readSingleNumber(aContent
, aNum
))
1940 maBaselineShiftNumber
= aNum
;
1942 if(SvgUnit::percent
== aNum
.getUnit())
1944 setBaselineShift(BaselineShift::Percentage
);
1948 setBaselineShift(BaselineShift::Length
);
1953 // no BaselineShift or inherit (which is automatically)
1954 setBaselineShift(BaselineShift::Baseline
);
1966 // #i125258# ask if fill is a direct hard attribute (no hierarchy)
1967 bool SvgStyleAttributes::isFillSet() const
1969 if(mbIsClipPathContent
)
1973 else if(maFill
.isSet())
1981 const basegfx::BColor
* SvgStyleAttributes::getCurrentColor() const
1983 static basegfx::BColor
aBlack(0.0, 0.0, 0.0);
1984 const basegfx::BColor
*aColor
= getColor();
1991 const basegfx::BColor
* SvgStyleAttributes::getFill() const
1995 if(maFill
.isCurrent())
1997 return getCurrentColor();
1999 else if(maFill
.isOn())
2001 return &maFill
.getBColor();
2003 else if(mbIsClipPathContent
)
2005 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2007 if (pSvgStyleAttributes
&& maResolvingParent
[0] < nStyleDepthLimit
)
2009 ++maResolvingParent
[0];
2010 const basegfx::BColor
* pFill
= pSvgStyleAttributes
->getFill();
2011 --maResolvingParent
[0];
2017 else if (maNodeFillURL
.isEmpty())
2019 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2021 if (pSvgStyleAttributes
&& maResolvingParent
[0] < nStyleDepthLimit
)
2023 ++maResolvingParent
[0];
2024 const basegfx::BColor
* pFill
= pSvgStyleAttributes
->getFill();
2025 --maResolvingParent
[0];
2027 if(mbIsClipPathContent
)
2035 static basegfx::BColor
aBlack(0.0, 0.0, 0.0);
2049 const basegfx::BColor
* SvgStyleAttributes::getStroke() const
2051 if(maStroke
.isSet())
2053 if(maStroke
.isCurrent())
2055 return getCurrentColor();
2057 else if(maStroke
.isOn())
2059 return &maStroke
.getBColor();
2062 else if (maNodeStrokeURL
.isEmpty())
2064 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2066 if (pSvgStyleAttributes
&& maResolvingParent
[1] < nStyleDepthLimit
)
2068 ++maResolvingParent
[1];
2069 auto ret
= pSvgStyleAttributes
->getStroke();
2070 --maResolvingParent
[1];
2078 const basegfx::BColor
& SvgStyleAttributes::getStopColor() const
2080 if(maStopColor
.isCurrent())
2082 return *getCurrentColor();
2086 return maStopColor
.getBColor();
2090 const SvgGradientNode
* SvgStyleAttributes::getSvgGradientNodeFill() const
2092 if (!maFill
.isSet())
2094 if (!maNodeFillURL
.isEmpty())
2096 const SvgNode
* pNode
= mrOwner
.getDocument().findSvgNodeById(maNodeFillURL
);
2100 if(SVGToken::LinearGradient
== pNode
->getType() || SVGToken::RadialGradient
== pNode
->getType())
2102 return static_cast< const SvgGradientNode
* >(pNode
);
2106 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2108 if (pSvgStyleAttributes
&& maResolvingParent
[2] < nStyleDepthLimit
)
2110 ++maResolvingParent
[2];
2111 auto ret
= pSvgStyleAttributes
->getSvgGradientNodeFill();
2112 --maResolvingParent
[2];
2120 const SvgGradientNode
* SvgStyleAttributes::getSvgGradientNodeStroke() const
2122 if (!maStroke
.isSet())
2124 if(!maNodeStrokeURL
.isEmpty())
2126 const SvgNode
* pNode
= mrOwner
.getDocument().findSvgNodeById(maNodeStrokeURL
);
2130 if(SVGToken::LinearGradient
== pNode
->getType() || SVGToken::RadialGradient
== pNode
->getType())
2132 return static_cast< const SvgGradientNode
* >(pNode
);
2137 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2139 if (pSvgStyleAttributes
&& maResolvingParent
[3] < nStyleDepthLimit
)
2141 ++maResolvingParent
[3];
2142 auto ret
= pSvgStyleAttributes
->getSvgGradientNodeStroke();
2143 --maResolvingParent
[3];
2151 const SvgPatternNode
* SvgStyleAttributes::getSvgPatternNodeFill() const
2153 if (!maFill
.isSet())
2155 if (!maNodeFillURL
.isEmpty())
2157 const SvgNode
* pNode
= mrOwner
.getDocument().findSvgNodeById(maNodeFillURL
);
2161 if(SVGToken::Pattern
== pNode
->getType())
2163 return static_cast< const SvgPatternNode
* >(pNode
);
2168 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2170 if (pSvgStyleAttributes
&& maResolvingParent
[4] < nStyleDepthLimit
)
2172 ++maResolvingParent
[4];
2173 auto ret
= pSvgStyleAttributes
->getSvgPatternNodeFill();
2174 --maResolvingParent
[4];
2182 const SvgPatternNode
* SvgStyleAttributes::getSvgPatternNodeStroke() const
2184 if (!maStroke
.isSet())
2186 if(!maNodeStrokeURL
.isEmpty())
2188 const SvgNode
* pNode
= mrOwner
.getDocument().findSvgNodeById(maNodeStrokeURL
);
2192 if(SVGToken::Pattern
== pNode
->getType())
2194 return static_cast< const SvgPatternNode
* >(pNode
);
2199 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2201 if (pSvgStyleAttributes
&& maResolvingParent
[5] < nStyleDepthLimit
)
2203 ++maResolvingParent
[5];
2204 auto ret
= pSvgStyleAttributes
->getSvgPatternNodeStroke();
2205 --maResolvingParent
[5];
2213 SvgNumber
SvgStyleAttributes::getStrokeWidth() const
2215 if(maStrokeWidth
.isSet())
2217 return maStrokeWidth
;
2220 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2222 if (pSvgStyleAttributes
&& maResolvingParent
[6] < nStyleDepthLimit
)
2224 ++maResolvingParent
[6];
2225 auto ret
= pSvgStyleAttributes
->getStrokeWidth();
2226 --maResolvingParent
[6];
2230 if(mbIsClipPathContent
)
2232 return SvgNumber(0.0);
2236 return SvgNumber(1.0);
2239 SvgNumber
SvgStyleAttributes::getStopOpacity() const
2241 if(maStopOpacity
.isSet())
2243 return maStopOpacity
;
2247 return SvgNumber(1.0);
2250 SvgNumber
SvgStyleAttributes::getFillOpacity() const
2252 if(maFillOpacity
.isSet())
2254 return maFillOpacity
;
2257 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2259 if (pSvgStyleAttributes
&& maResolvingParent
[7] < nStyleDepthLimit
)
2261 ++maResolvingParent
[7];
2262 auto ret
= pSvgStyleAttributes
->getFillOpacity();
2263 --maResolvingParent
[7];
2268 return SvgNumber(1.0);
2271 SvgNumber
SvgStyleAttributes::getOpacity() const
2273 if(maOpacity
.isSet())
2278 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2280 if (pSvgStyleAttributes
&& maResolvingParent
[8] < nStyleDepthLimit
)
2282 ++maResolvingParent
[8];
2283 auto ret
= pSvgStyleAttributes
->getOpacity();
2284 --maResolvingParent
[8];
2289 return SvgNumber(1.0);
2292 Visibility
SvgStyleAttributes::getVisibility() const
2294 if(Visibility::notset
== maVisibility
|| Visibility::inherit
== maVisibility
)
2296 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2298 if (pSvgStyleAttributes
&& maResolvingParent
[9] < nStyleDepthLimit
)
2300 ++maResolvingParent
[9];
2301 auto ret
= pSvgStyleAttributes
->getVisibility();
2302 --maResolvingParent
[9];
2305 //default is Visible
2306 return Visibility::visible
;
2309 // Visibility correction/exception for self-exported SVGs:
2310 // When Impress exports single or multi-page SVGs, it puts the
2311 // single slides into <g visibility="hidden">. Not sure why
2312 // this happens, but this leads (correctly) to empty imported
2314 // Thus, if Visibility::hidden is active and owner is a SVGToken::G
2315 // and it's parent is also a SVGToken::G and it has a Class 'SlideGroup'
2316 // set, check if we are an Impress export.
2317 // We are an Impress export if an SVG-Node titled 'ooo:meta_slides'
2319 // All together gives:
2320 if(Visibility::hidden
== maVisibility
2321 && SVGToken::G
== mrOwner
.getType()
2322 && nullptr != mrOwner
.getDocument().findSvgNodeById("ooo:meta_slides"))
2324 const SvgNode
* pParent(mrOwner
.getParent());
2326 if(nullptr != pParent
&& SVGToken::G
== pParent
->getType() && pParent
->getClass())
2328 const OUString
aClass(*pParent
->getClass());
2330 if("SlideGroup" == aClass
)
2332 // if we detect this exception,
2333 // override Visibility::hidden -> Visibility::visible
2334 return Visibility::visible
;
2339 return maVisibility
;
2342 FillRule
SvgStyleAttributes::getFillRule() const
2344 if(FillRule::notset
!= maFillRule
)
2349 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2351 if (pSvgStyleAttributes
&& maResolvingParent
[10] < nStyleDepthLimit
)
2353 ++maResolvingParent
[10];
2354 auto ret
= pSvgStyleAttributes
->getFillRule();
2355 --maResolvingParent
[10];
2359 // default is NonZero
2360 return FillRule::nonzero
;
2363 const SvgNumberVector
& SvgStyleAttributes::getStrokeDasharray() const
2365 if(!maStrokeDasharray
.empty())
2367 return maStrokeDasharray
;
2369 else if(mbStrokeDasharraySet
)
2371 // #121221# is set to empty *by purpose*, do not visit parent styles
2372 return maStrokeDasharray
;
2375 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2377 if (pSvgStyleAttributes
&& maResolvingParent
[11] < nStyleDepthLimit
)
2379 ++maResolvingParent
[11];
2380 const SvgNumberVector
& ret
= pSvgStyleAttributes
->getStrokeDasharray();
2381 --maResolvingParent
[11];
2386 return maStrokeDasharray
;
2389 SvgNumber
SvgStyleAttributes::getStrokeDashOffset() const
2391 if(maStrokeDashOffset
.isSet())
2393 return maStrokeDashOffset
;
2396 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2398 if (pSvgStyleAttributes
&& maResolvingParent
[12] < nStyleDepthLimit
)
2400 ++maResolvingParent
[12];
2401 auto ret
= pSvgStyleAttributes
->getStrokeDashOffset();
2402 --maResolvingParent
[12];
2407 return SvgNumber(0.0);
2410 StrokeLinecap
SvgStyleAttributes::getStrokeLinecap() const
2412 if(maStrokeLinecap
!= StrokeLinecap::notset
)
2414 return maStrokeLinecap
;
2417 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2419 if (pSvgStyleAttributes
&& maResolvingParent
[13] < nStyleDepthLimit
)
2421 ++maResolvingParent
[13];
2422 auto ret
= pSvgStyleAttributes
->getStrokeLinecap();
2423 --maResolvingParent
[13];
2427 // default is StrokeLinecap::butt
2428 return StrokeLinecap::butt
;
2431 StrokeLinejoin
SvgStyleAttributes::getStrokeLinejoin() const
2433 if(maStrokeLinejoin
!= StrokeLinejoin::notset
)
2435 return maStrokeLinejoin
;
2438 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2440 if (pSvgStyleAttributes
&& maResolvingParent
[14] < nStyleDepthLimit
)
2442 ++maResolvingParent
[14];
2443 auto ret
= pSvgStyleAttributes
->getStrokeLinejoin();
2444 --maResolvingParent
[14];
2448 // default is StrokeLinejoin::butt
2449 return StrokeLinejoin::miter
;
2452 SvgNumber
SvgStyleAttributes::getStrokeMiterLimit() const
2454 if(maStrokeMiterLimit
.isSet())
2456 return maStrokeMiterLimit
;
2459 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2461 if (pSvgStyleAttributes
&& maResolvingParent
[15] < nStyleDepthLimit
)
2463 ++maResolvingParent
[15];
2464 auto ret
= pSvgStyleAttributes
->getStrokeMiterLimit();
2465 --maResolvingParent
[15];
2470 return SvgNumber(4.0, SvgUnit::none
);
2473 SvgNumber
SvgStyleAttributes::getStrokeOpacity() const
2475 if(maStrokeOpacity
.isSet())
2477 return maStrokeOpacity
;
2480 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2482 if (pSvgStyleAttributes
&& maResolvingParent
[16] < nStyleDepthLimit
)
2484 ++maResolvingParent
[16];
2485 auto ret
= pSvgStyleAttributes
->getStrokeOpacity();
2486 --maResolvingParent
[16];
2491 return SvgNumber(1.0);
2494 const SvgStringVector
& SvgStyleAttributes::getFontFamily() const
2496 if(!maFontFamily
.empty() && !maFontFamily
[0].startsWith("inherit"))
2498 return maFontFamily
;
2501 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2503 if (pSvgStyleAttributes
&& maResolvingParent
[17] < nStyleDepthLimit
)
2505 ++maResolvingParent
[17];
2506 const SvgStringVector
& ret
= pSvgStyleAttributes
->getFontFamily();
2507 --maResolvingParent
[17];
2512 return maFontFamily
;
2515 SvgNumber
SvgStyleAttributes::getFontSizeNumber() const
2517 // default size is 'medium' or 16px, which is equal to the default PPI used in svgio ( 96.0 )
2518 // converted to pixels
2519 const double aDefaultSize
= F_SVG_PIXEL_PER_INCH
/ 6.0;
2521 if(maFontSizeNumber
.isSet())
2523 if(!maFontSizeNumber
.isPositive())
2524 return aDefaultSize
;
2526 // #122524# Handle SvgUnit::percent relative to parent FontSize (see SVG1.1
2527 // spec 10.10 Font selection properties \91font-size\92, lastline (click 'normative
2528 // definition of the property')
2529 if(SvgUnit::percent
== maFontSizeNumber
.getUnit())
2531 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2533 if(pSvgStyleAttributes
)
2535 const SvgNumber aParentNumber
= pSvgStyleAttributes
->getFontSizeNumber();
2538 aParentNumber
.getNumber() * maFontSizeNumber
.getNumber() * 0.01,
2539 aParentNumber
.getUnit(),
2542 // if there's no parent style, set the font size based on the default size
2545 maFontSizeNumber
.getNumber() * aDefaultSize
/ 100.0, SvgUnit::px
, true);
2547 else if((SvgUnit::em
== maFontSizeNumber
.getUnit()) || (SvgUnit::ex
== maFontSizeNumber
.getUnit()))
2549 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2551 if(pSvgStyleAttributes
)
2553 const SvgNumber aParentNumber
= pSvgStyleAttributes
->getFontSizeNumber();
2556 aParentNumber
.getNumber() * maFontSizeNumber
.getNumber(),
2557 aParentNumber
.getUnit(),
2562 return maFontSizeNumber
;
2565 //In CSS2, the suggested scaling factor between adjacent indexes is 1.2
2568 case FontSize::notset
:
2570 case FontSize::xx_small
:
2572 return SvgNumber(aDefaultSize
/ 1.728);
2574 case FontSize::x_small
:
2576 return SvgNumber(aDefaultSize
/ 1.44);
2578 case FontSize::small
:
2580 return SvgNumber(aDefaultSize
/ 1.2);
2582 case FontSize::smaller
:
2584 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2585 if(pSvgStyleAttributes
)
2587 const SvgNumber aParentNumber
= pSvgStyleAttributes
->getFontSizeNumber();
2588 return SvgNumber(aParentNumber
.getNumber() / 1.2, aParentNumber
.getUnit());
2592 case FontSize::medium
:
2593 case FontSize::initial
:
2595 return SvgNumber(aDefaultSize
);
2597 case FontSize::large
:
2599 return SvgNumber(aDefaultSize
* 1.2);
2601 case FontSize::larger
:
2603 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2604 if(pSvgStyleAttributes
)
2606 const SvgNumber aParentNumber
= pSvgStyleAttributes
->getFontSizeNumber();
2607 return SvgNumber(aParentNumber
.getNumber() * 1.2, aParentNumber
.getUnit());
2611 case FontSize::x_large
:
2613 return SvgNumber(aDefaultSize
* 1.44);
2615 case FontSize::xx_large
:
2617 return SvgNumber(aDefaultSize
* 1.728);
2621 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2623 if(pSvgStyleAttributes
)
2625 return pSvgStyleAttributes
->getFontSizeNumber();
2628 return SvgNumber(aDefaultSize
);
2631 FontStretch
SvgStyleAttributes::getFontStretch() const
2633 if(maFontStretch
!= FontStretch::notset
)
2635 if(FontStretch::wider
!= maFontStretch
&& FontStretch::narrower
!= maFontStretch
)
2637 return maFontStretch
;
2641 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2643 if (pSvgStyleAttributes
&& maResolvingParent
[18] < nStyleDepthLimit
)
2645 ++maResolvingParent
[18];
2646 FontStretch aInherited
= pSvgStyleAttributes
->getFontStretch();
2647 --maResolvingParent
[18];
2649 if(FontStretch::wider
== maFontStretch
)
2651 aInherited
= getWider(aInherited
);
2653 else if(FontStretch::narrower
== maFontStretch
)
2655 aInherited
= getNarrower(aInherited
);
2661 // default is FontStretch::normal
2662 return FontStretch::normal
;
2665 FontStyle
SvgStyleAttributes::getFontStyle() const
2667 if(maFontStyle
!= FontStyle::notset
)
2672 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2674 if (pSvgStyleAttributes
&& maResolvingParent
[19] < nStyleDepthLimit
)
2676 ++maResolvingParent
[19];
2677 auto ret
= pSvgStyleAttributes
->getFontStyle();
2678 --maResolvingParent
[19];
2682 // default is FontStyle::normal
2683 return FontStyle::normal
;
2686 FontWeight
SvgStyleAttributes::getFontWeight() const
2688 if(maFontWeight
!= FontWeight::notset
)
2690 if(FontWeight::bolder
!= maFontWeight
&& FontWeight::lighter
!= maFontWeight
)
2692 return maFontWeight
;
2696 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2698 if (pSvgStyleAttributes
&& maResolvingParent
[20] < nStyleDepthLimit
)
2700 ++maResolvingParent
[20];
2701 FontWeight aInherited
= pSvgStyleAttributes
->getFontWeight();
2702 --maResolvingParent
[20];
2704 if(FontWeight::bolder
== maFontWeight
)
2706 aInherited
= getBolder(aInherited
);
2708 else if(FontWeight::lighter
== maFontWeight
)
2710 aInherited
= getLighter(aInherited
);
2716 // default is FontWeight::N400 (FontWeight::normal)
2717 return FontWeight::N400
;
2720 TextAlign
SvgStyleAttributes::getTextAlign() const
2722 if(maTextAlign
!= TextAlign::notset
)
2727 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2729 if (pSvgStyleAttributes
&& maResolvingParent
[21] < nStyleDepthLimit
)
2731 ++maResolvingParent
[21];
2732 auto ret
= pSvgStyleAttributes
->getTextAlign();
2733 --maResolvingParent
[21];
2737 // default is TextAlign::left
2738 return TextAlign::left
;
2741 const SvgStyleAttributes
* SvgStyleAttributes::getTextDecorationDefiningSvgStyleAttributes() const
2743 if(maTextDecoration
!= TextDecoration::notset
)
2748 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2750 if (pSvgStyleAttributes
&& maResolvingParent
[22] < nStyleDepthLimit
)
2752 ++maResolvingParent
[22];
2753 auto ret
= pSvgStyleAttributes
->getTextDecorationDefiningSvgStyleAttributes();
2754 --maResolvingParent
[22];
2762 TextDecoration
SvgStyleAttributes::getTextDecoration() const
2764 const SvgStyleAttributes
* pDefining
= getTextDecorationDefiningSvgStyleAttributes();
2768 return pDefining
->maTextDecoration
;
2772 // default is TextDecoration::none
2773 return TextDecoration::none
;
2777 TextAnchor
SvgStyleAttributes::getTextAnchor() const
2779 if(maTextAnchor
!= TextAnchor::notset
)
2781 return maTextAnchor
;
2784 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2786 if (pSvgStyleAttributes
&& maResolvingParent
[23] < nStyleDepthLimit
)
2788 ++maResolvingParent
[23];
2789 auto ret
= pSvgStyleAttributes
->getTextAnchor();
2790 --maResolvingParent
[23];
2794 // default is TextAnchor::start
2795 return TextAnchor::start
;
2798 const basegfx::BColor
* SvgStyleAttributes::getColor() const
2802 if(maColor
.isCurrent())
2804 OSL_ENSURE(false, "Svg error: current color uses current color (!)");
2807 else if(maColor
.isOn())
2809 return &maColor
.getBColor();
2814 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2816 if (pSvgStyleAttributes
&& maResolvingParent
[24] < nStyleDepthLimit
)
2818 ++maResolvingParent
[24];
2819 auto ret
= pSvgStyleAttributes
->getColor();
2820 --maResolvingParent
[24];
2828 OUString
const & SvgStyleAttributes::getClipPathXLink() const
2830 return maClipPathXLink
;
2833 const SvgClipPathNode
* SvgStyleAttributes::accessClipPathXLink() const
2835 if(!mpClipPathXLink
)
2837 const OUString
aClipPath(getClipPathXLink());
2839 if(!aClipPath
.isEmpty())
2841 const_cast< SvgStyleAttributes
* >(this)->mpClipPathXLink
= dynamic_cast< const SvgClipPathNode
* >(mrOwner
.getDocument().findSvgNodeById(aClipPath
));
2845 return mpClipPathXLink
;
2848 OUString
SvgStyleAttributes::getMaskXLink() const
2850 if(!maMaskXLink
.isEmpty())
2855 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2857 if (pSvgStyleAttributes
&& !pSvgStyleAttributes
->maMaskXLink
.isEmpty() && maResolvingParent
[25] < nStyleDepthLimit
)
2859 ++maResolvingParent
[25];
2860 auto ret
= pSvgStyleAttributes
->getMaskXLink();
2861 --maResolvingParent
[25];
2868 const SvgMaskNode
* SvgStyleAttributes::accessMaskXLink() const
2872 const OUString
aMask(getMaskXLink());
2874 if(!aMask
.isEmpty())
2876 const_cast< SvgStyleAttributes
* >(this)->mpMaskXLink
= dynamic_cast< const SvgMaskNode
* >(mrOwner
.getDocument().findSvgNodeById(aMask
));
2883 OUString
SvgStyleAttributes::getMarkerStartXLink() const
2885 if(!maMarkerStartXLink
.isEmpty())
2887 return maMarkerStartXLink
;
2890 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2892 if (pSvgStyleAttributes
&& maResolvingParent
[26] < nStyleDepthLimit
)
2894 ++maResolvingParent
[26];
2895 auto ret
= pSvgStyleAttributes
->getMarkerStartXLink();
2896 --maResolvingParent
[26];
2903 const SvgMarkerNode
* SvgStyleAttributes::accessMarkerStartXLink() const
2905 if(!mpMarkerStartXLink
)
2907 const OUString
aMarker(getMarkerStartXLink());
2909 if(!aMarker
.isEmpty())
2911 const_cast< SvgStyleAttributes
* >(this)->mpMarkerStartXLink
= dynamic_cast< const SvgMarkerNode
* >(mrOwner
.getDocument().findSvgNodeById(getMarkerStartXLink()));
2915 return mpMarkerStartXLink
;
2918 OUString
SvgStyleAttributes::getMarkerMidXLink() const
2920 if(!maMarkerMidXLink
.isEmpty())
2922 return maMarkerMidXLink
;
2925 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2927 if (pSvgStyleAttributes
&& maResolvingParent
[27] < nStyleDepthLimit
)
2929 ++maResolvingParent
[27];
2930 auto ret
= pSvgStyleAttributes
->getMarkerMidXLink();
2931 --maResolvingParent
[27];
2938 const SvgMarkerNode
* SvgStyleAttributes::accessMarkerMidXLink() const
2940 if(!mpMarkerMidXLink
)
2942 const OUString
aMarker(getMarkerMidXLink());
2944 if(!aMarker
.isEmpty())
2946 const_cast< SvgStyleAttributes
* >(this)->mpMarkerMidXLink
= dynamic_cast< const SvgMarkerNode
* >(mrOwner
.getDocument().findSvgNodeById(getMarkerMidXLink()));
2950 return mpMarkerMidXLink
;
2953 OUString
SvgStyleAttributes::getMarkerEndXLink() const
2955 if(!maMarkerEndXLink
.isEmpty())
2957 return maMarkerEndXLink
;
2960 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2962 if (pSvgStyleAttributes
&& maResolvingParent
[28] < nStyleDepthLimit
)
2964 ++maResolvingParent
[28];
2965 auto ret
= pSvgStyleAttributes
->getMarkerEndXLink();
2966 --maResolvingParent
[28];
2973 const SvgMarkerNode
* SvgStyleAttributes::accessMarkerEndXLink() const
2975 if(!mpMarkerEndXLink
)
2977 const OUString
aMarker(getMarkerEndXLink());
2979 if(!aMarker
.isEmpty())
2981 const_cast< SvgStyleAttributes
* >(this)->mpMarkerEndXLink
= dynamic_cast< const SvgMarkerNode
* >(mrOwner
.getDocument().findSvgNodeById(getMarkerEndXLink()));
2985 return mpMarkerEndXLink
;
2988 SvgNumber
SvgStyleAttributes::getBaselineShiftNumber() const
2990 // #122524# Handle SvgUnit::percent relative to parent BaselineShift
2991 if(SvgUnit::percent
== maBaselineShiftNumber
.getUnit())
2993 const SvgStyleAttributes
* pSvgStyleAttributes
= getParentStyle();
2995 if (pSvgStyleAttributes
&& maResolvingParent
[29] < nStyleDepthLimit
)
2997 ++maResolvingParent
[29];
2998 const SvgNumber aParentNumber
= pSvgStyleAttributes
->getBaselineShiftNumber();
2999 --maResolvingParent
[29];
3002 aParentNumber
.getNumber() * maBaselineShiftNumber
.getNumber() * 0.01,
3003 aParentNumber
.getUnit(),
3008 return maBaselineShiftNumber
;
3010 } // end of namespace svgio
3012 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */