Commit Graph

11 Commits

Author SHA1 Message Date
karim 5abf1c0137 RTF: \line statt \par fuer Newlines zwischen Runs
Rhinos TextEntity-RTF-Parser rendert \par offenbar nicht als
Zeilenumbruch innerhalb eines Textfeldes. \line dagegen funktioniert
als soft line break.

Globales pending_newlines-Counting ueber alle Runs hinweg: jedes \n
im Text-Run wird zu einem \line, der erst VOR dem naechsten echten
Text emittiert wird. Damit bleiben auch Leerzeilen (mehrere \n
hintereinander) als mehrere \line erhalten.

Why: User-Vergleich Screenshots — WYSIWYG zeigt korrekte Leerzeile
zwischen Heading und Paragraph, Rhino rendert beide Runs auf der
gleichen Zeile konkateniert.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 08:38:50 +02:00
karim 91bc03184e RTF: Two-Pass fonttbl + Leerzeilen-Fix + wrap-Diagnose
Log-Analyse vom User: RTF preview zeigte
  '{\\fonttbl{\\f0\\fnil\\fcharset0 Georgia;}}'
ABER der Body nutzt
  '\\f1\\cf1\\fs20\\b ... Lorem ...'
\\f1 ist NICHT in der fonttbl! → Rhino fallback auf Default
\\cf1 ist NICHT in der colortbl (es gibt keine)! → ignoriert

Root cause: ich hatte die fonttbl/colortbl eagerly geschrieben BEVOR
die Runs ueberhaupt verarbeitet waren. fonts/colors-Listen wurden waehrend
des Body-Loops um neue Eintraege erweitert, aber der schon-emittierte
Header sah die nie.

Fix: Two-Pass.
PASS 1: Body bauen, dabei font_idx/color_idx aufrufen → fonts/colors-
Listen werden komplett gefuellt.
PASS 2: RTF-Header schreiben mit JETZT vollstaendigen Tables, dann Body
anhaengen.

Plus Leerzeilen: aufeinanderfolgende \\n in den Runs erzeugen jetzt
ein leeres Paragraph mit Space (" "), damit Rhinos Parser den \\par
nicht mit dem naechsten kollabiert. Resultat:
  Lorem...\\par
   \\par         ← Leerzeile (echtes Space im leeren Paragraph)
  Consectetur...

Plus Frame-Wrap-Diagnostic: "[TEXT-EDITOR] wrap: width=... applied_attr=
FormatWidth/TextWidth/None" damit ich sehen kann ob die Wrap-Property
ueberhaupt gesetzt wird in dieser Rhino-Version.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 08:15:26 +02:00
karim 2d48a6ed3a RTF: inline state codes + SetRichText method + Diagnose-Print
User: Mixed-Fonts gehen immer noch nicht — Rhino zeigt nur die letzte
gesetzte Schrift, Bold-State auch verloren.

_runs_to_rtf neu: klassisches Inline-RTF ohne {} Groups. Pro Run werden
ALLE Codes explizit gesetzt (auch Reset-Codes \\b0 \\i0 \\ulnone \\cf0
\\nosupersub) und alle in EINER Zeile mit dem Text:

  \\pard \\f0\\cf0\\fs60\\b\\i0\\ulnone\\nosupersub Georgia bold
  \\par \\f1\\cf0\\fs20\\b0\\i0\\ulnone\\nosupersub Helvetica regular

Damit ist klar: jeder Run hat eigene state-Definitions. RTF-Parser
nimmt nicht "letzte Code wirkt auf alles".

_commit-Reihenfolge bei RichText geaendert: KEIN _apply_font wenn RTF
verwendet wird. te.Font wuerde sonst die per-Run \\fN Codes
ueberschreiben.

Method-Switch: te.SetRichText(rtf, dimstyle) zuerst probiert (robuster
API), te.RichText property als Fallback, _apply_font als letzter
Notfall.

