Skip to content

Commit

Permalink
🎯 Integrate optimum fit into layout engine
Browse files Browse the repository at this point in the history
  • Loading branch information
ariebovenberg committed Aug 15, 2023
1 parent 8b640a7 commit 95ddbe3
Show file tree
Hide file tree
Showing 45 changed files with 3,422 additions and 1,493 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
Changelog
=========

0.6.0 (2023-??-??)
0.6.0 (2023-08-15)
------------------

**Added**

- 🧮 Paragraphs can be optimally typeset using the Knuth-Plass line
breaking algorithm. Use the ``optimal`` argument for this.
- 🛟 Paragraphs support automatically avoiding orphaned lines with
``avoid_orphans`` argument.

**Breaking**

- 📊 In the rare case that a paragraphs contains different text sizes,
Expand Down
50 changes: 31 additions & 19 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
🌷 pdf'je
=========
🌷 pdfje
========

.. image:: https://img.shields.io/pypi/v/pdfje.svg?style=flat-square&color=blue
:target: https://pypi.python.org/pypi/pdfje
Expand Down Expand Up @@ -33,16 +33,16 @@
Features
--------

Pdf'je distinguishes itself with the following combination of features:
What makes **pdfje** stand out from the other PDF writers? Here are some of the highlights:

🧩 Declarative API
~~~~~~~~~~~~~~~~~~

In most PDF writers, you first create empty objects and
In most PDF writers, you create empty objects and
then mutate them with methods like ``addText()``,
all while changing the state with methods like ``setFont()``.
**Pdf'je** is different. You describe the document you want to write,
and it takes care of the details. No state to manage, no mutations.
**Pdfje** is different. You describe the document you want to write,
and pdfje takes care of the details. No state to manage, no mutations.
This makes your code easier to reuse and reason about.

.. code-block:: python
Expand All @@ -60,15 +60,16 @@ for a complete overview of features, including:

See the roadmap_ for supported features.

📖 Typography focussed
~~~~~~~~~~~~~~~~~~~~~~
📖 Decent typography
~~~~~~~~~~~~~~~~~~~~

Legibility counts — and `kerning <https://en.wikipedia.org/wiki/Kerning>`_
is a key part of this.
We've come to expect it everywhere, from web browsers to word processors.
However, most PDF writers don't support it.
By using the proper metadata,
**pdf'je** helps you write documents that look great.
Legibility counts. Good typography is a key part of that.
**Pdfje** supports several features to make your documents look great:

- Visually pleasing linebreaks, using the `same basic principles as LaTeX <https://en.wikipedia.org/wiki/Line_wrap_and_word_wrap#Knuth's_algorithm>`_
- Automatic `kerning <https://en.wikipedia.org/wiki/Kerning>`_ using available font metrics
- Avoiding `widows and orphans <https://en.wikipedia.org/wiki/Widows_and_orphans>`_ by moving
lines between columns or pages.

.. image:: https://github.com/ariebovenberg/pdfje/raw/main/sample.png
:alt: Sample document with two columns of text
Expand All @@ -78,7 +79,7 @@ By using the proper metadata,

The PDF format supports many features, but most of the time you only need a few.
Why install many dependencies — just to write a simple document?
Not only is **pdf'je** pure-Python, it allows you to
Not only is **pdfje** pure-Python, it allows you to
install only the dependencies you need.

.. code-block:: bash
Expand All @@ -91,7 +92,7 @@ install only the dependencies you need.
Roadmap
-------

**Pdf'je** is still in active development,
**Pdfje** is still in active development,
so it is not yet feature-complete.
Until the 1.0 version, the API may change with minor releases.

Expand All @@ -106,9 +107,11 @@ Features:
- ✅ Centering text
- ✅ Justification
- ✅ Hyphenation
- 🚧 Move lines between columns/pages to avoid widows/orphans
- 🚧 Tex-style line breaking
- Move lines between columns/pages to avoid widows/orphans
- Tex-style line breaking
- 🚧 Headings (which stick to their paragraphs)
- 🚧 Indentation
- 🚧 Keeping layout elements together
- 🚧 Loosening paragraphs to avoid orphans/widows
- 🚧 Broader unicode support in text wrapping
- Drawing operations
Expand All @@ -134,6 +137,15 @@ Features:
- ❌ Forms
- ❌ Annotations

