1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <svgtools.hxx>
21 #include <sal/log.hxx>
22 #include <tools/color.hxx>
23 #include <rtl/math.hxx>
24 #include <o3tl/string_view.hxx>
25 #include <basegfx/matrix/b2dhommatrix.hxx>
26 #include <basegfx/matrix/b2dhommatrixtools.hxx>
27 #include <svgtoken.hxx>
28 #include <unordered_map>
30 namespace svgio::svgreader
32 basegfx::B2DHomMatrix
SvgAspectRatio::createLinearMapping(const basegfx::B2DRange
& rTarget
, const basegfx::B2DRange
& rSource
)
34 basegfx::B2DHomMatrix aRetval
;
35 const double fSWidth(rSource
.getWidth());
36 const double fSHeight(rSource
.getHeight());
37 const bool bNoSWidth(basegfx::fTools::equalZero(fSWidth
));
38 const bool bNoSHeight(basegfx::fTools::equalZero(fSHeight
));
40 // transform from source state to unit range
41 aRetval
.translate(-rSource
.getMinX(), -rSource
.getMinY());
43 (bNoSWidth
? 1.0 : 1.0 / fSWidth
) * rTarget
.getWidth(),
44 (bNoSHeight
? 1.0 : 1.0 / fSHeight
) * rTarget
.getHeight());
46 // transform from unit rage to target range
47 aRetval
.translate(rTarget
.getMinX(), rTarget
.getMinY());
52 basegfx::B2DHomMatrix
SvgAspectRatio::createMapping(const basegfx::B2DRange
& rTarget
, const basegfx::B2DRange
& rSource
) const
54 // removed !isSet() from below. Due to correct defaults in the constructor an instance
55 // of this class is perfectly useful without being set by any importer
56 if(SvgAlign::none
== getSvgAlign())
58 // create linear mapping (default)
59 return createLinearMapping(rTarget
, rSource
);
62 basegfx::B2DHomMatrix aRetval
;
64 const double fSWidth(rSource
.getWidth());
65 const double fSHeight(rSource
.getHeight());
66 const bool bNoSWidth(basegfx::fTools::equalZero(fSWidth
));
67 const bool bNoSHeight(basegfx::fTools::equalZero(fSHeight
));
68 const double fScaleX((bNoSWidth
? 1.0 : 1.0 / fSWidth
) * rTarget
.getWidth());
69 const double fScaleY((bNoSHeight
? 1.0 : 1.0 / fSHeight
) * rTarget
.getHeight());
70 const double fScale(isMeetOrSlice() ? std::min(fScaleX
, fScaleY
) : std::max(fScaleX
, fScaleY
));
72 // remove source translation, apply scale
73 aRetval
.translate(-rSource
.getMinX(), -rSource
.getMinY());
74 aRetval
.scale(fScale
, fScale
);
76 // evaluate horizontal alignment
77 const double fNewWidth(fSWidth
* fScale
);
82 case SvgAlign::xMidYMin
:
83 case SvgAlign::xMidYMid
:
84 case SvgAlign::xMidYMax
:
87 const double fFreeSpace(rTarget
.getWidth() - fNewWidth
);
88 fTransX
= fFreeSpace
* 0.5;
91 case SvgAlign::xMaxYMin
:
92 case SvgAlign::xMaxYMid
:
93 case SvgAlign::xMaxYMax
:
96 const double fFreeSpace(rTarget
.getWidth() - fNewWidth
);
103 // evaluate vertical alignment
104 const double fNewHeight(fSHeight
* fScale
);
107 switch(getSvgAlign())
109 case SvgAlign::xMinYMid
:
110 case SvgAlign::xMidYMid
:
111 case SvgAlign::xMaxYMid
:
114 const double fFreeSpace(rTarget
.getHeight() - fNewHeight
);
115 fTransY
= fFreeSpace
* 0.5;
118 case SvgAlign::xMinYMax
:
119 case SvgAlign::xMidYMax
:
120 case SvgAlign::xMaxYMax
:
123 const double fFreeSpace(rTarget
.getHeight() - fNewHeight
);
124 fTransY
= fFreeSpace
;
130 // add target translation
132 rTarget
.getMinX() + fTransX
,
133 rTarget
.getMinY() + fTransY
);
138 void skip_char(std::u16string_view rCandidate
, sal_Unicode nChar
, sal_Int32
& nPos
, const sal_Int32 nLen
)
140 while(nPos
< nLen
&& nChar
== rCandidate
[nPos
])
146 void skip_char(std::u16string_view rCandidate
, sal_Unicode nCharA
, sal_Unicode nCharB
, sal_Int32
& nPos
, const sal_Int32 nLen
)
148 while(nPos
< nLen
&& (nCharA
== rCandidate
[nPos
] || nCharB
== rCandidate
[nPos
]))
154 void copySign(std::u16string_view rCandidate
, sal_Int32
& nPos
, OUStringBuffer
& rTarget
, const sal_Int32 nLen
)
158 const sal_Unicode
aChar(rCandidate
[nPos
]);
160 if('+' == aChar
|| '-' == aChar
)
162 rTarget
.append(aChar
);
168 void copyNumber(std::u16string_view rCandidate
, sal_Int32
& nPos
, OUStringBuffer
& rTarget
, const sal_Int32 nLen
)
170 bool bOnNumber(true);
172 while(bOnNumber
&& nPos
< nLen
)
174 const sal_Unicode
aChar(rCandidate
[nPos
]);
176 bOnNumber
= ('0' <= aChar
&& '9' >= aChar
) || '.' == aChar
;
180 rTarget
.append(aChar
);
186 void copyHex(std::u16string_view rCandidate
, sal_Int32
& nPos
, OUStringBuffer
& rTarget
, const sal_Int32 nLen
)
190 while(bOnHex
&& nPos
< nLen
)
192 const sal_Unicode
aChar(rCandidate
[nPos
]);
194 bOnHex
= ('0' <= aChar
&& '9' >= aChar
)
195 || ('A' <= aChar
&& 'F' >= aChar
)
196 || ('a' <= aChar
&& 'f' >= aChar
);
200 rTarget
.append(aChar
);
206 void copyString(std::u16string_view rCandidate
, sal_Int32
& nPos
, OUStringBuffer
& rTarget
, const sal_Int32 nLen
)
210 while(bOnChar
&& nPos
< nLen
)
212 const sal_Unicode
aChar(rCandidate
[nPos
]);
214 bOnChar
= ('a' <= aChar
&& 'z' >= aChar
)
215 || ('A' <= aChar
&& 'Z' >= aChar
)
220 rTarget
.append(aChar
);
226 void copyToLimiter(std::u16string_view rCandidate
, sal_Unicode nLimiter
, sal_Int32
& nPos
, OUStringBuffer
& rTarget
, const sal_Int32 nLen
)
228 while(nPos
< nLen
&& nLimiter
!= rCandidate
[nPos
])
230 rTarget
.append(rCandidate
[nPos
]);
235 bool readNumber(std::u16string_view rCandidate
, sal_Int32
& nPos
, double& fNum
, const sal_Int32 nLen
)
241 copySign(rCandidate
, nPos
, aNum
, nLen
);
242 copyNumber(rCandidate
, nPos
, aNum
, nLen
);
246 const sal_Unicode
aChar(rCandidate
[nPos
]);
248 if('e' == aChar
|| 'E' == aChar
)
250 // try to read exponential number, but be careful. I had
251 // a case where dx="2em" was used, thus the 'e' was consumed
252 // by error. First try if there are numbers after the 'e',
253 // safe current state
255 const OUStringBuffer
aNum2(aNum
);
256 const sal_Int32
nPosAfterE(nPos
);
259 copySign(rCandidate
, nPos
, aNum
, nLen
);
260 copyNumber(rCandidate
, nPos
, aNum
, nLen
);
262 if(nPosAfterE
== nPos
)
264 // no number after 'e', go back. Do not
265 // return false, it's still a valid integer number
274 rtl_math_ConversionStatus eStatus
;
276 fNum
= rtl::math::stringToDouble(
280 return eStatus
== rtl_math_ConversionStatus_Ok
;
287 SvgUnit
readUnit(std::u16string_view rCandidate
, sal_Int32
& nPos
, const sal_Int32 nLen
)
289 SvgUnit
aRetval(SvgUnit::px
);
293 const sal_Unicode
aCharA(rCandidate
[nPos
]);
297 const sal_Unicode
aCharB(rCandidate
[nPos
+ 1]);
298 bool bTwoCharValid(false);
306 // 'em' Relative to current font size
307 aRetval
= SvgUnit::em
;
308 bTwoCharValid
= true;
310 else if('x' == aCharB
)
312 // 'ex' Relative to current font x-height
313 aRetval
= SvgUnit::ex
;
314 bTwoCharValid
= true;
322 // 'px' UserUnit (default)
323 bTwoCharValid
= true;
325 else if('t' == aCharB
)
328 aRetval
= SvgUnit::pt
;
329 bTwoCharValid
= true;
331 else if('c' == aCharB
)
334 aRetval
= SvgUnit::pc
;
335 bTwoCharValid
= true;
343 // 'in' == 96 px, since CSS 2.1
344 aRetval
= SvgUnit::in
;
345 bTwoCharValid
= true;
353 // 'cm' == 37.79527559 px
354 aRetval
= SvgUnit::cm
;
355 bTwoCharValid
= true;
363 // 'mm' == 3.779528 px
364 aRetval
= SvgUnit::mm
;
365 bTwoCharValid
= true;
380 // percent used, relative to current
382 aRetval
= SvgUnit::percent
;
390 bool readNumberAndUnit(std::u16string_view rCandidate
, sal_Int32
& nPos
, SvgNumber
& aNum
, const sal_Int32 nLen
)
394 if(readNumber(rCandidate
, nPos
, fNum
, nLen
))
396 skip_char(rCandidate
, ' ', nPos
, nLen
);
397 aNum
= SvgNumber(fNum
, readUnit(rCandidate
, nPos
, nLen
));
405 bool readAngle(std::u16string_view rCandidate
, sal_Int32
& nPos
, double& fAngle
, const sal_Int32 nLen
)
407 if(readNumber(rCandidate
, nPos
, fAngle
, nLen
))
409 skip_char(rCandidate
, ' ', nPos
, nLen
);
411 enum class DegreeType
416 } aType(DegreeType::deg
); // degrees is default
420 const sal_Unicode
aChar(rCandidate
[nPos
]);
421 static constexpr std::u16string_view aStrGrad
= u
"grad";
422 static constexpr std::u16string_view aStrRad
= u
"rad";
429 if(o3tl::matchIgnoreAsciiCase(rCandidate
, aStrGrad
, nPos
))
432 nPos
+= aStrGrad
.size();
433 aType
= DegreeType::grad
;
440 if(o3tl::matchIgnoreAsciiCase(rCandidate
, aStrRad
, nPos
))
443 nPos
+= aStrRad
.size();
444 aType
= DegreeType::rad
;
451 // convert to radians
452 if (DegreeType::deg
== aType
)
454 fAngle
= basegfx::deg2rad(fAngle
);
456 else if (DegreeType::grad
== aType
)
458 // looks like 100 grad is 90 degrees
459 fAngle
*= M_PI
/ 200.0;
468 sal_Int32
read_hex(sal_Unicode nChar
)
470 if(nChar
>= '0' && nChar
<= '9')
474 else if(nChar
>= 'A' && nChar
<= 'F')
476 return 10 + sal_Int32(nChar
- u
'A');
478 else if(nChar
>= 'a' && nChar
<= 'f')
480 return 10 + sal_Int32(nChar
- u
'a');
489 bool match_colorKeyword(basegfx::BColor
& rColor
, const OUString
& rName
)
491 typedef std::unordered_map
< OUString
, Color
> ColorTokenMapper
;
492 typedef std::pair
< OUString
, Color
> ColorTokenValueType
;
493 static const ColorTokenMapper aColorTokenMapperList
{
494 { ColorTokenValueType(OUString("aliceblue"), Color(240, 248, 255)) },
495 { ColorTokenValueType(OUString("antiquewhite"), Color(250, 235, 215) ) },
496 { ColorTokenValueType(OUString("aqua"), Color( 0, 255, 255) ) },
497 { ColorTokenValueType(OUString("aquamarine"), Color(127, 255, 212) ) },
498 { ColorTokenValueType(OUString("azure"), Color(240, 255, 255) ) },
499 { ColorTokenValueType(OUString("beige"), Color(245, 245, 220) ) },
500 { ColorTokenValueType(OUString("bisque"), Color(255, 228, 196) ) },
501 { ColorTokenValueType(OUString("black"), Color( 0, 0, 0) ) },
502 { ColorTokenValueType(OUString("blanchedalmond"), Color(255, 235, 205) ) },
503 { ColorTokenValueType(OUString("blue"), Color( 0, 0, 255) ) },
504 { ColorTokenValueType(OUString("blueviolet"), Color(138, 43, 226) ) },
505 { ColorTokenValueType(OUString("brown"), Color(165, 42, 42) ) },
506 { ColorTokenValueType(OUString("burlywood"), Color(222, 184, 135) ) },
507 { ColorTokenValueType(OUString("cadetblue"), Color( 95, 158, 160) ) },
508 { ColorTokenValueType(OUString("chartreuse"), Color(127, 255, 0) ) },
509 { ColorTokenValueType(OUString("chocolate"), Color(210, 105, 30) ) },
510 { ColorTokenValueType(OUString("coral"), Color(255, 127, 80) ) },
511 { ColorTokenValueType(OUString("cornflowerblue"), Color(100, 149, 237) ) },
512 { ColorTokenValueType(OUString("cornsilk"), Color(255, 248, 220) ) },
513 { ColorTokenValueType(OUString("crimson"), Color(220, 20, 60) ) },
514 { ColorTokenValueType(OUString("cyan"), Color( 0, 255, 255) ) },
515 { ColorTokenValueType(OUString("darkblue"), Color( 0, 0, 139) ) },
516 { ColorTokenValueType(OUString("darkcyan"), Color( 0, 139, 139) ) },
517 { ColorTokenValueType(OUString("darkgoldenrod"), Color(184, 134, 11) ) },
518 { ColorTokenValueType(OUString("darkgray"), Color(169, 169, 169) ) },
519 { ColorTokenValueType(OUString("darkgreen"), Color( 0, 100, 0) ) },
520 { ColorTokenValueType(OUString("darkgrey"), Color(169, 169, 169) ) },
521 { ColorTokenValueType(OUString("darkkhaki"), Color(189, 183, 107) ) },
522 { ColorTokenValueType(OUString("darkmagenta"), Color(139, 0, 139) ) },
523 { ColorTokenValueType(OUString("darkolivegreen"), Color( 85, 107, 47) ) },
524 { ColorTokenValueType(OUString("darkorange"), Color(255, 140, 0) ) },
525 { ColorTokenValueType(OUString("darkorchid"), Color(153, 50, 204) ) },
526 { ColorTokenValueType(OUString("darkred"), Color(139, 0, 0) ) },
527 { ColorTokenValueType(OUString("darksalmon"), Color(233, 150, 122) ) },
528 { ColorTokenValueType(OUString("darkseagreen"), Color(143, 188, 143) ) },
529 { ColorTokenValueType(OUString("darkslateblue"), Color( 72, 61, 139) ) },
530 { ColorTokenValueType(OUString("darkslategray"), Color( 47, 79, 79) ) },
531 { ColorTokenValueType(OUString("darkslategrey"), Color( 47, 79, 79) ) },
532 { ColorTokenValueType(OUString("darkturquoise"), Color( 0, 206, 209) ) },
533 { ColorTokenValueType(OUString("darkviolet"), Color(148, 0, 211) ) },
534 { ColorTokenValueType(OUString("deeppink"), Color(255, 20, 147) ) },
535 { ColorTokenValueType(OUString("deepskyblue"), Color( 0, 191, 255) ) },
536 { ColorTokenValueType(OUString("dimgray"), Color(105, 105, 105) ) },
537 { ColorTokenValueType(OUString("dimgrey"), Color(105, 105, 105) ) },
538 { ColorTokenValueType(OUString("dodgerblue"), Color( 30, 144, 255) ) },
539 { ColorTokenValueType(OUString("firebrick"), Color(178, 34, 34) ) },
540 { ColorTokenValueType(OUString("floralwhite"), Color(255, 250, 240) ) },
541 { ColorTokenValueType(OUString("forestgreen"), Color( 34, 139, 34) ) },
542 { ColorTokenValueType(OUString("fuchsia"), Color(255, 0, 255) ) },
543 { ColorTokenValueType(OUString("gainsboro"), Color(220, 220, 220) ) },
544 { ColorTokenValueType(OUString("ghostwhite"), Color(248, 248, 255) ) },
545 { ColorTokenValueType(OUString("gold"), Color(255, 215, 0) ) },
546 { ColorTokenValueType(OUString("goldenrod"), Color(218, 165, 32) ) },
547 { ColorTokenValueType(OUString("gray"), Color(128, 128, 128) ) },
548 { ColorTokenValueType(OUString("grey"), Color(128, 128, 128) ) },
549 { ColorTokenValueType(OUString("green"), Color(0, 128, 0) ) },
550 { ColorTokenValueType(OUString("greenyellow"), Color(173, 255, 47) ) },
551 { ColorTokenValueType(OUString("honeydew"), Color(240, 255, 240) ) },
552 { ColorTokenValueType(OUString("hotpink"), Color(255, 105, 180) ) },
553 { ColorTokenValueType(OUString("indianred"), Color(205, 92, 92) ) },
554 { ColorTokenValueType(OUString("indigo"), Color( 75, 0, 130) ) },
555 { ColorTokenValueType(OUString("ivory"), Color(255, 255, 240) ) },
556 { ColorTokenValueType(OUString("khaki"), Color(240, 230, 140) ) },
557 { ColorTokenValueType(OUString("lavender"), Color(230, 230, 250) ) },
558 { ColorTokenValueType(OUString("lavenderblush"), Color(255, 240, 245) ) },
559 { ColorTokenValueType(OUString("lawngreen"), Color(124, 252, 0) ) },
560 { ColorTokenValueType(OUString("lemonchiffon"), Color(255, 250, 205) ) },
561 { ColorTokenValueType(OUString("lightblue"), Color(173, 216, 230) ) },
562 { ColorTokenValueType(OUString("lightcoral"), Color(240, 128, 128) ) },
563 { ColorTokenValueType(OUString("lightcyan"), Color(224, 255, 255) ) },
564 { ColorTokenValueType(OUString("lightgoldenrodyellow"), Color(250, 250, 210) ) },
565 { ColorTokenValueType(OUString("lightgray"), Color(211, 211, 211) ) },
566 { ColorTokenValueType(OUString("lightgreen"), Color(144, 238, 144) ) },
567 { ColorTokenValueType(OUString("lightgrey"), Color(211, 211, 211) ) },
568 { ColorTokenValueType(OUString("lightpink"), Color(255, 182, 193) ) },
569 { ColorTokenValueType(OUString("lightsalmon"), Color(255, 160, 122) ) },
570 { ColorTokenValueType(OUString("lightseagreen"), Color( 32, 178, 170) ) },
571 { ColorTokenValueType(OUString("lightskyblue"), Color(135, 206, 250) ) },
572 { ColorTokenValueType(OUString("lightslategray"), Color(119, 136, 153) ) },
573 { ColorTokenValueType(OUString("lightslategrey"), Color(119, 136, 153) ) },
574 { ColorTokenValueType(OUString("lightsteelblue"), Color(176, 196, 222) ) },
575 { ColorTokenValueType(OUString("lightyellow"), Color(255, 255, 224) ) },
576 { ColorTokenValueType(OUString("lime"), Color( 0, 255, 0) ) },
577 { ColorTokenValueType(OUString("limegreen"), Color( 50, 205, 50) ) },
578 { ColorTokenValueType(OUString("linen"), Color(250, 240, 230) ) },
579 { ColorTokenValueType(OUString("magenta"), Color(255, 0, 255) ) },
580 { ColorTokenValueType(OUString("maroon"), Color(128, 0, 0) ) },
581 { ColorTokenValueType(OUString("mediumaquamarine"), Color(102, 205, 170) ) },
582 { ColorTokenValueType(OUString("mediumblue"), Color( 0, 0, 205) ) },
583 { ColorTokenValueType(OUString("mediumorchid"), Color(186, 85, 211) ) },
584 { ColorTokenValueType(OUString("mediumpurple"), Color(147, 112, 219) ) },
585 { ColorTokenValueType(OUString("mediumseagreen"), Color( 60, 179, 113) ) },
586 { ColorTokenValueType(OUString("mediumslateblue"), Color(123, 104, 238) ) },
587 { ColorTokenValueType(OUString("mediumspringgreen"), Color( 0, 250, 154) ) },
588 { ColorTokenValueType(OUString("mediumturquoise"), Color( 72, 209, 204) ) },
589 { ColorTokenValueType(OUString("mediumvioletred"), Color(199, 21, 133) ) },
590 { ColorTokenValueType(OUString("midnightblue"), Color( 25, 25, 112) ) },
591 { ColorTokenValueType(OUString("mintcream"), Color(245, 255, 250) ) },
592 { ColorTokenValueType(OUString("mistyrose"), Color(255, 228, 225) ) },
593 { ColorTokenValueType(OUString("moccasin"), Color(255, 228, 181) ) },
594 { ColorTokenValueType(OUString("navajowhite"), Color(255, 222, 173) ) },
595 { ColorTokenValueType(OUString("navy"), Color( 0, 0, 128) ) },
596 { ColorTokenValueType(OUString("oldlace"), Color(253, 245, 230) ) },
597 { ColorTokenValueType(OUString("olive"), Color(128, 128, 0) ) },
598 { ColorTokenValueType(OUString("olivedrab"), Color(107, 142, 35) ) },
599 { ColorTokenValueType(OUString("orange"), Color(255, 165, 0) ) },
600 { ColorTokenValueType(OUString("orangered"), Color(255, 69, 0) ) },
601 { ColorTokenValueType(OUString("orchid"), Color(218, 112, 214) ) },
602 { ColorTokenValueType(OUString("palegoldenrod"), Color(238, 232, 170) ) },
603 { ColorTokenValueType(OUString("palegreen"), Color(152, 251, 152) ) },
604 { ColorTokenValueType(OUString("paleturquoise"), Color(175, 238, 238) ) },
605 { ColorTokenValueType(OUString("palevioletred"), Color(219, 112, 147) ) },
606 { ColorTokenValueType(OUString("papayawhip"), Color(255, 239, 213) ) },
607 { ColorTokenValueType(OUString("peachpuff"), Color(255, 218, 185) ) },
608 { ColorTokenValueType(OUString("peru"), Color(205, 133, 63) ) },
609 { ColorTokenValueType(OUString("pink"), Color(255, 192, 203) ) },
610 { ColorTokenValueType(OUString("plum"), Color(221, 160, 221) ) },
611 { ColorTokenValueType(OUString("powderblue"), Color(176, 224, 230) ) },
612 { ColorTokenValueType(OUString("purple"), Color(128, 0, 128) ) },
613 { ColorTokenValueType(OUString("red"), Color(255, 0, 0) ) },
614 { ColorTokenValueType(OUString("rosybrown"), Color(188, 143, 143) ) },
615 { ColorTokenValueType(OUString("royalblue"), Color( 65, 105, 225) ) },
616 { ColorTokenValueType(OUString("saddlebrown"), Color(139, 69, 19) ) },
617 { ColorTokenValueType(OUString("salmon"), Color(250, 128, 114) ) },
618 { ColorTokenValueType(OUString("sandybrown"), Color(244, 164, 96) ) },
619 { ColorTokenValueType(OUString("seagreen"), Color( 46, 139, 87) ) },
620 { ColorTokenValueType(OUString("seashell"), Color(255, 245, 238) ) },
621 { ColorTokenValueType(OUString("sienna"), Color(160, 82, 45) ) },
622 { ColorTokenValueType(OUString("silver"), Color(192, 192, 192) ) },
623 { ColorTokenValueType(OUString("skyblue"), Color(135, 206, 235) ) },
624 { ColorTokenValueType(OUString("slateblue"), Color(106, 90, 205) ) },
625 { ColorTokenValueType(OUString("slategray"), Color(112, 128, 144) ) },
626 { ColorTokenValueType(OUString("slategrey"), Color(112, 128, 144) ) },
627 { ColorTokenValueType(OUString("snow"), Color(255, 250, 250) ) },
628 { ColorTokenValueType(OUString("springgreen"), Color( 0, 255, 127) ) },
629 { ColorTokenValueType(OUString("steelblue"), Color( 70, 130, 180) ) },
630 { ColorTokenValueType(OUString("tan"), Color(210, 180, 140) ) },
631 { ColorTokenValueType(OUString("teal"), Color( 0, 128, 128) ) },
632 { ColorTokenValueType(OUString("thistle"), Color(216, 191, 216) ) },
633 { ColorTokenValueType(OUString("tomato"), Color(255, 99, 71) ) },
634 { ColorTokenValueType(OUString("turquoise"), Color( 64, 224, 208) ) },
635 { ColorTokenValueType(OUString("violet"), Color(238, 130, 238) ) },
636 { ColorTokenValueType(OUString("wheat"), Color(245, 222, 179) ) },
637 { ColorTokenValueType(OUString("white"), Color(255, 255, 255) ) },
638 { ColorTokenValueType(OUString("whitesmoke"), Color(245, 245, 245) ) },
639 { ColorTokenValueType(OUString("yellow"), Color(255, 255, 0) ) },
640 { ColorTokenValueType(OUString("yellowgreen"), Color(154, 205, 50) ) },
643 ColorTokenMapper::const_iterator
aResult(aColorTokenMapperList
.find(rName
.toAsciiLowerCase().trim()));
645 if(aResult
== aColorTokenMapperList
.end())
651 rColor
= aResult
->second
.getBColor();
656 bool read_color(const OUString
& rCandidate
, basegfx::BColor
& rColor
, SvgNumber
& rOpacity
)
658 const sal_Int32
nLen(rCandidate
.getLength());
662 const sal_Unicode
aChar(rCandidate
[0]);
663 const double fFactor(1.0 / 255.0);
671 copyHex(rCandidate
, nPos
, aNum
, nLen
);
672 const sal_Int32
nLength(aNum
.getLength());
676 const sal_Int32
nR(read_hex(aNum
[0]));
677 const sal_Int32
nG(read_hex(aNum
[1]));
678 const sal_Int32
nB(read_hex(aNum
[2]));
680 rColor
.setRed((nR
| (nR
<< 4)) * fFactor
);
681 rColor
.setGreen((nG
| (nG
<< 4)) * fFactor
);
682 rColor
.setBlue((nB
| (nB
<< 4)) * fFactor
);
686 else if(6 == nLength
)
688 const sal_Int32
nR1(read_hex(aNum
[0]));
689 const sal_Int32
nR2(read_hex(aNum
[1]));
690 const sal_Int32
nG1(read_hex(aNum
[2]));
691 const sal_Int32
nG2(read_hex(aNum
[3]));
692 const sal_Int32
nB1(read_hex(aNum
[4]));
693 const sal_Int32
nB2(read_hex(aNum
[5]));
695 rColor
.setRed((nR2
| (nR1
<< 4)) * fFactor
);
696 rColor
.setGreen((nG2
| (nG1
<< 4)) * fFactor
);
697 rColor
.setBlue((nB2
| (nB1
<< 4)) * fFactor
);
704 static const char aStrRgb
[] = "rgb";
706 if(rCandidate
.matchIgnoreAsciiCase(aStrRgb
, 0))
708 // rgb/rgba definition
709 sal_Int32
nPos(strlen(aStrRgb
));
710 bool bIsRGBA
= false;
712 if('a' == rCandidate
[nPos
])
714 //Delete the 'a' from 'rbga'
715 skip_char(rCandidate
, 'a', nPos
, nPos
+ 1);
719 skip_char(rCandidate
, ' ', '(', nPos
, nLen
);
722 if(readNumber(rCandidate
, nPos
, fR
, nLen
))
724 skip_char(rCandidate
, ' ', nPos
, nLen
);
728 const sal_Unicode
aPercentChar(rCandidate
[nPos
]);
729 const bool bIsPercent('%' == aPercentChar
);
734 skip_char(rCandidate
, '%', nPos
, nLen
);
737 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
739 if(readNumber(rCandidate
, nPos
, fG
, nLen
))
745 skip_char(rCandidate
, '%', nPos
, nLen
);
748 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
750 if(readNumber(rCandidate
, nPos
, fB
, nLen
))
756 skip_char(rCandidate
, '%', nPos
, nLen
);
759 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
761 if(readNumber(rCandidate
, nPos
, fA
, nLen
))
765 const double fFac(bIsPercent
? 0.01 : 1);
766 rOpacity
= SvgNumber(fA
* fFac
);
770 skip_char(rCandidate
, '%', nPos
, nLen
);
779 const double fFac(bIsPercent
? 0.01 : fFactor
);
781 rColor
.setRed(fR
* fFac
);
782 rColor
.setGreen(fG
* fFac
);
783 rColor
.setBlue(fB
* fFac
);
785 skip_char(rCandidate
, ' ', ')', nPos
, nLen
);
795 if(match_colorKeyword(rColor
, rCandidate
))
806 basegfx::B2DRange
readViewBox(std::u16string_view rCandidate
, InfoProvider
const & rInfoProvider
)
808 const sal_Int32
nLen(rCandidate
.size());
814 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
816 if(readNumberAndUnit(rCandidate
, nPos
, aMinX
, nLen
))
819 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
821 if(readNumberAndUnit(rCandidate
, nPos
, aMinY
, nLen
))
824 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
826 if(readNumberAndUnit(rCandidate
, nPos
, aWidth
, nLen
))
829 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
831 if(readNumberAndUnit(rCandidate
, nPos
, aHeight
, nLen
))
833 double fX(aMinX
.solve(rInfoProvider
, NumberType::xcoordinate
));
834 double fY(aMinY
.solve(rInfoProvider
, NumberType::ycoordinate
));
835 double fW(aWidth
.solve(rInfoProvider
, NumberType::xcoordinate
));
836 double fH(aHeight
.solve(rInfoProvider
, NumberType::ycoordinate
));
837 return basegfx::B2DRange(fX
,fY
,fX
+fW
,fY
+fH
);
844 return basegfx::B2DRange();
847 basegfx::B2DHomMatrix
readTransform(std::u16string_view rCandidate
, InfoProvider
const & rInfoProvider
)
849 basegfx::B2DHomMatrix aMatrix
;
850 const sal_Int32
nLen(rCandidate
.size());
855 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
859 const sal_Unicode
aChar(rCandidate
[nPos
]);
860 const sal_Int32
nInitPos(nPos
);
861 static constexpr std::u16string_view aStrMatrix
= u
"matrix";
862 static constexpr std::u16string_view aStrTranslate
= u
"translate";
863 static constexpr std::u16string_view aStrScale
= u
"scale";
864 static constexpr std::u16string_view aStrRotate
= u
"rotate";
865 static constexpr std::u16string_view aStrSkewX
= u
"skewX";
866 static constexpr std::u16string_view aStrSkewY
= u
"skewY";
872 if(o3tl::matchIgnoreAsciiCase(rCandidate
, aStrMatrix
, nPos
))
875 nPos
+= aStrMatrix
.size();
876 skip_char(rCandidate
, ' ', '(', nPos
, nLen
);
878 basegfx::B2DHomMatrix aNew
;
880 if(readNumberAndUnit(rCandidate
, nPos
, aVal
, nLen
))
882 aNew
.set(0, 0, aVal
.solve(rInfoProvider
)); // Element A
883 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
885 if(readNumberAndUnit(rCandidate
, nPos
, aVal
, nLen
))
887 aNew
.set(1, 0, aVal
.solve(rInfoProvider
)); // Element B
888 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
890 if(readNumberAndUnit(rCandidate
, nPos
, aVal
, nLen
))
892 aNew
.set(0, 1, aVal
.solve(rInfoProvider
)); // Element C
893 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
895 if(readNumberAndUnit(rCandidate
, nPos
, aVal
, nLen
))
897 aNew
.set(1, 1, aVal
.solve(rInfoProvider
)); // Element D
898 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
900 if(readNumberAndUnit(rCandidate
, nPos
, aVal
, nLen
))
902 aNew
.set(0, 2, aVal
.solve(rInfoProvider
, NumberType::xcoordinate
)); // Element E
903 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
905 if(readNumberAndUnit(rCandidate
, nPos
, aVal
, nLen
))
907 aNew
.set(1, 2, aVal
.solve(rInfoProvider
, NumberType::ycoordinate
)); // Element F
908 skip_char(rCandidate
, ' ', ')', nPos
, nLen
);
909 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
911 // caution: String is evaluated from left to right, but matrix multiplication
912 // in SVG is right to left, so put the new transformation before the current
913 // one by multiplicating from the right side
914 aMatrix
= aMatrix
* aNew
;
926 if(o3tl::matchIgnoreAsciiCase(rCandidate
, aStrTranslate
, nPos
))
929 nPos
+= aStrTranslate
.size();
930 skip_char(rCandidate
, ' ', '(', nPos
, nLen
);
933 if(readNumberAndUnit(rCandidate
, nPos
, aTransX
, nLen
))
935 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
937 readNumberAndUnit(rCandidate
, nPos
, aTransY
, nLen
);
938 skip_char(rCandidate
, ' ', ')', nPos
, nLen
);
939 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
941 aMatrix
= aMatrix
* basegfx::utils::createTranslateB2DHomMatrix(
942 aTransX
.solve(rInfoProvider
, NumberType::xcoordinate
),
943 aTransY
.solve(rInfoProvider
, NumberType::ycoordinate
));
950 if(o3tl::matchIgnoreAsciiCase(rCandidate
, aStrScale
, nPos
))
953 nPos
+= aStrScale
.size();
954 skip_char(rCandidate
, ' ', '(', nPos
, nLen
);
957 if(readNumberAndUnit(rCandidate
, nPos
, aScaleX
, nLen
))
959 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
960 SvgNumber
aScaleY(aScaleX
);
961 readNumberAndUnit(rCandidate
, nPos
, aScaleY
, nLen
);
962 skip_char(rCandidate
, ' ', ')', nPos
, nLen
);
963 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
965 aMatrix
= aMatrix
* basegfx::utils::createScaleB2DHomMatrix(
966 aScaleX
.solve(rInfoProvider
),
967 aScaleY
.solve(rInfoProvider
));
970 else if(o3tl::matchIgnoreAsciiCase(rCandidate
, aStrSkewX
, nPos
))
973 nPos
+= aStrSkewX
.size();
974 skip_char(rCandidate
, ' ', '(', nPos
, nLen
);
977 if(readAngle(rCandidate
, nPos
, fSkewX
, nLen
))
979 skip_char(rCandidate
, ' ', ')', nPos
, nLen
);
980 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
982 aMatrix
= aMatrix
* basegfx::utils::createShearXB2DHomMatrix(tan(fSkewX
));
985 else if(o3tl::matchIgnoreAsciiCase(rCandidate
, aStrSkewY
, nPos
))
988 nPos
+= aStrSkewY
.size();
989 skip_char(rCandidate
, ' ', '(', nPos
, nLen
);
992 if(readAngle(rCandidate
, nPos
, fSkewY
, nLen
))
994 skip_char(rCandidate
, ' ', ')', nPos
, nLen
);
995 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
997 aMatrix
= aMatrix
* basegfx::utils::createShearYB2DHomMatrix(tan(fSkewY
));
1004 if(o3tl::matchIgnoreAsciiCase(rCandidate
, aStrRotate
, nPos
))
1007 nPos
+= aStrRotate
.size();
1008 skip_char(rCandidate
, ' ', '(', nPos
, nLen
);
1011 if(readAngle(rCandidate
, nPos
, fAngle
, nLen
))
1013 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
1015 readNumberAndUnit(rCandidate
, nPos
, aX
, nLen
);
1016 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
1018 readNumberAndUnit(rCandidate
, nPos
, aY
, nLen
);
1019 skip_char(rCandidate
, ' ', ')', nPos
, nLen
);
1020 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
1022 const double fX(aX
.isSet() ? aX
.solve(rInfoProvider
, NumberType::xcoordinate
) : 0.0);
1023 const double fY(aY
.isSet() ? aY
.solve(rInfoProvider
, NumberType::ycoordinate
) : 0.0);
1025 if(!basegfx::fTools::equalZero(fX
) || !basegfx::fTools::equalZero(fY
))
1027 // rotate around point
1028 aMatrix
= aMatrix
* basegfx::utils::createRotateAroundPoint(fX
, fY
, fAngle
);
1033 aMatrix
= aMatrix
* basegfx::utils::createRotateB2DHomMatrix(fAngle
);
1041 if(nInitPos
== nPos
)
1043 SAL_WARN("svgio", "Could not interpret on current position (!)");
1052 bool readSingleNumber(std::u16string_view rCandidate
, SvgNumber
& aNum
)
1054 const sal_Int32
nLen(rCandidate
.size());
1057 return readNumberAndUnit(rCandidate
, nPos
, aNum
, nLen
);
1060 bool readLocalLink(std::u16string_view rCandidate
, OUString
& rURL
)
1063 const sal_Int32
nLen(rCandidate
.size());
1065 skip_char(rCandidate
, ' ', nPos
, nLen
);
1067 if (nLen
&& nPos
< nLen
&& '#' == rCandidate
[nPos
])
1070 rURL
= rCandidate
.substr(nPos
);
1078 bool readLocalUrl(const OUString
& rCandidate
, OUString
& rURL
)
1080 static const char aStrUrl
[] = "url(";
1082 if(rCandidate
.startsWithIgnoreAsciiCase(aStrUrl
))
1084 const sal_Int32
nLen(rCandidate
.getLength());
1085 sal_Int32
nPos(strlen(aStrUrl
));
1086 sal_Unicode
aLimiter(')');
1088 skip_char(rCandidate
, ' ', nPos
, nLen
);
1090 if('"' == rCandidate
[nPos
])
1095 else if('\'' == rCandidate
[nPos
])
1101 skip_char(rCandidate
, ' ', nPos
, nLen
);
1102 skip_char(rCandidate
, '#', nPos
, nPos
+ 1);
1103 OUStringBuffer aTokenValue
;
1105 copyToLimiter(rCandidate
, aLimiter
, nPos
, aTokenValue
, nLen
);
1107 rURL
= aTokenValue
.makeStringAndClear();
1115 bool readSvgPaint(const OUString
& rCandidate
, SvgPaint
& rSvgPaint
,
1116 OUString
& rURL
, SvgNumber
& rOpacity
)
1118 if( !rCandidate
.isEmpty() )
1120 basegfx::BColor aColor
;
1122 if(read_color(rCandidate
, aColor
, rOpacity
))
1124 rSvgPaint
= SvgPaint(aColor
, true, true);
1129 if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(rCandidate
), u
"none"))
1131 rSvgPaint
= SvgPaint(aColor
, true, false, false);
1134 else if(readLocalUrl(rCandidate
, rURL
))
1136 /// Url is copied to rURL, but needs to be solved outside this helper
1139 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(rCandidate
), u
"currentColor"))
1141 rSvgPaint
= SvgPaint(aColor
, true, true, true);
1150 bool readSvgNumberVector(std::u16string_view rCandidate
, SvgNumberVector
& rSvgNumberVector
)
1152 const sal_Int32
nLen(rCandidate
.size());
1153 rSvgNumberVector
.clear();
1159 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
1161 while(readNumberAndUnit(rCandidate
, nPos
, aNum
, nLen
))
1163 rSvgNumberVector
.push_back(aNum
);
1164 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
1167 return !rSvgNumberVector
.empty();
1173 SvgAspectRatio
readSvgAspectRatio(std::u16string_view rCandidate
)
1175 const sal_Int32
nLen(rCandidate
.size());
1180 SvgAlign
aSvgAlign(SvgAlign::xMidYMid
);
1181 bool bMeetOrSlice(true);
1182 bool bChanged(false);
1186 const sal_Int32
nInitPos(nPos
);
1187 skip_char(rCandidate
, ' ', nPos
, nLen
);
1188 OUStringBuffer aTokenName
;
1189 copyString(rCandidate
, nPos
, aTokenName
, nLen
);
1191 if(!aTokenName
.isEmpty())
1193 switch(StrToSVGToken(aTokenName
.makeStringAndClear(), false))
1195 case SVGToken::Defer
:
1200 case SVGToken::None
:
1202 aSvgAlign
= SvgAlign::none
;
1206 case SVGToken::XMinYMin
:
1208 aSvgAlign
= SvgAlign::xMinYMin
;
1212 case SVGToken::XMidYMin
:
1214 aSvgAlign
= SvgAlign::xMidYMin
;
1218 case SVGToken::XMaxYMin
:
1220 aSvgAlign
= SvgAlign::xMaxYMin
;
1224 case SVGToken::XMinYMid
:
1226 aSvgAlign
= SvgAlign::xMinYMid
;
1230 case SVGToken::XMidYMid
:
1232 aSvgAlign
= SvgAlign::xMidYMid
;
1236 case SVGToken::XMaxYMid
:
1238 aSvgAlign
= SvgAlign::xMaxYMid
;
1242 case SVGToken::XMinYMax
:
1244 aSvgAlign
= SvgAlign::xMinYMax
;
1248 case SVGToken::XMidYMax
:
1250 aSvgAlign
= SvgAlign::xMidYMax
;
1254 case SVGToken::XMaxYMax
:
1256 aSvgAlign
= SvgAlign::xMaxYMax
;
1260 case SVGToken::Meet
:
1262 bMeetOrSlice
= true;
1266 case SVGToken::Slice
:
1268 bMeetOrSlice
= false;
1279 if(nInitPos
== nPos
)
1281 SAL_WARN("svgio", "Could not interpret on current position (!)");
1288 return SvgAspectRatio(aSvgAlign
, bMeetOrSlice
);
1292 return SvgAspectRatio();
1295 bool readSvgStringVector(std::u16string_view rCandidate
, SvgStringVector
& rSvgStringVector
)
1297 rSvgStringVector
.clear();
1298 const sal_Int32
nLen(rCandidate
.size());
1303 OUStringBuffer aTokenValue
;
1304 skip_char(rCandidate
, ' ', ',', nPos
, nLen
);
1308 copyToLimiter(rCandidate
, ',', nPos
, aTokenValue
, nLen
);
1309 skip_char(rCandidate
, ',', ' ', nPos
, nLen
);
1310 const OUString aString
= aTokenValue
.makeStringAndClear();
1312 if(!aString
.isEmpty())
1314 rSvgStringVector
.push_back(aString
);
1319 return !rSvgStringVector
.empty();
1322 void readImageLink(const OUString
& rCandidate
, OUString
& rXLink
, OUString
& rUrl
, OUString
& rMimeType
, OUString
& rData
)
1329 if(!readLocalLink(rCandidate
, rXLink
))
1331 static const char aStrData
[] = "data:";
1333 if(rCandidate
.matchIgnoreAsciiCase(aStrData
, 0))
1336 sal_Int32
nPos(strlen(aStrData
));
1337 sal_Int32
nLen(rCandidate
.getLength());
1338 OUStringBuffer aBuffer
;
1341 skip_char(rCandidate
, ' ', nPos
, nLen
);
1342 copyToLimiter(rCandidate
, ';', nPos
, aBuffer
, nLen
);
1343 skip_char(rCandidate
, ' ', ';', nPos
, nLen
);
1344 rMimeType
= aBuffer
.makeStringAndClear();
1346 if(!rMimeType
.isEmpty() && nPos
< nLen
)
1348 if(rMimeType
.startsWith("image"))
1351 std::u16string_view
aData(rCandidate
.subView(nPos
));
1352 static constexpr std::u16string_view aStrBase64
= u
"base64";
1354 if(o3tl::starts_with(aData
, aStrBase64
))
1357 nPos
= aStrBase64
.size();
1358 nLen
= aData
.size();
1360 skip_char(aData
, ' ', ',', nPos
, nLen
);
1364 rData
= aData
.substr(nPos
);
1372 // Url (path and filename)
1379 OUString
removeBlockComments(const OUString
& rCandidate
)
1381 const sal_Int32
nLen(rCandidate
.getLength());
1386 OUStringBuffer aBuffer
;
1387 bool bChanged(false);
1388 sal_Int32
nInsideComment(0);
1389 const sal_Unicode
aCommentSlash('/');
1390 const sal_Unicode
aCommentStar('*');
1394 const sal_Unicode
aChar(rCandidate
[nPos
]);
1395 const bool bStart(aCommentSlash
== aChar
&& nPos
+ 1 < nLen
&& aCommentStar
== rCandidate
[nPos
+ 1]);
1396 const bool bEnd(aCommentStar
== aChar
&& nPos
+ 1 < nLen
&& aCommentSlash
== rCandidate
[nPos
+ 1]);
1413 aBuffer
.append(aChar
);
1422 return aBuffer
.makeStringAndClear();
1429 OUString
consolidateContiguousSpace(const OUString
& rCandidate
)
1431 const sal_Int32
nLen(rCandidate
.getLength());
1436 OUStringBuffer aBuffer
;
1437 bool bInsideSpace(false);
1438 const sal_Unicode
aSpace(' ');
1442 const sal_Unicode
aChar(rCandidate
[nPos
]);
1446 bInsideSpace
= true;
1452 bInsideSpace
= false;
1453 aBuffer
.append(aSpace
);
1456 aBuffer
.append(aChar
);
1464 aBuffer
.append(aSpace
);
1467 if(aBuffer
.getLength() != nLen
)
1469 return aBuffer
.makeStringAndClear();
1476 ::std::vector
< double > solveSvgNumberVector(const SvgNumberVector
& rInput
, const InfoProvider
& rInfoProvider
)
1478 ::std::vector
< double > aRetval
;
1482 const double nCount(rInput
.size());
1483 aRetval
.reserve(nCount
);
1485 for(sal_uInt32
a(0); a
< nCount
; a
++)
1487 aRetval
.push_back(rInput
[a
].solve(rInfoProvider
));
1494 } // end of namespace svgio::svgreader
1496 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */