Skip to content

Commit e5dd18e

Browse files
committed
Generalise and simplify rendering, tidy exports
Should not rely on any particular naming conventions (except "images"). Not quite there but still need to support our messy Inkscape templates.
1 parent f992c33 commit e5dd18e

File tree

2 files changed

+82
-90
lines changed

2 files changed

+82
-90
lines changed

src/pdfbaker/__init__.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,7 @@
99
)
1010
from .render import (
1111
create_env,
12-
encode_image,
13-
encode_images,
14-
highlight,
15-
process_list_item_texts,
16-
process_list_items,
17-
process_style,
1812
process_template_data,
19-
process_text_with_jinja,
20-
space_bullets,
2113
)
2214

2315
__all__ = [
@@ -29,13 +21,5 @@
2921
"load_pages",
3022
# Render functions
3123
"create_env",
32-
"encode_image",
33-
"encode_images",
34-
"highlight",
35-
"process_list_item_texts",
36-
"process_list_items",
37-
"process_style",
3824
"process_template_data",
39-
"process_text_with_jinja",
40-
"space_bullets",
4125
]

src/pdfbaker/render.py

Lines changed: 82 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,12 @@
77
import jinja2
88

99
__all__ = [
10-
"process_template_data",
1110
"create_env",
11+
"process_template_data",
1212
]
1313

14-
15-
def highlight(text, style=None):
16-
"""Replaces <highlight>text</highlight> with highlighted tspan elements."""
17-
if not text:
18-
return text
19-
20-
if not style or "highlight_colour" not in style:
21-
raise ValueError(
22-
"highlight_colour must be configured in style to use <highlight> tags"
23-
)
24-
25-
def replacer(match):
26-
return (
27-
f'<tspan style="fill:{style["highlight_colour"]}">{match.group(1)}</tspan>'
28-
)
29-
30-
return re.sub(r"<highlight>(.*?)</highlight>", replacer, text)
14+
# Fields that need line counting for positioning
15+
LINE_COUNT_FIELDS = {"text", "title"}
3116

3217

3318
def process_style(style, theme):
@@ -39,38 +24,100 @@ def process_style(style, theme):
3924

4025

4126
def process_text_with_jinja(env, text, template_data):
42-
"""Process text through Jinja templating."""
27+
"""Process text through Jinja templating and apply highlighting."""
4328
if text is None:
4429
return None
4530

4631
template = env.from_string(text)
4732
processed = template.render(**template_data)
48-
if "style" in template_data:
49-
processed = highlight(processed, template_data["style"])
33+
34+
if "style" in template_data and "highlight_colour" in template_data["style"]:
35+
36+
def replacer(match):
37+
return (
38+
f'<tspan style="fill:{template_data["style"]["highlight_colour"]}">'
39+
f"{match.group(1)}</tspan>"
40+
)
41+
42+
processed = re.sub(r"<highlight>(.*?)</highlight>", replacer, processed)
43+
5044
return processed
5145

5246

53-
def process_list_item_texts(env, items, template_data):
54-
"""Process text fields in list items through Jinja."""
55-
for item in items:
47+
def process_list_items(list_items, template_data):
48+
"""Process a list of text items.
49+
50+
Applies Jinja templating and calculates positions for SVG layout.
51+
"""
52+
env = jinja2.Environment()
53+
previous_lines = 0
54+
55+
for i, item in enumerate(list_items):
56+
# Process text fields
5657
if "text" in item:
5758
item["text"] = process_text_with_jinja(env, item["text"], template_data)
5859
if "title" in item:
5960
item["title"] = process_text_with_jinja(env, item["title"], template_data)
60-
return items
61-
6261

63-
def process_list_items(list_items):
64-
"""Process a list of text items to calculate line positions."""
65-
previous_lines = 0
66-
for i, item in enumerate(list_items):
62+
# Calculate positions for SVG layout
6763
item["lines"] = previous_lines
6864
item["position"] = i
69-
if item.get("text") is not None:
70-
previous_lines = item["text"].count("\n") + previous_lines + 1
65+
# Count lines from both text and title fields
66+
for field in LINE_COUNT_FIELDS:
67+
if item.get(field) is not None:
68+
previous_lines = item[field].count("\n") + previous_lines + 1
69+
7170
return list_items
7271

