7.7 KiB
Architektur & Technische Dokumentation von ink-notes
Dieses Dokument beschreibt die interne Architektur, mathematische Modelle und Rendering-Pipelines von ink-notes.
1. Die Inking-Pipeline
[Hardware Stylus / Touch / Maus]
│
▼
[winit Event-Loop]
│ (tx, ty, pressure, timestamp)
▼
[Viewport Invertierung] (screen_to_page: Umrechnung in DIN-A4-Einheiten)
│
▼
[1€-Filter] (Jitter-Beseitigung bei langsamen Bewegungen)
│
▼
[Centripetal Catmull-Rom] (C1-stetige Spline-Interpolation, alpha = 0.5)
│
▼
[Mesh-Tessellierung] (Drucksensitive Dreiecksstreifen via Normalen)
│
▼
[wgpu GPU-Buffer] (Vertex- & Index-Buffer im VRAM)
1.1. 1€-Filter (One-Euro-Filter)
Stift- und Touch-Eingaben unterliegen bei niedrigen Geschwindigkeiten oft Mikrozittern (Jitter). Standard-Tiefpassfilter verursachen jedoch bei schnellen Schwüngen eine störende Verzögerung (Latenz).
Der 1€-Filter (filter.rs) löst dieses Dilemma mit einer adaptiven Grenzfrequenz f_c:
f_c = f_{c,\min} + \beta \cdot |\dot{x}|
- Bei geringer Geschwindigkeit (
|\dot{x}| \approx 0) dominiertf_{c,\min}, wodurch Zittern maximal geglättet wird. - Bei schnellen Zügen wächst
\beta \cdot |\dot{x}|an, wodurch der Filter durchlässig wird und Latenz eliminiert.
1.2. Centripetal Catmull-Rom Splines
Um Ecken zu vermeiden und eine organische Schriftführung zu erzielen, werden Abtastpunkte mit einem zentripetalen Catmull-Rom-Spline (spline.rs) interpoliert (\alpha = 0.5):
t_{i+1} = t_i + ||P_{i+1} - P_i||^\alpha
Diese Parametrisierung garantiert:
- Keine Selbstüberschneidungen bei scharfen Wendungen.
- Keine unerwünschten Cusp-Schleifen.
- Perfekte $C^1$-Stetigkeit an den Stützstellen.
1.3. Drucksensitive Dreiecksstreifen (Tessellierung)
Aus den interpolierten Spline-Punkten mit Radius r_i = f(\text{pressure}_i) wird ein Triangle-Strip erzeugt:
- Richtungsvektor
T_i = P_{i+1} - P_i. - Senkrechter Normalenvektor
N_i = (-T_{i,y}, T_{i,x}) / ||T_i||. - An inneren Stützstellen werden Normalen benachbarter Segmente gemittelt (Winkelhalbierende).
- Eckpunkte des Dreiecksstreifens:
V_{i,\text{left}} = P_i + N_i \cdot r_i, \quad V_{i,\text{right}} = P_i - N_i \cdot r_i
1.4. Zwei-Stufen-Modell (Live-Glättung vs. akausale Post-Stroke-Finalisierung)
Um minimale Latenz beim Zeichnen mit maximaler Präzision und Ecken-Erhaltung zu verbinden, arbeitet ink-notes mit einem strikten Zwei-Stufen-Modell:
- Live-Phase (Kausal, Zero-Latency):
- Eingehende Punkte durchlaufen in Echtzeit den 1€-Filter (
filter.rs). spline.rstesselliert inkrementell temporäre Triangle-Strips für die unverzögerte Bildschirmdarstellung im GPU-Live-Buffer.
- Eingehende Punkte durchlaufen in Echtzeit den 1€-Filter (
- Commit-Phase (Akausal,
finalize_stroke):- Beim Absetzen des Stifts wird der gesamte Strich akausal analysiert:
- Druckglättung: Glättung des Druckverlaufs
püber ein gewichtetes 3-Punkt-Fenster[0.25, 0.5, 0.25]zur Ausblendung von Digitalisierer-Stufen. - RDP-Dezimierung: Ausdünnung kollinearer Zwischenpunkte mit Ramer-Douglas-Peucker (
\epsilon \approx 0.075\text{ mm} = 0.75\text{ Einheiten}). - Ecken-Erkennung (Corner Preservation): Bei Richtungsänderungen
> 65^\circwird die $C^1$-Spline-Kontinuität zuC^0aufgebrochen, sodass Ecken (z. B. 90°-L-Knicke oder Zickzack-Linien) gestochen scharf bleiben und nicht rundgeschliffen werden. - Runde Kappen & Miter-Joins: Generierung sauberer runder Kappen an Start und Ende des Triangle-Strips.
- Druckglättung: Glättung des Druckverlaufs
- Der temporäre Live-Buffer wird vollständig entleert und der bereinigte
Strokein den persistenten Puffer der Seite überführt.
- Beim Absetzen des Stifts wird der gesamte Strich akausal analysiert:
2. Rendering & Kamera-Pipeline (wgpu)
2.1. Virtuelles Koordinatensystem
Die Anwendung rechnet intern in virtuellen DIN-A4-Koordinaten:
- Breite: 2100 Einheiten (210 mm)
- Höhe: 2970 Einheiten (297 mm)
- 1 mm entspricht exakt 10 Einheiten.
2.2. Viewport & Projektionsmatrix (viewport.rs)
Der Vertex-Shader transformiert DIN-A4-Koordinaten über eine Orthogonal-Projektion kombiniert mit Pan und Zoom in Normalized Device Coordinates (NDC, [-1, 1]):
struct CameraUniform {
transform: mat4x4<f32>,
};
Die Projektionsmatrix M wird berechnet aus:
- Skalierung des DIN-A4-Papiers auf den Viewport unter Berücksichtigung des Seitenverhältnisses.
- Anwenden des Zoom-Faktors
z \in [0.25, 5.0]. - Translation durch den Pan-Offset
(p_x, p_y).
2.3. Cursor-zentrierter Zoom
Beim Zoomen mit dem Mausrad wird der neue Pan-Offset so korrigiert, dass der Punkt unter dem Cursor fixiert bleibt:
\vec{p}_{\text{neu}} = \vec{c} - (\vec{c} - \vec{p}_{\text{alt}}) \cdot \frac{z_{\text{neu}}}{z_{\text{alt}}}
2.4. Render-Passes
In jedem Frame werden zwei Hauptpässe durchlaufen:
- Pass 1 (Canvas):
- Hintergrund-Quad und Linien/Gitter (Instanced oder Batch Vertex-Buffer).
- Gespeicherte Striche und Geometrie-Meshes.
- Aktiver Live-Strich (Ephemeral Buffer).
- Auswahl-Overlay & Transformationsgriffe.
- Pass 2 (egui UI):
- Floating Glassmorphism-Toolbar.
- Farb- und Werkzeug-Popovers.
- Modal-Dokumentenmanager.
3. Geometrieerkennung & Shape-Fitting (geometry.rs)
Die Erkennung erfolgt rein analytisch und deterministisch ohne schwere ML-Frameworks:
3.1. Ramer-Douglas-Peucker (RDP)
Reduziert den Rohpfad auf signifikante Eckpunkte mit konfigurierbarem Toleranzabstand \epsilon.
3.2. Erkennungsregeln:
- Gerade: Verhältnis von Sehnenlänge (
||P_{\text{end}} - P_{\text{start}}||) zur Pfad-Bogenlänge> 0.92. - Rechteck: RDP liefert 4 dominante Ecken; Kantenwinkel liegen zwischen
75^\circund105^\circ. - Kreis / Ellipse: Pfad ist geschlossen (
||P_{\text{start}} - P_{\text{end}}|| < 0.1 \times \text{Umfang}); Standardabweichung der Radien vom Mittelpunkt ist minimal (< 0.12 \times \bar{r}). - Koordinatensystem: Zwei orthogonale Schenkel mit Schnittpunkt nahe dem Start/Ende; Winkel
80^\circbis100^\circwird exakt auf90^\circeingerastet; positive Achsenenden erhalten Pfeilspitzen. - Dreiecke / Rhomben / Oktaeder: Spezifische Eckpunkt- und Symmetrieanalysen der RDP-Kontur.
3.3. Draw-and-Hold Erkennung
Hält der Anwender den Stift am Ende eines Strichs für > 400\text{ ms} innerhalb eines Radius von < 3\text{ px} ruhig, wird die Form im fliegenden Wechsel als Vektorform visualisiert und beim Abheben des Stifts fixiert.
4. Persistenz & Autosave-Architektur (storage.rs, autosave.rs)
[UI / Inking Event]
│
▼
[mark_page_dirty]
│
▼
[AutosaveController] ──(1.5s Debounce)──► [Hintergrund-Thread (MPSC)]
│
▼
[Temp-Datei *.tmp schreiben]
│
▼
[Atomares std::fs::rename]
│
▼
[manifest.json / page_<id>.json]
- Keine UI-Blockierung: Disk-I/O erfolgt vollständig in einem dedizierten Worker-Thread.
- Atomarität: Daten werden stets zuerst in
.json.tmpgeschrieben und viafs::renameatomar ersetzt. - Verlustfreier Notizbuchwechsel: Bei
switch_notebook_dirwird synchronflush()aufgerufen, um alle ungespeicherten Daten des aktuellen Dokuments sicherzustellen, bevor der Pfad gewechselt wird.