Diagnostic: RTF wird jetzt mit Length + 300-char-preview gedruckt
("[TEXT-EDITOR] RTF len=... preview=..."). Bitte Log copy-pasten bei
weiteren Issues damit ich seh was tatsaechlich Rhino erreicht.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 02:19:04 +02:00
karim 211078d229 Text-Editor: Style-Picker focus-restore + Runs-Round-Trip beim Reopen
User-Bugs:
1. Stil-Picker setzt Toolbar-State aber Editor zeigt alle Texte gleich
2. Beim Reopen (Doppelklick auf DOSSIER-Text) ist Editor leer/unformatiert

Fix 1 — applyStyle focus-restore:
Style-Picker stiehlt Focus → savedRange ist da, aber execCommand
ohne Focus auf editable schreibt nichts. Fix: editorRef.focus() +
restoreSelection() VOR execCommand. Plus Size jetzt via inline-span
(execCommand fontSize geht nur 1-7 Scale, kein Pixel).

Fix 2 — Runs persistieren als UserString fuer Round-Trip:
- _commit: runs als JSON in attrs.SetUserString("dossier_text_runs")
  abgelegt zusammen mit dem dossier_text-Tag
- open_for_edit: liest UserString, parsed JSON → initial_runs
- Bridge INIT: sendet initialRuns mit
- Frontend onMessage INIT: wenn initialRuns vorhanden → runsToHtml()
  baut HTML mit spans (font-family, font-size, color) + Tags (b/i/u/
  sup/sub) — Editor zeigt jetzt beim Reopen das tatsaechliche reiche
  Format statt PlainText im Default-Helvetica

runsToHtml: pro run text/style-Splittung per \n → <br>. Escaping fuer
&<> damit kein XSS / Parse-Fehler.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 02:13:42 +02:00
karim 3bd949e590 RTF: pro-Run-Groups + Newlines als \par zwischen Groups
User-Bug: Heading (Helvetica Bold) + Paragraph (Georgia Regular) als
2 Zeilen ergibt in Rhino: alle Text in einer Zeile, mit Bold vom
Heading aber Font+Size vom LETZTEN definierten Paragraph (Georgia).

Root cause: Mein RTF-Generator emitted alle Codes hintereinander ohne
{}-Grouping. RTF-Parser ohne Scope nimmt das letzte \\f und \\fs als
globalen State und appliziert auf ALLES.

Fix _runs_to_rtf:
- Pro Run: text per \\n splitten, zwischen Segmenten \\par emitten
- Jedes nicht-leere Segment wird in {} Group gewrapped mit eigenen
  Codes (\\fN \\cfN \\fsN \\b \\i \\ul \\super \\sub)
- Nur AKTIVE Toggles in der Group (kein \\b0/\\i0/\\ulnone mehr —
  Default ausserhalb der Group ist plain)
- Resultat: {\\f0\\b Lorem ...}\\par {\\f1 Consectetur ...}
  Jede Group ist isoliert, kein Cross-Run-State-Leak

_escape_no_par: \\n bleibt als Literal-Newline durchgelassen (splitting
geschieht im outer loop, nicht im escape).

_commit-Reihenfolge nochmal aufgeraeumt:
1. Plane
2. Defaults (Height, Font, Align) — gilt fuer alles
3. Content (RichText wenn nontrivial, sonst PlainText)
4. Wrap (FormatWidth + TextIsWrapped) — NACH RichText damit nicht
   zurueckgesetzt
5. Frame/Mask/DrawForward

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 02:07:46 +02:00
karim 1596bbd941 Text-Editor: B/I/U sync via queryCommandState + RTF-Reihenfolge fixen
User: B/I/U Toggle-Buttons hinken hinterher oder zeigen invertiert.
Im WYSIWYG sieht alles richtig aus aber Rhino zeigt nur die LETZTE
gesetzte Schriftart fuer alles.

