lok: vcl: fix multiple floatwin removal case more robustly.
[LibreOffice.git] / svgio / source / svgreader / svgstyleattributes.cxx
blob342cca90cacefb5d81c618de6f1385f49b4baf34
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 <o3tl/clamp.hxx>
23 #include <svgstyleattributes.hxx>
24 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
25 #include <drawinglayer/primitive2d/polypolygonprimitive2d.hxx>
26 #include <svgnode.hxx>
27 #include <svgdocument.hxx>
28 #include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
29 #include <svggradientnode.hxx>
30 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
31 #include <basegfx/vector/b2enums.hxx>
32 #include <drawinglayer/processor2d/linegeometryextractor2d.hxx>
33 #include <drawinglayer/processor2d/textaspolygonextractor2d.hxx>
34 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
35 #include <svgclippathnode.hxx>
36 #include <svgmasknode.hxx>
37 #include <basegfx/polygon/b2dpolypolygontools.hxx>
38 #include <svgmarkernode.hxx>
39 #include <basegfx/curve/b2dcubicbezier.hxx>
40 #include <svgpatternnode.hxx>
41 #include <drawinglayer/primitive2d/patternfillprimitive2d.hxx>
42 #include <basegfx/polygon/b2dpolygontools.hxx>
43 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
44 #include <drawinglayer/primitive2d/pagehierarchyprimitive2d.hxx>
46 const int nStyleDepthLimit = 1024;
48 namespace svgio
50 namespace svgreader
52 static basegfx::B2DLineJoin StrokeLinejoinToB2DLineJoin(StrokeLinejoin aStrokeLinejoin)
54 if(StrokeLinejoin_round == aStrokeLinejoin)
56 return basegfx::B2DLineJoin::Round;
58 else if(StrokeLinejoin_bevel == aStrokeLinejoin)
60 return basegfx::B2DLineJoin::Bevel;
63 return basegfx::B2DLineJoin::Miter;
66 static css::drawing::LineCap StrokeLinecapToDrawingLineCap(StrokeLinecap aStrokeLinecap)
68 switch(aStrokeLinecap)
70 default: /* StrokeLinecap_notset, StrokeLinecap_butt */
72 return css::drawing::LineCap_BUTT;
74 case StrokeLinecap_round:
76 return css::drawing::LineCap_ROUND;
78 case StrokeLinecap_square:
80 return css::drawing::LineCap_SQUARE;
85 FontStretch getWider(FontStretch aSource)
87 switch(aSource)
89 case FontStretch_ultra_condensed: aSource = FontStretch_extra_condensed; break;
90 case FontStretch_extra_condensed: aSource = FontStretch_condensed; break;
91 case FontStretch_condensed: aSource = FontStretch_semi_condensed; break;
92 case FontStretch_semi_condensed: aSource = FontStretch_normal; break;
93 case FontStretch_normal: aSource = FontStretch_semi_expanded; break;
94 case FontStretch_semi_expanded: aSource = FontStretch_expanded; break;
95 case FontStretch_expanded: aSource = FontStretch_extra_expanded; break;
96 case FontStretch_extra_expanded: aSource = FontStretch_ultra_expanded; break;
97 default: break;
100 return aSource;
103 FontStretch getNarrower(FontStretch aSource)
105 switch(aSource)
107 case FontStretch_extra_condensed: aSource = FontStretch_ultra_condensed; break;
108 case FontStretch_condensed: aSource = FontStretch_extra_condensed; break;
109 case FontStretch_semi_condensed: aSource = FontStretch_condensed; break;
110 case FontStretch_normal: aSource = FontStretch_semi_condensed; break;
111 case FontStretch_semi_expanded: aSource = FontStretch_normal; break;
112 case FontStretch_expanded: aSource = FontStretch_semi_expanded; break;
113 case FontStretch_extra_expanded: aSource = FontStretch_expanded; break;
114 case FontStretch_ultra_expanded: aSource = FontStretch_extra_expanded; break;
115 default: break;
118 return aSource;
121 FontWeight getBolder(FontWeight aSource)
123 switch(aSource)
125 case FontWeight_100: aSource = FontWeight_200; break;
126 case FontWeight_200: aSource = FontWeight_300; break;
127 case FontWeight_300: aSource = FontWeight_400; break;
128 case FontWeight_400: aSource = FontWeight_500; break;
129 case FontWeight_500: aSource = FontWeight_600; break;
130 case FontWeight_600: aSource = FontWeight_700; break;
131 case FontWeight_700: aSource = FontWeight_800; break;
132 case FontWeight_800: aSource = FontWeight_900; break;
133 default: break;
136 return aSource;
139 FontWeight getLighter(FontWeight aSource)
141 switch(aSource)
143 case FontWeight_200: aSource = FontWeight_100; break;
144 case FontWeight_300: aSource = FontWeight_200; break;
145 case FontWeight_400: aSource = FontWeight_300; break;
146 case FontWeight_500: aSource = FontWeight_400; break;
147 case FontWeight_600: aSource = FontWeight_500; break;
148 case FontWeight_700: aSource = FontWeight_600; break;
149 case FontWeight_800: aSource = FontWeight_700; break;
150 case FontWeight_900: aSource = FontWeight_800; break;
151 default: break;
154 return aSource;
157 ::FontWeight getVclFontWeight(FontWeight aSource)
159 ::FontWeight nRetval(WEIGHT_NORMAL);
161 switch(aSource)
163 case FontWeight_100: nRetval = WEIGHT_ULTRALIGHT; break;
164 case FontWeight_200: nRetval = WEIGHT_LIGHT; break;
165 case FontWeight_300: nRetval = WEIGHT_SEMILIGHT; break;
166 case FontWeight_400: nRetval = WEIGHT_NORMAL; break;
167 case FontWeight_500: nRetval = WEIGHT_MEDIUM; break;
168 case FontWeight_600: nRetval = WEIGHT_SEMIBOLD; break;
169 case FontWeight_700: nRetval = WEIGHT_BOLD; break;
170 case FontWeight_800: nRetval = WEIGHT_ULTRABOLD; break;
171 case FontWeight_900: nRetval = WEIGHT_BLACK; break;
172 default: break;
175 return nRetval;
178 void SvgStyleAttributes::readCssStyle(const OUString& rCandidate)
180 const sal_Int32 nLen(rCandidate.getLength());
181 sal_Int32 nPos(0);
183 while(nPos < nLen)
185 // get TokenName
186 OUStringBuffer aTokenName;
187 skip_char(rCandidate, u' ', nPos, nLen);
188 copyString(rCandidate, nPos, aTokenName, nLen);
190 if (aTokenName.isEmpty())
192 // if no TokenName advance one by force to avoid death loop, continue
193 OSL_ENSURE(false, "Could not interpret on current position, advancing one byte (!)");
194 nPos++;
195 continue;
198 // get TokenValue
199 OUStringBuffer aTokenValue;
200 skip_char(rCandidate, u' ', u':', nPos, nLen);
201 copyToLimiter(rCandidate, u';', nPos, aTokenValue, nLen);
202 skip_char(rCandidate, u' ', u';', nPos, nLen);
204 if (aTokenValue.isEmpty())
206 // no value - continue
207 continue;
210 // generate OUStrings
211 const OUString aOUTokenName(aTokenName.makeStringAndClear());
212 OUString aOUTokenValue(aTokenValue.makeStringAndClear());
214 // check for '!important' CssStyle mark, currently not supported
215 // but needs to be extracted for correct parsing
216 OUString aTokenImportant("!important");
217 const sal_Int32 nIndexTokenImportant(aOUTokenValue.indexOf(aTokenImportant));
219 if(-1 != nIndexTokenImportant)
221 // if there currently just remove it and remove spaces to have the value only
222 OUString aNewOUTokenValue;
224 if(nIndexTokenImportant > 0)
226 // copy content before token
227 aNewOUTokenValue += aOUTokenValue.copy(0, nIndexTokenImportant);
230 if(aOUTokenValue.getLength() > nIndexTokenImportant + aTokenImportant.getLength())
232 // copy content after token
233 aNewOUTokenValue += aOUTokenValue.copy(nIndexTokenImportant + aTokenImportant.getLength());
236 // remove spaces
237 aOUTokenValue = aNewOUTokenValue.trim();
240 // valid token-value pair, parse it
241 parseStyleAttribute(StrToSVGToken(aOUTokenName, true), aOUTokenValue, true);
245 const SvgStyleAttributes* SvgStyleAttributes::getParentStyle() const
247 if(getCssStyleParent())
249 return getCssStyleParent();
252 if(mrOwner.supportsParentStyle() && mrOwner.getParent())
254 return mrOwner.getParent()->getSvgStyleAttributes();
257 return nullptr;
260 void SvgStyleAttributes::add_text(
261 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
262 drawinglayer::primitive2d::Primitive2DContainer const & rSource) const
264 if(!rSource.empty())
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());
336 void SvgStyleAttributes::add_fillGradient(
337 const basegfx::B2DPolyPolygon& rPath,
338 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
339 const SvgGradientNode& rFillGradient,
340 const basegfx::B2DRange& rGeoRange) const
342 // create fill content
343 drawinglayer::primitive2d::SvgGradientEntryVector aSvgGradientEntryVector;
345 // get the color stops
346 rFillGradient.collectGradientEntries(aSvgGradientEntryVector);
348 if(!aSvgGradientEntryVector.empty())
350 basegfx::B2DHomMatrix aGeoToUnit;
351 basegfx::B2DHomMatrix aGradientTransform;
353 if(rFillGradient.getGradientTransform())
355 aGradientTransform = *rFillGradient.getGradientTransform();
358 if(userSpaceOnUse == rFillGradient.getGradientUnits())
360 aGeoToUnit.translate(-rGeoRange.getMinX(), -rGeoRange.getMinY());
361 aGeoToUnit.scale(1.0 / rGeoRange.getWidth(), 1.0 / rGeoRange.getHeight());
364 if(SVGTokenLinearGradient == rFillGradient.getType())
366 basegfx::B2DPoint aStart(0.0, 0.0);
367 basegfx::B2DPoint aEnd(1.0, 0.0);
369 if(userSpaceOnUse == rFillGradient.getGradientUnits())
371 // all possible units
372 aStart.setX(rFillGradient.getX1().solve(mrOwner, xcoordinate));
373 aStart.setY(rFillGradient.getY1().solve(mrOwner, ycoordinate));
374 aEnd.setX(rFillGradient.getX2().solve(mrOwner, xcoordinate));
375 aEnd.setY(rFillGradient.getY2().solve(mrOwner, 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(Unit_percent == X1.getUnit() ? X1.getNumber() * 0.01 : X1.getNumber());
386 aStart.setY(Unit_percent == Y1.getUnit() ? Y1.getNumber() * 0.01 : Y1.getNumber());
387 aEnd.setX(Unit_percent == X2.getUnit() ? X2.getNumber() * 0.01 : X2.getNumber());
388 aEnd.setY(Unit_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 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(userSpaceOnUse == rFillGradient.getGradientUnits())
418 // all possible units
419 aStart.setX(rFillGradient.getCx().solve(mrOwner, xcoordinate));
420 aStart.setY(rFillGradient.getCy().solve(mrOwner, ycoordinate));
421 fRadius = rFillGradient.getR().solve(mrOwner);
423 if(bFocal)
425 aFocal.setX(pFx ? pFx->solve(mrOwner, xcoordinate) : aStart.getX());
426 aFocal.setY(pFy ? pFy->solve(mrOwner, 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(Unit_percent == Cx.getUnit() ? Cx.getNumber() * 0.01 : Cx.getNumber());
437 aStart.setY(Unit_percent == Cy.getUnit() ? Cy.getNumber() * 0.01 : Cy.getNumber());
438 fRadius = (Unit_percent == R.getUnit()) ? R.getNumber() * 0.01 : R.getNumber();
440 if(bFocal)
442 aFocal.setX(pFx ? (Unit_percent == pFx->getUnit() ? pFx->getNumber() * 0.01 : pFx->getNumber()) : aStart.getX());
443 aFocal.setY(pFy ? (Unit_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 userSpaceOnUse != rFillGradient.getGradientUnits(),
466 rFillGradient.getSpreadMethod(),
467 bFocal ? &aFocal : nullptr));
472 void SvgStyleAttributes::add_fillPatternTransform(
473 const basegfx::B2DPolyPolygon& rPath,
474 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
475 const SvgPatternNode& rFillPattern,
476 const basegfx::B2DRange& rGeoRange) const
478 // prepare fill polyPolygon with given pattern, check for patternTransform
479 if(rFillPattern.getPatternTransform() && !rFillPattern.getPatternTransform()->isIdentity())
481 // PatternTransform is active; Handle by filling the inverse transformed
482 // path and back-transforming the result
483 basegfx::B2DPolyPolygon aPath(rPath);
484 basegfx::B2DHomMatrix aInv(*rFillPattern.getPatternTransform());
485 drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
487 aInv.invert();
488 aPath.transform(aInv);
489 add_fillPattern(aPath, aNewTarget, rFillPattern, aPath.getB2DRange());
491 if(!aNewTarget.empty())
493 rTarget.push_back(
494 new drawinglayer::primitive2d::TransformPrimitive2D(
495 *rFillPattern.getPatternTransform(),
496 aNewTarget));
499 else
501 // no patternTransform, create fillPattern directly
502 add_fillPattern(rPath, rTarget, rFillPattern, rGeoRange);
506 void SvgStyleAttributes::add_fillPattern(
507 const basegfx::B2DPolyPolygon& rPath,
508 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
509 const SvgPatternNode& rFillPattern,
510 const basegfx::B2DRange& rGeoRange) const
512 // fill polyPolygon with given pattern
513 const drawinglayer::primitive2d::Primitive2DContainer& rPrimitives = rFillPattern.getPatternPrimitives();
515 if(!rPrimitives.empty())
517 double fTargetWidth(rGeoRange.getWidth());
518 double fTargetHeight(rGeoRange.getHeight());
520 if(fTargetWidth > 0.0 && fTargetHeight > 0.0)
522 // get relative values from pattern
523 double fX(0.0);
524 double fY(0.0);
525 double fW(0.0);
526 double fH(0.0);
528 rFillPattern.getValuesRelative(fX, fY, fW, fH, rGeoRange, mrOwner);
530 if(fW > 0.0 && fH > 0.0)
532 // build the reference range relative to the rGeoRange
533 const basegfx::B2DRange aReferenceRange(fX, fY, fX + fW, fY + fH);
535 // find out how the content is mapped to the reference range
536 basegfx::B2DHomMatrix aMapPrimitivesToUnitRange;
537 const basegfx::B2DRange* pViewBox = rFillPattern.getViewBox();
539 if(pViewBox)
541 // use viewBox/preserveAspectRatio
542 const SvgAspectRatio& rRatio = rFillPattern.getSvgAspectRatio();
543 const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
545 if(rRatio.isSet())
547 // let mapping be created from SvgAspectRatio
548 aMapPrimitivesToUnitRange = rRatio.createMapping(aUnitRange, *pViewBox);
550 else
552 // choose default mapping
553 aMapPrimitivesToUnitRange = SvgAspectRatio::createLinearMapping(aUnitRange, *pViewBox);
556 else
558 // use patternContentUnits
559 const SvgUnits aPatternContentUnits(rFillPattern.getPatternContentUnits() ? *rFillPattern.getPatternContentUnits() : userSpaceOnUse);
561 if(userSpaceOnUse == aPatternContentUnits)
563 // create relative mapping to unit coordinates
564 aMapPrimitivesToUnitRange.scale(1.0 / (fW * fTargetWidth), 1.0 / (fH * fTargetHeight));
566 else
568 aMapPrimitivesToUnitRange.scale(1.0 / fW, 1.0 / fH);
572 // apply aMapPrimitivesToUnitRange to content when used
573 drawinglayer::primitive2d::Primitive2DContainer aPrimitives(rPrimitives);
575 if(!aMapPrimitivesToUnitRange.isIdentity())
577 const drawinglayer::primitive2d::Primitive2DReference xRef(
578 new drawinglayer::primitive2d::TransformPrimitive2D(
579 aMapPrimitivesToUnitRange,
580 aPrimitives));
582 aPrimitives = drawinglayer::primitive2d::Primitive2DContainer { xRef };
585 // embed in PatternFillPrimitive2D
586 rTarget.push_back(
587 new drawinglayer::primitive2d::PatternFillPrimitive2D(
588 rPath,
589 aPrimitives,
590 aReferenceRange));
596 void SvgStyleAttributes::add_fill(
597 const basegfx::B2DPolyPolygon& rPath,
598 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
599 const basegfx::B2DRange& rGeoRange) const
601 const basegfx::BColor* pFill = getFill();
602 const SvgGradientNode* pFillGradient = getSvgGradientNodeFill();
603 const SvgPatternNode* pFillPattern = getSvgPatternNodeFill();
605 if(pFill || pFillGradient || pFillPattern)
607 const double fFillOpacity(getFillOpacity().solve(mrOwner));
609 if(basegfx::fTools::more(fFillOpacity, 0.0))
611 drawinglayer::primitive2d::Primitive2DContainer aNewFill;
613 if(pFillGradient)
615 // create fill content with SVG gradient primitive
616 add_fillGradient(rPath, aNewFill, *pFillGradient, rGeoRange);
618 else if(pFillPattern)
620 // create fill content with SVG pattern primitive
621 add_fillPatternTransform(rPath, aNewFill, *pFillPattern, rGeoRange);
623 else // if(pFill)
625 // create fill content
626 aNewFill.resize(1);
627 aNewFill[0] = new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
628 rPath,
629 *pFill);
632 if(!aNewFill.empty())
634 if(basegfx::fTools::less(fFillOpacity, 1.0))
636 // embed in UnifiedTransparencePrimitive2D
637 rTarget.push_back(
638 new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
639 aNewFill,
640 1.0 - fFillOpacity));
642 else
644 // append
645 rTarget.append(aNewFill);
652 void SvgStyleAttributes::add_stroke(
653 const basegfx::B2DPolyPolygon& rPath,
654 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
655 const basegfx::B2DRange& rGeoRange) const
657 const basegfx::BColor* pStroke = getStroke();
658 const SvgGradientNode* pStrokeGradient = getSvgGradientNodeStroke();
659 const SvgPatternNode* pStrokePattern = getSvgPatternNodeStroke();
661 if(pStroke || pStrokeGradient || pStrokePattern)
663 drawinglayer::primitive2d::Primitive2DContainer aNewStroke;
664 const double fStrokeOpacity(getStrokeOpacity().solve(mrOwner));
666 if(basegfx::fTools::more(fStrokeOpacity, 0.0))
668 // get stroke width; SVG does not use 0.0 == hairline, so 0.0 is no line at all
669 const double fStrokeWidth(getStrokeWidth().isSet() ? getStrokeWidth().solve(mrOwner) : 1.0);
671 if(basegfx::fTools::more(fStrokeWidth, 0.0))
673 drawinglayer::primitive2d::Primitive2DReference aNewLinePrimitive;
675 // if we have a line with two identical points it is not really a line,
676 // but used by SVG sometimes to paint a single dot.In that case, create
677 // the geometry for a single dot
678 if(1 == rPath.count())
680 const basegfx::B2DPolygon& aSingle(rPath.getB2DPolygon(0));
682 if(2 == aSingle.count() && aSingle.getB2DPoint(0).equal(aSingle.getB2DPoint(1)))
684 aNewLinePrimitive = new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
685 basegfx::B2DPolyPolygon(
686 basegfx::utils::createPolygonFromCircle(
687 aSingle.getB2DPoint(0),
688 fStrokeWidth * (1.44 * 0.5))),
689 pStroke ? *pStroke : basegfx::BColor(0.0, 0.0, 0.0));
693 if(!aNewLinePrimitive.is())
695 // get LineJoin, LineCap and stroke array
696 const basegfx::B2DLineJoin aB2DLineJoin(StrokeLinejoinToB2DLineJoin(getStrokeLinejoin()));
697 const css::drawing::LineCap aLineCap(StrokeLinecapToDrawingLineCap(getStrokeLinecap()));
698 ::std::vector< double > aDashArray;
700 if(!getStrokeDasharray().empty())
702 aDashArray = solveSvgNumberVector(getStrokeDasharray(), mrOwner);
705 // convert svg:stroke-miterlimit to LineAttrute:mfMiterMinimumAngle
706 // The default needs to be set explicitly, because svg default <> Draw default
707 double fMiterMinimumAngle;
708 if (getStrokeMiterLimit().isSet())
710 fMiterMinimumAngle = 2.0 * asin(1.0/getStrokeMiterLimit().getNumber());
712 else
714 fMiterMinimumAngle = 2.0 * asin(0.25); // 1.0/default 4.0
717 // todo: Handle getStrokeDashOffset()
719 // prepare line attribute
720 const drawinglayer::attribute::LineAttribute aLineAttribute(
721 pStroke ? *pStroke : basegfx::BColor(0.0, 0.0, 0.0),
722 fStrokeWidth,
723 aB2DLineJoin,
724 aLineCap,
725 fMiterMinimumAngle);
727 if(aDashArray.empty())
729 aNewLinePrimitive = new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
730 rPath,
731 aLineAttribute);
733 else
735 const drawinglayer::attribute::StrokeAttribute aStrokeAttribute(aDashArray);
737 aNewLinePrimitive = new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
738 rPath,
739 aLineAttribute,
740 aStrokeAttribute);
744 if(pStrokeGradient || pStrokePattern)
746 // put primitive into Primitive2DReference and Primitive2DSequence
747 const drawinglayer::primitive2d::Primitive2DContainer aSeq { aNewLinePrimitive };
749 // use neutral ViewInformation and create LineGeometryExtractor2D
750 const drawinglayer::geometry::ViewInformation2D aViewInformation2D;
751 drawinglayer::processor2d::LineGeometryExtractor2D aExtractor(aViewInformation2D);
753 // process
754 aExtractor.process(aSeq);
756 // check for fill rsults
757 const basegfx::B2DPolyPolygonVector& rLineFillVector(aExtractor.getExtractedLineFills());
759 if(!rLineFillVector.empty())
761 const basegfx::B2DPolyPolygon aMergedArea(
762 basegfx::utils::mergeToSinglePolyPolygon(
763 rLineFillVector));
765 if(aMergedArea.count())
767 if(pStrokeGradient)
769 // create fill content with SVG gradient primitive. Use original GeoRange,
770 // e.g. from circle without LineWidth
771 add_fillGradient(aMergedArea, aNewStroke, *pStrokeGradient, rGeoRange);
773 else // if(pStrokePattern)
775 // create fill content with SVG pattern primitive. Use GeoRange
776 // from the expanded data, e.g. circle with extended geo by half linewidth
777 add_fillPatternTransform(aMergedArea, aNewStroke, *pStrokePattern, aMergedArea.getB2DRange());
782 else // if(pStroke)
784 aNewStroke.push_back(aNewLinePrimitive);
787 if(!aNewStroke.empty())
789 if(basegfx::fTools::less(fStrokeOpacity, 1.0))
791 // embed in UnifiedTransparencePrimitive2D
792 rTarget.push_back(
793 new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
794 aNewStroke,
795 1.0 - fStrokeOpacity));
797 else
799 // append
800 rTarget.append(aNewStroke);
808 bool SvgStyleAttributes::prepare_singleMarker(
809 drawinglayer::primitive2d::Primitive2DContainer& rMarkerPrimitives,
810 basegfx::B2DHomMatrix& rMarkerTransform,
811 basegfx::B2DRange& rClipRange,
812 const SvgMarkerNode& rMarker) const
814 // reset return values
815 rMarkerTransform.identity();
816 rClipRange.reset();
818 // get marker primitive representation
819 rMarkerPrimitives = rMarker.getMarkerPrimitives();
821 if(!rMarkerPrimitives.empty())
823 basegfx::B2DRange aPrimitiveRange(0.0, 0.0, 1.0, 1.0);
824 const basegfx::B2DRange* pViewBox = rMarker.getViewBox();
826 if(pViewBox)
828 aPrimitiveRange = *pViewBox;
831 if(aPrimitiveRange.getWidth() > 0.0 && aPrimitiveRange.getHeight() > 0.0)
833 double fTargetWidth(rMarker.getMarkerWidth().isSet() ? rMarker.getMarkerWidth().solve(mrOwner, xcoordinate) : 3.0);
834 double fTargetHeight(rMarker.getMarkerHeight().isSet() ? rMarker.getMarkerHeight().solve(mrOwner, xcoordinate) : 3.0);
835 const bool bStrokeWidth(SvgMarkerNode::MarkerUnits::strokeWidth == rMarker.getMarkerUnits());
836 const double fStrokeWidth(getStrokeWidth().isSet() ? getStrokeWidth().solve(mrOwner) : 1.0);
838 if(bStrokeWidth)
840 // relative to strokeWidth
841 fTargetWidth *= fStrokeWidth;
842 fTargetHeight *= fStrokeWidth;
845 if(fTargetWidth > 0.0 && fTargetHeight > 0.0)
847 // create mapping
848 const basegfx::B2DRange aTargetRange(0.0, 0.0, fTargetWidth, fTargetHeight);
849 const SvgAspectRatio& rRatio = rMarker.getSvgAspectRatio();
851 if(rRatio.isSet())
853 // let mapping be created from SvgAspectRatio
854 rMarkerTransform = rRatio.createMapping(aTargetRange, aPrimitiveRange);
856 if(rRatio.isMeetOrSlice())
858 // need to clip
859 rClipRange = aPrimitiveRange;
862 else
864 if(!pViewBox)
866 if(bStrokeWidth)
868 // adapt to strokewidth if needed
869 rMarkerTransform.scale(fStrokeWidth, fStrokeWidth);
872 else
874 // choose default mapping
875 rMarkerTransform = SvgAspectRatio::createLinearMapping(aTargetRange, aPrimitiveRange);
879 // get and apply reference point. Initially it's in marker local coordinate system
880 basegfx::B2DPoint aRefPoint(
881 rMarker.getRefX().isSet() ? rMarker.getRefX().solve(mrOwner, xcoordinate) : 0.0,
882 rMarker.getRefY().isSet() ? rMarker.getRefY().solve(mrOwner, ycoordinate) : 0.0);
884 // apply MarkerTransform to have it in mapped coordinates
885 aRefPoint *= rMarkerTransform;
887 // apply by moving RepPoint to (0.0)
888 rMarkerTransform.translate(-aRefPoint.getX(), -aRefPoint.getY());
890 return true;
895 return false;
898 void SvgStyleAttributes::add_markers(
899 const basegfx::B2DPolyPolygon& rPath,
900 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
901 const basegfx::utils::PointIndexSet* pHelpPointIndices) const
903 // try to access linked markers
904 const SvgMarkerNode* pStart = accessMarkerStartXLink();
905 const SvgMarkerNode* pMid = accessMarkerMidXLink();
906 const SvgMarkerNode* pEnd = accessMarkerEndXLink();
908 if(pStart || pMid || pEnd)
910 const sal_uInt32 nSubPathCount(rPath.count());
912 if(nSubPathCount)
914 // remember prepared marker; pStart, pMid and pEnd may all be equal when
915 // only 'marker' was used instead of 'marker-start', 'marker-mid' or 'marker-end',
916 // see 'case SVGTokenMarker' in this file; thus in this case only one common
917 // marker in primitive form will be prepared
918 const SvgMarkerNode* pPrepared = nullptr;
920 // values for the prepared marker, results of prepare_singleMarker
921 drawinglayer::primitive2d::Primitive2DContainer aPreparedMarkerPrimitives;
922 basegfx::B2DHomMatrix aPreparedMarkerTransform;
923 basegfx::B2DRange aPreparedMarkerClipRange;
925 for (sal_uInt32 a(0); a < nSubPathCount; a++)
927 // iterate over sub-paths
928 const basegfx::B2DPolygon& aSubPolygonPath(rPath.getB2DPolygon(a));
929 const sal_uInt32 nSubPolygonPointCount(aSubPolygonPath.count());
930 const bool bSubPolygonPathIsClosed(aSubPolygonPath.isClosed());
932 if(nSubPolygonPointCount)
934 // for each sub-path, create one marker per point (when closed, two markers
935 // need to pe created for the 1st point)
936 const sal_uInt32 nTargetMarkerCount(bSubPolygonPathIsClosed ? nSubPolygonPointCount + 1 : nSubPolygonPointCount);
938 for (sal_uInt32 b(0); b < nTargetMarkerCount; b++)
940 const bool bIsFirstMarker(!a && !b);
941 const bool bIsLastMarker(nSubPathCount - 1 == a && nTargetMarkerCount - 1 == b);
942 const SvgMarkerNode* pNeeded = nullptr;
944 if(bIsFirstMarker)
946 // 1st point in 1st sub-polygon, use pStart
947 pNeeded = pStart;
949 else if(bIsLastMarker)
951 // last point in last sub-polygon, use pEnd
952 pNeeded = pEnd;
954 else
956 // anything in-between, use pMid
957 pNeeded = pMid;
960 if(pHelpPointIndices && !pHelpPointIndices->empty())
962 const basegfx::utils::PointIndexSet::const_iterator aFound(
963 pHelpPointIndices->find(basegfx::utils::PointIndex(a, b)));
965 if(aFound != pHelpPointIndices->end())
967 // this point is a pure helper point; do not create a marker for it
968 continue;
972 if(!pNeeded)
974 // no marker needs to be created for this point
975 continue;
978 if(pPrepared != pNeeded)
980 // if needed marker is not yet prepared, do it now
981 if(prepare_singleMarker(aPreparedMarkerPrimitives, aPreparedMarkerTransform, aPreparedMarkerClipRange, *pNeeded))
983 pPrepared = pNeeded;
985 else
987 // error: could not prepare given marker
988 OSL_ENSURE(false, "OOps, could not prepare given marker as primitives (!)");
989 pPrepared = nullptr;
990 continue;
994 // prepare complete transform
995 basegfx::B2DHomMatrix aCombinedTransform(aPreparedMarkerTransform);
997 // get rotation
998 if(pPrepared->getOrientAuto())
1000 const sal_uInt32 nPointIndex(b % nSubPolygonPointCount);
1002 // get entering and leaving tangents; this will search backward/forward
1003 // in the polygon to find tangents unequal to zero, skipping empty edges
1004 // see basegfx descriptions)
1005 // Hint: Mozilla, Inkscape and others use only leaving tangent for start marker
1006 // and entering tangent for end marker. To achieve this (if wanted) it is possible
1007 // to make the fetch of aEntering/aLeaving dependent on bIsFirstMarker/bIsLastMarker.
1008 // This is not done here, see comment 14 in task #1232379#
1009 // or http://www.w3.org/TR/SVG/painting.html#OrientAttribute
1010 basegfx::B2DVector aEntering(
1011 basegfx::utils::getTangentEnteringPoint(
1012 aSubPolygonPath,
1013 nPointIndex));
1014 basegfx::B2DVector aLeaving(
1015 basegfx::utils::getTangentLeavingPoint(
1016 aSubPolygonPath,
1017 nPointIndex));
1018 const bool bEntering(!aEntering.equalZero());
1019 const bool bLeaving(!aLeaving.equalZero());
1021 if(bEntering || bLeaving)
1023 basegfx::B2DVector aSum(0.0, 0.0);
1025 if(bEntering)
1027 aSum += aEntering.normalize();
1030 if(bLeaving)
1032 aSum += aLeaving.normalize();
1035 if(!aSum.equalZero())
1037 const double fAngle(atan2(aSum.getY(), aSum.getX()));
1039 // apply rotation
1040 aCombinedTransform.rotate(fAngle);
1044 else
1046 // apply rotation
1047 aCombinedTransform.rotate(pPrepared->getAngle());
1050 // get and apply target position
1051 const basegfx::B2DPoint aPoint(aSubPolygonPath.getB2DPoint(b % nSubPolygonPointCount));
1053 aCombinedTransform.translate(aPoint.getX(), aPoint.getY());
1055 // prepare marker
1056 drawinglayer::primitive2d::Primitive2DReference xMarker(
1057 new drawinglayer::primitive2d::TransformPrimitive2D(
1058 aCombinedTransform,
1059 aPreparedMarkerPrimitives));
1061 if(!aPreparedMarkerClipRange.isEmpty())
1063 // marker needs to be clipped, it's bigger as the mapping
1064 basegfx::B2DPolyPolygon aClipPolygon(basegfx::utils::createPolygonFromRect(aPreparedMarkerClipRange));
1066 aClipPolygon.transform(aCombinedTransform);
1067 xMarker = new drawinglayer::primitive2d::MaskPrimitive2D(
1068 aClipPolygon,
1069 drawinglayer::primitive2d::Primitive2DContainer { xMarker });
1072 // add marker
1073 rTarget.push_back(xMarker);
1081 void SvgStyleAttributes::add_path(
1082 const basegfx::B2DPolyPolygon& rPath,
1083 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
1084 const basegfx::utils::PointIndexSet* pHelpPointIndices) const
1086 if(!rPath.count())
1088 // no geometry at all
1089 return;
1092 const basegfx::B2DRange aGeoRange(rPath.getB2DRange());
1094 if(aGeoRange.isEmpty())
1096 // no geometry range
1097 return;
1100 const double fOpacity(getOpacity().solve(mrOwner));
1102 if(basegfx::fTools::equalZero(fOpacity))
1104 // not visible
1105 return;
1108 // check if it's a line
1109 const bool bNoWidth(basegfx::fTools::equalZero(aGeoRange.getWidth()));
1110 const bool bNoHeight(basegfx::fTools::equalZero(aGeoRange.getHeight()));
1111 const bool bIsTwoPointLine(1 == rPath.count()
1112 && !rPath.areControlPointsUsed()
1113 && 2 == rPath.getB2DPolygon(0).count());
1114 const bool bIsLine(bIsTwoPointLine || bNoWidth || bNoHeight);
1116 if(!bIsLine)
1118 // create fill
1119 basegfx::B2DPolyPolygon aPath(rPath);
1120 const bool bNeedToCheckClipRule(SVGTokenPath == mrOwner.getType() || SVGTokenPolygon == mrOwner.getType());
1121 const bool bClipPathIsNonzero(bNeedToCheckClipRule && mbIsClipPathContent && FillRule_nonzero == maClipRule);
1122 const bool bFillRuleIsNonzero(bNeedToCheckClipRule && !mbIsClipPathContent && FillRule_nonzero == getFillRule());
1124 if(bClipPathIsNonzero || bFillRuleIsNonzero)
1126 if(getFill() || getSvgGradientNodeFill() || getSvgPatternNodeFill()) {
1127 // nonzero is wanted, solve geometrically (see description on basegfx)
1128 // basegfx::utils::createNonzeroConform() is expensive for huge paths
1129 // and is only needed if path will be filled later on
1130 aPath = basegfx::utils::createNonzeroConform(aPath);
1134 add_fill(aPath, rTarget, aGeoRange);
1137 // create stroke
1138 add_stroke(rPath, rTarget, aGeoRange);
1140 // Svg supports markers for path, polygon, polyline and line
1141 if(SVGTokenPath == mrOwner.getType() || // path
1142 SVGTokenPolygon == mrOwner.getType() || // polygon, polyline
1143 SVGTokenLine == mrOwner.getType()) // line
1145 // try to add markers
1146 add_markers(rPath, rTarget, pHelpPointIndices);
1150 void SvgStyleAttributes::add_postProcess(
1151 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
1152 const drawinglayer::primitive2d::Primitive2DContainer& rSource,
1153 const basegfx::B2DHomMatrix* pTransform) const
1155 if(!rSource.empty())
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 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 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 a 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(SVGTokenG == mrOwner.getType() && mrOwner.getClass())
1218 const OUString aOwnerClass(*mrOwner.getClass());
1220 if("Page" == aOwnerClass)
1222 const SvgNode* pParent(mrOwner.getParent());
1224 if(nullptr != pParent && SVGTokenG == 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);
1250 SvgStyleAttributes::SvgStyleAttributes(SvgNode& rOwner)
1251 : mrOwner(rOwner),
1252 mpCssStyleParent(nullptr),
1253 maFill(),
1254 maStroke(),
1255 maStopColor(basegfx::BColor(0.0, 0.0, 0.0), true),
1256 maStrokeWidth(),
1257 maStopOpacity(),
1258 mpSvgGradientNodeFill(nullptr),
1259 mpSvgGradientNodeStroke(nullptr),
1260 mpSvgPatternNodeFill(nullptr),
1261 mpSvgPatternNodeStroke(nullptr),
1262 maFillOpacity(),
1263 maStrokeDasharray(),
1264 maStrokeDashOffset(),
1265 maStrokeLinecap(StrokeLinecap_notset),
1266 maStrokeLinejoin(StrokeLinejoin_notset),
1267 maStrokeMiterLimit(),
1268 maStrokeOpacity(),
1269 maFontFamily(),
1270 maFontSize(),
1271 maFontSizeNumber(),
1272 maFontStretch(FontStretch_notset),
1273 maFontStyle(FontStyle_notset),
1274 maFontWeight(FontWeight_notset),
1275 maTextAlign(TextAlign_notset),
1276 maTextDecoration(TextDecoration_notset),
1277 maTextAnchor(TextAnchor_notset),
1278 maColor(),
1279 maOpacity(),
1280 maVisibility(Visibility_notset),
1281 maTitle(),
1282 maDesc(),
1283 maClipPathXLink(),
1284 mpClipPathXLink(nullptr),
1285 maMaskXLink(),
1286 mpMaskXLink(nullptr),
1287 maMarkerStartXLink(),
1288 mpMarkerStartXLink(nullptr),
1289 maMarkerMidXLink(),
1290 mpMarkerMidXLink(nullptr),
1291 maMarkerEndXLink(),
1292 mpMarkerEndXLink(nullptr),
1293 maFillRule(FillRule_notset),
1294 maClipRule(FillRule_nonzero),
1295 maBaselineShift(BaselineShift_Baseline),
1296 maBaselineShiftNumber(0),
1297 maResolvingParent(30, 0),
1298 mbIsClipPathContent(SVGTokenClipPathNode == mrOwner.getType()),
1299 mbStrokeDasharraySet(false)
1301 const SvgStyleAttributes* pParentStyle = getParentStyle();
1302 if(!mbIsClipPathContent)
1304 if(pParentStyle)
1306 mbIsClipPathContent = pParentStyle->mbIsClipPathContent;
1311 SvgStyleAttributes::~SvgStyleAttributes()
1315 void SvgStyleAttributes::parseStyleAttribute(
1316 SVGToken aSVGToken,
1317 const OUString& aContent,
1318 bool bCaseIndependent)
1320 switch(aSVGToken)
1322 case SVGTokenFill:
1324 SvgPaint aSvgPaint;
1325 OUString aURL;
1326 SvgNumber aOpacity;
1328 if(readSvgPaint(aContent, aSvgPaint, aURL, bCaseIndependent, aOpacity))
1330 setFill(aSvgPaint);
1331 if(aOpacity.isSet())
1333 setOpacity(SvgNumber(o3tl::clamp(aOpacity.getNumber(), 0.0, 1.0)));
1336 else if(!aURL.isEmpty())
1338 const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(aURL);
1340 if(pNode)
1342 if(SVGTokenLinearGradient == pNode->getType() || SVGTokenRadialGradient == pNode->getType())
1344 mpSvgGradientNodeFill = static_cast< const SvgGradientNode* >(pNode);
1346 else if(SVGTokenPattern == pNode->getType())
1348 mpSvgPatternNodeFill = static_cast< const SvgPatternNode* >(pNode);
1352 break;
1354 case SVGTokenFillOpacity:
1356 SvgNumber aNum;
1358 if(readSingleNumber(aContent, aNum))
1360 maFillOpacity = SvgNumber(o3tl::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet());
1362 break;
1364 case SVGTokenFillRule:
1366 if(!aContent.isEmpty())
1368 if(aContent.match(commonStrings::aStrNonzero))
1370 maFillRule = FillRule_nonzero;
1372 else if(aContent.match(commonStrings::aStrEvenOdd))
1374 maFillRule = FillRule_evenodd;
1377 break;
1379 case SVGTokenStroke:
1381 SvgPaint aSvgPaint;
1382 OUString aURL;
1383 SvgNumber aOpacity;
1385 if(readSvgPaint(aContent, aSvgPaint, aURL, bCaseIndependent, aOpacity))
1387 maStroke = aSvgPaint;
1388 if(aOpacity.isSet())
1390 setOpacity(SvgNumber(o3tl::clamp(aOpacity.getNumber(), 0.0, 1.0)));
1393 else if(!aURL.isEmpty())
1395 const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(aURL);
1397 if(pNode)
1399 if(SVGTokenLinearGradient == pNode->getType() || SVGTokenRadialGradient == pNode->getType())
1401 mpSvgGradientNodeStroke = static_cast< const SvgGradientNode* >(pNode);
1403 else if(SVGTokenPattern == pNode->getType())
1405 mpSvgPatternNodeStroke = static_cast< const SvgPatternNode* >(pNode);
1409 break;
1411 case SVGTokenStrokeDasharray:
1413 if(!aContent.isEmpty())
1415 SvgNumberVector aVector;
1417 if(aContent.startsWith("none"))
1419 // #121221# The special value 'none' needs to be handled
1420 // in the sense that *when* it is set, the parent shall not
1421 // be used. Before this was only dependent on the array being
1422 // empty
1423 mbStrokeDasharraySet = true;
1425 else if(readSvgNumberVector(aContent, aVector))
1427 maStrokeDasharray = aVector;
1430 break;
1432 case SVGTokenStrokeDashoffset:
1434 SvgNumber aNum;
1436 if(readSingleNumber(aContent, aNum))
1438 if(aNum.isPositive())
1440 maStrokeDashOffset = aNum;
1443 break;
1445 case SVGTokenStrokeLinecap:
1447 if(!aContent.isEmpty())
1449 if(aContent.startsWith("butt"))
1451 setStrokeLinecap(StrokeLinecap_butt);
1453 else if(aContent.startsWith("round"))
1455 setStrokeLinecap(StrokeLinecap_round);
1457 else if(aContent.startsWith("square"))
1459 setStrokeLinecap(StrokeLinecap_square);
1462 break;
1464 case SVGTokenStrokeLinejoin:
1466 if(!aContent.isEmpty())
1468 if(aContent.startsWith("miter"))
1470 setStrokeLinejoin(StrokeLinejoin_miter);
1472 else if(aContent.startsWith("round"))
1474 setStrokeLinejoin(StrokeLinejoin_round);
1476 else if(aContent.startsWith("bevel"))
1478 setStrokeLinejoin(StrokeLinejoin_bevel);
1481 break;
1483 case SVGTokenStrokeMiterlimit:
1485 SvgNumber aNum;
1487 if(readSingleNumber(aContent, aNum))
1489 if(basegfx::fTools::moreOrEqual(aNum.getNumber(), 1.0))
1490 { //readSingleNumber sets Unit_px as default, if unit is missing. Correct it here.
1491 maStrokeMiterLimit = SvgNumber(aNum.getNumber(), Unit_none);
1494 break;
1496 case SVGTokenStrokeOpacity:
1499 SvgNumber aNum;
1501 if(readSingleNumber(aContent, aNum))
1503 maStrokeOpacity = SvgNumber(o3tl::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet());
1505 break;
1507 case SVGTokenStrokeWidth:
1509 SvgNumber aNum;
1511 if(readSingleNumber(aContent, aNum))
1513 if(aNum.isPositive())
1515 maStrokeWidth = aNum;
1518 break;
1520 case SVGTokenStopColor:
1522 SvgPaint aSvgPaint;
1523 OUString aURL;
1524 SvgNumber aOpacity;
1526 if(readSvgPaint(aContent, aSvgPaint, aURL, bCaseIndependent, aOpacity))
1528 maStopColor = aSvgPaint;
1529 if(aOpacity.isSet())
1531 setOpacity(SvgNumber(o3tl::clamp(aOpacity.getNumber(), 0.0, 1.0)));
1534 break;
1536 case SVGTokenStopOpacity:
1538 SvgNumber aNum;
1540 if(readSingleNumber(aContent, aNum))
1542 if(aNum.isPositive())
1544 maStopOpacity = aNum;
1547 break;
1549 case SVGTokenFont:
1551 break;
1553 case SVGTokenFontFamily:
1555 SvgStringVector aSvgStringVector;
1557 if(readSvgStringVector(aContent, aSvgStringVector))
1559 maFontFamily = aSvgStringVector;
1561 break;
1563 case SVGTokenFontSize:
1565 if(!aContent.isEmpty())
1567 if(aContent.startsWith("xx-small"))
1569 setFontSize(FontSize_xx_small);
1571 else if(aContent.startsWith("x-small"))
1573 setFontSize(FontSize_x_small);
1575 else if(aContent.startsWith("small"))
1577 setFontSize(FontSize_small);
1579 else if(aContent.startsWith("smaller"))
1581 setFontSize(FontSize_smaller);
1583 else if(aContent.startsWith("medium"))
1585 setFontSize(FontSize_medium);
1587 else if(aContent.startsWith("larger"))
1589 setFontSize(FontSize_larger);
1591 else if(aContent.startsWith("large"))
1593 setFontSize(FontSize_large);
1595 else if(aContent.startsWith("x-large"))
1597 setFontSize(FontSize_x_large);
1599 else if(aContent.startsWith("xx-large"))
1601 setFontSize(FontSize_xx_large);
1603 else if(aContent.startsWith("initial"))
1605 setFontSize(FontSize_initial);
1607 else
1609 SvgNumber aNum;
1611 if(readSingleNumber(aContent, aNum))
1613 maFontSizeNumber = aNum;
1617 break;
1619 case SVGTokenFontSizeAdjust:
1621 break;
1623 case SVGTokenFontStretch:
1625 if(!aContent.isEmpty())
1627 if(aContent.startsWith("normal"))
1629 setFontStretch(FontStretch_normal);
1631 else if(aContent.startsWith("wider"))
1633 setFontStretch(FontStretch_wider);
1635 else if(aContent.startsWith("narrower"))
1637 setFontStretch(FontStretch_narrower);
1639 else if(aContent.startsWith("ultra-condensed"))
1641 setFontStretch(FontStretch_ultra_condensed);
1643 else if(aContent.startsWith("extra-condensed"))
1645 setFontStretch(FontStretch_extra_condensed);
1647 else if(aContent.startsWith("condensed"))
1649 setFontStretch(FontStretch_condensed);
1651 else if(aContent.startsWith("semi-condensed"))
1653 setFontStretch(FontStretch_semi_condensed);
1655 else if(aContent.startsWith("semi-expanded"))
1657 setFontStretch(FontStretch_semi_expanded);
1659 else if(aContent.startsWith("expanded"))
1661 setFontStretch(FontStretch_expanded);
1663 else if(aContent.startsWith("extra-expanded"))
1665 setFontStretch(FontStretch_extra_expanded);
1667 else if(aContent.startsWith("ultra-expanded"))
1669 setFontStretch(FontStretch_ultra_expanded);
1672 break;
1674 case SVGTokenFontStyle:
1676 if(!aContent.isEmpty())
1678 if(aContent.startsWith("normal"))
1680 setFontStyle(FontStyle_normal);
1682 else if(aContent.startsWith("italic"))
1684 setFontStyle(FontStyle_italic);
1686 else if(aContent.startsWith("oblique"))
1688 setFontStyle(FontStyle_oblique);
1691 break;
1693 case SVGTokenFontVariant:
1695 break;
1697 case SVGTokenFontWeight:
1699 if(!aContent.isEmpty())
1701 if(aContent.startsWith("100"))
1703 setFontWeight(FontWeight_100);
1705 else if(aContent.startsWith("200"))
1707 setFontWeight(FontWeight_200);
1709 else if(aContent.startsWith("300"))
1711 setFontWeight(FontWeight_300);
1713 else if(aContent.startsWith("400") || aContent.startsWith("normal"))
1715 setFontWeight(FontWeight_400);
1717 else if(aContent.startsWith("500"))
1719 setFontWeight(FontWeight_500);
1721 else if(aContent.startsWith("600"))
1723 setFontWeight(FontWeight_600);
1725 else if(aContent.startsWith("700") || aContent.startsWith("bold"))
1727 setFontWeight(FontWeight_700);
1729 else if(aContent.startsWith("800"))
1731 setFontWeight(FontWeight_800);
1733 else if(aContent.startsWith("900"))
1735 setFontWeight(FontWeight_900);
1737 else if(aContent.startsWith("bolder"))
1739 setFontWeight(FontWeight_bolder);
1741 else if(aContent.startsWith("lighter"))
1743 setFontWeight(FontWeight_lighter);
1746 break;
1748 case SVGTokenDirection:
1750 break;
1752 case SVGTokenLetterSpacing:
1754 break;
1756 case SVGTokenTextDecoration:
1758 if(!aContent.isEmpty())
1760 if(aContent.startsWith("none"))
1762 setTextDecoration(TextDecoration_none);
1764 else if(aContent.startsWith("underline"))
1766 setTextDecoration(TextDecoration_underline);
1768 else if(aContent.startsWith("overline"))
1770 setTextDecoration(TextDecoration_overline);
1772 else if(aContent.startsWith("line-through"))
1774 setTextDecoration(TextDecoration_line_through);
1776 else if(aContent.startsWith("blink"))
1778 setTextDecoration(TextDecoration_blink);
1781 break;
1783 case SVGTokenUnicodeBidi:
1785 break;
1787 case SVGTokenWordSpacing:
1789 break;
1791 case SVGTokenTextAnchor:
1793 if(!aContent.isEmpty())
1795 if(aContent.startsWith("start"))
1797 setTextAnchor(TextAnchor_start);
1799 else if(aContent.startsWith("middle"))
1801 setTextAnchor(TextAnchor_middle);
1803 else if(aContent.startsWith("end"))
1805 setTextAnchor(TextAnchor_end);
1808 break;
1810 case SVGTokenTextAlign:
1812 if(!aContent.isEmpty())
1814 if(aContent.startsWith("left"))
1816 setTextAlign(TextAlign_left);
1818 else if(aContent.startsWith("right"))
1820 setTextAlign(TextAlign_right);
1822 else if(aContent.startsWith("center"))
1824 setTextAlign(TextAlign_center);
1826 else if(aContent.startsWith("justify"))
1828 setTextAlign(TextAlign_justify);
1831 break;
1833 case SVGTokenColor:
1835 SvgPaint aSvgPaint;
1836 OUString aURL;
1837 SvgNumber aOpacity;
1839 if(readSvgPaint(aContent, aSvgPaint, aURL, bCaseIndependent, aOpacity))
1841 maColor = aSvgPaint;
1842 if(aOpacity.isSet())
1844 setOpacity(SvgNumber(o3tl::clamp(aOpacity.getNumber(), 0.0, 1.0)));
1847 break;
1849 case SVGTokenOpacity:
1851 SvgNumber aNum;
1853 if(readSingleNumber(aContent, aNum))
1855 setOpacity(SvgNumber(o3tl::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet()));
1857 break;
1859 case SVGTokenVisibility:
1861 if(!aContent.isEmpty())
1863 if(aContent.startsWith("visible"))
1865 setVisibility(Visibility_visible);
1867 else if(aContent.startsWith("hidden"))
1869 setVisibility(Visibility_hidden);
1871 else if(aContent.startsWith("collapse"))
1873 setVisibility(Visibility_collapse);
1875 else if(aContent.startsWith("inherit"))
1877 setVisibility(Visibility_inherit);
1880 break;
1882 case SVGTokenTitle:
1884 maTitle = aContent;
1885 break;
1887 case SVGTokenDesc:
1889 maDesc = aContent;
1890 break;
1892 case SVGTokenClipPathProperty:
1894 readLocalUrl(aContent, maClipPathXLink);
1895 break;
1897 case SVGTokenMask:
1899 readLocalUrl(aContent, maMaskXLink);
1900 break;
1902 case SVGTokenClipRule:
1904 if(!aContent.isEmpty())
1906 if(aContent.match(commonStrings::aStrNonzero))
1908 maClipRule = FillRule_nonzero;
1910 else if(aContent.match(commonStrings::aStrEvenOdd))
1912 maClipRule = FillRule_evenodd;
1915 break;
1917 case SVGTokenMarker:
1919 if(bCaseIndependent)
1921 readLocalUrl(aContent, maMarkerEndXLink);
1922 maMarkerStartXLink = maMarkerMidXLink = maMarkerEndXLink;
1924 break;
1926 case SVGTokenMarkerStart:
1928 readLocalUrl(aContent, maMarkerStartXLink);
1929 break;
1931 case SVGTokenMarkerMid:
1933 readLocalUrl(aContent, maMarkerMidXLink);
1934 break;
1936 case SVGTokenMarkerEnd:
1938 readLocalUrl(aContent, maMarkerEndXLink);
1939 break;
1941 case SVGTokenDisplay:
1943 // There may be display:none statements inside of style defines, e.g. the following line:
1944 // style="display:none"
1945 // taken from a svg example; this needs to be parsed and set at the owning node. Do not call
1946 // mrOwner.parseAttribute(...) here, this would lead to a recursion
1947 if(!aContent.isEmpty())
1949 mrOwner.setDisplay(getDisplayFromContent(aContent));
1951 break;
1953 case SVGTokenBaselineShift:
1955 if(!aContent.isEmpty())
1957 SvgNumber aNum;
1959 if(aContent.startsWith("sub"))
1961 setBaselineShift(BaselineShift_Sub);
1963 else if(aContent.startsWith("super"))
1965 setBaselineShift(BaselineShift_Super);
1967 else if(readSingleNumber(aContent, aNum))
1969 maBaselineShiftNumber = aNum;
1971 if(Unit_percent == aNum.getUnit())
1973 setBaselineShift(BaselineShift_Percentage);
1975 else
1977 setBaselineShift(BaselineShift_Length);
1980 else
1982 // no BaselineShift or inherit (which is automatically)
1983 setBaselineShift(BaselineShift_Baseline);
1986 break;
1988 default:
1990 break;
1995 // #i125258# ask if fill is a direct hard attribute (no hierarchy)
1996 bool SvgStyleAttributes::isFillSet() const
1998 if(mbIsClipPathContent)
2000 return false;
2002 else if(maFill.isSet())
2004 return true;
2007 return false;
2010 const basegfx::BColor* SvgStyleAttributes::getCurrentColor() const
2012 static basegfx::BColor aBlack(0.0, 0.0, 0.0);
2013 const basegfx::BColor *aColor = getColor();
2014 if( aColor )
2015 return aColor;
2016 else
2017 return &aBlack;
2020 const basegfx::BColor* SvgStyleAttributes::getFill() const
2022 if(maFill.isSet())
2024 if(maFill.isCurrent())
2026 return getCurrentColor();
2028 else if(maFill.isOn())
2030 return &maFill.getBColor();
2033 else if (!mpSvgGradientNodeFill && !mpSvgPatternNodeFill)
2035 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2037 if (pSvgStyleAttributes && maResolvingParent[0] < nStyleDepthLimit)
2039 ++maResolvingParent[0];
2040 const basegfx::BColor* pFill = pSvgStyleAttributes->getFill();
2041 --maResolvingParent[0];
2043 if(mbIsClipPathContent)
2045 if (pFill)
2047 return pFill;
2049 else
2051 static basegfx::BColor aBlack(0.0, 0.0, 0.0);
2052 return &aBlack;
2055 else
2057 return pFill;
2062 return nullptr;
2065 const basegfx::BColor* SvgStyleAttributes::getStroke() const
2067 if(maStroke.isSet())
2069 if(maStroke.isCurrent())
2071 return getCurrentColor();
2073 else if(maStroke.isOn())
2075 return &maStroke.getBColor();
2078 else if (!mpSvgGradientNodeStroke && !mpSvgPatternNodeStroke)
2080 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2082 if (pSvgStyleAttributes && maResolvingParent[1] < nStyleDepthLimit)
2084 ++maResolvingParent[1];
2085 auto ret = pSvgStyleAttributes->getStroke();
2086 --maResolvingParent[1];
2087 return ret;
2091 return nullptr;
2094 const basegfx::BColor& SvgStyleAttributes::getStopColor() const
2096 if(maStopColor.isCurrent())
2098 return *getCurrentColor();
2100 else
2102 return maStopColor.getBColor();
2106 const SvgGradientNode* SvgStyleAttributes::getSvgGradientNodeFill() const
2108 if(mpSvgGradientNodeFill)
2110 return mpSvgGradientNodeFill;
2112 else if (!maFill.isSet() && !mpSvgPatternNodeFill)
2114 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2116 if (pSvgStyleAttributes && maResolvingParent[2] < nStyleDepthLimit)
2118 ++maResolvingParent[2];
2119 auto ret = pSvgStyleAttributes->getSvgGradientNodeFill();
2120 --maResolvingParent[2];
2121 return ret;
2125 return nullptr;
2128 const SvgGradientNode* SvgStyleAttributes::getSvgGradientNodeStroke() const
2130 if(mpSvgGradientNodeStroke)
2132 return mpSvgGradientNodeStroke;
2134 else if (!maStroke.isSet() && !mpSvgPatternNodeStroke)
2136 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2138 if (pSvgStyleAttributes && maResolvingParent[3] < nStyleDepthLimit)
2140 ++maResolvingParent[3];
2141 auto ret = pSvgStyleAttributes->getSvgGradientNodeStroke();
2142 --maResolvingParent[3];
2143 return ret;
2147 return nullptr;
2150 const SvgPatternNode* SvgStyleAttributes::getSvgPatternNodeFill() const
2152 if(mpSvgPatternNodeFill)
2154 return mpSvgPatternNodeFill;
2156 else if (!maFill.isSet() && !mpSvgGradientNodeFill)
2158 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2160 if (pSvgStyleAttributes && maResolvingParent[4] < nStyleDepthLimit)
2162 ++maResolvingParent[4];
2163 auto ret = pSvgStyleAttributes->getSvgPatternNodeFill();
2164 --maResolvingParent[4];
2165 return ret;
2169 return nullptr;
2172 const SvgPatternNode* SvgStyleAttributes::getSvgPatternNodeStroke() const
2174 if(mpSvgPatternNodeStroke)
2176 return mpSvgPatternNodeStroke;
2178 else if (!maStroke.isSet() && !mpSvgGradientNodeStroke)
2180 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2182 if (pSvgStyleAttributes && maResolvingParent[5] < nStyleDepthLimit)
2184 ++maResolvingParent[5];
2185 auto ret = pSvgStyleAttributes->getSvgPatternNodeStroke();
2186 --maResolvingParent[5];
2187 return ret;
2191 return nullptr;
2194 SvgNumber SvgStyleAttributes::getStrokeWidth() const
2196 if(maStrokeWidth.isSet())
2198 return maStrokeWidth;
2201 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2203 if (pSvgStyleAttributes && maResolvingParent[6] < nStyleDepthLimit)
2205 ++maResolvingParent[6];
2206 auto ret = pSvgStyleAttributes->getStrokeWidth();
2207 --maResolvingParent[6];
2208 return ret;
2211 if(mbIsClipPathContent)
2213 return SvgNumber(0.0);
2216 // default is 1
2217 return SvgNumber(1.0);
2220 SvgNumber SvgStyleAttributes::getStopOpacity() const
2222 if(maStopOpacity.isSet())
2224 return maStopOpacity;
2227 // default is 1
2228 return SvgNumber(1.0);
2231 SvgNumber SvgStyleAttributes::getFillOpacity() const
2233 if(maFillOpacity.isSet())
2235 return maFillOpacity;
2238 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2240 if (pSvgStyleAttributes && maResolvingParent[7] < nStyleDepthLimit)
2242 ++maResolvingParent[7];
2243 auto ret = pSvgStyleAttributes->getFillOpacity();
2244 --maResolvingParent[7];
2245 return ret;
2248 // default is 1
2249 return SvgNumber(1.0);
2252 SvgNumber SvgStyleAttributes::getOpacity() const
2254 if(maOpacity.isSet())
2256 return maOpacity;
2259 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2261 if (pSvgStyleAttributes && maResolvingParent[8] < nStyleDepthLimit)
2263 ++maResolvingParent[8];
2264 auto ret = pSvgStyleAttributes->getOpacity();
2265 --maResolvingParent[8];
2266 return ret;
2269 // default is 1
2270 return SvgNumber(1.0);
2273 Visibility SvgStyleAttributes::getVisibility() const
2275 if(Visibility_notset == maVisibility || Visibility_inherit == maVisibility)
2277 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2279 if (pSvgStyleAttributes && maResolvingParent[9] < nStyleDepthLimit)
2281 ++maResolvingParent[9];
2282 auto ret = pSvgStyleAttributes->getVisibility();
2283 --maResolvingParent[9];
2284 return ret;
2286 //default is Visible
2287 return Visibility_visible;
2290 // Visibility correction/exception for self-exported SVGs:
2291 // When Impress exports single or multi-page SVGs, it puts the
2292 // single slides into <g visibility="hidden">. Not sure why
2293 // this happens, but this leads (correctly) to empty imported
2294 // Graphics.
2295 // Thus, if Visibility_hidden is active and owner is a SVGTokenG
2296 // and it's parent is also a SVGTokenG and it has a Class 'SlideGroup'
2297 // set, check if we are an Impress export.
2298 // We are an Impress export if an SVG-Node titled 'ooo:meta_slides'
2299 // exists.
2300 // All together gives:
2301 if(Visibility_hidden == maVisibility
2302 && SVGTokenG == mrOwner.getType()
2303 && nullptr != mrOwner.getDocument().findSvgNodeById("ooo:meta_slides"))
2305 const SvgNode* pParent(mrOwner.getParent());
2307 if(nullptr != pParent && SVGTokenG == pParent->getType() && pParent->getClass())
2309 const OUString aClass(*pParent->getClass());
2311 if("SlideGroup" == aClass)
2313 // if we detect this exception,
2314 // override Visibility_hidden -> Visibility_visible
2315 return Visibility_visible;
2320 return maVisibility;
2323 FillRule SvgStyleAttributes::getFillRule() const
2325 if(FillRule_notset != maFillRule)
2327 return maFillRule;
2330 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2332 if (pSvgStyleAttributes && maResolvingParent[10] < nStyleDepthLimit)
2334 ++maResolvingParent[10];
2335 auto ret = pSvgStyleAttributes->getFillRule();
2336 --maResolvingParent[10];
2337 return ret;
2340 // default is NonZero
2341 return FillRule_nonzero;
2344 const SvgNumberVector& SvgStyleAttributes::getStrokeDasharray() const
2346 if(!maStrokeDasharray.empty())
2348 return maStrokeDasharray;
2350 else if(mbStrokeDasharraySet)
2352 // #121221# is set to empty *by purpose*, do not visit parent styles
2353 return maStrokeDasharray;
2356 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2358 if (pSvgStyleAttributes && maResolvingParent[11] < nStyleDepthLimit)
2360 ++maResolvingParent[11];
2361 const SvgNumberVector& ret = pSvgStyleAttributes->getStrokeDasharray();
2362 --maResolvingParent[11];
2363 return ret;
2366 // default empty
2367 return maStrokeDasharray;
2370 SvgNumber SvgStyleAttributes::getStrokeDashOffset() const
2372 if(maStrokeDashOffset.isSet())
2374 return maStrokeDashOffset;
2377 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2379 if (pSvgStyleAttributes && maResolvingParent[12] < nStyleDepthLimit)
2381 ++maResolvingParent[12];
2382 auto ret = pSvgStyleAttributes->getStrokeDashOffset();
2383 --maResolvingParent[12];
2384 return ret;
2387 // default is 0
2388 return SvgNumber(0.0);
2391 StrokeLinecap SvgStyleAttributes::getStrokeLinecap() const
2393 if(maStrokeLinecap != StrokeLinecap_notset)
2395 return maStrokeLinecap;
2398 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2400 if (pSvgStyleAttributes && maResolvingParent[13] < nStyleDepthLimit)
2402 ++maResolvingParent[13];
2403 auto ret = pSvgStyleAttributes->getStrokeLinecap();
2404 --maResolvingParent[13];
2405 return ret;
2408 // default is StrokeLinecap_butt
2409 return StrokeLinecap_butt;
2412 StrokeLinejoin SvgStyleAttributes::getStrokeLinejoin() const
2414 if(maStrokeLinejoin != StrokeLinejoin_notset)
2416 return maStrokeLinejoin;
2419 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2421 if (pSvgStyleAttributes && maResolvingParent[14] < nStyleDepthLimit)
2423 ++maResolvingParent[14];
2424 auto ret = pSvgStyleAttributes->getStrokeLinejoin();
2425 --maResolvingParent[14];
2426 return ret;
2429 // default is StrokeLinejoin_butt
2430 return StrokeLinejoin_miter;
2433 SvgNumber SvgStyleAttributes::getStrokeMiterLimit() const
2435 if(maStrokeMiterLimit.isSet())
2437 return maStrokeMiterLimit;
2440 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2442 if (pSvgStyleAttributes && maResolvingParent[15] < nStyleDepthLimit)
2444 ++maResolvingParent[15];
2445 auto ret = pSvgStyleAttributes->getStrokeMiterLimit();
2446 --maResolvingParent[15];
2447 return ret;
2450 // default is 4
2451 return SvgNumber(4.0, Unit_none);
2454 SvgNumber SvgStyleAttributes::getStrokeOpacity() const
2456 if(maStrokeOpacity.isSet())
2458 return maStrokeOpacity;
2461 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2463 if (pSvgStyleAttributes && maResolvingParent[16] < nStyleDepthLimit)
2465 ++maResolvingParent[16];
2466 auto ret = pSvgStyleAttributes->getStrokeOpacity();
2467 --maResolvingParent[16];
2468 return ret;
2471 // default is 1
2472 return SvgNumber(1.0);
2475 const SvgStringVector& SvgStyleAttributes::getFontFamily() const
2477 if(!maFontFamily.empty() && !maFontFamily[0].startsWith("inherit"))
2479 return maFontFamily;
2482 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2484 if (pSvgStyleAttributes && maResolvingParent[17] < nStyleDepthLimit)
2486 ++maResolvingParent[17];
2487 const SvgStringVector& ret = pSvgStyleAttributes->getFontFamily();
2488 --maResolvingParent[17];
2489 return ret;
2492 // default is empty
2493 return maFontFamily;
2496 SvgNumber SvgStyleAttributes::getFontSizeNumber() const
2498 // default size is 'medium' or 16px, which is equal to the default PPI used in svgio ( 96.0 )
2499 // converted to pixels
2500 const double aDefaultSize = F_SVG_PIXEL_PER_INCH / 6.0;
2502 if(maFontSizeNumber.isSet())
2504 if(!maFontSizeNumber.isPositive())
2505 return aDefaultSize;
2507 // #122524# Handle Unit_percent realtive to parent FontSize (see SVG1.1
2508 // spec 10.10 Font selection properties \91font-size\92, lastline (click 'normative
2509 // definition of the property')
2510 if(Unit_percent == maFontSizeNumber.getUnit())
2512 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2514 if(pSvgStyleAttributes)
2516 const SvgNumber aParentNumber = pSvgStyleAttributes->getFontSizeNumber();
2518 return SvgNumber(
2519 aParentNumber.getNumber() * maFontSizeNumber.getNumber() * 0.01,
2520 aParentNumber.getUnit(),
2521 true);
2523 // if there's no parent style, set the font size based on the default size
2524 // 100% = 16px
2525 return SvgNumber(
2526 maFontSizeNumber.getNumber() * aDefaultSize / 100.0, Unit_px, true);
2528 else if((Unit_em == maFontSizeNumber.getUnit()) || (Unit_ex == maFontSizeNumber.getUnit()))
2530 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2532 if(pSvgStyleAttributes)
2534 const SvgNumber aParentNumber = pSvgStyleAttributes->getFontSizeNumber();
2536 return SvgNumber(
2537 aParentNumber.getNumber() * maFontSizeNumber.getNumber(),
2538 aParentNumber.getUnit(),
2539 true);
2543 return maFontSizeNumber;
2546 //In CSS2, the suggested scaling factor between adjacent indexes is 1.2
2547 switch(maFontSize)
2549 case FontSize_notset:
2550 break;
2551 case FontSize_xx_small:
2553 return SvgNumber(aDefaultSize / 1.728);
2555 case FontSize_x_small:
2557 return SvgNumber(aDefaultSize / 1.44);
2559 case FontSize_small:
2561 return SvgNumber(aDefaultSize / 1.2);
2563 case FontSize_smaller:
2565 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2566 if(pSvgStyleAttributes)
2568 const SvgNumber aParentNumber = pSvgStyleAttributes->getFontSizeNumber();
2569 return SvgNumber(aParentNumber.getNumber() / 1.2, aParentNumber.getUnit());
2571 SAL_FALLTHROUGH;
2573 case FontSize_medium:
2574 case FontSize_initial:
2576 return SvgNumber(aDefaultSize);
2578 case FontSize_large:
2580 return SvgNumber(aDefaultSize * 1.2);
2582 case FontSize_larger:
2584 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2585 if(pSvgStyleAttributes)
2587 const SvgNumber aParentNumber = pSvgStyleAttributes->getFontSizeNumber();
2588 return SvgNumber(aParentNumber.getNumber() * 1.2, aParentNumber.getUnit());
2590 SAL_FALLTHROUGH;
2592 case FontSize_x_large:
2594 return SvgNumber(aDefaultSize * 1.44);
2596 case FontSize_xx_large:
2598 return SvgNumber(aDefaultSize * 1.728);
2602 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2604 if(pSvgStyleAttributes)
2606 return pSvgStyleAttributes->getFontSizeNumber();
2609 return SvgNumber(aDefaultSize);
2612 FontStretch SvgStyleAttributes::getFontStretch() const
2614 if(maFontStretch != FontStretch_notset)
2616 if(FontStretch_wider != maFontStretch && FontStretch_narrower != maFontStretch)
2618 return maFontStretch;
2622 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2624 if (pSvgStyleAttributes && maResolvingParent[18] < nStyleDepthLimit)
2626 ++maResolvingParent[18];
2627 FontStretch aInherited = pSvgStyleAttributes->getFontStretch();
2628 --maResolvingParent[18];
2630 if(FontStretch_wider == maFontStretch)
2632 aInherited = getWider(aInherited);
2634 else if(FontStretch_narrower == maFontStretch)
2636 aInherited = getNarrower(aInherited);
2639 return aInherited;
2642 // default is FontStretch_normal
2643 return FontStretch_normal;
2646 FontStyle SvgStyleAttributes::getFontStyle() const
2648 if(maFontStyle != FontStyle_notset)
2650 return maFontStyle;
2653 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2655 if (pSvgStyleAttributes && maResolvingParent[19] < nStyleDepthLimit)
2657 ++maResolvingParent[19];
2658 auto ret = pSvgStyleAttributes->getFontStyle();
2659 --maResolvingParent[19];
2660 return ret;
2663 // default is FontStyle_normal
2664 return FontStyle_normal;
2667 FontWeight SvgStyleAttributes::getFontWeight() const
2669 if(maFontWeight != FontWeight_notset)
2671 if(FontWeight_bolder != maFontWeight && FontWeight_lighter != maFontWeight)
2673 return maFontWeight;
2677 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2679 if (pSvgStyleAttributes && maResolvingParent[20] < nStyleDepthLimit)
2681 ++maResolvingParent[20];
2682 FontWeight aInherited = pSvgStyleAttributes->getFontWeight();
2683 --maResolvingParent[20];
2685 if(FontWeight_bolder == maFontWeight)
2687 aInherited = getBolder(aInherited);
2689 else if(FontWeight_lighter == maFontWeight)
2691 aInherited = getLighter(aInherited);
2694 return aInherited;
2697 // default is FontWeight_400 (FontWeight_normal)
2698 return FontWeight_400;
2701 TextAlign SvgStyleAttributes::getTextAlign() const
2703 if(maTextAlign != TextAlign_notset)
2705 return maTextAlign;
2708 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2710 if (pSvgStyleAttributes && maResolvingParent[21] < nStyleDepthLimit)
2712 ++maResolvingParent[21];
2713 auto ret = pSvgStyleAttributes->getTextAlign();
2714 --maResolvingParent[21];
2715 return ret;
2718 // default is TextAlign_left
2719 return TextAlign_left;
2722 const SvgStyleAttributes* SvgStyleAttributes::getTextDecorationDefiningSvgStyleAttributes() const
2724 if(maTextDecoration != TextDecoration_notset)
2726 return this;
2729 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2731 if (pSvgStyleAttributes && maResolvingParent[22] < nStyleDepthLimit)
2733 ++maResolvingParent[22];
2734 auto ret = pSvgStyleAttributes->getTextDecorationDefiningSvgStyleAttributes();
2735 --maResolvingParent[22];
2736 return ret;
2739 // default is 0
2740 return nullptr;
2743 TextDecoration SvgStyleAttributes::getTextDecoration() const
2745 const SvgStyleAttributes* pDefining = getTextDecorationDefiningSvgStyleAttributes();
2747 if(pDefining)
2749 return pDefining->maTextDecoration;
2751 else
2753 // default is TextDecoration_none
2754 return TextDecoration_none;
2758 TextAnchor SvgStyleAttributes::getTextAnchor() const
2760 if(maTextAnchor != TextAnchor_notset)
2762 return maTextAnchor;
2765 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2767 if (pSvgStyleAttributes && maResolvingParent[23] < nStyleDepthLimit)
2769 ++maResolvingParent[23];
2770 auto ret = pSvgStyleAttributes->getTextAnchor();
2771 --maResolvingParent[23];
2772 return ret;
2775 // default is TextAnchor_start
2776 return TextAnchor_start;
2779 const basegfx::BColor* SvgStyleAttributes::getColor() const
2781 if(maColor.isSet())
2783 if(maColor.isCurrent())
2785 OSL_ENSURE(false, "Svg error: current color uses current color (!)");
2786 return nullptr;
2788 else if(maColor.isOn())
2790 return &maColor.getBColor();
2793 else
2795 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2797 if (pSvgStyleAttributes && maResolvingParent[24] < nStyleDepthLimit)
2799 ++maResolvingParent[24];
2800 auto ret = pSvgStyleAttributes->getColor();
2801 --maResolvingParent[24];
2802 return ret;
2806 return nullptr;
2809 OUString const & SvgStyleAttributes::getClipPathXLink() const
2811 return maClipPathXLink;
2814 const SvgClipPathNode* SvgStyleAttributes::accessClipPathXLink() const
2816 if(!mpClipPathXLink)
2818 const OUString aClipPath(getClipPathXLink());
2820 if(!aClipPath.isEmpty())
2822 const_cast< SvgStyleAttributes* >(this)->mpClipPathXLink = dynamic_cast< const SvgClipPathNode* >(mrOwner.getDocument().findSvgNodeById(aClipPath));
2826 return mpClipPathXLink;
2829 OUString SvgStyleAttributes::getMaskXLink() const
2831 if(!maMaskXLink.isEmpty())
2833 return maMaskXLink;
2836 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2838 if (pSvgStyleAttributes && !pSvgStyleAttributes->maMaskXLink.isEmpty() && maResolvingParent[25] < nStyleDepthLimit)
2840 ++maResolvingParent[25];
2841 auto ret = pSvgStyleAttributes->getMaskXLink();
2842 --maResolvingParent[25];
2843 return ret;
2846 return OUString();
2849 const SvgMaskNode* SvgStyleAttributes::accessMaskXLink() const
2851 if(!mpMaskXLink)
2853 const OUString aMask(getMaskXLink());
2855 if(!aMask.isEmpty())
2857 const_cast< SvgStyleAttributes* >(this)->mpMaskXLink = dynamic_cast< const SvgMaskNode* >(mrOwner.getDocument().findSvgNodeById(aMask));
2861 return mpMaskXLink;
2864 OUString SvgStyleAttributes::getMarkerStartXLink() const
2866 if(!maMarkerStartXLink.isEmpty())
2868 return maMarkerStartXLink;
2871 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2873 if (pSvgStyleAttributes && maResolvingParent[26] < nStyleDepthLimit)
2875 ++maResolvingParent[26];
2876 auto ret = pSvgStyleAttributes->getMarkerStartXLink();
2877 --maResolvingParent[26];
2878 return ret;
2881 return OUString();
2884 const SvgMarkerNode* SvgStyleAttributes::accessMarkerStartXLink() const
2886 if(!mpMarkerStartXLink)
2888 const OUString aMarker(getMarkerStartXLink());
2890 if(!aMarker.isEmpty())
2892 const_cast< SvgStyleAttributes* >(this)->mpMarkerStartXLink = dynamic_cast< const SvgMarkerNode* >(mrOwner.getDocument().findSvgNodeById(getMarkerStartXLink()));
2896 return mpMarkerStartXLink;
2899 OUString SvgStyleAttributes::getMarkerMidXLink() const
2901 if(!maMarkerMidXLink.isEmpty())
2903 return maMarkerMidXLink;
2906 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2908 if (pSvgStyleAttributes && maResolvingParent[27] < nStyleDepthLimit)
2910 ++maResolvingParent[27];
2911 auto ret = pSvgStyleAttributes->getMarkerMidXLink();
2912 --maResolvingParent[27];
2913 return ret;
2916 return OUString();
2919 const SvgMarkerNode* SvgStyleAttributes::accessMarkerMidXLink() const
2921 if(!mpMarkerMidXLink)
2923 const OUString aMarker(getMarkerMidXLink());
2925 if(!aMarker.isEmpty())
2927 const_cast< SvgStyleAttributes* >(this)->mpMarkerMidXLink = dynamic_cast< const SvgMarkerNode* >(mrOwner.getDocument().findSvgNodeById(getMarkerMidXLink()));
2931 return mpMarkerMidXLink;
2934 OUString SvgStyleAttributes::getMarkerEndXLink() const
2936 if(!maMarkerEndXLink.isEmpty())
2938 return maMarkerEndXLink;
2941 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2943 if (pSvgStyleAttributes && maResolvingParent[28] < nStyleDepthLimit)
2945 ++maResolvingParent[28];
2946 auto ret = pSvgStyleAttributes->getMarkerEndXLink();
2947 --maResolvingParent[28];
2948 return ret;
2951 return OUString();
2954 const SvgMarkerNode* SvgStyleAttributes::accessMarkerEndXLink() const
2956 if(!mpMarkerEndXLink)
2958 const OUString aMarker(getMarkerEndXLink());
2960 if(!aMarker.isEmpty())
2962 const_cast< SvgStyleAttributes* >(this)->mpMarkerEndXLink = dynamic_cast< const SvgMarkerNode* >(mrOwner.getDocument().findSvgNodeById(getMarkerEndXLink()));
2966 return mpMarkerEndXLink;
2969 SvgNumber SvgStyleAttributes::getBaselineShiftNumber() const
2971 // #122524# Handle Unit_percent realtive to parent BaselineShift
2972 if(Unit_percent == maBaselineShiftNumber.getUnit())
2974 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2976 if (pSvgStyleAttributes && maResolvingParent[29] < nStyleDepthLimit)
2978 ++maResolvingParent[29];
2979 const SvgNumber aParentNumber = pSvgStyleAttributes->getBaselineShiftNumber();
2980 --maResolvingParent[29];
2982 return SvgNumber(
2983 aParentNumber.getNumber() * maBaselineShiftNumber.getNumber() * 0.01,
2984 aParentNumber.getUnit(),
2985 true);
2989 return maBaselineShiftNumber;
2991 } // end of namespace svgreader
2992 } // end of namespace svgio
2994 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */