Sei sulla pagina 1di 7

Parse SVG Files to Bezier Curves in C++ and Sa... https://www.codeproject.com/Articles/1252897/...

Articles » Multimedia » General Graphics » Graphics

Parse SVG Files to Bezier Curves in C++ and Save to


PDF
Michael Moroz, 18 Jul 2018

This articles describes how to open SVG files in C++ application, work with Bezier curves and export to PDF.

Download source code - 864.1 KB

Introduction
This code was written to demonstrate the process of parsing vector SVG files in C/C++ application, working with Bezier
curves, and saving the vector image to PDF. For ease of understanding, all processing in app only works in grayscale
mode. The code was written for Windows, but has no Windows-only dependencies and can be easily ported to Linux or
any embedded platform.

Scalable Vector Graphics (SVG) is a 2D vector image format widely used in Web development. The SVG format is
heavily based on XML standard. While the raster image (PNG, JPG, GIF, etc.) is composed of a fixed set of pixels, the
vector SVG image is composed of a fixed set of shapes, like circles, lines, rectangles, Bezier curves, etc. The advantage
of SVG format is that it can be easily scaled to any level without the loss of quality.

I have included a nice kitten here as a sample of SVG image:

1 of 7 01/08/2018 11:34
Parse SVG Files to Bezier Curves in C++ and Sa... https://www.codeproject.com/Articles/1252897/...

Bézier curve is a parametric curve which is used to model smooth curves that can be scaled indefinitely. Bezier curve
can be represented as a set of control points. Quadratic and cubic Bezier curves are most common. Higher degree
curves are more computationally expensive to evaluate. When more complex shapes are needed, low order Bezier
curves are patched together, producing a composite Bezier curve. A composite Bezier curve is commonly referred to as
a path.

Quadratic Bézier curve Cubic Bézier curve

The Portable Document Format (PDF) is a file format to represent documents, including text formatting, raster images,
vector graphics, etc. Vector graphics in PDF are constructed with paths too. Paths are usually composed of lines and
cubic Bezier curves, but can also be constructed from the outlines of text. Paths in PDF can be stroked, filled, clipping.
Strokes and fills can use any color set in the graphics state, including patterns.

Background
For the processing of SVG files, I used brilliant library called nanosvg. It is small and lightweight and can be used even in
embedded development projects. To compose PDF files, I have used write-only libHaru library. Though not actively
developed now (2018), it still can be used in C/C++ projects.

Using the Code


The SvgConverter class is a basic workhorse to convert SVG to PDF. The same object can be used multiple to
convert other images. You just need to call a method that is responsible for loading the other file.

//..

2 of 7 01/08/2018 11:34
Parse SVG Files to Bezier Curves in C++ and Sa... https://www.codeproject.com/Articles/1252897/...

SvgConverter converter;
converter.loadFromFile(fileInput); // load svg image to inner buffer
converter.convertToPDF(fileOutput); // parse and write image from buffer to pdf file

//..

Class structure looks like this:

class SvgConverter {

private:

NSVGimage * g_image;
std::string fileName;

public:
SvgConverter(std::string fileName);
SvgConverter();
bool isLoaded();
bool loadFromFile(std::string fileName);
bool convertToPDF(std::string fileName);

~SvgConverter();

private:
float distPtSeg(float x, float y, float px, float py, float qx, float qy);
void pdfcubicBez(HPDF_Page page, float x1, float y1, float x2, float y2,
float x3, float y3, float x4, float y4,
float tol, int level, Vector2f startPoint);
void pdfPath(HPDF_Page page, float* pts, int npts, char closed, float tol, bool
bFilled,
Vector2f startPoint);
static void error_handler(HPDF_STATUS error_no,
HPDF_STATUS detail_no,
void *user_data);
};

To approximate Bezier curve and draw it, we will use one of the properties of such curves.
Any cubic Bezier curve B from beginning to end can be divided into two curves, which together will describe the same
curve as B.

Converting and approximation method implementation, some code taken from nanosvg sample and adapted to PDF
coordinate system:

bool SvgConverter::convertToPDF(std::string fileName) {


if (fileName.empty() || !isLoaded()) return false;

HPDF_Doc pdf = HPDF_New(error_handler, NULL);


if (!pdf) return false;

HPDF_Page page = HPDF_AddPage(pdf);


float width = this->g_image->width;
float height = this->g_image->height;
Vector2f startPoint = { 0, height};

HPDF_Page_SetWidth(page, width);
HPDF_Page_SetHeight(page, height);
HPDF_Page_SetLineWidth(page, 0.1f); //initializing page

for (NSVGshape * shape = g_image->shapes; shape != NULL; shape = shape->next) {


if (!(shape->flags & NSVG_FLAGS_VISIBLE)) continue; //pass invisible shapes

float r = (float)((shape->fill.color >> 16) & 0xFF) / 255.0f;

3 of 7 01/08/2018 11:34
Parse SVG Files to Bezier Curves in C++ and Sa... https://www.codeproject.com/Articles/1252897/...

float g = (float)((shape->fill.color >> 8) & 0xFF) / 255.0f;


float b = (float)((shape->fill.color) & 0xFF) / 255.0f;
float gray = (r + g + b) / 3.0f;
gray = (gray < 0.5f) ? 0 : 1.0f;

HPDF_Page_SetGrayFill(page, gray); // sets the filling color

for (NSVGpath * path = shape->paths; path != NULL; path = path->next ){


// drawing each path in shape to pdf file
pdfPath(page, path->pts, path->npts, path->closed,
0.1f, shape->fill.type != NSVG_PAINT_NONE, startPoint);
}
if (shape->fill.type != NSVG_PAINT_NONE) {
if (shape->fillRule == NSVGfillRule::NSVG_FILLRULE_EVENODD)
HPDF_Page_EofillStroke(page); // fills the curr path using the
// even-odd rule and paints
else HPDF_Page_FillStroke(page); // fills the curr path using the
// nonzero winding number rule and
paints
}
else HPDF_Page_Stroke(page); // paints the path
}
HPDF_SaveToFile(pdf, fileName.c_str());
HPDF_Free(pdf);

return true;
}

