Skip to content
15 changes: 15 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,21 @@ src/pdf_equilibrist/
ci.yml et par tous les workflows de publication — rien n'est buildé ni publié s'il échoue.
- En local : `flake8 src tests && bandit -r src -q && pytest -q` (`QT_QPA_PLATFORM=offscreen`).

## Release et tags

**Toujours préparer la prochaine release avant tout push de tag** (ou toute création de release GitHub, qui crée le tag).
Un tag `v*` déclenche automatiquement et publiquement : release Windows, AppImage, AUR, PPA, COPR et site. Un tag ne se « retire » pas proprement.

1. Préparer dans une PR, mergée **avant** le tag (voir `packaging/RELEASE_CHECKLIST.md`) :
- `packaging/ppa/debian/changelog` : entrées `X.Y.Z-1~noble1` **et** `X.Y.Z-1~resolute1` en tête
(le workflow PPA échoue si le haut du changelog ne correspond pas au tag) ;
- `packaging/copr/pdf-equilibrist.spec` : `Version:` + entrée `%changelog` ;
- exemples de `RELEASE_CHECKLIST.md` alignés sur la nouvelle version.
2. Ne **pas** toucher à `PKGBUILD` / `.SRCINFO` (mis à jour par `linux-aur-publish.yml` après le tag) ni à
`__version__` / `pyproject.toml` (injectés par les workflows à partir du tag).
3. Poser le tag sur `main` seulement quand la PR est mergée et que CI + « Linux packages check » sont verts sur `main`.
4. Demander la confirmation de l'utilisateur avant de pousser le tag, sauf demande explicite.

## PyMuPDF

Toujours `import pymupdf` (jamais `import fitz` : module de compatibilité déprécié qui affiche un avertissement).
Expand Down
6 changes: 3 additions & 3 deletions packaging/RELEASE_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
> et la mise à jour du site (`update-site-version.yml`). Un tag ne se « retire » pas proprement :
> **ne le créer qu'une fois toute la checklist cochée.**

## 1. Préparation (avant le tag) — fait pour 0.2.2
## 1. Préparation (avant le tag) — fait pour 0.2.3

- [x] `packaging/ppa/debian/changelog` : entrées `X.Y.Z-1~noble1` **et** `X.Y.Z-1~resolute1` en tête
(le workflow PPA échoue explicitement si la version ne correspond pas au tag)
Expand Down Expand Up @@ -49,8 +49,8 @@ OCR → PDF cherchable, OCR → Word, OCR → Excel. Mesure synthétique de réf

```bash
git switch main && git pull
git tag -a v0.2.2 -m "PDF-Equilibrist 0.2.2"
git push origin v0.2.2 # ← déclenche TOUT : release publique + AUR + PPA + COPR + site
git tag -a v0.2.3 -m "PDF-Equilibrist 0.2.3"
git push origin v0.2.3 # ← déclenche TOUT : release publique + AUR + PPA + COPR + site
```

## 5. Après le tag — à surveiller
Expand Down
10 changes: 9 additions & 1 deletion packaging/copr/pdf-equilibrist.spec
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Name: pdf-equilibrist
Version: 0.2.2
Version: 0.2.3
Release: 1%{?dist}
Summary: Free and open-source desktop PDF editor

Expand Down Expand Up @@ -197,6 +197,14 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/%{name}.desktop
%{_datadir}/icons/hicolor/256x256/apps/%{name}.png

%changelog
* Mon Nov 02 2026 Paul Woisard <paulwoisard@gmail.com> - 0.2.3-1
- New upstream release: add text where you click, with a text size that
follows the box. Add text and Add image wait for a click on the PDF and
drop the item where you clicked (Escape cancels). The text size now scales
with the box (width or height, whichever is tighter), and font, size, bold,
italic and colour stay editable until validation. Text can be placed at
any angle.

