Files
ink-notes/docs/FILE_FORMAT.md
haraldandClaude Opus 5.5 3890b6f182 feat: Stifte, Seiten löschen, lückenlose Seiten, sauberer Textmarker (Issues #11, #12, #14, #15, #17, #19)
- #15: Koordinatensystem ohne Kerbe am Ursprung (Achsen beginnen eine halbe
  Linienbreite hinter dem Ursprung; im PDF ein Linienzug mit spitzer Ecke)
- #17: Jeder Strich deckt ein Pixel nur einmal (Stencil-Puffer), der
  Textmarker wird innerhalb eines Strichs nicht dunkler. Im PDF-Export ist der
  Textmarker jetzt halbtransparent statt deckend.
- #11: ➕ in der Werkzeugleiste fügt nach der aktuellen Seite eine Seite ein
- #12: Seite löschen im ☰-Menü mit Rückfrage, in der Sitzung wiederherstellbar
- #14: Seiten liegen lückenlos untereinander, eine dunkle Linie markiert die Grenze
- #19: Vier Stifte mit eigener Farbe und Stärke, die sich Änderungen merken
  (ui.json); Blätterpfeile nur noch in der Einzelseitenansicht

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:55:32 +02:00

7.5 KiB
Raw Permalink Blame History

Dateiformat von ink-notes

Dieses Dokument beschreibt, wo und wie ink-notes Notizbücher speichert, und die Versionen des Dateiformats. Aktuell ist Version 8.


1. Ordner

~/.local/share/ink-notes/              ($XDG_DATA_HOME/ink-notes)
├── notebooks/
│   ├── <notizbuch-id>/
│   │   ├── manifest.json              Metadaten und Seitenreihenfolge
│   │   ├── manifest.json.bak          Kopie des vorherigen Manifests
│   │   ├── page_1.json                eine Datei je Seite
│   │   ├── page_2.json
│   │   ├── page_7.json.corrupt-<ts>   beiseitegelegte beschädigte Seite (falls vorgekommen)
│   │   ├── assets/                    eingefügte Bilder und importierte PDFs
│   │   │   ├── <sha256>.png
│   │   │   └── <sha256>.pdf
│   │   └── .lock                      Sperrdatei, solange das Notizbuch geöffnet ist
│   └── .trash/                        gelöschte Notizbücher (<id>_<zeitstempel>)
└── recovery/                          Notfall-Sicherungen ungespeicherter Seiten

~/.config/ink-notes/ui.json            ($XDG_CONFIG_HOME) Einstellungen der Oberfläche
  • Notizbuch-IDs werden aus dem Titel und dem Zeitpunkt gebildet (z. B. mathematik_1775460000); das erste Notizbuch heißt default.
  • Assets sind nach dem SHA-256-Hash ihres Inhalts benannt; dasselbe Bild wird nur einmal gespeichert. Bilder werden als PNG oder (unverändert) JPEG abgelegt, PDFs unverändert.
  • Alle Dateien werden mit Rechten 0600, Ordner mit 0700 angelegt.

2. Manifest (manifest.json)

{
  "format_version": 8,
  "id": "vorlesung_1775460000",
  "title": "Vorlesung",
  "created_at": 1775460000,
  "modified_at": 1775463600,
  "current_page_index": 0,
  "pages": [
    { "id": 1, "background": "Blank", "title": "Einführung", "size": [2970.0, 1671.0] },
    { "id": 2, "background": "Grid5mm" }
  ],
  "page_ids": [1, 2],
  "next_stroke_id": 42,
  "next_page_id": 3
}
Feld Bedeutung
format_version Formatversion (siehe Abschnitt 5)
id, title ID (= Ordnername) und Anzeigename
created_at, modified_at UNIX-Zeit in Sekunden
current_page_index zuletzt geöffnete Seite
pages Seiten in ihrer Reihenfolge: id, background, optional title und size (siehe unten) – damit Seitenübersicht und Ansicht sie kennen, ohne jede Seite zu laden
page_ids dieselbe Reihenfolge als reine ID-Liste (für ältere Versionen)
next_stroke_id, next_page_id nächste freie IDs

3. Seite (page_<id>.json)

Jede Seite liegt in einer eigenen Datei (kompaktes JSON, hier zur Lesbarkeit umbrochen):

{
  "format_version": 8,
  "id": 1,
  "background": "Blank",
  "title": "Einführung",
  "size": [2970.0, 1671.0],
  "pdf_background": { "asset": "<sha256>.pdf", "page": 0, "width_pt": 960.0, "height_pt": 540.0 },
  "strokes": [ … ]
}
Feld Bedeutung
background Muster: Blank, Grid5mm, Lined, DotGrid
title Seitentitel (optional)
size Seitengröße [breite, höhe] in Seiteneinheiten (10 = 1 mm); fehlt bei DIN A4 hoch ([2100, 2970])
pdf_background importierte PDF-Seite als Hintergrund (optional): Datei, Seitennummer ab 0, Größe in Punkt
strokes alle Objekte der Seite, von unten nach oben

