diff --git a/rhino/text_create.py b/rhino/text_create.py
index 7376f3c..4603f42 100644
--- a/rhino/text_create.py
+++ b/rhino/text_create.py
@@ -799,6 +799,22 @@ def _apply_align(te, align):
return False
+def _apply_valign(te, valign):
+ """Setzt TextVerticalAlignment (Top/Middle/Bottom)."""
+ try:
+ m = {
+ "top": Rhino.DocObjects.TextVerticalAlignment.Top,
+ "middle": Rhino.DocObjects.TextVerticalAlignment.Middle,
+ "bottom": Rhino.DocObjects.TextVerticalAlignment.Bottom,
+ }
+ if valign in m:
+ te.TextVerticalAlignment = m[valign]
+ return True
+ except Exception as ex:
+ print("[TEXT] apply valign:", ex)
+ return False
+
+
def apply_settings_to_selection(doc, patch):
"""Wendet font/size/bold/italic/align auf alle selektierten
TextEntities an. Returns Anzahl der geaenderten Objekte."""
diff --git a/rhino/text_editor.py b/rhino/text_editor.py
index d94712d..789f840 100644
--- a/rhino/text_editor.py
+++ b/rhino/text_editor.py
@@ -162,6 +162,7 @@ class TextEditorBridge(panel_base.BaseBridge):
try: te.TextHeight = float(st.get("size") or 0.2)
except Exception: pass
text_create._apply_align(te, st.get("align") or "left")
+ text_create._apply_valign(te, st.get("valign") or "top")
# Content. Bei RichText KEIN _apply_font — sonst ueberschreibt
# te.Font die per-Run-Fonts aus der RTF. Stattdessen lassen
@@ -225,12 +226,27 @@ class TextEditorBridge(panel_base.BaseBridge):
)
except Exception as ex:
print("[TEXT-EDITOR] frame:", ex)
+ # Mask: Type entscheidet ob/wie maskiert wird. Margin gilt
+ # nur wenn Maske aktiv. Solid-Color erst dann setzen wenn
+ # Type=solid (sonst dominiert Viewport-Color).
try:
+ mask_type = (st.get("maskType") or "none").lower()
mask_m = float(st.get("maskMargin") or 0)
- if mask_m > 0:
+ if mask_type == "none":
+ te.MaskEnabled = False
+ else:
te.MaskEnabled = True
te.MaskOffset = mask_m
- te.MaskUsesViewportColor = True
+ if mask_type == "solid":
+ te.MaskUsesViewportColor = False
+ mc = st.get("maskColor") or [255, 255, 255]
+ try:
+ te.MaskColor = System.Drawing.Color.FromArgb(
+ int(mc[0]), int(mc[1]), int(mc[2]))
+ except Exception as ex:
+ print("[TEXT-EDITOR] mask color:", ex)
+ else:
+ te.MaskUsesViewportColor = True
except Exception as ex:
print("[TEXT-EDITOR] mask:", ex)
@@ -497,6 +513,25 @@ def open_for_edit(obj):
settings["align"] = "right"
else: settings["align"] = "left"
except Exception: pass
+ try:
+ v = te.TextVerticalAlignment
+ VA = Rhino.DocObjects.TextVerticalAlignment
+ if v == VA.Middle: settings["valign"] = "middle"
+ elif v == VA.Bottom: settings["valign"] = "bottom"
+ else: settings["valign"] = "top"
+ except Exception: pass
+ try:
+ if te.MaskEnabled:
+ settings["maskType"] = "solid" if not te.MaskUsesViewportColor else "viewport"
+ try: settings["maskMargin"] = float(te.MaskOffset)
+ except Exception: pass
+ try:
+ mc = te.MaskColor
+ settings["maskColor"] = [mc.R, mc.G, mc.B]
+ except Exception: pass
+ else:
+ settings["maskType"] = "none"
+ except Exception: pass
initial_text = ""
try: initial_text = te.PlainText or ""
diff --git a/src/TextEditorApp.jsx b/src/TextEditorApp.jsx
index 9da0ccb..189f590 100644
--- a/src/TextEditorApp.jsx
+++ b/src/TextEditorApp.jsx
@@ -247,6 +247,9 @@ export default function TextEditorApp() {
const [frame, setFrame] = useState('none') // none | rect | capsule
const [horizontalToView, setHorizontalToView] = useState(false)
const [rotation, setRotation] = useState(0)
+ const [valign, setVAlign] = useState('top') // top | middle | bottom
+ const [maskType, setMaskType] = useState('none') // none | viewport | solid
+ const [maskColor, setMaskColor] = useState([255, 255, 255])
const [maskMargin, setMaskMargin] = useState(0)
const [symbolsOpen, setSymbolsOpen] = useState(false)
const [styles, setStyles] = useState([])
@@ -324,6 +327,9 @@ export default function TextEditorApp() {
if (s.italic != null) setItalic(!!s.italic)
if (s.underline != null) setUnderline(!!s.underline)
if (s.align) setAlign(s.align)
+ if (s.valign) setVAlign(s.valign)
+ if (s.maskType) setMaskType(s.maskType)
+ if (Array.isArray(s.maskColor)) setMaskColor(s.maskColor)
// Bei Edit-Mode: bestehenden Text in den Editor laden. Wenn Runs
// persistiert sind (= reicher Format-Stand vom letzten Save),
// diese als HTML laden — sonst PlainText fallback.
@@ -420,6 +426,34 @@ export default function TextEditorApp() {
}
const clearColor = () => { setColor(null); exec('foreColor', '#000000') }
+ const onMaskColorPick = (hex) => {
+ const m = hex.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i)
+ if (m) setMaskColor([parseInt(m[1],16), parseInt(m[2],16), parseInt(m[3],16)])
+ if (maskType === 'none') setMaskType('solid')
+ }
+
+ // Case-Transformationen auf die aktuelle Selektion. Wenn nichts
+ // markiert ist, no-op (kein All-Text-Modus, sonst zu invasiv).
+ const transformCase = (mode) => {
+ restoreSelection()
+ const sel = window.getSelection()
+ if (!sel || sel.rangeCount === 0) return
+ const range = sel.getRangeAt(0)
+ if (range.collapsed) return
+ const src = range.toString()
+ let out = src
+ if (mode === 'upper') out = src.toUpperCase()
+ else if (mode === 'lower') out = src.toLowerCase()
+ else if (mode === 'capitalize') out = src.replace(/\w\S*/g, w =>
+ w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
+ else if (mode === 'invert') out = src.split('').map(ch =>
+ ch === ch.toUpperCase() ? ch.toLowerCase() : ch.toUpperCase()).join('')
+ document.execCommand('insertText', false, out)
+ editorRef.current?.focus()
+ }
+ const rgbToHex = ([r, g, b]) => '#' +
+ [r, g, b].map(n => Math.max(0, Math.min(255, n|0)).toString(16).padStart(2, '0')).join('')
+
const onCommit = () => {
const el = editorRef.current
if (!el) return
@@ -432,8 +466,9 @@ export default function TextEditorApp() {
text,
runs,
settings: {
- font, size, bold, italic, underline, align, color,
- frame, horizontalToView, rotation, maskMargin,
+ font, size, bold, italic, underline, align, valign, color,
+ frame, horizontalToView, rotation,
+ maskType, maskColor, maskMargin,
},
})
}
@@ -528,24 +563,68 @@ export default function TextEditorApp() {