/* -*- 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"rebeccapurple" , Color(102, 51, 153) },
{ 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 OUString 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" ) ||
o3tl::equalsIgnoreAsciiCase(o3tl::trim(rCandidate), u"transparent" ))
{
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, sal_Unicode nSeparator)
{
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, nSeparator, nPos, aTokenValue, nLen);
skip_char(rCandidate, nSeparator, 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: */
Messung V0.5 C=96 H=96 G=95
¤ Dauer der Verarbeitung: 0.27 Sekunden
¤
*© Formatika GbR, Deutschland