dom-to-vector-pdf 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 XZ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,14 @@
1
+ # dom-to-vector-pdf
2
+
3
+ DOM 转矢量 PDF
4
+
5
+ 1. 克隆元素时对原页面的影响
6
+ - 内联控制(current)
7
+ - 样式优先级
8
+ - Shadow DOM
9
+ - iframe
10
+ 2. iconfont 大小问题:目前是默认 16px 字体大小来做的缩放
11
+ 3. 原来的 DOM 中本身存在的 SVG 用的内联样式来决定样式
12
+ - 目前只支持内联中属性名和元素属性名一致的情况
13
+ 4. 页面整体转换出来的文字对比背景要偏下一点
14
+ - 目前是暂时所有文字整体上移 3 个像素单位
@@ -0,0 +1,29 @@
1
+ import type { ExportPdfOptions, LifecycleHooks } from './types';
2
+ /**
3
+ * DOM转PDF转换器
4
+ */
5
+ export declare class DomToPdfConverter {
6
+ private fontManager;
7
+ constructor();
8
+ /**
9
+ * 导出PDF
10
+ */
11
+ exportPdf(options: ExportPdfOptions, hooks?: LifecycleHooks): Promise<void>;
12
+ /**
13
+ * 准备DOM元素
14
+ */
15
+ private prepareDomElement;
16
+ /**
17
+ * 准备SVG元素
18
+ */
19
+ private prepareSvgElement;
20
+ /**
21
+ * 创建PDF文档
22
+ */
23
+ private createPdfDocument;
24
+ /**
25
+ * 渲染SVG到PDF
26
+ */
27
+ private renderSvgToPdf;
28
+ }
29
+ //# sourceMappingURL=dom-converter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom-converter.d.ts","sourceRoot":"","sources":["../src/dom-converter.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAGhE;;GAEG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAAc;;IAMjC;;OAEG;IACU,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,KAAK,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAgDxF;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAwBzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAezB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAUzB;;OAEG;YACW,cAAc;CAQ7B"}
@@ -0,0 +1,33 @@
1
+ import { jsPDF } from 'jspdf';
2
+ import type { FontRegisterOptions } from './types';
3
+ /**
4
+ * 字体管理器
5
+ */
6
+ export declare class FontManager {
7
+ private static instance;
8
+ private registeredFonts;
9
+ private pdfInstance?;
10
+ private callbackList;
11
+ private constructor();
12
+ /**
13
+ * 获取字体管理器单例
14
+ */
15
+ static getInstance(): FontManager;
16
+ /**
17
+ * 设置PDF实例
18
+ */
19
+ setPdfInstance(pdf: jsPDF): void;
20
+ /**
21
+ * 注册字体
22
+ */
23
+ registerFont(options: FontRegisterOptions): void;
24
+ /**
25
+ * 批量注册字体
26
+ */
27
+ registerFonts(options: FontRegisterOptions[]): void;
28
+ /**
29
+ * 添加字体到PDF实例
30
+ */
31
+ private addFontToPdf;
32
+ }
33
+ //# sourceMappingURL=font-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"font-manager.d.ts","sourceRoot":"","sources":["../src/font-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAC9B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAGnD;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAc;IACrC,OAAO,CAAC,eAAe,CAAuC;IAC9D,OAAO,CAAC,WAAW,CAAC,CAAe;IACnC,OAAO,CAAC,YAAY,CAAsB;IAE1C,OAAO;IAEP;;OAEG;WACW,WAAW,IAAI,WAAW;IAOxC;;OAEG;IACI,cAAc,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI;IAMvC;;OAEG;IACI,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI;IAIvD;;OAEG;IACI,aAAa,CAAC,OAAO,EAAE,mBAAmB,EAAE,GAAG,IAAI;IAI1D;;OAEG;IACH,OAAO,CAAC,YAAY;CAYrB"}
@@ -0,0 +1,23 @@
1
+ import type { ExportPdfOptions, FontRegisterOptions, LifecycleHooks } from './types';
2
+ /**
3
+ * DOM转PDF工具实例
4
+ */
5
+ declare class DomToPdf {
6
+ private converter;
7
+ private fontManager;
8
+ constructor();
9
+ /**
10
+ * 导出PDF
11
+ * @param options 导出配置
12
+ * @param hooks 生命周期钩子
13
+ */
14
+ export(options: ExportPdfOptions, hooks?: LifecycleHooks): Promise<void>;
15
+ /**
16
+ * 注册字体
17
+ * @param options 字体注册选项
18
+ */
19
+ registerFont(options: FontRegisterOptions | FontRegisterOptions[]): void;
20
+ }
21
+ export declare const instance: DomToPdf;
22
+ export default instance;
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAErF;;GAEG;AACH,cAAM,QAAQ;IACZ,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,WAAW,CAAc;;IAOjC;;;;OAIG;IACU,MAAM,CAAC,OAAO,EAAE,gBAAgB,EAAE,KAAK,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrF;;;OAGG;IACI,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,mBAAmB,EAAE,GAAG,IAAI;CAOhF;AAGD,eAAO,MAAM,QAAQ,UAAiB,CAAC;AACvC,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,288 @@
1
+ import { jsPDF } from 'jspdf';
2
+ import { elementToSVG } from 'dom-to-svg';
3
+ import { svg2pdf } from 'svg2pdf.js';
4
+
5
+ /**
6
+ * 转换字体字重
7
+ * @param weight 字体粗细
8
+ * @returns 标准化后的字重
9
+ */
10
+ function normalizeFontWeight(weight) {
11
+ const weightMap = {
12
+ normal: '400',
13
+ bold: '700',
14
+ };
15
+ return weightMap[weight?.toString() || 'normal'] || weight?.toString() || '400';
16
+ }
17
+ /**
18
+ * 计算SVG symbol的缩放比例
19
+ */
20
+ function calculateSymbolScale(symbol) {
21
+ const viewBox = symbol.getAttribute('viewBox');
22
+ if (!viewBox) {
23
+ return 1;
24
+ }
25
+ const [, , width] = viewBox.split(' ').map(Number);
26
+ // 1em 通常计算的像素值
27
+ const expectedSize = 16;
28
+ return expectedSize / width;
29
+ }
30
+ /**
31
+ * 内联SVG中的symbol元素
32
+ */
33
+ function inlineSvgSymbols(element) {
34
+ const uses = element.querySelectorAll('use');
35
+ uses.forEach((use) => {
36
+ const href = use.getAttribute('xlink:href') || use.getAttribute('href');
37
+ if (!href) {
38
+ return;
39
+ }
40
+ const symbol = document.querySelector(href);
41
+ if (!symbol) {
42
+ return;
43
+ }
44
+ // 创建 <g> 容器保留所有属性
45
+ const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
46
+ // 复制除href外的所有属性
47
+ Array.from(use.attributes).forEach((attr) => {
48
+ if (attr.name !== 'xlink:href' && attr.name !== 'href') {
49
+ g.setAttribute(attr.name, attr.value);
50
+ }
51
+ });
52
+ // 插入缩放后的路径
53
+ g.innerHTML = `
54
+ <g transform="scale(${calculateSymbolScale(symbol)})">
55
+ ${symbol.innerHTML}
56
+ </g>
57
+ `;
58
+ // 替换并保留父SVG尺寸
59
+ use.replaceWith(g);
60
+ });
61
+ }
62
+ /**
63
+ * 递归处理SVG元素的字体属性
64
+ */
65
+ function processSvgFonts(element) {
66
+ if (element.classList.contains('no-print')) {
67
+ element.remove();
68
+ return;
69
+ }
70
+ if (element.tagName === 'text' || element.tagName === 'tspan') {
71
+ // 解析style字符串
72
+ const style = element.getAttribute('style');
73
+ if (style) {
74
+ style.split(';').forEach((css) => {
75
+ const [key, value] = css.split(':');
76
+ if (!key)
77
+ return;
78
+ element.setAttribute(key.trim(), value?.trim());
79
+ });
80
+ }
81
+ element.removeAttribute('style');
82
+ const fontFamily = element.getAttribute('font-family');
83
+ const fontWeight = element.getAttribute('font-weight');
84
+ // TODO
85
+ if (fontFamily) {
86
+ element.setAttribute('font-family', 'PingFang');
87
+ element.setAttribute('font-weight', normalizeFontWeight(fontWeight));
88
+ }
89
+ // 调整y坐标
90
+ const y = element.getAttribute('y');
91
+ if (y) {
92
+ element.setAttribute('y', String(Number(y) - 3));
93
+ }
94
+ }
95
+ // 递归处理子元素
96
+ Array.from(element.children).forEach((child) => processSvgFonts(child));
97
+ }
98
+
99
+ /**
100
+ * 字体管理器
101
+ */
102
+ class FontManager {
103
+ constructor() {
104
+ this.registeredFonts = new Map();
105
+ this.callbackList = [];
106
+ }
107
+ /**
108
+ * 获取字体管理器单例
109
+ */
110
+ static getInstance() {
111
+ if (!FontManager.instance) {
112
+ FontManager.instance = new FontManager();
113
+ }
114
+ return FontManager.instance;
115
+ }
116
+ /**
117
+ * 设置PDF实例
118
+ */
119
+ setPdfInstance(pdf) {
120
+ this.pdfInstance = pdf;
121
+ this.callbackList.forEach((callback) => callback());
122
+ this.callbackList = [];
123
+ }
124
+ /**
125
+ * 注册字体
126
+ */
127
+ registerFont(options) {
128
+ this.addFontToPdf(options);
129
+ }
130
+ /**
131
+ * 批量注册字体
132
+ */
133
+ registerFonts(options) {
134
+ options.map((font) => this.registerFont(font));
135
+ }
136
+ /**
137
+ * 添加字体到PDF实例
138
+ */
139
+ addFontToPdf(options) {
140
+ if (!this.pdfInstance) {
141
+ this.callbackList.push(() => this.addFontToPdf(options));
142
+ return;
143
+ }
144
+ this.pdfInstance.addFont(options.font, options.fontId, options.fontStyle || 'normal', normalizeFontWeight(options.fontWeight));
145
+ }
146
+ }
147
+
148
+ /**
149
+ * DOM转PDF转换器
150
+ */
151
+ class DomToPdfConverter {
152
+ constructor() {
153
+ this.fontManager = FontManager.getInstance();
154
+ }
155
+ /**
156
+ * 导出PDF
157
+ */
158
+ async exportPdf(options, hooks) {
159
+ try {
160
+ // 1. 获取并克隆DOM元素
161
+ const { element, parentElement } = this.prepareDomElement(options.id);
162
+ // 调用生命周期钩子
163
+ hooks?.afterDomClone?.(element);
164
+ // 2. 处理SVG符号
165
+ inlineSvgSymbols(element);
166
+ // 3. 转换为SVG
167
+ const svgDocument = elementToSVG(element);
168
+ parentElement?.removeChild(element);
169
+ const svgElement = svgDocument.documentElement;
170
+ document.body.appendChild(svgElement);
171
+ this.prepareSvgElement(svgElement);
172
+ // 4. 处理SVG字体
173
+ processSvgFonts(svgElement);
174
+ // 调用生命周期钩子
175
+ hooks?.beforeSvgConvert?.(svgElement);
176
+ // 5. 创建PDF文档
177
+ const pdf = this.createPdfDocument(svgElement);
178
+ this.fontManager.setPdfInstance(pdf);
179
+ // 6. 绘制SVG内容到PDF
180
+ await this.renderSvgToPdf(svgElement, pdf);
181
+ // 调用生命周期钩子
182
+ hooks?.beforePdfGenerate?.(pdf);
183
+ hooks?.beforePdfSave?.(pdf);
184
+ // 7. 保存PDF
185
+ pdf.save(`${options.filename}.pdf`);
186
+ // 8. 清理临时元素
187
+ svgElement.remove();
188
+ this.fontManager.setPdfInstance(null);
189
+ }
190
+ catch (error) {
191
+ console.error('生成PDF失败:', error);
192
+ throw error;
193
+ }
194
+ }
195
+ /**
196
+ * 准备DOM元素
197
+ */
198
+ prepareDomElement(id) {
199
+ const originElement = document.querySelector(id);
200
+ if (!originElement) {
201
+ throw new Error(`Element with id "${id}" not found`);
202
+ }
203
+ const parentElement = originElement.parentElement;
204
+ const element = originElement.cloneNode(true);
205
+ // 设置克隆元素的样式
206
+ element.style.cssText = `
207
+ z-index: -999999;
208
+ position: absolute;
209
+ top: 0;
210
+ left: 0;
211
+ `;
212
+ console.log(parentElement, '??????');
213
+ parentElement?.appendChild(element);
214
+ return { element, parentElement };
215
+ }
216
+ /**
217
+ * 准备SVG元素
218
+ */
219
+ prepareSvgElement(svgElement) {
220
+ svgElement.style.cssText = `
221
+ all: unset;
222
+ width: 100%;
223
+ position: absolute;
224
+ top: 0;
225
+ left: 0;
226
+ z-index: -999999;
227
+ `;
228
+ // 添加XML声明
229
+ const utf8Declaration = document.createTextNode('<?xml version="1.0" encoding="utf-8"?>');
230
+ svgElement.insertBefore(utf8Declaration, svgElement.firstChild);
231
+ }
232
+ /**
233
+ * 创建PDF文档
234
+ */
235
+ createPdfDocument(svgElement) {
236
+ const { width, height } = svgElement.getBoundingClientRect();
237
+ return new jsPDF({
238
+ orientation: 'portrait',
239
+ unit: 'px',
240
+ format: [width, height],
241
+ });
242
+ }
243
+ /**
244
+ * 渲染SVG到PDF
245
+ */
246
+ async renderSvgToPdf(svgElement, pdf) {
247
+ await svg2pdf(svgElement, pdf, {
248
+ x: 0,
249
+ y: 0,
250
+ width: pdf.internal.pageSize.getWidth(),
251
+ height: pdf.internal.pageSize.getHeight(),
252
+ });
253
+ }
254
+ }
255
+
256
+ /**
257
+ * DOM转PDF工具实例
258
+ */
259
+ class DomToPdf {
260
+ constructor() {
261
+ this.converter = new DomToPdfConverter();
262
+ this.fontManager = FontManager.getInstance();
263
+ }
264
+ /**
265
+ * 导出PDF
266
+ * @param options 导出配置
267
+ * @param hooks 生命周期钩子
268
+ */
269
+ async export(options, hooks) {
270
+ await this.converter.exportPdf(options, hooks);
271
+ }
272
+ /**
273
+ * 注册字体
274
+ * @param options 字体注册选项
275
+ */
276
+ registerFont(options) {
277
+ if (Array.isArray(options)) {
278
+ this.fontManager.registerFonts(options);
279
+ }
280
+ else {
281
+ this.fontManager.registerFont(options);
282
+ }
283
+ }
284
+ }
285
+ // 导出单例实例
286
+ const instance = new DomToPdf();
287
+
288
+ export { instance as default, instance };
package/dist/index.js ADDED
@@ -0,0 +1,295 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('jspdf'), require('dom-to-svg'), require('svg2pdf.js')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'jspdf', 'dom-to-svg', 'svg2pdf.js'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.DomToPdf = {}, global.jspdf, global.domToSvg, global.svg2pdf_js));
5
+ })(this, (function (exports, jspdf, domToSvg, svg2pdf_js) { 'use strict';
6
+
7
+ /**
8
+ * 转换字体字重
9
+ * @param weight 字体粗细
10
+ * @returns 标准化后的字重
11
+ */
12
+ function normalizeFontWeight(weight) {
13
+ const weightMap = {
14
+ normal: '400',
15
+ bold: '700',
16
+ };
17
+ return weightMap[weight?.toString() || 'normal'] || weight?.toString() || '400';
18
+ }
19
+ /**
20
+ * 计算SVG symbol的缩放比例
21
+ */
22
+ function calculateSymbolScale(symbol) {
23
+ const viewBox = symbol.getAttribute('viewBox');
24
+ if (!viewBox) {
25
+ return 1;
26
+ }
27
+ const [, , width] = viewBox.split(' ').map(Number);
28
+ // 1em 通常计算的像素值
29
+ const expectedSize = 16;
30
+ return expectedSize / width;
31
+ }
32
+ /**
33
+ * 内联SVG中的symbol元素
34
+ */
35
+ function inlineSvgSymbols(element) {
36
+ const uses = element.querySelectorAll('use');
37
+ uses.forEach((use) => {
38
+ const href = use.getAttribute('xlink:href') || use.getAttribute('href');
39
+ if (!href) {
40
+ return;
41
+ }
42
+ const symbol = document.querySelector(href);
43
+ if (!symbol) {
44
+ return;
45
+ }
46
+ // 创建 <g> 容器保留所有属性
47
+ const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
48
+ // 复制除href外的所有属性
49
+ Array.from(use.attributes).forEach((attr) => {
50
+ if (attr.name !== 'xlink:href' && attr.name !== 'href') {
51
+ g.setAttribute(attr.name, attr.value);
52
+ }
53
+ });
54
+ // 插入缩放后的路径
55
+ g.innerHTML = `
56
+ <g transform="scale(${calculateSymbolScale(symbol)})">
57
+ ${symbol.innerHTML}
58
+ </g>
59
+ `;
60
+ // 替换并保留父SVG尺寸
61
+ use.replaceWith(g);
62
+ });
63
+ }
64
+ /**
65
+ * 递归处理SVG元素的字体属性
66
+ */
67
+ function processSvgFonts(element) {
68
+ if (element.classList.contains('no-print')) {
69
+ element.remove();
70
+ return;
71
+ }
72
+ if (element.tagName === 'text' || element.tagName === 'tspan') {
73
+ // 解析style字符串
74
+ const style = element.getAttribute('style');
75
+ if (style) {
76
+ style.split(';').forEach((css) => {
77
+ const [key, value] = css.split(':');
78
+ if (!key)
79
+ return;
80
+ element.setAttribute(key.trim(), value?.trim());
81
+ });
82
+ }
83
+ element.removeAttribute('style');
84
+ const fontFamily = element.getAttribute('font-family');
85
+ const fontWeight = element.getAttribute('font-weight');
86
+ // TODO
87
+ if (fontFamily) {
88
+ element.setAttribute('font-family', 'PingFang');
89
+ element.setAttribute('font-weight', normalizeFontWeight(fontWeight));
90
+ }
91
+ // 调整y坐标
92
+ const y = element.getAttribute('y');
93
+ if (y) {
94
+ element.setAttribute('y', String(Number(y) - 3));
95
+ }
96
+ }
97
+ // 递归处理子元素
98
+ Array.from(element.children).forEach((child) => processSvgFonts(child));
99
+ }
100
+
101
+ /**
102
+ * 字体管理器
103
+ */
104
+ class FontManager {
105
+ constructor() {
106
+ this.registeredFonts = new Map();
107
+ this.callbackList = [];
108
+ }
109
+ /**
110
+ * 获取字体管理器单例
111
+ */
112
+ static getInstance() {
113
+ if (!FontManager.instance) {
114
+ FontManager.instance = new FontManager();
115
+ }
116
+ return FontManager.instance;
117
+ }
118
+ /**
119
+ * 设置PDF实例
120
+ */
121
+ setPdfInstance(pdf) {
122
+ this.pdfInstance = pdf;
123
+ this.callbackList.forEach((callback) => callback());
124
+ this.callbackList = [];
125
+ }
126
+ /**
127
+ * 注册字体
128
+ */
129
+ registerFont(options) {
130
+ this.addFontToPdf(options);
131
+ }
132
+ /**
133
+ * 批量注册字体
134
+ */
135
+ registerFonts(options) {
136
+ options.map((font) => this.registerFont(font));
137
+ }
138
+ /**
139
+ * 添加字体到PDF实例
140
+ */
141
+ addFontToPdf(options) {
142
+ if (!this.pdfInstance) {
143
+ this.callbackList.push(() => this.addFontToPdf(options));
144
+ return;
145
+ }
146
+ this.pdfInstance.addFont(options.font, options.fontId, options.fontStyle || 'normal', normalizeFontWeight(options.fontWeight));
147
+ }
148
+ }
149
+
150
+ /**
151
+ * DOM转PDF转换器
152
+ */
153
+ class DomToPdfConverter {
154
+ constructor() {
155
+ this.fontManager = FontManager.getInstance();
156
+ }
157
+ /**
158
+ * 导出PDF
159
+ */
160
+ async exportPdf(options, hooks) {
161
+ try {
162
+ // 1. 获取并克隆DOM元素
163
+ const { element, parentElement } = this.prepareDomElement(options.id);
164
+ // 调用生命周期钩子
165
+ hooks?.afterDomClone?.(element);
166
+ // 2. 处理SVG符号
167
+ inlineSvgSymbols(element);
168
+ // 3. 转换为SVG
169
+ const svgDocument = domToSvg.elementToSVG(element);
170
+ parentElement?.removeChild(element);
171
+ const svgElement = svgDocument.documentElement;
172
+ document.body.appendChild(svgElement);
173
+ this.prepareSvgElement(svgElement);
174
+ // 4. 处理SVG字体
175
+ processSvgFonts(svgElement);
176
+ // 调用生命周期钩子
177
+ hooks?.beforeSvgConvert?.(svgElement);
178
+ // 5. 创建PDF文档
179
+ const pdf = this.createPdfDocument(svgElement);
180
+ this.fontManager.setPdfInstance(pdf);
181
+ // 6. 绘制SVG内容到PDF
182
+ await this.renderSvgToPdf(svgElement, pdf);
183
+ // 调用生命周期钩子
184
+ hooks?.beforePdfGenerate?.(pdf);
185
+ hooks?.beforePdfSave?.(pdf);
186
+ // 7. 保存PDF
187
+ pdf.save(`${options.filename}.pdf`);
188
+ // 8. 清理临时元素
189
+ svgElement.remove();
190
+ this.fontManager.setPdfInstance(null);
191
+ }
192
+ catch (error) {
193
+ console.error('生成PDF失败:', error);
194
+ throw error;
195
+ }
196
+ }
197
+ /**
198
+ * 准备DOM元素
199
+ */
200
+ prepareDomElement(id) {
201
+ const originElement = document.querySelector(id);
202
+ if (!originElement) {
203
+ throw new Error(`Element with id "${id}" not found`);
204
+ }
205
+ const parentElement = originElement.parentElement;
206
+ const element = originElement.cloneNode(true);
207
+ // 设置克隆元素的样式
208
+ element.style.cssText = `
209
+ z-index: -999999;
210
+ position: absolute;
211
+ top: 0;
212
+ left: 0;
213
+ `;
214
+ console.log(parentElement, '??????');
215
+ parentElement?.appendChild(element);
216
+ return { element, parentElement };
217
+ }
218
+ /**
219
+ * 准备SVG元素
220
+ */
221
+ prepareSvgElement(svgElement) {
222
+ svgElement.style.cssText = `
223
+ all: unset;
224
+ width: 100%;
225
+ position: absolute;
226
+ top: 0;
227
+ left: 0;
228
+ z-index: -999999;
229
+ `;
230
+ // 添加XML声明
231
+ const utf8Declaration = document.createTextNode('<?xml version="1.0" encoding="utf-8"?>');
232
+ svgElement.insertBefore(utf8Declaration, svgElement.firstChild);
233
+ }
234
+ /**
235
+ * 创建PDF文档
236
+ */
237
+ createPdfDocument(svgElement) {
238
+ const { width, height } = svgElement.getBoundingClientRect();
239
+ return new jspdf.jsPDF({
240
+ orientation: 'portrait',
241
+ unit: 'px',
242
+ format: [width, height],
243
+ });
244
+ }
245
+ /**
246
+ * 渲染SVG到PDF
247
+ */
248
+ async renderSvgToPdf(svgElement, pdf) {
249
+ await svg2pdf_js.svg2pdf(svgElement, pdf, {
250
+ x: 0,
251
+ y: 0,
252
+ width: pdf.internal.pageSize.getWidth(),
253
+ height: pdf.internal.pageSize.getHeight(),
254
+ });
255
+ }
256
+ }
257
+
258
+ /**
259
+ * DOM转PDF工具实例
260
+ */
261
+ class DomToPdf {
262
+ constructor() {
263
+ this.converter = new DomToPdfConverter();
264
+ this.fontManager = FontManager.getInstance();
265
+ }
266
+ /**
267
+ * 导出PDF
268
+ * @param options 导出配置
269
+ * @param hooks 生命周期钩子
270
+ */
271
+ async export(options, hooks) {
272
+ await this.converter.exportPdf(options, hooks);
273
+ }
274
+ /**
275
+ * 注册字体
276
+ * @param options 字体注册选项
277
+ */
278
+ registerFont(options) {
279
+ if (Array.isArray(options)) {
280
+ this.fontManager.registerFonts(options);
281
+ }
282
+ else {
283
+ this.fontManager.registerFont(options);
284
+ }
285
+ }
286
+ }
287
+ // 导出单例实例
288
+ const instance = new DomToPdf();
289
+
290
+ exports["default"] = instance;
291
+ exports.instance = instance;
292
+
293
+ Object.defineProperty(exports, '__esModule', { value: true });
294
+
295
+ }));
@@ -0,0 +1,45 @@
1
+ import { jsPDF } from 'jspdf';
2
+ /**
3
+ * 导出PDF配置项
4
+ */
5
+ export interface ExportPdfOptions {
6
+ /** 要导出的DOM元素ID */
7
+ id: string;
8
+ /** 导出的PDF文件名 */
9
+ filename: string;
10
+ /** PDF方向,默认为 portrait */
11
+ orientation?: 'portrait' | 'landscape';
12
+ /** 单位,默认为 px */
13
+ unit?: 'pt' | 'px' | 'in' | 'mm' | 'cm' | 'ex' | 'em' | 'pc';
14
+ /** 自定义处理SVG元素的钩子 */
15
+ beforeSvgConvert?: (svgElement: SVGElement) => void;
16
+ /** 自定义处理PDF文档的钩子 */
17
+ beforePdfSave?: (pdf: jsPDF) => void;
18
+ }
19
+ /**
20
+ * 字体注册选项
21
+ */
22
+ export interface FontRegisterOptions {
23
+ /** 字体文件路径或URL */
24
+ font: string;
25
+ /** 字体ID,用于标识字体 */
26
+ fontId: string;
27
+ /** 字体样式 (normal/italic) */
28
+ fontStyle?: 'normal' | 'italic';
29
+ /** 字体粗细 (100-900) */
30
+ fontWeight?: string | number;
31
+ }
32
+ /**
33
+ * 生命周期钩子
34
+ */
35
+ export interface LifecycleHooks {
36
+ /** DOM克隆后触发 */
37
+ afterDomClone?: (clonedElement: HTMLElement) => void;
38
+ /** SVG转换前触发 */
39
+ beforeSvgConvert?: (svgElement: SVGElement) => void;
40
+ /** PDF生成前触发 */
41
+ beforePdfGenerate?: (pdf: jsPDF) => void;
42
+ /** PDF保存前触发 */
43
+ beforePdfSave?: (pdf: jsPDF) => void;
44
+ }
45
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAE9B;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kBAAkB;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,yBAAyB;IACzB,WAAW,CAAC,EAAE,UAAU,GAAG,WAAW,CAAC;IACvC,gBAAgB;IAChB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC7D,oBAAoB;IACpB,gBAAgB,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;IACpD,oBAAoB;IACpB,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,SAAS,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAChC,qBAAqB;IACrB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,eAAe;IACf,aAAa,CAAC,EAAE,CAAC,aAAa,EAAE,WAAW,KAAK,IAAI,CAAC;IACrD,eAAe;IACf,gBAAgB,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;IACpD,eAAe;IACf,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IACzC,eAAe;IACf,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CACtC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 转换字体字重
3
+ * @param weight 字体粗细
4
+ * @returns 标准化后的字重
5
+ */
6
+ export declare function normalizeFontWeight(weight?: string | number): string;
7
+ /**
8
+ * 计算SVG symbol的缩放比例
9
+ */
10
+ export declare function calculateSymbolScale(symbol: SVGElement): number;
11
+ /**
12
+ * 内联SVG中的symbol元素
13
+ */
14
+ export declare function inlineSvgSymbols(element: HTMLElement | SVGElement): void;
15
+ /**
16
+ * 递归处理SVG元素的字体属性
17
+ */
18
+ export declare function processSvgFonts(element: Element): void;
19
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAMpE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAU/D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,GAAG,IAAI,CAkCxE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAqCtD"}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "dom-to-vector-pdf",
3
+ "version": "0.0.1",
4
+ "author": {
5
+ "name": "xzboss"
6
+ },
7
+ "description": "Convert DOM elements to vector PDFs using jsPDF, dom-to-svg and svg2pdf.js",
8
+ "keywords": [
9
+ "dom",
10
+ "pdf",
11
+ "vector",
12
+ "conversion",
13
+ "jspdf",
14
+ "svg",
15
+ "dom-to-svg",
16
+ "svg2pdf.js",
17
+ "vector-pdf"
18
+ ],
19
+ "main": "dist/index.js",
20
+ "module": "dist/index.esm.js",
21
+ "types": "dist/index.d.ts",
22
+ "files": [
23
+ "dist",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "scripts": {
28
+ "build": "rollup -c",
29
+ "dev": "rollup -c -w",
30
+ "test": "jest",
31
+ "prepublishOnly": "npm run build",
32
+ "release": "standard-version",
33
+ "release:alpha": "standard-version --prerelease alpha",
34
+ "release:beta": "standard-version --prerelease beta",
35
+ "release:rc": "standard-version --prerelease rc",
36
+ "format": "prettier --write \"**/*.{js,ts,json,md}\" --config ./.prettierrc"
37
+ },
38
+ "lint-staged": {
39
+ "*.{js,ts,json,md}": "prettier --write"
40
+ },
41
+ "dependencies": {
42
+ "dom-to-svg": "^0.12.2",
43
+ "jspdf": "^2.5.2",
44
+ "rollup": "^2.79.2",
45
+ "svg2pdf.js": "^2.5.0"
46
+ },
47
+ "devDependencies": {
48
+ "@commitlint/cli": "^17.6.7",
49
+ "@commitlint/config-conventional": "^17.6.7",
50
+ "@rollup/plugin-typescript": "^8.3.0",
51
+ "@types/jspdf": "^1.3.3",
52
+ "husky": "^8.0.3",
53
+ "lint-staged": "^13.2.3",
54
+ "prettier": "^2.8.8",
55
+ "standard-version": "^9.5.0",
56
+ "tslib": "^2.8.1",
57
+ "typescript": "^5.2.2"
58
+ }
59
+ }