Text-Styling-Leiste wirkt auf selektierten Freitext/Textspalte

Die Text-Formatier-Gruppe der Oberleiste formatierte bisher nur den
Raum-Stempel. Jetzt ist auch ein selektierter Freitext/Textspalten-Element
(Drawing2D shape "text") ein Formatier-Ziel:
- Neues optionales Feld Drawing2D-Text.marks (Marks) für einheitliche
  Formatierung (Schriftfamilie, fett/kursiv/unterstrichen, Farbe).
- App.textTarget adaptiert den Text über docFromText/plainText an die
  Rich-Text-Leiste; die GRÖSSE bleibt bewusst über die Modell-Höhe (Meter),
  nicht über pt (massstabslos wäre falsch) — sizePt wird verworfen.
- generatePlan/PlanView rendern die Marks (fontFamily/-weight/-style/
  textDecoration); marks.color hat Vorrang vor der Kategorie-Farbe.
+3 Tests. tsc + vitest grün.
This commit is contained in:
2026-07-09 01:28:37 +02:00
parent 938d6421f9
commit d0b9d22141
5 changed files with 141 additions and 16 deletions
+13 -1
View File
@@ -3283,12 +3283,24 @@ function renderPrimitive(
} else {
lines.push(p.text);
}
// Einheitliche Formatierung (Text-Werkzeug + Oberleisten-Formatier-Gruppe):
// Schriftfamilie/fett/kursiv/Unterstreichung aus `marks`; Grösse bleibt über
// heightM (Modell-Meter). Farbe steckt bereits in p.color (marks.color-Vorrang
// in generatePlan aufgelöst).
const mk = p.marks;
const deco =
mk && (mk.underline || mk.strike)
? [mk.underline ? "underline" : "", mk.strike ? "line-through" : ""].join(" ").trim()
: undefined;
return (
<text
x={c.x}
y={c.y}
fontSize={fontSize}
fontFamily="var(--font-ui, sans-serif)"
fontFamily={mk?.font ?? "var(--font-ui, sans-serif)"}
fontWeight={mk?.bold ? 700 : undefined}
fontStyle={mk?.italic ? "italic" : undefined}
textDecoration={deco}
fill={p.color}
opacity={p.greyed ? 0.45 : undefined}
textAnchor="start"
+83
View File
@@ -0,0 +1,83 @@
// Freitext-Formatierung (Text-Werkzeug + Oberleisten-Formatier-Gruppe): die
// einheitlichen Marks (Schriftfamilie/fett/kursiv/Farbe) eines Drawing2D-Texts
// müssen ins `drawingText`-Primitiv wandern; marks.color hat Vorrang vor der
// Kategorie-/Elementfarbe.
import { describe, it, expect } from "vitest";
import { generatePlan } from "./generatePlan";
import type { Drawing2D, Project } from "../model/types";
function projectWithText(drawing: Drawing2D): Project {
return {
id: "p",
name: "T",
lineStyles: [{ id: "thin", name: "d", weight: 0.13, color: "#111", dash: null }],
hatches: [{ id: "none", name: "Ohne", pattern: "none", scale: 1, angle: 0, color: "#111" }],
components: [],
wallTypes: [],
drawingLevels: [
{
id: "eg",
name: "EG",
kind: "floor",
visible: true,
locked: false,
floorHeight: 2.6,
cutHeight: 1.0,
baseElevation: 0,
},
],
layers: [
{ code: "70", name: "Text", color: "#0a0a0a", lw: 0.3, visible: true, locked: false },
],
walls: [],
doors: [],
openings: [],
ceilings: [],
stairs: [],
rooms: [],
drawings2d: [drawing],
context: [],
} as unknown as Project;
}
const visible = new Set(["70"]);
function textPrim(drawing: Drawing2D) {
return generatePlan(projectWithText(drawing), "eg", visible, undefined, "mittel")
.primitives.find((p) => p.kind === "drawingText");
}
describe("Drawing2D-Text: Marks im drawingText-Primitiv", () => {
const base: Drawing2D = {
id: "TX1",
type: "drawing2d",
levelId: "eg",
categoryCode: "70",
geom: { shape: "text", at: { x: 1, y: 1 }, text: "Hallo", height: 0.2, angle: 0 },
};
it("trägt marks (font/bold/italic) ins Primitiv", () => {
const p = textPrim({
...base,
geom: { ...base.geom, marks: { font: "Merriweather", bold: true, italic: true } },
}) as { marks?: { font?: string; bold?: boolean; italic?: boolean } } | undefined;
expect(p?.marks?.font).toBe("Merriweather");
expect(p?.marks?.bold).toBe(true);
expect(p?.marks?.italic).toBe(true);
});
it("marks.color übersteuert die Kategorie-Farbe", () => {
const p = textPrim({
...base,
geom: { ...base.geom, marks: { color: "#ff0000" } },
}) as { color: string } | undefined;
expect(p?.color).toBe("#ff0000");
});
it("ohne marks bleibt das Primitiv unverändert (kein marks-Feld)", () => {
const p = textPrim(base) as { marks?: unknown } | undefined;
expect(p).toBeTruthy();
expect(p?.marks).toBeUndefined();
});
});
+6 -2
View File
@@ -44,7 +44,7 @@ import {
import { columnFootprint } from "../geometry/column";
import { effectiveOverrides, hasOverrides } from "../overrides/engine";
import { evaluateRoom, siaLabel } from "../geometry/roomArea";
import type { RichTextDoc } from "../text/richText";
import type { Marks, RichTextDoc } from "../text/richText";
import { docFromText } from "../text/richText";
import { roomStampToDoc, roomStampExtraLines } from "../model/roomStamp";
import type { RoomStampLine } from "../model/roomStamp";
@@ -335,6 +335,8 @@ export type Primitive =
/** Drehung in RADIANT (CCW im Modell). */
angle: number;
color: string;
/** Optionale einheitliche Formatierung (Schriftfamilie/fett/kursiv/…). */
marks?: Marks;
/**
* Optionale Spaltenbreite in Modell-Metern (Textspalte): ist sie gesetzt,
* bricht der Renderer den Text wortweise auf diese Breite um. Fehlt sie,
@@ -1289,8 +1291,10 @@ function addDrawing2D(
text: g.text,
heightM: g.height,
angle: g.angle,
color,
// marks.color (falls gesetzt) übersteuert die Kategorie-/Elementfarbe.
color: g.marks?.color ?? color,
...(g.width && g.width > 0 ? { wrapWidth: g.width } : {}),
...(g.marks ? { marks: g.marks } : {}),
greyed,
drawingId: d.id,
});