如何在C#或JS中无第三方库程序化将SVG嵌入PDF文件
不依赖第三方库实现SVG转PDF(C#/JavaScript)
C# 实现思路与代码示例
要在C#里手动完成SVG到PDF的转换,核心是两步:解析SVG的XML结构提取图形元素,手动构建PDF文件的内容流(PDF本质是带结构的文本文件)。
核心步骤
- 读取SVG文件,解析XML获取图形元素(如
<path>、<rect>、<circle>等)。 - 理解PDF的绘图模型:PDF使用左下角为原点的坐标系,以及
m(移动)、l(直线)、c(贝塞尔曲线)等路径操作符。 - 将SVG的图形指令转换为PDF对应的操作符,写入PDF的内容流。
- 构建PDF的完整结构(文件头、目录页、页面对象、内容流、交叉引用表等)。
极简代码示例(仅处理矩形)
using System; using System.IO; using System.Xml; class SvgToPdf { static void Main(string[] args) { // 解析SVG获取矩形参数 XmlDocument svgDoc = new XmlDocument(); svgDoc.Load("input.svg"); XmlNode rectNode = svgDoc.SelectSingleNode("//rect"); float x = float.Parse(rectNode.Attributes["x"].Value); float y = float.Parse(rectNode.Attributes["y"].Value); float width = float.Parse(rectNode.Attributes["width"].Value); float height = float.Parse(rectNode.Attributes["height"].Value); string fill = rectNode.Attributes["fill"]?.Value ?? "#000000"; // 构建PDF内容 string pdfContent = $@"%PDF-1.4 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /MediaBox [0 0 612 792] /Contents 4 0 R >> endobj 4 0 obj << /Length {GetContentStreamLength(x, y, width, height, fill)} >> stream q {ConvertColorToPdf(fill)} {x} {792 - y - height} {width} {height} re f Q endstream endobj xref 0 5 0000000000 65535 f 0000000010 00000 n 0000000079 00000 n 0000000146 00000 n 0000000225 00000 n trailer << /Size 5 /Root 1 0 R >> startxref 342 %%EOF"; File.WriteAllText("output.pdf", pdfContent); } static int GetContentStreamLength(float x, float y, float w, float h, string color) { return $"q\n{ConvertColorToPdf(color)} {x} {792 - y - h} {w} {h} re f\nQ".Length; } static string ConvertColorToPdf(string hexColor) { if (hexColor.StartsWith("#")) hexColor = hexColor.Substring(1); float r = int.Parse(hexColor.Substring(0, 2), System.Globalization.NumberStyles.HexNumber) / 255f; float g = int.Parse(hexColor.Substring(2, 2), System.Globalization.NumberStyles.HexNumber) / 255f; float b = int.Parse(hexColor.Substring(4, 2), System.Globalization.NumberStyles.HexNumber) / 255f; return $"{r} {g} {b} rg"; } }
说明:这个示例仅支持SVG矩形元素,若要支持路径、文本等复杂元素,需要解析对应的SVG指令并转换为PDF操作符,比如将SVG的
path指令映射到PDF的m、l、c等操作。
JavaScript 实现思路与代码示例
JS场景分为浏览器环境和Node.js环境,核心逻辑是解析SVG并生成PDF结构,或借助内置Canvas中转。
浏览器环境(Canvas中转)
利用浏览器内置Canvas渲染SVG,再将Canvas内容转换为PDF图像数据:
async function svgToPdf(svgElement, pdfFileName) { // 把SVG序列化为DataURL const svgData = new XMLSerializer().serializeToString(svgElement); const img = new Image(); img.src = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svgData)))}`; await new Promise(resolve => img.onload = resolve); // 创建Canvas绘制SVG const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); // 构建PDF基础结构 const pdfHeader = `%PDF-1.4\n1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n3 0 obj\n<< /Type /Page /MediaBox [0 0 ${canvas.width} ${canvas.height}] /Contents 4 0 R /Resources << /XObject << /Im1 5 0 R >> >> >>\nendobj\n`; // 将Canvas内容转为PDF图像对象 const pngData = canvas.toDataURL('image/png').split(',')[1]; const pngBytes = atob(pngData); const contentStream = `4 0 obj\n<< /Length ${pngBytes.length + 20} >>\nstream\nq\n${canvas.width} 0 0 ${canvas.height} 0 0 cm\n/Im1 Do\nQ\nendstream\nendobj\n5 0 obj\n<< /Type /XObject /Subtype /Image /Width ${canvas.width} /Height ${canvas.height} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /FlateDecode >>\nstream\n${pngBytes}\nendstream\nendobj\n`; // 生成交叉引用表和尾注 const xrefOffset = pdfHeader.length + contentStream.length; const xref = `xref\n0 6\n0000000000 65535 f \n${pad(pdfHeader.length, 10)} 00000 n \n${pad(pdfHeader.length + 65, 10)} 00000 n \n${pad(pdfHeader.length + 132, 10)} 00000 n \n${pad(pdfHeader.length + contentStream.indexOf('4 0 obj'), 10)} 00000 n \n${pad(pdfHeader.length + contentStream.indexOf('5 0 obj'), 10)} 00000 n \n`; const trailer = `trailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n${xrefOffset}\n%%EOF`; const pdfContent = pdfHeader + contentStream + xref + trailer; // 下载生成的PDF const blob = new Blob([pdfContent], { type: 'application/pdf' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = pdfFileName; link.click(); } function pad(num, length) { return num.toString().padStart(length, '0'); } // 使用示例:传入页面中的SVG元素 // svgToPdf(document.querySelector('svg'), 'output.pdf');
Node.js 环境(手动解析SVG+生成PDF)
用内置fs模块读写文件,手动解析简单SVG元素并生成PDF:
const fs = require('fs'); function svgToPdf(svgPath, pdfPath) { // 读取SVG文件内容 const svgContent = fs.readFileSync(svgPath, 'utf8'); // 简单提取矩形参数(复杂场景需完善解析逻辑) const rectMatch = svgContent.match(/<rect x="([\d.]+)" y="([\d.]+)" width="([\d.]+)" height="([\d.]+)" fill="([^"]+)"/); if (!rectMatch) { console.error('未找到可处理的矩形元素'); return; } const [, x, y, width, height, fill] = rectMatch; // 构建PDF内容 const pdfContent = `%PDF-1.4 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /MediaBox [0 0 612 792] /Contents 4 0 R >> endobj 4 0 obj << /Length ${getStreamLength(x, y, width, height, fill)} >> stream q ${convertColor(fill)} ${x} ${792 - parseFloat(y) - parseFloat(height)} ${width} ${height} re f Q endstream endobj xref 0 5 0000000000 65535 f 0000000010 00000 n 0000000079 00000 n 0000000146 00000 n 0000000225 00000 n trailer << /Size 5 /Root 1 0 R >> startxref 342 %%EOF`; fs.writeFileSync(pdfPath, pdfContent); } function getStreamLength(x, y, w, h, color) { return `q\n${convertColor(color)} ${x} ${792 - parseFloat(y) - parseFloat(h)} ${w} ${h} re f\nQ`.length; } function convertColor(hexColor) { if (hexColor.startsWith('#')) hexColor = hexColor.slice(1); const r = parseInt(hexColor.slice(0,2),16)/255; const g = parseInt(hexColor.slice(2,4),16)/255; const b = parseInt(hexColor.slice(4,6),16)/255; return `${r.toFixed(3)} ${g.toFixed(3)} ${b.toFixed(3)} rg`; } // 使用示例 // svgToPdf('input.svg', 'output.pdf');
注意:该示例仅支持简单矩形,若要处理复杂SVG,需自行实现完整的SVG DOM解析和指令转换逻辑。
内容的提问来源于stack exchange,提问作者KJohn
相关产品推荐
相关产品推荐

