Update server.js
Browse files
server.js
CHANGED
|
@@ -227,6 +227,150 @@ app.post('/api/parse-character-png', upload.single('file'), async (req, res) =>
|
|
| 227 |
}
|
| 228 |
});
|
| 229 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
const PORT = process.env.PORT || 7860;
|
| 231 |
|
| 232 |
app.listen(PORT, () => {
|
|
|
|
| 227 |
}
|
| 228 |
});
|
| 229 |
|
| 230 |
+
// PDF生成API
|
| 231 |
+
app.post('/api/generate-pdf', async (req, res) => {
|
| 232 |
+
try {
|
| 233 |
+
const { title, chapters, style } = req.body;
|
| 234 |
+
|
| 235 |
+
if (!title || !chapters) {
|
| 236 |
+
return res.status(400).json({ error: '缺少必要参数' });
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
// 导入必要的库
|
| 240 |
+
const { default: PDFDocument } = await import('pdfkit');
|
| 241 |
+
const { default: fetch } = await import('node-fetch');
|
| 242 |
+
|
| 243 |
+
// 创建PDF文档
|
| 244 |
+
const doc = new PDFDocument({
|
| 245 |
+
size: 'A4',
|
| 246 |
+
margins: { top: 50, bottom: 50, left: 50, right: 50 }
|
| 247 |
+
});
|
| 248 |
+
|
| 249 |
+
// 设置响应头
|
| 250 |
+
res.setHeader('Content-Type', 'application/pdf');
|
| 251 |
+
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(title)}.pdf"`);
|
| 252 |
+
|
| 253 |
+
// 管道输出到响应
|
| 254 |
+
doc.pipe(res);
|
| 255 |
+
|
| 256 |
+
// 样式配置
|
| 257 |
+
const styles = {
|
| 258 |
+
modern: {
|
| 259 |
+
titleFont: 'Helvetica-Bold',
|
| 260 |
+
titleSize: 32,
|
| 261 |
+
headingFont: 'Helvetica-Bold',
|
| 262 |
+
headingSize: 20,
|
| 263 |
+
bodyFont: 'Helvetica',
|
| 264 |
+
bodySize: 12,
|
| 265 |
+
lineHeight: 1.5
|
| 266 |
+
},
|
| 267 |
+
classic: {
|
| 268 |
+
titleFont: 'Times-Bold',
|
| 269 |
+
titleSize: 36,
|
| 270 |
+
headingFont: 'Times-Bold',
|
| 271 |
+
headingSize: 22,
|
| 272 |
+
bodyFont: 'Times-Roman',
|
| 273 |
+
bodySize: 13,
|
| 274 |
+
lineHeight: 1.6
|
| 275 |
+
},
|
| 276 |
+
magazine: {
|
| 277 |
+
titleFont: 'Helvetica-Bold',
|
| 278 |
+
titleSize: 40,
|
| 279 |
+
headingFont: 'Helvetica-Bold',
|
| 280 |
+
headingSize: 24,
|
| 281 |
+
bodyFont: 'Helvetica',
|
| 282 |
+
bodySize: 11,
|
| 283 |
+
lineHeight: 1.4
|
| 284 |
+
},
|
| 285 |
+
technical: {
|
| 286 |
+
titleFont: 'Courier-Bold',
|
| 287 |
+
titleSize: 28,
|
| 288 |
+
headingFont: 'Courier-Bold',
|
| 289 |
+
headingSize: 18,
|
| 290 |
+
bodyFont: 'Courier',
|
| 291 |
+
bodySize: 10,
|
| 292 |
+
lineHeight: 1.5
|
| 293 |
+
}
|
| 294 |
+
};
|
| 295 |
+
|
| 296 |
+
const currentStyle = styles[style] || styles.modern;
|
| 297 |
+
|
| 298 |
+
// 封面
|
| 299 |
+
doc.font(currentStyle.titleFont)
|
| 300 |
+
.fontSize(currentStyle.titleSize)
|
| 301 |
+
.text(title, 50, 300, { align: 'center' });
|
| 302 |
+
|
| 303 |
+
doc.fontSize(14)
|
| 304 |
+
.font('Helvetica')
|
| 305 |
+
.text(`生成于 ${new Date().toLocaleDateString('zh-CN')}`, 50, 400, { align: 'center' });
|
| 306 |
+
|
| 307 |
+
doc.addPage();
|
| 308 |
+
|
| 309 |
+
// 生成章节
|
| 310 |
+
for (let i = 0; i < chapters.length; i++) {
|
| 311 |
+
const chapter = chapters[i];
|
| 312 |
+
|
| 313 |
+
// 章节标题
|
| 314 |
+
if (i > 0) {
|
| 315 |
+
doc.addPage();
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
doc.font(currentStyle.headingFont)
|
| 319 |
+
.fontSize(currentStyle.headingSize)
|
| 320 |
+
.text(chapter.title, { align: 'left' });
|
| 321 |
+
|
| 322 |
+
doc.moveDown(1);
|
| 323 |
+
|
| 324 |
+
// 章节内容
|
| 325 |
+
doc.font(currentStyle.bodyFont)
|
| 326 |
+
.fontSize(currentStyle.bodySize);
|
| 327 |
+
|
| 328 |
+
// 移除IMAGE标记
|
| 329 |
+
const cleanContent = chapter.content.replace(/\[IMAGE:.*?\]/g, '');
|
| 330 |
+
const paragraphs = cleanContent.split('\n').filter(p => p.trim());
|
| 331 |
+
|
| 332 |
+
for (const paragraph of paragraphs) {
|
| 333 |
+
doc.text(paragraph, {
|
| 334 |
+
align: 'justify',
|
| 335 |
+
lineGap: currentStyle.lineHeight * 2
|
| 336 |
+
});
|
| 337 |
+
doc.moveDown(0.5);
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
// 插入图片
|
| 341 |
+
if (chapter.images && chapter.images.length > 0) {
|
| 342 |
+
for (const imageUrl of chapter.images) {
|
| 343 |
+
try {
|
| 344 |
+
// 下载图片
|
| 345 |
+
const imageResponse = await fetch(imageUrl);
|
| 346 |
+
const imageBuffer = await imageResponse.buffer();
|
| 347 |
+
|
| 348 |
+
doc.moveDown(1);
|
| 349 |
+
|
| 350 |
+
// 添加图片(适配页面宽度)
|
| 351 |
+
const pageWidth = doc.page.width - 100;
|
| 352 |
+
doc.image(imageBuffer, 50, doc.y, {
|
| 353 |
+
fit: [pageWidth, 300],
|
| 354 |
+
align: 'center'
|
| 355 |
+
});
|
| 356 |
+
|
| 357 |
+
doc.moveDown(1);
|
| 358 |
+
} catch (error) {
|
| 359 |
+
console.error('添加图片失败:', error);
|
| 360 |
+
}
|
| 361 |
+
}
|
| 362 |
+
}
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
// 完成PDF
|
| 366 |
+
doc.end();
|
| 367 |
+
|
| 368 |
+
} catch (error) {
|
| 369 |
+
console.error('PDF生成错误:', error);
|
| 370 |
+
res.status(500).json({ error: error.message });
|
| 371 |
+
}
|
| 372 |
+
});
|
| 373 |
+
|
| 374 |
const PORT = process.env.PORT || 7860;
|
| 375 |
|
| 376 |
app.listen(PORT, () => {
|