recipe-inator/backend/app/pdf/escape.py

41 lines
1.2 KiB
Python

import re
_SPECIAL_CHARS = {
"&": r"\&",
"%": r"\%",
"$": r"\$",
"#": r"\#",
"_": r"\_",
"{": r"\{",
"}": r"\}",
"~": r"\textasciitilde{}",
"^": r"\textasciicircum{}",
"\\": r"\textbackslash{}",
}
_SPECIAL_CHARS_RE = re.compile("|".join(re.escape(c) for c in _SPECIAL_CHARS))
def escape_latex(value: str) -> str:
"""Escape LaTeX special characters in plain user-entered text."""
return _SPECIAL_CHARS_RE.sub(lambda m: _SPECIAL_CHARS[m.group(0)], value)
_STEP_BOLD_RE = re.compile(r"^\*\*(.+?)\*\*(.*)$")
def format_multiline(value: str) -> str:
"""Escape text and convert user newlines into LaTeX line breaks.
Supports a leading "**Label**: rest" line convention (seen in the existing
hand-written recipes) which becomes \\textbf{Label}: rest.
"""
lines = value.strip().splitlines()
rendered_lines = []
for line in lines:
match = _STEP_BOLD_RE.match(line)
if match:
label, rest = match.groups()
rendered_lines.append(f"\\textbf{{{escape_latex(label)}}}{escape_latex(rest)}")
else:
rendered_lines.append(escape_latex(line))
return " \\\\\n ".join(rendered_lines)