Fix 1 — B/I/U sync:
- selectionchange-Listener pollt jetzt queryCommandState fuer
  bold/italic/underline und syncen das an Toolbar-State
- toggleBold/Italic/Underline machen nur noch exec() — kein manuelles
  setBold(b => !b) mehr (war out-of-sync wenn execCommand wegen
  fehlender Selection nicht griff)
- B/I/U-Button-Highlight reflektiert jetzt die echte Cursor-Position

Fix 2 — RTF nimmt nur letzte Schrift:
Reihenfolge im _commit war falsch. Vorher: RichText → TextHeight →
_apply_font → _apply_align. _apply_font setzt te.Font (Default-Font)
NACH dem RichText → schiesst die per-Run-Fonts in der RTF tot.

Neue Reihenfolge:
1. PlainText (Initial-Content)
2. TextHeight, Font, Align (Defaults fuer ALLES)
3. RichText (ueberschreibt Defaults pro Run)

Plus Font-Tabelle in _runs_to_rtf jetzt mit \fnil\fcharset0 Marker
(Rhinos RTF-Parser kann sonst font-Eintraege ignorieren und Default
verwenden).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 02:00:03 +02:00
karim 51987dcc38 Text-Editor: Selection-Preservation + per-Span Font/Size
User-Bug: Stile aendern nichts im Editor oder springen alle in eine
Zeile. Mit "Herumfummeln" partiell ge-fixed. Root-Causes:

1. Toolbar-Buttons stehlen Focus aus Editor → Selection futsch →
   execCommand wirkt auf nichts. Fix: onMouseDown + preventDefault
   auf B/I/U/Sup/Sub/Align (Pill akzeptiert jetzt onMouseDown prop).

2. Editor-div hat fontFamily/fontSize aus React-State → ueberschreibt
   per-Span-Styles → alles sieht gleich aus. Fix: editor-div hat
   statische Defaults (Helvetica 20px), per-Selection Styles wirken
   ueber span-Wrapping (applyInlineStyleToSelection).

3. Newlines kollabieren (text springt auf eine Zeile). Fix:
   white-space: pre-wrap auf editor-div.

4. Font/Size dropdowns: alter execCommand fontName war buggy. Neu:
   applyInlineStyleToSelection('font-family', font) bzw. 'font-size'
   wickelt die Selektion in ein <span style="..."> ein, neue Selection
   liegt auf dem Span (Folge-Operationen wirken sauber).

5. Selection-change Event-Listener speichert die letzte Editor-Selection
   in savedRangeRef. restoreSelection() vor jeder Operation stellt sie
   wieder her — robust auch wenn der Focus zwischendurch weg war.

Backend (_runs_to_rtf): BASE_PX = base_size_m * 100 statt hardcoded 14.
Frontend rendert 1m = 100px, also entspricht base_size_m*100px dem
\\fs20 in RTF (= 1.0× TextEntity.TextHeight). _commit passes settings.
size mit, damit das Mapping stimmt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 01:54:25 +02:00
karim f4404db64a Doppelklick-Hook auf DOSSIER-Texte + Size-Mapping in RTF
User: Doppelklick auf DOSSIER-Text oeffnet weiterhin Rhinos Editor.
Verschiedene Groessen im Editor erscheinen nicht in Rhino.

Doppelklick-Hook (rhino/text_editor.py):
- _DossierTextDoubleClickHook subklassiert Rhino.UI.MouseCallback.
  OnMouseDoubleClick prueft selektierte TextEntities auf UserString
  "dossier_text"="1" und cancelled das Event (= blockt Rhinos
  Standard-TextEdit-Dialog), setzt sticky["dossier_pending_text_edit"]
  mit der Obj-ID
- _on_idle_check_pending_edit (RhinoApp.Idle event): nimmt sticky-ID
  auf naechstem Idle-Tick und ruft open_for_edit(obj) — defer noetig
  weil Eto-Form aus MouseCallback heraus oeffnen Re-Entrancy macht
