#include "DocSVG.h"
#include "Util.h"
const char SVGOperation::PathOperationChar[] =
{
'M', 'L', 'H', 'V', 'C', 'S', 'Q', 'T', 'A', 'Z',
'm', 'l', 'h', 'v', 'c', 's', 'q', 't', 'a', 'z', ' ', ' ', ' '
};
const int SVGOperation::PathOperationArgs[] =
{
2, 2, 1, 1, 6, 4, 4, 2, 7, 0,
2, 2, 1, 1, 6, 4, 4, 2, 7, 0, 0, 0, 0
};
void SVGOperation::output(HPDF_Page page, float curPos[2], bool /*fill*/, PathOperation prevOp, float pathStartPos[2])
{
float pageHeight = HPDF_Page_GetHeight(page);
float x = m_values[0];
float y = m_values[1];
fprintf(stderr, "Output: OP: -%c- ARGS: %f %f \n", PathOperationChar[m_type], x, y);
switch (m_type) {
case MoveToAbs:
curPos[0] = x; curPos[1] = y;
HPDF_Page_MoveTo(page, curPos[0], pageHeight - curPos[1]);
fprintf(stderr, "move to: %f %f\n", curPos[0], curPos[1]);
break;
case LineToAbs:
curPos[0] = x; curPos[1] = y;
HPDF_Page_LineTo(page, curPos[0], pageHeight - curPos[1]);
fprintf(stderr, "line to: %f %f\n", curPos[0], curPos[1]);
break;
case HorizontalLineToAbs:
curPos[0] = x;
HPDF_Page_LineTo(page, curPos[0], pageHeight - curPos[1]);
break;
case VerticalLineToAbs:
curPos[1] = x;
HPDF_Page_LineTo(page, curPos[0], pageHeight - curPos[1]);
break;
case CurveToAbs:
HPDF_Page_CurveTo(page,
m_values[0], pageHeight - m_values[1],
m_values[2], pageHeight - m_values[3],
m_values[4], pageHeight - m_values[5]);
curPos[0] = m_values[4];
curPos[1] = m_values[5];
break;
case SmoothCurveToAbs:
HPDF_Page_CurveTo2(page,
m_values[0], pageHeight - m_values[1],
m_values[2], pageHeight - m_values[3]);
curPos[0] = m_values[2];
curPos[1] = m_values[3];
break;
case QuadraticBezierCurveToAbs:
//HPDF_Page_CurveTo2(HPDF_Page, HPDF_REAL x2, HPDF_REAL y2, HPDF_REAL x3, HPDF_REAL y3);
//HPDF_Page_CurveTo3(HPDF_Page, HPDF_REAL x1, HPDF_REAL y1, HPDF_REAL x3, HPDF_REAL y3);
// HPDF_Page_CurveTo3 // ??
HPDF_Page_CurveTo2(page,
m_values[0], pageHeight - m_values[1],
m_values[2], pageHeight - m_values[3]);
curPos[0] = m_values[2];
curPos[1] = m_values[3];
break;
case SmoothQuadraticBezierCurveToAbs:
// No idea, only has 2 args, I guess an x and y, but how does this make a curve?
break;
case EllipticalArcToAbs:
// SVG has 7 args, PDF has 5!
//HPDF_Page_Arc (HPDF_Page page, HPDF_REAL x, HPDF_REAL y,
// HPDF_REAL ray, HPDF_REAL ang1, HPDF_REAL ang2);
break;
case MoveToRel:
if (prevOp == BeginPathOperation) { // TODO: Perhaps should check this is really needed
curPos[0] = x; curPos[1] = y;
} else {
curPos[0] += x; curPos[1] += y;
}
//curPos[0] += x; curPos[1] += y;
HPDF_Page_MoveTo(page, curPos[0], pageHeight - curPos[1]);
break;
case LineToRel:
if (prevOp == BeginPathOperation) { // TODO: Perhaps should check this is really needed
curPos[0] = x; curPos[1] = y;
} else {
curPos[0] += x; curPos[1] += y;
}
HPDF_Page_LineTo(page, curPos[0], pageHeight - curPos[1]);
break;
case HorizontalLineToRel:
curPos[0] += x;
HPDF_Page_LineTo(page, curPos[0], pageHeight - curPos[1]);
break;
case VerticalLineToRel:
curPos[1] += x;
HPDF_Page_LineTo(page, curPos[0], pageHeight - curPos[1]);
break;
case CurveToRel:
// TODO: check if these are cumulatively relative, or all rel to current pos
HPDF_Page_CurveTo(page,
curPos[0] + m_values[0], pageHeight - curPos[1] - m_values[1],
curPos[0] + m_values[2], pageHeight - curPos[1] - m_values[3],
curPos[0] + m_values[4], pageHeight - curPos[1] - m_values[5]);
curPos[0] += m_values[4];
curPos[1] += m_values[5];
break;
case SmoothCurveToRel:
// TODO: check if these are cumulatively relative, or all rel to current pos
HPDF_Page_CurveTo2(page,
curPos[0] + m_values[0], pageHeight - curPos[1] - m_values[1],
curPos[0] + m_values[2], pageHeight - curPos[1] - m_values[3]);
curPos[0] += m_values[2];
curPos[1] += m_values[3];
break;
case QuadraticBezierCurveToRel:
fprintf(stderr, "TODO: %s %i\n", __FILE__, __LINE__);
break;
case SmoothQuadraticBezierCurveToRel:
fprintf(stderr, "TODO: %s %i\n", __FILE__, __LINE__);
break;
case EllipticalArcToRel:
fprintf(stderr, "TODO: %s %i\n", __FILE__, __LINE__);
break;
case ClosePath:
case AltClosePath:
// TODO: actually this means to draw a line back to the first point in the path
curPos[0] = pathStartPos[0]; curPos[1] = pathStartPos[1];
//HPDF_Page_LineTo(page, curPos[0], pageHeight - curPos[1]);
/*
if (fill)
HPDF_Page_FillStroke(page);
else
HPDF_Page_Stroke(page);
*/
break;
default:
break;
}
}
DocSVG::DocSVG(double a_scale) : m_scale(a_scale)
{
currentGradient = nullptr;
}
DocSVG::~DocSVG()
{
}
/// Visit a document.
bool DocSVG::VisitEnter( const TiXmlDocument& /* doc */ )
{
return true;
}
/// Visit a document.
bool DocSVG::VisitExit( const TiXmlDocument& /* doc */ )
{
return true;
}
/// Visit a declaration
bool DocSVG::Visit( const TiXmlDeclaration& /* declaration */ )
{
return true;
}
/// Visit a stylesheet reference
bool DocSVG::Visit( const TiXmlStylesheetReference& /* stylesheet */ )
{
return true;
}
/// Visit a comment node
bool DocSVG::Visit( const TiXmlComment& /* comment */ )
{
return true;
}
/// Visit an unknown node
bool DocSVG::Visit( const TiXmlUnknown& /* unknown */ )
{
return true;
}
// static
DocSVG::SVGStyle DocSVG::parseStyle(const char* a_str)
{
SVGStyle style = { 0.0, 0.0, 0, "", "", 0.0, 0, 0.0, 0, 0 };
std::vector<std::string> list = split(a_str, ';');
for (size_t i = 0; i < list.size(); ++i)
{
std::vector<std::string> kvp = split(list[i], ':');
std::string s = kvp[0];
if (s == "opacity") style.opacity = str2float(kvp[1]);
else if (s == "fill-opacity") style.fillOpacity = str2float(kvp[1]);
else if (s == "fill") style.fillString = kvp[1], style.fillColor = parseColor(kvp[1].c_str());
else if (s == "stroke-opacity") style.strokeOpacity = str2float(kvp[1]);
else if (s == "stroke") style.strokeString = kvp[1], style.strokeColor = parseColor(kvp[1].c_str());
else if (s == "stroke-width") style.strokeWidth = str2float(kvp[1]);
else if (s == "stroke-linecap") style.strokeLineCap = parseLineCap(kvp[1]);
else if (s == "stroke-linejoin") style.strokeLineJoin = parseLineJoin(kvp[1]);
}
return style;
}
// static
std::vector<SVGOperation> DocSVG::ParsePath(const char* a_pathData)
{
std::vector<SVGOperation> operations;
SVGOperation currentOp;
currentOp.m_type = SVGOperation::BeginPathOperation;
operations.push_back(currentOp);
currentOp.m_type = SVGOperation::MoveToAbs;
int val = 0;
const char* pathPtr = a_pathData;
while (*pathPtr) {
char* end = 0;
double v = strtod(pathPtr, &end);
if ( end != pathPtr ) {
currentOp.m_values[val] = v;
val++;
if (val == currentOp.getArgCount()) {
operations.push_back(currentOp);
// The first position in a move to op is where to go to, but after that it
// is where to draw a line to
if ( currentOp.m_type == SVGOperation::MoveToAbs )
currentOp.m_type = SVGOperation::LineToAbs;
if ( currentOp.m_type == SVGOperation::MoveToRel )
currentOp.m_type = SVGOperation::LineToRel;
val = 0;
}
pathPtr = end;
} else {
if (SVGOperation::getOperationForChar(*pathPtr) != SVGOperation::BadOperation)
{
if (val != 0) {
fprintf(stderr, "Broken SVG path data, expecting more numbers, got: -%c-\n", *pathPtr);
break;
}
if (currentOp.getArgCount() == 0) // eg: ClosePath
operations.push_back(currentOp);
currentOp.m_type = SVGOperation::getOperationForChar(*pathPtr);
}
pathPtr++;
}
}
if (val == currentOp.getArgCount()) {
operations.push_back(currentOp);
} else if (val) {
fprintf(stderr, "Broken SVG path data, expecting more numbers\n");
}
currentOp.m_type = SVGOperation::EndPathOperation;
operations.push_back(currentOp);
return operations;
}
// static
uint32_t DocSVG::parseLineCap(const std::string& s)
{
if (s == "butt") return 0;
else if (s == "round") return 1;
else if (s == "square") return 2;
printf("Unknown line-cap: %s\n", s.c_str());
return -1;
}
// static
uint32_t DocSVG::parseLineJoin(const std::string& s)
{
if (s == "round") return 0;
else if (s == "bevel") return 1;
else if (s == "miter") return 2;
printf("Unknown line-join: %s\n", s.c_str());
return -1;
}
// static
uint32_t DocSVG::parseColor(const char* a_str)
{
std::string s = a_str;
if (s == "red") return 0xffff0000;
else if (s == "blue") return 0xff0000ff;
else if (s == "green") return 0xff00ff00;
else if (s == "yellow") return 0xffffff00;
else if (s == "purple") return 0xffe00fe0;
else if (s == "cyan") return 0xff00ffff;
else if (s == "magenta") return 0xffff00ff;
else if (s == "black") return 0xff000000;
else if (s == "white") return 0xffffffff;
else if (s == "none") return 0;
return 0xff000000 | str2col(s);
}
/// Visit an element.
bool DocSVG::VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute )
{
// Inkscape:
// style="fill:#729fcf;stroke:none"
// Spec:
// fill="yellow" stroke="blue" stroke-width="5"
SVGPath path;
path.style = { 0.0, 0.0, 0, "none", "none", 0.0, 0, 0.0, 0, 0 };
if (strcmp(element.ValueTStr().c_str(), "stop") == 0) {
if (currentGradient != nullptr)
{
SVGColorStop stop;
const TiXmlAttribute* attrib = firstAttribute;
while ( attrib ) {
if (strcmp(attrib->Name(), "offset") == 0) {
stop.offset = str2float(attrib->Value());
} else if (strcmp(attrib->Name(), "style") == 0) {
std::vector<std::string> list = split(attrib->Value(), ';');
for (size_t i = 0; i < list.size(); ++i)
{
std::vector<std::string> kvp = split(list[i], ':');
std::string s = kvp[0];
if (s == "stop-color") stop.color = parseColor(kvp[1].c_str());
else if (s == "stop-opacity") stop.opacity = str2float(kvp[1]);
}
}
attrib = attrib->Next();
}
currentGradient->stops.push_back(stop);
}
}
if (strcmp(element.ValueTStr().c_str(), "linearGradient") == 0) {
SVGGradient gradient;
const TiXmlAttribute* attrib = firstAttribute;
while ( attrib ) {
if (strcmp(attrib->Name(), "id") == 0) {
gradient.name = attrib->Value();
} else if (strcmp(attrib->Name(), "xlink:href") == 0) {
gradient.stops = gradients[attrib->Value() + 1].stops;
} else if (strcmp(attrib->Name(), "x1") == 0) {
gradient.x1 = str2float(attrib->Value());
} else if (strcmp(attrib->Name(), "y1") == 0) {
gradient.y1 = str2float(attrib->Value());
} else if (strcmp(attrib->Name(), "x2") == 0) {
gradient.x2 = str2float(attrib->Value());
} else if (strcmp(attrib->Name(), "y2") == 0) {
gradient.y2 = str2float(attrib->Value());
}
attrib = attrib->Next();
}
gradients[gradient.name] = gradient;
currentGradient = &gradients[gradient.name];
}
if (strcmp(element.ValueTStr().c_str(), "path") == 0) {
const TiXmlAttribute* attrib = firstAttribute;
while ( attrib ) {
if (strcmp(attrib->Name(), "d") == 0) {
path.operations = ParsePath(attrib->Value());
} else if (strcmp(attrib->Name(), "style") == 0) {
path.style = parseStyle(attrib->Value());
} else if (strcmp(attrib->Name(), "fill") == 0) {
path.style.fillColor = parseColor(attrib->Value());
} else if (strcmp(attrib->Name(), "stroke") == 0) {
path.style.strokeColor = parseColor(attrib->Value());
} else if (strcmp(attrib->Name(), "stroke-width") == 0) {
path.style.strokeWidth = str2float(attrib->Value());
}
attrib = attrib->Next();
}
}
if (strcmp(element.ValueTStr().c_str(), "rect") == 0) {
const TiXmlAttribute* attrib = firstAttribute;
float x, y, w, h;
while ( attrib ) {
if (strcmp(attrib->Name(), "x") == 0) {
x = str2float(attrib->Value());
} else if (strcmp(attrib->Name(), "y") == 0) {
y = str2float(attrib->Value());
} else if (strcmp(attrib->Name(), "width") == 0) {
w = str2float(attrib->Value());
} else if (strcmp(attrib->Name(), "height") == 0) {
h = str2float(attrib->Value());
} else if (strcmp(attrib->Name(), "style") == 0) {
path.style = parseStyle(attrib->Value());
} else if (strcmp(attrib->Name(), "fill") == 0) {
path.style.fillColor = parseColor(attrib->Value());
} else if (strcmp(attrib->Name(), "stroke") == 0) {
path.style.strokeColor = parseColor(attrib->Value());
} else if (strcmp(attrib->Name(), "stroke-width") == 0) {
path.style.strokeWidth = str2float(attrib->Value());
}
attrib = attrib->Next();
}
SVGOperation currentOp;
currentOp.m_type = SVGOperation::BeginPathOperation;
path.operations.push_back(currentOp);
currentOp.m_type = SVGOperation::MoveToAbs;
currentOp.m_values[0] = x;
currentOp.m_values[1] = y;
path.operations.push_back(currentOp);
currentOp.m_type = SVGOperation::HorizontalLineToRel;
currentOp.m_values[0] = w;
path.operations.push_back(currentOp);
currentOp.m_type = SVGOperation::VerticalLineToRel;
currentOp.m_values[0] = h;
path.operations.push_back(currentOp);
currentOp.m_type = SVGOperation::HorizontalLineToRel;
currentOp.m_values[0] = -w;
path.operations.push_back(currentOp);
currentOp.m_type = SVGOperation::ClosePath;
path.operations.push_back(currentOp);
currentOp.m_type = SVGOperation::EndPathOperation;
path.operations.push_back(currentOp);
}
paths.push_back(path);
return true;
}
/// Visit an element.
bool DocSVG::VisitExit( const TiXmlElement& /* element */ )
{
return true;
}
/// Visit a text node
bool DocSVG::Visit( const TiXmlText& /* text */ )
{
return true;
}
void DocSVG::WriteTo(DocOutputPage* outputPage)
{
float height = outputPage->height();
HPDF_Page page = outputPage->page();
float pageHeight = HPDF_Page_GetHeight(page);
float curPos[2] = { 10.0, 10.0 };
//fprintf(stderr, "doc wxh: %f x %f\n", HPDF_Page_GetWidth(page), HPDF_Page_GetHeight(page));
/*
HPDF_Page_Rectangle(page, x1, pageHeight - y1 - h, w, h);
HPDF_Page_Circle(page, x, pageHeight - y, radius);
*/
HPDF_Page_EndText(page);
for (size_t p = 0; p < paths.size(); ++p)
{
bool strokeEnabled = paths[p].style.strokeString != "none";
bool fillEnabled = paths[p].style.fillString != "none";
uint32_t fCol = paths[p].style.fillColor;
uint32_t sCol = paths[p].style.strokeColor;
float sWidth = paths[p].style.strokeWidth;
float alpha = paths[p].style.opacity;
float penA = alpha * paths[p].style.strokeOpacity;
float fillA = alpha * paths[p].style.fillOpacity;
std::vector<SVGOperation> operations = paths[p].operations;
// Debugging
if (!strokeEnabled)
sCol = 0xFFFFFF;//0xFF00FF00;
if (!fillEnabled)
fCol = 0xFF00FF;//0xFFFF0000;
//if (!fCol)
// fCol = 0x123456;
std::string gradientStr = paths[p].style.fillString;
if (fillEnabled)
{
gradientStr = gradientStr.c_str() + 5;
gradientStr = gradientStr.substr(0, gradientStr.size()-1);
if (!gradients.count(gradientStr)) {
printf("GRADIENT NOT FOUND: --%s--\n", gradientStr.c_str());
}
}
//if (gradients.count(gradientStr))
{
HPDF_Page_GSave(page);
outputPage->setPenWidth(1.0);// sWidth * m_scale);
outputPage->setFillColor(fCol);
outputPage->setPenColor(sCol);
// outputPage->setAlpha(alpha);
outputPage->setPenWidth(sWidth * 2.82);
/*
outputPage->setAlphaPenFill(penA, fillA);
*/
HPDF_ExtGState extState = HPDF_CreateExtGState(outputPage->doc());
HPDF_ExtGState_SetAlphaStroke(extState, (strokeEnabled) ? penA : 0.0);
HPDF_ExtGState_SetAlphaFill(extState, (fillEnabled) ? fillA : 0.0);
// HPDF_ExtGState_SetBlendMode(extState, (HPDF_BlendMode)0);
HPDF_ExtGState_SetBlendMode(extState, HPDF_BM_NORMAL);
HPDF_Page_SetExtGState(page, extState);
HPDF_Page_MoveTo(page, 0.0, height);
float pathStartPos[2];
pathStartPos[0] = curPos[0];
pathStartPos[1] = curPos[1];
SVGOperation::PathOperation prevOp = SVGOperation::BadOperation;
for (size_t i = 0; i < operations.size(); i++) {
SVGOperation op = operations[i];
//op.scale(m_scale);
op.scale(2.82);
op.output(page, curPos, false, prevOp, pathStartPos);
if (prevOp == SVGOperation::BeginPathOperation) {
pathStartPos[0] = curPos[0];
pathStartPos[1] = curPos[1];
}
prevOp = op.m_type;
}
} // End gradient
printf("looking for gradient: -%s-\n", gradientStr.c_str());
if (gradients.count(gradientStr))
{
printf("found gradient: -%s-\n", gradientStr.c_str());
HPDF_Page_Clip(page);
if (strokeEnabled) // if (sCol)
HPDF_Page_Stroke(page);
else
HPDF_Page_EndPath(page);
//HPDF_Page_Eoclip(page);
const SVGGradient& gradient = gradients[gradientStr];
if (gradient.stops.size() >= 2)
{
uint32_t col1 = gradient.stops[0].color;
uint32_t col2 = gradient.stops[1].color;
float a1 = gradient.stops[0].opacity;
float a2 = gradient.stops[1].opacity;
std::vector<HPDF_GradientStop> stops;
for (const auto &stop : gradient.stops)
{
stops.emplace_back(HPDF_GradientStop{ stop.color, stop.opacity, stop.offset });
}
outputPage->setGradient(stops, gradient.x1 * 2.82, pageHeight - gradient.y1 * 2.82,
gradient.x2 * 2.82, pageHeight - gradient.y2 * 2.82);
/*
outputPage->setGradient(col1, a1, gradient.x1 * 2.82, pageHeight - gradient.y1 * 2.82,
col2, a2, gradient.x2 * 2.82, pageHeight - gradient.y2 * 2.82);
*/
HPDF_Page_GRestore(page);
}
else
{
printf("not enough stops for gradient: -%s-\n", gradientStr.c_str());
}
}
else
{
if (fillEnabled && strokeEnabled)
HPDF_Page_ClosePathFillStroke(page);
else if (strokeEnabled)
HPDF_Page_Stroke(page);
else if (fillEnabled)
HPDF_Page_Fill(page);
else
HPDF_Page_EndPath(page);
HPDF_Page_GRestore(page);
/*
if (fCol)
HPDF_Page_ClosePathFillStroke(page);
else
HPDF_Page_Stroke(page);
*/
}
/*
HPDF_Page_Stroke (HPDF_Page page)
HPDF_Page_ClosePathStroke (HPDF_Page page)
HPDF_Page_Fill (HPDF_Page page)
HPDF_Page_Eofill (HPDF_Page page) // Even-Odd Fill
HPDF_Page_FillStroke (HPDF_Page page)
HPDF_Page_EofillStroke (HPDF_Page page)
HPDF_Page_ClosePathFillStroke (HPDF_Page page)
HPDF_Page_ClosePathEofillStroke (HPDF_Page page)
HPDF_Page_EndPath (HPDF_Page page)
*/
}
/*
//
// Example of drawing triangles with vertex coloring
//
float pnts[] = { 0.0, 0.0, 100.0, 100.0, 0.0, 100.0 };
uint32_t cols[] = { 0x8092A4C6, 0x80C6A492, 0x80A4C692 };
outputPage->drawGradient(pnts, 3, (unsigned char*)cols, 4);
*/
HPDF_Page_BeginText(page);
}