salesforce-composite-envelo.../composite-envelope-builder/scripts/md_to_pdf.py

193 lines
5.0 KiB
Python

#!/usr/bin/env python3
import sys
from pathlib import Path
import textwrap
import re
try:
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
from reportlab.lib.units import inch
from reportlab.lib import colors
except Exception as e:
print('Missing reportlab:', e)
sys.exit(2)
def wrap_text(text, max_chars):
return textwrap.wrap(text, width=max_chars)
def draw_wrapped(c, text, x, y, font_name, font_size, max_width, indent=0):
# Estimate chars per line roughly
approx_char_width = font_size * 0.55
max_chars = max(20, int((max_width - indent) / approx_char_width))
wrapped = wrap_text(text, max_chars)
line_height = font_size * 1.25
for line in wrapped:
c.drawString(x + indent, y, line)
y -= line_height
return y
#!/usr/bin/env python3
import sys
from pathlib import Path
import textwrap
try:
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
from reportlab.lib.units import inch
from reportlab.lib import colors
except Exception as e:
print('Missing reportlab:', e)
sys.exit(2)
def wrap_text(text, max_chars):
return textwrap.wrap(text, width=max_chars)
def draw_wrapped(c, text, x, y, font_name, font_size, max_width, indent=0):
# Estimate chars per line roughly
approx_char_width = font_size * 0.55
max_chars = max(20, int((max_width - indent) / approx_char_width))
wrapped = wrap_text(text, max_chars)
line_height = font_size * 1.25
for line in wrapped:
c.drawString(x + indent, y, line)
y -= line_height
return y
if len(sys.argv) < 3:
print('Usage: md_to_pdf.py input.md output.pdf')
sys.exit(1)
in_path = Path(sys.argv[1])
out_path = Path(sys.argv[2])
if not in_path.exists():
print('Input file not found:', in_path)
sys.exit(1)
text = in_path.read_text(encoding='utf-8')
lines = text.splitlines()
c = canvas.Canvas(str(out_path), pagesize=letter)
width, height = letter
left = inch * 0.75
right = inch * 0.75
top = height - inch * 0.75
bottom = inch * 0.75
max_width = width - left - right
# default fonts
REGULAR = 'Helvetica'
BOLD = 'Helvetica-Bold'
MONO = 'Courier'
y = top
in_code = False
code_font_size = 8
para_font_size = 10
for raw in lines:
line = raw.rstrip('\n')
if line.strip() == '':
y -= para_font_size * 0.6
if y < bottom:
c.showPage()
y = top
continue
# Code fence toggle
if line.strip().startswith('```'):
in_code = not in_code
if in_code:
y -= 6
else:
y -= 6
if y < bottom:
c.showPage()
y = top
continue
if in_code:
c.setFont(MONO, code_font_size)
# draw code line with small left indent
y = draw_wrapped(c, line, left, y, MONO, code_font_size, max_width, indent=10)
c.setFont(REGULAR, para_font_size)
if y < bottom:
c.showPage()
y = top
continue
# Headings
if line.lstrip().startswith('#'):
hashes, _, rest = line.partition(' ')
level = hashes.count('#')
text = rest.strip()
# add extra space BEFORE top-level headings so they don't butt against previous paragraph
if level == 1:
y -= para_font_size * 1.8
elif level == 2:
y -= para_font_size * 0.8
else:
y -= para_font_size * 0.6
if level == 1:
font_size = 18
font = BOLD
elif level == 2:
font_size = 14
font = BOLD
else:
font_size = 12
font = BOLD
c.setFont(font, font_size)
y = draw_wrapped(c, text, left, y, font, font_size, max_width)
# modest spacing after headings
y -= para_font_size * 0.6
c.setFont(REGULAR, para_font_size)
if y < bottom:
c.showPage()
y = top
continue
# Horizontal rule
if line.strip() in ('---', '***', '___'):
c.setStrokeColor(colors.black)
c.setLineWidth(1)
y -= 6
c.line(left, y, left + max_width, y)
y -= 12
if y < bottom:
c.showPage()
y = top
continue
# Lists
stripped = line.lstrip()
if stripped.startswith(('- ', '* ', '+ ')) or stripped[:2].isdigit() and stripped[2:].startswith('. '):
bullet = ''
content = stripped[2:].strip() if not stripped[0].isdigit() else stripped.split('.', 1)[1].strip()
c.setFont(REGULAR, para_font_size)
# draw bullet and wrapped content with indent
c.drawString(left, y, bullet)
y = draw_wrapped(c, content, left + 12, y, REGULAR, para_font_size, max_width, indent=0)
if y < bottom:
c.showPage()
y = top
continue
# Paragraph
c.setFont(REGULAR, para_font_size)
y = draw_wrapped(c, line, left, y, REGULAR, para_font_size, max_width)
if y < bottom:
c.showPage()
y = top
c.save()
print('Wrote PDF:', out_path)