calc: on editing invalidation of view with different zoom is wrong
[LibreOffice.git] / svgio / source / svgreader / svgstyleattributes.cxx
blob6c42fbe744f9b2ce8c6694279a76f1b656e5084f
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
22 #include <algorithm>
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)
88 switch(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;
98 default: break;
101 return aSource;
104 FontStretch getNarrower(FontStretch aSource)
106 switch(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;
116 default: break;
119 return aSource;
122 FontWeight getBolder(FontWeight aSource)
124 switch(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;
134 default: break;
137 return aSource;
140 FontWeight getLighter(FontWeight aSource)
142 switch(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;
152 default: break;
155 return aSource;
158 ::FontWeight getVclFontWeight(FontWeight aSource)
160 ::FontWeight nRetval(WEIGHT_NORMAL);
162 switch(aSource)
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;
173 default: break;
176 return nRetval;
179 void SvgStyleAttributes::readCssStyle(std::u16string_view rCandidate)
181 const sal_Int32 nLen(rCandidate.size());
182 sal_Int32 nPos(0);
184 while(nPos < nLen)
186 // get TokenName
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 (!)");
195 nPos++;
196 continue;
199 // get TokenValue
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
208 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());
237 // remove spaces
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();
258 return nullptr;
261 void SvgStyleAttributes::add_text(
262 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
263 drawinglayer::primitive2d::Primitive2DContainer&& rSource) const
265 if(rSource.empty())
266 return;
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);
287 // process
288 aExtractor.process(rSource);
290 // get results
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
317 // renderers
318 if(aMergedArea.count() && (pFillGradient || pFillPattern || bStrokeUsed))
320 // create text fill content based on geometry
321 add_fill(aMergedArea, rTarget, aMergedArea.getB2DRange());
323 else if(pFill)
325 // add the already prepared primitives for single color fill
326 rTarget.append(std::move(rSource));
329 // add stroke
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())
350 return;
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));
379 else
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;
396 aEnd *= aGeoToUnit;
399 rTarget.push_back(
400 new drawinglayer::primitive2d::SvgLinearGradientPrimitive2D(
401 aGradientTransform,
402 rPath,
403 std::move(aSvgGradientEntryVector),
404 aStart,
405 aEnd,
406 SvgUnits::userSpaceOnUse != rFillGradient.getGradientUnits(),
407 rFillGradient.getSpreadMethod()));
409 else
411 basegfx::B2DPoint aStart(0.5, 0.5);
412 basegfx::B2DPoint aFocal;
413 double fRadius(0.5);
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);
425 if(bFocal)
427 aFocal.setX(pFx ? pFx->solve(mrOwner, NumberType::xcoordinate) : aStart.getX());
428 aFocal.setY(pFy ? pFy->solve(mrOwner, NumberType::ycoordinate) : aStart.getY());
431 else
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();
442 if(bFocal)
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();
454 if(bFocal)
456 aFocal *= aGeoToUnit;
460 rTarget.push_back(
461 new drawinglayer::primitive2d::SvgRadialGradientPrimitive2D(
462 aGradientTransform,
463 rPath,
464 std::move(aSvgGradientEntryVector),
465 aStart,
466 fRadius,
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;
488 aInv.invert();
489 aPath.transform(aInv);
490 add_fillPattern(aPath, aNewTarget, rFillPattern, aPath.getB2DRange());
492 if(!aNewTarget.empty())
494 rTarget.push_back(
495 new drawinglayer::primitive2d::TransformPrimitive2D(
496 *rFillPattern.getPatternTransform(),
497 std::move(aNewTarget)));
500 else
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())
517 return;
519 double fTargetWidth(rGeoRange.getWidth());
520 double fTargetHeight(rGeoRange.getHeight());
522 if(fTargetWidth <= 0.0 || fTargetHeight <= 0.0)
523 return;
525 // get relative values from pattern
526 double fX(0.0);
527 double fY(0.0);
528 double fW(0.0);
529 double fH(0.0);
531 rFillPattern.getValuesRelative(fX, fY, fW, fH, rGeoRange, mrOwner);
533 if(fW <= 0.0 || fH <= 0.0)
534 return;
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();
543 if(pViewBox)
545 // use viewBox/preserveAspectRatio
546 const SvgAspectRatio& rRatio = rFillPattern.getSvgAspectRatio();
547 const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
549 if(rRatio.isSet())
551 // let mapping be created from SvgAspectRatio
552 aMapPrimitivesToUnitRange = rRatio.createMapping(aUnitRange, *pViewBox);
554 else
556 // choose default mapping
557 aMapPrimitivesToUnitRange = SvgAspectRatio::createLinearMapping(aUnitRange, *pViewBox);
560 else
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));
570 else
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
590 rTarget.push_back(
591 new drawinglayer::primitive2d::PatternFillPrimitive2D(
592 rPath,
593 std::move(aPrimitives),
594 aReferenceRange));
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))
607 return;
609 const double fFillOpacity(getFillOpacity().solve(mrOwner));
611 if(!basegfx::fTools::more(fFillOpacity, 0.0))
612 return;
614 drawinglayer::primitive2d::Primitive2DContainer aNewFill;
616 if(pFillGradient)
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);
626 else // if(pFill)
628 // create fill content
629 aNewFill.resize(1);
630 aNewFill[0] = new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
631 rPath,
632 *pFill);
635 if(aNewFill.empty())
636 return;
638 if(basegfx::fTools::less(fFillOpacity, 1.0))
640 // embed in UnifiedTransparencePrimitive2D
641 rTarget.push_back(
642 new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
643 std::move(aNewFill),
644 1.0 - fFillOpacity));
646 else
648 // append
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))
663 return;
665 drawinglayer::primitive2d::Primitive2DContainer aNewStroke;
666 const double fStrokeOpacity(getStrokeOpacity().solve(mrOwner));
668 if(!basegfx::fTools::more(fStrokeOpacity, 0.0))
669 return;
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))
675 return;
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());
716 else
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),
726 fStrokeWidth,
727 aB2DLineJoin,
728 aLineCap,
729 fMiterMinimumAngle);
731 if(aDashArray.empty())
733 aNewLinePrimitive = new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
734 rPath,
735 aLineAttribute);
737 else
739 drawinglayer::attribute::StrokeAttribute aStrokeAttribute(std::move(aDashArray));
741 aNewLinePrimitive = new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
742 rPath,
743 aLineAttribute,
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);
757 // process
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(
767 rLineFillVector));
769 if(aMergedArea.count())
771 if(pStrokeGradient)
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());
786 else // if(pStroke)
788 aNewStroke.push_back(aNewLinePrimitive);
791 if(aNewStroke.empty())
792 return;
794 if(basegfx::fTools::less(fStrokeOpacity, 1.0))
796 // embed in UnifiedTransparencePrimitive2D
797 rTarget.push_back(
798 new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
799 std::move(aNewStroke),
800 1.0 - fStrokeOpacity));
802 else
804 // append
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();
817 rClipRange.reset();
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();
827 if(pViewBox)
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);
839 if(bStrokeWidth)
841 // relative to strokeWidth
842 fTargetWidth *= fStrokeWidth;
843 fTargetHeight *= fStrokeWidth;
846 if(fTargetWidth > 0.0 && fTargetHeight > 0.0)
848 // create mapping
849 const basegfx::B2DRange aTargetRange(0.0, 0.0, fTargetWidth, fTargetHeight);
850 const SvgAspectRatio& rRatio = rMarker.getSvgAspectRatio();
852 if(rRatio.isSet())
854 // let mapping be created from SvgAspectRatio
855 rMarkerTransform = rRatio.createMapping(aTargetRange, aPrimitiveRange);
857 if(rRatio.isMeetOrSlice())
859 // need to clip
860 rClipRange = aPrimitiveRange;
863 else
865 if(!pViewBox)
867 if(bStrokeWidth)
869 // adapt to strokewidth if needed
870 rMarkerTransform.scale(fStrokeWidth, fStrokeWidth);
873 else
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());
891 return true;
896 return false;
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))
910 return;
912 const sal_uInt32 nSubPathCount(rPath.count());
914 if(!nSubPathCount)
915 return;
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;
947 if(bIsFirstMarker)
949 // 1st point in 1st sub-polygon, use pStart
950 pNeeded = pStart;
952 else if(bIsLastMarker)
954 // last point in last sub-polygon, use pEnd
955 pNeeded = pEnd;
957 else
959 // anything in-between, use pMid
960 pNeeded = 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
971 continue;
975 if(!pNeeded)
977 // no marker needs to be created for this point
978 continue;
981 if(pPrepared != pNeeded)
983 // if needed marker is not yet prepared, do it now
984 if(prepare_singleMarker(aPreparedMarkerPrimitives, aPreparedMarkerTransform, aPreparedMarkerClipRange, *pNeeded))
986 pPrepared = pNeeded;
988 else
990 // error: could not prepare given marker
991 OSL_ENSURE(false, "OOps, could not prepare given marker as primitives (!)");
992 pPrepared = nullptr;
993 continue;
997 // prepare complete transform
998 basegfx::B2DHomMatrix aCombinedTransform(aPreparedMarkerTransform);
1000 // get rotation
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(
1016 aSubPolygonPath,
1017 nPointIndex));
1018 basegfx::B2DVector aLeaving(
1019 basegfx::utils::getTangentLeavingPoint(
1020 aSubPolygonPath,
1021 nPointIndex));
1022 const bool bEntering(!aEntering.equalZero());
1023 const bool bLeaving(!aLeaving.equalZero());
1025 if(bEntering || bLeaving)
1027 basegfx::B2DVector aSum(0.0, 0.0);
1029 if(bEntering)
1031 if(bIsFirstMarker && pPrepared->getMarkerOrient() == SvgMarkerNode::MarkerOrient::auto_start_reverse)
1032 aSum -= aEntering.normalize();
1033 else
1034 aSum += aEntering.normalize();
1037 if(bLeaving)
1039 if(bIsFirstMarker && pPrepared->getMarkerOrient() == SvgMarkerNode::MarkerOrient::auto_start_reverse)
1040 aSum -= aLeaving.normalize();
1041 else
1042 aSum += aLeaving.normalize();
1045 if(!aSum.equalZero())
1047 const double fAngle(atan2(aSum.getY(), aSum.getX()));
1049 // apply rotation
1050 aCombinedTransform.rotate(fAngle);
1054 else
1056 // apply rotation
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());
1065 // prepare marker
1066 drawinglayer::primitive2d::Primitive2DReference xMarker(
1067 new drawinglayer::primitive2d::TransformPrimitive2D(
1068 aCombinedTransform,
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 });
1082 // add marker
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
1094 if(!rPath.count())
1096 // no geometry at all
1097 return;
1100 const basegfx::B2DRange aGeoRange(rPath.getB2DRange());
1102 if(aGeoRange.isEmpty())
1104 // no geometry range
1105 return;
1108 const double fOpacity(getOpacity().solve(mrOwner));
1110 if(basegfx::fTools::equalZero(fOpacity))
1112 // not visible
1113 return;
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);
1124 if(!bIsLine)
1126 // create fill
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);
1146 // create stroke
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
1165 if(rSource.empty())
1166 return;
1168 const double fOpacity(getOpacity().solve(mrOwner));
1170 if(basegfx::fTools::equalZero(fOpacity))
1172 return;
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(
1182 std::move(aSource),
1183 1.0 - fOpacity));
1185 aSource = drawinglayer::primitive2d::Primitive2DContainer { xRef };
1188 if(pTransform)
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(
1194 *pTransform,
1195 std::move(aSource)));
1197 aSource = drawinglayer::primitive2d::Primitive2DContainer { xRef };
1200 const SvgClipPathNode* pClip = accessClipPathXLink();
1201 while(pClip)
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();
1211 if(pMask)
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
1222 // Object.
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)
1261 : mrOwner(rOwner),
1262 mpCssStyleParent(nullptr),
1263 maStopColor(basegfx::BColor(0.0, 0.0, 0.0), true),
1264 maStrokeLinecap(StrokeLinecap::notset),
1265 maStrokeLinejoin(StrokeLinejoin::notset),
1266 maFontSize(),
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)
1290 if(pParentStyle)
1292 mbIsClipPathContent = pParentStyle->mbIsClipPathContent;
1297 SvgStyleAttributes::~SvgStyleAttributes()
1301 void SvgStyleAttributes::parseStyleAttribute(
1302 SVGToken aSVGToken,
1303 const OUString& aContent)
1305 switch(aSVGToken)
1307 case SVGToken::Fill:
1309 SvgPaint aSvgPaint;
1310 OUString aURL;
1311 SvgNumber aOpacity;
1313 if(readSvgPaint(aContent, aSvgPaint, aURL, aOpacity))
1315 setFill(aSvgPaint);
1316 if(aOpacity.isSet())
1318 setOpacity(SvgNumber(std::clamp(aOpacity.getNumber(), 0.0, 1.0)));
1321 else if(!aURL.isEmpty())
1323 maNodeFillURL = aURL;
1325 break;
1327 case SVGToken::FillOpacity:
1329 SvgNumber aNum;
1331 if(readSingleNumber(aContent, aNum))
1333 maFillOpacity = SvgNumber(std::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet());
1335 break;
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;
1350 break;
1352 case SVGToken::Stroke:
1354 SvgPaint aSvgPaint;
1355 OUString aURL;
1356 SvgNumber aOpacity;
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;
1370 break;
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
1383 // empty
1384 mbStrokeDasharraySet = true;
1386 else if(readSvgNumberVector(aContent, aVector))
1388 maStrokeDasharray = aVector;
1391 break;
1393 case SVGToken::StrokeDashoffset:
1395 SvgNumber aNum;
1397 if(readSingleNumber(aContent, aNum))
1399 if(aNum.isPositive())
1401 maStrokeDashOffset = aNum;
1404 break;
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);
1423 break;
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);
1442 break;
1444 case SVGToken::StrokeMiterlimit:
1446 SvgNumber aNum;
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);
1455 break;
1457 case SVGToken::StrokeOpacity:
1460 SvgNumber aNum;
1462 if(readSingleNumber(aContent, aNum))
1464 maStrokeOpacity = SvgNumber(std::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet());
1466 break;
1468 case SVGToken::StrokeWidth:
1470 SvgNumber aNum;
1472 if(readSingleNumber(aContent, aNum))
1474 if(aNum.isPositive())
1476 maStrokeWidth = aNum;
1479 break;
1481 case SVGToken::StopColor:
1483 SvgPaint aSvgPaint;
1484 OUString aURL;
1485 SvgNumber aOpacity;
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)));
1495 break;
1497 case SVGToken::StopOpacity:
1499 SvgNumber aNum;
1501 if(readSingleNumber(aContent, aNum))
1503 if(aNum.isPositive())
1505 maStopOpacity = aNum;
1508 break;
1510 case SVGToken::Font:
1512 break;
1514 case SVGToken::FontFamily:
1516 SvgStringVector aSvgStringVector;
1518 if(readSvgStringVector(aContent, aSvgStringVector))
1520 maFontFamily = aSvgStringVector;
1522 break;
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);
1568 else
1570 SvgNumber aNum;
1572 if(readSingleNumber(aContent, aNum))
1574 maFontSizeNumber = aNum;
1578 break;
1580 case SVGToken::FontSizeAdjust:
1582 break;
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);
1633 break;
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);
1652 break;
1654 case SVGToken::FontVariant:
1656 break;
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);
1707 break;
1709 case SVGToken::Direction:
1711 break;
1713 case SVGToken::LetterSpacing:
1715 break;
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);
1742 break;
1744 case SVGToken::UnicodeBidi:
1746 break;
1748 case SVGToken::WordSpacing:
1750 break;
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);
1769 break;
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);
1792 break;
1794 case SVGToken::Color:
1796 SvgPaint aSvgPaint;
1797 OUString aURL;
1798 SvgNumber aOpacity;
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)));
1808 break;
1810 case SVGToken::Opacity:
1812 SvgNumber aNum;
1814 if(readSingleNumber(aContent, aNum))
1816 setOpacity(SvgNumber(std::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet()));
1818 break;
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);
1841 break;
1843 case SVGToken::Title:
1845 maTitle = aContent;
1846 break;
1848 case SVGToken::Desc:
1850 maDesc = aContent;
1851 break;
1853 case SVGToken::ClipPathProperty:
1855 readLocalUrl(aContent, maClipPathXLink);
1856 break;
1858 case SVGToken::Mask:
1860 readLocalUrl(aContent, maMaskXLink);
1861 break;
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;
1876 break;
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;
1886 break;
1888 case SVGToken::MarkerStart:
1890 readLocalUrl(aContent, maMarkerStartXLink);
1891 break;
1893 case SVGToken::MarkerMid:
1895 readLocalUrl(aContent, maMarkerMidXLink);
1896 break;
1898 case SVGToken::MarkerEnd:
1900 readLocalUrl(aContent, maMarkerEndXLink);
1901 break;
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));
1913 break;
1915 case SVGToken::BaselineShift:
1917 if(!aContent.isEmpty())
1919 SvgNumber aNum;
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);
1937 else
1939 setBaselineShift(BaselineShift::Length);
1942 else
1944 // no BaselineShift or inherit (which is automatically)
1945 setBaselineShift(BaselineShift::Baseline);
1948 break;
1950 default:
1952 break;
1957 // #i125258# ask if fill is a direct hard attribute (no hierarchy)
1958 bool SvgStyleAttributes::isFillSet() const
1960 if(mbIsClipPathContent)
1962 return false;
1964 else if(maFill.isSet())
1966 return true;
1969 return false;
1972 const basegfx::BColor* SvgStyleAttributes::getCurrentColor() const
1974 static basegfx::BColor aBlack(0.0, 0.0, 0.0);
1975 const basegfx::BColor *aColor = getColor();
1976 if( aColor )
1977 return aColor;
1978 else
1979 return &aBlack;
1982 const basegfx::BColor* SvgStyleAttributes::getFill() const
1984 if(maFill.isSet())
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];
2004 return pFill;
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)
2020 if (pFill)
2022 return pFill;
2024 else
2026 static basegfx::BColor aBlack(0.0, 0.0, 0.0);
2027 return &aBlack;
2030 else
2032 return pFill;
2037 return nullptr;
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];
2062 return ret;
2066 return nullptr;
2069 const basegfx::BColor& SvgStyleAttributes::getStopColor() const
2071 if(maStopColor.isCurrent())
2073 return *getCurrentColor();
2075 else
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);
2089 if(pNode)
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];
2104 return ret;
2108 return nullptr;
2111 const SvgGradientNode* SvgStyleAttributes::getSvgGradientNodeStroke() const
2113 if (!maStroke.isSet())
2115 if(!maNodeStrokeURL.isEmpty())
2117 const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(maNodeStrokeURL);
2119 if(pNode)
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];
2135 return ret;
2139 return nullptr;
2142 const SvgPatternNode* SvgStyleAttributes::getSvgPatternNodeFill() const
2144 if (!maFill.isSet())
2146 if (!maNodeFillURL.isEmpty())
2148 const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(maNodeFillURL);
2150 if(pNode)
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];
2166 return ret;
2170 return nullptr;
2173 const SvgPatternNode* SvgStyleAttributes::getSvgPatternNodeStroke() const
2175 if (!maStroke.isSet())
2177 if(!maNodeStrokeURL.isEmpty())
2179 const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(maNodeStrokeURL);
2181 if(pNode)
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];
2197 return ret;
2201 return nullptr;
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];
2218 return ret;
2221 if(mbIsClipPathContent)
2223 return SvgNumber(0.0);
2226 // default is 1
2227 return SvgNumber(1.0);
2230 SvgNumber SvgStyleAttributes::getStopOpacity() const
2232 if(maStopOpacity.isSet())
2234 return maStopOpacity;
2237 // default is 1
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];
2255 return ret;
2258 // default is 1
2259 return SvgNumber(1.0);
2262 SvgNumber SvgStyleAttributes::getOpacity() const
2264 if(maOpacity.isSet())
2266 return maOpacity;
2269 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2271 if (pSvgStyleAttributes && maResolvingParent[8] < nStyleDepthLimit)
2273 ++maResolvingParent[8];
2274 auto ret = pSvgStyleAttributes->getOpacity();
2275 --maResolvingParent[8];
2276 return ret;
2279 // default is 1
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];
2294 return ret;
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
2304 // Graphics.
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'
2309 // exists.
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)
2337 return maFillRule;
2340 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2342 if (pSvgStyleAttributes && maResolvingParent[10] < nStyleDepthLimit)
2344 ++maResolvingParent[10];
2345 auto ret = pSvgStyleAttributes->getFillRule();
2346 --maResolvingParent[10];
2347 return ret;
2350 // default is NonZero
2351 return FillRule::nonzero;
2354 FillRule SvgStyleAttributes::getClipRule() const
2356 if(FillRule::notset != maClipRule)
2358 return maClipRule;
2361 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2363 if (pSvgStyleAttributes && maResolvingParent[31] < nStyleDepthLimit)
2365 ++maResolvingParent[31];
2366 auto ret = pSvgStyleAttributes->getClipRule();
2367 --maResolvingParent[31];
2368 return ret;
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];
2394 return ret;
2397 // default empty
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];
2415 return ret;
2418 // default is 0
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];
2436 return ret;
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];
2457 return ret;
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];
2478 return ret;
2481 // default is 4
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];
2499 return ret;
2502 // default is 1
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];
2520 return ret;
2523 // default is empty
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();
2548 return SvgNumber(
2549 aParentNumber.getNumber() * maFontSizeNumber.getNumber() * 0.01,
2550 aParentNumber.getUnit(),
2551 true);
2553 // if there's no parent style, set the font size based on the default size
2554 // 100% = 16px
2555 return SvgNumber(
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();
2566 return SvgNumber(
2567 aParentNumber.getNumber() * maFontSizeNumber.getNumber(),
2568 aParentNumber.getUnit(),
2569 true);
2573 return maFontSizeNumber;
2576 //In CSS2, the suggested scaling factor between adjacent indexes is 1.2
2577 switch(maFontSize)
2579 case FontSize::notset:
2580 break;
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());
2601 [[fallthrough]];
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());
2620 [[fallthrough]];
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);
2669 return aInherited;
2672 // default is FontStretch::normal
2673 return FontStretch::normal;
2676 FontStyle SvgStyleAttributes::getFontStyle() const
2678 if(maFontStyle != FontStyle::notset)
2680 return maFontStyle;
2683 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2685 if (pSvgStyleAttributes && maResolvingParent[19] < nStyleDepthLimit)
2687 ++maResolvingParent[19];
2688 auto ret = pSvgStyleAttributes->getFontStyle();
2689 --maResolvingParent[19];
2690 return ret;
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);
2724 return aInherited;
2727 // default is FontWeight::N400 (FontWeight::normal)
2728 return FontWeight::N400;
2731 TextAlign SvgStyleAttributes::getTextAlign() const
2733 if(maTextAlign != TextAlign::notset)
2735 return maTextAlign;
2738 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2740 if (pSvgStyleAttributes && maResolvingParent[21] < nStyleDepthLimit)
2742 ++maResolvingParent[21];
2743 auto ret = pSvgStyleAttributes->getTextAlign();
2744 --maResolvingParent[21];
2745 return ret;
2748 // default is TextAlign::left
2749 return TextAlign::left;
2752 const SvgStyleAttributes* SvgStyleAttributes::getTextDecorationDefiningSvgStyleAttributes() const
2754 if(maTextDecoration != TextDecoration::notset)
2756 return this;
2759 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2761 if (pSvgStyleAttributes && maResolvingParent[22] < nStyleDepthLimit)
2763 ++maResolvingParent[22];
2764 auto ret = pSvgStyleAttributes->getTextDecorationDefiningSvgStyleAttributes();
2765 --maResolvingParent[22];
2766 return ret;
2769 // default is 0
2770 return nullptr;
2773 TextDecoration SvgStyleAttributes::getTextDecoration() const
2775 const SvgStyleAttributes* pDefining = getTextDecorationDefiningSvgStyleAttributes();
2777 if(pDefining)
2779 return pDefining->maTextDecoration;
2781 else
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];
2802 return ret;
2805 // default is TextAnchor::start
2806 return TextAnchor::start;
2809 const basegfx::BColor* SvgStyleAttributes::getColor() const
2811 if(maColor.isSet())
2813 if(maColor.isCurrent())
2815 OSL_ENSURE(false, "Svg error: current color uses current color (!)");
2816 return nullptr;
2818 else if(maColor.isOn())
2820 return &maColor.getBColor();
2823 else
2825 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2827 if (pSvgStyleAttributes && maResolvingParent[24] < nStyleDepthLimit)
2829 ++maResolvingParent[24];
2830 auto ret = pSvgStyleAttributes->getColor();
2831 --maResolvingParent[24];
2832 return ret;
2836 return nullptr;
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];
2855 return ret;
2859 return OUString();
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())
2881 return maMaskXLink;
2884 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2886 if (pSvgStyleAttributes && maResolvingParent[25] < nStyleDepthLimit)
2888 ++maResolvingParent[25];
2889 auto ret = pSvgStyleAttributes->getMaskXLink();
2890 --maResolvingParent[25];
2891 return ret;
2894 return OUString();
2897 const SvgMaskNode* SvgStyleAttributes::accessMaskXLink() const
2899 if(!mpMaskXLink)
2901 const OUString aMask(getMaskXLink());
2903 if(!aMask.isEmpty())
2905 return dynamic_cast< const SvgMaskNode* >(mrOwner.getDocument().findSvgNodeById(aMask));
2909 return mpMaskXLink;
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];
2926 return ret;
2929 return OUString();
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];
2961 return ret;
2964 return OUString();
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];
2996 return ret;
2999 return OUString();
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];
3030 return SvgNumber(
3031 aParentNumber.getNumber() * maBaselineShiftNumber.getNumber() * 0.01,
3032 aParentNumber.getUnit(),
3033 true);
3037 return maBaselineShiftNumber;
3039 } // end of namespace svgio
3041 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */