diff options
Diffstat (limited to 'svgio/source/svgreader/svgtools.cxx')
-rw-r--r-- | svgio/source/svgreader/svgtools.cxx | 1516 |
1 files changed, 1516 insertions, 0 deletions
diff --git a/svgio/source/svgreader/svgtools.cxx b/svgio/source/svgreader/svgtools.cxx new file mode 100644 index 0000000000..999de30753 --- /dev/null +++ b/svgio/source/svgreader/svgtools.cxx @@ -0,0 +1,1516 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <svgtools.hxx> +#include <sal/log.hxx> +#include <tools/color.hxx> +#include <rtl/math.hxx> +#include <o3tl/string_view.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> +#include <svgtoken.hxx> +#include <frozen/bits/defines.h> +#include <frozen/bits/elsa_std.h> +#include <frozen/unordered_map.h> + +namespace svgio::svgreader +{ + constexpr auto aColorTokenMapperList = frozen::make_unordered_map<std::u16string_view, Color>( + { + { u"aliceblue", Color(240, 248, 255) }, + { u"antiquewhite", Color(250, 235, 215) }, + { u"aqua", Color( 0, 255, 255) }, + { u"aquamarine", Color(127, 255, 212) }, + { u"azure", Color(240, 255, 255) }, + { u"beige", Color(245, 245, 220) }, + { u"bisque", Color(255, 228, 196) }, + { u"black", Color( 0, 0, 0) }, + { u"blanchedalmond", Color(255, 235, 205) }, + { u"blue", Color( 0, 0, 255) }, + { u"blueviolet", Color(138, 43, 226) }, + { u"brown", Color(165, 42, 42) }, + { u"burlywood", Color(222, 184, 135) }, + { u"cadetblue", Color( 95, 158, 160) }, + { u"chartreuse", Color(127, 255, 0) }, + { u"chocolate", Color(210, 105, 30) }, + { u"coral", Color(255, 127, 80) }, + { u"cornflowerblue", Color(100, 149, 237) }, + { u"cornsilk", Color(255, 248, 220) }, + { u"crimson", Color(220, 20, 60) }, + { u"cyan", Color( 0, 255, 255) }, + { u"darkblue", Color( 0, 0, 139) }, + { u"darkcyan", Color( 0, 139, 139) }, + { u"darkgoldenrod", Color(184, 134, 11) }, + { u"darkgray", Color(169, 169, 169) }, + { u"darkgreen", Color( 0, 100, 0) }, + { u"darkgrey", Color(169, 169, 169) }, + { u"darkkhaki", Color(189, 183, 107) }, + { u"darkmagenta", Color(139, 0, 139) }, + { u"darkolivegreen", Color( 85, 107, 47) }, + { u"darkorange", Color(255, 140, 0) }, + { u"darkorchid", Color(153, 50, 204) }, + { u"darkred", Color(139, 0, 0) }, + { u"darksalmon", Color(233, 150, 122) }, + { u"darkseagreen", Color(143, 188, 143) }, + { u"darkslateblue", Color( 72, 61, 139) }, + { u"darkslategray", Color( 47, 79, 79) }, + { u"darkslategrey", Color( 47, 79, 79) }, + { u"darkturquoise", Color( 0, 206, 209) }, + { u"darkviolet", Color(148, 0, 211) }, + { u"deeppink", Color(255, 20, 147) }, + { u"deepskyblue", Color( 0, 191, 255) }, + { u"dimgray", Color(105, 105, 105) }, + { u"dimgrey", Color(105, 105, 105) }, + { u"dodgerblue", Color( 30, 144, 255) }, + { u"firebrick", Color(178, 34, 34) }, + { u"floralwhite", Color(255, 250, 240) }, + { u"forestgreen", Color( 34, 139, 34) }, + { u"fuchsia", Color(255, 0, 255) }, + { u"gainsboro", Color(220, 220, 220) }, + { u"ghostwhite", Color(248, 248, 255) }, + { u"gold", Color(255, 215, 0) }, + { u"goldenrod", Color(218, 165, 32) }, + { u"gray", Color(128, 128, 128) }, + { u"grey", Color(128, 128, 128) }, + { u"green", Color(0, 128, 0) }, + { u"greenyellow", Color(173, 255, 47) }, + { u"honeydew", Color(240, 255, 240) }, + { u"hotpink", Color(255, 105, 180) }, + { u"indianred", Color(205, 92, 92) }, + { u"indigo", Color( 75, 0, 130) }, + { u"ivory", Color(255, 255, 240) }, + { u"khaki", Color(240, 230, 140) }, + { u"lavender", Color(230, 230, 250) }, + { u"lavenderblush", Color(255, 240, 245) }, + { u"lawngreen", Color(124, 252, 0) }, + { u"lemonchiffon", Color(255, 250, 205) }, + { u"lightblue", Color(173, 216, 230) }, + { u"lightcoral", Color(240, 128, 128) }, + { u"lightcyan", Color(224, 255, 255) }, + { u"lightgoldenrodyellow", Color(250, 250, 210) }, + { u"lightgray", Color(211, 211, 211) }, + { u"lightgreen", Color(144, 238, 144) }, + { u"lightgrey", Color(211, 211, 211) }, + { u"lightpink", Color(255, 182, 193) }, + { u"lightsalmon", Color(255, 160, 122) }, + { u"lightseagreen", Color( 32, 178, 170) }, + { u"lightskyblue", Color(135, 206, 250) }, + { u"lightslategray", Color(119, 136, 153) }, + { u"lightslategrey", Color(119, 136, 153) }, + { u"lightsteelblue", Color(176, 196, 222) }, + { u"lightyellow", Color(255, 255, 224) }, + { u"lime", Color( 0, 255, 0) }, + { u"limegreen", Color( 50, 205, 50) }, + { u"linen", Color(250, 240, 230) }, + { u"magenta", Color(255, 0, 255) }, + { u"maroon", Color(128, 0, 0) }, + { u"mediumaquamarine", Color(102, 205, 170) }, + { u"mediumblue", Color( 0, 0, 205) }, + { u"mediumorchid", Color(186, 85, 211) }, + { u"mediumpurple", Color(147, 112, 219) }, + { u"mediumseagreen", Color( 60, 179, 113) }, + { u"mediumslateblue", Color(123, 104, 238) }, + { u"mediumspringgreen", Color( 0, 250, 154) }, + { u"mediumturquoise", Color( 72, 209, 204) }, + { u"mediumvioletred", Color(199, 21, 133) }, + { u"midnightblue", Color( 25, 25, 112) }, + { u"mintcream", Color(245, 255, 250) }, + { u"mistyrose", Color(255, 228, 225) }, + { u"moccasin", Color(255, 228, 181) }, + { u"navajowhite", Color(255, 222, 173) }, + { u"navy", Color( 0, 0, 128) }, + { u"oldlace", Color(253, 245, 230) }, + { u"olive", Color(128, 128, 0) }, + { u"olivedrab", Color(107, 142, 35) }, + { u"orange", Color(255, 165, 0) }, + { u"orangered", Color(255, 69, 0) }, + { u"orchid", Color(218, 112, 214) }, + { u"palegoldenrod", Color(238, 232, 170) }, + { u"palegreen", Color(152, 251, 152) }, + { u"paleturquoise", Color(175, 238, 238) }, + { u"palevioletred", Color(219, 112, 147) }, + { u"papayawhip", Color(255, 239, 213) }, + { u"peachpuff", Color(255, 218, 185) }, + { u"peru", Color(205, 133, 63) }, + { u"pink", Color(255, 192, 203) }, + { u"plum", Color(221, 160, 221) }, + { u"powderblue", Color(176, 224, 230) }, + { u"purple", Color(128, 0, 128) }, + { u"red", Color(255, 0, 0) }, + { u"rosybrown", Color(188, 143, 143) }, + { u"royalblue", Color( 65, 105, 225) }, + { u"saddlebrown", Color(139, 69, 19) }, + { u"salmon", Color(250, 128, 114) }, + { u"sandybrown", Color(244, 164, 96) }, + { u"seagreen", Color( 46, 139, 87) }, + { u"seashell", Color(255, 245, 238) }, + { u"sienna", Color(160, 82, 45) }, + { u"silver", Color(192, 192, 192) }, + { u"skyblue", Color(135, 206, 235) }, + { u"slateblue", Color(106, 90, 205) }, + { u"slategray", Color(112, 128, 144) }, + { u"slategrey", Color(112, 128, 144) }, + { u"snow", Color(255, 250, 250) }, + { u"springgreen", Color( 0, 255, 127) }, + { u"steelblue", Color( 70, 130, 180) }, + { u"tan", Color(210, 180, 140) }, + { u"teal", Color( 0, 128, 128) }, + { u"thistle", Color(216, 191, 216) }, + { u"tomato", Color(255, 99, 71) }, + { u"turquoise", Color( 64, 224, 208) }, + { u"violet", Color(238, 130, 238) }, + { u"wheat", Color(245, 222, 179) }, + { u"white", Color(255, 255, 255) }, + { u"whitesmoke", Color(245, 245, 245) }, + { u"yellow", Color(255, 255, 0) }, + { u"yellowgreen", Color(154, 205, 50) } + }); + + basegfx::B2DHomMatrix SvgAspectRatio::createLinearMapping(const basegfx::B2DRange& rTarget, const basegfx::B2DRange& rSource) + { + basegfx::B2DHomMatrix aRetval; + const double fSWidth(rSource.getWidth()); + const double fSHeight(rSource.getHeight()); + const bool bNoSWidth(basegfx::fTools::equalZero(fSWidth)); + const bool bNoSHeight(basegfx::fTools::equalZero(fSHeight)); + + // transform from source state to unit range + aRetval.translate(-rSource.getMinX(), -rSource.getMinY()); + aRetval.scale( + (bNoSWidth ? 1.0 : 1.0 / fSWidth) * rTarget.getWidth(), + (bNoSHeight ? 1.0 : 1.0 / fSHeight) * rTarget.getHeight()); + + // transform from unit rage to target range + aRetval.translate(rTarget.getMinX(), rTarget.getMinY()); + + return aRetval; + } + + basegfx::B2DHomMatrix SvgAspectRatio::createMapping(const basegfx::B2DRange& rTarget, const basegfx::B2DRange& rSource) const + { + // removed !isSet() from below. Due to correct defaults in the constructor an instance + // of this class is perfectly useful without being set by any importer + if(SvgAlign::none == getSvgAlign()) + { + // create linear mapping (default) + return createLinearMapping(rTarget, rSource); + } + + basegfx::B2DHomMatrix aRetval; + + const double fSWidth(rSource.getWidth()); + const double fSHeight(rSource.getHeight()); + const bool bNoSWidth(basegfx::fTools::equalZero(fSWidth)); + const bool bNoSHeight(basegfx::fTools::equalZero(fSHeight)); + const double fScaleX((bNoSWidth ? 1.0 : 1.0 / fSWidth) * rTarget.getWidth()); + const double fScaleY((bNoSHeight ? 1.0 : 1.0 / fSHeight) * rTarget.getHeight()); + const double fScale(isMeetOrSlice() ? std::min(fScaleX, fScaleY) : std::max(fScaleX, fScaleY)); + + // remove source translation, apply scale + aRetval.translate(-rSource.getMinX(), -rSource.getMinY()); + aRetval.scale(fScale, fScale); + + // evaluate horizontal alignment + const double fNewWidth(fSWidth * fScale); + double fTransX(0.0); + + switch(getSvgAlign()) + { + case SvgAlign::xMidYMin: + case SvgAlign::xMidYMid: + case SvgAlign::xMidYMax: + { + // centerX + const double fFreeSpace(rTarget.getWidth() - fNewWidth); + fTransX = fFreeSpace * 0.5; + break; + } + case SvgAlign::xMaxYMin: + case SvgAlign::xMaxYMid: + case SvgAlign::xMaxYMax: + { + // Right align + const double fFreeSpace(rTarget.getWidth() - fNewWidth); + fTransX = fFreeSpace; + break; + } + default: break; + } + + // evaluate vertical alignment + const double fNewHeight(fSHeight * fScale); + double fTransY(0.0); + + switch(getSvgAlign()) + { + case SvgAlign::xMinYMid: + case SvgAlign::xMidYMid: + case SvgAlign::xMaxYMid: + { + // centerY + const double fFreeSpace(rTarget.getHeight() - fNewHeight); + fTransY = fFreeSpace * 0.5; + break; + } + case SvgAlign::xMinYMax: + case SvgAlign::xMidYMax: + case SvgAlign::xMaxYMax: + { + // Bottom align + const double fFreeSpace(rTarget.getHeight() - fNewHeight); + fTransY = fFreeSpace; + break; + } + default: break; + } + + // add target translation + aRetval.translate( + rTarget.getMinX() + fTransX, + rTarget.getMinY() + fTransY); + + return aRetval; + } + + void skip_char(std::u16string_view rCandidate, sal_Unicode nChar, sal_Int32& nPos, const sal_Int32 nLen) + { + while(nPos < nLen && nChar == rCandidate[nPos]) + { + nPos++; + } + } + + void skip_char(std::u16string_view rCandidate, sal_Unicode nCharA, sal_Unicode nCharB, sal_Int32& nPos, const sal_Int32 nLen) + { + while(nPos < nLen && (nCharA == rCandidate[nPos] || nCharB == rCandidate[nPos])) + { + nPos++; + } + } + + void copySign(std::u16string_view rCandidate, sal_Int32& nPos, OUStringBuffer& rTarget, const sal_Int32 nLen) + { + if(nPos < nLen) + { + const sal_Unicode aChar(rCandidate[nPos]); + + if('+' == aChar || '-' == aChar) + { + rTarget.append(aChar); + nPos++; + } + } + } + + void copyNumber(std::u16string_view rCandidate, sal_Int32& nPos, OUStringBuffer& rTarget, const sal_Int32 nLen) + { + bool bOnNumber(true); + + while(bOnNumber && nPos < nLen) + { + const sal_Unicode aChar(rCandidate[nPos]); + + bOnNumber = ('0' <= aChar && '9' >= aChar) || '.' == aChar; + + if(bOnNumber) + { + rTarget.append(aChar); + nPos++; + } + } + } + + void copyHex(std::u16string_view rCandidate, sal_Int32& nPos, OUStringBuffer& rTarget, const sal_Int32 nLen) + { + bool bOnHex(true); + + while(bOnHex && nPos < nLen) + { + const sal_Unicode aChar(rCandidate[nPos]); + + bOnHex = ('0' <= aChar && '9' >= aChar) + || ('A' <= aChar && 'F' >= aChar) + || ('a' <= aChar && 'f' >= aChar); + + if(bOnHex) + { + rTarget.append(aChar); + nPos++; + } + } + } + + void copyString(std::u16string_view rCandidate, sal_Int32& nPos, OUStringBuffer& rTarget, const sal_Int32 nLen) + { + bool bOnChar(true); + + while(bOnChar && nPos < nLen) + { + const sal_Unicode aChar(rCandidate[nPos]); + + bOnChar = ('a' <= aChar && 'z' >= aChar) + || ('A' <= aChar && 'Z' >= aChar) + || '-' == aChar; + + if(bOnChar) + { + rTarget.append(aChar); + nPos++; + } + } + } + + void copyToLimiter(std::u16string_view rCandidate, sal_Unicode nLimiter, sal_Int32& nPos, OUStringBuffer& rTarget, const sal_Int32 nLen) + { + while(nPos < nLen && nLimiter != rCandidate[nPos]) + { + rTarget.append(rCandidate[nPos]); + nPos++; + } + } + + bool readNumber(std::u16string_view rCandidate, sal_Int32& nPos, double& fNum, const sal_Int32 nLen) + { + if(nPos < nLen) + { + OUStringBuffer aNum; + + copySign(rCandidate, nPos, aNum, nLen); + copyNumber(rCandidate, nPos, aNum, nLen); + + if(nPos < nLen) + { + const sal_Unicode aChar(rCandidate[nPos]); + + if('e' == aChar || 'E' == aChar) + { + // try to read exponential number, but be careful. I had + // a case where dx="2em" was used, thus the 'e' was consumed + // by error. First try if there are numbers after the 'e', + // safe current state + nPos++; + const OUStringBuffer aNum2(aNum); + const sal_Int32 nPosAfterE(nPos); + + aNum.append(aChar); + copySign(rCandidate, nPos, aNum, nLen); + copyNumber(rCandidate, nPos, aNum, nLen); + + if(nPosAfterE == nPos) + { + // no number after 'e', go back. Do not + // return false, it's still a valid integer number + aNum = aNum2; + nPos--; + } + } + } + + if(!aNum.isEmpty()) + { + rtl_math_ConversionStatus eStatus; + + fNum = rtl::math::stringToDouble( + aNum, '.', ',', + &eStatus); + + return eStatus == rtl_math_ConversionStatus_Ok; + } + } + + return false; + } + + SvgUnit readUnit(std::u16string_view rCandidate, sal_Int32& nPos, const sal_Int32 nLen) + { + SvgUnit aRetval(SvgUnit::px); + + if(nPos < nLen) + { + const sal_Unicode aCharA(rCandidate[nPos]); + + if('%' == aCharA) + { + // percent used, relative to current + nPos++; + aRetval = SvgUnit::percent; + } + else if(nPos + 1 < nLen) + { + const sal_Unicode aCharB(rCandidate[nPos + 1]); + bool bTwoCharValid(false); + + switch(aCharA) + { + case u'e' : + { + if('m' == aCharB) + { + // 'em' Relative to current font size + aRetval = SvgUnit::em; + bTwoCharValid = true; + } + else if('x' == aCharB) + { + // 'ex' Relative to current font x-height + aRetval = SvgUnit::ex; + bTwoCharValid = true; + } + break; + } + case u'p' : + { + if('x' == aCharB) + { + // 'px' UserUnit (default) + bTwoCharValid = true; + } + else if('t' == aCharB) + { + // 'pt' == 4/3 px + aRetval = SvgUnit::pt; + bTwoCharValid = true; + } + else if('c' == aCharB) + { + // 'pc' == 16 px + aRetval = SvgUnit::pc; + bTwoCharValid = true; + } + break; + } + case u'i' : + { + if('n' == aCharB) + { + // 'in' == 96 px, since CSS 2.1 + aRetval = SvgUnit::in; + bTwoCharValid = true; + } + break; + } + case u'c' : + { + if('m' == aCharB) + { + // 'cm' == 37.79527559 px + aRetval = SvgUnit::cm; + bTwoCharValid = true; + } + break; + } + case u'm' : + { + if('m' == aCharB) + { + // 'mm' == 3.779528 px + aRetval = SvgUnit::mm; + bTwoCharValid = true; + } + break; + } + } + + if(bTwoCharValid) + { + nPos += 2; + } + } + } + + return aRetval; + } + + bool readNumberAndUnit(std::u16string_view rCandidate, sal_Int32& nPos, SvgNumber& aNum, const sal_Int32 nLen) + { + double fNum(0.0); + + if(readNumber(rCandidate, nPos, fNum, nLen)) + { + skip_char(rCandidate, ' ', nPos, nLen); + aNum = SvgNumber(fNum, readUnit(rCandidate, nPos, nLen)); + + return true; + } + + return false; + } + + bool readAngle(std::u16string_view rCandidate, sal_Int32& nPos, double& fAngle, const sal_Int32 nLen) + { + if(readNumber(rCandidate, nPos, fAngle, nLen)) + { + skip_char(rCandidate, ' ', nPos, nLen); + + enum class DegreeType + { + deg, + grad, + rad + } aType(DegreeType::deg); // degrees is default + + if(nPos < nLen) + { + const sal_Unicode aChar(rCandidate[nPos]); + static constexpr std::u16string_view aStrGrad = u"grad"; + static constexpr std::u16string_view aStrRad = u"rad"; + + switch(aChar) + { + case u'g' : + case u'G' : + { + if(o3tl::matchIgnoreAsciiCase(rCandidate, aStrGrad, nPos)) + { + // angle in grad + nPos += aStrGrad.size(); + aType = DegreeType::grad; + } + break; + } + case u'r' : + case u'R' : + { + if(o3tl::matchIgnoreAsciiCase(rCandidate, aStrRad, nPos)) + { + // angle in radians + nPos += aStrRad.size(); + aType = DegreeType::rad; + } + break; + } + } + } + + // convert to radians + if (DegreeType::deg == aType) + { + fAngle = basegfx::deg2rad(fAngle); + } + else if (DegreeType::grad == aType) + { + // looks like 100 grad is 90 degrees + fAngle *= M_PI / 200.0; + } + + return true; + } + + return false; + } + + sal_Int32 read_hex(sal_Unicode nChar) + { + if(nChar >= '0' && nChar <= '9') + { + return nChar - u'0'; + } + else if(nChar >= 'A' && nChar <= 'F') + { + return 10 + sal_Int32(nChar - u'A'); + } + else if(nChar >= 'a' && nChar <= 'f') + { + return 10 + sal_Int32(nChar - u'a'); + } + else + { + // error + return 0; + } + } + + bool match_colorKeyword(basegfx::BColor& rColor, const OUString& rName) + { + auto const aResult = aColorTokenMapperList.find(rName.toAsciiLowerCase().trim()); + + if(aResult == aColorTokenMapperList.end()) + { + return false; + } + else + { + rColor = aResult->second.getBColor(); + return true; + } + } + + bool read_color(const OUString& rCandidate, basegfx::BColor& rColor, SvgNumber& rOpacity) + { + const sal_Int32 nLen(rCandidate.getLength()); + + if(nLen) + { + const sal_Unicode aChar(rCandidate[0]); + const double fFactor(1.0 / 255.0); + + if(aChar == '#') + { + // hex definition + OUStringBuffer aNum; + sal_Int32 nPos(1); + + copyHex(rCandidate, nPos, aNum, nLen); + const sal_Int32 nLength(aNum.getLength()); + + if(3 == nLength) + { + const sal_Int32 nR(read_hex(aNum[0])); + const sal_Int32 nG(read_hex(aNum[1])); + const sal_Int32 nB(read_hex(aNum[2])); + + rColor.setRed((nR | (nR << 4)) * fFactor); + rColor.setGreen((nG | (nG << 4)) * fFactor); + rColor.setBlue((nB | (nB << 4)) * fFactor); + + return true; + } + else if(6 == nLength) + { + const sal_Int32 nR1(read_hex(aNum[0])); + const sal_Int32 nR2(read_hex(aNum[1])); + const sal_Int32 nG1(read_hex(aNum[2])); + const sal_Int32 nG2(read_hex(aNum[3])); + const sal_Int32 nB1(read_hex(aNum[4])); + const sal_Int32 nB2(read_hex(aNum[5])); + + rColor.setRed((nR2 | (nR1 << 4)) * fFactor); + rColor.setGreen((nG2 | (nG1 << 4)) * fFactor); + rColor.setBlue((nB2 | (nB1 << 4)) * fFactor); + + return true; + } + } + else + { + static const char aStrRgb[] = "rgb"; + + if(rCandidate.matchIgnoreAsciiCase(aStrRgb, 0)) + { + // rgb/rgba definition + sal_Int32 nPos(strlen(aStrRgb)); + bool bIsRGBA = false; + + if('a' == rCandidate[nPos]) + { + //Delete the 'a' from 'rbga' + skip_char(rCandidate, 'a', nPos, nPos + 1); + bIsRGBA = true; + } + + skip_char(rCandidate, ' ', '(', nPos, nLen); + double fR(0.0); + + if(readNumber(rCandidate, nPos, fR, nLen)) + { + skip_char(rCandidate, ' ', nPos, nLen); + + if(nPos < nLen) + { + const sal_Unicode aPercentChar(rCandidate[nPos]); + const bool bIsPercent('%' == aPercentChar); + double fG(0.0); + + if(bIsPercent) + { + skip_char(rCandidate, '%', nPos, nLen); + } + + skip_char(rCandidate, ' ', ',', nPos, nLen); + + if(readNumber(rCandidate, nPos, fG, nLen)) + { + double fB(0.0); + + if(bIsPercent) + { + skip_char(rCandidate, '%', nPos, nLen); + } + + skip_char(rCandidate, ' ', ',', nPos, nLen); + + if(readNumber(rCandidate, nPos, fB, nLen)) + { + double fA(1.0); + + if(bIsPercent) + { + skip_char(rCandidate, '%', nPos, nLen); + } + + skip_char(rCandidate, ' ', ',', nPos, nLen); + + if(readNumber(rCandidate, nPos, fA, nLen)) + { + if(bIsRGBA) + { + const double fFac(bIsPercent ? 0.01 : 1); + rOpacity = SvgNumber(fA * fFac); + + if(bIsPercent) + { + skip_char(rCandidate, '%', nPos, nLen); + } + } + else + { + return false; + } + } + + const double fFac(bIsPercent ? 0.01 : fFactor); + + rColor.setRed(fR * fFac); + rColor.setGreen(fG * fFac); + rColor.setBlue(fB * fFac); + + skip_char(rCandidate, ' ', ')', nPos, nLen); + return true; + } + } + } + } + } + else + { + // color keyword + if(match_colorKeyword(rColor, rCandidate)) + { + return true; + } + } + } + } + + return false; + } + + basegfx::B2DRange readViewBox(std::u16string_view rCandidate, InfoProvider const & rInfoProvider) + { + const sal_Int32 nLen(rCandidate.size()); + + if(nLen) + { + sal_Int32 nPos(0); + SvgNumber aMinX; + skip_char(rCandidate, ' ', ',', nPos, nLen); + + if(readNumberAndUnit(rCandidate, nPos, aMinX, nLen)) + { + SvgNumber aMinY; + skip_char(rCandidate, ' ', ',', nPos, nLen); + + if(readNumberAndUnit(rCandidate, nPos, aMinY, nLen)) + { + SvgNumber aWidth; + skip_char(rCandidate, ' ', ',', nPos, nLen); + + if(readNumberAndUnit(rCandidate, nPos, aWidth, nLen)) + { + SvgNumber aHeight; + skip_char(rCandidate, ' ', ',', nPos, nLen); + + if(readNumberAndUnit(rCandidate, nPos, aHeight, nLen)) + { + double fX(aMinX.solve(rInfoProvider, NumberType::xcoordinate)); + double fY(aMinY.solve(rInfoProvider, NumberType::ycoordinate)); + double fW(aWidth.solve(rInfoProvider, NumberType::xcoordinate)); + double fH(aHeight.solve(rInfoProvider, NumberType::ycoordinate)); + return basegfx::B2DRange(fX,fY,fX+fW,fY+fH); + } + } + } + } + } + + return basegfx::B2DRange(); + } + + std::vector<double> readFilterMatrix(std::u16string_view rCandidate, InfoProvider const & rInfoProvider) + { + std::vector<double> aVector; + const sal_Int32 nLen(rCandidate.size()); + + sal_Int32 nPos(0); + skip_char(rCandidate, ' ', ',', nPos, nLen); + + SvgNumber aVal; + + while (nPos < nLen) + { + if(readNumberAndUnit(rCandidate, nPos, aVal, nLen)) + { + aVector.push_back(aVal.solve(rInfoProvider)); + skip_char(rCandidate, ' ', ',', nPos, nLen); + } + } + + return aVector; + } + + basegfx::B2DHomMatrix readTransform(std::u16string_view rCandidate, InfoProvider const & rInfoProvider) + { + basegfx::B2DHomMatrix aMatrix; + const sal_Int32 nLen(rCandidate.size()); + + if(nLen) + { + sal_Int32 nPos(0); + skip_char(rCandidate, ' ', ',', nPos, nLen); + + while(nPos < nLen) + { + const sal_Unicode aChar(rCandidate[nPos]); + const sal_Int32 nInitPos(nPos); + static constexpr std::u16string_view aStrMatrix = u"matrix"; + static constexpr std::u16string_view aStrTranslate = u"translate"; + static constexpr std::u16string_view aStrScale = u"scale"; + static constexpr std::u16string_view aStrRotate = u"rotate"; + static constexpr std::u16string_view aStrSkewX = u"skewX"; + static constexpr std::u16string_view aStrSkewY = u"skewY"; + + switch(aChar) + { + case u'm' : + { + if(o3tl::matchIgnoreAsciiCase(rCandidate, aStrMatrix, nPos)) + { + // matrix element + nPos += aStrMatrix.size(); + skip_char(rCandidate, ' ', '(', nPos, nLen); + SvgNumber aVal; + basegfx::B2DHomMatrix aNew; + + if(readNumberAndUnit(rCandidate, nPos, aVal, nLen)) + { + aNew.set(0, 0, aVal.solve(rInfoProvider)); // Element A + skip_char(rCandidate, ' ', ',', nPos, nLen); + + if(readNumberAndUnit(rCandidate, nPos, aVal, nLen)) + { + aNew.set(1, 0, aVal.solve(rInfoProvider)); // Element B + skip_char(rCandidate, ' ', ',', nPos, nLen); + + if(readNumberAndUnit(rCandidate, nPos, aVal, nLen)) + { + aNew.set(0, 1, aVal.solve(rInfoProvider)); // Element C + skip_char(rCandidate, ' ', ',', nPos, nLen); + + if(readNumberAndUnit(rCandidate, nPos, aVal, nLen)) + { + aNew.set(1, 1, aVal.solve(rInfoProvider)); // Element D + skip_char(rCandidate, ' ', ',', nPos, nLen); + + if(readNumberAndUnit(rCandidate, nPos, aVal, nLen)) + { + aNew.set(0, 2, aVal.solve(rInfoProvider, NumberType::xcoordinate)); // Element E + skip_char(rCandidate, ' ', ',', nPos, nLen); + + if(readNumberAndUnit(rCandidate, nPos, aVal, nLen)) + { + aNew.set(1, 2, aVal.solve(rInfoProvider, NumberType::ycoordinate)); // Element F + skip_char(rCandidate, ' ', ')', nPos, nLen); + skip_char(rCandidate, ' ', ',', nPos, nLen); + + // caution: String is evaluated from left to right, but matrix multiplication + // in SVG is right to left, so put the new transformation before the current + // one by multiplicating from the right side + aMatrix = aMatrix * aNew; + } + } + } + } + } + } + } + break; + } + case u't' : + { + if(o3tl::matchIgnoreAsciiCase(rCandidate, aStrTranslate, nPos)) + { + // translate element + nPos += aStrTranslate.size(); + skip_char(rCandidate, ' ', '(', nPos, nLen); + SvgNumber aTransX; + + if(readNumberAndUnit(rCandidate, nPos, aTransX, nLen)) + { + skip_char(rCandidate, ' ', ',', nPos, nLen); + SvgNumber aTransY; + readNumberAndUnit(rCandidate, nPos, aTransY, nLen); + skip_char(rCandidate, ' ', ')', nPos, nLen); + skip_char(rCandidate, ' ', ',', nPos, nLen); + + aMatrix = aMatrix * basegfx::utils::createTranslateB2DHomMatrix( + aTransX.solve(rInfoProvider, NumberType::xcoordinate), + aTransY.solve(rInfoProvider, NumberType::ycoordinate)); + } + } + break; + } + case u's' : + { + if(o3tl::matchIgnoreAsciiCase(rCandidate, aStrScale, nPos)) + { + // scale element + nPos += aStrScale.size(); + skip_char(rCandidate, ' ', '(', nPos, nLen); + SvgNumber aScaleX; + + if(readNumberAndUnit(rCandidate, nPos, aScaleX, nLen)) + { + skip_char(rCandidate, ' ', ',', nPos, nLen); + SvgNumber aScaleY(aScaleX); + readNumberAndUnit(rCandidate, nPos, aScaleY, nLen); + skip_char(rCandidate, ' ', ')', nPos, nLen); + skip_char(rCandidate, ' ', ',', nPos, nLen); + + aMatrix = aMatrix * basegfx::utils::createScaleB2DHomMatrix( + aScaleX.solve(rInfoProvider), + aScaleY.solve(rInfoProvider)); + } + } + else if(o3tl::matchIgnoreAsciiCase(rCandidate, aStrSkewX, nPos)) + { + // skewx element + nPos += aStrSkewX.size(); + skip_char(rCandidate, ' ', '(', nPos, nLen); + double fSkewX(0.0); + + if(readAngle(rCandidate, nPos, fSkewX, nLen)) + { + skip_char(rCandidate, ' ', ')', nPos, nLen); + skip_char(rCandidate, ' ', ',', nPos, nLen); + + aMatrix = aMatrix * basegfx::utils::createShearXB2DHomMatrix(tan(fSkewX)); + } + } + else if(o3tl::matchIgnoreAsciiCase(rCandidate, aStrSkewY, nPos)) + { + // skewy element + nPos += aStrSkewY.size(); + skip_char(rCandidate, ' ', '(', nPos, nLen); + double fSkewY(0.0); + + if(readAngle(rCandidate, nPos, fSkewY, nLen)) + { + skip_char(rCandidate, ' ', ')', nPos, nLen); + skip_char(rCandidate, ' ', ',', nPos, nLen); + + aMatrix = aMatrix * basegfx::utils::createShearYB2DHomMatrix(tan(fSkewY)); + } + } + break; + } + case u'r' : + { + if(o3tl::matchIgnoreAsciiCase(rCandidate, aStrRotate, nPos)) + { + // rotate element + nPos += aStrRotate.size(); + skip_char(rCandidate, ' ', '(', nPos, nLen); + double fAngle(0.0); + + if(readAngle(rCandidate, nPos, fAngle, nLen)) + { + skip_char(rCandidate, ' ', ',', nPos, nLen); + SvgNumber aX; + readNumberAndUnit(rCandidate, nPos, aX, nLen); + skip_char(rCandidate, ' ', ',', nPos, nLen); + SvgNumber aY; + readNumberAndUnit(rCandidate, nPos, aY, nLen); + skip_char(rCandidate, ' ', ')', nPos, nLen); + skip_char(rCandidate, ' ', ',', nPos, nLen); + + const double fX(aX.isSet() ? aX.solve(rInfoProvider, NumberType::xcoordinate) : 0.0); + const double fY(aY.isSet() ? aY.solve(rInfoProvider, NumberType::ycoordinate) : 0.0); + + if(!basegfx::fTools::equalZero(fX) || !basegfx::fTools::equalZero(fY)) + { + // rotate around point + aMatrix = aMatrix * basegfx::utils::createRotateAroundPoint(fX, fY, fAngle); + } + else + { + // rotate + aMatrix = aMatrix * basegfx::utils::createRotateB2DHomMatrix(fAngle); + } + } + } + break; + } + } + + if(nInitPos == nPos) + { + SAL_WARN("svgio", "Could not interpret on current position (!)"); + nPos++; + } + } + } + + return aMatrix; + } + + bool readSingleNumber(std::u16string_view rCandidate, SvgNumber& aNum) + { + const sal_Int32 nLen(rCandidate.size()); + sal_Int32 nPos(0); + + return readNumberAndUnit(rCandidate, nPos, aNum, nLen); + } + + bool readLocalLink(std::u16string_view rCandidate, OUString& rURL) + { + sal_Int32 nPos(0); + const sal_Int32 nLen(rCandidate.size()); + + skip_char(rCandidate, ' ', nPos, nLen); + + if (nLen && nPos < nLen && '#' == rCandidate[nPos]) + { + ++nPos; + rURL = rCandidate.substr(nPos); + + return true; + } + + return false; + } + + bool readLocalUrl(const OUString& rCandidate, OUString& rURL) + { + static const char aStrUrl[] = "url("; + + if(rCandidate.startsWithIgnoreAsciiCase(aStrUrl)) + { + const sal_Int32 nLen(rCandidate.getLength()); + sal_Int32 nPos(strlen(aStrUrl)); + sal_Unicode aLimiter(')'); + + skip_char(rCandidate, ' ', nPos, nLen); + + if('"' == rCandidate[nPos]) + { + aLimiter = '"'; + ++nPos; + } + else if('\'' == rCandidate[nPos]) + { + aLimiter = '\''; + ++nPos; + } + + skip_char(rCandidate, ' ', nPos, nLen); + skip_char(rCandidate, '#', nPos, nPos + 1); + OUStringBuffer aTokenValue; + + copyToLimiter(rCandidate, aLimiter, nPos, aTokenValue, nLen); + + rURL = aTokenValue.makeStringAndClear(); + + return true; + } + + return false; + } + + bool readSvgPaint(const OUString& rCandidate, SvgPaint& rSvgPaint, + OUString& rURL, SvgNumber& rOpacity) + { + if( !rCandidate.isEmpty() ) + { + basegfx::BColor aColor; + + if(read_color(rCandidate, aColor, rOpacity)) + { + rSvgPaint = SvgPaint(aColor, true, true); + return true; + } + else + { + if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(rCandidate), u"none")) + { + rSvgPaint = SvgPaint(aColor, true, false, false); + return true; + } + else if(readLocalUrl(rCandidate, rURL)) + { + /// Url is copied to rURL, but needs to be solved outside this helper + return false; + } + else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(rCandidate), u"currentColor")) + { + rSvgPaint = SvgPaint(aColor, true, true, true); + return true; + } + } + } + + return false; + } + + bool readSvgNumberVector(std::u16string_view rCandidate, SvgNumberVector& rSvgNumberVector) + { + const sal_Int32 nLen(rCandidate.size()); + rSvgNumberVector.clear(); + + if(nLen) + { + sal_Int32 nPos(0); + SvgNumber aNum; + skip_char(rCandidate, ' ', ',', nPos, nLen); + + while(readNumberAndUnit(rCandidate, nPos, aNum, nLen)) + { + rSvgNumberVector.push_back(aNum); + skip_char(rCandidate, ' ', ',', nPos, nLen); + } + + return !rSvgNumberVector.empty(); + } + + return false; + } + + SvgAspectRatio readSvgAspectRatio(std::u16string_view rCandidate) + { + const sal_Int32 nLen(rCandidate.size()); + + if(nLen) + { + sal_Int32 nPos(0); + SvgAlign aSvgAlign(SvgAlign::xMidYMid); + bool bMeetOrSlice(true); + bool bChanged(false); + + while(nPos < nLen) + { + const sal_Int32 nInitPos(nPos); + skip_char(rCandidate, ' ', nPos, nLen); + OUStringBuffer aTokenName; + copyString(rCandidate, nPos, aTokenName, nLen); + + if(!aTokenName.isEmpty()) + { + switch(StrToSVGToken(aTokenName.makeStringAndClear(), false)) + { + case SVGToken::Defer: + { + bChanged = true; + break; + } + case SVGToken::None: + { + aSvgAlign = SvgAlign::none; + bChanged = true; + break; + } + case SVGToken::XMinYMin: + { + aSvgAlign = SvgAlign::xMinYMin; + bChanged = true; + break; + } + case SVGToken::XMidYMin: + { + aSvgAlign = SvgAlign::xMidYMin; + bChanged = true; + break; + } + case SVGToken::XMaxYMin: + { + aSvgAlign = SvgAlign::xMaxYMin; + bChanged = true; + break; + } + case SVGToken::XMinYMid: + { + aSvgAlign = SvgAlign::xMinYMid; + bChanged = true; + break; + } + case SVGToken::XMidYMid: + { + aSvgAlign = SvgAlign::xMidYMid; + bChanged = true; + break; + } + case SVGToken::XMaxYMid: + { + aSvgAlign = SvgAlign::xMaxYMid; + bChanged = true; + break; + } + case SVGToken::XMinYMax: + { + aSvgAlign = SvgAlign::xMinYMax; + bChanged = true; + break; + } + case SVGToken::XMidYMax: + { + aSvgAlign = SvgAlign::xMidYMax; + bChanged = true; + break; + } + case SVGToken::XMaxYMax: + { + aSvgAlign = SvgAlign::xMaxYMax; + bChanged = true; + break; + } + case SVGToken::Meet: + { + bMeetOrSlice = true; + bChanged = true; + break; + } + case SVGToken::Slice: + { + bMeetOrSlice = false; + bChanged = true; + break; + } + default: + { + break; + } + } + } + + if(nInitPos == nPos) + { + SAL_WARN("svgio", "Could not interpret on current position (!)"); + nPos++; + } + } + + if(bChanged) + { + return SvgAspectRatio(aSvgAlign, bMeetOrSlice); + } + } + + return SvgAspectRatio(); + } + + bool readSvgStringVector(std::u16string_view rCandidate, SvgStringVector& rSvgStringVector) + { + rSvgStringVector.clear(); + const sal_Int32 nLen(rCandidate.size()); + + if(nLen) + { + sal_Int32 nPos(0); + OUStringBuffer aTokenValue; + skip_char(rCandidate, ' ', ',', nPos, nLen); + + while(nPos < nLen) + { + copyToLimiter(rCandidate, ',', nPos, aTokenValue, nLen); + skip_char(rCandidate, ',', ' ', nPos, nLen); + const OUString aString = aTokenValue.makeStringAndClear(); + + if(!aString.isEmpty()) + { + rSvgStringVector.push_back(aString); + } + } + } + + return !rSvgStringVector.empty(); + } + + void readImageLink(const OUString& rCandidate, OUString& rXLink, OUString& rUrl, OUString& rData) + { + rXLink.clear(); + rUrl.clear(); + rData.clear(); + + if(!readLocalLink(rCandidate, rXLink)) + { + static const char aStrData[] = "data:"; + + if(rCandidate.matchIgnoreAsciiCase(aStrData, 0)) + { + // embedded data + sal_Int32 nPos(strlen(aStrData)); + sal_Int32 nLen(rCandidate.getLength()); + OUStringBuffer aBuffer; + + // read mime type + skip_char(rCandidate, ' ', nPos, nLen); + copyToLimiter(rCandidate, ';', nPos, aBuffer, nLen); + skip_char(rCandidate, ' ', ';', nPos, nLen); + const OUString aMimeType = aBuffer.makeStringAndClear(); + + if(!aMimeType.isEmpty() && nPos < nLen) + { + if(aMimeType.startsWith("image")) + { + // image data + std::u16string_view aData(rCandidate.subView(nPos)); + static constexpr std::u16string_view aStrBase64 = u"base64"; + + if(o3tl::starts_with(aData, aStrBase64)) + { + // base64 encoded + nPos = aStrBase64.size(); + nLen = aData.size(); + + skip_char(aData, ' ', ',', nPos, nLen); + + if(nPos < nLen) + { + rData = aData.substr(nPos); + } + } + } + } + } + else + { + // Url (path and filename) + rUrl = rCandidate; + } + } + } + + // #i125325# + OUString removeBlockComments(const OUString& rCandidate) + { + const sal_Int32 nLen(rCandidate.getLength()); + + if(nLen) + { + sal_Int32 nPos(0); + OUStringBuffer aBuffer; + bool bChanged(false); + sal_Int32 nInsideComment(0); + const sal_Unicode aCommentSlash('/'); + const sal_Unicode aCommentStar('*'); + + while(nPos < nLen) + { + const sal_Unicode aChar(rCandidate[nPos]); + const bool bStart(aCommentSlash == aChar && nPos + 1 < nLen && aCommentStar == rCandidate[nPos + 1]); + const bool bEnd(aCommentStar == aChar && nPos + 1 < nLen && aCommentSlash == rCandidate[nPos + 1]); + + if(bStart) + { + nPos += 2; + nInsideComment++; + bChanged = true; + } + else if(bEnd) + { + nPos += 2; + nInsideComment--; + } + else + { + if(!nInsideComment) + { + aBuffer.append(aChar); + } + + nPos++; + } + } + + if(bChanged) + { + return aBuffer.makeStringAndClear(); + } + } + + return rCandidate; + } + + OUString consolidateContiguousSpace(const OUString& rCandidate) + { + const sal_Int32 nLen(rCandidate.getLength()); + + if(nLen) + { + sal_Int32 nPos(0); + OUStringBuffer aBuffer; + bool bInsideSpace(false); + const sal_Unicode aSpace(' '); + + while(nPos < nLen) + { + const sal_Unicode aChar(rCandidate[nPos]); + + if(aSpace == aChar) + { + bInsideSpace = true; + } + else + { + if(bInsideSpace) + { + bInsideSpace = false; + aBuffer.append(aSpace); + } + + aBuffer.append(aChar); + } + + nPos++; + } + + if(bInsideSpace) + { + aBuffer.append(aSpace); + } + + if(aBuffer.getLength() != nLen) + { + return aBuffer.makeStringAndClear(); + } + } + + return rCandidate; + } + + ::std::vector< double > solveSvgNumberVector(const SvgNumberVector& rInput, const InfoProvider& rInfoProvider) + { + ::std::vector< double > aRetval; + + if(!rInput.empty()) + { + const double nCount(rInput.size()); + aRetval.reserve(nCount); + + for(sal_uInt32 a(0); a < nCount; a++) + { + aRetval.push_back(rInput[a].solve(rInfoProvider)); + } + } + + return aRetval; + } + +} // end of namespace svgio::svgreader + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |