mirror of
https://github.com/ONLYOFFICE/core.git
synced 2026-07-11 06:22:44 +08:00
1748 lines
46 KiB
C++
1748 lines
46 KiB
C++
/*
|
|
* (c) Copyright Ascensio System SIA 2010-2023
|
|
*
|
|
* This program is a free software product. You can redistribute it and/or
|
|
* modify it under the terms of the GNU Affero General Public License (AGPL)
|
|
* version 3 as published by the Free Software Foundation. In accordance with
|
|
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
|
|
* that Ascensio System SIA expressly excludes the warranty of non-infringement
|
|
* of any third-party rights.
|
|
*
|
|
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
|
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
|
*
|
|
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
|
|
* street, Riga, Latvia, EU, LV-1050.
|
|
*
|
|
* The interactive user interfaces in modified source and object code versions
|
|
* of the Program must display Appropriate Legal Notices, as required under
|
|
* Section 5 of the GNU AGPL version 3.
|
|
*
|
|
* Pursuant to Section 7(b) of the License you must retain the original Product
|
|
* logo when distributing the program. Pursuant to Section 7(e) we decline to
|
|
* grant you any rights under trademark law for use of our trademarks.
|
|
*
|
|
* All the Product's GUI elements, including illustrations and icon sets, as
|
|
* well as technical writing content are licensed under the terms of the
|
|
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
|
|
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
|
*
|
|
*/
|
|
#include "Utils.h"
|
|
#include "../../DesktopEditor/common/StringExt.h"
|
|
#include "../../DesktopEditor/common/Types.h"
|
|
#include "../../DesktopEditor/xml/include/xmlutils.h"
|
|
#include "../../DesktopEditor/graphics/IRenderer.h"
|
|
#include <math.h>
|
|
|
|
#ifndef M_PI
|
|
#define M_PI 3.14159265358979323846
|
|
#endif
|
|
|
|
#ifndef xpsUnitToMM
|
|
#define xpsUnitToMM(x) ((x) * 25.4 / 96)
|
|
#endif
|
|
|
|
#define IsNumber(X) \
|
|
('0' == (X)\
|
|
|| '1' == (X)\
|
|
|| '2' == (X)\
|
|
|| '3' == (X)\
|
|
|| '4' == (X)\
|
|
|| '5' == (X)\
|
|
|| '6' == (X)\
|
|
|| '7' == (X)\
|
|
|| '8' == (X)\
|
|
|| '9' == (X)\
|
|
|| '-' == (X)\
|
|
|| '.' == (X)\
|
|
|| 'e' == (X)\
|
|
|| 'E' == (X))
|
|
|
|
#define GetChar(STRING, POS) STRING[POS++]
|
|
#define LookChar(STRING, POS) STRING[POS]
|
|
|
|
#ifndef SQR
|
|
#define SQR(X) ((X)*(X))
|
|
#endif
|
|
|
|
#ifndef SQRT
|
|
#define SQRT(X) (sqrt(X))
|
|
#endif
|
|
|
|
namespace XPSEllipse
|
|
{
|
|
double AngToEllPrm (double fAngle, double fXRad, double fYRad)
|
|
{
|
|
// Function for converting a real angle to parametric ellipse representation
|
|
// i.e. x = a cos(t) y = b sin(t) - parametric ellipse representation.
|
|
// x = r cos(p), y = r sin(p) => t = atan2( sin(p) / b, cos(p) / a );
|
|
return atan2(sin(fAngle) / fYRad, cos(fAngle) / fXRad);
|
|
}
|
|
void CurveTo (IRenderer* pRenderer, Aggplus::CMatrix& oTransform, double dCX1, double dCY1, double dCX2, double dCY2, double dX2, double dY2)
|
|
{
|
|
oTransform.TransformPoint(dCX1, dCY1);
|
|
oTransform.TransformPoint(dCX2, dCY2);
|
|
oTransform.TransformPoint(dX2, dY2);
|
|
pRenderer->PathCommandCurveTo(xpsUnitToMM(dCX1), xpsUnitToMM(dCY1), xpsUnitToMM(dCX2), xpsUnitToMM(dCY2), xpsUnitToMM(dX2), xpsUnitToMM(dY2));
|
|
}
|
|
void LineTo (IRenderer* pRenderer, Aggplus::CMatrix& oTransform, double dX, double dY)
|
|
{
|
|
oTransform.TransformPoint(dX, dY);
|
|
pRenderer->PathCommandLineTo(xpsUnitToMM(dX), xpsUnitToMM(dY));
|
|
}
|
|
void MoveTo (IRenderer* pRenderer, Aggplus::CMatrix& oTransform, double dX, double dY)
|
|
{
|
|
oTransform.TransformPoint(dX, dY);
|
|
pRenderer->PathCommandMoveTo(xpsUnitToMM(dX), xpsUnitToMM(dY));
|
|
}
|
|
void EllipseArc3 (IRenderer* pRenderer, Aggplus::CMatrix& oTransform, double fX, double fY, double fXRad, double fYRad, double dAngle1, double dAngle2, double *pfXCur, double *pfYCur, bool bClockDirection)
|
|
{
|
|
// Calculate start, end and control points
|
|
double fX1 = 0.0, fX2 = 0.0, fY1 = 0.0, fY2 = 0.0;
|
|
double fCX1 = 0.0, fCX2 = 0.0, fCY1 = 0.0, fCY2 = 0.0;
|
|
|
|
double fAlpha = sin(dAngle2 - dAngle1) * (sqrt(4.0 + 3.0 * tan((dAngle2 - dAngle1) / 2.0) * tan((dAngle2 - dAngle1) / 2.0)) - 1.0) / 3.0;
|
|
|
|
double fKoef = 1;
|
|
|
|
fX1 = fX + fXRad * cos(dAngle1);
|
|
fY1 = fY + fYRad * sin(dAngle1);
|
|
|
|
fX2 = fX + fXRad * cos(dAngle2);
|
|
fY2 = fY + fYRad * sin(dAngle2);
|
|
|
|
fCX1 = fX1 - fAlpha * fXRad * sin(dAngle1);
|
|
fCY1 = fY1 + fAlpha * fYRad * cos(dAngle1);
|
|
|
|
fCX2 = fX2 + fAlpha * fXRad * sin(dAngle2);
|
|
fCY2 = fY2 - fAlpha * fYRad * cos(dAngle2);
|
|
|
|
if (!bClockDirection)
|
|
{
|
|
CurveTo(pRenderer, oTransform, fCX1, fCY1, fCX2, fCY2, fX2, fY2);
|
|
|
|
*pfXCur = fX2;
|
|
*pfYCur = fY2;
|
|
}
|
|
else
|
|
{
|
|
CurveTo(pRenderer, oTransform, fCX2, fCY2, fCX1, fCY1, fX1, fY1);
|
|
|
|
*pfXCur = fX1;
|
|
*pfYCur = fY1;
|
|
}
|
|
}
|
|
void EllipseArc2 (IRenderer* pRenderer, Aggplus::CMatrix& oTransform, double fX, double fY, double fXRad, double fYRad, double fAngle1, double fAngle2, bool bClockDirection)
|
|
{
|
|
// Convert angles to radians
|
|
double dAngle1 = fAngle1 * 3.141592 / 180;
|
|
double dAngle2 = fAngle2 * 3.141592 / 180;
|
|
|
|
// Determine in which quadrants the start and end points are located
|
|
unsigned int nFirstPointQuard = int(fAngle1) / 90 + 1;
|
|
unsigned int nSecondPointQuard = int(fAngle2) / 90 + 1;
|
|
nSecondPointQuard = std::min((unsigned int)4, std::max((unsigned int)1, nSecondPointQuard));
|
|
nFirstPointQuard = std::min((unsigned int)4, std::max((unsigned int)1, nFirstPointQuard));
|
|
// Draw a line to the starting point of the arc
|
|
double fStartX = 0.0, fStartY = 0.0, fEndX = 0.0, fEndY = 0.0;
|
|
|
|
fStartX = fX + fXRad * cos(AngToEllPrm(dAngle1, fXRad, fYRad));
|
|
fStartY = fY + fYRad * sin(AngToEllPrm(dAngle1, fXRad, fYRad));
|
|
|
|
LineTo(pRenderer, oTransform, fStartX, fStartY);
|
|
|
|
// Next we draw by quadrants
|
|
|
|
double fCurX = fStartX, fCurY = fStartY;
|
|
double dStartAngle = dAngle1;
|
|
double dEndAngle = 0;
|
|
|
|
if (!bClockDirection)
|
|
{
|
|
for (unsigned int nIndex = nFirstPointQuard; nIndex <= nSecondPointQuard; nIndex++)
|
|
{
|
|
if (nIndex == nSecondPointQuard)
|
|
dEndAngle = dAngle2;
|
|
else
|
|
dEndAngle = (90 * (nIndex)) * 3.141592f / 180;
|
|
if (!(nIndex == nFirstPointQuard))
|
|
dStartAngle = (90 * (nIndex - 1)) * 3.141592f / 180;
|
|
|
|
EllipseArc3(pRenderer, oTransform, fX, fY, fXRad, fYRad, AngToEllPrm(dStartAngle, fXRad, fYRad), AngToEllPrm(dEndAngle, fXRad, fYRad), &fEndX, &fEndY, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (unsigned int nIndex = nFirstPointQuard; nIndex >= nSecondPointQuard; nIndex--)
|
|
{
|
|
if (nIndex == nFirstPointQuard)
|
|
dStartAngle = dAngle1;
|
|
else
|
|
dStartAngle = (90 * (nIndex)) * 3.141592f / 180;
|
|
if (!(nIndex == nSecondPointQuard))
|
|
dEndAngle = (90 * (nIndex - 1)) * 3.141592f / 180;
|
|
else
|
|
dEndAngle = dAngle2;
|
|
|
|
EllipseArc3(pRenderer, oTransform, fX, fY, fXRad, fYRad, AngToEllPrm(dStartAngle, fXRad, fYRad), AngToEllPrm(dEndAngle, fXRad, fYRad), &fEndX, &fEndY, false);
|
|
}
|
|
}
|
|
}
|
|
void EllipseArc (IRenderer* pRenderer, Aggplus::CMatrix& oTransform, double fX, double fY, double fXRad, double fYRad, double fAngle1, double fAngle2, bool bClockDirection)
|
|
{
|
|
while (fAngle1 < 0)
|
|
fAngle1 += 360;
|
|
|
|
while (fAngle1 > 360)
|
|
fAngle1 -= 360;
|
|
|
|
while (fAngle2 < 0)
|
|
fAngle2 += 360;
|
|
|
|
while (fAngle2 >= 360)
|
|
fAngle2 -= 360;
|
|
|
|
if (!bClockDirection)
|
|
{
|
|
if (fAngle1 <= fAngle2)
|
|
EllipseArc2(pRenderer, oTransform, fX, fY, fXRad, fYRad, fAngle1, fAngle2, false);
|
|
else
|
|
{
|
|
EllipseArc2(pRenderer, oTransform, fX, fY, fXRad, fYRad, fAngle1, 360, false);
|
|
EllipseArc2(pRenderer, oTransform, fX, fY, fXRad, fYRad, 0, fAngle2, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (fAngle1 >= fAngle2)
|
|
EllipseArc2(pRenderer, oTransform, fX, fY, fXRad, fYRad, fAngle1, fAngle2, true);
|
|
else
|
|
{
|
|
EllipseArc2(pRenderer, oTransform, fX, fY, fXRad, fYRad, fAngle1, 0, true);
|
|
EllipseArc2(pRenderer, oTransform, fX, fY, fXRad, fYRad, 360, fAngle2, true);
|
|
}
|
|
}
|
|
}
|
|
void Ellipse (IRenderer* pRenderer, Aggplus::CMatrix& oTransform, double fX, double fY, double fXRad, double fYRad)
|
|
{
|
|
MoveTo(pRenderer, oTransform, fX - fXRad, fY);
|
|
|
|
double c_fKappa = 0.552;
|
|
CurveTo(pRenderer, oTransform, fX - fXRad, fY + fYRad * c_fKappa, fX - fXRad * c_fKappa, fY + fYRad, fX, fY + fYRad);
|
|
CurveTo(pRenderer, oTransform, fX + fXRad * c_fKappa, fY + fYRad, fX + fXRad, fY + fYRad * c_fKappa, fX + fXRad, fY);
|
|
CurveTo(pRenderer, oTransform, fX + fXRad, fY - fYRad * c_fKappa, fX + fXRad * c_fKappa, fY - fYRad, fX, fY - fYRad);
|
|
CurveTo(pRenderer, oTransform, fX - fXRad * c_fKappa, fY - fYRad, fX - fXRad, fY - fYRad * c_fKappa, fX - fXRad, fY);
|
|
}
|
|
void AppendEllipseArc(IRenderer* pRenderer, Aggplus::CMatrix& oTransform, double fX, double fY, double fXRad, double fYRad, double fStartAngle, double fEndAngle, bool bClockDirection)
|
|
{
|
|
if (fXRad <= 0 || fYRad <= 0)
|
|
return;
|
|
|
|
if (fabs(fEndAngle - fStartAngle) >= 360) // Full ellipse
|
|
{
|
|
Ellipse(pRenderer, oTransform, fX, fY, fXRad, fYRad);
|
|
}
|
|
else // Ellipse arc
|
|
{
|
|
EllipseArc(pRenderer, oTransform, fX, fY, fXRad, fYRad, fStartAngle, fEndAngle, bClockDirection);
|
|
}
|
|
}
|
|
inline void GetEllipseKoefs (const double& dX1, const double& dY1, const double& dRadX, const double& dRadY, double& dA1, double& dB1, double& dA2, double& dB2, double& dC2, double& dRoot)
|
|
{
|
|
// Y = A1 * X + B1
|
|
dA1 = -dX1 / dY1 * SQR(dRadY) / SQR(dRadX);
|
|
dB1 = (SQR(dX1) / SQR(dRadX) + SQR(dY1) / SQR(dRadY)) * SQR(dRadY) / (2 * dY1);
|
|
|
|
// Get the quadratic equation A2 * X^2 + B2 * X + C2 = 0 (where A2 != 0 in our case)
|
|
dA2 = SQR(dA1) / SQR(dRadY) + 1 / SQR(dRadX);
|
|
dB2 = 2 * dA1 * dB1 / SQR(dRadY);
|
|
dC2 = SQR(dB1) / SQR(dRadY) - 1;
|
|
|
|
dRoot = (SQR(dB2) - 4 * dA2 * dC2);
|
|
}
|
|
inline bool GetEllipsesCommonCase (const double& dX1, const double& dY1, double& dRadX, double& dRadY, double& dCx1, double& dCy1, double& dCx2, double& dCy2)
|
|
{
|
|
double dA1, dB1, dA2, dB2, dC2, dRoot;
|
|
GetEllipseKoefs(dX1, dY1, dRadX, dRadY, dA1, dB1, dA2, dB2, dC2, dRoot);
|
|
if (dRoot < 0)
|
|
{
|
|
double dK1 = (SQR(dA1) + SQR(dRadY) / SQR(dRadX)); // dK1 is always positive (so we can safely divide by it)
|
|
double dK2 = 2 * dA1 * dB1;
|
|
double dK3 = SQR(dB1);
|
|
double dK4 = dK3 - SQR(dK2) / (4 * dK1);
|
|
if (dK4 < 0)
|
|
return false; // This should not happen
|
|
|
|
double dNewRadY = SQRT(dK4);
|
|
double dKoef = dNewRadY / dRadY;
|
|
if (dKoef > 1)
|
|
{
|
|
dRadX *= dKoef;
|
|
dRadY *= dKoef;
|
|
GetEllipseKoefs(dX1, dY1, dRadX, dRadY, dA1, dB1, dA2, dB2, dC2, dRoot);
|
|
}
|
|
}
|
|
|
|
if (dRoot < -0.001)
|
|
{
|
|
// This should not happen
|
|
return false;
|
|
}
|
|
else if (dRoot < 0)
|
|
{
|
|
dCx1 = -dB2 / (2 * dA2);
|
|
dCx2 = dCx1;
|
|
}
|
|
else
|
|
{
|
|
dCx1 = (-dB2 + SQRT(dRoot)) / (2 * dA2);
|
|
dCx2 = (-dB2 - SQRT(dRoot)) / (2 * dA2);
|
|
}
|
|
|
|
dCy1 = dA1 * dCx1 + dB1;
|
|
dCy2 = dA1 * dCx2 + dB1;
|
|
return true;
|
|
}
|
|
inline bool GetEllipsesVerticalCase(const double& dX1, double& dRadX, double& dRadY, double& dCx1, double& dCy1, double& dCx2, double& dCy2)
|
|
{
|
|
if (abs(dX1) < 0.001)
|
|
return false;
|
|
|
|
// Centers of the sought ellipses lie on a vertical line
|
|
dCx1 = dX1 / 2;
|
|
dCx2 = dCx1;
|
|
|
|
double dRoot = SQR(dRadY) - SQR(dRadY) / SQR(dRadX) * SQR(dCx1);
|
|
if (dRoot < 0)
|
|
{
|
|
double dNewRadX = dX1 / 2;
|
|
double dKoef = dNewRadX / dRadX;
|
|
if (dKoef > 1)
|
|
{
|
|
dRadX *= dKoef;
|
|
dRadY *= dKoef;
|
|
dRoot = SQR(dRadY) - SQR(dRadY) / SQR(dRadX) * SQR(dCx1);
|
|
}
|
|
}
|
|
|
|
if (dRoot < -0.001)
|
|
{
|
|
// This should not happen
|
|
return false;
|
|
}
|
|
else if (dRoot < 0)
|
|
{
|
|
dCy1 = 0;
|
|
dCy2 = 0;
|
|
}
|
|
else
|
|
{
|
|
dCy1 = SQRT(dRoot);
|
|
dCy2 = -dCy1;
|
|
}
|
|
return true;
|
|
}
|
|
inline bool GetEllipses (const double& dX1, const double& dY1, double& dRadX, double& dRadY, double& dCx1, double& dCy1, double& dCx2, double& dCy2)
|
|
{
|
|
if (abs(dY1) > 0.001)
|
|
return GetEllipsesCommonCase(dX1, dY1, dRadX, dRadY, dCx1, dCy1, dCx2, dCy2);
|
|
else
|
|
return GetEllipsesVerticalCase(dX1, dRadX, dRadY, dCx1, dCy1, dCx2, dCy2);
|
|
}
|
|
}
|
|
|
|
namespace XPS
|
|
{
|
|
|
|
static inline double GetEllipseAngle(const double& dCx, const double& dCy, const double& dRadX, const double dRadY, const double& dX, const double& dY)
|
|
{
|
|
// Determine the quadrant
|
|
int nQuarter = -1;
|
|
if (dX >= dCx)
|
|
{
|
|
if (dY <= dCy)
|
|
nQuarter = 3;
|
|
else
|
|
nQuarter = 0;
|
|
}
|
|
else
|
|
{
|
|
if (dY <= dCy)
|
|
nQuarter = 2;
|
|
else
|
|
nQuarter = 1;
|
|
}
|
|
|
|
double dDist = SQRT(SQR(dX - dCx) + SQR(dY - dCy));
|
|
double dRadAngle = asin(abs(dY - dCy) / dDist);
|
|
|
|
double dAngle = dRadAngle * 180 / 3.14159265358979323846;
|
|
switch (nQuarter)
|
|
{
|
|
case 1: dAngle = 180 - dAngle; break;
|
|
case 2: dAngle = 180 + dAngle; break;
|
|
case 3: dAngle = 360 - dAngle; break;
|
|
}
|
|
|
|
return dAngle;
|
|
}
|
|
static inline bool SkipWhiteSpaces(const wchar_t* wsString, int& nPos, const int& nLen)
|
|
{
|
|
while (1)
|
|
{
|
|
if (nPos >= nLen)
|
|
return false;
|
|
wchar_t wChar = LookChar(wsString, nPos);
|
|
if (' ' == wChar)
|
|
{
|
|
nPos++;
|
|
continue;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
static inline bool SkipWhiteSpacesAndCommas(const wchar_t* wsString, int& nPos, const int& nLen)
|
|
{
|
|
while (1)
|
|
{
|
|
if (nPos >= nLen)
|
|
return false;
|
|
wchar_t wChar = LookChar(wsString, nPos);
|
|
if (' ' == wChar || ',' == wChar)
|
|
{
|
|
nPos++;
|
|
continue;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
static inline double GetDouble(const wchar_t* wsString, int& nPos, const int& nLen)
|
|
{
|
|
if (!SkipWhiteSpacesAndCommas(wsString, nPos, nLen))
|
|
return 0.0;
|
|
|
|
wchar_t wChar = GetChar(wsString, nPos);
|
|
if (IsNumber(wChar))
|
|
{
|
|
bool bNegative = false;
|
|
int nInt = 0;
|
|
double dFloat = 0.0, dScale = 0.1;
|
|
|
|
if ('-' == wChar)
|
|
{
|
|
bNegative = true;
|
|
}
|
|
else if ('.' == wChar)
|
|
{
|
|
goto doReal;
|
|
}
|
|
else
|
|
{
|
|
nInt = wChar - '0';
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
wChar = LookChar(wsString, nPos);
|
|
if (isdigit(wChar))
|
|
{
|
|
nPos++;
|
|
nInt = nInt * 10 + (wChar - '0');
|
|
}
|
|
else if ('.' == wChar)
|
|
{
|
|
nPos++;
|
|
dFloat = (double)nInt;
|
|
goto doReal;
|
|
}
|
|
else if ('e' == wChar || 'E' == wChar)
|
|
{
|
|
nPos++;
|
|
dFloat = (double)nInt;
|
|
goto doExponent;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (bNegative ? (double)(-nInt) : (double)nInt);
|
|
|
|
doReal:
|
|
|
|
while (1)
|
|
{
|
|
wChar = LookChar(wsString, nPos);
|
|
|
|
if ('e' == wChar || 'E' == wChar)
|
|
{
|
|
nPos++;
|
|
goto doExponent;
|
|
}
|
|
else if (!isdigit(wChar))
|
|
break;
|
|
|
|
nPos++;
|
|
dFloat = dFloat + dScale * (wChar - '0');
|
|
dScale *= 0.1;
|
|
}
|
|
|
|
return (bNegative ? (double)(-dFloat) : (double)dFloat);
|
|
|
|
doExponent:
|
|
|
|
wChar = GetChar(wsString, nPos);
|
|
bool bNegativeExponent = false;
|
|
int nExp = 0;
|
|
if ('-' == wChar)
|
|
{
|
|
bNegativeExponent = true;
|
|
}
|
|
else if ('+' == wChar)
|
|
{
|
|
bNegativeExponent = false;
|
|
}
|
|
else
|
|
{
|
|
nExp = wChar - '0';
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
wChar = LookChar(wsString, nPos);
|
|
if (!isdigit(wChar))
|
|
break;
|
|
|
|
nPos++;
|
|
nExp = nExp * 10 + (wChar - '0');
|
|
}
|
|
|
|
dFloat = (bNegative ? (double)(-dFloat) : (double)dFloat);
|
|
nExp = std::max(20, std::min(0, nExp));
|
|
|
|
while (nExp)
|
|
{
|
|
if (bNegativeExponent)
|
|
dFloat /= 10;
|
|
else
|
|
dFloat *= 10;
|
|
|
|
nExp--;
|
|
}
|
|
|
|
return dFloat;
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
static inline int GetInt (const wchar_t* wsString, int& nPos, const int& nLen)
|
|
{
|
|
if (!SkipWhiteSpaces(wsString, nPos, nLen))
|
|
return 0;
|
|
|
|
wchar_t wChar = GetChar(wsString, nPos);
|
|
if (IsNumber(wChar))
|
|
{
|
|
bool bNegative = false;
|
|
int nInt = 0;
|
|
|
|
if ('-' == wChar)
|
|
{
|
|
bNegative = true;
|
|
}
|
|
else
|
|
{
|
|
nInt = wChar - '0';
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
wChar = LookChar(wsString, nPos);
|
|
if (isdigit(wChar))
|
|
{
|
|
nPos++;
|
|
nInt = nInt * 10 + (wChar - '0');
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (bNegative ? -nInt : nInt);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
static inline bool GetBool(const wchar_t* wsString, int& nPos, const int& nLen)
|
|
{
|
|
if (!SkipWhiteSpacesAndCommas(wsString, nPos, nLen))
|
|
return false;
|
|
|
|
int nBufPos = 0;
|
|
wchar_t pBuffer[32];
|
|
memset(pBuffer, 0x00, sizeof(wchar_t) * 32);
|
|
|
|
wchar_t wChar = LookChar(wsString, nPos);
|
|
while (' ' != wChar)
|
|
{
|
|
pBuffer[nBufPos++] = wChar;
|
|
nPos++;
|
|
|
|
if (nPos >= nLen)
|
|
break;
|
|
|
|
wChar = LookChar(wsString, nPos);
|
|
}
|
|
|
|
CWString wsBool(pBuffer, false, nBufPos);
|
|
if ((wsBool == L"true") || (wsBool == L"t") || (wsBool == L"1") || (wsBool == L"on"))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
static inline void AppendArcTo(const wchar_t* wsString, int& nPos, const int& nLen, double& dCurX, double& dCurY, IRenderer* pRenderer,bool bRelative)
|
|
{
|
|
double dRadX = GetDouble(wsString, nPos, nLen);
|
|
double dRadY = GetDouble(wsString, nPos, nLen);
|
|
double dAngle = GetDouble(wsString, nPos, nLen);
|
|
|
|
bool bIsLarge = !GetBool(wsString, nPos, nLen);
|
|
bool bClockwise = !GetBool(wsString, nPos, nLen);
|
|
|
|
double dX0 = dCurX;
|
|
double dY0 = dCurY;
|
|
double dX1 = GetDouble(wsString, nPos, nLen);
|
|
double dY1 = GetDouble(wsString, nPos, nLen);
|
|
|
|
if (bRelative)
|
|
{
|
|
dX1 += dCurX;
|
|
dY1 += dCurY;
|
|
}
|
|
|
|
double _dX1 = dX1;
|
|
double _dY1 = dY1;
|
|
if (abs(dRadX) < 0.001 || abs(dRadY) < 0.001)
|
|
{
|
|
pRenderer->PathCommandLineTo(_dX1, _dY1);
|
|
dCurX = dX1;
|
|
dCurY = dY1;
|
|
return;
|
|
}
|
|
|
|
// Move point X0, Y0 to the origin and rotate by the inverse angle so that the sought ellipses are positioned correctly
|
|
Aggplus::CMatrix oTransform, oInverse;
|
|
oTransform.Rotate(-dAngle);
|
|
oTransform.Translate(-dX0, -dY0);
|
|
oInverse.Translate(dX0, dY0);
|
|
oInverse.Rotate(dAngle);
|
|
|
|
double dTestX = 0, dTestY = 0;
|
|
oTransform.TransformPoint(dX1, dY1);
|
|
oInverse.TransformPoint(dTestX, dTestY);
|
|
|
|
double dCx1, dCy1, dCx2, dCy2;
|
|
// Find ellipses whose intersection points are (0, 0) and (dX1, dY1)
|
|
if (!XPSEllipse::GetEllipses(dX1, dY1, dRadX, dRadY, dCx1, dCy1, dCx2, dCy2))
|
|
{
|
|
// This should not happen
|
|
pRenderer->PathCommandLineTo(_dX1, _dY1);
|
|
dCurX = _dX1;
|
|
dCurY = _dY1;
|
|
return;
|
|
}
|
|
|
|
//double dTest1 = SQR(dCx1) / SQR(dRadX) + SQR(dCy1) / SQR(dRadY);
|
|
//double dTest2 = SQR(dCx1 - dX1) / SQR(dRadX) + SQR(dCy1 - dY1) / SQR(dRadY);
|
|
//double dTest3 = SQR(dCx2) / SQR(dRadX) + SQR(dCy2) / SQR(dRadY);
|
|
//double dTest4 = SQR(dCx2 - dX1) / SQR(dRadX) + SQR(dCy2 - dY1) / SQR(dRadY);
|
|
|
|
// Now we have 2 ellipses, we need to determine the arc that we need to draw.
|
|
// First, let's find the angles of the start and end points for both ellipses.
|
|
double dAngleStart1 = GetEllipseAngle(dCx1, dCy1, dRadX, dRadY, 0, 0);
|
|
double dAngleEnd1 = GetEllipseAngle(dCx1, dCy1, dRadX, dRadY, dX1, dY1);
|
|
|
|
if (dAngleEnd1 < dAngleStart1)
|
|
dAngleEnd1 += 360;
|
|
|
|
double dAngleStart2 = GetEllipseAngle(dCx2, dCy2, dRadX, dRadY, 0, 0);
|
|
double dAngleEnd2 = GetEllipseAngle(dCx2, dCy2, dRadX, dRadY, dX1, dY1);
|
|
|
|
if (dAngleEnd2 < dAngleStart2)
|
|
dAngleEnd2 += 360;
|
|
|
|
double dCx, dCy, dAngleStart, dAngleEnd;
|
|
if ((dAngleEnd1 - dAngleStart1 > 180 && ((bIsLarge && !bClockwise) || (!bIsLarge && bClockwise)))
|
|
|| (dAngleEnd1 - dAngleStart1 <= 180 && ((bIsLarge && bClockwise) || (!bIsLarge && !bClockwise))))
|
|
{
|
|
dCx = dCx1;
|
|
dCy = dCy1;
|
|
dAngleStart = dAngleStart1;
|
|
dAngleEnd = dAngleEnd1;
|
|
}
|
|
else
|
|
{
|
|
dCx = dCx2;
|
|
dCy = dCy2;
|
|
dAngleStart = dAngleStart2;
|
|
dAngleEnd = dAngleEnd2;
|
|
}
|
|
|
|
// Approximate the ellipse with Bezier curves
|
|
XPSEllipse::AppendEllipseArc(pRenderer, oInverse, dCx, dCy, dRadX, dRadY, dAngleStart, dAngleEnd, bClockwise);
|
|
dCurX = _dX1;
|
|
dCurY = _dY1;
|
|
}
|
|
}
|
|
|
|
namespace XPS
|
|
{
|
|
int GetDigit(wchar_t wChar)
|
|
{
|
|
if (wChar >= '0' && wChar <= '9')
|
|
return (int)(wChar - '0');
|
|
if (wChar >= 'a' && wChar <= 'f')
|
|
return 10 + (int)(wChar - 'a');
|
|
if (wChar >= 'A' && wChar <= 'F')
|
|
return 10 + (int)(wChar - 'A');
|
|
|
|
return 0;
|
|
}
|
|
bool IsAlpha(wchar_t wChar)
|
|
{
|
|
return (((wChar >= 'A') && (wChar <= 'Z')) || ((wChar >= 'a') && (wChar <= 'z')));
|
|
}
|
|
double GetDouble(const CWString& wsString)
|
|
{
|
|
std::wstring sTmp(wsString.c_str(), wsString.size());
|
|
return std::stod(sTmp);
|
|
}
|
|
double GetDouble(const std::wstring& wsString)
|
|
{
|
|
return std::stod(wsString.c_str());
|
|
}
|
|
int GetInteger(const std::wstring& wsString)
|
|
{
|
|
return std::stoi(wsString.c_str());
|
|
}
|
|
bool GetBool(const std::wstring& wsString)
|
|
{
|
|
std::wstring wsStr = wsString;
|
|
NSStringExt::ToLower(wsStr);
|
|
if ((wsStr == L"true") || (wsStr == L"t") || (wsStr == L"1") || (wsStr == L"on"))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
void GetBgra(const std::wstring& wsString, int& nBgr, int& nAlpha)
|
|
{
|
|
if (L'#' == wsString[0])
|
|
{
|
|
std::wstring wsStr = wsString.substr(1);
|
|
|
|
while (wsStr.length() < 6)
|
|
wsStr = L"0" + wsStr;
|
|
|
|
while (wsStr.length() < 8)
|
|
wsStr = L"F" + wsStr;
|
|
|
|
nAlpha = GetDigit(wsStr[0]);
|
|
nAlpha = nAlpha << 4;
|
|
nAlpha += GetDigit(wsStr[1]);
|
|
|
|
nBgr = GetDigit(wsStr[6]);
|
|
nBgr = nBgr << 4;
|
|
nBgr += GetDigit(wsStr[7]);
|
|
nBgr = nBgr << 4;
|
|
nBgr += GetDigit(wsStr[4]);
|
|
nBgr = nBgr << 4;
|
|
nBgr += GetDigit(wsStr[5]);
|
|
nBgr = nBgr << 4;
|
|
nBgr += GetDigit(wsStr[2]);
|
|
nBgr = nBgr << 4;
|
|
nBgr += GetDigit(wsStr[3]);
|
|
}
|
|
else if (L's' == wsString[0] && L'c' == wsString[1] && L'#' == wsString[2])
|
|
{
|
|
std::wstring wsStr = wsString.substr(3);
|
|
std::vector<std::wstring> arrElements = NSStringExt::Split(wsStr, L',');
|
|
|
|
if (3 == arrElements.size())
|
|
{
|
|
nAlpha = 255;
|
|
nBgr = (((int)(std::min(GetDouble(arrElements[2]), 1.0) * 255)) << 16) + (((int)(std::min(GetDouble(arrElements[1]), 1.0) * 255)) << 8) + ((int)(std::min(GetDouble(arrElements[0]), 1.0) * 255));
|
|
}
|
|
else if (4 == arrElements.size())
|
|
{
|
|
nAlpha = GetDouble(arrElements[0]) * 255;
|
|
nBgr = (((int)(std::min(GetDouble(arrElements[3]), 1.0) * 255)) << 16) + (((int)(std::min(GetDouble(arrElements[2]), 1.0) * 255)) << 8) + ((int)(std::min(GetDouble(arrElements[1]), 1.0) * 255));
|
|
}
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
unsigned char GetCapStyle(const std::string& wsCapStyle)
|
|
{
|
|
BYTE nCapStyle = Aggplus::LineCapFlat;
|
|
if (wsCapStyle == "Flat")
|
|
nCapStyle = Aggplus::LineCapFlat;
|
|
else if (wsCapStyle == "Round")
|
|
nCapStyle = Aggplus::LineCapRound;
|
|
else if (wsCapStyle == "Square")
|
|
nCapStyle = Aggplus::LineCapSquare;
|
|
else if (wsCapStyle == "Triangle")
|
|
nCapStyle = Aggplus::LineCapTriangle;
|
|
|
|
return nCapStyle;
|
|
}
|
|
std::wstring NormalizePath(const std::wstring& wsPath)
|
|
{
|
|
#ifdef WIN32
|
|
std::wstring wsResult = wsPath;
|
|
NSStringExt::Replace(wsResult, L"/", L"\\");
|
|
while (std::wstring::npos != wsResult.find(L"\\\\"))
|
|
{
|
|
NSStringExt::Replace(wsResult, L"\\\\", L"\\");
|
|
}
|
|
return wsResult;
|
|
#else
|
|
std::wstring wsResult = wsPath;
|
|
NSStringExt::Replace(wsResult, L"//", L"/");
|
|
NSStringExt::Replace(wsResult, L"//", L"/");
|
|
return wsResult;
|
|
#endif
|
|
}
|
|
std::wstring GetPath(const std::wstring& wsPath)
|
|
{
|
|
std::wstring wsResult;
|
|
wsResult = wsPath.substr(0, wsPath.find_last_of('/') + 1);
|
|
return wsResult;
|
|
}
|
|
std::wstring GetFileName(const std::wstring& wsPath)
|
|
{
|
|
int nCommaPos = wsPath.find_last_of(L'.');
|
|
int nSlashPos = wsPath.find_last_of(L'/');
|
|
|
|
if (std::wstring::npos == nCommaPos)
|
|
nCommaPos = wsPath.length();
|
|
if (std::wstring::npos == nSlashPos)
|
|
nSlashPos = -1;
|
|
|
|
if (nCommaPos < nSlashPos)
|
|
return L"";
|
|
|
|
std::wstring wsResult = wsPath.substr(nSlashPos + 1, nCommaPos - nSlashPos - 1);
|
|
return wsResult;
|
|
}
|
|
std::wstring GetFileExtension(const std::wstring& wsPath)
|
|
{
|
|
int nCommaPos = wsPath.find_last_of(L'.');
|
|
|
|
if (std::wstring::npos == nCommaPos)
|
|
return L"";
|
|
|
|
std::wstring wsResult = wsPath.substr(nCommaPos + 1);
|
|
return wsResult;
|
|
}
|
|
std::wstring RemoveNamespace(const std::wstring& wsString)
|
|
{
|
|
std::wstring wsResult;
|
|
|
|
int nPos = wsString.find(L":");
|
|
if (std::wstring::npos != nPos)
|
|
wsResult = wsString.substr(nPos + 1);
|
|
else
|
|
wsResult = wsString;
|
|
|
|
return wsResult;
|
|
}
|
|
std::vector<std::vector<std::wstring>> Split(const std::wstring& wsString, wchar_t wDelim1, wchar_t wDelim2)
|
|
{
|
|
std::vector<std::vector<std::wstring>> arrResult;
|
|
std::vector<std::wstring> arrStrings = NSStringExt::Split(wsString, wDelim1);
|
|
int nCount = arrStrings.size();
|
|
for (int nIndex = 0; nIndex < nCount; nIndex++)
|
|
{
|
|
std::vector<std::wstring> arrStr = NSStringExt::Split(arrStrings[nIndex], wDelim2);
|
|
arrResult.push_back(arrStr);
|
|
}
|
|
return arrResult;
|
|
}
|
|
void ReadAttribute(XmlUtils::CXmlLiteReader& oReader, const wchar_t* wsAttrName, std::wstring& wsAttr)
|
|
{
|
|
if (oReader.GetAttributesCount() <= 0)
|
|
return;
|
|
|
|
if (!oReader.MoveToFirstAttribute())
|
|
return;
|
|
|
|
std::wstring wsName = oReader.GetName();
|
|
|
|
while (!wsName.empty())
|
|
{
|
|
if (wsAttrName == wsName)
|
|
{
|
|
wsAttr = oReader.GetText();
|
|
break;
|
|
}
|
|
if (!oReader.MoveToNextAttribute())
|
|
break;
|
|
|
|
wsName = oReader.GetName();
|
|
}
|
|
|
|
oReader.MoveToElement();
|
|
}
|
|
void ReadAttribute(XmlUtils::CXmlLiteReader& oReader, const wchar_t* wsAttrName, CWString& wsAttr)
|
|
{
|
|
if (oReader.GetAttributesCount() <= 0)
|
|
return;
|
|
|
|
if (!oReader.MoveToFirstAttribute())
|
|
return;
|
|
|
|
CWString wsName = oReader.GetName();
|
|
while (!wsName.empty())
|
|
{
|
|
if (wsName == wsAttrName)
|
|
{
|
|
wsAttr.create(oReader.GetText(), true);
|
|
break;
|
|
}
|
|
if (!oReader.MoveToNextAttribute())
|
|
break;
|
|
|
|
wsName = oReader.GetName();
|
|
}
|
|
|
|
oReader.MoveToElement();
|
|
}
|
|
bool VmlToRenderer(const CWString& _wsString, IRenderer* pRenderer)
|
|
{
|
|
bool bWinding = false;
|
|
|
|
const wchar_t* wsString = _wsString.c_str();
|
|
int nLen = _wsString.size();
|
|
int nPos = 0;
|
|
|
|
double dCurX = 0.0, dCurY = 0.0;
|
|
double dCpX = 0.0, dCpY = 0.0;
|
|
|
|
bool bPrevCommandIsCurve = false;
|
|
wchar_t wChar = 0x00;
|
|
while (nPos < nLen)
|
|
{
|
|
if (!SkipWhiteSpacesAndCommas(wsString, nPos, nLen))
|
|
break;
|
|
|
|
wChar = GetChar(wsString, nPos);
|
|
switch (wChar)
|
|
{
|
|
case 'F':
|
|
{
|
|
if (!SkipWhiteSpacesAndCommas(wsString, nPos, nLen))
|
|
break;
|
|
|
|
bWinding = GetBool(wsString, nPos, nLen);
|
|
bPrevCommandIsCurve = false;
|
|
break;
|
|
}
|
|
case 'm':
|
|
{
|
|
dCurX += GetDouble(wsString, nPos, nLen);
|
|
dCurY += GetDouble(wsString, nPos, nLen);
|
|
pRenderer->PathCommandMoveTo(xpsUnitToMM(dCurX), xpsUnitToMM(dCurY));
|
|
bPrevCommandIsCurve = false;
|
|
break;
|
|
}
|
|
case 'M':
|
|
{
|
|
dCurX = GetDouble(wsString, nPos, nLen);
|
|
dCurY = GetDouble(wsString, nPos, nLen);
|
|
pRenderer->PathCommandMoveTo(xpsUnitToMM(dCurX), xpsUnitToMM(dCurY));
|
|
bPrevCommandIsCurve = false;
|
|
break;
|
|
}
|
|
case 'L':
|
|
case 'l':
|
|
case 'H':
|
|
case 'h':
|
|
case 'V':
|
|
case 'v':
|
|
{
|
|
while (true)
|
|
{
|
|
if (!SkipWhiteSpacesAndCommas(wsString, nPos, nLen))
|
|
break;
|
|
|
|
if (IsNumber(LookChar(wsString, nPos)))
|
|
{
|
|
switch (wChar)
|
|
{
|
|
case 'L':
|
|
{
|
|
dCurX = GetDouble(wsString, nPos, nLen);
|
|
dCurY = GetDouble(wsString, nPos, nLen);
|
|
break;
|
|
}
|
|
case 'l':
|
|
{
|
|
dCurX += GetDouble(wsString, nPos, nLen);
|
|
dCurY += GetDouble(wsString, nPos, nLen);
|
|
break;
|
|
}
|
|
case 'H':
|
|
{
|
|
dCurX = GetDouble(wsString, nPos, nLen);
|
|
break;
|
|
}
|
|
case 'h':
|
|
{
|
|
dCurX += GetDouble(wsString, nPos, nLen);
|
|
break;
|
|
}
|
|
case 'V':
|
|
{
|
|
dCurY = GetDouble(wsString, nPos, nLen);
|
|
break;
|
|
}
|
|
case 'v':
|
|
{
|
|
dCurY += GetDouble(wsString, nPos, nLen);
|
|
break;
|
|
}
|
|
}
|
|
|
|
pRenderer->PathCommandLineTo(xpsUnitToMM(dCurX), xpsUnitToMM(dCurY));
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
bPrevCommandIsCurve = false;
|
|
break;
|
|
}
|
|
case 'A':
|
|
case 'a':
|
|
{
|
|
while (true)
|
|
{
|
|
if (!SkipWhiteSpacesAndCommas(wsString, nPos, nLen))
|
|
break;
|
|
|
|
if (IsNumber(LookChar(wsString, nPos)))
|
|
AppendArcTo(wsString, nPos, nLen, dCurX, dCurY, pRenderer, 'a' == wChar ? true : false);
|
|
else
|
|
break;
|
|
}
|
|
bPrevCommandIsCurve = false;
|
|
break;
|
|
}
|
|
case 'C':
|
|
case 'c':
|
|
{
|
|
while (true)
|
|
{
|
|
if (!SkipWhiteSpacesAndCommas(wsString, nPos, nLen))
|
|
break;
|
|
|
|
if (IsNumber(LookChar(wsString, nPos)))
|
|
{
|
|
double dX1, dY1, dX2, dY2;
|
|
if ('c' == wChar)
|
|
{
|
|
dX1 = dCurX + GetDouble(wsString, nPos, nLen);
|
|
dY1 = dCurY + GetDouble(wsString, nPos, nLen);
|
|
dX2 = dCurX + GetDouble(wsString, nPos, nLen);
|
|
dY2 = dCurY + GetDouble(wsString, nPos, nLen);
|
|
dCurX += GetDouble(wsString, nPos, nLen);
|
|
dCurY += GetDouble(wsString, nPos, nLen);
|
|
}
|
|
else
|
|
{
|
|
dX1 = GetDouble(wsString, nPos, nLen);
|
|
dY1 = GetDouble(wsString, nPos, nLen);
|
|
dX2 = GetDouble(wsString, nPos, nLen);
|
|
dY2 = GetDouble(wsString, nPos, nLen);
|
|
dCurX = GetDouble(wsString, nPos, nLen);
|
|
dCurY = GetDouble(wsString, nPos, nLen);
|
|
}
|
|
pRenderer->PathCommandCurveTo(xpsUnitToMM(dX1), xpsUnitToMM(dY1), xpsUnitToMM(dX2), xpsUnitToMM(dY2), xpsUnitToMM(dCurX), xpsUnitToMM(dCurY));
|
|
dCpX = dX2;
|
|
dCpY = dY2;
|
|
bPrevCommandIsCurve = true;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 'Q':
|
|
case 'q':
|
|
{
|
|
while (true)
|
|
{
|
|
if (!SkipWhiteSpacesAndCommas(wsString, nPos, nLen))
|
|
break;
|
|
|
|
if (IsNumber(LookChar(wsString, nPos)))
|
|
{
|
|
double dX1, dY1, dEndX, dEndY;
|
|
|
|
if ('q' == wChar)
|
|
{
|
|
dX1 = dCurX + GetDouble(wsString, nPos, nLen);
|
|
dY1 = dCurY + GetDouble(wsString, nPos, nLen);
|
|
dEndX = dCurX + GetDouble(wsString, nPos, nLen);
|
|
dEndY = dCurY + GetDouble(wsString, nPos, nLen);
|
|
}
|
|
else
|
|
{
|
|
dX1 = GetDouble(wsString, nPos, nLen);
|
|
dY1 = GetDouble(wsString, nPos, nLen);
|
|
dEndX = GetDouble(wsString, nPos, nLen);
|
|
dEndY = GetDouble(wsString, nPos, nLen);
|
|
}
|
|
|
|
double dCx1 = dCurX / 3 + 2 * dX1 / 3;
|
|
double dCy1 = dCurY / 3 + 2 * dY1 / 3;
|
|
|
|
double dCx2 = dEndX / 3 + 2 * dX1 / 3;
|
|
double dCy2 = dEndY / 3 + 2 * dY1 / 3;
|
|
|
|
pRenderer->PathCommandCurveTo(xpsUnitToMM(dCx1), xpsUnitToMM(dCy1), xpsUnitToMM(dCx2), xpsUnitToMM(dCy2), xpsUnitToMM(dEndX), xpsUnitToMM(dEndY));
|
|
|
|
dCurX = dEndX;
|
|
dCurY = dEndY;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
bPrevCommandIsCurve = false;
|
|
break;
|
|
}
|
|
case 'S':
|
|
case 's':
|
|
{
|
|
while (true)
|
|
{
|
|
if (!SkipWhiteSpacesAndCommas(wsString, nPos, nLen))
|
|
break;
|
|
|
|
if (IsNumber(LookChar(wsString, nPos)))
|
|
{
|
|
double dX1, dY1, dX2, dY2;
|
|
if (!bPrevCommandIsCurve)
|
|
{
|
|
dX1 = dCurX;
|
|
dY1 = dCurY;
|
|
}
|
|
else
|
|
{
|
|
dX1 = 2 * dCurX - dCpX;
|
|
dY1 = 2 * dCurY - dCpY;
|
|
}
|
|
|
|
if ('s' == wChar)
|
|
{
|
|
dX2 = dCurX + GetDouble(wsString, nPos, nLen);
|
|
dY2 = dCurX + GetDouble(wsString, nPos, nLen);
|
|
dCurX += GetDouble(wsString, nPos, nLen);
|
|
dCurY += GetDouble(wsString, nPos, nLen);
|
|
}
|
|
else
|
|
{
|
|
dX2 = GetDouble(wsString, nPos, nLen);
|
|
dY2 = GetDouble(wsString, nPos, nLen);
|
|
dCurX = GetDouble(wsString, nPos, nLen);
|
|
dCurY = GetDouble(wsString, nPos, nLen);
|
|
}
|
|
|
|
pRenderer->PathCommandCurveTo(xpsUnitToMM(dX1), xpsUnitToMM(dY1), xpsUnitToMM(dX2), xpsUnitToMM(dY2), xpsUnitToMM(dCurX), xpsUnitToMM(dCurY));
|
|
dCpX = dX2;
|
|
dCpY = dY2;
|
|
bPrevCommandIsCurve = true;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 'Z':
|
|
case 'z':
|
|
{
|
|
pRenderer->PathCommandClose();
|
|
bPrevCommandIsCurve = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bWinding;
|
|
}
|
|
bool GetNextGlyph(const wchar_t* wsIndices, int& nIndicesPos, const int& nIndicesLen, unsigned short* pUtf16, int& nUtf16Pos, const int& nUtf16Len, TIndicesEntry& oEntry)
|
|
{
|
|
oEntry.Reset();
|
|
if (!wsIndices || nIndicesPos >= nIndicesLen)
|
|
{
|
|
if (!pUtf16 || nUtf16Pos >= nUtf16Len)
|
|
return false;
|
|
|
|
oEntry.nUnicode = pUtf16[nUtf16Pos++];
|
|
oEntry.bUnicode = true;
|
|
return true;
|
|
}
|
|
|
|
if (!SkipWhiteSpaces(wsIndices, nIndicesPos, nIndicesLen))
|
|
return false;
|
|
|
|
wchar_t wChar = LookChar(wsIndices, nIndicesPos);
|
|
|
|
int nCodeUnitCount = 1, nGlyphCount = 1;
|
|
if ('(' == wChar)
|
|
{
|
|
nIndicesPos++;
|
|
if (!SkipWhiteSpaces(wsIndices, nIndicesPos, nIndicesLen))
|
|
return false;
|
|
|
|
wChar = LookChar(wsIndices, nIndicesPos);
|
|
if (IsNumber(wChar))
|
|
nCodeUnitCount = GetInt(wsIndices, nIndicesPos, nIndicesLen);
|
|
else
|
|
nCodeUnitCount = 1;
|
|
|
|
if (!SkipWhiteSpaces(wsIndices, nIndicesPos, nIndicesLen))
|
|
return false;
|
|
|
|
wChar = LookChar(wsIndices, nIndicesPos);
|
|
if (':' == wChar)
|
|
{
|
|
nIndicesPos++;
|
|
if (!SkipWhiteSpaces(wsIndices, nIndicesPos, nIndicesLen))
|
|
return false;
|
|
|
|
wChar = LookChar(wsIndices, nIndicesPos);
|
|
if (IsNumber(wChar))
|
|
nGlyphCount = GetInt(wsIndices, nIndicesPos, nIndicesLen);
|
|
else
|
|
nGlyphCount = 1;
|
|
}
|
|
else
|
|
return false;
|
|
|
|
if (!SkipWhiteSpaces(wsIndices, nIndicesPos, nIndicesLen))
|
|
return false;
|
|
|
|
wChar = LookChar(wsIndices, nIndicesPos);
|
|
if (')' != wChar)
|
|
return false; // This should not happen
|
|
|
|
nIndicesPos++;
|
|
}
|
|
else if (oEntry.vRemainUnicodes.size() > 0)
|
|
{
|
|
nCodeUnitCount = 0;
|
|
nGlyphCount = 0;
|
|
}
|
|
|
|
|
|
if (nCodeUnitCount > 0 && nGlyphCount > 0)
|
|
{
|
|
oEntry.vRemainUnicodes.clear();
|
|
// We need to read how many real unicode values are in the
|
|
// range [pUnicode + nUnicodePos, pUnicode + nUnicodePos + nCodeUnitCount]
|
|
int nUnicodesCount = 0;
|
|
unsigned int* pUnicodes = NULL;
|
|
nCodeUnitCount = std::min(nUtf16Len - nUtf16Pos, nCodeUnitCount);
|
|
if (nCodeUnitCount)
|
|
{
|
|
pUnicodes = new unsigned int[nCodeUnitCount];
|
|
if (!pUnicodes)
|
|
return false;
|
|
|
|
unsigned short ushLeading, ushTraling;
|
|
unsigned int unCode;
|
|
int nCodeUnitPos = 0;
|
|
while (nCodeUnitPos < nCodeUnitCount)
|
|
{
|
|
ushLeading = pUtf16[nUtf16Pos + nCodeUnitPos++];
|
|
if (ushLeading < 0xD800 || ushLeading > 0xDFFF)
|
|
{
|
|
pUnicodes[nUnicodesCount++] = (unsigned int)ushLeading;
|
|
}
|
|
else if (ushLeading >= 0xDC00)
|
|
{
|
|
// This should not happen
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (nCodeUnitPos >= nCodeUnitCount)
|
|
break;
|
|
|
|
unCode = (ushLeading & 0x3FF) << 10;
|
|
ushTraling = pUtf16[nUtf16Pos + nCodeUnitPos++];
|
|
if (ushTraling < 0xDC00 || ushTraling > 0xDFFF)
|
|
{
|
|
// This should not happen
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
pUnicodes[nUnicodesCount++] = (unCode | (ushTraling & 0x3FF) + 0x10000);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Evenly distribute unicodes across glyphs, ideally their count should match.
|
|
// If there are more unicodes, we remove the extra ones; if there are fewer, we fill the missing ones with spaces.
|
|
nUnicodesCount = std::min(nUnicodesCount, nGlyphCount);
|
|
for (int nIndex = 0; nIndex < nGlyphCount; nIndex++)
|
|
{
|
|
if (nIndex < nUnicodesCount)
|
|
oEntry.vRemainUnicodes.push_back(pUnicodes[nIndex]);
|
|
else
|
|
oEntry.vRemainUnicodes.push_back(0x20);
|
|
}
|
|
|
|
if (pUnicodes)
|
|
delete[] pUnicodes;
|
|
|
|
nUtf16Pos += nCodeUnitCount;
|
|
}
|
|
|
|
if (oEntry.vRemainUnicodes.size() <= 0)
|
|
return false;
|
|
|
|
// Now we read exactly 1 glyph with possible metrics
|
|
oEntry.nUnicode = oEntry.vRemainUnicodes.at(0);
|
|
oEntry.vRemainUnicodes.erase(oEntry.vRemainUnicodes.begin());
|
|
|
|
wChar = LookChar(wsIndices, nIndicesPos);
|
|
if (!SkipWhiteSpaces(wsIndices, nIndicesPos, nIndicesLen))
|
|
return false;
|
|
|
|
if (IsNumber(wChar))
|
|
{
|
|
oEntry.nGid = GetInt(wsIndices, nIndicesPos, nIndicesLen);
|
|
oEntry.bGid = true;
|
|
}
|
|
else
|
|
{
|
|
oEntry.bGid = false;
|
|
}
|
|
|
|
if (!SkipWhiteSpaces(wsIndices, nIndicesPos, nIndicesLen))
|
|
return true;
|
|
|
|
wChar = LookChar(wsIndices, nIndicesPos);
|
|
if (',' == wChar)
|
|
{
|
|
nIndicesPos++;
|
|
|
|
if (!SkipWhiteSpaces(wsIndices, nIndicesPos, nIndicesLen))
|
|
return true;
|
|
|
|
wChar = LookChar(wsIndices, nIndicesPos);
|
|
if (IsNumber(wChar))
|
|
{
|
|
oEntry.dAdvance = GetDouble(wsIndices, nIndicesPos, nIndicesLen);
|
|
oEntry.bAdvance = true;
|
|
}
|
|
else
|
|
{
|
|
oEntry.bAdvance = false;
|
|
}
|
|
|
|
if (!SkipWhiteSpaces(wsIndices, nIndicesPos, nIndicesLen))
|
|
return true;
|
|
|
|
wChar = LookChar(wsIndices, nIndicesPos);
|
|
if (',' == wChar)
|
|
{
|
|
nIndicesPos++;
|
|
if (!SkipWhiteSpaces(wsIndices, nIndicesPos, nIndicesLen))
|
|
return true;
|
|
|
|
wChar = LookChar(wsIndices, nIndicesPos);
|
|
if (IsNumber(wChar))
|
|
{
|
|
oEntry.dHorOffset = GetDouble(wsIndices, nIndicesPos, nIndicesLen);
|
|
oEntry.bHorOffset = true;
|
|
}
|
|
else
|
|
{
|
|
oEntry.bHorOffset = false;
|
|
}
|
|
|
|
if (!SkipWhiteSpaces(wsIndices, nIndicesPos, nIndicesLen))
|
|
return true;
|
|
|
|
wChar = LookChar(wsIndices, nIndicesPos);
|
|
if (',' == wChar)
|
|
{
|
|
nIndicesPos++;
|
|
if (!SkipWhiteSpaces(wsIndices, nIndicesPos, nIndicesLen))
|
|
return true;
|
|
|
|
wChar = LookChar(wsIndices, nIndicesPos);
|
|
if (IsNumber(wChar))
|
|
{
|
|
oEntry.dVerOffset = GetDouble(wsIndices, nIndicesPos, nIndicesLen);
|
|
oEntry.bVerOffset = true;
|
|
}
|
|
else
|
|
{
|
|
oEntry.bVerOffset = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!SkipWhiteSpaces(wsIndices, nIndicesPos, nIndicesLen))
|
|
return true;
|
|
|
|
wChar = LookChar(wsIndices, nIndicesPos);
|
|
if (';' == wChar)
|
|
{
|
|
nIndicesPos++;
|
|
return true;
|
|
}
|
|
else
|
|
return false; // This should not happen
|
|
}
|
|
void ReadMatrixTransform(XmlUtils::CXmlLiteReader& oReader, CWString& wsTransform, CWString* pwsKey)
|
|
{
|
|
if (oReader.GetAttributesCount() <= 0)
|
|
return;
|
|
|
|
if (!oReader.MoveToFirstAttribute())
|
|
return;
|
|
|
|
CWString wsAttrName = oReader.GetName();
|
|
while (!wsAttrName.empty())
|
|
{
|
|
if (wsAttrName == L"Matrix")
|
|
wsTransform.create(oReader.GetText(), true);
|
|
else if (wsAttrName == L"x:Key" && pwsKey)
|
|
pwsKey->create(oReader.GetText(), true);
|
|
|
|
if (!oReader.MoveToNextAttribute())
|
|
break;
|
|
|
|
wsAttrName = oReader.GetName();
|
|
}
|
|
|
|
oReader.MoveToElement();
|
|
}
|
|
void ReadTransform (XmlUtils::CXmlLiteReader& oReader, CWString& wsTransform, CWString* pwsKey)
|
|
{
|
|
CWString wsNodeName;
|
|
int nCurDepth = oReader.GetDepth();
|
|
while (oReader.ReadNextSiblingNode(nCurDepth))
|
|
{
|
|
wsNodeName = oReader.GetNameNoNS();
|
|
if (wsNodeName == L"MatrixTransform")
|
|
{
|
|
ReadMatrixTransform(oReader, wsTransform, pwsKey);
|
|
}
|
|
}
|
|
}
|
|
void ReadPathGeometry (XmlUtils::CXmlLiteReader& oReader, CWString& wsData, CWString& wsTransform, CWString* pwsKey)
|
|
{
|
|
bool bEvenOdd = true;
|
|
CWString wsAttrName;
|
|
if (oReader.MoveToFirstAttribute())
|
|
{
|
|
wsAttrName = oReader.GetName();
|
|
while (!wsAttrName.empty())
|
|
{
|
|
if (wsAttrName == L"x:Key" && pwsKey)
|
|
pwsKey->create(oReader.GetText(), true);
|
|
else if (wsAttrName == L"Figures")
|
|
wsData.create(oReader.GetText(), true);
|
|
else if (wsAttrName == L"Transform")
|
|
wsTransform.create(oReader.GetText(), true);
|
|
else if (wsAttrName == L"FillRule")
|
|
{
|
|
CWString wsFillingRule = oReader.GetText();
|
|
bEvenOdd = wsFillingRule == L"EvenOdd" ? true : false;
|
|
}
|
|
|
|
if (!oReader.MoveToNextAttribute())
|
|
break;
|
|
|
|
wsAttrName = oReader.GetName();
|
|
}
|
|
|
|
oReader.MoveToElement();
|
|
}
|
|
|
|
if (oReader.IsEmptyNode())
|
|
return;
|
|
|
|
std::wstring _wsData;
|
|
CWString wsNodeName;
|
|
int nCurDepth = oReader.GetDepth();
|
|
while (oReader.ReadNextSiblingNode(nCurDepth))
|
|
{
|
|
wsNodeName = oReader.GetNameNoNS();
|
|
if (wsNodeName == L"PathGeometry.Transform" && wsTransform.empty())
|
|
ReadTransform(oReader, wsTransform);
|
|
else if (wsNodeName == L"PathFigure" && wsData.empty())
|
|
ReadPathFigure(oReader, _wsData, bEvenOdd);
|
|
}
|
|
wsData.create(_wsData.c_str(), true);
|
|
}
|
|
void ReadPathFigure (XmlUtils::CXmlLiteReader& oReader, std::wstring& wsData, bool bEvenOdd)
|
|
{
|
|
// TODO: Improve string concatenation and attribute traversal here
|
|
if (oReader.IsEmptyNode())
|
|
return;
|
|
|
|
if (!bEvenOdd)
|
|
wsData += L"F 1";
|
|
|
|
std::wstring wsStartPoint;
|
|
std::wstring wsIsClosed;
|
|
ReadAttribute(oReader, L"StartPoint", wsStartPoint);
|
|
ReadAttribute(oReader, L"IsClosed", wsIsClosed);
|
|
wsData += L" M " + wsStartPoint;
|
|
|
|
std::wstring wsNodeName;
|
|
std::wstring wsText;
|
|
int nCurDepth = oReader.GetDepth();
|
|
while (oReader.ReadNextSiblingNode(nCurDepth))
|
|
{
|
|
wsNodeName = oReader.GetNameNoNS();
|
|
wsText.empty();
|
|
if (L"PolyLineSegment" == wsNodeName)
|
|
{
|
|
ReadAttribute(oReader, L"Points", wsText);
|
|
wsData += L" L " + wsText;
|
|
}
|
|
else if (L"PolyBezierSegment" == wsNodeName)
|
|
{
|
|
ReadAttribute(oReader, L"Points", wsText);
|
|
wsData += L" C " + wsText;
|
|
}
|
|
else if (L"PolyQuadraticBezierSegment" == wsNodeName)
|
|
{
|
|
ReadAttribute(oReader, L"Points", wsText);
|
|
wsData += L" Q " + wsText;
|
|
}
|
|
else if (L"ArcSegment" == wsNodeName)
|
|
{
|
|
std::wstring wsSize, wsRotationAngle, wsIsLargeArc, wsSweepDirection, wsPoint, wsIsStroked;
|
|
if (oReader.MoveToFirstAttribute())
|
|
{
|
|
std::wstring wsAttrName = oReader.GetName();
|
|
while (!wsAttrName.empty())
|
|
{
|
|
if (L"Size" == wsAttrName)
|
|
wsSize = oReader.GetText();
|
|
else if (L"RotationAngle" == wsAttrName)
|
|
wsRotationAngle = oReader.GetText();
|
|
else if (L"IsLargeArc" == wsAttrName)
|
|
wsIsLargeArc = oReader.GetText();
|
|
else if (L"SweepDirection" == wsAttrName)
|
|
wsSweepDirection = oReader.GetText();
|
|
else if (L"Point" == wsAttrName)
|
|
wsPoint = oReader.GetText();
|
|
|
|
if (!oReader.MoveToNextAttribute())
|
|
break;
|
|
|
|
wsAttrName = oReader.GetName();
|
|
}
|
|
oReader.MoveToElement();
|
|
}
|
|
|
|
wsData += L" A " + wsSize + L" " + wsRotationAngle + L" ";
|
|
if (GetBool(wsIsLargeArc))
|
|
wsData += L"0 ";
|
|
else
|
|
wsData += L"1 ";
|
|
|
|
if (L"Counterclockwise" == wsSweepDirection)
|
|
wsData += L"0 ";
|
|
else
|
|
wsData += L"1 ";
|
|
|
|
wsData += wsPoint;
|
|
}
|
|
}
|
|
|
|
if (GetBool(wsIsClosed))
|
|
wsData += L" Z ";
|
|
}
|
|
void ReadGradientStops (XmlUtils::CXmlLiteReader& oReader, std::vector<LONG>& vColors, std::vector<double>& vPositions, const double& dOpacity)
|
|
{
|
|
if (oReader.IsEmptyNode())
|
|
return;
|
|
|
|
CWString wsNodeName, wsAttrName;
|
|
int nCurDepth = oReader.GetDepth();
|
|
while (oReader.ReadNextSiblingNode(nCurDepth))
|
|
{
|
|
wsNodeName = oReader.GetNameNoNS();
|
|
if (wsNodeName == L"GradientStop")
|
|
{
|
|
double dPos = 0;
|
|
LONG lColor = 0;
|
|
if (oReader.MoveToFirstAttribute())
|
|
{
|
|
wsAttrName = oReader.GetName();
|
|
while (!wsAttrName.empty())
|
|
{
|
|
if (wsAttrName == L"Color")
|
|
{
|
|
int nBgr, nAlpha;
|
|
ReadSTColor(oReader.GetText(), nBgr, nAlpha);
|
|
nAlpha *= dOpacity;
|
|
lColor = (nAlpha << 24 & 0xFF000000) | (nBgr & 0xFFFFFF);
|
|
}
|
|
else if (wsAttrName == L"Offset")
|
|
{
|
|
ReadSTDouble(oReader.GetText(), dPos);
|
|
}
|
|
|
|
if (!oReader.MoveToNextAttribute())
|
|
break;
|
|
|
|
wsAttrName = oReader.GetName();
|
|
}
|
|
|
|
oReader.MoveToElement();
|
|
}
|
|
vColors.push_back(lColor);
|
|
vPositions.push_back(dPos);
|
|
}
|
|
}
|
|
}
|
|
void ReadClip (XmlUtils::CXmlLiteReader& oReader, CWString& wsClip)
|
|
{
|
|
CWString wsNodeName;
|
|
int nCurDepth = oReader.GetDepth();
|
|
while (oReader.ReadNextSiblingNode(nCurDepth))
|
|
{
|
|
wsNodeName = oReader.GetNameNoNS();
|
|
if (wsNodeName == L"PathGeometry")
|
|
{
|
|
CWString wsTransform;
|
|
ReadPathGeometry(oReader, wsClip, wsTransform);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ReadSTPoint(const CWString& wsString, double& dX, double& dY)
|
|
{
|
|
int nCommaPos = 0;
|
|
while (nCommaPos < wsString.size())
|
|
{
|
|
if (wsString[nCommaPos] == ',')
|
|
break;
|
|
|
|
nCommaPos++;
|
|
}
|
|
|
|
if (nCommaPos >= wsString.size())
|
|
{
|
|
CWString wsX = wsString.c_str();
|
|
dX = GetDouble(wsX);
|
|
dY = 0;
|
|
}
|
|
else
|
|
{
|
|
CWString wsX((wchar_t*)wsString.c_str(), false, nCommaPos);
|
|
CWString wsY((wchar_t*)(wsString.c_str() + nCommaPos + 1), false, wsString.size() - nCommaPos - 1);
|
|
dX = GetDouble(wsX);
|
|
dY = GetDouble(wsY);
|
|
}
|
|
}
|
|
void ReadSTColor(const CWString& wsString, int& nBgr, int& nAlpha)
|
|
{
|
|
nBgr = 0;
|
|
nAlpha = 255;
|
|
int nLen = wsString.size();
|
|
if (nLen <= 0)
|
|
return;
|
|
|
|
const wchar_t* pBuffer = wsString.c_str();
|
|
if (L'#' == pBuffer[0])
|
|
{
|
|
nLen--;
|
|
pBuffer++;
|
|
|
|
if (6 != nLen && 8 != nLen)
|
|
return;
|
|
|
|
if (8 == nLen)
|
|
{
|
|
nAlpha = GetDigit(*pBuffer++);
|
|
nAlpha <<= 4;
|
|
nAlpha += GetDigit(*pBuffer++);
|
|
}
|
|
else
|
|
{
|
|
nAlpha = 255;
|
|
}
|
|
|
|
nBgr = GetDigit(pBuffer[4]);
|
|
nBgr <<= 4;
|
|
nBgr += GetDigit(pBuffer[5]);
|
|
nBgr <<= 4;
|
|
nBgr += GetDigit(pBuffer[2]);
|
|
nBgr <<= 4;
|
|
nBgr += GetDigit(pBuffer[3]);
|
|
nBgr <<= 4;
|
|
nBgr += GetDigit(pBuffer[0]);
|
|
nBgr <<= 4;
|
|
nBgr += GetDigit(pBuffer[1]);
|
|
}
|
|
else if (nLen >= 3 && L's' == pBuffer[0] && L'c' == pBuffer[1] && L'#' == pBuffer[2])
|
|
{
|
|
int nPos = 3;
|
|
if (nPos >= nLen)
|
|
return;
|
|
|
|
CWString wsString2;
|
|
wsString2.create(pBuffer + 3, false);
|
|
|
|
std::vector<CWString> vElements = wsString2.split(',');
|
|
if (3 == vElements.size())
|
|
{
|
|
nAlpha = 255;
|
|
nBgr = (((int)(std::min(GetDouble(vElements[2]), 1.0) * 255)) << 16) + (((int)(std::min(GetDouble(vElements[1]), 1.0) * 255)) << 8) + ((int)(std::min(GetDouble(vElements[0]), 1.0) * 255));
|
|
}
|
|
else if (4 == vElements.size())
|
|
{
|
|
nAlpha = (int)(std::min(GetDouble(vElements[0]), 1.0) * 255);
|
|
nBgr = (((int)(std::min(GetDouble(vElements[3]), 1.0) * 255)) << 16) + (((int)(std::min(GetDouble(vElements[2]), 1.0) * 255)) << 8) + ((int)(std::min(GetDouble(vElements[1]), 1.0) * 255));
|
|
}
|
|
}
|
|
}
|
|
void ReadSTColor(const CWString& wsString, LONG& lColor)
|
|
{
|
|
int nBgr, nAlpha;
|
|
ReadSTColor(wsString, nBgr, nAlpha);
|
|
lColor = (nAlpha << 24 & 0xFF000000) | (nBgr & 0xFFFFFF);
|
|
}
|
|
void ReadSTDouble(const CWString& wsString, double& dValue)
|
|
{
|
|
int nPos = 0;
|
|
dValue = GetDouble(wsString.c_str(), nPos, wsString.size());
|
|
}
|
|
}
|