149 lines
6.7 KiB
Python
149 lines
6.7 KiB
Python
"""Render the verified Chinese CAN Markdown document as a printable PDF."""
|
|
import argparse
|
|
import html
|
|
import re
|
|
from pathlib import Path
|
|
|
|
from reportlab.lib import colors
|
|
from reportlab.lib.enums import TA_LEFT
|
|
from reportlab.lib.pagesizes import A4, landscape
|
|
from reportlab.lib.styles import ParagraphStyle
|
|
from reportlab.pdfbase import pdfmetrics
|
|
from reportlab.pdfbase.ttfonts import TTFont
|
|
from reportlab.platypus import (
|
|
KeepTogether, PageBreak, Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle,
|
|
)
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SOURCE = ROOT / 'docs/AMI_CAN_DBC说明.md'
|
|
OUTPUT = ROOT / 'output/pdf/AMI_CAN报文说明.pdf'
|
|
REMOTE = 'https://git.acetaffy.ltd/acetaffy/AMI/src/branch/main/'
|
|
|
|
|
|
def inline(text):
|
|
"""Escape Markdown and retain useful links in the printed document."""
|
|
text = html.escape(text)
|
|
text = re.sub(r'`([^`]+)`', r'<font color="#155E75">\1</font>', text)
|
|
|
|
def link(match):
|
|
label, target = match.groups()
|
|
if not target.startswith(('http://', 'https://')):
|
|
target = REMOTE + (target[3:] if target.startswith('../') else 'docs/' + target)
|
|
return f'<link href="{target}" color="#155E75">{label}</link>'
|
|
|
|
return re.sub(r'\[([^\]]+)\]\(([^)]+)\)', link, text)
|
|
|
|
|
|
def export(font_path):
|
|
pdfmetrics.registerFont(TTFont('AMIChinese', str(font_path), subfontIndex=0))
|
|
page_width, page_height = landscape(A4)
|
|
width = page_width - 80
|
|
body = ParagraphStyle('Body', fontName='AMIChinese', fontSize=9.3,
|
|
leading=15, spaceAfter=8, wordWrap='CJK', alignment=TA_LEFT)
|
|
heading = ParagraphStyle('Heading', parent=body, fontSize=16, leading=23,
|
|
textColor=colors.HexColor('#12485E'), spaceBefore=3,
|
|
spaceAfter=12, keepWithNext=True)
|
|
subheading = ParagraphStyle('Subheading', parent=body, fontSize=11,
|
|
leading=17, textColor=colors.HexColor('#12485E'),
|
|
spaceBefore=8, spaceAfter=6, keepWithNext=True)
|
|
title = ParagraphStyle('Title', parent=heading, fontSize=25, leading=32)
|
|
cell = ParagraphStyle('Cell', parent=body, fontSize=8.5, leading=12, spaceAfter=0)
|
|
code = ParagraphStyle('Code', parent=body, fontSize=8, leading=13,
|
|
backColor=colors.HexColor('#F2F5F7'), borderPadding=8)
|
|
lines = SOURCE.read_text(encoding='utf-8').splitlines()
|
|
story = []
|
|
i = 0
|
|
while i < len(lines):
|
|
line = lines[i]
|
|
if not line.strip():
|
|
i += 1
|
|
continue
|
|
if line.startswith('# '):
|
|
story.append(Paragraph('AMI CAN 报文说明', title))
|
|
elif line.startswith('## '):
|
|
section = line[3:]
|
|
if section != '使用范围':
|
|
story.append(PageBreak())
|
|
story.append(Paragraph(inline(section), heading))
|
|
elif line.startswith('### '):
|
|
story.append(Paragraph(inline(line[4:]), subheading))
|
|
elif line.startswith('|'):
|
|
rows = []
|
|
while i < len(lines) and lines[i].startswith('|'):
|
|
row = [x.strip() for x in lines[i].strip().strip('|').split('|')]
|
|
if not all(re.fullmatch(r':?-+:?', x) for x in row):
|
|
rows.append([Paragraph(inline(x), cell) for x in row])
|
|
i += 1
|
|
count = len(rows[0])
|
|
proportions = {
|
|
2: [0.15, 0.85],
|
|
3: [0.17, 0.40, 0.43],
|
|
4: [0.12, 0.15, 0.20, 0.53],
|
|
6: [0.11, 0.055, 0.04, 0.28, 0.365, 0.15],
|
|
8: [0.32, 0.055, 0.055, 0.10, 0.09, 0.08, 0.06, 0.24],
|
|
}[count]
|
|
total = sum(proportions)
|
|
table = Table(rows, colWidths=[width*x/total for x in proportions],
|
|
repeatRows=1, hAlign='LEFT')
|
|
table.setStyle(TableStyle([
|
|
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#DDECF2')),
|
|
('ROWBACKGROUNDS', (0, 1), (-1, -1),
|
|
[colors.white, colors.HexColor('#F5F8FA')]),
|
|
('LINEBELOW', (0, 0), (-1, 0), 0.8, colors.HexColor('#7DABB9')),
|
|
('LINEBELOW', (0, 1), (-1, -1), 0.3, colors.HexColor('#D7E0E5')),
|
|
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
|
('LEFTPADDING', (0, 0), (-1, -1), 7),
|
|
('RIGHTPADDING', (0, 0), (-1, -1), 7),
|
|
('TOPPADDING', (0, 0), (-1, -1), 6),
|
|
('BOTTOMPADDING', (0, 0), (-1, -1), 6),
|
|
]))
|
|
if i+1 < len(lines) and not lines[i].strip() and lines[i+1].startswith('`'):
|
|
group = [table, Spacer(1, 10), Paragraph(inline(lines[i+1]), body)]
|
|
if story and isinstance(story[-1], Paragraph) and story[-1].style is subheading:
|
|
group.insert(0, story.pop())
|
|
story.append(KeepTogether(group))
|
|
i += 2
|
|
else:
|
|
story.extend([table, Spacer(1, 10)])
|
|
continue
|
|
elif line.startswith('```'):
|
|
block = []
|
|
i += 1
|
|
while i < len(lines) and not lines[i].startswith('```'):
|
|
block.append(html.escape(lines[i]))
|
|
i += 1
|
|
story.append(KeepTogether([Paragraph('<br/>'.join(block), code)]))
|
|
else:
|
|
paragraph = [line]
|
|
while i+1 < len(lines) and lines[i+1].strip() and not lines[i+1].startswith(('#', '|', '```')):
|
|
i += 1
|
|
paragraph.append(lines[i])
|
|
story.append(Paragraph(inline(' '.join(paragraph)), body))
|
|
i += 1
|
|
|
|
def footer(canvas, doc):
|
|
canvas.saveState()
|
|
canvas.setStrokeColor(colors.HexColor('#C4D8E1'))
|
|
canvas.line(40, 31, page_width-40, 31)
|
|
canvas.setFont('AMIChinese', 8)
|
|
canvas.setFillColor(colors.HexColor('#506775'))
|
|
canvas.drawString(40, 19, 'AMI-2.4 | 2026-10-03 | CAN1 500K / CAN2 250K')
|
|
canvas.drawRightString(page_width-40, 19, f'{doc.page}')
|
|
canvas.restoreState()
|
|
|
|
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
|
document = SimpleDocTemplate(str(OUTPUT), pagesize=landscape(A4),
|
|
leftMargin=40, rightMargin=40,
|
|
topMargin=36, bottomMargin=44,
|
|
title='AMI CAN 报文说明', author='AMI / YMR',
|
|
subject='AMI CAN DBC v2.4: CAN1 500K / CAN2 250K')
|
|
document.build(story, onFirstPage=footer, onLaterPages=footer)
|
|
print(OUTPUT)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--font', type=Path, default=Path('C:/Windows/Fonts/simhei.ttf'),
|
|
help='CJK TrueType font path (default: Windows SimHei)')
|
|
export(parser.parse_args().font)
|