Report builder for LaTeX documents.
This module provides functionality for creating structured LaTeX reports programmatically.
The Report class offers a fluent API for building documents with headings, paragraphs,
equations, tables, figures, and lists. Reports can be exported to LaTeX format for
compilation with pdflatex, compiled directly to PDF, or converted to Word documents.
Key Features:
- Fluent API with method chaining for easy document construction
- Support for mathematical equations using LaTeX syntax
- Integration with Blueprints Formula objects
- Table and figure insertion with customizable formatting
- Nested bulleted and numbered lists
- Export to LaTeX (.tex), PDF (.pdf), and Word (.docx) formats
- Multi-language support through translation
Developer notes:
The LaTeX styling is designed to match Word document styling as closely as possible.
Changes to LaTeX output should be reflected in _report_to_word.py converter.
Classes:
-
Report
–
Report builder for creating structured documents with standardized formatting.
utils.report.report.Report
dataclass
Report builder for creating structured documents with standardized formatting.
Check our docs for examples of usage.
Parameters:
-
title
(str, default:
''
)
–
Examples:
>>> report = Report(title="Sample Report")
>>> report.add_heading("Introduction")
>>> report.add_heading("Background", level=2)
>>> report.add_heading("Details", level=3)
>>> report.add_paragraph("This is normal text.")
>>> report.add_paragraph("This is bold text with newline after.", bold=True).add_newline()
>>> report.add_paragraph("This is italic text with 4 newlines after.", italic=True).add_newline(n=4)
>>> report.add_paragraph("This is bold and italic text.", bold=True, italic=True)
>>> report.add_newline()
>>> report.add_equation("E=mc^2", tag="3.14")
>>> report.add_paragraph("Before inline equation:", italic=True).add_equation(r"\frac{a}{b}", inline=True).add_paragraph(
... " and after inline equation.", bold=True
... ).add_newline()
>>> report.add_equation(r"e^{i \pi} + 1 = 0", inline=True).add_paragraph("inline can be at start of text.").add_newline()
>>> report.add_paragraph("Or at the end of text", bold=True).add_equation(r"\int_a^b f(x) dx", inline=True).add_newline(n=2)
>>> report.add_paragraph("Equations can also be $a^2 + b^2 = c^2$ inline in the add text method.").add_newline()
>>> report.add_table(
... headers=["Parameter", "Value", "Unit"], rows=[[r"\text{Length}", "10", r"\text{m}"], [r"\text{Density}", "500", r"\text{kg/$m^3$}"]]
... )
>>> report.add_figure(r"tomato.png", width=0.2) # needs the tomato.png file in working directory
>>> report.add_list(["First item", "Second item"], style="numbered")
>>> report.add_list(["Layer one", ["Layer two", ["Layer three", ["Layer four"]]]], style="numbered")
>>> report.add_list(["Bullet one", "Bullet two"], style="bulleted")
>>> latex_document = report.to_latex()
>>> print(latex_document) # prints the complete LaTeX document string, which can be compiled with pdflatex in for example Overleaf.
utils.report.report.Report.add_equation
add_equation(
equation: str,
tag: str | None = None,
inline: bool = False,
split_after: list[tuple[int, str]] | None = None,
) -> Self
Add an equation to the report. For adding Blueprints formulas, use add_formula instead.
Parameters:
-
equation
(str)
–
An equation in LaTeX format.
-
tag
(str or None, default:
None
)
–
Tag to label the equation (e.g., "6.83", "EN 1992-1-1:2004 6.6n", etc.).
-
inline
(bool, default:
False
)
–
Whether to add the equation inline (meaning within text) or as a separate equation block. Default is False.
-
split_after
(list[tuple[int, str]] | None, default:
None
)
–
List of characters to split the equation line on for better readability.
e.g. a = b + c = 2 + 3 = 5 with split_after=[(2, "="), (2, "+")] will split after second "=" and second "+"
to give: a = b + c = \ 2 + \ 3 = 5. Default is None.
Returns:
-
Report
–
Returns self for method chaining.
Examples:
When creating a report, you can add equations in different ways:
>>> report = Report()
>>> report.add_equation("a^2+b^2=c^2")
>>> report.add_equation("a^2+b^2=c^2", tag="6.83")
>>> report.add_equation(r"\\frac{a}{b}", inline=True)
Source code in blueprints/utils/report/report.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187 | def add_equation(
self,
equation: str,
tag: str | None = None,
inline: bool = False,
split_after: list[tuple[int, str]] | None = None,
) -> Self:
r"""Add an equation to the report. For adding Blueprints formulas, use add_formula instead.
Parameters
----------
equation : str
An equation in LaTeX format.
tag : str or None, optional
Tag to label the equation (e.g., "6.83", "EN 1992-1-1:2004 6.6n", etc.).
inline : bool, optional
Whether to add the equation inline (meaning within text) or as a separate equation block. Default is False.
split_after: list[tuple[int, str]], optional
List of characters to split the equation line on for better readability.
e.g. a = b + c = 2 + 3 = 5 with split_after=[(2, "="), (2, "+")] will split after second "=" and second "+"
to give: a = b + c = \\ 2 + \\ 3 = 5. Default is None.
Returns
-------
Report
Returns self for method chaining.
Examples
--------
When creating a report, you can add equations in different ways:
>>> report = Report()
>>> report.add_equation("a^2+b^2=c^2")
>>> report.add_equation("a^2+b^2=c^2", tag="6.83")
>>> report.add_equation(r"\\frac{a}{b}", inline=True)
"""
def _split_equation(eq: str, split_after: list[tuple[int, str]] | None) -> str:
if not split_after:
return eq
eq_mod = eq
# Sort by decreasing index so insertion doesn't affect later positions
for n, char in sorted(split_after, reverse=True):
# Find nth occurrence of char
idx = -1
count = 0
for i, c in enumerate(eq_mod):
if c == char:
count += 1
if count == n:
idx = i
break
if idx != -1:
eq_mod = eq_mod[: idx + 1] + r" \\" + eq_mod[idx + 1 :]
return eq_mod
# Don't split equations for inline math as \\ line breaks are illegal in inline math
eq_to_use = _split_equation(equation, None if inline else split_after)
multline_vs_equation = "multline" if split_after else "equation"
if inline:
self.content += r"\txt{ " + rf"${eq_to_use}$" + f"{f' ({tag})' if tag else ''}" + r" }"
elif tag:
self.content += rf"\begin{{{multline_vs_equation}}} {eq_to_use} \tag{{{tag}}} \end{{{multline_vs_equation}}}"
else:
self.content += rf"\begin{{{multline_vs_equation}}} {eq_to_use} \notag \end{{{multline_vs_equation}}}"
# Add a newline for visual separation
self.content += "\n"
return self
|
add_figure(
image_path: str, width: float = 0.9, caption: str | None = None
) -> Self
Adds a figure to the report.
Parameters:
-
image_path
(str)
–
-
width
(float, default:
0.9
)
–
Width specification for the image as ratio of the text width. Default is 0.9.
-
caption
(str, default:
None
)
–
Caption text for the figure. Will be displayed below the image. Default is None.
Returns:
-
Report
–
Returns self for method chaining.
Examples:
>>> report = Report()
>>> report.add_figure("path_to_image")
>>> report.add_figure("path_to_image", width=0.5)
>>> report.add_figure("plot.png", caption="Results of the analysis")
Source code in blueprints/utils/report/report.py
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416 | def add_figure(
self,
image_path: str,
width: float = 0.9,
caption: str | None = None,
) -> Self:
r"""Adds a figure to the report.
Parameters
----------
image_path : str
Path to the image file.
width : float, optional
Width specification for the image as ratio of the text width. Default is 0.9.
caption : str, optional
Caption text for the figure. Will be displayed below the image. Default is None.
Returns
-------
Report
Returns self for method chaining.
Examples
--------
>>> report = Report()
>>> report.add_figure("path_to_image")
>>> report.add_figure("path_to_image", width=0.5)
>>> report.add_figure("plot.png", caption="Results of the analysis")
"""
# Convert Windows backslashes to forward slashes for LaTeX compatibility
latex_image_path = image_path.replace("\\", "/")
# Build the figure environment
figure_parts = [r"\begin{figure}[H] \centering ", rf"\includegraphics[width={width}\textwidth]{{{latex_image_path}}} "]
# Add optional caption
if caption:
figure_parts.append(rf"\caption{{{caption}}} ")
figure_parts.append(r"\end{figure}")
figure = "".join(figure_parts)
self.content += figure
# Add a newline for visual separation
self.content += "\n"
return self
|
add_formula(
formula: Formula,
options: Literal["short", "complete", "complete_with_units"] = "complete",
n: int = 2,
include_source: bool = True,
include_formula_number: bool = True,
inline: bool = False,
split_after: list[tuple[int, str]] | None = None,
) -> Self
Add a Blueprints formula to the report, for generic equations, use add_equation.
Parameters:
-
formula
(Formula)
–
Use any Blueprints Formula object.
-
options
(Literal['short', 'complete', 'complete_with_units'], default:
'complete'
)
–
The representation of the formula to add.
short - Minimal representation (symbol = result [unit])
complete - Complete representation (symbol = equation = numeric_equation = result [unit])
complete_with_units - Complete representation with units (symbol = equation = numeric_equation_with_units [unit] = result [unit])
-
n
(int, default:
2
)
–
Number of decimal places for numerical values in the formula (default is 2).
-
include_source
(bool, default:
True
)
–
If True, includes the source document in the equation tag. Default is True.
For example: "EN 1993-1-1:2005" or "EN 1992-1-1:2004".
-
include_formula_number
(bool, default:
True
)
–
If True, includes the formula number in the equation tag. Default is True.
For example: "6.5" or "6.6n".
-
inline
(bool, default:
False
)
–
Whether to add the formula inline (meaning within text) or as a separate equation block (default).
-
split_after
(list[tuple[int, str]] | None, default:
None
)
–
List of characters to split the equation line on for better readability.
e.g. a = b + c = 2 + 3 = 5 with split_after=[(2, "="), (2, "+")] will split after second "=" and second "+"
to give: a = b + c = \ 2 + \ 3 = 5. Default is None.
Returns:
-
Report
–
Returns self for method chaining.
Examples:
>>> from blueprints.codes.eurocode.en_1993_1_1_2005.chapter_6_ultimate_limit_state import formula_6_5
>>> formula = formula_6_5.Form6Dot5UnityCheckTensileStrength(n_ed=150000, n_t_rd=200000)
>>> report = Report()
>>> report.add_formula(formula, options="short") # Minimal representation
>>> print(report)
# report can be converted to formatted LaTeX document with report.to_document()
>>> print(report.to_latex())
Source code in blueprints/utils/report/report.py
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264 | def add_formula(
self,
formula: Formula,
options: Literal["short", "complete", "complete_with_units"] = "complete",
n: int = 2,
include_source: bool = True,
include_formula_number: bool = True,
inline: bool = False,
split_after: list[tuple[int, str]] | None = None,
) -> Self:
r"""Add a Blueprints formula to the report, for generic equations, use add_equation.
Parameters
----------
formula : Formula
Use any Blueprints Formula object.
options : Literal["short", "complete", "complete_with_units"]
The representation of the formula to add.
short - Minimal representation (symbol = result [unit])
complete - Complete representation (symbol = equation = numeric_equation = result [unit])
complete_with_units - Complete representation with units (symbol = equation = numeric_equation_with_units [unit] = result [unit])
n : int, optional
Number of decimal places for numerical values in the formula (default is 2).
include_source: bool, optional
If True, includes the source document in the equation tag. Default is True.
For example: "EN 1993-1-1:2005" or "EN 1992-1-1:2004".
include_formula_number: bool, optional
If True, includes the formula number in the equation tag. Default is True.
For example: "6.5" or "6.6n".
inline : bool, optional
Whether to add the formula inline (meaning within text) or as a separate equation block (default).
split_after: list[tuple[int, str]], optional
List of characters to split the equation line on for better readability.
e.g. a = b + c = 2 + 3 = 5 with split_after=[(2, "="), (2, "+")] will split after second "=" and second "+"
to give: a = b + c = \\ 2 + \\ 3 = 5. Default is None.
Returns
-------
Report
Returns self for method chaining.
Examples
--------
>>> from blueprints.codes.eurocode.en_1993_1_1_2005.chapter_6_ultimate_limit_state import formula_6_5
>>> formula = formula_6_5.Form6Dot5UnityCheckTensileStrength(n_ed=150000, n_t_rd=200000)
>>> report = Report()
>>> report.add_formula(formula, options="short") # Minimal representation
>>> print(report)
# report can be converted to formatted LaTeX document with report.to_document()
>>> print(report.to_latex())
"""
# Get the desired LaTeX representation from the formula
latex = formula.latex(n=n)
# define the equation string based on options
equation_str: str = ""
match options.lower():
case "short":
equation_str = latex.short
case "complete":
equation_str = latex.complete
case "complete_with_units":
equation_str = latex.complete_with_units
case _:
raise ValueError(f"Invalid option '{options}'. Choose from 'short', 'complete', or 'complete_with_units'.")
# Build tag from include_source and include_formula_number flags
tag_parts = []
if include_source or include_formula_number:
if include_source:
tag_parts.append(formula.source_document)
if include_formula_number:
tag_parts.append(formula.label)
tag_str = " ".join(tag_parts).strip()
return self.add_equation(equation=equation_str, inline=inline, tag=tag_str or None, split_after=split_after)
|
utils.report.report.Report.add_heading
add_heading(text: str, level: int = 1) -> Self
Add a heading to the report.
Currently, supports levels 1 (section), 2 (subsection), and 3 (subsubsection).
Parameters:
-
text
(str)
–
-
level
(int, default:
1
)
–
The heading level (1 for section, 2 for subsection, 3 for subsubsection). Default is 1.
Returns:
-
Report
–
Returns self for method chaining.
Examples:
>>> report = Report()
>>> report.add_heading("This is a section")
Source code in blueprints/utils/report/report.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301 | def add_heading(self, text: str, level: int = 1) -> Self:
"""Add a heading to the report.
Currently, supports levels 1 (section), 2 (subsection), and 3 (subsubsection).
Parameters
----------
text : str
The heading text.
level : int
The heading level (1 for section, 2 for subsection, 3 for subsubsection). Default is 1.
Returns
-------
Report
Returns self for method chaining.
Examples
--------
>>> report = Report()
>>> report.add_heading("This is a section")
"""
match level:
case 1:
self.content += rf"\section{{{text}}}"
case 2:
self.content += rf"\subsection{{{text}}}"
case 3:
self.content += rf"\subsubsection{{{text}}}"
case _:
raise ValueError(f"Invalid heading level '{level}'. Choose from 1 (section), 2 (subsection), or 3 (subsubsection).")
# Add a newline for visual separation
self.content += "\n"
return self
|
utils.report.report.Report.add_list
add_list(
items: Sequence[Any], style: Literal["bulleted", "numbered"] = "bulleted"
) -> Self
Add a list to the report, either bulleted or numbered.
Parameters:
-
items
(Sequence[Any])
–
List of items to display.
-
style
(Literal['bulleted', 'numbered'], default:
'bulleted'
)
–
Style of the list, either 'bulleted' for itemize or 'numbered' for enumerate. Default is 'bulleted'.
Returns:
-
Report
–
Returns self for method chaining.
Examples:
>>> report = Report()
>>> report.add_list(["Item 1", "Item 2", "Item 3"], style="bulleted")
>>> report.add_list(["First", "Second", "Third"], style="numbered")
Source code in blueprints/utils/report/report.py
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471 | def add_list(self, items: Sequence[Any], style: Literal["bulleted", "numbered"] = "bulleted") -> Self:
"""Add a list to the report, either bulleted or numbered.
Parameters
----------
items : Sequence[Any]
List of items to display.
style : Literal["bulleted", "numbered"], optional
Style of the list, either 'bulleted' for itemize or 'numbered' for enumerate. Default is 'bulleted'.
Returns
-------
Report
Returns self for method chaining.
Examples
--------
>>> report = Report()
>>> report.add_list(["Item 1", "Item 2", "Item 3"], style="bulleted")
>>> report.add_list(["First", "Second", "Third"], style="numbered")
"""
if style.lower() not in ["bulleted", "numbered"]:
raise ValueError(f"Invalid style '{style}'. Choose 'bulleted' or 'numbered'.")
def _build_list(item_list: list, depth: int = 0) -> str:
r"""Recursively build LaTeX environment for nested lists.
Parameters
----------
item_list : list
List of items to convert to LaTeX. Items can be strings or nested lists.
depth : int, optional
Current nesting depth (used for recursion tracking). Default is 0.
Returns
-------
str
LaTeX string with \\begin{itemize}/\\begin{enumerate} environment.
"""
result = r"\begin{itemize} " if style.lower() == "bulleted" else r"\begin{enumerate} "
for item in item_list:
if isinstance(item, list):
result += _build_list(item, depth + 1)
else:
result += rf"\item {item} "
result += r"\end{itemize} " if style.lower() == "bulleted" else r"\end{enumerate} "
return result
self.content += _build_list(list(items))
# Add a newline for visual separation
self.content += "\n"
return self
|
utils.report.report.Report.add_newline
add_newline(n: int = 1) -> Self
Add one or more newlines to separate content.
Useful for adding vertical spacing between paragraphs, equations, or other elements.
Parameters:
-
n
(int, default:
1
)
–
Number of newlines to add. Default is 1.
Returns:
-
Report
–
Returns self for method chaining.
Examples:
>>> report = Report()
>>> report.add_newline()
Source code in blueprints/utils/report/report.py
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498 | def add_newline(self, n: int = 1) -> Self:
"""Add one or more newlines to separate content.
Useful for adding vertical spacing between paragraphs, equations, or other elements.
Parameters
----------
n : int, optional
Number of newlines to add. Default is 1.
Returns
-------
Report
Returns self for method chaining.
Examples
--------
>>> report = Report()
>>> report.add_newline()
"""
self.content += r"\newline" * n
# Add a newline for visual separation
self.content += "\n"
return self
|
utils.report.report.Report.add_paragraph
add_paragraph(text: str, bold: bool = False, italic: bool = False) -> Self
Add text with optional bold and italic formatting.
Parameters:
-
text
(str)
–
The text content of the paragraph.
-
bold
(bool, default:
False
)
–
Whether to format the text in bold.
-
italic
(bool, default:
False
)
–
Whether to format the text in italics.
Returns:
-
Report
–
Returns self for method chaining.
Examples:
>>> report = Report()
>>> report.add_paragraph("This is regular text")
>>> report.add_paragraph("This is bold text", bold=True)
>>> report.add_paragraph("This is bold and italic", bold=True, italic=True)
Source code in blueprints/utils/report/report.py
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115 | def add_paragraph(self, text: str, bold: bool = False, italic: bool = False) -> Self:
r"""Add text with optional bold and italic formatting.
Parameters
----------
text : str
The text content of the paragraph.
bold : bool, optional
Whether to format the text in bold.
italic : bool, optional
Whether to format the text in italics.
Returns
-------
Report
Returns self for method chaining.
Examples
--------
>>> report = Report()
>>> report.add_paragraph("This is regular text")
>>> report.add_paragraph("This is bold text", bold=True)
>>> report.add_paragraph("This is bold and italic", bold=True, italic=True)
"""
if bold and italic:
self.content += rf"\textbf{{\textit{{{text}}}}}"
elif bold:
self.content += rf"\textbf{{{text}}}"
elif italic:
self.content += rf"\textit{{{text}}}"
else:
self.content += rf"\txt{{{text}}}"
# Add a newline for visual separation
self.content += "\n"
return self
|
utils.report.report.Report.add_table
add_table(
headers: list[str], rows: list[list[str]], centering: bool = True
) -> Self
Add a table to the report.
Parameters:
-
headers
(list[str])
–
-
rows
(list[list[str]])
–
List of rows, where each row is a list of cell values.
-
centering
(bool, default:
True
)
–
If True, centers the table. Default is True.
Returns:
-
Report
–
Returns self for method chaining.
Examples:
>>> report = Report()
>>> headers = ["Check", "Utilization", "Status"]
>>> rows = [[r"\\text{Concrete strut capacity}", "0.588", r"\\text{PASS}"], [r"\\text{Torsion moment capacity}", "4.825", r"\\text{FAIL}"]]
>>> report.add_table(headers, rows)
Source code in blueprints/utils/report/report.py
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367 | def add_table(
self,
headers: list[str],
rows: list[list[str]],
centering: bool = True,
) -> Self:
r"""Add a table to the report.
Parameters
----------
headers : list[str]
List of column headers.
rows : list[list[str]]
List of rows, where each row is a list of cell values.
centering : bool, optional
If True, centers the table. Default is True.
Returns
-------
Report
Returns self for method chaining.
Examples
--------
>>> report = Report()
>>> headers = ["Check", "Utilization", "Status"]
>>> rows = [[r"\\text{Concrete strut capacity}", "0.588", r"\\text{PASS}"], [r"\\text{Torsion moment capacity}", "4.825", r"\\text{FAIL}"]]
>>> report.add_table(headers, rows)
"""
# Validate headers and rows match
num_cols = len(headers)
if not headers:
raise ValueError("At least one header is required.")
if not rows:
raise ValueError("At least one row is required.")
for i, row in enumerate(rows):
if len(row) != num_cols:
raise ValueError(
f"Row {i} has {len(row)} columns but {num_cols} headers were provided. All rows must have the same number of columns as headers."
)
col_spec = "l" * num_cols
# Build header row
header_row = " & ".join(headers) + r" \\"
# Build data rows
data_rows = " ".join([" & ".join(row) + r" \\" for row in rows])
# Build table
centering_cmd = r"\centering " if centering else ""
table = (
rf"\begin{{table}}[H] {centering_cmd}"
rf"\begin{{tabular}}{{{col_spec}}} "
rf"\toprule {header_row} \midrule {data_rows} "
rf"\bottomrule \end{{tabular}} \end{{table}}"
)
self.content += table
# Add a newline for visual separation
self.content += "\n"
return self
|
utils.report.report.Report.to_latex
to_latex(path: str | Path | None = None, language: str = 'en') -> str | None
Generate a complete LaTeX document with proper preamble and structure.
You could compile the output with pdflatex in for example Overleaf.
Parameters:
-
path
(str | Path | None, default:
None
)
–
The destination for the LaTeX document:
- str or Path: File path where the .tex file will be saved
- None: Return the document as a string (default)
-
language
(str, default:
'en'
)
–
Language code for localization, full list on https://docs.cloud.google.com/translate/docs/languages
Warning: only English is officially supported in Blueprints (default is "en" for English).
Note: this feature is slow in a .ipynb notebook environment.
Returns:
-
str | None
–
If path is None, returns the LaTeX document as a string.
If path is provided (str or Path), returns None after saving to file.
Examples:
Get LaTeX as a string:
>>> report = Report(title="My Report")
>>> report.add_heading("Introduction")
>>> report.add_paragraph("Some text")
>>> latex_doc = report.to_latex()
Save directly to a file:
>>> report.to_latex("report.tex")
Save using pathlib.Path:
>>> from pathlib import Path
>>> report.to_latex(Path("report.tex"))
Source code in blueprints/utils/report/report.py
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707 | def to_latex(self, path: str | Path | None = None, language: str = "en") -> str | None:
"""Generate a complete LaTeX document with proper preamble and structure.
You could compile the output with pdflatex in for example Overleaf.
Parameters
----------
path : str | Path | None, optional
The destination for the LaTeX document:
- str or Path: File path where the .tex file will be saved
- None: Return the document as a string (default)
language : str, optional
Language code for localization, full list on https://docs.cloud.google.com/translate/docs/languages
Warning: only English is officially supported in Blueprints (default is "en" for English).
Note: this feature is slow in a .ipynb notebook environment.
Returns
-------
str | None
If path is None, returns the LaTeX document as a string.
If path is provided (str or Path), returns None after saving to file.
Examples
--------
Get LaTeX as a string:
>>> report = Report(title="My Report")
>>> report.add_heading("Introduction")
>>> report.add_paragraph("Some text")
>>> latex_doc = report.to_latex()
Save directly to a file:
>>> report.to_latex("report.tex")
Save using pathlib.Path:
>>> from pathlib import Path
>>> report.to_latex(Path("report.tex"))
"""
# Build the preamble with styling to match Word document converter (pdflatex compatible)
preamble = (
r"\documentclass[11pt]{article}" + "\n"
# Required packages
r"\usepackage{amsmath}" + "\n" # Advanced math environments and symbols
r"\usepackage{booktabs}" + "\n" # Professional-looking tables with \toprule, \midrule, \bottomrule
r"\usepackage{float}" + "\n" # Improved float handling
r"\usepackage{geometry}" + "\n" # Page layout and margins
r"\usepackage{graphicx}" + "\n" # Include images and graphics
r"\usepackage{icomma}" + "\n" # Proper comma handling in numbers
r"\usepackage{setspace}" + "\n" # Line spacing control
r"\usepackage{xcolor}" + "\n" # Color definitions and usage
r"\usepackage{titlesec}" + "\n" # Customize section titles
r"\usepackage{helvet}" + "\n" # Helvetica font family (sans-serif)
r"\usepackage[T1]{fontenc}" + "\n" # Better font encoding for special characters
r"\usepackage{enumitem}" + "\n" # Enhanced list customization
# Page setup
r"\geometry{a4paper, margin=1in}" + "\n" # A4 paper with 1-inch margins
r"\setstretch{1.3}" + "\n" # Line spacing factor
"\n"
# Custom commands
r"\newcommand{\txt}[1]{#1}" + "\n" # Simple text wrapper command
# Spacing configuration
r"\setlength{\parskip}{0pt}" + "\n" # No extra space between paragraphs
r"\setlength{\abovedisplayskip}{12pt}" + "\n" # Space above equations
r"\setlength{\belowdisplayskip}{12pt}" + "\n" # Space below equations
r"\setlist{nosep}" + "\n" # Remove extra spacing in lists
"\n"
# Color definitions
r"\definecolor{blueprintblue}{RGB}{0,40,85}" + "\n" # Custom blue color (0, 40, 85)
"\n"
# Title formatting
r"\makeatletter" + "\n" # Access internal LaTeX commands
r"\renewcommand{\maketitle}{%" + "\n" # Redefine \maketitle command
r" \begin{center}%" + "\n" # Center the title
r" {\sffamily\fontsize{18}{19}\selectfont\bfseries\color{blueprintblue}\@title}%" + "\n" # 18pt, bold, blue, sans-serif title
r" \vspace{4pt}%" + "\n" # 4pt vertical space after title
r" \end{center}%" + "\n"
r"}" + "\n"
r"\makeatother" + "\n" # Restore @ character behavior
"\n"
# Section formatting
r"\titleformat{\section}" + "\n" # Section heading format
r" {\sffamily\fontsize{14}{15}\selectfont\bfseries\color{blueprintblue}}" + "\n" # 14pt, bold, blue, sans-serif
r" {\thesection}{1em}{}" + "\n" # Section number with 1em space
r"\titlespacing*{\section}{0pt}{8pt}{4pt}" + "\n" # Spacing: left, before, after
"\n"
# Subsection formatting
r"\titleformat{\subsection}" + "\n" # Subsection heading format
r" {\sffamily\fontsize{12}{13}\selectfont\bfseries\color{blueprintblue}}" + "\n" # 12pt, bold, blue, sans-serif
r" {\thesubsection}{1em}{}" + "\n" # Subsection number with 1em space
r"\titlespacing*{\subsection}{0pt}{8pt}{4pt}" + "\n" # Spacing: left, before, after
"\n"
# Subsubsection formatting
r"\titleformat{\subsubsection}" + "\n" # Subsubsection heading format
r" {\sffamily\fontsize{12}{13}\selectfont\bfseries\color{blueprintblue}}" + "\n" # 12pt, bold, blue, sans-serif
r" {\thesubsubsection}{1em}{}" + "\n" # Subsubsection number with 1em space
r"\titlespacing*{\subsubsection}{0pt}{4pt}{0pt}" + "\n" # Spacing: left, before, after
"\n"
# Paragraph formatting
r"\parindent 0in" + "\n" # No paragraph indentation
# Begin document
r"\begin{document}" + "\n"
rf"\title{{{self.title}}}" + "\n" # Set document title
r"\date{}" + "\n" # No date displayed
r"\maketitle" + "\n" # Generate the title
)
latex = preamble + self.content + r"\end{document}"
if language != "en":
# Translate content to the specified language
latex = LatexTranslator(original_text=latex, destination_language=language).text
# If path is provided, save to file and return None
if path is not None:
# Convert Path to str if needed
file_path = str(path) if isinstance(path, Path) else path
Path(file_path).write_text(latex, encoding="utf-8")
return None
# Return LaTeX string
return latex
|
utils.report.report.Report.to_pdf
to_pdf(
path: str | Path | None = None, language: str = "en", cleanup: bool = True
) -> bytes | None
Generate a PDF document by compiling LaTeX with pdflatex.
This method generates LaTeX content using to_latex(), compiles it with pdflatex,
and returns or saves the resulting PDF. Requires pdflatex to be installed and
available in the system PATH. Can be downloaded from a LaTeX distribution such as TeX Live or MiKTeX.
Parameters:
-
path
(str | Path | None, default:
None
)
–
The destination for the PDF document:
- str or Path: File path where the .pdf file will be saved
- None: Return the PDF as bytes (default)
-
language
(str, default:
'en'
)
–
Language code for localization, full list on https://docs.cloud.google.com/translate/docs/languages
Warning: only English is officially supported in Blueprints (default is "en" for English).
Note: this feature is slow in a .ipynb notebook environment.
-
cleanup
(bool, default:
True
)
–
Whether to remove temporary LaTeX files after compilation. Default is True.
Returns:
-
bytes | None
–
If path is None, returns the PDF document as bytes.
If path is provided (str or Path), returns None after saving to file.
Raises:
-
RuntimeError
–
If pdflatex is not found or compilation fails.
Examples:
Get PDF as bytes:
>>> report = Report(title="My Report")
>>> report.add_heading("Introduction")
>>> report.add_paragraph("Some text")
>>> pdf_bytes = report.to_pdf()
Save directly to a file:
>>> report.to_pdf("report.pdf")
Save using pathlib.Path:
>>> from pathlib import Path
>>> report.to_pdf(Path("report.pdf"))
Keep temporary LaTeX files for debugging:
>>> report.to_pdf("report.pdf", cleanup=False)
Source code in blueprints/utils/report/report.py
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913 | def to_pdf(self, path: str | Path | None = None, language: str = "en", cleanup: bool = True) -> bytes | None: # noqa: C901
"""Generate a PDF document by compiling LaTeX with pdflatex.
This method generates LaTeX content using to_latex(), compiles it with pdflatex,
and returns or saves the resulting PDF. Requires pdflatex to be installed and
available in the system PATH. Can be downloaded from a LaTeX distribution such as TeX Live or MiKTeX.
Parameters
----------
path : str | Path | None, optional
The destination for the PDF document:
- str or Path: File path where the .pdf file will be saved
- None: Return the PDF as bytes (default)
language : str, optional
Language code for localization, full list on https://docs.cloud.google.com/translate/docs/languages
Warning: only English is officially supported in Blueprints (default is "en" for English).
Note: this feature is slow in a .ipynb notebook environment.
cleanup : bool, optional
Whether to remove temporary LaTeX files after compilation. Default is True.
Returns
-------
bytes | None
If path is None, returns the PDF document as bytes.
If path is provided (str or Path), returns None after saving to file.
Raises
------
RuntimeError
If pdflatex is not found or compilation fails.
Examples
--------
Get PDF as bytes:
>>> report = Report(title="My Report")
>>> report.add_heading("Introduction")
>>> report.add_paragraph("Some text")
>>> pdf_bytes = report.to_pdf()
Save directly to a file:
>>> report.to_pdf("report.pdf")
Save using pathlib.Path:
>>> from pathlib import Path
>>> report.to_pdf(Path("report.pdf"))
Keep temporary LaTeX files for debugging:
>>> report.to_pdf("report.pdf", cleanup=False)
"""
# Check if pdflatex is available
try:
subprocess.run(
["pdflatex", "--version"],
capture_output=True,
check=True,
timeout=10,
)
except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired) as e:
raise RuntimeError(
"pdflatex is not installed or not found in PATH. Please install a LaTeX distribution (e.g., MiKTeX, TeX Live) that includes pdflatex."
" Go to https://miktex.org/download or https://www.tug.org/texlive/ for more information."
) from e
# Generate LaTeX content
latex_content = self.to_latex(language=language)
assert latex_content is not None # to_latex returns str when path is None
# Create temporary directory for compilation
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir_path = Path(tmpdir)
tex_file = tmpdir_path / "report.tex"
pdf_file = tmpdir_path / "report.pdf"
# Write LaTeX content to temporary file
tex_file.write_text(latex_content, encoding="utf-8")
# Run pdflatex twice for proper references and table of contents
for _ in range(2):
result = subprocess.run(
["pdflatex", "-interaction=nonstopmode", "-halt-on-error", str(tex_file)],
check=False,
cwd=tmpdir,
capture_output=True,
text=True,
timeout=60,
)
if result.returncode != 0:
# Extract error information from log
error_msg = "pdflatex compilation failed.\n"
if result.stdout: # pragma: no cover
# Find the first error in the output
lines = result.stdout.split("\n")
for i, line in enumerate(lines):
if line.startswith("!"):
error_msg += "\n".join(lines[i : i + 5])
break
raise RuntimeError(error_msg)
# Check if PDF was created
if not pdf_file.exists():
raise RuntimeError("PDF file was not created by pdflatex.") # pragma: no cover
# Read the PDF content
pdf_content = pdf_file.read_bytes()
# If path is provided, save to file
if path is not None:
output_path = Path(path) if isinstance(path, str) else path
output_path.write_bytes(pdf_content)
# Optionally copy auxiliary files for debugging
if not cleanup: # pragma: no cover
aux_files = [".aux", ".log", ".out"]
base_name = output_path.stem
output_dir = output_path.parent
for ext in aux_files:
aux_file = tmpdir_path / f"report{ext}"
if aux_file.exists():
(output_dir / f"{base_name}{ext}").write_bytes(aux_file.read_bytes())
return None
# Return PDF as bytes
return pdf_content
|
utils.report.report.Report.to_word
to_word(
path: str | Path | BytesIO | None = None, language: str = "en"
) -> bytes | None
Convert the LaTeX report to a Word document.
This method uses the ReportToWordConverter to convert the LaTeX content
of the report into a Word document format. The output can be saved to a file,
written to a BytesIO object, or returned as bytes.
Parameters:
-
path
(str | Path | BytesIO | None, default:
None
)
–
The destination for the Word document:
- str or Path: File path where the document will be saved, for example 'report.docx'. Remember to use .docx extension.
- BytesIO: Buffer to write the document to (in-memory)
from io import BytesIO
buffer = BytesIO()
report.to_word(buffer)
docx_bytes = buffer.getvalue()
- None: Return the document as bytes (default)
-
language
(str, default:
'en'
)
–
Language code for localization, full list on https://docs.cloud.google.com/translate/docs/languages
Warning: only English is officially supported in Blueprints (default is "en" for English).
Note: this feature is slow in a .ipynb notebook environment.
Returns:
-
bytes | None
–
If path is None, returns the Word document as bytes.
If path is provided (str, Path, or BytesIO), returns None.
Examples:
Save to a file path:
>>> report = Report(title="My Report")
>>> report.add_heading("Introduction")
>>> report.add_paragraph("Some text")
>>> report.to_word("report.docx") # Save to file
Save to a pathlib.Path:
>>> from pathlib import Path
>>> report.to_word(Path("report.docx"))
Write to a BytesIO object for in-memory processing:
>>> from io import BytesIO
>>> buffer = BytesIO()
>>> report.to_word(buffer)
>>> docx_bytes = buffer.getvalue()
Get bytes directly (useful for streaming, email attachments, etc.):
>>> docx_bytes = report.to_word()
>>> # Can now send as email attachment or stream over HTTP
Source code in blueprints/utils/report/report.py
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783 | def to_word(self, path: str | Path | BytesIO | None = None, language: str = "en") -> bytes | None: # pragma: no cover
"""Convert the LaTeX report to a Word document.
This method uses the ReportToWordConverter to convert the LaTeX content
of the report into a Word document format. The output can be saved to a file,
written to a BytesIO object, or returned as bytes.
Parameters
----------
path : str | Path | BytesIO | None, optional
The destination for the Word document:
- str or Path: File path where the document will be saved, for example 'report.docx'. Remember to use .docx extension.
- BytesIO: Buffer to write the document to (in-memory)
```python
from io import BytesIO
buffer = BytesIO()
report.to_word(buffer)
docx_bytes = buffer.getvalue()
```
- None: Return the document as bytes (default)
language : str, optional
Language code for localization, full list on https://docs.cloud.google.com/translate/docs/languages
Warning: only English is officially supported in Blueprints (default is "en" for English).
Note: this feature is slow in a .ipynb notebook environment.
Returns
-------
bytes | None
If path is None, returns the Word document as bytes.
If path is provided (str, Path, or BytesIO), returns None.
Examples
--------
Save to a file path:
>>> report = Report(title="My Report")
>>> report.add_heading("Introduction")
>>> report.add_paragraph("Some text")
>>> report.to_word("report.docx") # Save to file
Save to a pathlib.Path:
>>> from pathlib import Path
>>> report.to_word(Path("report.docx"))
Write to a BytesIO object for in-memory processing:
>>> from io import BytesIO
>>> buffer = BytesIO()
>>> report.to_word(buffer)
>>> docx_bytes = buffer.getvalue()
Get bytes directly (useful for streaming, email attachments, etc.):
>>> docx_bytes = report.to_word()
>>> # Can now send as email attachment or stream over HTTP
"""
latex_content = self.to_latex(language=language)
converter = _ReportToWordConverter(latex_content)
if converter.document:
if path is None:
# Return bytes directly
buffer = BytesIO()
converter.document.save(buffer)
buffer.seek(0)
return buffer.getvalue()
# Save to file path or BytesIO object
# Convert Path to str for compatibility with python-docx
if isinstance(path, Path):
converter.document.save(str(path))
else:
converter.document.save(path)
return None
return None
|