|
|
@@ -375,6 +375,8 @@ export async function GeneratePDF(ctx) {
|
|
375
|
375
|
return;
|
|
376
|
376
|
}
|
|
377
|
377
|
|
|
|
378
|
+ const content = params.Content;
|
|
|
379
|
+
|
|
378
|
380
|
// 文章类型映射
|
|
379
|
381
|
const ARTICLE_STYLE = {
|
|
380
|
382
|
"成长": "Personal Growth",
|
|
|
@@ -398,8 +400,6 @@ export async function GeneratePDF(ctx) {
|
|
398
|
400
|
"High school vocabulary size",
|
|
399
|
401
|
"College vocabulary size"
|
|
400
|
402
|
];
|
|
401
|
|
-
|
|
402
|
|
- const content = params.Content;
|
|
403
|
403
|
|
|
404
|
404
|
try {
|
|
405
|
405
|
// 创建新的 PDF 文档 - 使用A4尺寸
|
|
|
@@ -456,10 +456,10 @@ export async function GeneratePDF(ctx) {
|
|
456
|
456
|
questions = content.Question;
|
|
457
|
457
|
}
|
|
458
|
458
|
|
|
459
|
|
- doc.image('./public/images/PDF.png', 0, 0, {
|
|
460
|
|
- width: pixelToPt(2100),
|
|
461
|
|
- height: pixelToPt(2970)
|
|
462
|
|
- });
|
|
|
459
|
+ // doc.image('./public/images/PDF.png', 0, 0, {
|
|
|
460
|
+ // width: pixelToPt(2100),
|
|
|
461
|
+ // height: pixelToPt(2970)
|
|
|
462
|
+ // });
|
|
463
|
463
|
|
|
464
|
464
|
// 1. 标题 - 文章类型
|
|
465
|
465
|
doc.font(selectFont(ARTICLE_STYLE[content.ArticleStyle] || "Story"))
|
|
|
@@ -556,13 +556,38 @@ export async function GeneratePDF(ctx) {
|
|
556
|
556
|
|
|
557
|
557
|
// 渲染选项
|
|
558
|
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);
|
|
|
559
|
+ const optionLabels = ['A', 'B', 'C', 'D'];
|
|
|
560
|
+
|
|
|
561
|
+ // 设置选项标签宽度和选项总宽度
|
|
|
562
|
+ const labelWidth = pixelToPt(40); // 标签(A./B./C./D.)的宽度
|
|
|
563
|
+ const totalWidth = pixelToPt(496); // 选项的总宽度
|
|
|
564
|
+ const contentWidth = totalWidth - labelWidth; // 选项内容的宽度
|
|
|
565
|
+
|
|
|
566
|
+ options.forEach((opt, index) => {
|
|
|
567
|
+ if (index < optionLabels.length) {
|
|
|
568
|
+ // 提取选项内容,移除可能存在的标签前缀
|
|
|
569
|
+ let optionContent = opt.replace(/^[A-D]\.\s*/, '');
|
|
|
570
|
+
|
|
|
571
|
+ // 渲染选项标签(A./B./C./D.)
|
|
|
572
|
+ doc.font('Helvetica')
|
|
|
573
|
+ .fontSize(pixelToPt(36))
|
|
|
574
|
+ .text(`${optionLabels[index]}.`, currentX, optionY, {
|
|
|
575
|
+ width: labelWidth,
|
|
|
576
|
+ align: 'left'
|
|
|
577
|
+ });
|
|
|
578
|
+
|
|
|
579
|
+ // 渲染选项内容,确保折行时与内容第一行对齐
|
|
|
580
|
+ doc.font('Helvetica')
|
|
|
581
|
+ .fontSize(pixelToPt(36))
|
|
|
582
|
+ .text(optionContent, currentX + labelWidth, optionY, {
|
|
|
583
|
+ width: contentWidth,
|
|
|
584
|
+ align: 'left'
|
|
|
585
|
+ });
|
|
|
586
|
+
|
|
|
587
|
+ // 更新选项Y坐标,为下一个选项做准备
|
|
|
588
|
+ // 获取当前位置,确保下一个选项在当前选项完全渲染后的位置
|
|
|
589
|
+ optionY = doc.y + pixelToPt(8);
|
|
|
590
|
+ }
|
|
566
|
591
|
});
|
|
567
|
592
|
|
|
568
|
593
|
// 保存该列最后一个选项的Y坐标
|
|
|
@@ -595,13 +620,37 @@ export async function GeneratePDF(ctx) {
|
|
595
|
620
|
|
|
596
|
621
|
// 渲染选项
|
|
597
|
622
|
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);
|
|
|
623
|
+ const optionLabels = ['A', 'B', 'C', 'D'];
|
|
|
624
|
+
|
|
|
625
|
+ // 设置选项标签宽度和选项总宽度
|
|
|
626
|
+ const labelWidth = pixelToPt(40); // 标签(A./B./C./D.)的宽度
|
|
|
627
|
+ const totalWidth = pixelToPt(496); // 选项的总宽度
|
|
|
628
|
+ const contentWidth = totalWidth - labelWidth; // 选项内容的宽度
|
|
|
629
|
+
|
|
|
630
|
+ options.forEach((opt, index) => {
|
|
|
631
|
+ if (index < optionLabels.length) {
|
|
|
632
|
+ // 提取选项内容,移除可能存在的标签前缀
|
|
|
633
|
+ let optionContent = opt.replace(/^[A-D]\.\s*/, '');
|
|
|
634
|
+
|
|
|
635
|
+ // 渲染选项标签(A./B./C./D.)
|
|
|
636
|
+ doc.font('Helvetica')
|
|
|
637
|
+ .fontSize(pixelToPt(36))
|
|
|
638
|
+ .text(`${optionLabels[index]}.`, questionXPositions[1], optionY, {
|
|
|
639
|
+ width: labelWidth,
|
|
|
640
|
+ align: 'left'
|
|
|
641
|
+ });
|
|
|
642
|
+
|
|
|
643
|
+ // 渲染选项内容,确保折行时与内容第一行对齐
|
|
|
644
|
+ doc.font('Helvetica')
|
|
|
645
|
+ .fontSize(pixelToPt(36))
|
|
|
646
|
+ .text(optionContent, questionXPositions[1] + labelWidth, optionY, {
|
|
|
647
|
+ width: contentWidth,
|
|
|
648
|
+ align: 'left'
|
|
|
649
|
+ });
|
|
|
650
|
+
|
|
|
651
|
+ // 更新选项Y坐标,为下一个选项做准备
|
|
|
652
|
+ optionY = doc.y + pixelToPt(8);
|
|
|
653
|
+ }
|
|
605
|
654
|
});
|
|
606
|
655
|
}
|
|
607
|
656
|
|
|
|
@@ -625,13 +674,37 @@ export async function GeneratePDF(ctx) {
|
|
625
|
674
|
|
|
626
|
675
|
// 渲染选项
|
|
627
|
676
|
const options = questions[3].OptionsEnglish || [];
|
|
628
|
|
- options.forEach((opt) => {
|
|
629
|
|
- doc.font('Helvetica')
|
|
630
|
|
- .fontSize(pixelToPt(36))
|
|
631
|
|
- .text(`${opt}`, questionXPositions[3], optionY);
|
|
632
|
|
-
|
|
633
|
|
- // 更新选项Y坐标,为下一个选项做准备
|
|
634
|
|
- optionY = doc.y + pixelToPt(8);
|
|
|
677
|
+ const optionLabels = ['A', 'B', 'C', 'D'];
|
|
|
678
|
+
|
|
|
679
|
+ // 设置选项标签宽度和选项总宽度
|
|
|
680
|
+ const labelWidth = pixelToPt(40); // 标签(A./B./C./D.)的宽度
|
|
|
681
|
+ const totalWidth = pixelToPt(496); // 选项的总宽度
|
|
|
682
|
+ const contentWidth = totalWidth - labelWidth; // 选项内容的宽度
|
|
|
683
|
+
|
|
|
684
|
+ options.forEach((opt, index) => {
|
|
|
685
|
+ if (index < optionLabels.length) {
|
|
|
686
|
+ // 提取选项内容,移除可能存在的标签前缀
|
|
|
687
|
+ let optionContent = opt.replace(/^[A-D]\.\s*/, '');
|
|
|
688
|
+
|
|
|
689
|
+ // 渲染选项标签(A./B./C./D.)
|
|
|
690
|
+ doc.font('Helvetica')
|
|
|
691
|
+ .fontSize(pixelToPt(36))
|
|
|
692
|
+ .text(`${optionLabels[index]}.`, questionXPositions[3], optionY, {
|
|
|
693
|
+ width: labelWidth,
|
|
|
694
|
+ align: 'left'
|
|
|
695
|
+ });
|
|
|
696
|
+
|
|
|
697
|
+ // 渲染选项内容,确保折行时与内容第一行对齐
|
|
|
698
|
+ doc.font('Helvetica')
|
|
|
699
|
+ .fontSize(pixelToPt(36))
|
|
|
700
|
+ .text(optionContent, questionXPositions[3] + labelWidth, optionY, {
|
|
|
701
|
+ width: contentWidth,
|
|
|
702
|
+ align: 'left'
|
|
|
703
|
+ });
|
|
|
704
|
+
|
|
|
705
|
+ // 更新选项Y坐标,为下一个选项做准备
|
|
|
706
|
+ optionY = doc.y + pixelToPt(8);
|
|
|
707
|
+ }
|
|
635
|
708
|
});
|
|
636
|
709
|
}
|
|
637
|
710
|
|
|
|
@@ -654,13 +727,37 @@ export async function GeneratePDF(ctx) {
|
|
654
|
727
|
|
|
655
|
728
|
// 渲染选项
|
|
656
|
729
|
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);
|
|
|
730
|
+ const optionLabels = ['A', 'B', 'C', 'D'];
|
|
|
731
|
+
|
|
|
732
|
+ // 设置选项标签宽度和选项总宽度
|
|
|
733
|
+ const labelWidth = pixelToPt(40); // 标签(A./B./C./D.)的宽度
|
|
|
734
|
+ const totalWidth = pixelToPt(496); // 选项的总宽度
|
|
|
735
|
+ const contentWidth = totalWidth - labelWidth; // 选项内容的宽度
|
|
|
736
|
+
|
|
|
737
|
+ options.forEach((opt, index) => {
|
|
|
738
|
+ if (index < optionLabels.length) {
|
|
|
739
|
+ // 提取选项内容,移除可能存在的标签前缀
|
|
|
740
|
+ let optionContent = opt.replace(/^[A-D]\.\s*/, '');
|
|
|
741
|
+
|
|
|
742
|
+ // 渲染选项标签(A./B./C./D.)
|
|
|
743
|
+ doc.font('Helvetica')
|
|
|
744
|
+ .fontSize(pixelToPt(36))
|
|
|
745
|
+ .text(`${optionLabels[index]}.`, currentX, optionY, {
|
|
|
746
|
+ width: labelWidth,
|
|
|
747
|
+ align: 'left'
|
|
|
748
|
+ });
|
|
|
749
|
+
|
|
|
750
|
+ // 渲染选项内容,确保折行时与内容第一行对齐
|
|
|
751
|
+ doc.font('Helvetica')
|
|
|
752
|
+ .fontSize(pixelToPt(36))
|
|
|
753
|
+ .text(optionContent, currentX + labelWidth, optionY, {
|
|
|
754
|
+ width: contentWidth,
|
|
|
755
|
+ align: 'left'
|
|
|
756
|
+ });
|
|
|
757
|
+
|
|
|
758
|
+ // 更新选项Y坐标,为下一个选项做准备
|
|
|
759
|
+ optionY = doc.y + pixelToPt(8);
|
|
|
760
|
+ }
|
|
664
|
761
|
});
|
|
665
|
762
|
}
|
|
666
|
763
|
}
|