2026-04-02 23:11:09 +03:00
|
|
|
use acadrust::entities::Table;
|
|
|
|
|
use glam::Vec3;
|
|
|
|
|
|
|
|
|
|
use crate::command::EntityTransform;
|
|
|
|
|
use crate::entities::common::{ro_prop as ro, square_grip};
|
2026-06-11 00:04:37 +03:00
|
|
|
use crate::entities::text_support::{
|
|
|
|
|
layout_mtext, MTextRenderOpts, MTextVAnchor, ResolvedTextStyle,
|
|
|
|
|
};
|
2026-04-02 23:11:09 +03:00
|
|
|
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
|
2026-06-16 11:33:46 +03:00
|
|
|
use crate::scene::convert::acad_to_truck::{TruckEntity, TruckObject};
|
|
|
|
|
use crate::scene::model::object::{GripApply, GripDef, PropSection};
|
|
|
|
|
use crate::scene::view::transform;
|
|
|
|
|
use crate::scene::model::wire_model::SnapHint;
|
2026-04-02 23:11:09 +03:00
|
|
|
|
|
|
|
|
fn v3(v: &acadrust::types::Vector3) -> Vec3 {
|
|
|
|
|
Vec3::new(v.x as f32, v.y as f32, v.z as f32)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TruckConvertible for Table {
|
2026-05-21 12:38:03 +03:00
|
|
|
fn to_truck(&self, document: &acadrust::CadDocument) -> Option<TruckEntity> {
|
2026-04-02 23:11:09 +03:00
|
|
|
if self.rows.is_empty() || self.columns.is_empty() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-22 18:47:45 +03:00
|
|
|
// Lay the table out in a local frame with the origin at zero. The world
|
|
|
|
|
// insertion point is added back as f64 at the widening step below, so
|
|
|
|
|
// large coordinates (UTM etc.) keep full precision instead of snapping
|
|
|
|
|
// onto the coarse f32 grid — which would collide cell-text baselines and
|
|
|
|
|
// overflow the integer border-dedup keys.
|
|
|
|
|
let base = [
|
|
|
|
|
self.insertion_point.x,
|
|
|
|
|
self.insertion_point.y,
|
|
|
|
|
self.insertion_point.z,
|
|
|
|
|
];
|
|
|
|
|
let origin = Vec3::ZERO;
|
2026-04-02 23:11:09 +03:00
|
|
|
let h_raw = v3(&self.horizontal_direction);
|
2026-05-12 10:36:15 +03:00
|
|
|
let h = if h_raw.length_squared() > 1e-10 {
|
|
|
|
|
h_raw.normalize()
|
|
|
|
|
} else {
|
|
|
|
|
Vec3::X
|
|
|
|
|
};
|
2026-04-02 23:11:09 +03:00
|
|
|
// Perpendicular "down" direction in the drawing plane (tables grow downward)
|
|
|
|
|
let v_down = Vec3::new(h.y, -h.x, 0.0);
|
|
|
|
|
|
|
|
|
|
let col_offsets: Vec<f32> = {
|
|
|
|
|
let mut off = 0.0f32;
|
|
|
|
|
let mut v = vec![0.0f32];
|
|
|
|
|
for col in &self.columns {
|
|
|
|
|
off += col.width as f32;
|
|
|
|
|
v.push(off);
|
|
|
|
|
}
|
|
|
|
|
v
|
|
|
|
|
};
|
|
|
|
|
let total_w = *col_offsets.last().unwrap_or(&0.0);
|
|
|
|
|
|
|
|
|
|
let row_offsets: Vec<f32> = {
|
|
|
|
|
let mut off = 0.0f32;
|
|
|
|
|
let mut v = vec![0.0f32];
|
|
|
|
|
for row in &self.rows {
|
|
|
|
|
off += row.height as f32;
|
|
|
|
|
v.push(off);
|
|
|
|
|
}
|
|
|
|
|
v
|
|
|
|
|
};
|
|
|
|
|
let total_h = *row_offsets.last().unwrap_or(&0.0);
|
|
|
|
|
|
|
|
|
|
let mut pts: Vec<[f32; 3]> = Vec::new();
|
2026-06-25 18:17:01 +01:00
|
|
|
let mut tris_pts: Vec<[f32; 3]> = Vec::new();
|
2026-04-02 23:11:09 +03:00
|
|
|
|
feat(entities): integrate remaining fields on block-based entities
MultiLeader and Table were ignoring most of the styling and content
fields that the parser populated. Bring both up to file-driven render:
MultiLeader
- line_color: leader stroke / arrow colour with ByBlock/ByLayer fallback
- line_type_handle: dashed leader via line_types lookup by handle
- line_weight (ml.line_weight): per-leader pixel width override
- arrowhead_handle: arrow shape resolved through the dim arrow emitter
(Closed Filled / Open / Dot / Origin / Box / Datum / Tick / None)
- extend_leader_to_text: continue the leader past the landing to the
text insertion point
- text_angle_type: Horizontal forces 0, Optimized clamps to a readable
half-plane, ParallelToLastLeaderLine keeps the stored direction
- text_direction_negative: adds π to the rotation
- text_alignment: drives the horizontal anchor when more specific than
the context's text_attachment_point
- text_attachment_direction + text_top_attachment + text_bottom_attachment:
Vertical mode uses the top / bottom attachment values chosen from the
first root's direction.y sign
- has_block_contents + block_content_handle + block_content_location +
block_rotation + block_scale + block_content_color: synthesise an
Insert and explode it through the standard tessellator so block-
content MultiLeaders render their referenced block
- style_handle, arrowhead_handle, line_type_handle, text_style_handle,
block_content_handle, property_override_flags, text_align_in_ipe,
block_scale, block_rotation, extend_leader_to_text,
text_direction_negative, text_top_attachment, text_bottom_attachment
surfaced in the Properties panel
Table
- Per-cell borders: drop the always-grid pass, walk the cells and
emit each border honouring its `invisible` flag with dedup so shared
edges aren't doubled
- table_style_handle, block_record_handle, data_version, value_flags,
override_flag / override_border_* flags, break_options /
break_flow_direction / break_spacing, normal surfaced in the
Properties panel
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:17:05 +03:00
|
|
|
// Per-cell borders. When a cell carries a CellStyle, honour the
|
|
|
|
|
// visibility / `invisible` flag of each of its four borders so
|
|
|
|
|
// hidden borders disappear from the grid. Cells with no style still
|
|
|
|
|
// emit the standard four borders. To avoid drawing each shared edge
|
|
|
|
|
// twice we coalesce the segments by their (start, end) coordinates.
|
2026-06-05 00:35:53 +03:00
|
|
|
use rustc_hash::FxHashSet as HashSet;
|
|
|
|
|
let mut emitted: HashSet<(i32, i32, i32, i32)> = HashSet::default();
|
2026-06-11 00:04:37 +03:00
|
|
|
let try_add = |a: Vec3,
|
|
|
|
|
b: Vec3,
|
|
|
|
|
vis: bool,
|
|
|
|
|
emitted: &mut HashSet<(i32, i32, i32, i32)>,
|
|
|
|
|
pts: &mut Vec<[f32; 3]>| {
|
feat(entities): integrate remaining fields on block-based entities
MultiLeader and Table were ignoring most of the styling and content
fields that the parser populated. Bring both up to file-driven render:
MultiLeader
- line_color: leader stroke / arrow colour with ByBlock/ByLayer fallback
- line_type_handle: dashed leader via line_types lookup by handle
- line_weight (ml.line_weight): per-leader pixel width override
- arrowhead_handle: arrow shape resolved through the dim arrow emitter
(Closed Filled / Open / Dot / Origin / Box / Datum / Tick / None)
- extend_leader_to_text: continue the leader past the landing to the
text insertion point
- text_angle_type: Horizontal forces 0, Optimized clamps to a readable
half-plane, ParallelToLastLeaderLine keeps the stored direction
- text_direction_negative: adds π to the rotation
- text_alignment: drives the horizontal anchor when more specific than
the context's text_attachment_point
- text_attachment_direction + text_top_attachment + text_bottom_attachment:
Vertical mode uses the top / bottom attachment values chosen from the
first root's direction.y sign
- has_block_contents + block_content_handle + block_content_location +
block_rotation + block_scale + block_content_color: synthesise an
Insert and explode it through the standard tessellator so block-
content MultiLeaders render their referenced block
- style_handle, arrowhead_handle, line_type_handle, text_style_handle,
block_content_handle, property_override_flags, text_align_in_ipe,
block_scale, block_rotation, extend_leader_to_text,
text_direction_negative, text_top_attachment, text_bottom_attachment
surfaced in the Properties panel
Table
- Per-cell borders: drop the always-grid pass, walk the cells and
emit each border honouring its `invisible` flag with dedup so shared
edges aren't doubled
- table_style_handle, block_record_handle, data_version, value_flags,
override_flag / override_border_* flags, break_options /
break_flow_direction / break_spacing, normal surfaced in the
Properties panel
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:17:05 +03:00
|
|
|
if !vis {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let key = (
|
|
|
|
|
(a.x * 1_000.0) as i32,
|
|
|
|
|
(a.y * 1_000.0) as i32,
|
|
|
|
|
(b.x * 1_000.0) as i32,
|
|
|
|
|
(b.y * 1_000.0) as i32,
|
|
|
|
|
);
|
|
|
|
|
let key_rev = (key.2, key.3, key.0, key.1);
|
|
|
|
|
if emitted.contains(&key) || emitted.contains(&key_rev) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
emitted.insert(key);
|
2026-04-02 23:11:09 +03:00
|
|
|
if !pts.is_empty() {
|
|
|
|
|
pts.push([f32::NAN; 3]);
|
|
|
|
|
}
|
|
|
|
|
pts.push([a.x, a.y, a.z]);
|
|
|
|
|
pts.push([b.x, b.y, b.z]);
|
|
|
|
|
};
|
feat(entities): integrate remaining fields on block-based entities
MultiLeader and Table were ignoring most of the styling and content
fields that the parser populated. Bring both up to file-driven render:
MultiLeader
- line_color: leader stroke / arrow colour with ByBlock/ByLayer fallback
- line_type_handle: dashed leader via line_types lookup by handle
- line_weight (ml.line_weight): per-leader pixel width override
- arrowhead_handle: arrow shape resolved through the dim arrow emitter
(Closed Filled / Open / Dot / Origin / Box / Datum / Tick / None)
- extend_leader_to_text: continue the leader past the landing to the
text insertion point
- text_angle_type: Horizontal forces 0, Optimized clamps to a readable
half-plane, ParallelToLastLeaderLine keeps the stored direction
- text_direction_negative: adds π to the rotation
- text_alignment: drives the horizontal anchor when more specific than
the context's text_attachment_point
- text_attachment_direction + text_top_attachment + text_bottom_attachment:
Vertical mode uses the top / bottom attachment values chosen from the
first root's direction.y sign
- has_block_contents + block_content_handle + block_content_location +
block_rotation + block_scale + block_content_color: synthesise an
Insert and explode it through the standard tessellator so block-
content MultiLeaders render their referenced block
- style_handle, arrowhead_handle, line_type_handle, text_style_handle,
block_content_handle, property_override_flags, text_align_in_ipe,
block_scale, block_rotation, extend_leader_to_text,
text_direction_negative, text_top_attachment, text_bottom_attachment
surfaced in the Properties panel
Table
- Per-cell borders: drop the always-grid pass, walk the cells and
emit each border honouring its `invisible` flag with dedup so shared
edges aren't doubled
- table_style_handle, block_record_handle, data_version, value_flags,
override_flag / override_border_* flags, break_options /
break_flow_direction / break_spacing, normal surfaced in the
Properties panel
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:17:05 +03:00
|
|
|
for (ri, row) in self.rows.iter().enumerate() {
|
|
|
|
|
let row_top = row_offsets[ri];
|
|
|
|
|
let row_bot = row_offsets
|
|
|
|
|
.get(ri + 1)
|
|
|
|
|
.copied()
|
|
|
|
|
.unwrap_or(row_top + row.height as f32);
|
|
|
|
|
for (ci, cell) in row.cells.iter().enumerate() {
|
|
|
|
|
let col_left = col_offsets[ci];
|
2026-06-11 00:04:37 +03:00
|
|
|
let col_right = col_offsets.get(ci + 1).copied().unwrap_or(
|
|
|
|
|
col_left + self.columns.get(ci).map(|c| c.width as f32).unwrap_or(1.0),
|
|
|
|
|
);
|
feat(entities): integrate remaining fields on block-based entities
MultiLeader and Table were ignoring most of the styling and content
fields that the parser populated. Bring both up to file-driven render:
MultiLeader
- line_color: leader stroke / arrow colour with ByBlock/ByLayer fallback
- line_type_handle: dashed leader via line_types lookup by handle
- line_weight (ml.line_weight): per-leader pixel width override
- arrowhead_handle: arrow shape resolved through the dim arrow emitter
(Closed Filled / Open / Dot / Origin / Box / Datum / Tick / None)
- extend_leader_to_text: continue the leader past the landing to the
text insertion point
- text_angle_type: Horizontal forces 0, Optimized clamps to a readable
half-plane, ParallelToLastLeaderLine keeps the stored direction
- text_direction_negative: adds π to the rotation
- text_alignment: drives the horizontal anchor when more specific than
the context's text_attachment_point
- text_attachment_direction + text_top_attachment + text_bottom_attachment:
Vertical mode uses the top / bottom attachment values chosen from the
first root's direction.y sign
- has_block_contents + block_content_handle + block_content_location +
block_rotation + block_scale + block_content_color: synthesise an
Insert and explode it through the standard tessellator so block-
content MultiLeaders render their referenced block
- style_handle, arrowhead_handle, line_type_handle, text_style_handle,
block_content_handle, property_override_flags, text_align_in_ipe,
block_scale, block_rotation, extend_leader_to_text,
text_direction_negative, text_top_attachment, text_bottom_attachment
surfaced in the Properties panel
Table
- Per-cell borders: drop the always-grid pass, walk the cells and
emit each border honouring its `invisible` flag with dedup so shared
edges aren't doubled
- table_style_handle, block_record_handle, data_version, value_flags,
override_flag / override_border_* flags, break_options /
break_flow_direction / break_spacing, normal surfaced in the
Properties panel
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:17:05 +03:00
|
|
|
// Default to visible when no style override is present.
|
|
|
|
|
let (top_vis, right_vis, bottom_vis, left_vis) = cell
|
|
|
|
|
.style
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|s| {
|
|
|
|
|
(
|
|
|
|
|
!s.top_border.invisible,
|
|
|
|
|
!s.right_border.invisible,
|
|
|
|
|
!s.bottom_border.invisible,
|
|
|
|
|
!s.left_border.invisible,
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or((true, true, true, true));
|
|
|
|
|
let tl = origin + h * col_left + v_down * row_top;
|
|
|
|
|
let tr = origin + h * col_right + v_down * row_top;
|
|
|
|
|
let br_ = origin + h * col_right + v_down * row_bot;
|
|
|
|
|
let bl = origin + h * col_left + v_down * row_bot;
|
|
|
|
|
try_add(tl, tr, top_vis, &mut emitted, &mut pts);
|
|
|
|
|
try_add(tr, br_, right_vis, &mut emitted, &mut pts);
|
|
|
|
|
try_add(bl, br_, bottom_vis, &mut emitted, &mut pts);
|
|
|
|
|
try_add(tl, bl, left_vis, &mut emitted, &mut pts);
|
|
|
|
|
}
|
2026-04-02 23:11:09 +03:00
|
|
|
}
|
feat(entities): integrate remaining fields on block-based entities
MultiLeader and Table were ignoring most of the styling and content
fields that the parser populated. Bring both up to file-driven render:
MultiLeader
- line_color: leader stroke / arrow colour with ByBlock/ByLayer fallback
- line_type_handle: dashed leader via line_types lookup by handle
- line_weight (ml.line_weight): per-leader pixel width override
- arrowhead_handle: arrow shape resolved through the dim arrow emitter
(Closed Filled / Open / Dot / Origin / Box / Datum / Tick / None)
- extend_leader_to_text: continue the leader past the landing to the
text insertion point
- text_angle_type: Horizontal forces 0, Optimized clamps to a readable
half-plane, ParallelToLastLeaderLine keeps the stored direction
- text_direction_negative: adds π to the rotation
- text_alignment: drives the horizontal anchor when more specific than
the context's text_attachment_point
- text_attachment_direction + text_top_attachment + text_bottom_attachment:
Vertical mode uses the top / bottom attachment values chosen from the
first root's direction.y sign
- has_block_contents + block_content_handle + block_content_location +
block_rotation + block_scale + block_content_color: synthesise an
Insert and explode it through the standard tessellator so block-
content MultiLeaders render their referenced block
- style_handle, arrowhead_handle, line_type_handle, text_style_handle,
block_content_handle, property_override_flags, text_align_in_ipe,
block_scale, block_rotation, extend_leader_to_text,
text_direction_negative, text_top_attachment, text_bottom_attachment
surfaced in the Properties panel
Table
- Per-cell borders: drop the always-grid pass, walk the cells and
emit each border honouring its `invisible` flag with dedup so shared
edges aren't doubled
- table_style_handle, block_record_handle, data_version, value_flags,
override_flag / override_border_* flags, break_options /
break_flow_direction / break_spacing, normal surfaced in the
Properties panel
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:17:05 +03:00
|
|
|
// Suppress unused-variable warnings now that the simple grid-pass
|
|
|
|
|
// is gone — col/row offsets still feed cell drawing below.
|
|
|
|
|
let _ = (total_w, total_h);
|
2026-04-02 23:11:09 +03:00
|
|
|
|
2026-05-21 12:38:03 +03:00
|
|
|
// Cell text — resolve defaults via TableStyle, then layer per-cell
|
|
|
|
|
// overrides on top. Resolution order (text height, text style, alignment):
|
|
|
|
|
// 1. CellContent.* (per-content explicit override)
|
|
|
|
|
// 2. CellStyle.* (per-cell explicit override)
|
|
|
|
|
// 3. TableStyle.<row_kind>_row_style.* (table-wide default for this row class)
|
|
|
|
|
// 4. compiled-in fallback (0.18 / "txt" / MiddleCenter)
|
|
|
|
|
//
|
|
|
|
|
// Row classification: row 0 is Title (when not suppressed), row 1 is
|
|
|
|
|
// Header (when not suppressed), everything else is Data. The two
|
|
|
|
|
// suppressed flags shift the leading rows down to Data.
|
|
|
|
|
let lookup_style = |h: acadrust::Handle| -> Option<&acadrust::tables::TextStyle> {
|
|
|
|
|
document.text_styles.iter().find(|s| s.handle == h)
|
|
|
|
|
};
|
|
|
|
|
let table_style: Option<&acadrust::objects::TableStyle> =
|
|
|
|
|
self.table_style_handle.and_then(|h| {
|
|
|
|
|
document.objects.get(&h).and_then(|obj| match obj {
|
|
|
|
|
acadrust::objects::ObjectType::TableStyle(ts) => Some(ts),
|
|
|
|
|
_ => None,
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
let title_suppressed = table_style.map(|t| t.title_suppressed).unwrap_or(false);
|
|
|
|
|
let header_suppressed = table_style.map(|t| t.header_suppressed).unwrap_or(false);
|
|
|
|
|
|
|
|
|
|
let font_for_handle = |handle: Option<acadrust::Handle>| -> Option<String> {
|
|
|
|
|
handle.and_then(|h| lookup_style(h)).and_then(|s| {
|
2026-06-25 18:17:01 +01:00
|
|
|
let mut font_name = if !s.true_type_font.trim().is_empty() {
|
|
|
|
|
s.true_type_font.trim().to_string()
|
|
|
|
|
} else {
|
|
|
|
|
let file = s.font_file.trim();
|
|
|
|
|
if !file.is_empty() {
|
|
|
|
|
let basename = file.rsplit(['/', '\\']).next().unwrap_or(file);
|
|
|
|
|
let stem = basename.split('.').next().unwrap_or(basename).trim();
|
|
|
|
|
if !stem.is_empty() {
|
|
|
|
|
stem.to_string()
|
|
|
|
|
} else {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if !crate::scene::text::lff::is_builtin(&font_name) {
|
|
|
|
|
if let Some(canonical) = crate::scene::text::sysfont::canonical_family_name(&font_name) {
|
|
|
|
|
font_name = canonical;
|
2026-05-21 12:38:03 +03:00
|
|
|
}
|
|
|
|
|
}
|
2026-06-25 18:17:01 +01:00
|
|
|
Some(font_name)
|
2026-05-21 12:38:03 +03:00
|
|
|
})
|
|
|
|
|
};
|
feat(text): route every text-bearing entity through the run-aware MText pipeline
Lift the MText parser + renderer into a reusable `layout_mtext` helper in
`text_support` and re-point the remaining text-bearing entities at it, so
every inline format code (`\f`, `\C`/`\c`, `\H`, `\W`, `\Q`, `\T`, `\A`,
`\p…`, decorations, stacked fractions, paragraph indents, tab stops, …)
reaches the stroke output uniformly.
Per-entity changes:
- TABLE cell ([entities/table.rs]) — anchor + alignment math now feeds
`layout_mtext`; `\C7;Red`-style codes no longer render as literal
glyph noise.
- MLEADER MText content (both entity path in [entities/multileader.rs]
and the LOD-aware path in [scene/tessellate.rs]) — drops the
`strip_mtext_codes` + per-line `tessellate_text_ex` chain; the LOD 0
baseline / LOD 1 greek rect / LOD 2 stroke ladder and the text-frame /
bg-fill rectangle now read line widths from the layout.
- ATTRIB / ATTDEF — when `mtext_flag` is MultiLine / ConstantMultiLine
the value goes through the MText pipeline (SingleLine stays on the
Text-style anchor maths because the DXF spec disallows codes there).
- DIMENSION text_override — `dimension_text_entity` emits a synthetic
MText (instead of Text) whenever the value contains MText escapes;
the LOD ladder via `tessellate_entity_dim_text` handles both kinds.
Infrastructure:
- `MTextVAnchor` gains `MiddleOfTopLine`, `MiddleOfBottomLine`,
`BottomOfTopLine` so MLEADER's 9 attachment variants all map cleanly.
- `MTextLayout` returns `line_widths` / `line_height` / `v_offset` so
callers that draw chrome (frames, fills, baselines, greek rects)
share the same numbers as the rendered strokes.
- `mtext_line_count` re-routed through the new parser so LOD row counts
and rendered row counts can never disagree.
- Legacy helpers (`strip_mtext_codes`, `split_mtext_lines`,
`measure_mtext_chars`, `word_wrap`, `cxf::measure_text`, the
`v_offset_for_attachment` MLEADER offset table, `MTextLine::plain_text`)
deleted — all callers migrated.
Audit (TEXT_FORMAT_CODE_INTEGRATION.md) removed; its plan is implemented
here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 05:53:01 +03:00
|
|
|
// Build a ResolvedTextStyle for the cell — needed by the shared MText
|
|
|
|
|
// pipeline so inline `\W`, `\Q`, etc. compose with the style baseline.
|
2026-06-11 00:04:37 +03:00
|
|
|
let resolved_style_for_handle =
|
|
|
|
|
|handle: Option<acadrust::Handle>, font_name: String| -> ResolvedTextStyle {
|
|
|
|
|
let style = handle.and_then(|h| lookup_style(h));
|
|
|
|
|
ResolvedTextStyle {
|
|
|
|
|
font_name,
|
|
|
|
|
width_factor: style.map(|s| s.width_factor as f32).unwrap_or(1.0),
|
|
|
|
|
oblique_angle: style.map(|s| s.oblique_angle as f32).unwrap_or(0.0),
|
|
|
|
|
is_backward: style.map(|s| s.is_backward()).unwrap_or(false),
|
|
|
|
|
is_upside_down: style.map(|s| s.is_upside_down()).unwrap_or(false),
|
|
|
|
|
}
|
|
|
|
|
};
|
Feat: ROADMAP Phase 1-5 batch — trim/extend, plot scale, viewport UX, mline caps, table alignment
- TRIM/EXTEND: Ray and XLine entities supported as cutting boundaries and trim targets;
trim results in Ray→Line/Ray, XLine→Ray/Ray or Line; hover preview added
- Plot scale override: Fit/1:1/1:2/1:5/1:10/1:20/1:50/1:100/2:1 buttons added to Page Setup
panel; PageSetupCommit applies scale to PlotSettings
- Viewport UX: locked viewport gets orange border; selected viewport gets white border
- Named view → viewport assignment: Choice dropdown in properties panel copies
view_target/direction/height to the viewport entity
- MLine caps: perpendicular start/end cap lines added for open (non-closed) MLines
- Wipeout polygon grips: boundary vertices become editable grips when clipping_enabled+Polygonal;
world→pixel back-projection on drag
- Table cell alignment: CellStyle.alignment (1-9) drives left/center/right + top/middle/bottom
text positioning; cxf::measure_text() helper added
- New commands/features: DIMCONTINUE (DCO), DIMBASELINE (DBA), TOLERANCE (TOL), TABLE,
ATTDEF, ATTEDIT, ATTDISP, BREAKATPOINT (BAP), PLOTWINDOW (PW), MLINE (ML);
DIMSTYLE/DIMCURRENT/CLAYER/LTSCALE/CELTSCALE/SCALETEXT/XDATA/UCSICON/REGEN/DRAWORDER extended
- PLINE arc mode: A/L/CLOSE text input, bulge computation, arc preview
- Warning cleanup: removed unused imports and unreachable patterns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:31:05 +03:00
|
|
|
|
2026-04-02 23:11:09 +03:00
|
|
|
for (ri, row) in self.rows.iter().enumerate() {
|
Feat: ROADMAP Phase 1-5 batch — trim/extend, plot scale, viewport UX, mline caps, table alignment
- TRIM/EXTEND: Ray and XLine entities supported as cutting boundaries and trim targets;
trim results in Ray→Line/Ray, XLine→Ray/Ray or Line; hover preview added
- Plot scale override: Fit/1:1/1:2/1:5/1:10/1:20/1:50/1:100/2:1 buttons added to Page Setup
panel; PageSetupCommit applies scale to PlotSettings
- Viewport UX: locked viewport gets orange border; selected viewport gets white border
- Named view → viewport assignment: Choice dropdown in properties panel copies
view_target/direction/height to the viewport entity
- MLine caps: perpendicular start/end cap lines added for open (non-closed) MLines
- Wipeout polygon grips: boundary vertices become editable grips when clipping_enabled+Polygonal;
world→pixel back-projection on drag
- Table cell alignment: CellStyle.alignment (1-9) drives left/center/right + top/middle/bottom
text positioning; cxf::measure_text() helper added
- New commands/features: DIMCONTINUE (DCO), DIMBASELINE (DBA), TOLERANCE (TOL), TABLE,
ATTDEF, ATTEDIT, ATTDISP, BREAKATPOINT (BAP), PLOTWINDOW (PW), MLINE (ML);
DIMSTYLE/DIMCURRENT/CLAYER/LTSCALE/CELTSCALE/SCALETEXT/XDATA/UCSICON/REGEN/DRAWORDER extended
- PLINE arc mode: A/L/CLOSE text input, bulge computation, arc preview
- Warning cleanup: removed unused imports and unreachable patterns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:31:05 +03:00
|
|
|
let row_top = row_offsets[ri];
|
2026-05-12 10:36:15 +03:00
|
|
|
let row_bot = row_offsets
|
|
|
|
|
.get(ri + 1)
|
|
|
|
|
.copied()
|
|
|
|
|
.unwrap_or(row_top + row.height as f32);
|
Feat: ROADMAP Phase 1-5 batch — trim/extend, plot scale, viewport UX, mline caps, table alignment
- TRIM/EXTEND: Ray and XLine entities supported as cutting boundaries and trim targets;
trim results in Ray→Line/Ray, XLine→Ray/Ray or Line; hover preview added
- Plot scale override: Fit/1:1/1:2/1:5/1:10/1:20/1:50/1:100/2:1 buttons added to Page Setup
panel; PageSetupCommit applies scale to PlotSettings
- Viewport UX: locked viewport gets orange border; selected viewport gets white border
- Named view → viewport assignment: Choice dropdown in properties panel copies
view_target/direction/height to the viewport entity
- MLine caps: perpendicular start/end cap lines added for open (non-closed) MLines
- Wipeout polygon grips: boundary vertices become editable grips when clipping_enabled+Polygonal;
world→pixel back-projection on drag
- Table cell alignment: CellStyle.alignment (1-9) drives left/center/right + top/middle/bottom
text positioning; cxf::measure_text() helper added
- New commands/features: DIMCONTINUE (DCO), DIMBASELINE (DBA), TOLERANCE (TOL), TABLE,
ATTDEF, ATTEDIT, ATTDISP, BREAKATPOINT (BAP), PLOTWINDOW (PW), MLINE (ML);
DIMSTYLE/DIMCURRENT/CLAYER/LTSCALE/CELTSCALE/SCALETEXT/XDATA/UCSICON/REGEN/DRAWORDER extended
- PLINE arc mode: A/L/CLOSE text input, bulge computation, arc preview
- Warning cleanup: removed unused imports and unreachable patterns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:31:05 +03:00
|
|
|
let row_mid = (row_top + row_bot) * 0.5;
|
2026-04-02 23:11:09 +03:00
|
|
|
|
2026-05-21 12:38:03 +03:00
|
|
|
// Pick the appropriate row_style from TableStyle for this row's role.
|
2026-06-11 00:04:37 +03:00
|
|
|
let row_style: Option<&acadrust::objects::RowCellStyle> = table_style.map(|ts| {
|
|
|
|
|
let kind = match (title_suppressed, header_suppressed, ri) {
|
|
|
|
|
(false, _, 0) => 0, // title
|
|
|
|
|
(false, false, 1) => 1, // header
|
|
|
|
|
(true, false, 0) => 1, // header pulled up
|
|
|
|
|
_ => 2, // data
|
|
|
|
|
};
|
|
|
|
|
match kind {
|
|
|
|
|
0 => &ts.title_row_style,
|
|
|
|
|
1 => &ts.header_row_style,
|
|
|
|
|
_ => &ts.data_row_style,
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-05-21 12:38:03 +03:00
|
|
|
|
2026-04-02 23:11:09 +03:00
|
|
|
for (ci, cell) in row.cells.iter().enumerate() {
|
|
|
|
|
let text = cell.text_value();
|
|
|
|
|
if text.is_empty() {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
Feat: ROADMAP Phase 1-5 batch — trim/extend, plot scale, viewport UX, mline caps, table alignment
- TRIM/EXTEND: Ray and XLine entities supported as cutting boundaries and trim targets;
trim results in Ray→Line/Ray, XLine→Ray/Ray or Line; hover preview added
- Plot scale override: Fit/1:1/1:2/1:5/1:10/1:20/1:50/1:100/2:1 buttons added to Page Setup
panel; PageSetupCommit applies scale to PlotSettings
- Viewport UX: locked viewport gets orange border; selected viewport gets white border
- Named view → viewport assignment: Choice dropdown in properties panel copies
view_target/direction/height to the viewport entity
- MLine caps: perpendicular start/end cap lines added for open (non-closed) MLines
- Wipeout polygon grips: boundary vertices become editable grips when clipping_enabled+Polygonal;
world→pixel back-projection on drag
- Table cell alignment: CellStyle.alignment (1-9) drives left/center/right + top/middle/bottom
text positioning; cxf::measure_text() helper added
- New commands/features: DIMCONTINUE (DCO), DIMBASELINE (DBA), TOLERANCE (TOL), TABLE,
ATTDEF, ATTEDIT, ATTDISP, BREAKATPOINT (BAP), PLOTWINDOW (PW), MLINE (ML);
DIMSTYLE/DIMCURRENT/CLAYER/LTSCALE/CELTSCALE/SCALETEXT/XDATA/UCSICON/REGEN/DRAWORDER extended
- PLINE arc mode: A/L/CLOSE text input, bulge computation, arc preview
- Warning cleanup: removed unused imports and unreachable patterns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:31:05 +03:00
|
|
|
|
2026-05-12 10:36:15 +03:00
|
|
|
let col_left = col_offsets[ci];
|
Feat: ROADMAP Phase 1-5 batch — trim/extend, plot scale, viewport UX, mline caps, table alignment
- TRIM/EXTEND: Ray and XLine entities supported as cutting boundaries and trim targets;
trim results in Ray→Line/Ray, XLine→Ray/Ray or Line; hover preview added
- Plot scale override: Fit/1:1/1:2/1:5/1:10/1:20/1:50/1:100/2:1 buttons added to Page Setup
panel; PageSetupCommit applies scale to PlotSettings
- Viewport UX: locked viewport gets orange border; selected viewport gets white border
- Named view → viewport assignment: Choice dropdown in properties panel copies
view_target/direction/height to the viewport entity
- MLine caps: perpendicular start/end cap lines added for open (non-closed) MLines
- Wipeout polygon grips: boundary vertices become editable grips when clipping_enabled+Polygonal;
world→pixel back-projection on drag
- Table cell alignment: CellStyle.alignment (1-9) drives left/center/right + top/middle/bottom
text positioning; cxf::measure_text() helper added
- New commands/features: DIMCONTINUE (DCO), DIMBASELINE (DBA), TOLERANCE (TOL), TABLE,
ATTDEF, ATTEDIT, ATTDISP, BREAKATPOINT (BAP), PLOTWINDOW (PW), MLINE (ML);
DIMSTYLE/DIMCURRENT/CLAYER/LTSCALE/CELTSCALE/SCALETEXT/XDATA/UCSICON/REGEN/DRAWORDER extended
- PLINE arc mode: A/L/CLOSE text input, bulge computation, arc preview
- Warning cleanup: removed unused imports and unreachable patterns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:31:05 +03:00
|
|
|
let col_width = self.columns.get(ci).map(|c| c.width as f32).unwrap_or(1.0);
|
|
|
|
|
let col_right = col_left + col_width;
|
|
|
|
|
|
2026-05-21 12:38:03 +03:00
|
|
|
// Resolve text height: content → cell-style → row-style → 0.18.
|
|
|
|
|
let content = cell.contents.first();
|
|
|
|
|
let cell_h = content
|
|
|
|
|
.map(|c| c.text_height)
|
|
|
|
|
.filter(|h| *h > 1e-6)
|
2026-06-11 00:04:37 +03:00
|
|
|
.or_else(|| {
|
|
|
|
|
cell.style
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|s| s.text_height)
|
|
|
|
|
.filter(|h| *h > 1e-6)
|
|
|
|
|
})
|
2026-05-21 12:38:03 +03:00
|
|
|
.or_else(|| row_style.map(|s| s.text_height).filter(|h| *h > 1e-6))
|
|
|
|
|
.map(|h| h as f32)
|
|
|
|
|
.unwrap_or(0.18);
|
|
|
|
|
let margin = cell_h * 0.5_f32;
|
|
|
|
|
|
|
|
|
|
// Resolve text-style handle: content → cell-style → row-style.
|
|
|
|
|
let style_handle = content
|
|
|
|
|
.and_then(|c| c.text_style_handle)
|
|
|
|
|
.or_else(|| cell.style.as_ref().and_then(|s| s.text_style_handle))
|
|
|
|
|
.or_else(|| row_style.and_then(|s| s.text_style_handle));
|
2026-06-11 00:04:37 +03:00
|
|
|
let font_owned = font_for_handle(style_handle).unwrap_or_else(|| "txt".to_string());
|
feat(text): route every text-bearing entity through the run-aware MText pipeline
Lift the MText parser + renderer into a reusable `layout_mtext` helper in
`text_support` and re-point the remaining text-bearing entities at it, so
every inline format code (`\f`, `\C`/`\c`, `\H`, `\W`, `\Q`, `\T`, `\A`,
`\p…`, decorations, stacked fractions, paragraph indents, tab stops, …)
reaches the stroke output uniformly.
Per-entity changes:
- TABLE cell ([entities/table.rs]) — anchor + alignment math now feeds
`layout_mtext`; `\C7;Red`-style codes no longer render as literal
glyph noise.
- MLEADER MText content (both entity path in [entities/multileader.rs]
and the LOD-aware path in [scene/tessellate.rs]) — drops the
`strip_mtext_codes` + per-line `tessellate_text_ex` chain; the LOD 0
baseline / LOD 1 greek rect / LOD 2 stroke ladder and the text-frame /
bg-fill rectangle now read line widths from the layout.
- ATTRIB / ATTDEF — when `mtext_flag` is MultiLine / ConstantMultiLine
the value goes through the MText pipeline (SingleLine stays on the
Text-style anchor maths because the DXF spec disallows codes there).
- DIMENSION text_override — `dimension_text_entity` emits a synthetic
MText (instead of Text) whenever the value contains MText escapes;
the LOD ladder via `tessellate_entity_dim_text` handles both kinds.
Infrastructure:
- `MTextVAnchor` gains `MiddleOfTopLine`, `MiddleOfBottomLine`,
`BottomOfTopLine` so MLEADER's 9 attachment variants all map cleanly.
- `MTextLayout` returns `line_widths` / `line_height` / `v_offset` so
callers that draw chrome (frames, fills, baselines, greek rects)
share the same numbers as the rendered strokes.
- `mtext_line_count` re-routed through the new parser so LOD row counts
and rendered row counts can never disagree.
- Legacy helpers (`strip_mtext_codes`, `split_mtext_lines`,
`measure_mtext_chars`, `word_wrap`, `cxf::measure_text`, the
`v_offset_for_attachment` MLEADER offset table, `MTextLine::plain_text`)
deleted — all callers migrated.
Audit (TEXT_FORMAT_CODE_INTEGRATION.md) removed; its plan is implemented
here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 05:53:01 +03:00
|
|
|
let resolved = resolved_style_for_handle(style_handle, font_owned);
|
2026-05-21 12:38:03 +03:00
|
|
|
|
|
|
|
|
// Alignment resolution: cell.style.alignment (1-9) overrides;
|
|
|
|
|
// otherwise fall back to row_style.alignment, then MiddleCenter.
|
|
|
|
|
let align = cell
|
|
|
|
|
.style
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|s| s.alignment)
|
|
|
|
|
.filter(|a| *a != 0)
|
|
|
|
|
.or_else(|| row_style.map(|s| s.alignment as i32))
|
|
|
|
|
.unwrap_or(5);
|
Feat: ROADMAP Phase 1-5 batch — trim/extend, plot scale, viewport UX, mline caps, table alignment
- TRIM/EXTEND: Ray and XLine entities supported as cutting boundaries and trim targets;
trim results in Ray→Line/Ray, XLine→Ray/Ray or Line; hover preview added
- Plot scale override: Fit/1:1/1:2/1:5/1:10/1:20/1:50/1:100/2:1 buttons added to Page Setup
panel; PageSetupCommit applies scale to PlotSettings
- Viewport UX: locked viewport gets orange border; selected viewport gets white border
- Named view → viewport assignment: Choice dropdown in properties panel copies
view_target/direction/height to the viewport entity
- MLine caps: perpendicular start/end cap lines added for open (non-closed) MLines
- Wipeout polygon grips: boundary vertices become editable grips when clipping_enabled+Polygonal;
world→pixel back-projection on drag
- Table cell alignment: CellStyle.alignment (1-9) drives left/center/right + top/middle/bottom
text positioning; cxf::measure_text() helper added
- New commands/features: DIMCONTINUE (DCO), DIMBASELINE (DBA), TOLERANCE (TOL), TABLE,
ATTDEF, ATTEDIT, ATTDISP, BREAKATPOINT (BAP), PLOTWINDOW (PW), MLINE (ML);
DIMSTYLE/DIMCURRENT/CLAYER/LTSCALE/CELTSCALE/SCALETEXT/XDATA/UCSICON/REGEN/DRAWORDER extended
- PLINE arc mode: A/L/CLOSE text input, bulge computation, arc preview
- Warning cleanup: removed unused imports and unreachable patterns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:31:05 +03:00
|
|
|
let horiz = ((align - 1).rem_euclid(3)) + 1; // 1=left, 2=center, 3=right
|
2026-05-12 10:36:15 +03:00
|
|
|
let vert = ((align - 1) / 3) + 1; // 1=top, 2=middle, 3=bottom
|
Feat: ROADMAP Phase 1-5 batch — trim/extend, plot scale, viewport UX, mline caps, table alignment
- TRIM/EXTEND: Ray and XLine entities supported as cutting boundaries and trim targets;
trim results in Ray→Line/Ray, XLine→Ray/Ray or Line; hover preview added
- Plot scale override: Fit/1:1/1:2/1:5/1:10/1:20/1:50/1:100/2:1 buttons added to Page Setup
panel; PageSetupCommit applies scale to PlotSettings
- Viewport UX: locked viewport gets orange border; selected viewport gets white border
- Named view → viewport assignment: Choice dropdown in properties panel copies
view_target/direction/height to the viewport entity
- MLine caps: perpendicular start/end cap lines added for open (non-closed) MLines
- Wipeout polygon grips: boundary vertices become editable grips when clipping_enabled+Polygonal;
world→pixel back-projection on drag
- Table cell alignment: CellStyle.alignment (1-9) drives left/center/right + top/middle/bottom
text positioning; cxf::measure_text() helper added
- New commands/features: DIMCONTINUE (DCO), DIMBASELINE (DBA), TOLERANCE (TOL), TABLE,
ATTDEF, ATTEDIT, ATTDISP, BREAKATPOINT (BAP), PLOTWINDOW (PW), MLINE (ML);
DIMSTYLE/DIMCURRENT/CLAYER/LTSCALE/CELTSCALE/SCALETEXT/XDATA/UCSICON/REGEN/DRAWORDER extended
- PLINE arc mode: A/L/CLOSE text input, bulge computation, arc preview
- Warning cleanup: removed unused imports and unreachable patterns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:31:05 +03:00
|
|
|
|
feat(text): route every text-bearing entity through the run-aware MText pipeline
Lift the MText parser + renderer into a reusable `layout_mtext` helper in
`text_support` and re-point the remaining text-bearing entities at it, so
every inline format code (`\f`, `\C`/`\c`, `\H`, `\W`, `\Q`, `\T`, `\A`,
`\p…`, decorations, stacked fractions, paragraph indents, tab stops, …)
reaches the stroke output uniformly.
Per-entity changes:
- TABLE cell ([entities/table.rs]) — anchor + alignment math now feeds
`layout_mtext`; `\C7;Red`-style codes no longer render as literal
glyph noise.
- MLEADER MText content (both entity path in [entities/multileader.rs]
and the LOD-aware path in [scene/tessellate.rs]) — drops the
`strip_mtext_codes` + per-line `tessellate_text_ex` chain; the LOD 0
baseline / LOD 1 greek rect / LOD 2 stroke ladder and the text-frame /
bg-fill rectangle now read line widths from the layout.
- ATTRIB / ATTDEF — when `mtext_flag` is MultiLine / ConstantMultiLine
the value goes through the MText pipeline (SingleLine stays on the
Text-style anchor maths because the DXF spec disallows codes there).
- DIMENSION text_override — `dimension_text_entity` emits a synthetic
MText (instead of Text) whenever the value contains MText escapes;
the LOD ladder via `tessellate_entity_dim_text` handles both kinds.
Infrastructure:
- `MTextVAnchor` gains `MiddleOfTopLine`, `MiddleOfBottomLine`,
`BottomOfTopLine` so MLEADER's 9 attachment variants all map cleanly.
- `MTextLayout` returns `line_widths` / `line_height` / `v_offset` so
callers that draw chrome (frames, fills, baselines, greek rects)
share the same numbers as the rendered strokes.
- `mtext_line_count` re-routed through the new parser so LOD row counts
and rendered row counts can never disagree.
- Legacy helpers (`strip_mtext_codes`, `split_mtext_lines`,
`measure_mtext_chars`, `word_wrap`, `cxf::measure_text`, the
`v_offset_for_attachment` MLEADER offset table, `MTextLine::plain_text`)
deleted — all callers migrated.
Audit (TEXT_FORMAT_CODE_INTEGRATION.md) removed; its plan is implemented
here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 05:53:01 +03:00
|
|
|
// Position the cell's MText block anchor at the requested
|
|
|
|
|
// alignment corner / midpoint of the cell's content area.
|
|
|
|
|
let (x_offset, attach_h_anchor) = match horiz {
|
|
|
|
|
1 => (col_left + margin, 0.0_f32),
|
|
|
|
|
3 => (col_right - margin, 1.0_f32),
|
|
|
|
|
_ => (col_left + col_width * 0.5, 0.5_f32),
|
Feat: ROADMAP Phase 1-5 batch — trim/extend, plot scale, viewport UX, mline caps, table alignment
- TRIM/EXTEND: Ray and XLine entities supported as cutting boundaries and trim targets;
trim results in Ray→Line/Ray, XLine→Ray/Ray or Line; hover preview added
- Plot scale override: Fit/1:1/1:2/1:5/1:10/1:20/1:50/1:100/2:1 buttons added to Page Setup
panel; PageSetupCommit applies scale to PlotSettings
- Viewport UX: locked viewport gets orange border; selected viewport gets white border
- Named view → viewport assignment: Choice dropdown in properties panel copies
view_target/direction/height to the viewport entity
- MLine caps: perpendicular start/end cap lines added for open (non-closed) MLines
- Wipeout polygon grips: boundary vertices become editable grips when clipping_enabled+Polygonal;
world→pixel back-projection on drag
- Table cell alignment: CellStyle.alignment (1-9) drives left/center/right + top/middle/bottom
text positioning; cxf::measure_text() helper added
- New commands/features: DIMCONTINUE (DCO), DIMBASELINE (DBA), TOLERANCE (TOL), TABLE,
ATTDEF, ATTEDIT, ATTDISP, BREAKATPOINT (BAP), PLOTWINDOW (PW), MLINE (ML);
DIMSTYLE/DIMCURRENT/CLAYER/LTSCALE/CELTSCALE/SCALETEXT/XDATA/UCSICON/REGEN/DRAWORDER extended
- PLINE arc mode: A/L/CLOSE text input, bulge computation, arc preview
- Warning cleanup: removed unused imports and unreachable patterns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:31:05 +03:00
|
|
|
};
|
feat(text): route every text-bearing entity through the run-aware MText pipeline
Lift the MText parser + renderer into a reusable `layout_mtext` helper in
`text_support` and re-point the remaining text-bearing entities at it, so
every inline format code (`\f`, `\C`/`\c`, `\H`, `\W`, `\Q`, `\T`, `\A`,
`\p…`, decorations, stacked fractions, paragraph indents, tab stops, …)
reaches the stroke output uniformly.
Per-entity changes:
- TABLE cell ([entities/table.rs]) — anchor + alignment math now feeds
`layout_mtext`; `\C7;Red`-style codes no longer render as literal
glyph noise.
- MLEADER MText content (both entity path in [entities/multileader.rs]
and the LOD-aware path in [scene/tessellate.rs]) — drops the
`strip_mtext_codes` + per-line `tessellate_text_ex` chain; the LOD 0
baseline / LOD 1 greek rect / LOD 2 stroke ladder and the text-frame /
bg-fill rectangle now read line widths from the layout.
- ATTRIB / ATTDEF — when `mtext_flag` is MultiLine / ConstantMultiLine
the value goes through the MText pipeline (SingleLine stays on the
Text-style anchor maths because the DXF spec disallows codes there).
- DIMENSION text_override — `dimension_text_entity` emits a synthetic
MText (instead of Text) whenever the value contains MText escapes;
the LOD ladder via `tessellate_entity_dim_text` handles both kinds.
Infrastructure:
- `MTextVAnchor` gains `MiddleOfTopLine`, `MiddleOfBottomLine`,
`BottomOfTopLine` so MLEADER's 9 attachment variants all map cleanly.
- `MTextLayout` returns `line_widths` / `line_height` / `v_offset` so
callers that draw chrome (frames, fills, baselines, greek rects)
share the same numbers as the rendered strokes.
- `mtext_line_count` re-routed through the new parser so LOD row counts
and rendered row counts can never disagree.
- Legacy helpers (`strip_mtext_codes`, `split_mtext_lines`,
`measure_mtext_chars`, `word_wrap`, `cxf::measure_text`, the
`v_offset_for_attachment` MLEADER offset table, `MTextLine::plain_text`)
deleted — all callers migrated.
Audit (TEXT_FORMAT_CODE_INTEGRATION.md) removed; its plan is implemented
here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 05:53:01 +03:00
|
|
|
let (y_offset, v_anchor) = match vert {
|
|
|
|
|
1 => (row_top + margin, MTextVAnchor::Top),
|
|
|
|
|
3 => (row_bot - margin, MTextVAnchor::Bottom),
|
|
|
|
|
_ => (row_mid, MTextVAnchor::Middle),
|
Feat: ROADMAP Phase 1-5 batch — trim/extend, plot scale, viewport UX, mline caps, table alignment
- TRIM/EXTEND: Ray and XLine entities supported as cutting boundaries and trim targets;
trim results in Ray→Line/Ray, XLine→Ray/Ray or Line; hover preview added
- Plot scale override: Fit/1:1/1:2/1:5/1:10/1:20/1:50/1:100/2:1 buttons added to Page Setup
panel; PageSetupCommit applies scale to PlotSettings
- Viewport UX: locked viewport gets orange border; selected viewport gets white border
- Named view → viewport assignment: Choice dropdown in properties panel copies
view_target/direction/height to the viewport entity
- MLine caps: perpendicular start/end cap lines added for open (non-closed) MLines
- Wipeout polygon grips: boundary vertices become editable grips when clipping_enabled+Polygonal;
world→pixel back-projection on drag
- Table cell alignment: CellStyle.alignment (1-9) drives left/center/right + top/middle/bottom
text positioning; cxf::measure_text() helper added
- New commands/features: DIMCONTINUE (DCO), DIMBASELINE (DBA), TOLERANCE (TOL), TABLE,
ATTDEF, ATTEDIT, ATTDISP, BREAKATPOINT (BAP), PLOTWINDOW (PW), MLINE (ML);
DIMSTYLE/DIMCURRENT/CLAYER/LTSCALE/CELTSCALE/SCALETEXT/XDATA/UCSICON/REGEN/DRAWORDER extended
- PLINE arc mode: A/L/CLOSE text input, bulge computation, arc preview
- Warning cleanup: removed unused imports and unreachable patterns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:31:05 +03:00
|
|
|
};
|
|
|
|
|
let text_origin = origin + h * x_offset + v_down * y_offset;
|
2026-04-02 23:11:09 +03:00
|
|
|
|
2026-05-21 12:38:03 +03:00
|
|
|
// Content rotation (radians) on top of table cell rotation.
|
2026-06-11 00:04:37 +03:00
|
|
|
let rot = content.map(|c| c.rotation as f32).unwrap_or(0.0) + cell.rotation as f32;
|
feat(text): route every text-bearing entity through the run-aware MText pipeline
Lift the MText parser + renderer into a reusable `layout_mtext` helper in
`text_support` and re-point the remaining text-bearing entities at it, so
every inline format code (`\f`, `\C`/`\c`, `\H`, `\W`, `\Q`, `\T`, `\A`,
`\p…`, decorations, stacked fractions, paragraph indents, tab stops, …)
reaches the stroke output uniformly.
Per-entity changes:
- TABLE cell ([entities/table.rs]) — anchor + alignment math now feeds
`layout_mtext`; `\C7;Red`-style codes no longer render as literal
glyph noise.
- MLEADER MText content (both entity path in [entities/multileader.rs]
and the LOD-aware path in [scene/tessellate.rs]) — drops the
`strip_mtext_codes` + per-line `tessellate_text_ex` chain; the LOD 0
baseline / LOD 1 greek rect / LOD 2 stroke ladder and the text-frame /
bg-fill rectangle now read line widths from the layout.
- ATTRIB / ATTDEF — when `mtext_flag` is MultiLine / ConstantMultiLine
the value goes through the MText pipeline (SingleLine stays on the
Text-style anchor maths because the DXF spec disallows codes there).
- DIMENSION text_override — `dimension_text_entity` emits a synthetic
MText (instead of Text) whenever the value contains MText escapes;
the LOD ladder via `tessellate_entity_dim_text` handles both kinds.
Infrastructure:
- `MTextVAnchor` gains `MiddleOfTopLine`, `MiddleOfBottomLine`,
`BottomOfTopLine` so MLEADER's 9 attachment variants all map cleanly.
- `MTextLayout` returns `line_widths` / `line_height` / `v_offset` so
callers that draw chrome (frames, fills, baselines, greek rects)
share the same numbers as the rendered strokes.
- `mtext_line_count` re-routed through the new parser so LOD row counts
and rendered row counts can never disagree.
- Legacy helpers (`strip_mtext_codes`, `split_mtext_lines`,
`measure_mtext_chars`, `word_wrap`, `cxf::measure_text`, the
`v_offset_for_attachment` MLEADER offset table, `MTextLine::plain_text`)
deleted — all callers migrated.
Audit (TEXT_FORMAT_CODE_INTEGRATION.md) removed; its plan is implemented
here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 05:53:01 +03:00
|
|
|
let layout = layout_mtext(&MTextRenderOpts {
|
|
|
|
|
value: text,
|
|
|
|
|
insertion: [text_origin.x as f64, text_origin.y as f64, origin.z as f64],
|
|
|
|
|
height: cell_h,
|
|
|
|
|
rect_w: 0.0,
|
|
|
|
|
rotation: rot,
|
|
|
|
|
style: &resolved,
|
|
|
|
|
attach_h_anchor,
|
|
|
|
|
v_anchor,
|
|
|
|
|
line_spacing_factor: 1.0,
|
|
|
|
|
vertical_text: false,
|
2026-05-31 02:33:47 +03:00
|
|
|
want_glyph_boxes: false,
|
feat(text): route every text-bearing entity through the run-aware MText pipeline
Lift the MText parser + renderer into a reusable `layout_mtext` helper in
`text_support` and re-point the remaining text-bearing entities at it, so
every inline format code (`\f`, `\C`/`\c`, `\H`, `\W`, `\Q`, `\T`, `\A`,
`\p…`, decorations, stacked fractions, paragraph indents, tab stops, …)
reaches the stroke output uniformly.
Per-entity changes:
- TABLE cell ([entities/table.rs]) — anchor + alignment math now feeds
`layout_mtext`; `\C7;Red`-style codes no longer render as literal
glyph noise.
- MLEADER MText content (both entity path in [entities/multileader.rs]
and the LOD-aware path in [scene/tessellate.rs]) — drops the
`strip_mtext_codes` + per-line `tessellate_text_ex` chain; the LOD 0
baseline / LOD 1 greek rect / LOD 2 stroke ladder and the text-frame /
bg-fill rectangle now read line widths from the layout.
- ATTRIB / ATTDEF — when `mtext_flag` is MultiLine / ConstantMultiLine
the value goes through the MText pipeline (SingleLine stays on the
Text-style anchor maths because the DXF spec disallows codes there).
- DIMENSION text_override — `dimension_text_entity` emits a synthetic
MText (instead of Text) whenever the value contains MText escapes;
the LOD ladder via `tessellate_entity_dim_text` handles both kinds.
Infrastructure:
- `MTextVAnchor` gains `MiddleOfTopLine`, `MiddleOfBottomLine`,
`BottomOfTopLine` so MLEADER's 9 attachment variants all map cleanly.
- `MTextLayout` returns `line_widths` / `line_height` / `v_offset` so
callers that draw chrome (frames, fills, baselines, greek rects)
share the same numbers as the rendered strokes.
- `mtext_line_count` re-routed through the new parser so LOD row counts
and rendered row counts can never disagree.
- Legacy helpers (`strip_mtext_codes`, `split_mtext_lines`,
`measure_mtext_chars`, `word_wrap`, `cxf::measure_text`, the
`v_offset_for_attachment` MLEADER offset table, `MTextLine::plain_text`)
deleted — all callers migrated.
Audit (TEXT_FORMAT_CODE_INTEGRATION.md) removed; its plan is implemented
here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 05:53:01 +03:00
|
|
|
});
|
|
|
|
|
// Flatten TextStroke groups into the table's Lines buffer.
|
|
|
|
|
// Per-run inline `\C` / `\c` colour is dropped here because the
|
|
|
|
|
// table emits a single TruckObject::Lines for borders + text;
|
|
|
|
|
// tracking it would require splitting the table into multiple
|
|
|
|
|
// WireModels per cell colour. Borders + uniform-coloured runs
|
|
|
|
|
// honour the entity's outer colour.
|
|
|
|
|
for ts in &layout.strokes {
|
|
|
|
|
let ox = ts.origin[0] as f32;
|
|
|
|
|
let oy = ts.origin[1] as f32;
|
|
|
|
|
for stroke in &ts.strokes {
|
|
|
|
|
if stroke.len() < 2 {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if !pts.is_empty() {
|
|
|
|
|
pts.push([f32::NAN; 3]);
|
|
|
|
|
}
|
|
|
|
|
for &[x, y] in stroke {
|
|
|
|
|
pts.push([x + ox, y + oy, origin.z]);
|
|
|
|
|
}
|
2026-04-02 23:11:09 +03:00
|
|
|
}
|
2026-06-25 18:17:01 +01:00
|
|
|
for &[x, y] in &ts.fill_tris {
|
|
|
|
|
tris_pts.push([x + ox, y + oy, origin.z]);
|
|
|
|
|
}
|
2026-04-02 23:11:09 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-22 18:47:45 +03:00
|
|
|
// The layout above is in a local f32 frame (small magnitudes). Widen to
|
|
|
|
|
// f64 and add the world insertion so the absolute position carries full
|
|
|
|
|
// precision; tessellate.rs then applies world_offset.
|
2026-05-12 23:47:40 +03:00
|
|
|
let pts_f64: Vec<[f64; 3]> = pts
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|[x, y, z]| {
|
|
|
|
|
if x.is_nan() {
|
|
|
|
|
[f64::NAN, f64::NAN, f64::NAN]
|
|
|
|
|
} else {
|
2026-06-22 18:47:45 +03:00
|
|
|
[x as f64 + base[0], y as f64 + base[1], z as f64 + base[2]]
|
2026-05-12 23:47:40 +03:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
2026-06-25 18:17:01 +01:00
|
|
|
let fill_tris_f64: Vec<[f64; 3]> = tris_pts
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|[x, y, z]| {
|
|
|
|
|
[x as f64 + base[0], y as f64 + base[1], z as f64 + base[2]]
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
2026-04-02 23:11:09 +03:00
|
|
|
Some(TruckEntity {
|
2026-05-12 23:47:40 +03:00
|
|
|
object: TruckObject::Lines(pts_f64),
|
refactor(snap): producers emit f64 snap points (world_offset removal P3)
TruckEntity.snap_pts is now DVec3 and every entity producer builds snap
candidates straight from the acadrust f64 coordinates instead of casting
to f32 first (text, mtext, arc, circle, ellipse, point, shape, solid,
mline, mtext, attribute, tolerance, table, underlay, ole2frame, mesh,
multileader, dimension, block-cache). offset_snap_pts stays f64
throughout. Still offset-relative (inert) — but snap precision is no
longer capped at ~0.5 m by an early f32 cast, which fixes a latent
UTM-scale snap error and sets up the absolute-coordinate switch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 22:10:29 +03:00
|
|
|
snap_pts: vec![(glam::DVec3::new(self.insertion_point.x, self.insertion_point.y, self.insertion_point.z), SnapHint::Insertion)],
|
2026-04-02 23:11:09 +03:00
|
|
|
tangent_geoms: vec![],
|
|
|
|
|
key_vertices: vec![],
|
2026-06-25 18:17:01 +01:00
|
|
|
fill_tris: fill_tris_f64,
|
2026-04-02 23:11:09 +03:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 02:28:12 +03:00
|
|
|
/// Coloured synthesis render for tables WITHOUT a baked block (e.g. tables
|
|
|
|
|
/// created in-app). Emits one `WireModel` per distinct colour for cell fills,
|
|
|
|
|
/// per-cell text, and grid borders, honouring every `TableStyle` /
|
|
|
|
|
/// `RowCellStyle` / per-cell `CellStyle` field: fill colour + enable, text
|
|
|
|
|
/// colour, border type/weight/colour/visibility (incl. inside borders),
|
|
|
|
|
/// margins, and flow direction. Imported tables keep using AutoCAD's baked
|
|
|
|
|
/// block (see scene/mod.rs).
|
|
|
|
|
pub fn tessellate_table(
|
|
|
|
|
tab: &Table,
|
|
|
|
|
document: &acadrust::CadDocument,
|
|
|
|
|
selected: bool,
|
|
|
|
|
entity_color: [f32; 4],
|
|
|
|
|
line_weight_px: f32,
|
2026-06-16 11:33:46 +03:00
|
|
|
) -> Vec<crate::scene::model::wire_model::WireModel> {
|
|
|
|
|
use crate::scene::convert::tess_util::aci_to_rgba;
|
|
|
|
|
use crate::scene::model::wire_model::WireModel;
|
2026-06-04 02:28:12 +03:00
|
|
|
use acadrust::types::Color;
|
2026-06-05 00:35:53 +03:00
|
|
|
use rustc_hash::FxHashMap as HashMap;
|
2026-06-04 02:28:12 +03:00
|
|
|
|
|
|
|
|
if tab.rows.is_empty() || tab.columns.is_empty() {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let rel = |p: Vec3| -> [f32; 3] {
|
|
|
|
|
[
|
refactor: remove the last world_offset zero-locals
Clean up the remaining `let [ox,oy,oz] = [0,0,0]` bindings and their
zero-valued add/subtract arithmetic across scene/mod.rs (mesh LOD resplit,
model AABB, wide-polyline fills, wipeout boundary, hatch/solid models,
insert/entity AABB), table, multileader and xclip. Coordinates now read as
the plain absolute values they are. No world_offset reference remains.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:55:00 +03:00
|
|
|
(p.x as f64) as f32,
|
|
|
|
|
(p.y as f64) as f32,
|
|
|
|
|
(p.z as f64) as f32,
|
2026-06-04 02:28:12 +03:00
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
let resolve_col = |c: &Color, fallback: [f32; 4]| -> [f32; 4] {
|
|
|
|
|
match c {
|
|
|
|
|
Color::ByLayer | Color::ByBlock => fallback,
|
|
|
|
|
_ => aci_to_rgba(c),
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let key4 = |c: [f32; 4]| -> [u8; 4] {
|
|
|
|
|
[
|
|
|
|
|
(c[0] * 255.0) as u8,
|
|
|
|
|
(c[1] * 255.0) as u8,
|
|
|
|
|
(c[2] * 255.0) as u8,
|
|
|
|
|
(c[3] * 255.0) as u8,
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
let lw_px = |w: &acadrust::types::LineWeight| -> f32 {
|
|
|
|
|
match w {
|
|
|
|
|
acadrust::types::LineWeight::Value(v) if *v >= 0 => (*v as f32 / 100.0) * (96.0 / 25.4),
|
|
|
|
|
_ => line_weight_px,
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let origin = v3(&tab.insertion_point);
|
|
|
|
|
let h_raw = v3(&tab.horizontal_direction);
|
|
|
|
|
let h = if h_raw.length_squared() > 1e-10 {
|
|
|
|
|
h_raw.normalize()
|
|
|
|
|
} else {
|
|
|
|
|
Vec3::X
|
|
|
|
|
};
|
|
|
|
|
let v_down = Vec3::new(h.y, -h.x, 0.0);
|
|
|
|
|
// Flow direction: `Up` stacks rows upward instead of downward.
|
2026-06-11 00:04:37 +03:00
|
|
|
let table_style: Option<&acadrust::objects::TableStyle> =
|
|
|
|
|
tab.table_style_handle.and_then(|h| {
|
|
|
|
|
document.objects.get(&h).and_then(|obj| match obj {
|
|
|
|
|
acadrust::objects::ObjectType::TableStyle(ts) => Some(ts),
|
|
|
|
|
_ => None,
|
|
|
|
|
})
|
|
|
|
|
});
|
2026-06-04 02:28:12 +03:00
|
|
|
let flow_up = matches!(
|
|
|
|
|
table_style.map(|t| t.flow_direction),
|
|
|
|
|
Some(acadrust::objects::TableFlowDirection::Up)
|
|
|
|
|
);
|
|
|
|
|
let v_flow = if flow_up { -v_down } else { v_down };
|
|
|
|
|
|
|
|
|
|
let col_offsets: Vec<f32> = {
|
|
|
|
|
let mut off = 0.0f32;
|
|
|
|
|
let mut v = vec![0.0f32];
|
|
|
|
|
for col in &tab.columns {
|
|
|
|
|
off += col.width as f32;
|
|
|
|
|
v.push(off);
|
|
|
|
|
}
|
|
|
|
|
v
|
|
|
|
|
};
|
|
|
|
|
let row_offsets: Vec<f32> = {
|
|
|
|
|
let mut off = 0.0f32;
|
|
|
|
|
let mut v = vec![0.0f32];
|
|
|
|
|
for row in &tab.rows {
|
|
|
|
|
off += row.height as f32;
|
|
|
|
|
v.push(off);
|
|
|
|
|
}
|
|
|
|
|
v
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let title_suppressed = table_style.map(|t| t.title_suppressed).unwrap_or(false);
|
|
|
|
|
let header_suppressed = table_style.map(|t| t.header_suppressed).unwrap_or(false);
|
2026-06-11 00:04:37 +03:00
|
|
|
let h_margin = table_style
|
|
|
|
|
.map(|t| t.horizontal_margin as f32)
|
|
|
|
|
.unwrap_or(0.0);
|
2026-06-04 02:28:12 +03:00
|
|
|
let v_margin = table_style.map(|t| t.vertical_margin as f32).unwrap_or(0.0);
|
|
|
|
|
|
|
|
|
|
let lookup_style = |hh: acadrust::Handle| -> Option<&acadrust::tables::TextStyle> {
|
|
|
|
|
document.text_styles.iter().find(|s| s.handle == hh)
|
|
|
|
|
};
|
|
|
|
|
let font_for_handle = |handle: Option<acadrust::Handle>| -> Option<String> {
|
|
|
|
|
handle.and_then(lookup_style).and_then(|s| {
|
2026-06-25 18:17:01 +01:00
|
|
|
let mut font_name = if !s.true_type_font.trim().is_empty() {
|
|
|
|
|
s.true_type_font.trim().to_string()
|
|
|
|
|
} else {
|
|
|
|
|
let file = s.font_file.trim();
|
|
|
|
|
let basename = file.rsplit(['/', '\\']).next().unwrap_or(file);
|
|
|
|
|
let stem = basename.split('.').next().unwrap_or(basename).trim();
|
|
|
|
|
if !stem.is_empty() {
|
|
|
|
|
stem.to_string()
|
|
|
|
|
} else {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if !crate::scene::text::lff::is_builtin(&font_name) {
|
|
|
|
|
if let Some(canonical) = crate::scene::text::sysfont::canonical_family_name(&font_name) {
|
|
|
|
|
font_name = canonical;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some(font_name)
|
2026-06-04 02:28:12 +03:00
|
|
|
})
|
|
|
|
|
};
|
|
|
|
|
let resolved_style_for_handle =
|
|
|
|
|
|handle: Option<acadrust::Handle>, font_name: String| -> ResolvedTextStyle {
|
|
|
|
|
let style = handle.and_then(lookup_style);
|
|
|
|
|
ResolvedTextStyle {
|
|
|
|
|
font_name,
|
|
|
|
|
width_factor: style.map(|s| s.width_factor as f32).unwrap_or(1.0),
|
|
|
|
|
oblique_angle: style.map(|s| s.oblique_angle as f32).unwrap_or(0.0),
|
|
|
|
|
is_backward: style.map(|s| s.is_backward()).unwrap_or(false),
|
|
|
|
|
is_upside_down: style.map(|s| s.is_upside_down()).unwrap_or(false),
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Accumulators keyed by quantised colour (+ weight for borders).
|
2026-06-05 00:35:53 +03:00
|
|
|
let mut fills: HashMap<[u8; 4], ([f32; 4], Vec<[f32; 3]>)> = HashMap::default();
|
2026-06-25 18:17:01 +01:00
|
|
|
let mut texts: HashMap<[u8; 4], ([f32; 4], Vec<[f32; 3]>, Vec<[f32; 3]>)> = HashMap::default();
|
2026-06-05 00:35:53 +03:00
|
|
|
let mut borders: HashMap<([u8; 4], u32), ([f32; 4], f32, Vec<[f32; 3]>)> = HashMap::default();
|
|
|
|
|
let mut emitted: rustc_hash::FxHashSet<(i32, i32, i32, i32)> = rustc_hash::FxHashSet::default();
|
2026-06-04 02:28:12 +03:00
|
|
|
let sel_col = WireModel::SELECTED;
|
|
|
|
|
|
2026-06-11 00:04:37 +03:00
|
|
|
let mut add_edge = |a: Vec3, b: Vec3, col: [f32; 4], lw: f32| {
|
|
|
|
|
let k = (
|
|
|
|
|
(a.x * 1000.0) as i32,
|
|
|
|
|
(a.y * 1000.0) as i32,
|
|
|
|
|
(b.x * 1000.0) as i32,
|
|
|
|
|
(b.y * 1000.0) as i32,
|
|
|
|
|
);
|
|
|
|
|
let kr = (k.2, k.3, k.0, k.1);
|
|
|
|
|
if emitted.contains(&k) || emitted.contains(&kr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
emitted.insert(k);
|
|
|
|
|
let entry = borders
|
|
|
|
|
.entry((key4(col), (lw * 100.0) as u32))
|
|
|
|
|
.or_insert_with(|| (col, lw, Vec::new()));
|
|
|
|
|
if !entry.2.is_empty() {
|
|
|
|
|
entry.2.push([f32::NAN; 3]);
|
|
|
|
|
}
|
|
|
|
|
entry.2.push(rel(a));
|
|
|
|
|
entry.2.push(rel(b));
|
|
|
|
|
};
|
2026-06-04 02:28:12 +03:00
|
|
|
|
|
|
|
|
for (ri, row) in tab.rows.iter().enumerate() {
|
|
|
|
|
let row_top = row_offsets[ri];
|
|
|
|
|
let row_bot = row_offsets
|
|
|
|
|
.get(ri + 1)
|
|
|
|
|
.copied()
|
|
|
|
|
.unwrap_or(row_top + row.height as f32);
|
|
|
|
|
let row_mid = (row_top + row_bot) * 0.5;
|
|
|
|
|
let row_style: Option<&acadrust::objects::RowCellStyle> = table_style.map(|ts| {
|
|
|
|
|
let kind = match (title_suppressed, header_suppressed, ri) {
|
|
|
|
|
(false, _, 0) => 0,
|
|
|
|
|
(false, false, 1) => 1,
|
|
|
|
|
(true, false, 0) => 1,
|
|
|
|
|
_ => 2,
|
|
|
|
|
};
|
|
|
|
|
match kind {
|
|
|
|
|
0 => &ts.title_row_style,
|
|
|
|
|
1 => &ts.header_row_style,
|
|
|
|
|
_ => &ts.data_row_style,
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
for (ci, cell) in row.cells.iter().enumerate() {
|
|
|
|
|
let col_left = col_offsets[ci];
|
|
|
|
|
let col_width = tab.columns.get(ci).map(|c| c.width as f32).unwrap_or(1.0);
|
|
|
|
|
let col_right = col_left + col_width;
|
|
|
|
|
let tl = origin + h * col_left + v_flow * row_top;
|
|
|
|
|
let tr = origin + h * col_right + v_flow * row_top;
|
|
|
|
|
let br_ = origin + h * col_right + v_flow * row_bot;
|
|
|
|
|
let bl = origin + h * col_left + v_flow * row_bot;
|
|
|
|
|
|
|
|
|
|
// ── Fill ──────────────────────────────────────────────────────
|
|
|
|
|
let (fill_on, fill_color) = if let Some(cs) = &cell.style {
|
|
|
|
|
(cs.fill_enabled, cs.background_color)
|
|
|
|
|
} else if let Some(rs) = row_style {
|
|
|
|
|
(rs.fill_enabled, rs.fill_color)
|
|
|
|
|
} else {
|
|
|
|
|
(false, Color::ByLayer)
|
|
|
|
|
};
|
|
|
|
|
if fill_on {
|
|
|
|
|
let col = resolve_col(&fill_color, entity_color);
|
2026-06-11 00:04:37 +03:00
|
|
|
let buf = &mut fills
|
|
|
|
|
.entry(key4(col))
|
|
|
|
|
.or_insert_with(|| (col, Vec::new()))
|
|
|
|
|
.1;
|
2026-06-04 02:28:12 +03:00
|
|
|
for v in [bl, br_, tr, bl, tr, tl] {
|
|
|
|
|
buf.push(rel(v));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Borders (per edge: cell override → row style → default) ───
|
|
|
|
|
// (top, right, bottom, left)
|
|
|
|
|
let cell_b = cell.style.as_ref();
|
|
|
|
|
let edge = |which: u8| -> (bool, [f32; 4], f32) {
|
|
|
|
|
if let Some(cs) = cell_b {
|
|
|
|
|
let b = match which {
|
|
|
|
|
0 => &cs.top_border,
|
|
|
|
|
1 => &cs.right_border,
|
|
|
|
|
2 => &cs.bottom_border,
|
|
|
|
|
_ => &cs.left_border,
|
|
|
|
|
};
|
|
|
|
|
(
|
|
|
|
|
!b.invisible,
|
2026-06-11 00:04:37 +03:00
|
|
|
if selected {
|
|
|
|
|
sel_col
|
|
|
|
|
} else {
|
|
|
|
|
resolve_col(&b.color, entity_color)
|
|
|
|
|
},
|
2026-06-04 02:28:12 +03:00
|
|
|
lw_px(&b.line_weight),
|
|
|
|
|
)
|
|
|
|
|
} else if let Some(rs) = row_style {
|
|
|
|
|
let b = match which {
|
|
|
|
|
0 => &rs.top_border,
|
|
|
|
|
1 => &rs.right_border,
|
|
|
|
|
2 => &rs.bottom_border,
|
|
|
|
|
_ => &rs.left_border,
|
|
|
|
|
};
|
|
|
|
|
(
|
|
|
|
|
!b.is_invisible,
|
2026-06-11 00:04:37 +03:00
|
|
|
if selected {
|
|
|
|
|
sel_col
|
|
|
|
|
} else {
|
|
|
|
|
resolve_col(&b.color, entity_color)
|
|
|
|
|
},
|
2026-06-04 02:28:12 +03:00
|
|
|
lw_px(&b.line_weight),
|
|
|
|
|
)
|
|
|
|
|
} else {
|
2026-06-11 00:04:37 +03:00
|
|
|
(
|
|
|
|
|
true,
|
|
|
|
|
if selected { sel_col } else { entity_color },
|
|
|
|
|
line_weight_px,
|
|
|
|
|
)
|
2026-06-04 02:28:12 +03:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let (tv, tc, tw) = edge(0);
|
2026-06-11 00:04:37 +03:00
|
|
|
if tv {
|
|
|
|
|
add_edge(tl, tr, tc, tw);
|
|
|
|
|
}
|
2026-06-04 02:28:12 +03:00
|
|
|
let (rv, rc, rw) = edge(1);
|
2026-06-11 00:04:37 +03:00
|
|
|
if rv {
|
|
|
|
|
add_edge(tr, br_, rc, rw);
|
|
|
|
|
}
|
2026-06-04 02:28:12 +03:00
|
|
|
let (bv, bc, bw) = edge(2);
|
2026-06-11 00:04:37 +03:00
|
|
|
if bv {
|
|
|
|
|
add_edge(bl, br_, bc, bw);
|
|
|
|
|
}
|
2026-06-04 02:28:12 +03:00
|
|
|
let (lv, lc, lw) = edge(3);
|
2026-06-11 00:04:37 +03:00
|
|
|
if lv {
|
|
|
|
|
add_edge(tl, bl, lc, lw);
|
|
|
|
|
}
|
2026-06-04 02:28:12 +03:00
|
|
|
|
|
|
|
|
// ── Text ──────────────────────────────────────────────────────
|
|
|
|
|
let text = cell.text_value();
|
|
|
|
|
if text.is_empty() {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
let content = cell.contents.first();
|
|
|
|
|
let cell_h = content
|
|
|
|
|
.map(|c| c.text_height)
|
|
|
|
|
.filter(|h| *h > 1e-6)
|
2026-06-11 00:04:37 +03:00
|
|
|
.or_else(|| {
|
|
|
|
|
cell.style
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|s| s.text_height)
|
|
|
|
|
.filter(|h| *h > 1e-6)
|
|
|
|
|
})
|
2026-06-04 02:28:12 +03:00
|
|
|
.or_else(|| row_style.map(|s| s.text_height).filter(|h| *h > 1e-6))
|
|
|
|
|
.map(|h| h as f32)
|
|
|
|
|
.unwrap_or(0.18);
|
2026-06-11 00:04:37 +03:00
|
|
|
let m_h = if h_margin > 1e-6 {
|
|
|
|
|
h_margin
|
|
|
|
|
} else {
|
|
|
|
|
cell_h * 0.5
|
|
|
|
|
};
|
|
|
|
|
let m_v = if v_margin > 1e-6 {
|
|
|
|
|
v_margin
|
|
|
|
|
} else {
|
|
|
|
|
cell_h * 0.5
|
|
|
|
|
};
|
2026-06-04 02:28:12 +03:00
|
|
|
|
|
|
|
|
let style_handle = content
|
|
|
|
|
.and_then(|c| c.text_style_handle)
|
|
|
|
|
.or_else(|| cell.style.as_ref().and_then(|s| s.text_style_handle))
|
|
|
|
|
.or_else(|| row_style.and_then(|s| s.text_style_handle));
|
|
|
|
|
let font_owned = font_for_handle(style_handle).unwrap_or_else(|| "txt".to_string());
|
|
|
|
|
let resolved = resolved_style_for_handle(style_handle, font_owned);
|
|
|
|
|
|
|
|
|
|
let align = cell
|
|
|
|
|
.style
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|s| s.alignment)
|
|
|
|
|
.filter(|a| *a != 0)
|
|
|
|
|
.or_else(|| row_style.map(|s| s.alignment as i32))
|
|
|
|
|
.unwrap_or(5);
|
|
|
|
|
let horiz = ((align - 1).rem_euclid(3)) + 1;
|
|
|
|
|
let vert = ((align - 1) / 3) + 1;
|
|
|
|
|
let (x_offset, attach_h_anchor) = match horiz {
|
|
|
|
|
1 => (col_left + m_h, 0.0_f32),
|
|
|
|
|
3 => (col_right - m_h, 1.0_f32),
|
|
|
|
|
_ => (col_left + col_width * 0.5, 0.5_f32),
|
|
|
|
|
};
|
|
|
|
|
let (y_offset, v_anchor) = match vert {
|
|
|
|
|
1 => (row_top + m_v, MTextVAnchor::Top),
|
|
|
|
|
3 => (row_bot - m_v, MTextVAnchor::Bottom),
|
|
|
|
|
_ => (row_mid, MTextVAnchor::Middle),
|
|
|
|
|
};
|
|
|
|
|
let to = origin + h * x_offset + v_flow * y_offset;
|
|
|
|
|
let rot = content.map(|c| c.rotation as f32).unwrap_or(0.0) + cell.rotation as f32;
|
|
|
|
|
let layout = layout_mtext(&MTextRenderOpts {
|
|
|
|
|
value: text,
|
refactor: remove the last world_offset zero-locals
Clean up the remaining `let [ox,oy,oz] = [0,0,0]` bindings and their
zero-valued add/subtract arithmetic across scene/mod.rs (mesh LOD resplit,
model AABB, wide-polyline fills, wipeout boundary, hatch/solid models,
insert/entity AABB), table, multileader and xclip. Coordinates now read as
the plain absolute values they are. No world_offset reference remains.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:55:00 +03:00
|
|
|
insertion: [(to.x as f64), (to.y as f64), (to.z as f64)],
|
2026-06-04 02:28:12 +03:00
|
|
|
height: cell_h,
|
|
|
|
|
rect_w: 0.0,
|
|
|
|
|
rotation: rot,
|
|
|
|
|
style: &resolved,
|
|
|
|
|
attach_h_anchor,
|
|
|
|
|
v_anchor,
|
|
|
|
|
line_spacing_factor: 1.0,
|
|
|
|
|
vertical_text: false,
|
|
|
|
|
want_glyph_boxes: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let tcol = if selected {
|
|
|
|
|
sel_col
|
|
|
|
|
} else if let Some(cs) = &cell.style {
|
|
|
|
|
resolve_col(&cs.content_color, entity_color)
|
|
|
|
|
} else if let Some(rs) = row_style {
|
|
|
|
|
resolve_col(&rs.text_color, entity_color)
|
|
|
|
|
} else {
|
|
|
|
|
entity_color
|
|
|
|
|
};
|
2026-06-25 18:17:01 +01:00
|
|
|
let entry = texts
|
2026-06-11 00:04:37 +03:00
|
|
|
.entry(key4(tcol))
|
2026-06-25 18:17:01 +01:00
|
|
|
.or_insert_with(|| (tcol, Vec::new(), Vec::new()));
|
|
|
|
|
let buf = &mut entry.1;
|
|
|
|
|
let tris_buf = &mut entry.2;
|
2026-06-04 02:28:12 +03:00
|
|
|
for ts in &layout.strokes {
|
|
|
|
|
let sx = ts.origin[0] as f32;
|
|
|
|
|
let sy = ts.origin[1] as f32;
|
|
|
|
|
for stroke in &ts.strokes {
|
|
|
|
|
if stroke.len() < 2 {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if !buf.is_empty() {
|
|
|
|
|
buf.push([f32::NAN; 3]);
|
|
|
|
|
}
|
|
|
|
|
for &[x, y] in stroke {
|
refactor: remove the last world_offset zero-locals
Clean up the remaining `let [ox,oy,oz] = [0,0,0]` bindings and their
zero-valued add/subtract arithmetic across scene/mod.rs (mesh LOD resplit,
model AABB, wide-polyline fills, wipeout boundary, hatch/solid models,
insert/entity AABB), table, multileader and xclip. Coordinates now read as
the plain absolute values they are. No world_offset reference remains.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:55:00 +03:00
|
|
|
buf.push([x + sx, y + sy, (to.z as f64) as f32]);
|
2026-06-04 02:28:12 +03:00
|
|
|
}
|
|
|
|
|
}
|
2026-06-25 18:17:01 +01:00
|
|
|
for &[x, y] in &ts.fill_tris {
|
|
|
|
|
tris_buf.push([x + sx, y + sy, (to.z as f64) as f32]);
|
|
|
|
|
}
|
2026-06-04 02:28:12 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let name = tab.common.handle.value().to_string();
|
2026-06-11 00:04:37 +03:00
|
|
|
let mk =
|
|
|
|
|
|color: [f32; 4], points: Vec<[f32; 3]>, fill_tris: Vec<[f32; 3]>, lw: f32| -> WireModel {
|
|
|
|
|
WireModel {
|
|
|
|
|
name: name.clone(),
|
|
|
|
|
points,
|
2026-06-23 17:08:54 +03:00
|
|
|
points_low: Vec::new(),
|
2026-06-11 00:04:37 +03:00
|
|
|
color,
|
|
|
|
|
selected,
|
|
|
|
|
pattern_length: 0.0,
|
|
|
|
|
pattern: [0.0; 8],
|
|
|
|
|
line_weight_px: lw,
|
|
|
|
|
aci: 0,
|
|
|
|
|
snap_pts: vec![],
|
|
|
|
|
tangent_geoms: vec![],
|
|
|
|
|
key_vertices: vec![],
|
|
|
|
|
aabb: WireModel::UNBOUNDED_AABB,
|
|
|
|
|
plinegen: true,
|
|
|
|
|
vp_scissor: None,
|
|
|
|
|
fill_tris,
|
2026-06-28 21:57:51 +03:00
|
|
|
// fill_tris_low intentionally empty: this fill renders on the
|
|
|
|
|
// top-level path, where consumers (face3d_gpu, xclip) treat a
|
|
|
|
|
// short low half as all-zero, so it draws at f32 precision
|
|
|
|
|
// (sub-metre error at UTM scale) — not a crash. Follow-up:
|
|
|
|
|
// double-single-split via points_to_ds to match emit_wire.
|
2026-06-23 18:03:40 +03:00
|
|
|
fill_tris_low: Vec::new(),
|
2026-06-11 00:04:37 +03:00
|
|
|
}
|
|
|
|
|
};
|
2026-06-04 02:28:12 +03:00
|
|
|
|
|
|
|
|
let mut out: Vec<WireModel> = Vec::new();
|
|
|
|
|
// Fills first (drawn under borders/text).
|
|
|
|
|
for (_, (color, tris)) in fills {
|
|
|
|
|
if !tris.is_empty() {
|
|
|
|
|
out.push(mk(color, vec![], tris, 1.0));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (_, (color, lw, pts)) in borders {
|
|
|
|
|
if !pts.is_empty() {
|
|
|
|
|
out.push(mk(color, pts, vec![], lw));
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-25 18:17:01 +01:00
|
|
|
for (_, (color, pts, tris)) in texts {
|
2026-06-04 02:28:12 +03:00
|
|
|
if !pts.is_empty() {
|
|
|
|
|
out.push(mk(color, pts, vec![], line_weight_px));
|
|
|
|
|
}
|
2026-06-25 18:17:01 +01:00
|
|
|
if !tris.is_empty() {
|
|
|
|
|
out.push(mk(color, vec![], tris, line_weight_px));
|
|
|
|
|
}
|
2026-06-04 02:28:12 +03:00
|
|
|
}
|
|
|
|
|
out
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 23:11:09 +03:00
|
|
|
impl Grippable for Table {
|
|
|
|
|
fn grips(&self) -> Vec<GripDef> {
|
2026-06-11 00:04:37 +03:00
|
|
|
vec![square_grip(
|
|
|
|
|
0,
|
|
|
|
|
glam::DVec3::new(
|
|
|
|
|
self.insertion_point.x,
|
|
|
|
|
self.insertion_point.y,
|
|
|
|
|
self.insertion_point.z,
|
|
|
|
|
),
|
|
|
|
|
)]
|
2026-04-02 23:11:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
|
|
|
|
if grip_id == 0 {
|
|
|
|
|
match apply {
|
|
|
|
|
GripApply::Translate(d) => {
|
|
|
|
|
self.insertion_point.x += d.x as f64;
|
|
|
|
|
self.insertion_point.y += d.y as f64;
|
|
|
|
|
self.insertion_point.z += d.z as f64;
|
|
|
|
|
}
|
|
|
|
|
GripApply::Absolute(p) => {
|
|
|
|
|
self.insertion_point.x = p.x as f64;
|
|
|
|
|
self.insertion_point.y = p.y as f64;
|
|
|
|
|
self.insertion_point.z = p.z as f64;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PropertyEditable for Table {
|
2026-07-02 22:46:19 +03:00
|
|
|
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
feat(entities): integrate remaining fields on block-based entities
MultiLeader and Table were ignoring most of the styling and content
fields that the parser populated. Bring both up to file-driven render:
MultiLeader
- line_color: leader stroke / arrow colour with ByBlock/ByLayer fallback
- line_type_handle: dashed leader via line_types lookup by handle
- line_weight (ml.line_weight): per-leader pixel width override
- arrowhead_handle: arrow shape resolved through the dim arrow emitter
(Closed Filled / Open / Dot / Origin / Box / Datum / Tick / None)
- extend_leader_to_text: continue the leader past the landing to the
text insertion point
- text_angle_type: Horizontal forces 0, Optimized clamps to a readable
half-plane, ParallelToLastLeaderLine keeps the stored direction
- text_direction_negative: adds π to the rotation
- text_alignment: drives the horizontal anchor when more specific than
the context's text_attachment_point
- text_attachment_direction + text_top_attachment + text_bottom_attachment:
Vertical mode uses the top / bottom attachment values chosen from the
first root's direction.y sign
- has_block_contents + block_content_handle + block_content_location +
block_rotation + block_scale + block_content_color: synthesise an
Insert and explode it through the standard tessellator so block-
content MultiLeaders render their referenced block
- style_handle, arrowhead_handle, line_type_handle, text_style_handle,
block_content_handle, property_override_flags, text_align_in_ipe,
block_scale, block_rotation, extend_leader_to_text,
text_direction_negative, text_top_attachment, text_bottom_attachment
surfaced in the Properties panel
Table
- Per-cell borders: drop the always-grid pass, walk the cells and
emit each border honouring its `invisible` flag with dedup so shared
edges aren't doubled
- table_style_handle, block_record_handle, data_version, value_flags,
override_flag / override_border_* flags, break_options /
break_flow_direction / break_spacing, normal surfaced in the
Properties panel
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:17:05 +03:00
|
|
|
let fmt_h = |oh: &Option<acadrust::types::Handle>| -> String {
|
|
|
|
|
match oh {
|
|
|
|
|
Some(h) if !h.is_null() => format!("{:X}", h.value()),
|
|
|
|
|
_ => "(none)".to_string(),
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-07-02 22:46:19 +03:00
|
|
|
vec![PropSection {
|
2026-04-02 23:11:09 +03:00
|
|
|
title: "Table".into(),
|
|
|
|
|
props: vec![
|
2026-05-12 10:36:15 +03:00
|
|
|
ro("Rows", "tbl_rows", self.rows.len().to_string()),
|
|
|
|
|
ro("Columns", "tbl_cols", self.columns.len().to_string()),
|
|
|
|
|
ro(
|
|
|
|
|
"Insert X",
|
|
|
|
|
"tbl_ix",
|
|
|
|
|
format!("{:.4}", self.insertion_point.x),
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Insert Y",
|
|
|
|
|
"tbl_iy",
|
|
|
|
|
format!("{:.4}", self.insertion_point.y),
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Insert Z",
|
|
|
|
|
"tbl_iz",
|
|
|
|
|
format!("{:.4}", self.insertion_point.z),
|
|
|
|
|
),
|
feat(entities): integrate remaining fields on block-based entities
MultiLeader and Table were ignoring most of the styling and content
fields that the parser populated. Bring both up to file-driven render:
MultiLeader
- line_color: leader stroke / arrow colour with ByBlock/ByLayer fallback
- line_type_handle: dashed leader via line_types lookup by handle
- line_weight (ml.line_weight): per-leader pixel width override
- arrowhead_handle: arrow shape resolved through the dim arrow emitter
(Closed Filled / Open / Dot / Origin / Box / Datum / Tick / None)
- extend_leader_to_text: continue the leader past the landing to the
text insertion point
- text_angle_type: Horizontal forces 0, Optimized clamps to a readable
half-plane, ParallelToLastLeaderLine keeps the stored direction
- text_direction_negative: adds π to the rotation
- text_alignment: drives the horizontal anchor when more specific than
the context's text_attachment_point
- text_attachment_direction + text_top_attachment + text_bottom_attachment:
Vertical mode uses the top / bottom attachment values chosen from the
first root's direction.y sign
- has_block_contents + block_content_handle + block_content_location +
block_rotation + block_scale + block_content_color: synthesise an
Insert and explode it through the standard tessellator so block-
content MultiLeaders render their referenced block
- style_handle, arrowhead_handle, line_type_handle, text_style_handle,
block_content_handle, property_override_flags, text_align_in_ipe,
block_scale, block_rotation, extend_leader_to_text,
text_direction_negative, text_top_attachment, text_bottom_attachment
surfaced in the Properties panel
Table
- Per-cell borders: drop the always-grid pass, walk the cells and
emit each border honouring its `invisible` flag with dedup so shared
edges aren't doubled
- table_style_handle, block_record_handle, data_version, value_flags,
override_flag / override_border_* flags, break_options /
break_flow_direction / break_spacing, normal surfaced in the
Properties panel
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:17:05 +03:00
|
|
|
ro(
|
|
|
|
|
"Table Style",
|
|
|
|
|
"tbl_style_handle",
|
|
|
|
|
fmt_h(&self.table_style_handle),
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Block Record",
|
|
|
|
|
"tbl_block_rec_handle",
|
|
|
|
|
fmt_h(&self.block_record_handle),
|
|
|
|
|
),
|
2026-06-11 00:04:37 +03:00
|
|
|
ro(
|
|
|
|
|
"Data Version",
|
|
|
|
|
"tbl_data_version",
|
|
|
|
|
self.data_version.to_string(),
|
|
|
|
|
),
|
feat(entities): integrate remaining fields on block-based entities
MultiLeader and Table were ignoring most of the styling and content
fields that the parser populated. Bring both up to file-driven render:
MultiLeader
- line_color: leader stroke / arrow colour with ByBlock/ByLayer fallback
- line_type_handle: dashed leader via line_types lookup by handle
- line_weight (ml.line_weight): per-leader pixel width override
- arrowhead_handle: arrow shape resolved through the dim arrow emitter
(Closed Filled / Open / Dot / Origin / Box / Datum / Tick / None)
- extend_leader_to_text: continue the leader past the landing to the
text insertion point
- text_angle_type: Horizontal forces 0, Optimized clamps to a readable
half-plane, ParallelToLastLeaderLine keeps the stored direction
- text_direction_negative: adds π to the rotation
- text_alignment: drives the horizontal anchor when more specific than
the context's text_attachment_point
- text_attachment_direction + text_top_attachment + text_bottom_attachment:
Vertical mode uses the top / bottom attachment values chosen from the
first root's direction.y sign
- has_block_contents + block_content_handle + block_content_location +
block_rotation + block_scale + block_content_color: synthesise an
Insert and explode it through the standard tessellator so block-
content MultiLeaders render their referenced block
- style_handle, arrowhead_handle, line_type_handle, text_style_handle,
block_content_handle, property_override_flags, text_align_in_ipe,
block_scale, block_rotation, extend_leader_to_text,
text_direction_negative, text_top_attachment, text_bottom_attachment
surfaced in the Properties panel
Table
- Per-cell borders: drop the always-grid pass, walk the cells and
emit each border honouring its `invisible` flag with dedup so shared
edges aren't doubled
- table_style_handle, block_record_handle, data_version, value_flags,
override_flag / override_border_* flags, break_options /
break_flow_direction / break_spacing, normal surfaced in the
Properties panel
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:17:05 +03:00
|
|
|
ro(
|
|
|
|
|
"Value Flags",
|
|
|
|
|
"tbl_value_flags",
|
|
|
|
|
format!("{:#010x}", self.value_flags),
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Override Flag",
|
|
|
|
|
"tbl_override_flag",
|
|
|
|
|
if self.override_flag { "Yes" } else { "No" },
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Override Border Color",
|
|
|
|
|
"tbl_override_border_color",
|
2026-06-11 00:04:37 +03:00
|
|
|
if self.override_border_color {
|
|
|
|
|
"Yes"
|
|
|
|
|
} else {
|
|
|
|
|
"No"
|
|
|
|
|
},
|
feat(entities): integrate remaining fields on block-based entities
MultiLeader and Table were ignoring most of the styling and content
fields that the parser populated. Bring both up to file-driven render:
MultiLeader
- line_color: leader stroke / arrow colour with ByBlock/ByLayer fallback
- line_type_handle: dashed leader via line_types lookup by handle
- line_weight (ml.line_weight): per-leader pixel width override
- arrowhead_handle: arrow shape resolved through the dim arrow emitter
(Closed Filled / Open / Dot / Origin / Box / Datum / Tick / None)
- extend_leader_to_text: continue the leader past the landing to the
text insertion point
- text_angle_type: Horizontal forces 0, Optimized clamps to a readable
half-plane, ParallelToLastLeaderLine keeps the stored direction
- text_direction_negative: adds π to the rotation
- text_alignment: drives the horizontal anchor when more specific than
the context's text_attachment_point
- text_attachment_direction + text_top_attachment + text_bottom_attachment:
Vertical mode uses the top / bottom attachment values chosen from the
first root's direction.y sign
- has_block_contents + block_content_handle + block_content_location +
block_rotation + block_scale + block_content_color: synthesise an
Insert and explode it through the standard tessellator so block-
content MultiLeaders render their referenced block
- style_handle, arrowhead_handle, line_type_handle, text_style_handle,
block_content_handle, property_override_flags, text_align_in_ipe,
block_scale, block_rotation, extend_leader_to_text,
text_direction_negative, text_top_attachment, text_bottom_attachment
surfaced in the Properties panel
Table
- Per-cell borders: drop the always-grid pass, walk the cells and
emit each border honouring its `invisible` flag with dedup so shared
edges aren't doubled
- table_style_handle, block_record_handle, data_version, value_flags,
override_flag / override_border_* flags, break_options /
break_flow_direction / break_spacing, normal surfaced in the
Properties panel
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:17:05 +03:00
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Override Border LW",
|
|
|
|
|
"tbl_override_border_lw",
|
|
|
|
|
if self.override_border_line_weight {
|
|
|
|
|
"Yes"
|
|
|
|
|
} else {
|
|
|
|
|
"No"
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Override Border Vis",
|
|
|
|
|
"tbl_override_border_vis",
|
|
|
|
|
if self.override_border_visibility {
|
|
|
|
|
"Yes"
|
|
|
|
|
} else {
|
|
|
|
|
"No"
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Break Spacing",
|
|
|
|
|
"tbl_break_spacing",
|
|
|
|
|
format!("{:.4}", self.break_spacing),
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Break Flow",
|
|
|
|
|
"tbl_break_flow",
|
|
|
|
|
format!("{:?}", self.break_flow_direction),
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Break Options",
|
|
|
|
|
"tbl_break_options",
|
|
|
|
|
format!("{:#018b}", self.break_options.bits()),
|
|
|
|
|
),
|
|
|
|
|
ro(
|
|
|
|
|
"Normal",
|
|
|
|
|
"tbl_normal",
|
|
|
|
|
format!(
|
|
|
|
|
"{:.3}, {:.3}, {:.3}",
|
|
|
|
|
self.normal.x, self.normal.y, self.normal.z
|
|
|
|
|
),
|
|
|
|
|
),
|
2026-04-02 23:11:09 +03:00
|
|
|
],
|
2026-07-02 22:46:19 +03:00
|
|
|
}]
|
2026-04-02 23:11:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_geom_prop(&mut self, _field: &str, _value: &str) {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Transformable for Table {
|
|
|
|
|
fn apply_transform(&mut self, t: &EntityTransform) {
|
|
|
|
|
transform::apply_standard_entity_transform(self, t, |entity, p1, p2| {
|
|
|
|
|
transform::reflect_xy_point(
|
|
|
|
|
&mut entity.insertion_point.x,
|
|
|
|
|
&mut entity.insertion_point.y,
|
|
|
|
|
p1,
|
|
|
|
|
p2,
|
|
|
|
|
);
|
|
|
|
|
// Reflect the horizontal direction by reflecting a tip point
|
|
|
|
|
let mut tip_x = entity.insertion_point.x + entity.horizontal_direction.x;
|
|
|
|
|
let mut tip_y = entity.insertion_point.y + entity.horizontal_direction.y;
|
|
|
|
|
transform::reflect_xy_point(&mut tip_x, &mut tip_y, p1, p2);
|
|
|
|
|
entity.horizontal_direction.x = tip_x - entity.insertion_point.x;
|
|
|
|
|
entity.horizontal_direction.y = tip_y - entity.insertion_point.y;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|