Fields: stored values on redraw, evaluation events, per-field shading

MTEXT draws its stored text instead of re-evaluating fields on every
redraw. Fields update on their evaluation events as the reference does:
open (1), save (2, not autosave), plot (4) and REGEN/REGENALL (16), each
masked with the drawing's FIELDEVAL (variable dictionary, 0-31, with the
reference's prompt and message), and the new UPDATEFIELD command
(selection, attributes of selected block references, "N field(s)
found." / "N field(s) updated."). A Date field (on demand only) keeps
its value until UPDATEFIELD. The FIELDDISPLAY gray box now covers each
field's own glyphs, one box per line it spans, instead of the whole
text. Codec moved to the update_fields / field_spans revision.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-10-06 11:38:01 +03:00
1 parent 71cebbb3fe
commit 1c242eb489
13 files changed
+229 -19

No files matched your search

+25 -5
View File
@@ -84,10 +84,30 @@ impl FieldContext for OcsFieldContext<'_> {
}
}
/// Re-evaluate the field hosted by entity `host` (usually an MTEXT), or `None`
/// to keep the cached text. Thin wrapper over the library engine.
pub fn resolve(document: &CadDocument, host: Handle) -> Option<String> {
codec::fields::resolve(document, host, &OcsFieldContext(Some(document)))
/// FIELDEVAL of the drawing (kept in its variable dictionary; default 31):
/// the events that update fields — 1 open, 2 save, 4 plot, 8 eTransmit,
/// 16 regen.
pub fn fieldeval(document: &CadDocument) -> i32 {
crate::io::drawing_variable(document, "FIELDEVAL")
.and_then(|v| v.trim().parse().ok())
.unwrap_or(31)
}
/// Update the fields an event evaluates and store their values in the host
/// texts: `event` 1 open, 2 save or 16 regen (masked with FIELDEVAL), or 32
/// an explicit UPDATEFIELD. `hosts` limits it to those hosts. Returns the
/// hosts whose text changed and the number of fields the hosts hold.
pub fn update_fields(
document: &mut CadDocument,
event: i32,
hosts: Option<&[Handle]>,
) -> (Vec<Handle>, usize) {
let event = if event == 32 { event } else { event & fieldeval(document) };
if event == 0 || document.fields.is_empty() {
return (Vec::new(), 0);
}
let snapshot = document.clone();
document.update_fields(&OcsFieldContext(Some(&snapshot)), event, hosts)
}
pub fn resolve_handle(
@@ -388,7 +408,7 @@ pub fn stamp_plot_fields(document: &mut CadDocument) -> Vec<Handle> {
.objects
.values()
.any(|object| matches!(object, codec::objects::ObjectType::Field(_)));
if !has_fields {
if !has_fields || fieldeval(document) & 4 == 0 {
return Vec::new();
}
let snapshot = document.clone();
+6 -7
View File
@@ -82,11 +82,6 @@ fn drawing_dir_str(d: &DrawingDirection) -> &'static str {
/// Per-visible-character world-space boxes for the MText editor's
/// click-to-select preview. Uses the exact same layout opts as `to_render`
/// so the boxes line up with the rendered glyphs.
/// The MTEXT string to render: the live re-evaluated value when the entity
/// hosts a dynamic field, otherwise its stored (cached) value.
fn display_value(t: &MText, document: &codec::CadDocument) -> String {
crate::entities::field::resolve(document, t.common.handle).unwrap_or_else(|| t.value.clone())
}
pub fn glyph_boxes(t: &MText, document: &codec::CadDocument) -> Vec<GlyphBox> {
let resolved_style = resolve_text_style(&t.style, document);
@@ -115,7 +110,9 @@ pub fn glyph_boxes(t: &MText, document: &codec::CadDocument) -> Vec<GlyphBox> {
} else {
t.rotation as f32
};
let display = display_value(t, document);
// A field shows its stored value: fields update on their evaluation
// events (open, save, plot, regen, UPDATEFIELD), never on a redraw.
let display = t.value.clone();
let layout = layout_mtext(&MTextRenderOpts {
value: &display,
insertion: [
@@ -171,7 +168,9 @@ fn to_render(t: &MText, document: &codec::CadDocument) -> RenderEntity {
} else {
t.rotation as f32
};
let display = display_value(t, document);
// A field shows its stored value: fields update on their evaluation
// events (open, save, plot, regen, UPDATEFIELD), never on a redraw.
let display = t.value.clone();
let layout = layout_mtext(&MTextRenderOpts {
value: &display,
insertion: [
+16
View File
@@ -488,6 +488,22 @@ fn font_stem(name: &str) -> String {
.to_string()
}
/// How many inked glyphs (non-blank characters, stacked parts included) an
/// MTEXT string draws, in drawing order: the index of the first glyph of
/// what follows `s` in a longer string.
pub fn mtext_glyph_count(s: &str) -> usize {
let ink = |t: &str| t.chars().filter(|c| !c.is_whitespace()).count();
adapt_mtext_paragraphs(s, 1.0, false)
.iter()
.flat_map(|line| &line.runs)
.map(|run| match &run.kind {
MTextRunKind::Glyphs(text) => ink(text),
MTextRunKind::Tab => 0,
MTextRunKind::Stack { numerator, denominator, .. } => ink(numerator) + ink(denominator),
})
.sum()
}
/// Parse an MTEXT string into the layout's `Vec<MTextLine>`, using opencadcodec's
/// structured `mtext_format::parse_mtext` — OCS keeps only the layout engine
/// (`layout_mtext` and callers read `MTextLine`/`RunState`), not a second MTEXT