# 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$) dominiert $f_{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: 1. Richtungsvektor $T_i = P_{i+1} - P_i$. 2. Senkrechter Normalenvektor $N_i = (-T_{i,y}, T_{i,x}) / ||T_i||$. 3. An inneren Stützstellen werden Normalen benachbarter Segmente gemittelt (Winkelhalbierende). 4. 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: 1. **Live-Phase (Kausal, Zero-Latency):** - Eingehende Punkte durchlaufen in Echtzeit den 1€-Filter (`filter.rs`). - `spline.rs` tesselliert inkrementell temporäre Triangle-Strips für die unverzögerte Bildschirmdarstellung im GPU-Live-Buffer. 2. **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^\circ$ wird die $C^1$-Spline-Kontinuität zu $C^0$ aufgebrochen, 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. - Der temporäre Live-Buffer wird vollständig entleert und der bereinigte `Stroke` in den persistenten Puffer der Seite überführt. --- ## 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]$): ```wgsl struct CameraUniform { transform: mat4x4, }; ``` Die Projektionsmatrix $M$ wird berechnet aus: 1. Skalierung des DIN-A4-Papiers auf den Viewport unter Berücksichtigung des Seitenverhältnisses. 2. Anwenden des Zoom-Faktors $z \in [0.25, 5.0]$. 3. 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: 1. **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. 2. **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^\circ$ und $105^\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^\circ$ bis $100^\circ$ wird exakt auf $90^\circ$ eingerastet; 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_.json] ``` - **Keine UI-Blockierung:** Disk-I/O erfolgt vollständig in einem dedizierten Worker-Thread. - **Atomarität:** Daten werden stets zuerst in `.json.tmp` geschrieben und via `fs::rename` atomar ersetzt. - **Verlustfreier Notizbuchwechsel:** Bei `switch_notebook_dir` wird synchron `flush()` aufgerufen, um alle ungespeicherten Daten des aktuellen Dokuments sicherzustellen, bevor der Pfad gewechselt wird.