Skip to content

Commit

Permalink
Min row height in tables + docs on fpdf2 internals (#1346)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucas-C authored Jan 17, 2025
1 parent a3cc423 commit bb3aaa1
Show file tree
Hide file tree
Showing 21 changed files with 366 additions and 185 deletions.
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ This can also be enabled programmatically with `warnings.simplefilter('default',
## [2.8.3] - Not released yet
### Added
* support for [shading patterns (gradients)](https://py-pdf.github.io/fpdf2/Patterns.html)
* support for strikethrough text
* Improved SVG image speed by 50% to 70% - thanks to @petri-lipponen-movesense - [PR #1350](https://github.com/py-pdf/fpdf2/pull/1350)
* improved SVG image speed by 50% to 70% - thanks to @petri-lipponen-movesense - [PR #1350](https://github.com/py-pdf/fpdf2/pull/1350)
* support for <s>strikethrough text</s>
* support for [setting a minimal row height in tables](https://py-pdf.github.io/fpdf2/Tables.html#setting-row-height)
* support for [`v_align` at the row level in tables](https://py-pdf.github.io/fpdf2/Tables.html#setting-vertical-alignment-of-text-in-cells)
* documentation on [verifying provenance of `fpdf2` releases](https://py-pdf.github.io/fpdf2/#verifying-provenance)
* documentation on [`fpdf2` internals](https://py-pdf.github.io/fpdf2/Internals.html)
### Fixed
* [`FPDF.write_html()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html): Fixed rendering of content following `<a>` tags; now correctly resets emphasis style post `</a>` tag: hyperlink styling contained within the tag authority. - [Issue #1311](https://github.com/py-pdf/fpdf2/issues/1311)

Expand Down
97 changes: 97 additions & 0 deletions docs/Internals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# fpdf2 internals

## FPDF.pages
`FPDF` is designed to add content progressively to the document generated, page by page.

Each page is an entry in the `.pages` attribute of `FPDF` instances.
Indices start at 1 (the first page) and values are [`PDFPage`](https://py-pdf.github.io/fpdf2/fpdf/output.html#fpdf.output.PDFPage) instances.

`PDFPage` instances have a `.contents` attribute that is a [`bytearray`](https://docs.python.org/3/library/stdtypes.html#bytearray) and contains the **Content Stream** for this page
(`bytearray` makes things [a lot faster](https://github.com/reingart/pyfpdf/pull/164)).

Going back to a previously generated page to add content is possible,
using the [`.page` attribute](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.page), but may result in unexpected behavior, because [.add_page()](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.add_page) takes special care to ensure the page's content stream matches `FPDF`'s instance attributes.


## syntax.py & objects serialization
The [syntax.py](https://github.com/py-pdf/fpdf2/blob/master/fpdf/syntax.py) package contains classes representing core elements of the PDF syntax.

Classes inherit from the [PDFObject](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.syntax.PDFObject) class, that has the following properties:

* every PDF object has an `.id`, that is assigned during the document serialization by the [OutputProducer](#outputproducer)
* the `.serialize()` method renders the PDF object as an `obj<</>>endobj` text block. It can be overridden by child classes.
* the `.content_stream()` method must return non empty bytes if the PDF Object has a _content stream_

Other notable core classes are:

* [Name](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.syntax.Name)
* [Raw](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.syntax.Raw)
* [PDFString](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.syntax.PDFString)
* [PDFArray](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.syntax.PDFArray)
* [PDFDate](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.syntax.PDFDate)


## GraphicsStateMixin
This _mixin_ class, inherited by the `FPDF` class,
allows to manage a stack of graphics state variables:

* docstring: [fpdf.graphics_state.GraphicsStateMixin](https://py-pdf.github.io/fpdf2/fpdf/graphics_state.html#fpdf.graphics_state.GraphicsStateMixin)
* source file: [graphics_state.py](https://github.com/py-pdf/fpdf2/blob/master/fpdf/graphics_state.py)

The main methods of this API are:

* [_push_local_stack()](https://py-pdf.github.io/fpdf2/fpdf/graphics_state.html#fpdf.graphics_state.GraphicsStateMixin._push_local_stack): Push a graphics state on the stack
* [_pop_local_stack()](https://py-pdf.github.io/fpdf2/fpdf/graphics_state.html#fpdf.graphics_state.GraphicsStateMixin._pop_local_stack): Pop the last graphics state on the stack
* [_get_current_graphics_state()](https://py-pdf.github.io/fpdf2/fpdf/graphics_state.html#fpdf.graphics_state.GraphicsStateMixin._get_current_graphics_state): Retrieve the current graphics state
* [_is_current_graphics_state_nested()](https://py-pdf.github.io/fpdf2/fpdf/graphics_state.html#fpdf.graphics_state.GraphicsStateMixin._is_current_graphics_state_nested): Indicate if the stack contains items (else it is empty)

Thanks to this _mixin_, we can use the following semantics:
```python
{% include "../tutorial/graphics_state.py" %}
```

The graphics states used in the code above
can be depicted by this diagram:

``` mermaid
stateDiagram-v2
direction LR
state gs0 {
initial1 : Base state
}
state gs1 {
initial2 : Base state
font_size_pt2 : font_size_pt=16
underline2 : underline=True
font_size_pt2 --> initial2
underline2 --> font_size_pt2
}
gs0 --> gs1: Step 1
state "gs0" as stack2 {
initial3 : Base state
}
gs1 --> stack2: Step 2
```


## OutputProducer
In `fpdf2`, the `FPDF` class is used to store the document **definition**,
its state as it is progressively built. Most attributes and internal data is **mutable**.

Once it's done, when the [FPDF.output()](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.output) method is called, the actual PDF file creation is delegated to the [OutputProducer](https://py-pdf.github.io/fpdf2/fpdf/output.html#fpdf.output.OutputProducer) class.

It performs the serialization of the PDF document,
including the generation of the [cross-reference table & file trailer](https://py-pdf.github.io/fpdf2/fpdf/output.html#fpdf.output.PDFXrefAndTrailer).
This class uses the `FPDF` instance as **immutable input**:
it does not perform any modification on it.

<!-- Other topics to mention:
## Vector Graphics
drawing.py & svg.py packages
## Text regions & flow ?
## Text shaping ?
-->
3 changes: 3 additions & 0 deletions docs/Signing.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ allows to add a signature based on arbitrary key & certificates, not necessarily
[examples/pdf-verify.py](https://github.com/m32/endesive/blob/master/examples/pdf-verify.py)
or the [`check_signature()`](https://github.com/py-pdf/fpdf2/blob/master/test/conftest.py#L111) function
used in `fpdf2` unit tests can be good starting points for you, if you want to perform PDF signature control.

If you want to sign **existing** PDF documents,
you should consider using PyHanko: <https://pyhanko.readthedocs.io>.
25 changes: 19 additions & 6 deletions docs/Tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,24 +120,37 @@ left and right is supplied then c_margin is ignored.

_New in [:octicons-tag-24: 2.7.6](https://github.com/PyFPDF/fpdf2/blob/master/CHANGELOG.md)_

Can be set globally or per cell.
Works the same way as padding, but with the `v_align` parameter.

Can be set globally, per row or per cell, by passing a string or a [VAlign](https://py-pdf.github.io/fpdf2/fpdf/enums.html#fpdf.enums.VAlign) enum value as `v_align`:
```python

...
with pdf.table(v_align=VAlign.M) as table:
...
row.cell(f"custom v-align", v_align=VAlign.T) # <-- align to top
row.cell(f"custom v-align", v_align="TOP")
```

## Setting row height

First, `line_height` can be provided to set the height of every individual line of text:
```python
...
with pdf.table(line_height=2.5 * pdf.font_size) as table:
...
```

_New in [:octicons-tag-24: 2.8.3](https://github.com/py-pdf/fpdf2/blob/master/CHANGELOG.md)_

Second, a global `min_row_height` can be set,
or configured per row as `min_height`:
```python
...
with pdf.table(min_row_height=30) as table:
row = table.row()
row.cell("A")
row.cell("B")
row = table.row(min_height=50)
row.cell("C")
row.cell("D")
```

## Disable table headings

By default, `fpdf2` considers that the first row of tables contains its headings.
Expand Down
Loading

0 comments on commit bb3aaa1

Please sign in to comment.