float SvgConverter::distPtSeg(float x, float y, float px, float py, float qx, float
qy)
{
float pqx, pqy, dx, dy, d, t;
pqx = qx - px;
pqy = qy - py;
dx = x - px;
dy = y - py;
d = pqx*pqx + pqy*pqy;
t = pqx*dx + pqy*dy;
if (d > 0) t /= d;
if (t < 0) t = 0;
else if (t > 1) t = 1;
dx = px + t*pqx - x;
dy = py + t*pqy - y;
return dx*dx + dy*dy;
}

void SvgConverter::pdfcubicBez(HPDF_Page page, float x1, float y1, float x2, float y2,
float x3, float y3, float x4, float y4,
float tol, int level, Vector2f startPoint)
{
float x12, y12, x23, y23, x34, y34, x123, y123, x234, y234, x1234, y1234;
float d;

if (level > 12) return;


//geting midpoints
x12 = (x1 + x2)*0.5f;
y12 = (y1 + y2)*0.5f;
x23 = (x2 + x3)*0.5f;
y23 = (y2 + y3)*0.5f;
x34 = (x3 + x4)*0.5f;
y34 = (y3 + y4)*0.5f;
x123 = (x12 + x23)*0.5f;
y123 = (y12 + y23)*0.5f;
x234 = (x23 + x34)*0.5f;

4 of 7 01/08/2018 11:34
Parse SVG Files to Bezier Curves in C++ and Sa... https://www.codeproject.com/Articles/1252897/...

y234 = (y23 + y34)*0.5f;


x1234 = (x123 + x234)*0.5f;
y1234 = (y123 + y234)*0.5f;

d = distPtSeg(x1234, y1234, x1, y1, x4, y4); //check if curr curve is


flat
if (d > tol * tol) {
pdfcubicBez(page, x1, y1, x12, y12, x123,
y123, x1234, y1234, tol, level + 1, startPoint); //dividing first piece
pdfcubicBez(page, x1234, y1234, x234, y234, x34,
y34, x4, y4, tol, level + 1, startPoint); // dividing second piece
}
else HPDF_Page_LineTo(page, startPoint.x + x4 / 3.0f,
startPoint.y - y4 / 3.0f); //curr piece of curve is
enough flat
// appends a path from the curr point to x4,y4
}

void SvgConverter::pdfPath(HPDF_Page page, float* pts, int npts, char closed,


float tol, bool bFilled, Vector2f startPoint)
{
HPDF_Page_MoveTo(page, startPoint.x + pts[0] / 3.0f,
startPoint.y - pts[1] / 3.0f); // moving to first point of bezier curve

for (int i = 0; i < npts - 1; i += 3) {


float* p = &pts[i * 2];
pdfcubicBez(page, p[0], p[1], p[2], p[3], p[4], p[5],
p[6], p[7], tol, 0, startPoint); // draw a cubic bezier curve
}
if (closed) HPDF_Page_LineTo(page, startPoint.x + pts[0] / 3.0f, startPoint.y -
pts[1] / 3.0f);
}

You can use an application from console to include in some batch processing. Application receives two command line
arguments, where the first argument is SVG file path and the second is PDF file path.
For example:

./svgtopdf cat.svg cat.pdf

Points of Interest
While trying to link C library to C++ project, first you have to disable precompiled headers for C files. This can be done
by selecting multiple C files first in Solution Explorer, right click, then go to "Precompiled Headers" tab and select "Not
using Precompiled Headers".

5 of 7 01/08/2018 11:34
Parse SVG Files to Bezier Curves in C++ and Sa... https://www.codeproject.com/Articles/1252897/...

Also, you need to wrap the C include files in an extern "C" directive:

extern "C" {
#include "libharu\hpdf.h"
#include "libharu\hpdf_utils.h"
}

Conclusion
Feel free to send me any comments and suggestions. If you find this approach useful, feel free to upvote the article. If
you find yourself wanting to improve the code, feel free to clone the github repository.

Good luck!

History
18th July, 2018 - First version

License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author


Michael Moroz
Software Developer IQ Direct
Ukraine

6 of 7 01/08/2018 11:34
Parse SVG Files to Bezier Curves in C++ and Sa... https://www.codeproject.com/Articles/1252897/...

Comments and Discussions


2 messages have been posted for this article Visit https://www.codeproject.com/Articles/1252897/Parse-SVG-
Files-to-Bezier-Curves-in-Cplusplus-and to post and view comments on this article, or click here to get a print view
with messages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile Article Copyright 2018 by Michael Moroz
Web01-2016 | 2.8.180728.1 | Last Updated 19 Jul 2018 Everything else Copyright © CodeProject, 1999-2018

7 of 7 01/08/2018 11:34

Potrebbero piacerti anche