Versioning and compatibility policy
-----------------------------------

**Pdfje** follows semantic versioning.
Until the 1.0 version, the API may change with minor releases.
Breaking changes will be announced in the changelog.
Since the API is fully typed, your typechecker and/or IDE
will help you adjust to any API changes.

License
-------

Expand All @@ -153,7 +165,7 @@ Here are some useful tips for developing in the ``pdfje`` codebase itself:
Acknowledgements
----------------

**pdf'je** is inspired by the following projects.
**pdfje** is inspired by the following projects.
If you're looking for a PDF writer, you may want to check them out as well:

- `python-typesetting <https://github.com/brandon-rhodes/python-typesetting>`_
Expand Down
8 changes: 4 additions & 4 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
sphinx~=7.0
furo~=2023.5.20
sphinx-toolbox~=3.4.0
sphinx-autodoc-typehints~=1.23
sphinx~=7.1
furo~=2023.7.26
sphinx-toolbox~=3.5.0
sphinx-autodoc-typehints~=1.24
35 changes: 33 additions & 2 deletions docs/tutorial.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Tutorial
========

This guide will walk you through the basics of using **pdf'je**.
This guide will walk you through the basics of using **pdfje**.
Each section will build on the previous one, so it is recommended to read
the sections in order.

Expand Down Expand Up @@ -378,4 +378,35 @@ To customize hyphenation, pass a :class:`~pyphen.Pyphen` object to the
- If you don't have ``pyphen`` installed, pdfje will use a simple
hyphenation algorithm for english text.

- To disable hyphenation, pass ``hyphens=None`` to the :class:`~pdfje.Style` constructor.
- To disable hyphenation altogether,
pass ``hyphens=None`` to the :class:`~pdfje.Style` constructor.

🧮 Optimal line breaks
----------------------

Breaking paragraphs into lines is `more complex than it seems <https://www.youtube.com/watch?v=kzdugwr4Fgk>`_.
A naive approach often leads to uneven line lengths and excessive or awkward hypenation.
Pdfje uses the `Knuth-Plass <https://en.wikipedia.org/wiki/Line_wrap_and_word_wrap#Knuth.E2.80.93Plass_algorithm>`_
algorithm to find the optimal line breaks.
This algorithm is used by most typesetting systems, including TeX and InDesign.
Pdfje's implementation optimizes for:

- Filling the lines as evenly as possible
- Avoiding hyphenation in general, especially if the previous line is also hyphenated

A disadvantage of the Knuth-Plass algorithm is that it is relatively slow.
If you'd like to use a faster, but less optimal algorithm, you can pass
``optimal=False`` to the :class:`~pdfje.layout.Paragraph` constructor.

🛟 Avoiding orphaned lines
--------------------------

Orphaned or widowed lines are lines that are left dangling at the top or bottom of a page.
They are considered bad typography, and should be avoided.

There are many ways to avoid orphaned lines, but the most common one is to
move a line to the next page if it avoids a line being left alone.
pdfje supports this behavior by default.
If you want to disable this behavior, pass ``avoid_orphans=False`` to the
:class:`~pdfje.layout.Paragraph` constructor.
This is useful in multi-column layouts, where you want to avoid uneven columns.
5 changes: 4 additions & 1 deletion examples/book.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,18 +88,21 @@ def create_page(num: int) -> Page:
def chapters() -> Iterable[Sequence[Paragraph | Rule]]:
"Book content grouped by chapters"
buffer: list[Paragraph | Rule] = [Paragraph("Chapter I\n", HEADING)]
indent = 0
for p in PARAGRAPHS:
if p.strip() in _CHAPTER_NUMERALS:
yield buffer
buffer = [Paragraph(f"Chapter {p.strip()}\n", HEADING)]
indent = 0
elif p.startswith("------"):
buffer.append(Rule("#aaaaaa", (20, 10, 10)))
else:
buffer.append(
Paragraph(
p, Style(line_spacing=1.2), align="justify", indent=20
p, Style(line_spacing=1.2), align="justify", indent=indent
)
)
indent = 15
yield buffer


Expand Down
Loading

0 comments on commit 95ddbe3

Please sign in to comment.