7372

73+
def process_nested_text(template, data=None):
74+
"""Process text fields in any nested dictionary or list structure.
75+
76+
Args:
77+
template: The template structure to process
78+
data: Optional data to use for rendering. If None, uses template as data.
79+
"""
80+
env = jinja2.Environment()
81+
render_data = data if data is not None else template
82+
83+
if isinstance(template, dict):
84+
return {
85+
key: process_nested_text(value, render_data)
86+
if isinstance(value, dict | list)
87+
else process_text_with_jinja(env, value, render_data)
88+
if isinstance(value, str)
89+
else value
90+
for key, value in template.items()
91+
}
92+
if isinstance(template, list):
93+
return [
94+
process_nested_text(item, render_data)
95+
if isinstance(item, dict | list)
96+
else process_text_with_jinja(env, item, render_data)
97+
if isinstance(item, str)
98+
else item
99+
for item in template
100+
]
101+
102+
return template
103+
104+
105+
def process_nested_lists(data, template_data):
106+
"""Process any nested lists of items that have text fields."""
107+
if isinstance(data, dict):
108+
for key, value in data.items():
109+
if isinstance(value, list) and value and isinstance(value[0], dict):
110+
# Check if any item in the list has text or title fields
111+
if any("text" in item or "title" in item for item in value):
112+
data[key] = process_list_items(value, template_data)
113+
elif isinstance(value, dict | list):
114+
process_nested_lists(value, template_data)
115+
elif isinstance(data, list):
116+
for item in data:
117+
if isinstance(item, dict | list):
118+
process_nested_lists(item, template_data)
119+
120+
74121
def process_template_data(template_data, defaults, images_dir=None):
75122
"""Process and enhance template data with images, list items, and styling."""
76123
# Process style first
@@ -83,30 +130,11 @@ def process_template_data(template_data, defaults, images_dir=None):
83130

84131
template_data["style"] = process_style(template_data["style"], defaults["theme"])
85132

86-
# Create single Jinja environment for all text processing
87-
env = jinja2.Environment()
88-
89133
# Process all text fields through Jinja
90-
for key in template_data:
91-
if (key == "text" or key.startswith("text_")) and template_data[
92-
key
93-
] is not None:
94-
template_data[key] = process_text_with_jinja(
95-
env, template_data[key], template_data
96-
)
134+
template_data = process_nested_text(template_data)
97135

98-
# Process list items
99-
if template_data.get("list_items") is not None:
100-
template_data["list_items"] = process_list_item_texts(
101-
env, template_data["list_items"], template_data
102-
)
103-
template_data["list_items"] = process_list_items(template_data["list_items"])
104-
105-
if template_data.get("specs_items") is not None:
106-
template_data["specs_items"] = process_list_item_texts(
107-
env, template_data["specs_items"], template_data
108-
)
109-
template_data["specs_items"] = process_list_items(template_data["specs_items"])
136+
# Process any nested lists of items that have text fields
137+
process_nested_lists(template_data, template_data)
110138

111139
# Process images
112140
if template_data.get("images") is not None:
@@ -124,29 +152,9 @@ def create_env(templates_dir=None):
124152
loader=jinja2.FileSystemLoader(str(templates_dir)),
125153
autoescape=jinja2.select_autoescape(),
126154
)
127-
env.filters["space_bullets"] = space_bullets
128155
return env
129156

130157

131-
def space_bullets(text):
132-
"""Add spacing after each bullet point while preserving line formatting."""
133-
if not text:
134-
return text
135-
136-
lines = text.split("\n")
137-
result = []
138-
139-
for i, line in enumerate(lines):
140-
if line.strip().startswith("•"):
141-
if i > 0 and not lines[i - 1].strip() == "":
142-
result.append("")
143-
result.append(line)
144-
else:
145-
result.append(line)
146-
147-
return "\n".join(result)
148-
149-
150158
def encode_image(filename, images_dir):
151159
"""Encode an image file to a base64 data URI."""
152160
image_path = os.path.join(images_dir, filename)

0 commit comments

Comments
 (0)