md2xhs 1.0.0
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 +26 -0
- package/README.md +622 -0
- package/dist/assets/auto-render.min.js +1 -0
- package/dist/assets/katex.min.css +1 -0
- package/dist/assets/katex.min.js +1 -0
- package/dist/assets/mermaid.min.js +1646 -0
- package/dist/assets-loader.d.ts +27 -0
- package/dist/assets-loader.js +55 -0
- package/dist/converter.d.ts +87 -0
- package/dist/converter.js +421 -0
- package/dist/extensions.d.ts +20 -0
- package/dist/extensions.js +2 -0
- package/dist/file-utils.d.ts +21 -0
- package/dist/file-utils.js +69 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +24 -0
- package/dist/styles.d.ts +21 -0
- package/dist/styles.js +250 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.js +15 -0
- package/package.json +53 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 资源加载器 - 从本地读取 CDN 资源
|
|
3
|
+
*/
|
|
4
|
+
export declare class AssetLoader {
|
|
5
|
+
private static assetsDir;
|
|
6
|
+
private static cache;
|
|
7
|
+
/**
|
|
8
|
+
* 读取本地资源文件
|
|
9
|
+
*/
|
|
10
|
+
static loadAsset(filename: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* 获取 KaTeX CSS
|
|
13
|
+
*/
|
|
14
|
+
static getKatexCss(): string;
|
|
15
|
+
/**
|
|
16
|
+
* 获取 KaTeX JS
|
|
17
|
+
*/
|
|
18
|
+
static getKatexJs(): string;
|
|
19
|
+
/**
|
|
20
|
+
* 获取 KaTeX Auto-render JS
|
|
21
|
+
*/
|
|
22
|
+
static getKatexAutoRender(): string;
|
|
23
|
+
/**
|
|
24
|
+
* 获取 Mermaid JS
|
|
25
|
+
*/
|
|
26
|
+
static getMermaidJs(): string;
|
|
27
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AssetLoader = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
/**
|
|
7
|
+
* 资源加载器 - 从本地读取 CDN 资源
|
|
8
|
+
*/
|
|
9
|
+
class AssetLoader {
|
|
10
|
+
/**
|
|
11
|
+
* 读取本地资源文件
|
|
12
|
+
*/
|
|
13
|
+
static loadAsset(filename) {
|
|
14
|
+
if (this.cache.has(filename)) {
|
|
15
|
+
return this.cache.get(filename);
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const filePath = (0, path_1.join)(this.assetsDir, filename);
|
|
19
|
+
const content = (0, fs_1.readFileSync)(filePath, 'utf-8');
|
|
20
|
+
this.cache.set(filename, content);
|
|
21
|
+
return content;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.warn(`无法加载本地资源 ${filename}, 将使用 CDN`);
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 获取 KaTeX CSS
|
|
30
|
+
*/
|
|
31
|
+
static getKatexCss() {
|
|
32
|
+
return this.loadAsset('katex.min.css');
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 获取 KaTeX JS
|
|
36
|
+
*/
|
|
37
|
+
static getKatexJs() {
|
|
38
|
+
return this.loadAsset('katex.min.js');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 获取 KaTeX Auto-render JS
|
|
42
|
+
*/
|
|
43
|
+
static getKatexAutoRender() {
|
|
44
|
+
return this.loadAsset('auto-render.min.js');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 获取 Mermaid JS
|
|
48
|
+
*/
|
|
49
|
+
static getMermaidJs() {
|
|
50
|
+
return this.loadAsset('mermaid.min.js');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.AssetLoader = AssetLoader;
|
|
54
|
+
AssetLoader.assetsDir = (0, path_1.join)(__dirname, 'assets');
|
|
55
|
+
AssetLoader.cache = new Map();
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Md2ImageOptions, ConvertResult } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Markdown 转图片转换器
|
|
4
|
+
*/
|
|
5
|
+
export declare class Md2ImageConverter {
|
|
6
|
+
private browser;
|
|
7
|
+
private options;
|
|
8
|
+
constructor(options?: Md2ImageOptions);
|
|
9
|
+
/**
|
|
10
|
+
* 初始化浏览器
|
|
11
|
+
*/
|
|
12
|
+
private initBrowser;
|
|
13
|
+
/**
|
|
14
|
+
* 关闭浏览器
|
|
15
|
+
*/
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* 获取背景样式
|
|
19
|
+
*/
|
|
20
|
+
private getBackgroundStyle;
|
|
21
|
+
/**
|
|
22
|
+
* 计算目标高度(根据模式)
|
|
23
|
+
*/
|
|
24
|
+
private calculateHeight;
|
|
25
|
+
/**
|
|
26
|
+
* 预处理 Markdown (支持卡片语法)
|
|
27
|
+
*/
|
|
28
|
+
private preprocessMarkdown;
|
|
29
|
+
/**
|
|
30
|
+
* 生成扩展功能的脚本
|
|
31
|
+
*/
|
|
32
|
+
private generateExtensionScripts;
|
|
33
|
+
/**
|
|
34
|
+
* 生成卡片样式
|
|
35
|
+
*/
|
|
36
|
+
private generateCardStyles;
|
|
37
|
+
/**
|
|
38
|
+
* 生成完整的 HTML
|
|
39
|
+
*/
|
|
40
|
+
private generateHTML;
|
|
41
|
+
/**
|
|
42
|
+
* 转换 Markdown 为图片
|
|
43
|
+
*/
|
|
44
|
+
convert(markdown: string, outputPath?: string): Promise<ConvertResult>;
|
|
45
|
+
/**
|
|
46
|
+
* 批量转换
|
|
47
|
+
*/
|
|
48
|
+
convertBatch(items: Array<{
|
|
49
|
+
markdown: string;
|
|
50
|
+
outputPath: string;
|
|
51
|
+
}>): Promise<ConvertResult[]>;
|
|
52
|
+
/**
|
|
53
|
+
* 从文件转换
|
|
54
|
+
* @param filePath Markdown 文件路径
|
|
55
|
+
* @param outputPath 可选,输出路径(不提供则生成同名 .png 文件)
|
|
56
|
+
*/
|
|
57
|
+
convertFromFile(filePath: string, outputPath?: string): Promise<ConvertResult>;
|
|
58
|
+
/**
|
|
59
|
+
* 从目录批量转换
|
|
60
|
+
* @param dirPath 目录路径
|
|
61
|
+
* @param outputDir 可选,输出目录(不提供则输出到原文件所在目录)
|
|
62
|
+
* @param recursive 是否递归处理子目录,默认 false
|
|
63
|
+
*/
|
|
64
|
+
convertFromDirectory(dirPath: string, outputDir?: string, recursive?: boolean): Promise<ConvertResult[]>;
|
|
65
|
+
/**
|
|
66
|
+
* 更新配置
|
|
67
|
+
*/
|
|
68
|
+
updateOptions(options: Partial<Md2ImageOptions>): void;
|
|
69
|
+
/**
|
|
70
|
+
* 获取当前配置
|
|
71
|
+
*/
|
|
72
|
+
getOptions(): Required<Md2ImageOptions>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 便捷函数:快速转换
|
|
76
|
+
* 支持 Markdown 字符串或文件路径
|
|
77
|
+
* @param input Markdown 字符串或文件路径
|
|
78
|
+
* @param options 转换选项
|
|
79
|
+
*/
|
|
80
|
+
export declare function md2image(input: string, options?: Md2ImageOptions): Promise<ConvertResult>;
|
|
81
|
+
/**
|
|
82
|
+
* 便捷函数:批量转换目录
|
|
83
|
+
* @param dirPath 目录路径
|
|
84
|
+
* @param options 转换选项
|
|
85
|
+
* @param recursive 是否递归处理子目录
|
|
86
|
+
*/
|
|
87
|
+
export declare function md2imageDir(dirPath: string, options?: Md2ImageOptions, recursive?: boolean): Promise<ConvertResult[]>;
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Md2ImageConverter = void 0;
|
|
7
|
+
exports.md2image = md2image;
|
|
8
|
+
exports.md2imageDir = md2imageDir;
|
|
9
|
+
const marked_1 = require("marked");
|
|
10
|
+
const puppeteer_1 = __importDefault(require("puppeteer"));
|
|
11
|
+
const path_1 = require("path");
|
|
12
|
+
const types_1 = require("./types");
|
|
13
|
+
const styles_1 = require("./styles");
|
|
14
|
+
const assets_loader_1 = require("./assets-loader");
|
|
15
|
+
const file_utils_1 = require("./file-utils");
|
|
16
|
+
/**
|
|
17
|
+
* Markdown 转图片转换器
|
|
18
|
+
*/
|
|
19
|
+
class Md2ImageConverter {
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
this.browser = null;
|
|
22
|
+
// 设置默认选项
|
|
23
|
+
this.options = {
|
|
24
|
+
mode: options.mode || types_1.ExportMode.XHS,
|
|
25
|
+
width: options.width || 640,
|
|
26
|
+
padding: options.padding || 40,
|
|
27
|
+
fontSize: options.fontSize || 18,
|
|
28
|
+
backgroundPreset: options.backgroundPreset || 'purple',
|
|
29
|
+
customBackground: options.customBackground,
|
|
30
|
+
scale: options.scale || 3,
|
|
31
|
+
outputPath: options.outputPath || (0, path_1.join)(process.cwd(), 'output.png'),
|
|
32
|
+
extensions: {
|
|
33
|
+
enableMath: options.extensions?.enableMath !== false,
|
|
34
|
+
enableDiagram: options.extensions?.enableDiagram !== false,
|
|
35
|
+
enableCard: options.extensions?.enableCard !== false,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
// 配置 marked
|
|
39
|
+
marked_1.marked.setOptions({
|
|
40
|
+
breaks: true,
|
|
41
|
+
gfm: true,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 初始化浏览器
|
|
46
|
+
*/
|
|
47
|
+
async initBrowser() {
|
|
48
|
+
if (!this.browser) {
|
|
49
|
+
this.browser = await puppeteer_1.default.launch({
|
|
50
|
+
headless: true,
|
|
51
|
+
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 关闭浏览器
|
|
57
|
+
*/
|
|
58
|
+
async close() {
|
|
59
|
+
if (this.browser) {
|
|
60
|
+
await this.browser.close();
|
|
61
|
+
this.browser = null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 获取背景样式
|
|
66
|
+
*/
|
|
67
|
+
getBackgroundStyle() {
|
|
68
|
+
if (this.options.customBackground) {
|
|
69
|
+
return (0, styles_1.generateCustomBackground)(this.options.customBackground);
|
|
70
|
+
}
|
|
71
|
+
return styles_1.BACKGROUND_PRESETS[this.options.backgroundPreset];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 计算目标高度(根据模式)
|
|
75
|
+
*/
|
|
76
|
+
calculateHeight(mode, width) {
|
|
77
|
+
switch (mode) {
|
|
78
|
+
case types_1.ExportMode.XHS:
|
|
79
|
+
case 'xhs':
|
|
80
|
+
// 小红书 3:4 比例
|
|
81
|
+
return Math.round((width / 3) * 4);
|
|
82
|
+
case types_1.ExportMode.PYQ:
|
|
83
|
+
case 'pyq':
|
|
84
|
+
// 朋友圈 1290:2796 比例
|
|
85
|
+
return Math.round(width * (2796 / 1290));
|
|
86
|
+
case types_1.ExportMode.FREE:
|
|
87
|
+
case 'free':
|
|
88
|
+
default:
|
|
89
|
+
return null; // 自适应高度
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 预处理 Markdown (支持卡片语法)
|
|
94
|
+
*/
|
|
95
|
+
preprocessMarkdown(markdown) {
|
|
96
|
+
let processed = markdown;
|
|
97
|
+
// 处理卡片语法 :::card
|
|
98
|
+
if (this.options.extensions.enableCard) {
|
|
99
|
+
processed = processed.replace(/:::card(?:\s+(info|success|warning|error))?\s*\n([\s\S]*?)\n:::/g, (_match, type, content) => {
|
|
100
|
+
const cardClass = type ? `card-${type}` : '';
|
|
101
|
+
return `<div class="madopic-card ${cardClass}">\n<div class="card-content">\n\n${content}\n\n</div>\n</div>`;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return processed;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 生成扩展功能的脚本
|
|
108
|
+
*/
|
|
109
|
+
generateExtensionScripts() {
|
|
110
|
+
const scripts = [];
|
|
111
|
+
// KaTeX 数学公式支持
|
|
112
|
+
if (this.options.extensions.enableMath) {
|
|
113
|
+
scripts.push(`
|
|
114
|
+
<script>
|
|
115
|
+
// 等待 KaTeX 加载完成后渲染数学公式
|
|
116
|
+
window.addEventListener('load', function() {
|
|
117
|
+
if (typeof renderMathInElement !== 'undefined') {
|
|
118
|
+
renderMathInElement(document.body, {
|
|
119
|
+
delimiters: [
|
|
120
|
+
{left: '$$', right: '$$', display: true},
|
|
121
|
+
{left: '$', right: '$', display: false},
|
|
122
|
+
{left: '\\\\[', right: '\\\\]', display: true},
|
|
123
|
+
{left: '\\\\(', right: '\\\\)', display: false}
|
|
124
|
+
],
|
|
125
|
+
throwOnError: false
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
</script>
|
|
130
|
+
`);
|
|
131
|
+
}
|
|
132
|
+
// Mermaid 图表支持
|
|
133
|
+
if (this.options.extensions.enableDiagram) {
|
|
134
|
+
scripts.push(`
|
|
135
|
+
<script>
|
|
136
|
+
// 初始化 Mermaid
|
|
137
|
+
if (typeof mermaid !== 'undefined') {
|
|
138
|
+
mermaid.initialize({
|
|
139
|
+
startOnLoad: true,
|
|
140
|
+
theme: 'default',
|
|
141
|
+
themeVariables: {
|
|
142
|
+
primaryColor: '#6366f1',
|
|
143
|
+
primaryTextColor: '#1f2937',
|
|
144
|
+
primaryBorderColor: '#4f46e5',
|
|
145
|
+
lineColor: '#6b7280',
|
|
146
|
+
secondaryColor: '#f3f4f6',
|
|
147
|
+
tertiaryColor: '#ffffff'
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
</script>
|
|
152
|
+
`);
|
|
153
|
+
}
|
|
154
|
+
return scripts.join('\n');
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 生成卡片样式
|
|
158
|
+
*/
|
|
159
|
+
generateCardStyles() {
|
|
160
|
+
if (!this.options.extensions.enableCard) {
|
|
161
|
+
return '';
|
|
162
|
+
}
|
|
163
|
+
return `
|
|
164
|
+
.madopic-card {
|
|
165
|
+
margin: 20px 0;
|
|
166
|
+
padding: 20px 24px;
|
|
167
|
+
background: linear-gradient(135deg, rgba(99, 102, 241, 0.12) 0%, rgba(139, 92, 246, 0.12) 100%);
|
|
168
|
+
border: 1px solid rgba(99, 102, 241, 0.2);
|
|
169
|
+
border-radius: 10px;
|
|
170
|
+
box-shadow: 0 4px 20px rgba(99, 102, 241, 0.1);
|
|
171
|
+
transition: all 0.3s ease;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.madopic-card .card-content {
|
|
175
|
+
color: var(--text-light);
|
|
176
|
+
line-height: 1.7;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.madopic-card.card-info {
|
|
180
|
+
background: linear-gradient(135deg, rgba(59, 130, 246, 0.12) 0%, rgba(29, 78, 216, 0.12) 100%);
|
|
181
|
+
border-color: rgba(59, 130, 246, 0.2);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.madopic-card.card-success {
|
|
185
|
+
background: linear-gradient(135deg, rgba(16, 185, 129, 0.12) 0%, rgba(5, 150, 105, 0.12) 100%);
|
|
186
|
+
border-color: rgba(16, 185, 129, 0.2);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.madopic-card.card-warning {
|
|
190
|
+
background: linear-gradient(135deg, rgba(245, 158, 11, 0.12) 0%, rgba(217, 119, 6, 0.12) 100%);
|
|
191
|
+
border-color: rgba(245, 158, 11, 0.2);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.madopic-card.card-error {
|
|
195
|
+
background: linear-gradient(135deg, rgba(239, 68, 68, 0.12) 0%, rgba(220, 38, 38, 0.12) 100%);
|
|
196
|
+
border-color: rgba(239, 68, 68, 0.2);
|
|
197
|
+
}
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* 生成完整的 HTML
|
|
202
|
+
*/
|
|
203
|
+
generateHTML(markdownContent) {
|
|
204
|
+
// 预处理 Markdown
|
|
205
|
+
const preprocessed = this.preprocessMarkdown(markdownContent);
|
|
206
|
+
const htmlContent = marked_1.marked.parse(preprocessed);
|
|
207
|
+
const backgroundStyle = this.getBackgroundStyle();
|
|
208
|
+
const fontSizeVars = (0, styles_1.generateFontSizeVars)(this.options.fontSize);
|
|
209
|
+
const targetHeight = this.calculateHeight(this.options.mode, this.options.width);
|
|
210
|
+
const posterHeightStyle = targetHeight
|
|
211
|
+
? `height: ${targetHeight}px; min-height: ${targetHeight}px; overflow: hidden;`
|
|
212
|
+
: 'min-height: 600px;';
|
|
213
|
+
const contentMaxHeightStyle = targetHeight
|
|
214
|
+
? `max-height: ${targetHeight - this.options.padding * 2}px; overflow: hidden;`
|
|
215
|
+
: '';
|
|
216
|
+
// 生成内联资源
|
|
217
|
+
const inlineAssets = [];
|
|
218
|
+
const inlineScripts = [];
|
|
219
|
+
if (this.options.extensions.enableMath) {
|
|
220
|
+
// 内联 KaTeX CSS
|
|
221
|
+
const katexCss = assets_loader_1.AssetLoader.getKatexCss();
|
|
222
|
+
if (katexCss) {
|
|
223
|
+
inlineAssets.push(`<style>${katexCss}</style>`);
|
|
224
|
+
}
|
|
225
|
+
// 内联 KaTeX JS
|
|
226
|
+
const katexJs = assets_loader_1.AssetLoader.getKatexJs();
|
|
227
|
+
const autoRenderJs = assets_loader_1.AssetLoader.getKatexAutoRender();
|
|
228
|
+
if (katexJs && autoRenderJs) {
|
|
229
|
+
inlineScripts.push(`<script>${katexJs}</script>`);
|
|
230
|
+
inlineScripts.push(`<script>${autoRenderJs}</script>`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (this.options.extensions.enableDiagram) {
|
|
234
|
+
// 内联 Mermaid JS
|
|
235
|
+
const mermaidJs = assets_loader_1.AssetLoader.getMermaidJs();
|
|
236
|
+
if (mermaidJs) {
|
|
237
|
+
inlineScripts.push(`<script>${mermaidJs}</script>`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return `
|
|
241
|
+
<!DOCTYPE html>
|
|
242
|
+
<html lang="zh-CN">
|
|
243
|
+
<head>
|
|
244
|
+
<meta charset="UTF-8">
|
|
245
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
246
|
+
<title>Markdown to Image</title>
|
|
247
|
+
${inlineAssets.join('\n ')}
|
|
248
|
+
${inlineScripts.join('\n ')}
|
|
249
|
+
<style>
|
|
250
|
+
${styles_1.FULL_STYLES}
|
|
251
|
+
${this.generateCardStyles()}
|
|
252
|
+
</style>
|
|
253
|
+
</head>
|
|
254
|
+
<body>
|
|
255
|
+
<div class="markdown-poster" style="
|
|
256
|
+
background: ${backgroundStyle};
|
|
257
|
+
width: ${this.options.width}px;
|
|
258
|
+
padding: ${this.options.padding}px;
|
|
259
|
+
${posterHeightStyle}
|
|
260
|
+
">
|
|
261
|
+
<div class="poster-content" style="
|
|
262
|
+
${fontSizeVars}
|
|
263
|
+
${contentMaxHeightStyle}
|
|
264
|
+
">
|
|
265
|
+
${htmlContent}
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
${this.generateExtensionScripts()}
|
|
269
|
+
</body>
|
|
270
|
+
</html>
|
|
271
|
+
`.trim();
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* 转换 Markdown 为图片
|
|
275
|
+
*/
|
|
276
|
+
async convert(markdown, outputPath) {
|
|
277
|
+
await this.initBrowser();
|
|
278
|
+
if (!this.browser) {
|
|
279
|
+
throw new Error('浏览器初始化失败');
|
|
280
|
+
}
|
|
281
|
+
const page = await this.browser.newPage();
|
|
282
|
+
try {
|
|
283
|
+
// 生成 HTML 内容
|
|
284
|
+
const htmlContent = this.generateHTML(markdown);
|
|
285
|
+
// 设置页面内容
|
|
286
|
+
await page.setContent(htmlContent, {
|
|
287
|
+
waitUntil: 'networkidle0',
|
|
288
|
+
});
|
|
289
|
+
// 等待渲染完成
|
|
290
|
+
await page.waitForSelector('.markdown-poster', { timeout: 5000 });
|
|
291
|
+
// 如果启用了扩展功能,等待额外时间确保渲染完成
|
|
292
|
+
if (this.options.extensions.enableMath || this.options.extensions.enableDiagram) {
|
|
293
|
+
// 等待 1.5 秒确保 KaTeX 和 Mermaid 渲染完成
|
|
294
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
295
|
+
}
|
|
296
|
+
// 获取元素尺寸
|
|
297
|
+
const element = await page.$('.markdown-poster');
|
|
298
|
+
if (!element) {
|
|
299
|
+
throw new Error('无法找到 .markdown-poster 元素');
|
|
300
|
+
}
|
|
301
|
+
const boundingBox = await element.boundingBox();
|
|
302
|
+
if (!boundingBox) {
|
|
303
|
+
throw new Error('无法获取元素边界框');
|
|
304
|
+
}
|
|
305
|
+
// 截图
|
|
306
|
+
const finalOutputPath = outputPath || this.options.outputPath;
|
|
307
|
+
// 设置设备像素比以提高清晰度
|
|
308
|
+
await page.setViewport({
|
|
309
|
+
width: this.options.width,
|
|
310
|
+
height: boundingBox.height,
|
|
311
|
+
deviceScaleFactor: this.options.scale,
|
|
312
|
+
});
|
|
313
|
+
await element.screenshot({
|
|
314
|
+
path: finalOutputPath,
|
|
315
|
+
type: 'png',
|
|
316
|
+
omitBackground: false,
|
|
317
|
+
});
|
|
318
|
+
return {
|
|
319
|
+
imagePath: finalOutputPath,
|
|
320
|
+
width: Math.round(boundingBox.width),
|
|
321
|
+
height: Math.round(boundingBox.height),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
finally {
|
|
325
|
+
await page.close();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* 批量转换
|
|
330
|
+
*/
|
|
331
|
+
async convertBatch(items) {
|
|
332
|
+
await this.initBrowser();
|
|
333
|
+
const results = [];
|
|
334
|
+
for (const item of items) {
|
|
335
|
+
const result = await this.convert(item.markdown, item.outputPath);
|
|
336
|
+
results.push(result);
|
|
337
|
+
}
|
|
338
|
+
return results;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* 从文件转换
|
|
342
|
+
* @param filePath Markdown 文件路径
|
|
343
|
+
* @param outputPath 可选,输出路径(不提供则生成同名 .png 文件)
|
|
344
|
+
*/
|
|
345
|
+
async convertFromFile(filePath, outputPath) {
|
|
346
|
+
const markdown = await (0, file_utils_1.readMarkdownFile)(filePath);
|
|
347
|
+
const finalOutputPath = outputPath || (0, file_utils_1.generateOutputPath)(filePath);
|
|
348
|
+
return this.convert(markdown, finalOutputPath);
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* 从目录批量转换
|
|
352
|
+
* @param dirPath 目录路径
|
|
353
|
+
* @param outputDir 可选,输出目录(不提供则输出到原文件所在目录)
|
|
354
|
+
* @param recursive 是否递归处理子目录,默认 false
|
|
355
|
+
*/
|
|
356
|
+
async convertFromDirectory(dirPath, outputDir, recursive = false) {
|
|
357
|
+
const markdownFiles = await (0, file_utils_1.getMarkdownFiles)(dirPath, recursive);
|
|
358
|
+
const results = [];
|
|
359
|
+
for (const filePath of markdownFiles) {
|
|
360
|
+
const outputPath = (0, file_utils_1.generateOutputPath)(filePath, outputDir);
|
|
361
|
+
const result = await this.convertFromFile(filePath, outputPath);
|
|
362
|
+
results.push(result);
|
|
363
|
+
}
|
|
364
|
+
return results;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* 更新配置
|
|
368
|
+
*/
|
|
369
|
+
updateOptions(options) {
|
|
370
|
+
this.options = {
|
|
371
|
+
...this.options,
|
|
372
|
+
...options,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* 获取当前配置
|
|
377
|
+
*/
|
|
378
|
+
getOptions() {
|
|
379
|
+
return { ...this.options };
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
exports.Md2ImageConverter = Md2ImageConverter;
|
|
383
|
+
/**
|
|
384
|
+
* 便捷函数:快速转换
|
|
385
|
+
* 支持 Markdown 字符串或文件路径
|
|
386
|
+
* @param input Markdown 字符串或文件路径
|
|
387
|
+
* @param options 转换选项
|
|
388
|
+
*/
|
|
389
|
+
async function md2image(input, options) {
|
|
390
|
+
const converter = new Md2ImageConverter(options);
|
|
391
|
+
try {
|
|
392
|
+
// 检测输入是文件路径还是 Markdown 内容
|
|
393
|
+
const isFilePath = await (0, file_utils_1.isFile)(input);
|
|
394
|
+
if (isFilePath) {
|
|
395
|
+
// 如果是文件路径
|
|
396
|
+
return await converter.convertFromFile(input, options?.outputPath);
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
// 否则当作 Markdown 内容
|
|
400
|
+
return await converter.convert(input);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
finally {
|
|
404
|
+
await converter.close();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* 便捷函数:批量转换目录
|
|
409
|
+
* @param dirPath 目录路径
|
|
410
|
+
* @param options 转换选项
|
|
411
|
+
* @param recursive 是否递归处理子目录
|
|
412
|
+
*/
|
|
413
|
+
async function md2imageDir(dirPath, options, recursive = false) {
|
|
414
|
+
const converter = new Md2ImageConverter(options);
|
|
415
|
+
try {
|
|
416
|
+
return await converter.convertFromDirectory(dirPath, options?.outputPath, recursive);
|
|
417
|
+
}
|
|
418
|
+
finally {
|
|
419
|
+
await converter.close();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 扩展功能配置
|
|
3
|
+
*/
|
|
4
|
+
export interface ExtensionOptions {
|
|
5
|
+
/**
|
|
6
|
+
* 是否启用数学公式支持 (KaTeX)
|
|
7
|
+
* @default false
|
|
8
|
+
*/
|
|
9
|
+
enableMath?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* 是否启用图表支持 (Mermaid)
|
|
12
|
+
* @default false
|
|
13
|
+
*/
|
|
14
|
+
enableDiagram?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* 是否启用卡片语法
|
|
17
|
+
* @default true
|
|
18
|
+
*/
|
|
19
|
+
enableCard?: boolean;
|
|
20
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 判断路径是否为文件
|
|
3
|
+
*/
|
|
4
|
+
export declare function isFile(path: string): Promise<boolean>;
|
|
5
|
+
/**
|
|
6
|
+
* 判断路径是否为目录
|
|
7
|
+
*/
|
|
8
|
+
export declare function isDirectory(path: string): Promise<boolean>;
|
|
9
|
+
/**
|
|
10
|
+
* 读取 Markdown 文件内容
|
|
11
|
+
*/
|
|
12
|
+
export declare function readMarkdownFile(filePath: string): Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* 获取目录中所有的 Markdown 文件
|
|
15
|
+
*/
|
|
16
|
+
export declare function getMarkdownFiles(dirPath: string, recursive?: boolean): Promise<string[]>;
|
|
17
|
+
/**
|
|
18
|
+
* 生成输出文件路径
|
|
19
|
+
* 例如: './docs/README.md' -> './docs/README.png'
|
|
20
|
+
*/
|
|
21
|
+
export declare function generateOutputPath(inputPath: string, outputDir?: string): string;
|