bump product version to 7.2.5.1
[LibreOffice.git] / svgio / source / svgreader / svgstyleattributes.cxx
blob8ad08b0414e292eb165c3f2fd888f61c7cf5fca2
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>
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)
86 switch(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;
96 default: break;
99 return aSource;
102 FontStretch getNarrower(FontStretch aSource)
104 switch(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;
114 default: break;
117 return aSource;
120 FontWeight getBolder(FontWeight aSource)
122 switch(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;
132 default: break;
135 return aSource;
138 FontWeight getLighter(FontWeight aSource)
140 switch(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;
150 default: break;
153 return aSource;
156 ::FontWeight getVclFontWeight(FontWeight aSource)
158 ::FontWeight nRetval(WEIGHT_NORMAL);
160 switch(aSource)
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;
171 default: break;
174 return nRetval;
177 void SvgStyleAttributes::readCssStyle(const OUString& rCandidate)
179 const sal_Int32 nLen(rCandidate.getLength());
180 sal_Int32 nPos(0);
182 while(nPos < nLen)
184 // get TokenName
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 (!)");
193 nPos++;
194 continue;
197 // get TokenValue
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
206 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());
235 // remove spaces
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();
256 return nullptr;
259 void SvgStyleAttributes::add_text(
260 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
261 drawinglayer::primitive2d::Primitive2DContainer const & rSource) const
263 if(rSource.empty())
264 return;
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);
285 // process
286 aExtractor.process(rSource);
288 // get results
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
315 // renderers
316 if(aMergedArea.count() && (pFillGradient || pFillPattern || bStrokeUsed))
318 // create text fill content based on geometry
319 add_fill(aMergedArea, rTarget, aMergedArea.getB2DRange());
321 else if(pFill)
323 // add the already prepared primitives for single color fill
324 rTarget.append(rSource);
327 // add stroke
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())
348 return;
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));
377 else
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;
394 aEnd *= aGeoToUnit;
397 rTarget.push_back(
398 new drawinglayer::primitive2d::SvgLinearGradientPrimitive2D(
399 aGradientTransform,
400 rPath,
401 aSvgGradientEntryVector,
402 aStart,
403 aEnd,
404 SvgUnits::userSpaceOnUse != rFillGradient.getGradientUnits(),
405 rFillGradient.getSpreadMethod()));
407 else
409 basegfx::B2DPoint aStart(0.5, 0.5);
410 basegfx::B2DPoint aFocal;
411 double fRadius(0.5);
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);
423 if(bFocal)
425 aFocal.setX(pFx ? pFx->solve(mrOwner, NumberType::xcoordinate) : aStart.getX());
426 aFocal.setY(pFy ? pFy->solve(mrOwner, NumberType::ycoordinate) : aStart.getY());
429 else
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();
440 if(bFocal)
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();
452 if(bFocal)
454 aFocal *= aGeoToUnit;
458 rTarget.push_back(
459 new drawinglayer::primitive2d::SvgRadialGradientPrimitive2D(
460 aGradientTransform,
461 rPath,
462 aSvgGradientEntryVector,
463 aStart,
464 fRadius,
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;
486 aInv.invert();
487 aPath.transform(aInv);
488 add_fillPattern(aPath, aNewTarget, rFillPattern, aPath.getB2DRange());
490 if(!aNewTarget.empty())
492 rTarget.push_back(
493 new drawinglayer::primitive2d::TransformPrimitive2D(
494 *rFillPattern.getPatternTransform(),
495 aNewTarget));
498 else
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())
515 return;
517 double fTargetWidth(rGeoRange.getWidth());
518 double fTargetHeight(rGeoRange.getHeight());
520 if(fTargetWidth <= 0.0 || fTargetHeight <= 0.0)
521 return;
523 // get relative values from pattern
524 double fX(0.0);
525 double fY(0.0);
526 double fW(0.0);
527 double fH(0.0);
529 rFillPattern.getValuesRelative(fX, fY, fW, fH, rGeoRange, mrOwner);
531 if(fW <= 0.0 || fH <= 0.0)
532 return;
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();
541 if(pViewBox)
543 // use viewBox/preserveAspectRatio
544 const SvgAspectRatio& rRatio = rFillPattern.getSvgAspectRatio();
545 const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
547 if(rRatio.isSet())
549 // let mapping be created from SvgAspectRatio
550 aMapPrimitivesToUnitRange = rRatio.createMapping(aUnitRange, *pViewBox);
552 else
554 // choose default mapping
555 aMapPrimitivesToUnitRange = SvgAspectRatio::createLinearMapping(aUnitRange, *pViewBox);
558 else
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));
568 else
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,
582 aPrimitives));
584 aPrimitives = drawinglayer::primitive2d::Primitive2DContainer { xRef };
587 // embed in PatternFillPrimitive2D
588 rTarget.push_back(
589 new drawinglayer::primitive2d::PatternFillPrimitive2D(
590 rPath,
591 aPrimitives,
592 aReferenceRange));
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))
605 return;
607 const double fFillOpacity(getFillOpacity().solve(mrOwner));
609 if(!basegfx::fTools::more(fFillOpacity, 0.0))
610 return;
612 drawinglayer::primitive2d::Primitive2DContainer aNewFill;
614 if(pFillGradient)
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);
624 else // if(pFill)
626 // create fill content
627 aNewFill.resize(1);
628 aNewFill[0] = new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
629 rPath,
630 *pFill);
633 if(aNewFill.empty())
634 return;
636 if(basegfx::fTools::less(fFillOpacity, 1.0))
638 // embed in UnifiedTransparencePrimitive2D
639 rTarget.push_back(
640 new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
641 aNewFill,
642 1.0 - fFillOpacity));
644 else
646 // append
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))
661 return;
663 drawinglayer::primitive2d::Primitive2DContainer aNewStroke;
664 const double fStrokeOpacity(getStrokeOpacity().solve(mrOwner));
666 if(!basegfx::fTools::more(fStrokeOpacity, 0.0))
667 return;
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))
673 return;
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());
714 else
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),
724 fStrokeWidth,
725 aB2DLineJoin,
726 aLineCap,
727 fMiterMinimumAngle);
729 if(aDashArray.empty())
731 aNewLinePrimitive = new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
732 rPath,
733 aLineAttribute);
735 else
737 const drawinglayer::attribute::StrokeAttribute aStrokeAttribute(aDashArray);
739 aNewLinePrimitive = new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
740 rPath,
741 aLineAttribute,
742 aStrokeAttribute);
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);
755 // process
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(
765 rLineFillVector));
767 if(aMergedArea.count())
769 if(pStrokeGradient)
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());
784 else // if(pStroke)
786 aNewStroke.push_back(aNewLinePrimitive);
789 if(aNewStroke.empty())
790 return;
792 if(basegfx::fTools::less(fStrokeOpacity, 1.0))
794 // embed in UnifiedTransparencePrimitive2D
795 rTarget.push_back(
796 new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
797 aNewStroke,
798 1.0 - fStrokeOpacity));
800 else
802 // append
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();
815 rClipRange.reset();
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();
825 if(pViewBox)
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);
837 if(bStrokeWidth)
839 // relative to strokeWidth
840 fTargetWidth *= fStrokeWidth;
841 fTargetHeight *= fStrokeWidth;
844 if(fTargetWidth > 0.0 && fTargetHeight > 0.0)
846 // create mapping
847 const basegfx::B2DRange aTargetRange(0.0, 0.0, fTargetWidth, fTargetHeight);
848 const SvgAspectRatio& rRatio = rMarker.getSvgAspectRatio();
850 if(rRatio.isSet())
852 // let mapping be created from SvgAspectRatio
853 rMarkerTransform = rRatio.createMapping(aTargetRange, aPrimitiveRange);
855 if(rRatio.isMeetOrSlice())
857 // need to clip
858 rClipRange = aPrimitiveRange;
861 else
863 if(!pViewBox)
865 if(bStrokeWidth)
867 // adapt to strokewidth if needed
868 rMarkerTransform.scale(fStrokeWidth, fStrokeWidth);
871 else
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());
889 return true;
894 return false;
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))
908 return;
910 const sal_uInt32 nSubPathCount(rPath.count());
912 if(!nSubPathCount)
913 return;
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;
945 if(bIsFirstMarker)
947 // 1st point in 1st sub-polygon, use pStart
948 pNeeded = pStart;
950 else if(bIsLastMarker)
952 // last point in last sub-polygon, use pEnd
953 pNeeded = pEnd;
955 else
957 // anything in-between, use pMid
958 pNeeded = 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
969 continue;
973 if(!pNeeded)
975 // no marker needs to be created for this point
976 continue;
979 if(pPrepared != pNeeded)
981 // if needed marker is not yet prepared, do it now
982 if(prepare_singleMarker(aPreparedMarkerPrimitives, aPreparedMarkerTransform, aPreparedMarkerClipRange, *pNeeded))
984 pPrepared = pNeeded;
986 else
988 // error: could not prepare given marker
989 OSL_ENSURE(false, "OOps, could not prepare given marker as primitives (!)");
990 pPrepared = nullptr;
991 continue;
995 // prepare complete transform
996 basegfx::B2DHomMatrix aCombinedTransform(aPreparedMarkerTransform);
998 // get rotation
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(
1013 aSubPolygonPath,
1014 nPointIndex));
1015 basegfx::B2DVector aLeaving(
1016 basegfx::utils::getTangentLeavingPoint(
1017 aSubPolygonPath,
1018 nPointIndex));
1019 const bool bEntering(!aEntering.equalZero());
1020 const bool bLeaving(!aLeaving.equalZero());
1022 if(bEntering || bLeaving)
1024 basegfx::B2DVector aSum(0.0, 0.0);
1026 if(bEntering)
1028 aSum += aEntering.normalize();
1031 if(bLeaving)
1033 aSum += aLeaving.normalize();
1036 if(!aSum.equalZero())
1038 const double fAngle(atan2(aSum.getY(), aSum.getX()));
1040 // apply rotation
1041 aCombinedTransform.rotate(fAngle);
1045 else
1047 // apply rotation
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());
1056 // prepare marker
1057 drawinglayer::primitive2d::Primitive2DReference xMarker(
1058 new drawinglayer::primitive2d::TransformPrimitive2D(
1059 aCombinedTransform,
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(
1069 aClipPolygon,
1070 drawinglayer::primitive2d::Primitive2DContainer { xMarker });
1073 // add marker
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
1085 if(!rPath.count())
1087 // no geometry at all
1088 return;
1091 const basegfx::B2DRange aGeoRange(rPath.getB2DRange());
1093 if(aGeoRange.isEmpty())
1095 // no geometry range
1096 return;
1099 const double fOpacity(getOpacity().solve(mrOwner));
1101 if(basegfx::fTools::equalZero(fOpacity))
1103 // not visible
1104 return;
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);
1115 if(!bIsLine)
1117 // create fill
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);
1136 // create stroke
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
1154 if(rSource.empty())
1155 return;
1157 const double fOpacity(getOpacity().solve(mrOwner));
1159 if(basegfx::fTools::equalZero(fOpacity))
1161 return;
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(
1171 aSource,
1172 1.0 - fOpacity));
1174 aSource = drawinglayer::primitive2d::Primitive2DContainer { xRef };
1177 if(pTransform)
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(
1183 *pTransform,
1184 aSource));
1186 aSource = drawinglayer::primitive2d::Primitive2DContainer { xRef };
1189 const SvgClipPathNode* pClip = accessClipPathXLink();
1190 while(pClip)
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();
1200 if(pMask)
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
1211 // Object.
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(
1234 aSource));
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)
1250 : mrOwner(rOwner),
1251 mpCssStyleParent(nullptr),
1252 maFill(),
1253 maStroke(),
1254 maStopColor(basegfx::BColor(0.0, 0.0, 0.0), true),
1255 maStrokeWidth(),
1256 maStopOpacity(),
1257 maFillOpacity(),
1258 maStrokeDasharray(),
1259 maStrokeDashOffset(),
1260 maStrokeLinecap(StrokeLinecap::notset),
1261 maStrokeLinejoin(StrokeLinejoin::notset),
1262 maStrokeMiterLimit(),
1263 maStrokeOpacity(),
1264 maFontFamily(),
1265 maFontSize(),
1266 maFontSizeNumber(),
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 maColor(),
1274 maOpacity(),
1275 maVisibility(Visibility::notset),
1276 maTitle(),
1277 maDesc(),
1278 maClipPathXLink(),
1279 mpClipPathXLink(nullptr),
1280 maMaskXLink(),
1281 mpMaskXLink(nullptr),
1282 maMarkerStartXLink(),
1283 mpMarkerStartXLink(nullptr),
1284 maMarkerMidXLink(),
1285 mpMarkerMidXLink(nullptr),
1286 maMarkerEndXLink(),
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)
1299 if(pParentStyle)
1301 mbIsClipPathContent = pParentStyle->mbIsClipPathContent;
1306 SvgStyleAttributes::~SvgStyleAttributes()
1310 void SvgStyleAttributes::parseStyleAttribute(
1311 SVGToken aSVGToken,
1312 const OUString& aContent,
1313 bool bCaseIndependent)
1315 switch(aSVGToken)
1317 case SVGToken::Fill:
1319 SvgPaint aSvgPaint;
1320 OUString aURL;
1321 SvgNumber aOpacity;
1323 if(readSvgPaint(aContent, aSvgPaint, aURL, bCaseIndependent, aOpacity))
1325 setFill(aSvgPaint);
1326 if(aOpacity.isSet())
1328 setOpacity(SvgNumber(std::clamp(aOpacity.getNumber(), 0.0, 1.0)));
1331 else if(!aURL.isEmpty())
1333 maNodeFillURL = aURL;
1335 break;
1337 case SVGToken::FillOpacity:
1339 SvgNumber aNum;
1341 if(readSingleNumber(aContent, aNum))
1343 maFillOpacity = SvgNumber(std::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet());
1345 break;
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;
1360 break;
1362 case SVGToken::Stroke:
1364 SvgPaint aSvgPaint;
1365 OUString aURL;
1366 SvgNumber aOpacity;
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;
1380 break;
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
1393 // empty
1394 mbStrokeDasharraySet = true;
1396 else if(readSvgNumberVector(aContent, aVector))
1398 maStrokeDasharray = aVector;
1401 break;
1403 case SVGToken::StrokeDashoffset:
1405 SvgNumber aNum;
1407 if(readSingleNumber(aContent, aNum))
1409 if(aNum.isPositive())
1411 maStrokeDashOffset = aNum;
1414 break;
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);
1433 break;
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);
1452 break;
1454 case SVGToken::StrokeMiterlimit:
1456 SvgNumber aNum;
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);
1465 break;
1467 case SVGToken::StrokeOpacity:
1470 SvgNumber aNum;
1472 if(readSingleNumber(aContent, aNum))
1474 maStrokeOpacity = SvgNumber(std::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet());
1476 break;
1478 case SVGToken::StrokeWidth:
1480 SvgNumber aNum;
1482 if(readSingleNumber(aContent, aNum))
1484 if(aNum.isPositive())
1486 maStrokeWidth = aNum;
1489 break;
1491 case SVGToken::StopColor:
1493 SvgPaint aSvgPaint;
1494 OUString aURL;
1495 SvgNumber aOpacity;
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)));
1505 break;
1507 case SVGToken::StopOpacity:
1509 SvgNumber aNum;
1511 if(readSingleNumber(aContent, aNum))
1513 if(aNum.isPositive())
1515 maStopOpacity = aNum;
1518 break;
1520 case SVGToken::Font:
1522 break;
1524 case SVGToken::FontFamily:
1526 SvgStringVector aSvgStringVector;
1528 if(readSvgStringVector(aContent, aSvgStringVector))
1530 maFontFamily = aSvgStringVector;
1532 break;
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);
1578 else
1580 SvgNumber aNum;
1582 if(readSingleNumber(aContent, aNum))
1584 maFontSizeNumber = aNum;
1588 break;
1590 case SVGToken::FontSizeAdjust:
1592 break;
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);
1643 break;
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);
1662 break;
1664 case SVGToken::FontVariant:
1666 break;
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);
1717 break;
1719 case SVGToken::Direction:
1721 break;
1723 case SVGToken::LetterSpacing:
1725 break;
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);
1752 break;
1754 case SVGToken::UnicodeBidi:
1756 break;
1758 case SVGToken::WordSpacing:
1760 break;
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);
1779 break;
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);
1802 break;
1804 case SVGToken::Color:
1806 SvgPaint aSvgPaint;
1807 OUString aURL;
1808 SvgNumber aOpacity;
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)));
1818 break;
1820 case SVGToken::Opacity:
1822 SvgNumber aNum;
1824 if(readSingleNumber(aContent, aNum))
1826 setOpacity(SvgNumber(std::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet()));
1828 break;
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);
1851 break;
1853 case SVGToken::Title:
1855 maTitle = aContent;
1856 break;
1858 case SVGToken::Desc:
1860 maDesc = aContent;
1861 break;
1863 case SVGToken::ClipPathProperty:
1865 readLocalUrl(aContent, maClipPathXLink);
1866 break;
1868 case SVGToken::Mask:
1870 readLocalUrl(aContent, maMaskXLink);
1871 break;
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;
1886 break;
1888 case SVGToken::Marker:
1890 if(bCaseIndependent)
1892 readLocalUrl(aContent, maMarkerEndXLink);
1893 maMarkerStartXLink = maMarkerMidXLink = maMarkerEndXLink;
1895 break;
1897 case SVGToken::MarkerStart:
1899 readLocalUrl(aContent, maMarkerStartXLink);
1900 break;
1902 case SVGToken::MarkerMid:
1904 readLocalUrl(aContent, maMarkerMidXLink);
1905 break;
1907 case SVGToken::MarkerEnd:
1909 readLocalUrl(aContent, maMarkerEndXLink);
1910 break;
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));
1922 break;
1924 case SVGToken::BaselineShift:
1926 if(!aContent.isEmpty())
1928 SvgNumber aNum;
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);
1946 else
1948 setBaselineShift(BaselineShift::Length);
1951 else
1953 // no BaselineShift or inherit (which is automatically)
1954 setBaselineShift(BaselineShift::Baseline);
1957 break;
1959 default:
1961 break;
1966 // #i125258# ask if fill is a direct hard attribute (no hierarchy)
1967 bool SvgStyleAttributes::isFillSet() const
1969 if(mbIsClipPathContent)
1971 return false;
1973 else if(maFill.isSet())
1975 return true;
1978 return false;
1981 const basegfx::BColor* SvgStyleAttributes::getCurrentColor() const
1983 static basegfx::BColor aBlack(0.0, 0.0, 0.0);
1984 const basegfx::BColor *aColor = getColor();
1985 if( aColor )
1986 return aColor;
1987 else
1988 return &aBlack;
1991 const basegfx::BColor* SvgStyleAttributes::getFill() const
1993 if(maFill.isSet())
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];
2013 return pFill;
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)
2029 if (pFill)
2031 return pFill;
2033 else
2035 static basegfx::BColor aBlack(0.0, 0.0, 0.0);
2036 return &aBlack;
2039 else
2041 return pFill;
2046 return nullptr;
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];
2071 return ret;
2075 return nullptr;
2078 const basegfx::BColor& SvgStyleAttributes::getStopColor() const
2080 if(maStopColor.isCurrent())
2082 return *getCurrentColor();
2084 else
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);
2098 if(pNode)
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];
2113 return ret;
2117 return nullptr;
2120 const SvgGradientNode* SvgStyleAttributes::getSvgGradientNodeStroke() const
2122 if (!maStroke.isSet())
2124 if(!maNodeStrokeURL.isEmpty())
2126 const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(maNodeStrokeURL);
2128 if(pNode)
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];
2144 return ret;
2148 return nullptr;
2151 const SvgPatternNode* SvgStyleAttributes::getSvgPatternNodeFill() const
2153 if (!maFill.isSet())
2155 if (!maNodeFillURL.isEmpty())
2157 const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(maNodeFillURL);
2159 if(pNode)
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];
2175 return ret;
2179 return nullptr;
2182 const SvgPatternNode* SvgStyleAttributes::getSvgPatternNodeStroke() const
2184 if (!maStroke.isSet())
2186 if(!maNodeStrokeURL.isEmpty())
2188 const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(maNodeStrokeURL);
2190 if(pNode)
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];
2206 return ret;
2210 return nullptr;
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];
2227 return ret;
2230 if(mbIsClipPathContent)
2232 return SvgNumber(0.0);
2235 // default is 1
2236 return SvgNumber(1.0);
2239 SvgNumber SvgStyleAttributes::getStopOpacity() const
2241 if(maStopOpacity.isSet())
2243 return maStopOpacity;
2246 // default is 1
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];
2264 return ret;
2267 // default is 1
2268 return SvgNumber(1.0);
2271 SvgNumber SvgStyleAttributes::getOpacity() const
2273 if(maOpacity.isSet())
2275 return maOpacity;
2278 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2280 if (pSvgStyleAttributes && maResolvingParent[8] < nStyleDepthLimit)
2282 ++maResolvingParent[8];
2283 auto ret = pSvgStyleAttributes->getOpacity();
2284 --maResolvingParent[8];
2285 return ret;
2288 // default is 1
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];
2303 return ret;
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
2313 // Graphics.
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'
2318 // exists.
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)
2346 return maFillRule;
2349 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2351 if (pSvgStyleAttributes && maResolvingParent[10] < nStyleDepthLimit)
2353 ++maResolvingParent[10];
2354 auto ret = pSvgStyleAttributes->getFillRule();
2355 --maResolvingParent[10];
2356 return ret;
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];
2382 return ret;
2385 // default empty
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];
2403 return ret;
2406 // default is 0
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];
2424 return ret;
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];
2445 return ret;
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];
2466 return ret;
2469 // default is 4
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];
2487 return ret;
2490 // default is 1
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];
2508 return ret;
2511 // default is empty
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();
2537 return SvgNumber(
2538 aParentNumber.getNumber() * maFontSizeNumber.getNumber() * 0.01,
2539 aParentNumber.getUnit(),
2540 true);
2542 // if there's no parent style, set the font size based on the default size
2543 // 100% = 16px
2544 return SvgNumber(
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();
2555 return SvgNumber(
2556 aParentNumber.getNumber() * maFontSizeNumber.getNumber(),
2557 aParentNumber.getUnit(),
2558 true);
2562 return maFontSizeNumber;
2565 //In CSS2, the suggested scaling factor between adjacent indexes is 1.2
2566 switch(maFontSize)
2568 case FontSize::notset:
2569 break;
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());
2590 [[fallthrough]];
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());
2609 [[fallthrough]];
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);
2658 return aInherited;
2661 // default is FontStretch::normal
2662 return FontStretch::normal;
2665 FontStyle SvgStyleAttributes::getFontStyle() const
2667 if(maFontStyle != FontStyle::notset)
2669 return maFontStyle;
2672 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2674 if (pSvgStyleAttributes && maResolvingParent[19] < nStyleDepthLimit)
2676 ++maResolvingParent[19];
2677 auto ret = pSvgStyleAttributes->getFontStyle();
2678 --maResolvingParent[19];
2679 return ret;
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);
2713 return aInherited;
2716 // default is FontWeight::N400 (FontWeight::normal)
2717 return FontWeight::N400;
2720 TextAlign SvgStyleAttributes::getTextAlign() const
2722 if(maTextAlign != TextAlign::notset)
2724 return maTextAlign;
2727 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2729 if (pSvgStyleAttributes && maResolvingParent[21] < nStyleDepthLimit)
2731 ++maResolvingParent[21];
2732 auto ret = pSvgStyleAttributes->getTextAlign();
2733 --maResolvingParent[21];
2734 return ret;
2737 // default is TextAlign::left
2738 return TextAlign::left;
2741 const SvgStyleAttributes* SvgStyleAttributes::getTextDecorationDefiningSvgStyleAttributes() const
2743 if(maTextDecoration != TextDecoration::notset)
2745 return this;
2748 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2750 if (pSvgStyleAttributes && maResolvingParent[22] < nStyleDepthLimit)
2752 ++maResolvingParent[22];
2753 auto ret = pSvgStyleAttributes->getTextDecorationDefiningSvgStyleAttributes();
2754 --maResolvingParent[22];
2755 return ret;
2758 // default is 0
2759 return nullptr;
2762 TextDecoration SvgStyleAttributes::getTextDecoration() const
2764 const SvgStyleAttributes* pDefining = getTextDecorationDefiningSvgStyleAttributes();
2766 if(pDefining)
2768 return pDefining->maTextDecoration;
2770 else
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];
2791 return ret;
2794 // default is TextAnchor::start
2795 return TextAnchor::start;
2798 const basegfx::BColor* SvgStyleAttributes::getColor() const
2800 if(maColor.isSet())
2802 if(maColor.isCurrent())
2804 OSL_ENSURE(false, "Svg error: current color uses current color (!)");
2805 return nullptr;
2807 else if(maColor.isOn())
2809 return &maColor.getBColor();
2812 else
2814 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2816 if (pSvgStyleAttributes && maResolvingParent[24] < nStyleDepthLimit)
2818 ++maResolvingParent[24];
2819 auto ret = pSvgStyleAttributes->getColor();
2820 --maResolvingParent[24];
2821 return ret;
2825 return nullptr;
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())
2852 return maMaskXLink;
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];
2862 return ret;
2865 return OUString();
2868 const SvgMaskNode* SvgStyleAttributes::accessMaskXLink() const
2870 if(!mpMaskXLink)
2872 const OUString aMask(getMaskXLink());
2874 if(!aMask.isEmpty())
2876 const_cast< SvgStyleAttributes* >(this)->mpMaskXLink = dynamic_cast< const SvgMaskNode* >(mrOwner.getDocument().findSvgNodeById(aMask));
2880 return mpMaskXLink;
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];
2897 return ret;
2900 return OUString();
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];
2932 return ret;
2935 return OUString();
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];
2967 return ret;
2970 return OUString();
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];
3001 return SvgNumber(
3002 aParentNumber.getNumber() * maBaselineShiftNumber.getNumber() * 0.01,
3003 aParentNumber.getUnit(),
3004 true);
3008 return maBaselineShiftNumber;
3010 } // end of namespace svgio
3012 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */