C++ 解析 SVG 文件为贝塞尔曲线并保存为 PDF






4.64/5 (9投票s)
本文介绍了如何在 C++ 应用程序中打开 SVG 文件,使用贝塞尔曲线并导出为 PDF。
引言
此代码旨在演示在 C/C++ 应用程序中解析矢量 SVG 文件,使用贝塞尔曲线并将矢量图像保存到 PDF 的过程。 为了便于理解,应用程序中的所有处理仅在灰度模式下工作。 该代码是为 Windows 编写的,但没有仅限 Windows 的依赖项,可以轻松移植到 Linux 或任何嵌入式平台。
可缩放矢量图形 (SVG) 是一种在 Web 开发中广泛使用的 2D 矢量图像格式。 SVG 格式很大程度上基于 XML 标准。 虽然光栅图像(PNG、JPG、GIF 等)由一组固定的像素组成,但矢量 SVG 图像由一组固定的形状组成,如圆形、线条、矩形、贝塞尔曲线等。 SVG 格式的优势在于它可以轻松缩放到任何级别而不会损失质量。
我在这里包含了一只可爱的猫作为 SVG 图像的示例
贝塞尔曲线 是一种参数曲线,用于对可以无限缩放的平滑曲线进行建模。 贝塞尔曲线可以表示为一组控制点。 二次和三次贝塞尔曲线是最常见的。 更高阶的曲线计算起来更昂贵。 当需要更复杂的形状时,低阶贝塞尔曲线会拼接在一起,从而生成复合贝塞尔曲线。 复合贝塞尔曲线通常被称为路径。
| |
二次贝塞尔曲线 | 三次贝塞尔曲线 |
可移植文档格式 (PDF) 是一种用于表示文档的文件格式,包括文本格式、光栅图像、矢量图形等。 PDF 中的矢量图形也是用路径构造的。 路径通常由线条和三次贝塞尔曲线组成,但也可以从文本轮廓构造。 PDF 中的路径可以进行描边、填充和剪切。 描边和填充可以使用图形状态中设置的任何颜色,包括图案。
背景
对于 SVG 文件的处理,我使用了名为 nanosvg 的出色库。 它体积小、重量轻,甚至可以在嵌入式开发项目中使用。 为了编写 PDF 文件,我使用了只写 libHaru 库。 虽然现在(2018 年)没有积极开发,但仍然可以在 C/C++ 项目中使用。
Using the Code
SvgConverter
类是将 SVG 转换为 PDF 的基本主力。 可以多次使用同一个对象来转换其他图像。 您只需要调用负责加载其他文件的方法即可。
//..
SvgConverter converter;
converter.loadFromFile(fileInput); // load svg image to inner buffer
converter.convertToPDF(fileOutput); // parse and write image from buffer to pdf file
//..
类结构如下所示
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);
};
为了近似贝塞尔曲线并绘制它,我们将使用这种曲线的一个属性。
从开始到结束的任何三次贝塞尔曲线 B 都可以分为两条曲线,这两条曲线将共同描述与 B 相同的曲线。
转换和近似方法实现,一些代码取自 nanosvg 示例并适应于 PDF 坐标系
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;
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;
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);
}
您可以使用控制台中的应用程序包含在某些批处理中。 应用程序接收两个命令行参数,其中第一个参数是 SVG 文件路径,第二个参数是 PDF 文件路径。
例如
./svgtopdf cat.svg cat.pdf
关注点
在尝试将 C 库链接到 C++ 项目时,首先必须为 C 文件禁用预编译头。 这可以通过首先在解决方案资源管理器中选择多个 C 文件来完成,右键单击,然后转到“预编译头”选项卡并选择“不使用预编译头”。
此外,您需要将 C include
文件包装在 extern "C"
指令中
extern "C" {
#include "libharu\hpdf.h"
#include "libharu\hpdf_utils.h"
}
结论
欢迎向我发送任何意见和建议。 如果您发现此方法有用,请随意投票支持这篇文章。 如果您发现自己想改进代码,请随意克隆 github 存储库。
祝您好运!
历史
- 2018 年 7 月 18 日th - 第一个版本