Skip to content

report

utils.report.report

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(title: str = '')

Report builder for creating structured documents with standardized formatting.

Check our docs for examples of usage.

Parameters:

  • title (str, default: '' ) –

    The title of the report.

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

utils.report.report.Report.add_figure

add_figure(
    image_path: str, width: float = 0.9, caption: str | None = None
) -> Self

Adds a figure to the report.

Parameters:

  • image_path (str) –

    Path to the image file.

  • 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

utils.report.report.Report.add_formula

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) –

    The heading text.

  • 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]) –

    List of column headers.

  • 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