3.1 Objekte (strokes)

Jedes Objekt hat eine id, eine color (RGBA, linear, Werte 0–1) und samples: Punkte mit x, y (Seiteneinheiten), p (Druck 0–1) und t (Zeit in Sekunden). Die Dreiecke für die Anzeige werden beim Laden aus den Punkten berechnet und nicht gespeichert.

Tinte

{ "id": 7, "color": [0.12, 0.12, 0.14, 1.0],
  "samples": [{ "x": 350.2, "y": 420.5, "p": 0.65, "t": 12.345 }, …],
  "tool": "pen", "width": 5.0, "finalized": true }
  • tool: pen, highlighter oder shape
  • width: Strichbreite in Seiteneinheiten (5.0 = 0,5 mm)
  • finalized: Der Strich wurde nach dem Absetzen nachbearbeitet (Glättung, Ecken, Endkappen)

Formen haben zusätzlich shape, z. B.:

"shape": { "Rectangle": { "corners": [[500, 600], [800, 600], [800, 800], [500, 800]] } }

Mögliche Formen: Line { start, end }, Arrow { start, end, double }, Circle { center, radius }, Ellipse { center, radius_x, radius_y, rotation }, Rectangle { corners }, Triangle { corners }, Rhombus { corners }, Polygon { vertices }, CoordinateSystem { origin, x_axis_end, y_axis_end, with_arrows }, Octahedron { … }. Punkte sind [x, y].

Bilder haben image (Dateiname in assets/); die vier samples sind die Ecken oben links, oben rechts, unten rechts, unten links:

{ "id": 9, "color": [1, 1, 1, 1], "samples": [ …4 Ecken… ], "width": 0.0, "image": "<sha256>.png" }

Textfelder haben text; die vier samples sind wie bei Bildern die Ecken des Felds:

"text": { "content": "Überschrift\nzweite Zeile", "size": 42.3, "font": "Sans", "wrap_width": 1500.0 }
  • size: Schriftgröße (Em) in Seiteneinheiten (12 pt ≈ 42,3)
  • font: Sans (Ubuntu Light) oder Mono (Hack)
  • wrap_width: Umbruchbreite in Seiteneinheiten; die Ecken bilden das Feld dieser Breite (und der Höhe des gesetzten Texts) ab, Drehung und Skalierung stecken in den Ecken

4. Sicherheit beim Speichern

Atomares Schreiben

  1. Der Inhalt wird in <datei>.tmp.<pid>.<nanosekunden> geschrieben und mit fsync auf den Datenträger gebracht.
  2. rename ersetzt die alte Datei in einem Schritt.
  3. Danach wird auch der Ordner mit fsync gesichert.

Ein Absturz während des Speicherns hinterlässt deshalb entweder die alte oder die neue Datei, nie eine halbe.

Beschädigte Dateien

  • Lässt sich eine Seite nicht lesen, wird sie nach page_<id>.json.corrupt-<zeitstempel> verschoben (nicht gelöscht) und die Seite schreibgeschützt angezeigt.
  • Ist manifest.json beschädigt oder fehlt, wird manifest.json.bak verwendet.

Gelöschte Seiten

Eine gelöschte Seite verschwindet aus manifest.json; ihre Datei page_<id>.json bleibt im Ordner liegen. Seitenkennungen werden nie wiederverwendet (next_page_id), die Datei kann also mit keiner neuen Seite kollidieren. Wird die Seite in derselben Sitzung wiederhergestellt, schreibt die App sie neu.

Neuere Versionen

Dateien mit einer höheren format_version als der unterstützten werden nicht geöffnet – so kann eine ältere App-Version keine Inhalte verlieren, die sie nicht kennt.

Migration

Beim ersten Speichern einer unversionierten Datei (Version 0) wird vorher eine Kopie *.v0.bak angelegt.


5. Versionen

Version App Änderung
0 – ohne format_version
1 – format_version, Hintergründe im Manifest
2 – tool, width, finalized an Strichen; kompaktes JSON
3 0.3.7 Formen Ellipse und Arrow
4 0.5 Bilder (image, Ordner assets/)
5 0.6.1 importierte PDF-Seiten (pdf_background)
6 0.7 Seitentitel (title in Seite und Manifest)
7 0.10 Textfelder (text)
8 0.11 Seitengröße je Seite (size in Seite und Manifest)

Jede neue Version ändert nur Ergänzungen; ältere Dateien werden ohne Umwandlung gelesen und beim nächsten Speichern mit der aktuellen Versionsnummer geschrieben.


6. Warum JSON

Ein Binärformat (z. B. postcard) würde Dateien um weitere 20–30 % verkleinern. JSON bleibt trotzdem, weil es sich ansehen, vergleichen und im Notfall von Hand reparieren lässt und das Speichern einer Seite auch bei vielen Strichen deutlich unter einer Millisekunde dauert.