- _ensure_double_click_hook() installiert Hook + Idle-Handler einmalig
  pro Rhino-Session (idempotent)
- startup.py ruft das jetzt direkt nach Modul-Load auf

Edit-Mode (open_for_edit):
- Liest aus bestehendem TextEntity die Settings (Font, Size, Bold,
  Italic, Underline, Align) + PlainText
- Frame fuer Dialog-Positionierung aus BBox abgeleitet
- TextEditorBridge mit edit_obj_id + initial_text gestartet
- INIT-Payload um initialText + editMode erweitert
- COMMIT: bei edit_obj_id gesetzt → doc.Objects.Replace statt AddText.
  Plane wird vom Original uebernommen wenn keine explizite Rotation,
  damit der Text an seinem Platz bleibt

Frontend (TextEditorApp.jsx):
- Bei INIT mit initialText: editor.innerText wird damit befuellt
- htmlToRuns extrahiert font-size in Pixel pro Run (inline style oder
  computed style != base)
- baseCtx _basePx aus computed style des Editor-divs

Size-Mapping (rhino/text_editor.py _runs_to_rtf):
- fontSizePx in Runs triggert non-trivial (RTF wird generiert)
- Pro Run: \fs in Halb-Punkten = 20 * (run_px / 14_base_px) round
- 14px = \fs20 (1.0× TextEntity.TextHeight)
- 21px = \fs30 (1.5×)
- 28px = \fs40 (2.0×)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 01:44:45 +02:00
karim e7a1753519 Text-Editor: Default-Stile + Stil-Picker im Dialog
User-Wunsch: vorgespeicherte Stile (Heading, Paragraph Helvetica/Georgia)
direkt im Editor anwendbar.

Backend (text_create.py):
- _DEFAULT_STYLES: 7 sinnvolle Architektur-Defaults — Titel (0.40m bold),
  Heading 1 (0.30m bold), Heading 2 (0.20m bold), Paragraph Helvetica
  (0.15m), Paragraph Georgia (0.15m Georgia), Notiz (0.10m italic),
  Bildlegende (0.08m italic)
- list_styles: seedet die Defaults beim ersten Zugriff falls noch keine
  Styles im Doc existieren (analog mass_style)
- Bestehende save_style/delete_style/apply_style funktionieren weiter

Backend (text_editor.py):
- INIT-Payload erweitert um styles[] (Liste aller verfuegbaren Stile
  mit id/name/font/size/bold/italic/underline/align)

Frontend (TextEditorApp.jsx):
- Neuer Stil-Picker als erstes Dropdown in Toolbar-Row 1 (150px)
- Optionen: "— Stil wählen —" + alle verfuegbaren Stile
- onChange: applyStyle(style) — setzt Toolbar-State + appliziert via
  execCommand auf die aktuelle Selektion im WYSIWYG-Editor (oder als
  Default fuer kommendes Tippen wenn keine Selektion)
- queryCommandState-Check fuer Bold/Italic/Underline damit nur toggled
  wird wenn nicht schon im gewuenschten State

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 01:38:23 +02:00
karim 54aa1c9e84 Text-Editor: Symbol-Popover, Frame/Mask/Rot/Kamera, Phase 2 RTF-Mapping
User: Sonderzeichen in einen Button packen mit Popover-Box, neue
Optionen (Frame around text, Horizontal to view, Rotation, Mask
margins), und Phase 2 = mixed-fonts via Rich-Text.

Frontend (TextEditorApp.jsx):
- Sonderzeichen-Reihe weg, ersetzt durch [Symbole] Pill-Button mit
  Popover-Box. Symbols gruppiert (Mathematik, Pfeile, Auszeichnung)
- Toolbar Reihe 3 neu: [Rahmen ▼] [Mask ____ m] [Rot ____ °]
  [Zur Kamera Toggle] [Symbole Popover]