* Mon Nov 02 2026 Paul Woisard <paulwoisard@gmail.com> - 0.2.2-1
- New upstream release: font, size, style and colour for added text.
"Add text" now opens a formatting dialog (font, size, bold, italic,
Expand Down
34 changes: 34 additions & 0 deletions packaging/ppa/debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
pdf-equilibrist (0.2.3-1~noble1) noble; urgency=medium

* New upstream release: add text where you click, with a text size that
follows the box.
- Add text and Add image now wait for a click on the PDF ("Click on the
page to place the text") and drop the item where you clicked, on the
page you clicked, instead of pasting it in the middle of the current
page. Escape or Cancel abandons the placement.
- The text size is now dynamic: resizing the text box scales the font
to fit its width or height, whichever is the tighter constraint.
- Font, size, bold, italic and colour stay editable until the text is
validated.
- Text can be placed at any angle, not only multiples of 90 degrees.
- New English translations.

-- Paul Woisard <paulwoisard@gmail.com> Mon, 02 Nov 2026 15:00:00 +0000

pdf-equilibrist (0.2.3-1~resolute1) resolute; urgency=medium

* New upstream release: add text where you click, with a text size that
follows the box.
- Add text and Add image now wait for a click on the PDF ("Click on the
page to place the text") and drop the item where you clicked, on the
page you clicked, instead of pasting it in the middle of the current
page. Escape or Cancel abandons the placement.
- The text size is now dynamic: resizing the text box scales the font
to fit its width or height, whichever is the tighter constraint.
- Font, size, bold, italic and colour stay editable until the text is
validated.
- Text can be placed at any angle, not only multiples of 90 degrees.
- New English translations.

-- Paul Woisard <paulwoisard@gmail.com> Mon, 02 Nov 2026 15:00:00 +0000

pdf-equilibrist (0.2.2-1~noble1) noble; urgency=medium

* New upstream release: font, size, style and colour for added text.
Expand Down
51 changes: 51 additions & 0 deletions src/pdf_equilibrist/operations/edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,57 @@ def add_text(
)


def text_line_metrics(fontname: str) -> tuple[float, float]:
"""Ascendante et descendante (positive) d'une police, pour une taille de 1 pt."""
font = pymupdf.Font(fontname)
return font.ascender, -font.descender


def text_natural_size(text: str, fontname: str, fontsize: float) -> tuple[float, float]:
"""Largeur et hauteur (pts) d'une ligne de ``text`` à ``fontsize``."""
asc, desc = text_line_metrics(fontname)
width = pymupdf.Font(fontname).text_length(text, fontsize=1) * fontsize
return width, (asc + desc) * fontsize


def fit_fontsize(text: str, fontname: str, box_w: float, box_h: float,
min_size: float = 1.0, max_size: float = 1000.0) -> float:
"""
Plus grande taille de police pour laquelle ``text`` tient dans la boîte.

C'est la contrainte la plus forte (largeur ou hauteur) qui l'emporte :
la boîte est remplie au maximum sans que le texte ne déborde.
"""
asc, desc = text_line_metrics(fontname)
unit_w = pymupdf.Font(fontname).text_length(text, fontsize=1)
by_h = box_h / (asc + desc)
by_w = box_w / unit_w if unit_w > 0 else max_size
return max(min_size, min(by_w, by_h, max_size))


def add_text_fitted(page: pymupdf.Page, rect: pymupdf.Rect, text: str,
fontname: str = "helv", color: tuple = (0, 0, 0),
angle: float = 0.0) -> float:
"""
Écrit ``text`` sur une ligne, à la taille maximale qui tient dans ``rect``.

Le texte est aligné à gauche et centré verticalement dans ``rect``.
``angle`` (degrés, sens horaire à l'écran) pivote autour du centre de ``rect``.
Retourne la taille de police utilisée (pts).
"""
size = fit_fontsize(text, fontname, rect.width, rect.height)
asc, desc = text_line_metrics(fontname)
baseline_y = rect.y0 + (rect.height - (asc + desc) * size) / 2 + asc * size
morph = None
if angle % 360:
center = pymupdf.Point((rect.x0 + rect.x1) / 2, (rect.y0 + rect.y1) / 2)
morph = (center, pymupdf.Matrix(-angle))
page.insert_text(pymupdf.Point(rect.x0, baseline_y), text,
fontname=fontname, fontsize=size, color=color,
morph=morph, overlay=True)
return size


def add_image(doc: pymupdf.Document, page_index: int,
image_path: Path, rect: pymupdf.Rect):
"""
Expand Down
Loading
Loading