126 lines
5.8 KiB
Python
126 lines
5.8 KiB
Python
# -*- coding: utf-8 -*-
|
||||
|
|
"""
|
|||
|
|
每日英语听力材料 PDF 生成脚本(学习用)
|
|||
|
|
|
|||
|
|
用途:把一段英语学习材料(英文原文 + 逐句音标 + 中文翻译 + 生词表 + 听力技巧)
|
|||
|
|
生成一份排版美观、可离线收藏的 PDF 文档。
|
|||
|
|
|
|||
|
|
依赖:
|
|||
|
|
pip install reportlab
|
|||
|
|
系统字体(Linux):
|
|||
|
|
- DejaVu Sans:覆盖英文与 IPA 音标字符
|
|||
|
|
- 文鼎 UMingCN(uming.ttc):覆盖中文字符(宋体风格)
|
|||
|
|
|
|||
|
|
关键知识点:
|
|||
|
|
1. reportlab 的 Platypus(SimpleDocTemplate + Paragraph + Table)负责自动排版、分页;
|
|||
|
|
2. reportlab 内置字体不支持中文/IPA,需用 TTFont 注册系统 TTF/TTC 字体;
|
|||
|
|
3. 同一段落里混排中文与英文/IPA 时,用 <font name="..."> 标签切换字体;
|
|||
|
|
4. 注意:reportlab 不支持 CFF 轮廓的字体(如 NotoSansCJK.otf),只能用 TrueType;
|
|||
|
|
5. .ttc 集合字体通过 subfontIndex 选择子字体。
|
|||
|
|
"""
|
|||
|
|
import html as h
|
|||
|
|
|
|||
|
|
from reportlab.lib import colors
|
|||
|
|
from reportlab.lib.pagesizes import A4
|
|||
|
|
from reportlab.lib.styles import ParagraphStyle
|
|||
|
|
from reportlab.lib.units import mm
|
|||
|
|
from reportlab.pdfbase import pdfmetrics
|
|||
|
|
from reportlab.pdfbase.ttfonts import TTFont
|
|||
|
|
from reportlab.platypus import (Paragraph, SimpleDocTemplate, Spacer,
|
|||
|
|
Table, TableStyle)
|
|||
|
|
|
|||
|
|
# ------------------------- 1. 注册字体 -------------------------
|
|||
|
|
BASE = '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf'
|
|||
|
|
BASE_BOLD = '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf'
|
|||
|
|
CJK = '/usr/share/fonts/truetype/arphic/uming.ttc'
|
|||
|
|
|
|||
|
|
pdfmetrics.registerFont(TTFont('Base', BASE))
|
|||
|
|
pdfmetrics.registerFont(TTFont('Base-Bold', BASE_BOLD))
|
|||
|
|
pdfmetrics.registerFont(TTFont('CJK', CJK, subfontIndex=0)) # UMingCN
|
|||
|
|
|
|||
|
|
|
|||
|
|
def esc(s: str) -> str:
|
|||
|
|
"""转义 < > & 等,避免 Paragraph 把内容当成 XML 标签。"""
|
|||
|
|
return h.escape(s, quote=False)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ------------------------- 2. 定义样式 -------------------------
|
|||
|
|
title = ParagraphStyle('title', fontName='Base-Bold', fontSize=18, leading=24,
|
|||
|
|
textColor=colors.HexColor('#B22222'), alignment=1, spaceAfter=6)
|
|||
|
|
meta = ParagraphStyle('meta', fontName='CJK', fontSize=10, leading=14,
|
|||
|
|
textColor=colors.HexColor('#666666'), alignment=1, spaceAfter=10)
|
|||
|
|
h1 = ParagraphStyle('h1', fontName='CJK', fontSize=13, leading=18,
|
|||
|
|
textColor=colors.HexColor('#8B0000'), spaceBefore=10, spaceAfter=6)
|
|||
|
|
en = ParagraphStyle('en', fontName='Base', fontSize=11.5, leading=17, spaceBefore=4)
|
|||
|
|
ipa = ParagraphStyle('ipa', fontName='Base', fontSize=9.5, leading=14,
|
|||
|
|
textColor=colors.HexColor('#555555'), leftIndent=6, spaceAfter=2)
|
|||
|
|
note = ParagraphStyle('note', fontName='CJK', fontSize=9.5, leading=14,
|
|||
|
|
textColor=colors.HexColor('#2F5D8A'), leftIndent=12, spaceAfter=4)
|
|||
|
|
zh = ParagraphStyle('zh', fontName='CJK', fontSize=11, leading=18, spaceBefore=3)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def sen(num: str, text: str):
|
|||
|
|
return Paragraph('<font name="Base-Bold">%s</font> %s' % (esc(num), esc(text)), en)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ------------------------- 3. 准备内容数据 -------------------------
|
|||
|
|
sentences = [
|
|||
|
|
# (编号, 英文句子, 音标, 发音要点中文说明)
|
|||
|
|
("1.", "Today is October 1st — China's National Day.",
|
|||
|
|
"/təˈdeɪ ɪz oʊkˈtoʊbər fɜːrst | ˈtʃaɪnəz ˈnæʃənəl deɪ/",
|
|||
|
|
"● 连读:October 1st 中 /r/ 与 /f/ 自然衔接。"),
|
|||
|
|
("2.", "Streets are decorated with red flags, and cities glow with lanterns and flowers.",
|
|||
|
|
"/striːts ər ˈdekəreɪtɪd wɪð red flægz | ənd ˈsɪtiz gloʊ wɪð ˈlæntərnz ənd ˈflaʊərz/",
|
|||
|
|
"● 重读:decorated、glow 是画面词,加重读出画面感。"),
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
words = [
|
|||
|
|
# (单词, 音标, 中文释义)
|
|||
|
|
("decorate", "/ˈdekəreɪt/", "装饰"),
|
|||
|
|
("glow", "/gloʊ/", "发光,焕发光彩"),
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# ------------------------- 4. 组装文档 -------------------------
|
|||
|
|
story = []
|
|||
|
|
story.append(Paragraph('<font name="Base-Bold">Happy National Day!</font>'
|
|||
|
|
'<font name="CJK">(国庆节快乐)</font>', title))
|
|||
|
|
story.append(Paragraph('每日英语听力推送 · 示例材料 · 约1分钟朗读量', meta))
|
|||
|
|
|
|||
|
|
story.append(Paragraph('一、英文原文(逐句 + IPA 音标)', h1))
|
|||
|
|
for num, text, ip, n in sentences:
|
|||
|
|
story.append(sen(num, text))
|
|||
|
|
story.append(Paragraph(esc(ip), ipa))
|
|||
|
|
story.append(Paragraph(esc(n), note))
|
|||
|
|
|
|||
|
|
story.append(Paragraph('二、生词与短语', h1))
|
|||
|
|
cell_en = ParagraphStyle('c1', fontName='Base', fontSize=10, leading=13)
|
|||
|
|
cell_ipa = ParagraphStyle('c2', fontName='Base', fontSize=10, leading=13,
|
|||
|
|
textColor=colors.HexColor('#555555'))
|
|||
|
|
cell_zh = ParagraphStyle('c3', fontName='CJK', fontSize=10, leading=13)
|
|||
|
|
head = ParagraphStyle('ch', fontName='CJK', fontSize=10, leading=13, textColor=colors.white)
|
|||
|
|
data = [[Paragraph('单词/短语', head), Paragraph('音标', head), Paragraph('释义', head)]]
|
|||
|
|
for w, ip, z in words:
|
|||
|
|
data.append([Paragraph(esc(w), cell_en),
|
|||
|
|
Paragraph(esc(ip), cell_ipa),
|
|||
|
|
Paragraph(esc(z), cell_zh)])
|
|||
|
|
tbl = Table(data, colWidths=[52*mm, 58*mm, 62*mm])
|
|||
|
|
tbl.setStyle(TableStyle([
|
|||
|
|
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#8B0000')),
|
|||
|
|
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#F5F0E6')]),
|
|||
|
|
('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#CCCCCC')),
|
|||
|
|
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
|||
|
|
('LEFTPADDING', (0, 0), (-1, -1), 5),
|
|||
|
|
('TOPPADDING', (0, 0), (-1, -1), 4),
|
|||
|
|
('BOTTOMPADDING', (0, 0), (-1, -1), 4),
|
|||
|
|
]))
|
|||
|
|
story.append(tbl)
|
|||
|
|
|
|||
|
|
# ------------------------- 5. 写出 PDF -------------------------
|
|||
|
|
doc = SimpleDocTemplate("每日英语听力_示例.pdf", pagesize=A4,
|
|||
|
|
rightMargin=18*mm, leftMargin=18*mm,
|
|||
|
|
topMargin=16*mm, bottomMargin=16*mm,
|
|||
|
|
title="每日英语听力示例")
|
|||
|
|
doc.build(story)
|
|||
|
|
print("PDF generated OK")
|
|||
|
|
#(注:内容由AI生成)
|