- htmlToRuns(rootEl): walks contentEditable DOM, extrahiert Format-
  Runs (text, font, color, bold, italic, underline, sup, sub) basierend
  auf Tag-Hierarchie (b/i/u/sup/sub/font/span style)
- onCommit sendet jetzt zusaetzlich runs[] + frame/horizontalToView/
  rotation/maskMargin in settings

Backend (text_editor.py):
- _commit: setzt Plane mit Rotation um Z (math.radians + Plane.Rotate)
- MaskFrame: NoFrame/RectFrame/CapsuleFrame ueber TextMaskFrame-Enum
- MaskEnabled+MaskOffset+MaskUsesViewportColor wenn maskMargin>0
- te.DrawForward = horizontalToView (Text steht zur Kamera)
- _runs_to_rtf(runs, default_font): Phase 2 — generiert Rhinos RTF aus
  den Runs. Triviale Runs (alle plain, ein Font) → None (PlainText
  fallback). Sonst: \rtf1 + fonttbl + colortbl + Format-Codes pro Run
  (\f \cf \b \i \ul \super \sub \nosupersub). _rtf_escape handelt \,
  {, }, \n und non-ASCII (\u-Notation). te.RichText = rtf, Fallback
  auf PlainText wenn das fehlschlaegt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 01:35:28 +02:00
karim ab0ecfbf14 React WYSIWYG Text-Editor (Topmost Satellite-WebView) — Phase 1
User-Wunsch: eigener WYSIWYG-Editor im React/Topbar-GUI-Stil. Topmost.
Verschiedene Schriftarten/-Dicken sichtbar im Editor selbst.

Neues Backend (rhino/text_editor.py):
- TextEditorBridge mit Frame-Daten im Konstruktor, INIT-Push mit
  Settings + Font-Liste, COMMIT erstellt TextEntity, CANCEL schliesst
- open_with_frame(p1, p2, origin, width, height): oeffnet Satellite-
  Window mit mode='text_editor' + topmost=True
- panel_base.open_satellite_window: neuer Parameter topmost (default
  False) der form.Topmost setzt

text_create.create_text: ruft jetzt text_editor.open_with_frame nach
dem Frame-Pick. Eto-basierter _dossier_text_editor bleibt im Modul als
Fallback aber wird nicht mehr verwendet.

Neues Frontend (src/TextEditorApp.jsx, mode='text_editor'):
- Layout im DOSSIER-Topbar-Stil (dunkle Pills, accent on hover)
- Pill-Helper-Komponente fuer alle Toggle/Action-Buttons
- Dropdown-Helper fuer Font + Size
- Toolbar Row 1: Font-Dropdown | Size-Dropdown | Color-Picker | Layer-Reset
- Toolbar Row 2: B/I/U mit Material-Icons | L/C/R Align | x²/x₂ Sup/Sub
- Sonderzeichen-Palette: 41 Unicode-Symbole (Architektur/Math/Pfeile/
  Auszeichnungen), Klick inserted am Cursor
- WYSIWYG-Editor: contentEditable div mit fontFamily=ausgewaehlt,
  textAlign=ausgewaehlt — Format-Toolbar wirkt via document.execCommand
  (bold/italic/underline/justifyLeft/Center/Right/superscript/subscript/
  fontName/foreColor)
- "Einfuegen" sendet COMMIT mit text (innerText) + settings
- "Abbrechen" CANCEL → Bridge schliesst Form

Phase 1 Limitation: rendert in Rhino als PlainText mit den globalen
Settings (font/size/bold/italic/align/color) — verschiedene Schriftarten
INNERHALB des Texts sind im Editor sichtbar aber nicht im finalen
Rhino-TextEntity. Phase 2: HTML → Rhino RichText/RTF Mapping.

rhinoBridge.js: send() jetzt exportiert (war intern) damit
TextEditorApp generisch COMMIT/CANCEL senden kann.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 01:28:26 +02:00