Version 6.1.0.2, tag libreoffice-6.1.0.2
[LibreOffice.git] / svgio / source / svgreader / svgstyleattributes.cxx
blob2d96cfb9d02255bbe587f8ae92c958a1d7ce5b11
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 <svgstyleattributes.hxx>
21 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
22 #include <drawinglayer/primitive2d/polypolygonprimitive2d.hxx>
23 #include <svgnode.hxx>
24 #include <svgdocument.hxx>
25 #include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
26 #include <svggradientnode.hxx>
27 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
28 #include <basegfx/vector/b2enums.hxx>
29 #include <drawinglayer/processor2d/linegeometryextractor2d.hxx>
30 #include <drawinglayer/processor2d/textaspolygonextractor2d.hxx>
31 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
32 #include <svgclippathnode.hxx>
33 #include <svgmasknode.hxx>
34 #include <basegfx/polygon/b2dpolypolygontools.hxx>
35 #include <svgmarkernode.hxx>
36 #include <basegfx/curve/b2dcubicbezier.hxx>
37 #include <svgpatternnode.hxx>
38 #include <drawinglayer/primitive2d/patternfillprimitive2d.hxx>
39 #include <basegfx/polygon/b2dpolygontools.hxx>
40 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
41 #include <drawinglayer/primitive2d/pagehierarchyprimitive2d.hxx>
43 namespace svgio
45 namespace svgreader
47 basegfx::B2DLineJoin StrokeLinejoinToB2DLineJoin(StrokeLinejoin aStrokeLinejoin)
49 if(StrokeLinejoin_round == aStrokeLinejoin)
51 return basegfx::B2DLineJoin::Round;
53 else if(StrokeLinejoin_bevel == aStrokeLinejoin)
55 return basegfx::B2DLineJoin::Bevel;
58 return basegfx::B2DLineJoin::Miter;
61 css::drawing::LineCap StrokeLinecapToDrawingLineCap(StrokeLinecap aStrokeLinecap)
63 switch(aStrokeLinecap)
65 default: /* StrokeLinecap_notset, StrokeLinecap_butt */
67 return css::drawing::LineCap_BUTT;
69 case StrokeLinecap_round:
71 return css::drawing::LineCap_ROUND;
73 case StrokeLinecap_square:
75 return css::drawing::LineCap_SQUARE;
80 FontStretch getWider(FontStretch aSource)
82 switch(aSource)
84 case FontStretch_ultra_condensed: aSource = FontStretch_extra_condensed; break;
85 case FontStretch_extra_condensed: aSource = FontStretch_condensed; break;
86 case FontStretch_condensed: aSource = FontStretch_semi_condensed; break;
87 case FontStretch_semi_condensed: aSource = FontStretch_normal; break;
88 case FontStretch_normal: aSource = FontStretch_semi_expanded; break;
89 case FontStretch_semi_expanded: aSource = FontStretch_expanded; break;
90 case FontStretch_expanded: aSource = FontStretch_extra_expanded; break;
91 case FontStretch_extra_expanded: aSource = FontStretch_ultra_expanded; break;
92 default: break;
95 return aSource;
98 FontStretch getNarrower(FontStretch aSource)
100 switch(aSource)
102 case FontStretch_extra_condensed: aSource = FontStretch_ultra_condensed; break;
103 case FontStretch_condensed: aSource = FontStretch_extra_condensed; break;
104 case FontStretch_semi_condensed: aSource = FontStretch_condensed; break;
105 case FontStretch_normal: aSource = FontStretch_semi_condensed; break;
106 case FontStretch_semi_expanded: aSource = FontStretch_normal; break;
107 case FontStretch_expanded: aSource = FontStretch_semi_expanded; break;
108 case FontStretch_extra_expanded: aSource = FontStretch_expanded; break;
109 case FontStretch_ultra_expanded: aSource = FontStretch_extra_expanded; break;
110 default: break;
113 return aSource;
116 FontWeight getBolder(FontWeight aSource)
118 switch(aSource)
120 case FontWeight_100: aSource = FontWeight_200; break;
121 case FontWeight_200: aSource = FontWeight_300; break;
122 case FontWeight_300: aSource = FontWeight_400; break;
123 case FontWeight_400: aSource = FontWeight_500; break;
124 case FontWeight_500: aSource = FontWeight_600; break;
125 case FontWeight_600: aSource = FontWeight_700; break;
126 case FontWeight_700: aSource = FontWeight_800; break;
127 case FontWeight_800: aSource = FontWeight_900; break;
128 default: break;
131 return aSource;
134 FontWeight getLighter(FontWeight aSource)
136 switch(aSource)
138 case FontWeight_200: aSource = FontWeight_100; break;
139 case FontWeight_300: aSource = FontWeight_200; break;
140 case FontWeight_400: aSource = FontWeight_300; break;
141 case FontWeight_500: aSource = FontWeight_400; break;
142 case FontWeight_600: aSource = FontWeight_500; break;
143 case FontWeight_700: aSource = FontWeight_600; break;
144 case FontWeight_800: aSource = FontWeight_700; break;
145 case FontWeight_900: aSource = FontWeight_800; break;
146 default: break;
149 return aSource;
152 ::FontWeight getVclFontWeight(FontWeight aSource)
154 ::FontWeight nRetval(WEIGHT_NORMAL);
156 switch(aSource)
158 case FontWeight_100: nRetval = WEIGHT_ULTRALIGHT; break;
159 case FontWeight_200: nRetval = WEIGHT_LIGHT; break;
160 case FontWeight_300: nRetval = WEIGHT_SEMILIGHT; break;
161 case FontWeight_400: nRetval = WEIGHT_NORMAL; break;
162 case FontWeight_500: nRetval = WEIGHT_MEDIUM; break;
163 case FontWeight_600: nRetval = WEIGHT_SEMIBOLD; break;
164 case FontWeight_700: nRetval = WEIGHT_BOLD; break;
165 case FontWeight_800: nRetval = WEIGHT_ULTRABOLD; break;
166 case FontWeight_900: nRetval = WEIGHT_BLACK; break;
167 default: break;
170 return nRetval;
173 void SvgStyleAttributes::readCssStyle(const OUString& rCandidate)
175 const sal_Int32 nLen(rCandidate.getLength());
176 sal_Int32 nPos(0);
178 while(nPos < nLen)
180 // get TokenName
181 OUStringBuffer aTokenName;
182 skip_char(rCandidate, u' ', nPos, nLen);
183 copyString(rCandidate, nPos, aTokenName, nLen);
185 if (aTokenName.isEmpty())
187 // if no TokenName advance one by force to avoid death loop, continue
188 OSL_ENSURE(false, "Could not interpret on current position, advancing one byte (!)");
189 nPos++;
190 continue;
193 // get TokenValue
194 OUStringBuffer aTokenValue;
195 skip_char(rCandidate, u' ', u':', nPos, nLen);
196 copyToLimiter(rCandidate, u';', nPos, aTokenValue, nLen);
197 skip_char(rCandidate, u' ', u';', nPos, nLen);
199 if (aTokenValue.isEmpty())
201 // no value - continue
202 continue;
205 // generate OUStrings
206 const OUString aOUTokenName(aTokenName.makeStringAndClear());
207 OUString aOUTokenValue(aTokenValue.makeStringAndClear());
209 // check for '!important' CssStyle mark, currently not supported
210 // but needs to be extracted for correct parsing
211 OUString aTokenImportant("!important");
212 const sal_Int32 nIndexTokenImportant(aOUTokenValue.indexOf(aTokenImportant));
214 if(-1 != nIndexTokenImportant)
216 // if there currently just remove it and remove spaces to have the value only
217 OUString aNewOUTokenValue;
219 if(nIndexTokenImportant > 0)
221 // copy content before token
222 aNewOUTokenValue += aOUTokenValue.copy(0, nIndexTokenImportant);
225 if(aOUTokenValue.getLength() > nIndexTokenImportant + aTokenImportant.getLength())
227 // copy content after token
228 aNewOUTokenValue += aOUTokenValue.copy(nIndexTokenImportant + aTokenImportant.getLength());
231 // remove spaces
232 aOUTokenValue = aNewOUTokenValue.trim();
235 // valid token-value pair, parse it
236 parseStyleAttribute(StrToSVGToken(aOUTokenName, true), aOUTokenValue, true);
240 const SvgStyleAttributes* SvgStyleAttributes::getParentStyle() const
242 if(getCssStyleParent())
244 return getCssStyleParent();
247 if(mrOwner.supportsParentStyle() && mrOwner.getParent())
249 return mrOwner.getParent()->getSvgStyleAttributes();
252 return nullptr;
255 void SvgStyleAttributes::add_text(
256 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
257 drawinglayer::primitive2d::Primitive2DContainer const & rSource) const
259 if(!rSource.empty())
261 // at this point the primitives in rSource are of type TextSimplePortionPrimitive2D
262 // or TextDecoratedPortionPrimitive2D and have the Fill Color (pAttributes->getFill())
263 // set. When another fill is used and also evtl. stroke is set it gets necessary to
264 // dismantle to geometry and add needed primitives
265 const basegfx::BColor* pFill = getFill();
266 const SvgGradientNode* pFillGradient = getSvgGradientNodeFill();
267 const SvgPatternNode* pFillPattern = getSvgPatternNodeFill();
268 const basegfx::BColor* pStroke = getStroke();
269 const SvgGradientNode* pStrokeGradient = getSvgGradientNodeStroke();
270 const SvgPatternNode* pStrokePattern = getSvgPatternNodeStroke();
271 basegfx::B2DPolyPolygon aMergedArea;
273 if(pFillGradient || pFillPattern || pStroke || pStrokeGradient || pStrokePattern)
275 // text geometry is needed, create
276 // use neutral ViewInformation and create LineGeometryExtractor2D
277 const drawinglayer::geometry::ViewInformation2D aViewInformation2D;
278 drawinglayer::processor2d::TextAsPolygonExtractor2D aExtractor(aViewInformation2D);
280 // process
281 aExtractor.process(rSource);
283 // get results
284 const drawinglayer::processor2d::TextAsPolygonDataNodeVector& rResult = aExtractor.getTarget();
285 const sal_uInt32 nResultCount(rResult.size());
286 basegfx::B2DPolyPolygonVector aTextFillVector;
287 aTextFillVector.reserve(nResultCount);
289 for(sal_uInt32 a(0); a < nResultCount; a++)
291 const drawinglayer::processor2d::TextAsPolygonDataNode& rCandidate = rResult[a];
293 if(rCandidate.getIsFilled())
295 aTextFillVector.push_back(rCandidate.getB2DPolyPolygon());
299 if(!aTextFillVector.empty())
301 aMergedArea = basegfx::utils::mergeToSinglePolyPolygon(aTextFillVector);
305 const bool bStrokeUsed(pStroke || pStrokeGradient || pStrokePattern);
307 // add fill. Use geometry even for simple color fill when stroke
308 // is used, else text rendering and the geometry-based stroke will
309 // normally not really match optically due to diverse system text
310 // renderers
311 if(aMergedArea.count() && (pFillGradient || pFillPattern || bStrokeUsed))
313 // create text fill content based on geometry
314 add_fill(aMergedArea, rTarget, aMergedArea.getB2DRange());
316 else if(pFill)
318 // add the already prepared primitives for single color fill
319 rTarget.append(rSource);
322 // add stroke
323 if(aMergedArea.count() && bStrokeUsed)
325 // create text stroke content
326 add_stroke(aMergedArea, rTarget, aMergedArea.getB2DRange());
331 void SvgStyleAttributes::add_fillGradient(
332 const basegfx::B2DPolyPolygon& rPath,
333 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
334 const SvgGradientNode& rFillGradient,
335 const basegfx::B2DRange& rGeoRange) const
337 // create fill content
338 drawinglayer::primitive2d::SvgGradientEntryVector aSvgGradientEntryVector;
340 // get the color stops
341 rFillGradient.collectGradientEntries(aSvgGradientEntryVector);
343 if(!aSvgGradientEntryVector.empty())
345 basegfx::B2DHomMatrix aGeoToUnit;
346 basegfx::B2DHomMatrix aGradientTransform;
348 if(rFillGradient.getGradientTransform())
350 aGradientTransform = *rFillGradient.getGradientTransform();
353 if(userSpaceOnUse == rFillGradient.getGradientUnits())
355 aGeoToUnit.translate(-rGeoRange.getMinX(), -rGeoRange.getMinY());
356 aGeoToUnit.scale(1.0 / rGeoRange.getWidth(), 1.0 / rGeoRange.getHeight());
359 if(SVGTokenLinearGradient == rFillGradient.getType())
361 basegfx::B2DPoint aStart(0.0, 0.0);
362 basegfx::B2DPoint aEnd(1.0, 0.0);
364 if(userSpaceOnUse == rFillGradient.getGradientUnits())
366 // all possible units
367 aStart.setX(rFillGradient.getX1().solve(mrOwner, xcoordinate));
368 aStart.setY(rFillGradient.getY1().solve(mrOwner, ycoordinate));
369 aEnd.setX(rFillGradient.getX2().solve(mrOwner, xcoordinate));
370 aEnd.setY(rFillGradient.getY2().solve(mrOwner, ycoordinate));
372 else
374 // fractions or percent relative to object bounds
375 const SvgNumber X1(rFillGradient.getX1());
376 const SvgNumber Y1(rFillGradient.getY1());
377 const SvgNumber X2(rFillGradient.getX2());
378 const SvgNumber Y2(rFillGradient.getY2());
380 aStart.setX(Unit_percent == X1.getUnit() ? X1.getNumber() * 0.01 : X1.getNumber());
381 aStart.setY(Unit_percent == Y1.getUnit() ? Y1.getNumber() * 0.01 : Y1.getNumber());
382 aEnd.setX(Unit_percent == X2.getUnit() ? X2.getNumber() * 0.01 : X2.getNumber());
383 aEnd.setY(Unit_percent == Y2.getUnit() ? Y2.getNumber() * 0.01 : Y2.getNumber());
386 if(!aGeoToUnit.isIdentity())
388 aStart *= aGeoToUnit;
389 aEnd *= aGeoToUnit;
392 rTarget.push_back(
393 new drawinglayer::primitive2d::SvgLinearGradientPrimitive2D(
394 aGradientTransform,
395 rPath,
396 aSvgGradientEntryVector,
397 aStart,
398 aEnd,
399 userSpaceOnUse != rFillGradient.getGradientUnits(),
400 rFillGradient.getSpreadMethod()));
402 else
404 basegfx::B2DPoint aStart(0.5, 0.5);
405 basegfx::B2DPoint aFocal;
406 double fRadius(0.5);
407 const SvgNumber* pFx = rFillGradient.getFx();
408 const SvgNumber* pFy = rFillGradient.getFy();
409 const bool bFocal(pFx || pFy);
411 if(userSpaceOnUse == rFillGradient.getGradientUnits())
413 // all possible units
414 aStart.setX(rFillGradient.getCx().solve(mrOwner, xcoordinate));
415 aStart.setY(rFillGradient.getCy().solve(mrOwner, ycoordinate));
416 fRadius = rFillGradient.getR().solve(mrOwner);
418 if(bFocal)
420 aFocal.setX(pFx ? pFx->solve(mrOwner, xcoordinate) : aStart.getX());
421 aFocal.setY(pFy ? pFy->solve(mrOwner, ycoordinate) : aStart.getY());
424 else
426 // fractions or percent relative to object bounds
427 const SvgNumber Cx(rFillGradient.getCx());
428 const SvgNumber Cy(rFillGradient.getCy());
429 const SvgNumber R(rFillGradient.getR());
431 aStart.setX(Unit_percent == Cx.getUnit() ? Cx.getNumber() * 0.01 : Cx.getNumber());
432 aStart.setY(Unit_percent == Cy.getUnit() ? Cy.getNumber() * 0.01 : Cy.getNumber());
433 fRadius = (Unit_percent == R.getUnit()) ? R.getNumber() * 0.01 : R.getNumber();
435 if(bFocal)
437 aFocal.setX(pFx ? (Unit_percent == pFx->getUnit() ? pFx->getNumber() * 0.01 : pFx->getNumber()) : aStart.getX());
438 aFocal.setY(pFy ? (Unit_percent == pFy->getUnit() ? pFy->getNumber() * 0.01 : pFy->getNumber()) : aStart.getY());
442 if(!aGeoToUnit.isIdentity())
444 aStart *= aGeoToUnit;
445 fRadius = (aGeoToUnit * basegfx::B2DVector(fRadius, 0.0)).getLength();
447 if(bFocal)
449 aFocal *= aGeoToUnit;
453 rTarget.push_back(
454 new drawinglayer::primitive2d::SvgRadialGradientPrimitive2D(
455 aGradientTransform,
456 rPath,
457 aSvgGradientEntryVector,
458 aStart,
459 fRadius,
460 userSpaceOnUse != rFillGradient.getGradientUnits(),
461 rFillGradient.getSpreadMethod(),
462 bFocal ? &aFocal : nullptr));
467 void SvgStyleAttributes::add_fillPatternTransform(
468 const basegfx::B2DPolyPolygon& rPath,
469 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
470 const SvgPatternNode& rFillPattern,
471 const basegfx::B2DRange& rGeoRange) const
473 // prepare fill polyPolygon with given pattern, check for patternTransform
474 if(rFillPattern.getPatternTransform() && !rFillPattern.getPatternTransform()->isIdentity())
476 // PatternTransform is active; Handle by filling the inverse transformed
477 // path and back-transforming the result
478 basegfx::B2DPolyPolygon aPath(rPath);
479 basegfx::B2DHomMatrix aInv(*rFillPattern.getPatternTransform());
480 drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
482 aInv.invert();
483 aPath.transform(aInv);
484 add_fillPattern(aPath, aNewTarget, rFillPattern, aPath.getB2DRange());
486 if(!aNewTarget.empty())
488 rTarget.push_back(
489 new drawinglayer::primitive2d::TransformPrimitive2D(
490 *rFillPattern.getPatternTransform(),
491 aNewTarget));
494 else
496 // no patternTransform, create fillPattern directly
497 add_fillPattern(rPath, rTarget, rFillPattern, rGeoRange);
501 void SvgStyleAttributes::add_fillPattern(
502 const basegfx::B2DPolyPolygon& rPath,
503 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
504 const SvgPatternNode& rFillPattern,
505 const basegfx::B2DRange& rGeoRange) const
507 // fill polyPolygon with given pattern
508 const drawinglayer::primitive2d::Primitive2DContainer& rPrimitives = rFillPattern.getPatternPrimitives();
510 if(!rPrimitives.empty())
512 double fTargetWidth(rGeoRange.getWidth());
513 double fTargetHeight(rGeoRange.getHeight());
515 if(fTargetWidth > 0.0 && fTargetHeight > 0.0)
517 // get relative values from pattern
518 double fX(0.0);
519 double fY(0.0);
520 double fW(0.0);
521 double fH(0.0);
523 rFillPattern.getValuesRelative(fX, fY, fW, fH, rGeoRange, mrOwner);
525 if(fW > 0.0 && fH > 0.0)
527 // build the reference range relative to the rGeoRange
528 const basegfx::B2DRange aReferenceRange(fX, fY, fX + fW, fY + fH);
530 // find out how the content is mapped to the reference range
531 basegfx::B2DHomMatrix aMapPrimitivesToUnitRange;
532 const basegfx::B2DRange* pViewBox = rFillPattern.getViewBox();
534 if(pViewBox)
536 // use viewBox/preserveAspectRatio
537 const SvgAspectRatio& rRatio = rFillPattern.getSvgAspectRatio();
538 const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
540 if(rRatio.isSet())
542 // let mapping be created from SvgAspectRatio
543 aMapPrimitivesToUnitRange = rRatio.createMapping(aUnitRange, *pViewBox);
545 else
547 // choose default mapping
548 aMapPrimitivesToUnitRange = SvgAspectRatio::createLinearMapping(aUnitRange, *pViewBox);
551 else
553 // use patternContentUnits
554 const SvgUnits aPatternContentUnits(rFillPattern.getPatternContentUnits() ? *rFillPattern.getPatternContentUnits() : userSpaceOnUse);
556 if(userSpaceOnUse == aPatternContentUnits)
558 // create relative mapping to unit coordinates
559 aMapPrimitivesToUnitRange.scale(1.0 / (fW * fTargetWidth), 1.0 / (fH * fTargetHeight));
561 else
563 aMapPrimitivesToUnitRange.scale(1.0 / fW, 1.0 / fH);
567 // apply aMapPrimitivesToUnitRange to content when used
568 drawinglayer::primitive2d::Primitive2DContainer aPrimitives(rPrimitives);
570 if(!aMapPrimitivesToUnitRange.isIdentity())
572 const drawinglayer::primitive2d::Primitive2DReference xRef(
573 new drawinglayer::primitive2d::TransformPrimitive2D(
574 aMapPrimitivesToUnitRange,
575 aPrimitives));
577 aPrimitives = drawinglayer::primitive2d::Primitive2DContainer { xRef };
580 // embed in PatternFillPrimitive2D
581 rTarget.push_back(
582 new drawinglayer::primitive2d::PatternFillPrimitive2D(
583 rPath,
584 aPrimitives,
585 aReferenceRange));
591 void SvgStyleAttributes::add_fill(
592 const basegfx::B2DPolyPolygon& rPath,
593 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
594 const basegfx::B2DRange& rGeoRange) const
596 const basegfx::BColor* pFill = getFill();
597 const SvgGradientNode* pFillGradient = getSvgGradientNodeFill();
598 const SvgPatternNode* pFillPattern = getSvgPatternNodeFill();
600 if(pFill || pFillGradient || pFillPattern)
602 const double fFillOpacity(getFillOpacity().solve(mrOwner));
604 if(basegfx::fTools::more(fFillOpacity, 0.0))
606 drawinglayer::primitive2d::Primitive2DContainer aNewFill;
608 if(pFillGradient)
610 // create fill content with SVG gradient primitive
611 add_fillGradient(rPath, aNewFill, *pFillGradient, rGeoRange);
613 else if(pFillPattern)
615 // create fill content with SVG pattern primitive
616 add_fillPatternTransform(rPath, aNewFill, *pFillPattern, rGeoRange);
618 else // if(pFill)
620 // create fill content
621 aNewFill.resize(1);
622 aNewFill[0] = new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
623 rPath,
624 *pFill);
627 if(!aNewFill.empty())
629 if(basegfx::fTools::less(fFillOpacity, 1.0))
631 // embed in UnifiedTransparencePrimitive2D
632 rTarget.push_back(
633 new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
634 aNewFill,
635 1.0 - fFillOpacity));
637 else
639 // append
640 rTarget.append(aNewFill);
647 void SvgStyleAttributes::add_stroke(
648 const basegfx::B2DPolyPolygon& rPath,
649 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
650 const basegfx::B2DRange& rGeoRange) const
652 const basegfx::BColor* pStroke = getStroke();
653 const SvgGradientNode* pStrokeGradient = getSvgGradientNodeStroke();
654 const SvgPatternNode* pStrokePattern = getSvgPatternNodeStroke();
656 if(pStroke || pStrokeGradient || pStrokePattern)
658 drawinglayer::primitive2d::Primitive2DContainer aNewStroke;
659 const double fStrokeOpacity(getStrokeOpacity().solve(mrOwner));
661 if(basegfx::fTools::more(fStrokeOpacity, 0.0))
663 // get stroke width; SVG does not use 0.0 == hairline, so 0.0 is no line at all
664 const double fStrokeWidth(getStrokeWidth().isSet() ? getStrokeWidth().solve(mrOwner) : 1.0);
666 if(basegfx::fTools::more(fStrokeWidth, 0.0))
668 drawinglayer::primitive2d::Primitive2DReference aNewLinePrimitive;
670 // if we have a line with two identical points it is not really a line,
671 // but used by SVG sometimes to paint a single dot.In that case, create
672 // the geometry for a single dot
673 if(1 == rPath.count())
675 const basegfx::B2DPolygon aSingle(rPath.getB2DPolygon(0));
677 if(2 == aSingle.count() && aSingle.getB2DPoint(0).equal(aSingle.getB2DPoint(1)))
679 aNewLinePrimitive = new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
680 basegfx::B2DPolyPolygon(
681 basegfx::utils::createPolygonFromCircle(
682 aSingle.getB2DPoint(0),
683 fStrokeWidth * (1.44 * 0.5))),
684 pStroke ? *pStroke : basegfx::BColor(0.0, 0.0, 0.0));
688 if(!aNewLinePrimitive.is())
690 // get LineJoin, LineCap and stroke array
691 const basegfx::B2DLineJoin aB2DLineJoin(StrokeLinejoinToB2DLineJoin(getStrokeLinejoin()));
692 const css::drawing::LineCap aLineCap(StrokeLinecapToDrawingLineCap(getStrokeLinecap()));
693 ::std::vector< double > aDashArray;
695 if(!getStrokeDasharray().empty())
697 aDashArray = solveSvgNumberVector(getStrokeDasharray(), mrOwner);
700 // convert svg:stroke-miterlimit to LineAttrute:mfMiterMinimumAngle
701 // The default needs to be set explicitly, because svg default <> Draw default
702 double fMiterMinimumAngle;
703 if (getStrokeMiterLimit().isSet())
705 fMiterMinimumAngle = 2.0 * asin(1.0/getStrokeMiterLimit().getNumber());
707 else
709 fMiterMinimumAngle = 2.0 * asin(0.25); // 1.0/default 4.0
712 // todo: Handle getStrokeDashOffset()
714 // prepare line attribute
715 const drawinglayer::attribute::LineAttribute aLineAttribute(
716 pStroke ? *pStroke : basegfx::BColor(0.0, 0.0, 0.0),
717 fStrokeWidth,
718 aB2DLineJoin,
719 aLineCap,
720 fMiterMinimumAngle);
722 if(aDashArray.empty())
724 aNewLinePrimitive = new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
725 rPath,
726 aLineAttribute);
728 else
730 const drawinglayer::attribute::StrokeAttribute aStrokeAttribute(aDashArray);
732 aNewLinePrimitive = new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
733 rPath,
734 aLineAttribute,
735 aStrokeAttribute);
739 if(pStrokeGradient || pStrokePattern)
741 // put primitive into Primitive2DReference and Primitive2DSequence
742 const drawinglayer::primitive2d::Primitive2DContainer aSeq { aNewLinePrimitive };
744 // use neutral ViewInformation and create LineGeometryExtractor2D
745 const drawinglayer::geometry::ViewInformation2D aViewInformation2D;
746 drawinglayer::processor2d::LineGeometryExtractor2D aExtractor(aViewInformation2D);
748 // process
749 aExtractor.process(aSeq);
751 // check for fill rsults
752 const basegfx::B2DPolyPolygonVector& rLineFillVector(aExtractor.getExtractedLineFills());
754 if(!rLineFillVector.empty())
756 const basegfx::B2DPolyPolygon aMergedArea(
757 basegfx::utils::mergeToSinglePolyPolygon(
758 rLineFillVector));
760 if(aMergedArea.count())
762 if(pStrokeGradient)
764 // create fill content with SVG gradient primitive. Use original GeoRange,
765 // e.g. from circle without LineWidth
766 add_fillGradient(aMergedArea, aNewStroke, *pStrokeGradient, rGeoRange);
768 else // if(pStrokePattern)
770 // create fill content with SVG pattern primitive. Use GeoRange
771 // from the expanded data, e.g. circle with extended geo by half linewidth
772 add_fillPatternTransform(aMergedArea, aNewStroke, *pStrokePattern, aMergedArea.getB2DRange());
777 else // if(pStroke)
779 aNewStroke.push_back(aNewLinePrimitive);
782 if(!aNewStroke.empty())
784 if(basegfx::fTools::less(fStrokeOpacity, 1.0))
786 // embed in UnifiedTransparencePrimitive2D
787 rTarget.push_back(
788 new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
789 aNewStroke,
790 1.0 - fStrokeOpacity));
792 else
794 // append
795 rTarget.append(aNewStroke);
803 bool SvgStyleAttributes::prepare_singleMarker(
804 drawinglayer::primitive2d::Primitive2DContainer& rMarkerPrimitives,
805 basegfx::B2DHomMatrix& rMarkerTransform,
806 basegfx::B2DRange& rClipRange,
807 const SvgMarkerNode& rMarker) const
809 // reset return values
810 rMarkerTransform.identity();
811 rClipRange.reset();
813 // get marker primitive representation
814 rMarkerPrimitives = rMarker.getMarkerPrimitives();
816 if(!rMarkerPrimitives.empty())
818 basegfx::B2DRange aPrimitiveRange(0.0, 0.0, 1.0, 1.0);
819 const basegfx::B2DRange* pViewBox = rMarker.getViewBox();
821 if(pViewBox)
823 aPrimitiveRange = *pViewBox;
826 if(aPrimitiveRange.getWidth() > 0.0 && aPrimitiveRange.getHeight() > 0.0)
828 double fTargetWidth(rMarker.getMarkerWidth().isSet() ? rMarker.getMarkerWidth().solve(mrOwner, xcoordinate) : 3.0);
829 double fTargetHeight(rMarker.getMarkerHeight().isSet() ? rMarker.getMarkerHeight().solve(mrOwner, xcoordinate) : 3.0);
830 const bool bStrokeWidth(SvgMarkerNode::strokeWidth == rMarker.getMarkerUnits());
831 const double fStrokeWidth(getStrokeWidth().isSet() ? getStrokeWidth().solve(mrOwner) : 1.0);
833 if(bStrokeWidth)
835 // relative to strokeWidth
836 fTargetWidth *= fStrokeWidth;
837 fTargetHeight *= fStrokeWidth;
840 if(fTargetWidth > 0.0 && fTargetHeight > 0.0)
842 // create mapping
843 const basegfx::B2DRange aTargetRange(0.0, 0.0, fTargetWidth, fTargetHeight);
844 const SvgAspectRatio& rRatio = rMarker.getSvgAspectRatio();
846 if(rRatio.isSet())
848 // let mapping be created from SvgAspectRatio
849 rMarkerTransform = rRatio.createMapping(aTargetRange, aPrimitiveRange);
851 if(rRatio.isMeetOrSlice())
853 // need to clip
854 rClipRange = aPrimitiveRange;
857 else
859 if(!pViewBox)
861 if(bStrokeWidth)
863 // adapt to strokewidth if needed
864 rMarkerTransform.scale(fStrokeWidth, fStrokeWidth);
867 else
869 // choose default mapping
870 rMarkerTransform = SvgAspectRatio::createLinearMapping(aTargetRange, aPrimitiveRange);
874 // get and apply reference point. Initially it's in marker local coordinate system
875 basegfx::B2DPoint aRefPoint(
876 rMarker.getRefX().isSet() ? rMarker.getRefX().solve(mrOwner, xcoordinate) : 0.0,
877 rMarker.getRefY().isSet() ? rMarker.getRefY().solve(mrOwner, ycoordinate) : 0.0);
879 // apply MarkerTransform to have it in mapped coordinates
880 aRefPoint *= rMarkerTransform;
882 // apply by moving RepPoint to (0.0)
883 rMarkerTransform.translate(-aRefPoint.getX(), -aRefPoint.getY());
885 return true;
890 return false;
893 void SvgStyleAttributes::add_markers(
894 const basegfx::B2DPolyPolygon& rPath,
895 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
896 const basegfx::utils::PointIndexSet* pHelpPointIndices) const
898 // try to access linked markers
899 const SvgMarkerNode* pStart = accessMarkerStartXLink();
900 const SvgMarkerNode* pMid = accessMarkerMidXLink();
901 const SvgMarkerNode* pEnd = accessMarkerEndXLink();
903 if(pStart || pMid || pEnd)
905 const sal_uInt32 nSubPathCount(rPath.count());
907 if(nSubPathCount)
909 // remember prepared marker; pStart, pMid and pEnd may all be equal when
910 // only 'marker' was used instead of 'marker-start', 'marker-mid' or 'marker-end',
911 // see 'case SVGTokenMarker' in this file; thus in this case only one common
912 // marker in primitive form will be prepared
913 const SvgMarkerNode* pPrepared = nullptr;
915 // values for the prepared marker, results of prepare_singleMarker
916 drawinglayer::primitive2d::Primitive2DContainer aPreparedMarkerPrimitives;
917 basegfx::B2DHomMatrix aPreparedMarkerTransform;
918 basegfx::B2DRange aPreparedMarkerClipRange;
920 for (sal_uInt32 a(0); a < nSubPathCount; a++)
922 // iterate over sub-paths
923 const basegfx::B2DPolygon aSubPolygonPath(rPath.getB2DPolygon(a));
924 const sal_uInt32 nSubPolygonPointCount(aSubPolygonPath.count());
925 const bool bSubPolygonPathIsClosed(aSubPolygonPath.isClosed());
927 if(nSubPolygonPointCount)
929 // for each sub-path, create one marker per point (when closed, two markers
930 // need to pe created for the 1st point)
931 const sal_uInt32 nTargetMarkerCount(bSubPolygonPathIsClosed ? nSubPolygonPointCount + 1 : nSubPolygonPointCount);
933 for (sal_uInt32 b(0); b < nTargetMarkerCount; b++)
935 const bool bIsFirstMarker(!a && !b);
936 const bool bIsLastMarker(nSubPathCount - 1 == a && nTargetMarkerCount - 1 == b);
937 const SvgMarkerNode* pNeeded = nullptr;
939 if(bIsFirstMarker)
941 // 1st point in 1st sub-polygon, use pStart
942 pNeeded = pStart;
944 else if(bIsLastMarker)
946 // last point in last sub-polygon, use pEnd
947 pNeeded = pEnd;
949 else
951 // anything in-between, use pMid
952 pNeeded = pMid;
955 if(pHelpPointIndices && !pHelpPointIndices->empty())
957 const basegfx::utils::PointIndexSet::const_iterator aFound(
958 pHelpPointIndices->find(basegfx::utils::PointIndex(a, b)));
960 if(aFound != pHelpPointIndices->end())
962 // this point is a pure helper point; do not create a marker for it
963 continue;
967 if(!pNeeded)
969 // no marker needs to be created for this point
970 continue;
973 if(pPrepared != pNeeded)
975 // if needed marker is not yet prepared, do it now
976 if(prepare_singleMarker(aPreparedMarkerPrimitives, aPreparedMarkerTransform, aPreparedMarkerClipRange, *pNeeded))
978 pPrepared = pNeeded;
980 else
982 // error: could not prepare given marker
983 OSL_ENSURE(false, "OOps, could not prepare given marker as primitives (!)");
984 pPrepared = nullptr;
985 continue;
989 // prepare complete transform
990 basegfx::B2DHomMatrix aCombinedTransform(aPreparedMarkerTransform);
992 // get rotation
993 if(pPrepared->getOrientAuto())
995 const sal_uInt32 nPointIndex(b % nSubPolygonPointCount);
997 // get entering and leaving tangents; this will search backward/forward
998 // in the polygon to find tangents unequal to zero, skipping empty edges
999 // see basegfx descriptions)
1000 // Hint: Mozilla, Inkscape and others use only leaving tangent for start marker
1001 // and entering tangent for end marker. To achieve this (if wanted) it is possible
1002 // to make the fetch of aEntering/aLeaving dependent on bIsFirstMarker/bIsLastMarker.
1003 // This is not done here, see comment 14 in task #1232379#
1004 // or http://www.w3.org/TR/SVG/painting.html#OrientAttribute
1005 basegfx::B2DVector aEntering(
1006 basegfx::utils::getTangentEnteringPoint(
1007 aSubPolygonPath,
1008 nPointIndex));
1009 basegfx::B2DVector aLeaving(
1010 basegfx::utils::getTangentLeavingPoint(
1011 aSubPolygonPath,
1012 nPointIndex));
1013 const bool bEntering(!aEntering.equalZero());
1014 const bool bLeaving(!aLeaving.equalZero());
1016 if(bEntering || bLeaving)
1018 basegfx::B2DVector aSum(0.0, 0.0);
1020 if(bEntering)
1022 aSum += aEntering.normalize();
1025 if(bLeaving)
1027 aSum += aLeaving.normalize();
1030 if(!aSum.equalZero())
1032 const double fAngle(atan2(aSum.getY(), aSum.getX()));
1034 // apply rotation
1035 aCombinedTransform.rotate(fAngle);
1039 else
1041 // apply rotation
1042 aCombinedTransform.rotate(pPrepared->getAngle());
1045 // get and apply target position
1046 const basegfx::B2DPoint aPoint(aSubPolygonPath.getB2DPoint(b % nSubPolygonPointCount));
1048 aCombinedTransform.translate(aPoint.getX(), aPoint.getY());
1050 // prepare marker
1051 drawinglayer::primitive2d::Primitive2DReference xMarker(
1052 new drawinglayer::primitive2d::TransformPrimitive2D(
1053 aCombinedTransform,
1054 aPreparedMarkerPrimitives));
1056 if(!aPreparedMarkerClipRange.isEmpty())
1058 // marker needs to be clipped, it's bigger as the mapping
1059 basegfx::B2DPolyPolygon aClipPolygon(basegfx::utils::createPolygonFromRect(aPreparedMarkerClipRange));
1061 aClipPolygon.transform(aCombinedTransform);
1062 xMarker = new drawinglayer::primitive2d::MaskPrimitive2D(
1063 aClipPolygon,
1064 drawinglayer::primitive2d::Primitive2DContainer { xMarker });
1067 // add marker
1068 rTarget.push_back(xMarker);
1076 void SvgStyleAttributes::add_path(
1077 const basegfx::B2DPolyPolygon& rPath,
1078 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
1079 const basegfx::utils::PointIndexSet* pHelpPointIndices) const
1081 if(!rPath.count())
1083 // no geometry at all
1084 return;
1087 const basegfx::B2DRange aGeoRange(rPath.getB2DRange());
1089 if(aGeoRange.isEmpty())
1091 // no geometry range
1092 return;
1095 const double fOpacity(getOpacity().solve(mrOwner));
1097 if(basegfx::fTools::equalZero(fOpacity))
1099 // not visible
1100 return;
1103 // check if it's a line
1104 const bool bNoWidth(basegfx::fTools::equalZero(aGeoRange.getWidth()));
1105 const bool bNoHeight(basegfx::fTools::equalZero(aGeoRange.getHeight()));
1106 const bool bIsTwoPointLine(1 == rPath.count()
1107 && !rPath.areControlPointsUsed()
1108 && 2 == rPath.getB2DPolygon(0).count());
1109 const bool bIsLine(bIsTwoPointLine || bNoWidth || bNoHeight);
1111 if(!bIsLine)
1113 // create fill
1114 basegfx::B2DPolyPolygon aPath(rPath);
1115 const bool bNeedToCheckClipRule(SVGTokenPath == mrOwner.getType() || SVGTokenPolygon == mrOwner.getType());
1116 const bool bClipPathIsNonzero(bNeedToCheckClipRule && mbIsClipPathContent && FillRule_nonzero == maClipRule);
1117 const bool bFillRuleIsNonzero(bNeedToCheckClipRule && !mbIsClipPathContent && FillRule_nonzero == getFillRule());
1119 if(bClipPathIsNonzero || bFillRuleIsNonzero)
1121 if(getFill() || getSvgGradientNodeFill() || getSvgPatternNodeFill()) {
1122 // nonzero is wanted, solve geometrically (see description on basegfx)
1123 // basegfx::utils::createNonzeroConform() is expensive for huge paths
1124 // and is only needed if path will be filled later on
1125 aPath = basegfx::utils::createNonzeroConform(aPath);
1129 add_fill(aPath, rTarget, aGeoRange);
1132 // create stroke
1133 add_stroke(rPath, rTarget, aGeoRange);
1135 // Svg supports markers for path, polygon, polyline and line
1136 if(SVGTokenPath == mrOwner.getType() || // path
1137 SVGTokenPolygon == mrOwner.getType() || // polygon, polyline
1138 SVGTokenLine == mrOwner.getType()) // line
1140 // try to add markers
1141 add_markers(rPath, rTarget, pHelpPointIndices);
1145 void SvgStyleAttributes::add_postProcess(
1146 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
1147 const drawinglayer::primitive2d::Primitive2DContainer& rSource,
1148 const basegfx::B2DHomMatrix* pTransform) const
1150 if(!rSource.empty())
1152 const double fOpacity(getOpacity().solve(mrOwner));
1154 if(basegfx::fTools::equalZero(fOpacity))
1156 return;
1159 drawinglayer::primitive2d::Primitive2DContainer aSource(rSource);
1161 if(basegfx::fTools::less(fOpacity, 1.0))
1163 // embed in UnifiedTransparencePrimitive2D
1164 const drawinglayer::primitive2d::Primitive2DReference xRef(
1165 new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
1166 aSource,
1167 1.0 - fOpacity));
1169 aSource = drawinglayer::primitive2d::Primitive2DContainer { xRef };
1172 if(pTransform)
1174 // create embedding group element with transformation. This applies the given
1175 // transformation to the graphical content, but *not* to mask and/or clip (as needed)
1176 const drawinglayer::primitive2d::Primitive2DReference xRef(
1177 new drawinglayer::primitive2d::TransformPrimitive2D(
1178 *pTransform,
1179 aSource));
1181 aSource = drawinglayer::primitive2d::Primitive2DContainer { xRef };
1184 const SvgClipPathNode* pClip = accessClipPathXLink();
1185 while(pClip)
1187 // #i124852# transform may be needed when userSpaceOnUse
1188 pClip->apply(aSource, pTransform);
1189 pClip = pClip->getSvgStyleAttributes()->accessClipPathXLink();
1192 if(!aSource.empty()) // test again, applied clipPath may have lead to empty geometry
1194 const SvgMaskNode* pMask = accessMaskXLink();
1195 if(pMask)
1197 // #i124852# transform may be needed when userSpaceOnUse
1198 pMask->apply(aSource, pTransform);
1202 // This is part of the SVG import of self-written SVGs from
1203 // Draw/Impress containing multiple Slides/Pages. To be able
1204 // to later 'break' these to multiple Pages if wanted, embed
1205 // each Page-Content in a identifiable Primitive Grouping
1206 // Object.
1207 // This is the case when the current Node is a GroupNode, has
1208 // class="Page" set, has a parent that also is a GroupNode
1209 // at which class="Slide" is set.
1210 // Multiple Slides/Pages are possible for Draw and Impress.
1211 if(SVGTokenG == mrOwner.getType() && mrOwner.getClass())
1213 const OUString aOwnerClass(*mrOwner.getClass());
1215 if("Page" == aOwnerClass)
1217 const SvgNode* pParent(mrOwner.getParent());
1219 if(nullptr != pParent && SVGTokenG == pParent->getType() && pParent->getClass())
1221 const OUString aParentClass(*pParent->getClass());
1223 if("Slide" == aParentClass)
1225 // embed to grouping primitive to identify the
1226 // Slide/Page information
1227 const drawinglayer::primitive2d::Primitive2DReference xRef(
1228 new drawinglayer::primitive2d::PageHierarchyPrimitive2D(
1229 aSource));
1231 aSource = drawinglayer::primitive2d::Primitive2DContainer { xRef };
1237 if(!aSource.empty()) // test again, applied mask may have lead to empty geometry
1239 // append to current target
1240 rTarget.append(aSource);
1245 SvgStyleAttributes::SvgStyleAttributes(SvgNode& rOwner)
1246 : mrOwner(rOwner),
1247 mpCssStyleParent(nullptr),
1248 maFill(),
1249 maStroke(),
1250 maStopColor(basegfx::BColor(0.0, 0.0, 0.0), true),
1251 maStrokeWidth(),
1252 maStopOpacity(),
1253 mpSvgGradientNodeFill(nullptr),
1254 mpSvgGradientNodeStroke(nullptr),
1255 mpSvgPatternNodeFill(nullptr),
1256 mpSvgPatternNodeStroke(nullptr),
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 mbIsClipPathContent(SVGTokenClipPathNode == mrOwner.getType()),
1293 mbStrokeDasharraySet(false)
1295 const SvgStyleAttributes* pParentStyle = getParentStyle();
1296 if(!mbIsClipPathContent)
1298 if(pParentStyle)
1300 mbIsClipPathContent = pParentStyle->mbIsClipPathContent;
1305 SvgStyleAttributes::~SvgStyleAttributes()
1309 void SvgStyleAttributes::parseStyleAttribute(
1310 SVGToken aSVGToken,
1311 const OUString& aContent,
1312 bool bCaseIndependent)
1314 switch(aSVGToken)
1316 case SVGTokenFill:
1318 SvgPaint aSvgPaint;
1319 OUString aURL;
1320 SvgNumber aOpacity;
1322 if(readSvgPaint(aContent, aSvgPaint, aURL, bCaseIndependent, aOpacity))
1324 setFill(aSvgPaint);
1325 if(aOpacity.isSet())
1327 setOpacity(SvgNumber(basegfx::clamp(aOpacity.getNumber(), 0.0, 1.0)));
1330 else if(!aURL.isEmpty())
1332 const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(aURL);
1334 if(pNode)
1336 if(SVGTokenLinearGradient == pNode->getType() || SVGTokenRadialGradient == pNode->getType())
1338 mpSvgGradientNodeFill = static_cast< const SvgGradientNode* >(pNode);
1340 else if(SVGTokenPattern == pNode->getType())
1342 mpSvgPatternNodeFill = static_cast< const SvgPatternNode* >(pNode);
1346 break;
1348 case SVGTokenFillOpacity:
1350 SvgNumber aNum;
1352 if(readSingleNumber(aContent, aNum))
1354 maFillOpacity = SvgNumber(basegfx::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet());
1356 break;
1358 case SVGTokenFillRule:
1360 if(!aContent.isEmpty())
1362 if(aContent.match(commonStrings::aStrNonzero))
1364 maFillRule = FillRule_nonzero;
1366 else if(aContent.match(commonStrings::aStrEvenOdd))
1368 maFillRule = FillRule_evenodd;
1371 break;
1373 case SVGTokenStroke:
1375 SvgPaint aSvgPaint;
1376 OUString aURL;
1377 SvgNumber aOpacity;
1379 if(readSvgPaint(aContent, aSvgPaint, aURL, bCaseIndependent, aOpacity))
1381 maStroke = aSvgPaint;
1382 if(aOpacity.isSet())
1384 setOpacity(SvgNumber(basegfx::clamp(aOpacity.getNumber(), 0.0, 1.0)));
1387 else if(!aURL.isEmpty())
1389 const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(aURL);
1391 if(pNode)
1393 if(SVGTokenLinearGradient == pNode->getType() || SVGTokenRadialGradient == pNode->getType())
1395 mpSvgGradientNodeStroke = static_cast< const SvgGradientNode* >(pNode);
1397 else if(SVGTokenPattern == pNode->getType())
1399 mpSvgPatternNodeStroke = static_cast< const SvgPatternNode* >(pNode);
1403 break;
1405 case SVGTokenStrokeDasharray:
1407 if(!aContent.isEmpty())
1409 SvgNumberVector aVector;
1411 if(aContent.startsWith("none"))
1413 // #121221# The special value 'none' needs to be handled
1414 // in the sense that *when* it is set, the parent shall not
1415 // be used. Before this was only dependent on the array being
1416 // empty
1417 mbStrokeDasharraySet = true;
1419 else if(readSvgNumberVector(aContent, aVector))
1421 maStrokeDasharray = aVector;
1424 break;
1426 case SVGTokenStrokeDashoffset:
1428 SvgNumber aNum;
1430 if(readSingleNumber(aContent, aNum))
1432 if(aNum.isPositive())
1434 maStrokeDashOffset = aNum;
1437 break;
1439 case SVGTokenStrokeLinecap:
1441 if(!aContent.isEmpty())
1443 if(aContent.startsWith("butt"))
1445 setStrokeLinecap(StrokeLinecap_butt);
1447 else if(aContent.startsWith("round"))
1449 setStrokeLinecap(StrokeLinecap_round);
1451 else if(aContent.startsWith("square"))
1453 setStrokeLinecap(StrokeLinecap_square);
1456 break;
1458 case SVGTokenStrokeLinejoin:
1460 if(!aContent.isEmpty())
1462 if(aContent.startsWith("miter"))
1464 setStrokeLinejoin(StrokeLinejoin_miter);
1466 else if(aContent.startsWith("round"))
1468 setStrokeLinejoin(StrokeLinejoin_round);
1470 else if(aContent.startsWith("bevel"))
1472 setStrokeLinejoin(StrokeLinejoin_bevel);
1475 break;
1477 case SVGTokenStrokeMiterlimit:
1479 SvgNumber aNum;
1481 if(readSingleNumber(aContent, aNum))
1483 if(basegfx::fTools::moreOrEqual(aNum.getNumber(), 1.0))
1484 { //readSingleNumber sets Unit_px as default, if unit is missing. Correct it here.
1485 maStrokeMiterLimit = SvgNumber(aNum.getNumber(), Unit_none);
1488 break;
1490 case SVGTokenStrokeOpacity:
1493 SvgNumber aNum;
1495 if(readSingleNumber(aContent, aNum))
1497 maStrokeOpacity = SvgNumber(basegfx::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet());
1499 break;
1501 case SVGTokenStrokeWidth:
1503 SvgNumber aNum;
1505 if(readSingleNumber(aContent, aNum))
1507 if(aNum.isPositive())
1509 maStrokeWidth = aNum;
1512 break;
1514 case SVGTokenStopColor:
1516 SvgPaint aSvgPaint;
1517 OUString aURL;
1518 SvgNumber aOpacity;
1520 if(readSvgPaint(aContent, aSvgPaint, aURL, bCaseIndependent, aOpacity))
1522 maStopColor = aSvgPaint;
1523 if(aOpacity.isSet())
1525 setOpacity(SvgNumber(basegfx::clamp(aOpacity.getNumber(), 0.0, 1.0)));
1528 break;
1530 case SVGTokenStopOpacity:
1532 SvgNumber aNum;
1534 if(readSingleNumber(aContent, aNum))
1536 if(aNum.isPositive())
1538 maStopOpacity = aNum;
1541 break;
1543 case SVGTokenFont:
1545 break;
1547 case SVGTokenFontFamily:
1549 SvgStringVector aSvgStringVector;
1551 if(readSvgStringVector(aContent, aSvgStringVector))
1553 maFontFamily = aSvgStringVector;
1555 break;
1557 case SVGTokenFontSize:
1559 if(!aContent.isEmpty())
1561 if(aContent.startsWith("xx-small"))
1563 setFontSize(FontSize_xx_small);
1565 else if(aContent.startsWith("x-small"))
1567 setFontSize(FontSize_x_small);
1569 else if(aContent.startsWith("small"))
1571 setFontSize(FontSize_small);
1573 else if(aContent.startsWith("smaller"))
1575 setFontSize(FontSize_smaller);
1577 else if(aContent.startsWith("medium"))
1579 setFontSize(FontSize_medium);
1581 else if(aContent.startsWith("larger"))
1583 setFontSize(FontSize_larger);
1585 else if(aContent.startsWith("large"))
1587 setFontSize(FontSize_large);
1589 else if(aContent.startsWith("x-large"))
1591 setFontSize(FontSize_x_large);
1593 else if(aContent.startsWith("xx-large"))
1595 setFontSize(FontSize_xx_large);
1597 else if(aContent.startsWith("initial"))
1599 setFontSize(FontSize_initial);
1601 else
1603 SvgNumber aNum;
1605 if(readSingleNumber(aContent, aNum))
1607 maFontSizeNumber = aNum;
1611 break;
1613 case SVGTokenFontSizeAdjust:
1615 break;
1617 case SVGTokenFontStretch:
1619 if(!aContent.isEmpty())
1621 if(aContent.startsWith("normal"))
1623 setFontStretch(FontStretch_normal);
1625 else if(aContent.startsWith("wider"))
1627 setFontStretch(FontStretch_wider);
1629 else if(aContent.startsWith("narrower"))
1631 setFontStretch(FontStretch_narrower);
1633 else if(aContent.startsWith("ultra-condensed"))
1635 setFontStretch(FontStretch_ultra_condensed);
1637 else if(aContent.startsWith("extra-condensed"))
1639 setFontStretch(FontStretch_extra_condensed);
1641 else if(aContent.startsWith("condensed"))
1643 setFontStretch(FontStretch_condensed);
1645 else if(aContent.startsWith("semi-condensed"))
1647 setFontStretch(FontStretch_semi_condensed);
1649 else if(aContent.startsWith("semi-expanded"))
1651 setFontStretch(FontStretch_semi_expanded);
1653 else if(aContent.startsWith("expanded"))
1655 setFontStretch(FontStretch_expanded);
1657 else if(aContent.startsWith("extra-expanded"))
1659 setFontStretch(FontStretch_extra_expanded);
1661 else if(aContent.startsWith("ultra-expanded"))
1663 setFontStretch(FontStretch_ultra_expanded);
1666 break;
1668 case SVGTokenFontStyle:
1670 if(!aContent.isEmpty())
1672 if(aContent.startsWith("normal"))
1674 setFontStyle(FontStyle_normal);
1676 else if(aContent.startsWith("italic"))
1678 setFontStyle(FontStyle_italic);
1680 else if(aContent.startsWith("oblique"))
1682 setFontStyle(FontStyle_oblique);
1685 break;
1687 case SVGTokenFontVariant:
1689 break;
1691 case SVGTokenFontWeight:
1693 if(!aContent.isEmpty())
1695 if(aContent.startsWith("100"))
1697 setFontWeight(FontWeight_100);
1699 else if(aContent.startsWith("200"))
1701 setFontWeight(FontWeight_200);
1703 else if(aContent.startsWith("300"))
1705 setFontWeight(FontWeight_300);
1707 else if(aContent.startsWith("400") || aContent.startsWith("normal"))
1709 setFontWeight(FontWeight_400);
1711 else if(aContent.startsWith("500"))
1713 setFontWeight(FontWeight_500);
1715 else if(aContent.startsWith("600"))
1717 setFontWeight(FontWeight_600);
1719 else if(aContent.startsWith("700") || aContent.startsWith("bold"))
1721 setFontWeight(FontWeight_700);
1723 else if(aContent.startsWith("800"))
1725 setFontWeight(FontWeight_800);
1727 else if(aContent.startsWith("900"))
1729 setFontWeight(FontWeight_900);
1731 else if(aContent.startsWith("bolder"))
1733 setFontWeight(FontWeight_bolder);
1735 else if(aContent.startsWith("lighter"))
1737 setFontWeight(FontWeight_lighter);
1740 break;
1742 case SVGTokenDirection:
1744 break;
1746 case SVGTokenLetterSpacing:
1748 break;
1750 case SVGTokenTextDecoration:
1752 if(!aContent.isEmpty())
1754 if(aContent.startsWith("none"))
1756 setTextDecoration(TextDecoration_none);
1758 else if(aContent.startsWith("underline"))
1760 setTextDecoration(TextDecoration_underline);
1762 else if(aContent.startsWith("overline"))
1764 setTextDecoration(TextDecoration_overline);
1766 else if(aContent.startsWith("line-through"))
1768 setTextDecoration(TextDecoration_line_through);
1770 else if(aContent.startsWith("blink"))
1772 setTextDecoration(TextDecoration_blink);
1775 break;
1777 case SVGTokenUnicodeBidi:
1779 break;
1781 case SVGTokenWordSpacing:
1783 break;
1785 case SVGTokenTextAnchor:
1787 if(!aContent.isEmpty())
1789 if(aContent.startsWith("start"))
1791 setTextAnchor(TextAnchor_start);
1793 else if(aContent.startsWith("middle"))
1795 setTextAnchor(TextAnchor_middle);
1797 else if(aContent.startsWith("end"))
1799 setTextAnchor(TextAnchor_end);
1802 break;
1804 case SVGTokenTextAlign:
1806 if(!aContent.isEmpty())
1808 if(aContent.startsWith("left"))
1810 setTextAlign(TextAlign_left);
1812 else if(aContent.startsWith("right"))
1814 setTextAlign(TextAlign_right);
1816 else if(aContent.startsWith("center"))
1818 setTextAlign(TextAlign_center);
1820 else if(aContent.startsWith("justify"))
1822 setTextAlign(TextAlign_justify);
1825 break;
1827 case SVGTokenColor:
1829 SvgPaint aSvgPaint;
1830 OUString aURL;
1831 SvgNumber aOpacity;
1833 if(readSvgPaint(aContent, aSvgPaint, aURL, bCaseIndependent, aOpacity))
1835 maColor = aSvgPaint;
1836 if(aOpacity.isSet())
1838 setOpacity(SvgNumber(basegfx::clamp(aOpacity.getNumber(), 0.0, 1.0)));
1841 break;
1843 case SVGTokenOpacity:
1845 SvgNumber aNum;
1847 if(readSingleNumber(aContent, aNum))
1849 setOpacity(SvgNumber(basegfx::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet()));
1851 break;
1853 case SVGTokenVisibility:
1855 if(!aContent.isEmpty())
1857 if(aContent.startsWith("visible"))
1859 setVisibility(Visibility_visible);
1861 else if(aContent.startsWith("hidden"))
1863 setVisibility(Visibility_hidden);
1865 else if(aContent.startsWith("collapse"))
1867 setVisibility(Visibility_collapse);
1869 else if(aContent.startsWith("inherit"))
1871 setVisibility(Visibility_inherit);
1874 break;
1876 case SVGTokenTitle:
1878 maTitle = aContent;
1879 break;
1881 case SVGTokenDesc:
1883 maDesc = aContent;
1884 break;
1886 case SVGTokenClipPathProperty:
1888 readLocalUrl(aContent, maClipPathXLink);
1889 break;
1891 case SVGTokenMask:
1893 readLocalUrl(aContent, maMaskXLink);
1894 break;
1896 case SVGTokenClipRule:
1898 if(!aContent.isEmpty())
1900 if(aContent.match(commonStrings::aStrNonzero))
1902 maClipRule = FillRule_nonzero;
1904 else if(aContent.match(commonStrings::aStrEvenOdd))
1906 maClipRule = FillRule_evenodd;
1909 break;
1911 case SVGTokenMarker:
1913 if(bCaseIndependent)
1915 readLocalUrl(aContent, maMarkerEndXLink);
1916 maMarkerStartXLink = maMarkerMidXLink = maMarkerEndXLink;
1918 break;
1920 case SVGTokenMarkerStart:
1922 readLocalUrl(aContent, maMarkerStartXLink);
1923 break;
1925 case SVGTokenMarkerMid:
1927 readLocalUrl(aContent, maMarkerMidXLink);
1928 break;
1930 case SVGTokenMarkerEnd:
1932 readLocalUrl(aContent, maMarkerEndXLink);
1933 break;
1935 case SVGTokenDisplay:
1937 // There may be display:none statements inside of style defines, e.g. the following line:
1938 // style="display:none"
1939 // taken from a svg example; this needs to be parsed and set at the owning node. Do not call
1940 // mrOwner.parseAttribute(...) here, this would lead to a recursion
1941 if(!aContent.isEmpty())
1943 mrOwner.setDisplay(getDisplayFromContent(aContent));
1945 break;
1947 case SVGTokenBaselineShift:
1949 if(!aContent.isEmpty())
1951 SvgNumber aNum;
1953 if(aContent.startsWith("sub"))
1955 setBaselineShift(BaselineShift_Sub);
1957 else if(aContent.startsWith("super"))
1959 setBaselineShift(BaselineShift_Super);
1961 else if(readSingleNumber(aContent, aNum))
1963 maBaselineShiftNumber = aNum;
1965 if(Unit_percent == aNum.getUnit())
1967 setBaselineShift(BaselineShift_Percentage);
1969 else
1971 setBaselineShift(BaselineShift_Length);
1974 else
1976 // no BaselineShift or inherit (which is automatically)
1977 setBaselineShift(BaselineShift_Baseline);
1980 break;
1982 default:
1984 break;
1989 // #i125258# ask if fill is a direct hard attribute (no hierarchy)
1990 bool SvgStyleAttributes::isFillSet() const
1992 if(mbIsClipPathContent)
1994 return false;
1996 else if(maFill.isSet())
1998 return true;
2001 return false;
2004 const basegfx::BColor* SvgStyleAttributes::getCurrentColor() const
2006 static basegfx::BColor aBlack(0.0, 0.0, 0.0);
2007 const basegfx::BColor *aColor = getColor();
2008 if( aColor )
2009 return aColor;
2010 else
2011 return &aBlack;
2014 const basegfx::BColor* SvgStyleAttributes::getFill() const
2016 if(maFill.isSet())
2018 if(maFill.isCurrent())
2020 return getCurrentColor();
2022 else if(maFill.isOn())
2024 return &maFill.getBColor();
2027 else if (!mpSvgGradientNodeFill && !mpSvgPatternNodeFill)
2029 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2031 if(pSvgStyleAttributes)
2033 const basegfx::BColor* pFill = pSvgStyleAttributes->getFill();
2035 if(mbIsClipPathContent)
2037 if (pFill)
2039 return pFill;
2041 else
2043 static basegfx::BColor aBlack(0.0, 0.0, 0.0);
2044 return &aBlack;
2047 else
2049 return pFill;
2054 return nullptr;
2057 const basegfx::BColor* SvgStyleAttributes::getStroke() const
2059 if(maStroke.isSet())
2061 if(maStroke.isCurrent())
2063 return getCurrentColor();
2065 else if(maStroke.isOn())
2067 return &maStroke.getBColor();
2070 else if (!mpSvgGradientNodeStroke && !mpSvgPatternNodeStroke)
2072 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2074 if(pSvgStyleAttributes)
2076 return pSvgStyleAttributes->getStroke();
2080 return nullptr;
2083 const basegfx::BColor& SvgStyleAttributes::getStopColor() const
2085 if(maStopColor.isCurrent())
2087 return *getCurrentColor();
2089 else
2091 return maStopColor.getBColor();
2095 const SvgGradientNode* SvgStyleAttributes::getSvgGradientNodeFill() const
2097 if(mpSvgGradientNodeFill)
2099 return mpSvgGradientNodeFill;
2101 else if (!maFill.isSet() && !mpSvgPatternNodeFill)
2103 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2105 if(pSvgStyleAttributes)
2107 return pSvgStyleAttributes->getSvgGradientNodeFill();
2111 return nullptr;
2114 const SvgGradientNode* SvgStyleAttributes::getSvgGradientNodeStroke() const
2116 if(mpSvgGradientNodeStroke)
2118 return mpSvgGradientNodeStroke;
2120 else if (!maStroke.isSet() && !mpSvgPatternNodeStroke)
2122 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2124 if(pSvgStyleAttributes)
2126 return pSvgStyleAttributes->getSvgGradientNodeStroke();
2130 return nullptr;
2133 const SvgPatternNode* SvgStyleAttributes::getSvgPatternNodeFill() const
2135 if(mpSvgPatternNodeFill)
2137 return mpSvgPatternNodeFill;
2139 else if (!maFill.isSet() && !mpSvgGradientNodeFill)
2141 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2143 if(pSvgStyleAttributes)
2145 return pSvgStyleAttributes->getSvgPatternNodeFill();
2149 return nullptr;
2152 const SvgPatternNode* SvgStyleAttributes::getSvgPatternNodeStroke() const
2154 if(mpSvgPatternNodeStroke)
2156 return mpSvgPatternNodeStroke;
2158 else if (!maStroke.isSet() && !mpSvgGradientNodeStroke)
2160 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2162 if(pSvgStyleAttributes)
2164 return pSvgStyleAttributes->getSvgPatternNodeStroke();
2168 return nullptr;
2171 SvgNumber SvgStyleAttributes::getStrokeWidth() const
2173 if(maStrokeWidth.isSet())
2175 return maStrokeWidth;
2178 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2180 if(pSvgStyleAttributes)
2182 return pSvgStyleAttributes->getStrokeWidth();
2185 if(mbIsClipPathContent)
2187 return SvgNumber(0.0);
2190 // default is 1
2191 return SvgNumber(1.0);
2194 SvgNumber SvgStyleAttributes::getStopOpacity() const
2196 if(maStopOpacity.isSet())
2198 return maStopOpacity;
2201 // default is 1
2202 return SvgNumber(1.0);
2205 SvgNumber SvgStyleAttributes::getFillOpacity() const
2207 if(maFillOpacity.isSet())
2209 return maFillOpacity;
2212 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2214 if(pSvgStyleAttributes)
2216 return pSvgStyleAttributes->getFillOpacity();
2219 // default is 1
2220 return SvgNumber(1.0);
2223 SvgNumber SvgStyleAttributes::getOpacity() const
2225 if(maOpacity.isSet())
2227 return maOpacity;
2230 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2232 if(pSvgStyleAttributes)
2234 return pSvgStyleAttributes->getOpacity();
2237 // default is 1
2238 return SvgNumber(1.0);
2241 Visibility SvgStyleAttributes::getVisibility() const
2243 if(Visibility_notset == maVisibility || Visibility_inherit == maVisibility)
2245 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2247 if(pSvgStyleAttributes)
2249 return pSvgStyleAttributes->getVisibility();
2251 //default is Visible
2252 return Visibility_visible;
2255 // Visibility correction/exception for self-exported SVGs:
2256 // When Impress exports single or multi-page SVGs, it puts the
2257 // single slides into <g visibility="hidden">. Not sure why
2258 // whis happens, but this leads (correctly) to empty imported
2259 // Graphics.
2260 // Thus, if Visibility_hidden is active and owner is a SVGTokenG
2261 // and it's parent is also a SVGTokenG and it has a Class 'SlideGroup'
2262 // set, check if we are an Impress export.
2263 // We are an Impress export if an SVG-Node titled 'ooo:meta_slides'
2264 // exists.
2265 // All togehter gives:
2266 if(Visibility_hidden == maVisibility
2267 && SVGTokenG == mrOwner.getType()
2268 && nullptr != mrOwner.getDocument().findSvgNodeById("ooo:meta_slides"))
2270 const SvgNode* pParent(mrOwner.getParent());
2272 if(nullptr != pParent && SVGTokenG == pParent->getType() && pParent->getClass())
2274 const OUString aClass(*pParent->getClass());
2276 if("SlideGroup" == aClass)
2278 // if we detect this exception,
2279 // ovverride Visibility_hidden -> Visibility_visible
2280 return Visibility_visible;
2285 return maVisibility;
2288 FillRule SvgStyleAttributes::getFillRule() const
2290 if(FillRule_notset != maFillRule)
2292 return maFillRule;
2295 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2297 if(pSvgStyleAttributes)
2299 return pSvgStyleAttributes->getFillRule();
2302 // default is NonZero
2303 return FillRule_nonzero;
2306 const SvgNumberVector& SvgStyleAttributes::getStrokeDasharray() const
2308 if(!maStrokeDasharray.empty())
2310 return maStrokeDasharray;
2312 else if(mbStrokeDasharraySet)
2314 // #121221# is set to empty *by purpose*, do not visit parent styles
2315 return maStrokeDasharray;
2318 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2320 if(pSvgStyleAttributes)
2322 return pSvgStyleAttributes->getStrokeDasharray();
2325 // default empty
2326 return maStrokeDasharray;
2329 SvgNumber SvgStyleAttributes::getStrokeDashOffset() const
2331 if(maStrokeDashOffset.isSet())
2333 return maStrokeDashOffset;
2336 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2338 if(pSvgStyleAttributes)
2340 return pSvgStyleAttributes->getStrokeDashOffset();
2343 // default is 0
2344 return SvgNumber(0.0);
2347 StrokeLinecap SvgStyleAttributes::getStrokeLinecap() const
2349 if(maStrokeLinecap != StrokeLinecap_notset)
2351 return maStrokeLinecap;
2354 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2356 if(pSvgStyleAttributes)
2358 return pSvgStyleAttributes->getStrokeLinecap();
2361 // default is StrokeLinecap_butt
2362 return StrokeLinecap_butt;
2365 StrokeLinejoin SvgStyleAttributes::getStrokeLinejoin() const
2367 if(maStrokeLinejoin != StrokeLinejoin_notset)
2369 return maStrokeLinejoin;
2372 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2374 if(pSvgStyleAttributes)
2376 return pSvgStyleAttributes->getStrokeLinejoin();
2379 // default is StrokeLinejoin_butt
2380 return StrokeLinejoin_miter;
2383 SvgNumber SvgStyleAttributes::getStrokeMiterLimit() const
2385 if(maStrokeMiterLimit.isSet())
2387 return maStrokeMiterLimit;
2390 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2392 if(pSvgStyleAttributes)
2394 return pSvgStyleAttributes->getStrokeMiterLimit();
2397 // default is 4
2398 return SvgNumber(4.0, Unit_none);
2401 SvgNumber SvgStyleAttributes::getStrokeOpacity() const
2403 if(maStrokeOpacity.isSet())
2405 return maStrokeOpacity;
2408 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2410 if(pSvgStyleAttributes)
2412 return pSvgStyleAttributes->getStrokeOpacity();
2415 // default is 1
2416 return SvgNumber(1.0);
2419 const SvgStringVector& SvgStyleAttributes::getFontFamily() const
2421 if(!maFontFamily.empty() && !maFontFamily[0].startsWith("inherit"))
2423 return maFontFamily;
2426 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2428 if(pSvgStyleAttributes)
2430 return pSvgStyleAttributes->getFontFamily();
2433 // default is empty
2434 return maFontFamily;
2437 SvgNumber SvgStyleAttributes::getFontSizeNumber() const
2439 // default size is 'medium' or 16px, which is equal to the default PPI used in svgio ( 96.0 )
2440 // converted to pixels
2441 const double aDefaultSize = F_SVG_PIXEL_PER_INCH / 6.0;
2443 if(maFontSizeNumber.isSet())
2445 if(!maFontSizeNumber.isPositive())
2446 return aDefaultSize;
2448 // #122524# Handle Unit_percent realtive to parent FontSize (see SVG1.1
2449 // spec 10.10 Font selection properties \91font-size\92, lastline (click 'normative
2450 // definition of the property')
2451 if(Unit_percent == maFontSizeNumber.getUnit())
2453 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2455 if(pSvgStyleAttributes)
2457 const SvgNumber aParentNumber = pSvgStyleAttributes->getFontSizeNumber();
2459 return SvgNumber(
2460 aParentNumber.getNumber() * maFontSizeNumber.getNumber() * 0.01,
2461 aParentNumber.getUnit(),
2462 true);
2464 // if there's no parent style, set the font size based on the default size
2465 // 100% = 16px
2466 return SvgNumber(
2467 maFontSizeNumber.getNumber() * aDefaultSize / 100.0, Unit_px, true);
2469 else if((Unit_em == maFontSizeNumber.getUnit()) || (Unit_ex == maFontSizeNumber.getUnit()))
2471 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2473 if(pSvgStyleAttributes)
2475 const SvgNumber aParentNumber = pSvgStyleAttributes->getFontSizeNumber();
2477 return SvgNumber(
2478 aParentNumber.getNumber() * maFontSizeNumber.getNumber(),
2479 aParentNumber.getUnit(),
2480 true);
2484 return maFontSizeNumber;
2487 //In CSS2, the suggested scaling factor between adjacent indexes is 1.2
2488 switch(maFontSize)
2490 case FontSize_notset:
2491 break;
2492 case FontSize_xx_small:
2494 return SvgNumber(aDefaultSize / 1.728);
2496 case FontSize_x_small:
2498 return SvgNumber(aDefaultSize / 1.44);
2500 case FontSize_small:
2502 return SvgNumber(aDefaultSize / 1.2);
2504 case FontSize_smaller:
2506 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2507 if(pSvgStyleAttributes)
2509 const SvgNumber aParentNumber = pSvgStyleAttributes->getFontSizeNumber();
2510 return SvgNumber(aParentNumber.getNumber() / 1.2, aParentNumber.getUnit());
2512 SAL_FALLTHROUGH;
2514 case FontSize_medium:
2515 case FontSize_initial:
2517 return SvgNumber(aDefaultSize);
2519 case FontSize_large:
2521 return SvgNumber(aDefaultSize * 1.2);
2523 case FontSize_larger:
2525 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2526 if(pSvgStyleAttributes)
2528 const SvgNumber aParentNumber = pSvgStyleAttributes->getFontSizeNumber();
2529 return SvgNumber(aParentNumber.getNumber() * 1.2, aParentNumber.getUnit());
2531 SAL_FALLTHROUGH;
2533 case FontSize_x_large:
2535 return SvgNumber(aDefaultSize * 1.44);
2537 case FontSize_xx_large:
2539 return SvgNumber(aDefaultSize * 1.728);
2543 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2545 if(pSvgStyleAttributes)
2547 return pSvgStyleAttributes->getFontSizeNumber();
2550 return SvgNumber(aDefaultSize);
2553 FontStretch SvgStyleAttributes::getFontStretch() const
2555 if(maFontStretch != FontStretch_notset)
2557 if(FontStretch_wider != maFontStretch && FontStretch_narrower != maFontStretch)
2559 return maFontStretch;
2563 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2565 if(pSvgStyleAttributes)
2567 FontStretch aInherited = pSvgStyleAttributes->getFontStretch();
2569 if(FontStretch_wider == maFontStretch)
2571 aInherited = getWider(aInherited);
2573 else if(FontStretch_narrower == maFontStretch)
2575 aInherited = getNarrower(aInherited);
2578 return aInherited;
2581 // default is FontStretch_normal
2582 return FontStretch_normal;
2585 FontStyle SvgStyleAttributes::getFontStyle() const
2587 if(maFontStyle != FontStyle_notset)
2589 return maFontStyle;
2592 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2594 if(pSvgStyleAttributes)
2596 return pSvgStyleAttributes->getFontStyle();
2599 // default is FontStyle_normal
2600 return FontStyle_normal;
2603 FontWeight SvgStyleAttributes::getFontWeight() const
2605 if(maFontWeight != FontWeight_notset)
2607 if(FontWeight_bolder != maFontWeight && FontWeight_lighter != maFontWeight)
2609 return maFontWeight;
2613 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2615 if(pSvgStyleAttributes)
2617 FontWeight aInherited = pSvgStyleAttributes->getFontWeight();
2619 if(FontWeight_bolder == maFontWeight)
2621 aInherited = getBolder(aInherited);
2623 else if(FontWeight_lighter == maFontWeight)
2625 aInherited = getLighter(aInherited);
2628 return aInherited;
2631 // default is FontWeight_400 (FontWeight_normal)
2632 return FontWeight_400;
2635 TextAlign SvgStyleAttributes::getTextAlign() const
2637 if(maTextAlign != TextAlign_notset)
2639 return maTextAlign;
2642 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2644 if(pSvgStyleAttributes)
2646 return pSvgStyleAttributes->getTextAlign();
2649 // default is TextAlign_left
2650 return TextAlign_left;
2653 const SvgStyleAttributes* SvgStyleAttributes::getTextDecorationDefiningSvgStyleAttributes() const
2655 if(maTextDecoration != TextDecoration_notset)
2657 return this;
2660 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2662 if(pSvgStyleAttributes)
2664 return pSvgStyleAttributes->getTextDecorationDefiningSvgStyleAttributes();
2667 // default is 0
2668 return nullptr;
2671 TextDecoration SvgStyleAttributes::getTextDecoration() const
2673 const SvgStyleAttributes* pDefining = getTextDecorationDefiningSvgStyleAttributes();
2675 if(pDefining)
2677 return pDefining->maTextDecoration;
2679 else
2681 // default is TextDecoration_none
2682 return TextDecoration_none;
2686 TextAnchor SvgStyleAttributes::getTextAnchor() const
2688 if(maTextAnchor != TextAnchor_notset)
2690 return maTextAnchor;
2693 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2695 if(pSvgStyleAttributes)
2697 return pSvgStyleAttributes->getTextAnchor();
2700 // default is TextAnchor_start
2701 return TextAnchor_start;
2704 const basegfx::BColor* SvgStyleAttributes::getColor() const
2706 if(maColor.isSet())
2708 if(maColor.isCurrent())
2710 OSL_ENSURE(false, "Svg error: current color uses current color (!)");
2711 return nullptr;
2713 else if(maColor.isOn())
2715 return &maColor.getBColor();
2718 else
2720 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2722 if(pSvgStyleAttributes)
2724 return pSvgStyleAttributes->getColor();
2728 return nullptr;
2731 OUString const & SvgStyleAttributes::getClipPathXLink() const
2733 return maClipPathXLink;
2736 const SvgClipPathNode* SvgStyleAttributes::accessClipPathXLink() const
2738 if(!mpClipPathXLink)
2740 const OUString aClipPath(getClipPathXLink());
2742 if(!aClipPath.isEmpty())
2744 const_cast< SvgStyleAttributes* >(this)->mpClipPathXLink = dynamic_cast< const SvgClipPathNode* >(mrOwner.getDocument().findSvgNodeById(aClipPath));
2748 return mpClipPathXLink;
2751 OUString SvgStyleAttributes::getMaskXLink() const
2753 if(!maMaskXLink.isEmpty())
2755 return maMaskXLink;
2758 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2760 if(pSvgStyleAttributes && !pSvgStyleAttributes->maMaskXLink.isEmpty())
2762 return pSvgStyleAttributes->getMaskXLink();
2765 return OUString();
2768 const SvgMaskNode* SvgStyleAttributes::accessMaskXLink() const
2770 if(!mpMaskXLink)
2772 const OUString aMask(getMaskXLink());
2774 if(!aMask.isEmpty())
2776 const_cast< SvgStyleAttributes* >(this)->mpMaskXLink = dynamic_cast< const SvgMaskNode* >(mrOwner.getDocument().findSvgNodeById(aMask));
2780 return mpMaskXLink;
2783 OUString SvgStyleAttributes::getMarkerStartXLink() const
2785 if(!maMarkerStartXLink.isEmpty())
2787 return maMarkerStartXLink;
2790 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2792 if(pSvgStyleAttributes)
2794 return pSvgStyleAttributes->getMarkerStartXLink();
2797 return OUString();
2800 const SvgMarkerNode* SvgStyleAttributes::accessMarkerStartXLink() const
2802 if(!mpMarkerStartXLink)
2804 const OUString aMarker(getMarkerStartXLink());
2806 if(!aMarker.isEmpty())
2808 const_cast< SvgStyleAttributes* >(this)->mpMarkerStartXLink = dynamic_cast< const SvgMarkerNode* >(mrOwner.getDocument().findSvgNodeById(getMarkerStartXLink()));
2812 return mpMarkerStartXLink;
2815 OUString SvgStyleAttributes::getMarkerMidXLink() const
2817 if(!maMarkerMidXLink.isEmpty())
2819 return maMarkerMidXLink;
2822 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2824 if(pSvgStyleAttributes)
2826 return pSvgStyleAttributes->getMarkerMidXLink();
2829 return OUString();
2832 const SvgMarkerNode* SvgStyleAttributes::accessMarkerMidXLink() const
2834 if(!mpMarkerMidXLink)
2836 const OUString aMarker(getMarkerMidXLink());
2838 if(!aMarker.isEmpty())
2840 const_cast< SvgStyleAttributes* >(this)->mpMarkerMidXLink = dynamic_cast< const SvgMarkerNode* >(mrOwner.getDocument().findSvgNodeById(getMarkerMidXLink()));
2844 return mpMarkerMidXLink;
2847 OUString SvgStyleAttributes::getMarkerEndXLink() const
2849 if(!maMarkerEndXLink.isEmpty())
2851 return maMarkerEndXLink;
2854 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2856 if(pSvgStyleAttributes)
2858 return pSvgStyleAttributes->getMarkerEndXLink();
2861 return OUString();
2864 const SvgMarkerNode* SvgStyleAttributes::accessMarkerEndXLink() const
2866 if(!mpMarkerEndXLink)
2868 const OUString aMarker(getMarkerEndXLink());
2870 if(!aMarker.isEmpty())
2872 const_cast< SvgStyleAttributes* >(this)->mpMarkerEndXLink = dynamic_cast< const SvgMarkerNode* >(mrOwner.getDocument().findSvgNodeById(getMarkerEndXLink()));
2876 return mpMarkerEndXLink;
2879 SvgNumber SvgStyleAttributes::getBaselineShiftNumber() const
2881 // #122524# Handle Unit_percent realtive to parent BaselineShift
2882 if(Unit_percent == maBaselineShiftNumber.getUnit())
2884 const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
2886 if(pSvgStyleAttributes)
2888 const SvgNumber aParentNumber = pSvgStyleAttributes->getBaselineShiftNumber();
2890 return SvgNumber(
2891 aParentNumber.getNumber() * maBaselineShiftNumber.getNumber() * 0.01,
2892 aParentNumber.getUnit(),
2893 true);
2897 return maBaselineShiftNumber;
2899 } // end of namespace svgreader
2900 } // end of namespace svgio
2902 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */