chengjie vor 4 Monaten
Ursprung
Commit
56afa1b037
2 geänderte Dateien mit 221 neuen und 303 gelöschten Zeilen
  1. BIN
      public/images/PDF.png
  2. 221 303
      src/api/yjbdc/yjbdcController.js

BIN
public/images/PDF.png


+ 221 - 303
src/api/yjbdc/yjbdcController.js

@@ -366,6 +366,7 @@ export async function DeleteYJBDCArticleList(ctx) {
366 366
 }
367 367
 
368 368
 
369
+
369 370
 export async function GeneratePDF(ctx) {
370 371
     const params = ctx.request.body;
371 372
     if (!params || !params.Content) {
@@ -373,32 +374,32 @@ export async function GeneratePDF(ctx) {
373 374
         ctx.body = { error: 'Invalid request body: Content is required' };
374 375
         return;
375 376
     }
376
-    //文章类型
377
-    const ARTICLE_STYLE={
378
-        "成长":"Personal Growth",
379
-        "童话":"Fairy Tales",
380
-        "家庭亲子":"Family Stories",
381
-        "人生励志":"Inspirational",
382
-        "科幻":"Science Fiction",
383
-        "奇幻":"Fantasy",
384
-        "校园生活":"School Life",
385
-        "节日文化":"Cultural Stories",
386
-        "旅行":"Travel Stories",
387
-        "科普":"Popular Science",
388
-        "动物":"Animal Stories",
389
-        "环保":"Environmental Stories",
377
+
378
+    // 文章类型映射
379
+    const ARTICLE_STYLE = {
380
+        "成长": "Personal Growth",
381
+        "童话": "Fairy Tales",
382
+        "家庭亲子": "Family Stories",
383
+        "人生励志": "Inspirational",
384
+        "科幻": "Science Fiction",
385
+        "奇幻": "Fantasy",
386
+        "校园生活": "School Life",
387
+        "节日文化": "Cultural Stories",
388
+        "旅行": "Travel Stories",
389
+        "科普": "Popular Science",
390
+        "动物": "Animal Stories",
391
+        "环保": "Environmental Stories",
390 392
     };
391 393
 
392
-    //等级难度
393
-    const LEVEL=[
394
+    // 等级难度
395
+    const LEVEL = [
394 396
         "Primary school vocabulary size",
395 397
         "Junior high school vocabulary size",
396 398
         "High school vocabulary size",
397 399
         "College vocabulary size"
398
-    ]
400
+    ];
399 401
 
400 402
     const content = params.Content;
401
-    //console.log("Generating PDF with content:", JSON.stringify(content).substring(0, 200) + "...");
402 403
     
403 404
     try {
404 405
         // 创建新的 PDF 文档 - 使用A4尺寸
@@ -416,9 +417,8 @@ export async function GeneratePDF(ctx) {
416 417
         // 注册中文字体
417 418
         doc.registerFont('ChineseFont', './public/fonts/方正黑体简体.TTF');
418 419
         
419
-        // 定义字体选择函数,根据内容是否包含中文选择合适的字体
420
+        // 定义字体选择函数
420 421
         const selectFont = (text, defaultFont = 'Helvetica') => {
421
-            // 检查文本是否包含中文字符
422 422
             if (/[\u4E00-\u9FFF]/.test(text)) {
423 423
                 return 'ChineseFont';
424 424
             }
@@ -429,302 +429,261 @@ export async function GeneratePDF(ctx) {
429 429
         const chunks = [];
430 430
         doc.on('data', (chunk) => chunks.push(chunk));
431 431
         
432
-        // 像素到点(pt)的转换函数 - A4纸张像素尺寸2100×2970,PDFKit点尺寸595.28×841.89
432
+        // 像素到点(pt)的转换函数
433 433
         const pixelToPt = (pixel) => pixel * (595.28 / 2100);
434 434
 
435
-        // 获取文章内容和单词列表
435
+        // 获取文章内容
436 436
         let articleText = "";
437 437
         if (content.ArticleEnglish) {
438
-            if (Array.isArray(content.ArticleEnglish)) {
439
-                articleText = content.ArticleEnglish.join(" ");
440
-            } else {
441
-                articleText = content.ArticleEnglish;
442
-            }
438
+            articleText = Array.isArray(content.ArticleEnglish) ? 
439
+                content.ArticleEnglish.join(" ") : content.ArticleEnglish;
443 440
         } else {
444 441
             articleText = "No content available";
445 442
         }
446
-        
447
-        // 清理文章文本中的HTML标签
448 443
         articleText = articleText.replace(/<[^>]*>/g, '');
449 444
             
450
-        // 获取单词列表(如果存在)
445
+        // 获取单词列表
451 446
         let words = [];
452 447
         if (content.Words) {
453
-            try {
454
-                if (typeof content.Words === 'string') {
455
-                    // 尝试解析JSON字符串
456
-                    words = content.Words.split(",");
457
-                } else if (Array.isArray(content.Words)) {
458
-                    // 已经是数组
459
-                    words = content.Words;
460
-                }
461
-            } catch (e) {
462
-                console.error("Error parsing words:", e);
463
-                // 如果解析失败,尝试直接使用
464
-                words = typeof content.Words === 'string' ? content.Words.split(',') : [];
465
-            }
448
+            words = typeof content.Words === 'string' ? 
449
+                content.Words.split(",") : 
450
+                (Array.isArray(content.Words) ? content.Words : []);
466 451
         }
467
-        //console.log("Words to display:", words);
468 452
 
469
-        // 获取问题列表(如果存在)
453
+        // 获取问题列表
470 454
         let questions = [];
471 455
         if (content.Question && Array.isArray(content.Question)) {
472 456
             questions = content.Question;
473 457
         }
474
-        //console.log("Questions to display:", questions.length);
475
-
476
-        // 1. 在top:90,left:120像素处写"Story",60像素大小,Semibold粗细
477
-        doc.font('Helvetica-Bold')  // 使用Helvetica-Bold作为Semibold替代
478
-           .fontSize(pixelToPt(60))
479
-           .text("Story", pixelToPt(120), pixelToPt(90), {
480
-               width: pixelToPt(200),
481
-               align: 'left'
482
-           });
483 458
 
484
-        // 2. 在top:250,left:120像素处写英文文章,48像素大小,宽1240像素,高自适应,Regular粗细
485
-        doc.font('Helvetica')
486
-           .fontSize(pixelToPt(48))
487
-           .text(articleText, pixelToPt(120), pixelToPt(250), {
488
-               width: pixelToPt(1240),
489
-               align: 'left',
490
-               lineGap: pixelToPt(50)  // 行间距
491
-           });
459
+        doc.image('./public/images/PDF.png', 0, 0, {
460
+                width: pixelToPt(2100),
461
+                height: pixelToPt(2970)
462
+        });
492 463
 
493
-        // 记录文章结束位置的y坐标
494
-        const articleEndY = doc.y;
495
-        //console.log("Article end Y position:", articleEndY);
464
+        // 1. 标题 - 文章类型
465
+        doc.font(selectFont(ARTICLE_STYLE[content.ArticleStyle] || "Story"))
466
+           .fontSize(pixelToPt(48))
467
+           .text(ARTICLE_STYLE[content.ArticleStyle] || "Story", 
468
+                 pixelToPt(120), pixelToPt(110));
496 469
 
497
-        // 3. 在top:90,left:1443像素处写"备注",48像素大小,Regular粗细
498
-        // 使用中文字体显示中文
470
+        // 2. 副标题 - 难度级别
471
+        doc.font('Helvetica')
472
+           .fontSize(pixelToPt(30))
473
+           .text(LEVEL[content.Level] || "", 
474
+                 pixelToPt(120), pixelToPt(170));
475
+
476
+        // 3. 时间
477
+        const currentDate = moment().format("YYYY年MM月DD日 HH:mm");
478
+        if (params.CreateTime)
479
+            currentDate=moment(params.CreateTime).format("YYYY年MM月DD日 HH:mm");
499 480
         doc.font('ChineseFont')
500
-           .fontSize(pixelToPt(48))
501
-           .text("备注", pixelToPt(1443), pixelToPt(90), {
502
-               width: pixelToPt(537),
503
-               align: 'left'
504
-           });
481
+           .fontSize(pixelToPt(30))
482
+           .text(currentDate, 
483
+                 pixelToPt(120), pixelToPt(212));
484
+
505 485
 
506
-        // 4. 在top:210,left:1443像素处画一条黑线,537像素宽,10像素高
507
-        doc.rect(pixelToPt(1443), pixelToPt(210), pixelToPt(537), pixelToPt(10))
486
+        // 4. 黑线
487
+        doc.rect(pixelToPt(120), pixelToPt(289), pixelToPt(537), pixelToPt(10))
508 488
            .fill('black');
509 489
 
510
-        // 5. 在top:250,left:1443像素,宽537像素,高900像素的空间内竖排显示1-10个单词,每行一个,右对齐
511
-        doc.font('Helvetica');  // 使用常规体(Regular)
512
-           
513
-        // 计算单词显示区域
514
-        const wordAreaTop = pixelToPt(250);
515
-        const wordAreaLeft = pixelToPt(1443);
516
-        const wordAreaWidth = pixelToPt(537);
517
-        const wordAreaHeight = pixelToPt(900);
518
-        
519
-        // 确保words是数组
520
-        const wordsArray = Array.isArray(words) ? words : 
521
-                          (typeof words === 'string' ? words.split(',') : []);
522
-        
523
-        // 显示单词(最多10个)
524
-        const maxWords = Math.min(wordsArray.length, 10);
525
-        
526
-        // 根据单词数量动态计算每个单词的高度空间
527
-        const wordHeight = pixelToPt(80);  // 固定高度,确保足够的空间显示
528
-        
529
-        // 首先确定所有单词中需要的最小字体大小
530
-        let minFontSize = pixelToPt(48); // 默认字体大小
531
-        for (let i = 0; i < maxWords; i++) {
532
-            const word = wordsArray[i];
533
-            if (word) {
534
-                const wordLength = word.toString().length;
535
-                // 根据词组长度确定需要的字体大小
536
-                if (wordLength > 15) {
537
-                    minFontSize = Math.min(minFontSize, pixelToPt(36)); // 长词组使用较小字体
538
-                } else if (wordLength > 10) {
539
-                    minFontSize = Math.min(minFontSize, pixelToPt(42)); // 中等长度词组使用中等字体
540
-                }
541
-            }
542
-        }
543
-        
544
-        // 使用确定的字体大小显示所有单词
545
-        doc.fontSize(minFontSize);
546
-        console.log(`Using font size ${minFontSize} for all words`);
490
+        // 5. 单词列表
491
+        doc.font('Helvetica')
492
+           .fontSize(pixelToPt(36));
547 493
         
548
-        for (let i = 0; i < maxWords; i++) {
549
-            const word = wordsArray[i];
550
-            if (word) {  // 确保单词存在
551
-                const yPosition = wordAreaTop + (i * wordHeight);
552
-                
553
-                // 确保不超出指定区域
554
-                if (yPosition + wordHeight <= wordAreaTop + wordAreaHeight) {
555
-                    // 添加调试信息
556
-                    console.log(`Drawing word: "${word}" at position: ${yPosition}`);
557
-                    
558
-                    doc.text(word.toString(), wordAreaLeft, yPosition, {
559
-                        width: wordAreaWidth,
560
-                        align: 'right',  // 右对齐
561
-                        lineBreak: true  // 允许自动换行,处理特别长的词组
562
-                    });
563
-                }
564
-            }
565
-        }
494
+        let wordY = pixelToPt(364);
495
+        words.slice(0, 10).forEach(word => {
496
+            doc.text(word, pixelToPt(122), wordY, {
497
+                width: pixelToPt(535),
498
+                align: 'left'
499
+            });
500
+            wordY += pixelToPt(70);
501
+        });
502
+
503
+        // 6. 文章内容
504
+        doc.font('Helvetica')
505
+           .fontSize(pixelToPt(48))
506
+           .text(articleText, pixelToPt(740), pixelToPt(105), {
507
+               width: pixelToPt(1240),
508
+               lineGap: pixelToPt(40.5)
509
+           });
566 510
 
567
-        // 6. 在文章显示全部完成的下方100像素,left是120位置,画一个黑线,1860像素宽,10像素高
568
-        const lineY = Math.max(articleEndY, wordAreaTop + wordAreaHeight) + pixelToPt(100);
569
-        doc.rect(pixelToPt(120), lineY, pixelToPt(1860), pixelToPt(10))
511
+        // 7. 黑线
512
+        const articleEndY = doc.y;
513
+        doc.rect(pixelToPt(120), articleEndY + pixelToPt(41), 
514
+                pixelToPt(1860), pixelToPt(10))
570 515
            .fill('black');
571 516
 
572
-        // 7-12. 添加问题和答案部分
517
+        // 8-13. 问题和答案
573 518
         if (questions.length > 0) {
574
-            // 问题1和答案 - left:120像素位置
575
-            if (questions.length >= 1) {
576
-                // 问题标题 - 检查是否包含中文并使用适当的字体
577
-                const q1Text = `1. ${questions[0].QuestionEnglish || "Question 1"}`;
578
-                doc.font(/[\u4E00-\u9FFF]/.test(q1Text) ? 'ChineseFont' : 'Helvetica-Bold')
579
-                   .fontSize(pixelToPt(36))
580
-                   .text(q1Text, pixelToPt(120), lineY + pixelToPt(100), {
581
-                       width: pixelToPt(540),
582
-                       align: 'left'
583
-                   });
584
-                
585
-                // 问题选项 - 使用常规字体(Regular)
586
-                doc.font('Helvetica') // 明确设置为常规字体,不使用粗体
587
-                   .fontSize(pixelToPt(36));
588
-                
589
-                const options = questions[0].OptionsEnglish || [];
590
-                let optionY = doc.y + pixelToPt(20);
591
-                
592
-                for (let i = 0; i < options.length; i++) {
593
-                    doc.text(options[i] || `Option ${i+1}`, pixelToPt(120), optionY, {
594
-                        width: pixelToPt(540),
595
-                        align: 'left'
519
+            // 定义问题的X坐标位置
520
+            const questionXPositions = [
521
+                pixelToPt(120),  // 问题1
522
+                pixelToPt(120),  // 问题2
523
+                pixelToPt(740),  // 问题3
524
+                pixelToPt(740),  // 问题4
525
+                pixelToPt(1360)  // 问题5
526
+            ];
527
+            
528
+            // 存储每列最后一个选项的Y坐标位置
529
+            let lastOptionYPositions = {
530
+                column1: 0, // 用于跟踪第一列(问题1)的最后位置
531
+                column2: 0, // 用于跟踪第二列(问题3)的最后位置
532
+            };
533
+            
534
+            // 首先渲染问题1、3、5(第一行问题)
535
+            const firstRowQuestions = [0, 2, 4]; // 问题1、3、5的索引
536
+            
537
+            for (const i of firstRowQuestions) {
538
+                if (i < questions.length && questions[i]) {
539
+                    const currentX = questionXPositions[i];
540
+                    const currentY = articleEndY + pixelToPt(130); // 所有第一行问题的起始Y坐标相同
541
+                    
542
+                    // 渲染问题文本
543
+                    doc.font('Helvetica-Bold')
544
+                       .fontSize(pixelToPt(36))
545
+                       .text(`${i+1}. ${questions[i].QuestionEnglish || `Question ${i+1}`}`, 
546
+                             currentX, currentY, {
547
+                           width: pixelToPt(540),
548
+                           align: 'left'
549
+                       });
550
+                    
551
+                    // 获取问题文本渲染后的Y坐标位置
552
+                    const questionEndY = doc.y;
553
+                    
554
+                    // 选项起始位置 = 问题结束位置 + 20像素间距
555
+                    let optionY = questionEndY + pixelToPt(28);
556
+                    
557
+                    // 渲染选项
558
+                    const options = questions[i].OptionsEnglish || [];
559
+                    options.forEach((opt) => {
560
+                        doc.font('Helvetica')
561
+                           .fontSize(pixelToPt(36))
562
+                           .text(`${opt}`, currentX, optionY);
563
+                        
564
+                        // 更新选项Y坐标,为下一个选项做准备
565
+                        optionY = doc.y + pixelToPt(8);
596 566
                     });
597
-                    optionY = doc.y + pixelToPt(10);
567
+                    
568
+                    // 保存该列最后一个选项的Y坐标
569
+                    if (i === 0) {
570
+                        lastOptionYPositions.column1 = doc.y;
571
+                    } else if (i === 2) {
572
+                        lastOptionYPositions.column2 = doc.y;
573
+                    }
598 574
                 }
599 575
             }
600 576
             
601
-            // 问题2和答案 - left:120像素位置
602
-            if (questions.length >= 2) {
603
-                // 获取问题1结束的Y坐标
604
-                const q1EndY = doc.y + pixelToPt(60);
577
+            // 然后渲染问题2和4(第二行问题)
578
+            if (questions.length > 1 && questions[1]) {
579
+                // 问题2位于问题1的选项下方60像素处
580
+                const question2Y = lastOptionYPositions.column1 + pixelToPt(75);
605 581
                 
606
-                // 问题标题
607 582
                 doc.font('Helvetica-Bold')
608 583
                    .fontSize(pixelToPt(36))
609
-                   .text(`2. ${questions[1].QuestionEnglish || "Question 2"}`, pixelToPt(120), q1EndY, {
584
+                   .text(`2. ${questions[1].QuestionEnglish || "Question 2"}`, 
585
+                         questionXPositions[1], question2Y, {
610 586
                        width: pixelToPt(540),
611 587
                        align: 'left'
612 588
                    });
613 589
                 
614
-                // 问题选项
615
-                doc.font('Helvetica')
616
-                   .fontSize(pixelToPt(36));
590
+                // 获取问题文本渲染后的Y坐标位置
591
+                const questionEndY = doc.y;
617 592
                 
618
-                const options = questions[1].OptionsEnglish || [];
619
-                let optionY = doc.y + pixelToPt(20);
593
+                // 选项起始位置 = 问题结束位置 + 20像素间距
594
+                let optionY = questionEndY + pixelToPt(28);
620 595
                 
621
-                for (let i = 0; i < options.length; i++) {
622
-                    doc.text(options[i] || `Option ${i+1}`, pixelToPt(120), optionY, {
623
-                        width: pixelToPt(540),
624
-                        align: 'left'
625
-                    });
626
-                    optionY = doc.y + pixelToPt(10);
627
-                }
596
+                // 渲染选项
597
+                const options = questions[1].OptionsEnglish || [];
598
+                options.forEach((opt) => {
599
+                    doc.font('Helvetica')
600
+                       .fontSize(pixelToPt(36))
601
+                       .text(`${opt}`, questionXPositions[1], optionY);
602
+                    
603
+                    // 更新选项Y坐标,为下一个选项做准备
604
+                    optionY = doc.y + pixelToPt(8);
605
+                });
628 606
             }
629 607
             
630
-            // 问题3和问题4 - left:740像素位置
631
-            if (questions.length >= 3) {
632
-                // 问题3标题
608
+            if (questions.length > 3 && questions[3]) {
609
+                // 问题4位于问题3的选项下方60像素处
610
+                const question4Y = lastOptionYPositions.column2 + pixelToPt(75);
611
+                
633 612
                 doc.font('Helvetica-Bold')
634 613
                    .fontSize(pixelToPt(36))
635
-                   .text(`3. ${questions[2].QuestionEnglish || "Question 3"}`, pixelToPt(740), lineY + pixelToPt(100), {
614
+                   .text(`4. ${questions[3].QuestionEnglish || "Question 4"}`, 
615
+                         questionXPositions[3], question4Y, {
636 616
                        width: pixelToPt(540),
637 617
                        align: 'left'
638 618
                    });
639 619
                 
640
-                // 问题3选项
641
-                doc.font('Helvetica')
642
-                   .fontSize(pixelToPt(36));
620
+                // 获取问题文本渲染后的Y坐标位置
621
+                const questionEndY = doc.y;
643 622
                 
644
-                const options3 = questions[2].OptionsEnglish || [];
645
-                let option3Y = doc.y + pixelToPt(20);
646
-                
647
-                for (let i = 0; i < options3.length; i++) {
648
-                    doc.text(options3[i] || `Option ${i+1}`, pixelToPt(740), option3Y, {
649
-                        width: pixelToPt(540),
650
-                        align: 'left'
651
-                    });
652
-                    option3Y = doc.y + pixelToPt(10);
653
-                }
623
+                // 选项起始位置 = 问题结束位置 + 20像素间距
624
+                let optionY = questionEndY + pixelToPt(28);
654 625
                 
655
-                // 问题4 - 如果存在
656
-                if (questions.length >= 4) {
657
-                    // 获取问题3结束的Y坐标
658
-                    const q3EndY = doc.y + pixelToPt(60);
659
-                    
660
-                    // 问题4标题
661
-                    doc.font('Helvetica-Bold')
662
-                       .fontSize(pixelToPt(36))
663
-                       .text(`4. ${questions[3].QuestionEnglish || "Question 4"}`, pixelToPt(740), q3EndY, {
664
-                           width: pixelToPt(540),
665
-                           align: 'left'
666
-                       });
667
-                    
668
-                    // 问题4选项
626
+                // 渲染选项
627
+                const options = questions[3].OptionsEnglish || [];
628
+                options.forEach((opt) => {
669 629
                     doc.font('Helvetica')
670
-                       .fontSize(pixelToPt(36));
671
-                    
672
-                    const options4 = questions[3].OptionsEnglish || [];
673
-                    let option4Y = doc.y + pixelToPt(20);
630
+                       .fontSize(pixelToPt(36))
631
+                       .text(`${opt}`, questionXPositions[3], optionY);
674 632
                     
675
-                    for (let i = 0; i < options4.length; i++) {
676
-                        doc.text(options4[i] || `Option ${i+1}`, pixelToPt(740), option4Y, {
677
-                            width: pixelToPt(540),
678
-                            align: 'left'
679
-                        });
680
-                        option4Y = doc.y + pixelToPt(10);
681
-                    }
682
-                }
633
+                    // 更新选项Y坐标,为下一个选项做准备
634
+                    optionY = doc.y + pixelToPt(8);
635
+                });
683 636
             }
684 637
             
685
-            // 问题5 - left:1360像素位置
686
-            if (questions.length >= 5) {
687
-                // 问题5标题
638
+            // 问题5保持原位置不变
639
+            if (questions.length > 4 && questions[4]) {
640
+                const currentX = questionXPositions[4];
641
+                const currentY = articleEndY + pixelToPt(130);
642
+                
688 643
                 doc.font('Helvetica-Bold')
689 644
                    .fontSize(pixelToPt(36))
690
-                   .text(`5. ${questions[4].QuestionEnglish || "Question 5"}`, pixelToPt(1360), lineY + pixelToPt(100), {
645
+                   .text(`5. ${questions[4].QuestionEnglish || "Question 5"}`, 
646
+                         currentX, currentY, {
691 647
                        width: pixelToPt(540),
692 648
                        align: 'left'
693 649
                    });
694 650
                 
695
-                // 问题5选项
696
-                doc.font('Helvetica')
697
-                   .fontSize(pixelToPt(36));
651
+                const questionEndY = doc.y;
698 652
                 
699
-                const options5 = questions[4].OptionsEnglish || [];
700
-                let option5Y = doc.y + pixelToPt(20);
653
+                let optionY = questionEndY + pixelToPt(28);
701 654
                 
702
-                for (let i = 0; i < options5.length; i++) {
703
-                    doc.text(options5[i] || `Option ${i+1}`, pixelToPt(1360), option5Y, {
704
-                        width: pixelToPt(540),
705
-                        align: 'left'
706
-                    });
707
-                    option5Y = doc.y + pixelToPt(10);
708
-                }
655
+                // 渲染选项
656
+                const options = questions[4].OptionsEnglish || [];
657
+                options.forEach((opt) => {
658
+                    doc.font('Helvetica')
659
+                       .fontSize(pixelToPt(36))
660
+                       .text(`${opt}`, currentX, optionY);
661
+                    
662
+                    // 更新选项Y坐标,为下一个选项做准备
663
+                    optionY = doc.y + pixelToPt(8);
664
+                });
709 665
             }
710 666
         }
711 667
 
712
-        // 13. 下方距离底边330像素,left:120像素处,画一条虚线,1860像素宽,20像素高,颜色为#D2D2D2
713
-        const dashLineY = doc.page.height - pixelToPt(330);
668
+        // 14. 虚线
669
+        const lastContentY = doc.page.height - pixelToPt(190);
670
+        // doc.rect(pixelToPt(120), lastContentY, 
671
+        //         pixelToPt(1630), pixelToPt(10))
672
+        //    .fill('#D2D2D2');
714 673
         doc.strokeColor('#D2D2D2')
715
-           .dash(pixelToPt(20), { space: pixelToPt(20) }) // 设置虚线样式:宽20像素,间隔20像素
674
+           .dash(pixelToPt(20), { space: pixelToPt(28) }) // 设置虚线样式:宽20像素,间隔20像素
716 675
            .lineWidth(pixelToPt(10)) // 线高10像素
717
-           .moveTo(pixelToPt(120), dashLineY)
718
-           .lineTo(pixelToPt(120) + pixelToPt(1860), dashLineY)
676
+           .moveTo(pixelToPt(120), lastContentY)
677
+           .lineTo(pixelToPt(120) + pixelToPt(1630), lastContentY)
719 678
            .stroke()
720 679
            .undash(); // 重置虚线样式
721 680
 
722
-        // 14-18. 显示问题答案
681
+        // 15-19. 答案和底部信息
723 682
         doc.font('Helvetica-Bold') // 使用Helvetica-Bold作为Semibold替代
724
-           .fontSize(pixelToPt(45))
683
+           .fontSize(pixelToPt(36))
725 684
            .fillColor('black'); // 重置颜色为黑色
726 685
         
727
-        const answersY = doc.page.height - pixelToPt(177);
686
+        const answersY = doc.page.height - pixelToPt(144);
728 687
         
729 688
         // 获取问题答案
730 689
         const answers = [];
@@ -736,42 +695,19 @@ export async function GeneratePDF(ctx) {
736 695
         }
737 696
         
738 697
         // 显示答案(如果存在)
739
-        const answerPositions = [120, 277, 443, 611, 778];
698
+        const answerPositions = [120, 262, 411, 561, 711];
740 699
         for (let i = 0; i < Math.min(answers.length, 5); i++) {
741
-            doc.text(answers[i], pixelToPt(answerPositions[i]), answersY, {
700
+            doc.text(answers[i], pixelToPt(answerPositions[i]), answersY+2, {
742 701
                 width: pixelToPt(100),
743 702
                 align: 'left'
744 703
             });
745 704
         }
746
-        
747
-        // 20. 显示"秒过·语境背单词"
748
-        const appNameY = doc.page.height - pixelToPt(223);
749
-        doc.font('ChineseFont')  // 使用中文字体显示中文
750
-           .fontSize(pixelToPt(48))
751
-           .fillColor('black')
752
-           .text("秒过·语境背单词", pixelToPt(1320), appNameY, {
753
-               width: pixelToPt(400),
754
-               align: 'right'
755
-           });
756
-        
757
-        // 21. 显示"微信小程序"
758
-        const appTypeY = doc.page.height - pixelToPt(163);
759
-        doc.font('ChineseFont')  // 使用中文字体显示中文
760
-           .fontSize(pixelToPt(28))
761
-           .text("微信小程序", pixelToPt(1320), appTypeY, {
762
-               width: pixelToPt(400),
763
-               align: 'right'
764
-           });
765 705
 
766
-        // 19. 显示当前时间
767
-        const currentTime = moment().format('YYYY年MM月DD日 HH:mm');
768
-        const timeY = doc.page.height - pixelToPt(118);
769
-        // 计算正确的位置:总宽度2100像素 - 右边距380像素 = 右边缘位置1720像素
770
-        // 为了使用text()方法的右对齐,设置起始位置为1720-380=1340像素
771
-        doc.font('ChineseFont')  // 使用中文字体显示包含中文的日期
772
-           .fontSize(pixelToPt(32))
773
-           .text(currentTime, pixelToPt(1340), timeY, {
774
-               width: pixelToPt(380),
706
+        // 20-21. 应用名称
707
+        doc.font('ChineseFont')
708
+           .fontSize(pixelToPt(36))
709
+           .text("语境背单词(微信小程序)", pixelToPt(1338), answersY,{
710
+               width: pixelToPt(440),
775 711
                align: 'right'
776 712
            });
777 713
         
@@ -782,7 +718,7 @@ export async function GeneratePDF(ctx) {
782 718
             const qrCodeWidth = pixelToPt(200);
783 719
             const qrCodeHeight = pixelToPt(200);
784 720
             const qrCodeX = doc.page.width - pixelToPt(120) - qrCodeWidth; // 右边距离转换为左边距离
785
-            const qrCodeY = doc.page.height - pixelToPt(60) - qrCodeHeight; // 底部距离转换为顶部距离
721
+            const qrCodeY = doc.page.height - pixelToPt(100) - qrCodeHeight; // 底部距离转换为顶部距离
786 722
             
787 723
             // 添加二维码图片
788 724
             doc.image('./public/images/acode/YJBDC_QRCode.png', qrCodeX, qrCodeY, {
@@ -794,44 +730,26 @@ export async function GeneratePDF(ctx) {
794 730
             console.error("Error adding QR Code image:", imgError);
795 731
         }
796 732
         
797
-        //console.log("PDF generation completed, finalizing document...");
798 733
 
799
-        // 使用 Promise 等待 PDF 生成完成
800
-        await new Promise((resolve, reject) => {
801
-            // 监听 end 事件,表示 PDF 生成完成
802
-            doc.on('end', () => {
803
-                try {
804
-                    // 将所有数据块合并为一个 Buffer
805
-                    const pdfBuffer = Buffer.concat(chunks);
806
-                    console.log("PDF buffer size:", pdfBuffer.length);
807
-                    
808
-                    // 设置响应头
809
-                    ctx.set('Content-Type', 'application/pdf');
810
-                    ctx.set('Content-Disposition', 'attachment; filename=reading.pdf');
811
-                    
812
-                    // 设置响应体为 PDF buffer
813
-                    ctx.body = pdfBuffer;
814
-                    resolve();
815
-                } catch (err) {
816
-                    console.error("Error in PDF end handler:", err);
817
-                    reject(err);
818
-                }
819
-            });
734
+        // 结束PDF生成
735
+        doc.end();
820 736
 
821
-            // 监听错误事件
822
-            doc.on('error', (err) => {
823
-                console.error("PDF generation error:", err);
824
-                reject(err);
737
+        // 等待PDF生成完成
738
+        const pdfBuffer = await new Promise((resolve) => {
739
+            doc.on('end', () => {
740
+                resolve(Buffer.concat(chunks));
825 741
             });
826
-
827
-            // 结束文档生成
828
-            doc.end();
829 742
         });
830 743
 
744
+        // 设置响应头
745
+        ctx.set('Content-Type', 'application/pdf');
746
+        ctx.set('Content-Disposition', 'attachment; filename=article.pdf');
747
+        ctx.body = pdfBuffer;
748
+
831 749
     } catch (error) {
832
-        console.error('Error generating PDF:', error);
750
+        console.error("Error generating PDF:", error);
833 751
         ctx.status = 500;
834
-        ctx.body = { error: error.message, stack: error.stack };
752
+        ctx.body = { error: "Failed to generate PDF" };
835 753
     }
836 754
 }
837 755