Align MText properties and column layout
This commit is contained in:
parent
4be01774e1
commit
96ca741f35
6 changed files with 214 additions and 34 deletions
|
|
@ -200,6 +200,7 @@ fn build_attr_render(input: AttrTextInputs<'_>, document: &acadrust::CadDocument
|
||||||
oblique_angle,
|
oblique_angle,
|
||||||
is_backward: width_factor < 0.0,
|
is_backward: width_factor < 0.0,
|
||||||
is_upside_down: false,
|
is_upside_down: false,
|
||||||
|
is_vertical: false,
|
||||||
};
|
};
|
||||||
let layout = layout_mtext(&MTextRenderOpts {
|
let layout = layout_mtext(&MTextRenderOpts {
|
||||||
// Not an MTEXT: text in a fixed box, never columnar.
|
// Not an MTEXT: text in a fixed box, never columnar.
|
||||||
|
|
@ -213,6 +214,8 @@ fn build_attr_render(input: AttrTextInputs<'_>, document: &acadrust::CadDocument
|
||||||
attach_h_anchor,
|
attach_h_anchor,
|
||||||
v_anchor,
|
v_anchor,
|
||||||
line_spacing_factor: 1.0,
|
line_spacing_factor: 1.0,
|
||||||
|
exact_line_spacing: false,
|
||||||
|
rectangle_height: 0.0,
|
||||||
vertical_text: false,
|
vertical_text: false,
|
||||||
want_glyph_boxes: false,
|
want_glyph_boxes: false,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,9 @@ fn columns_of(t: &MText) -> MTextColumns {
|
||||||
count: c.column_count.max(0) as usize,
|
count: c.column_count.max(0) as usize,
|
||||||
width: c.width as f32,
|
width: c.width as f32,
|
||||||
gutter: c.gutter as f32,
|
gutter: c.gutter as f32,
|
||||||
|
flow_reversed: c.flow_reversed,
|
||||||
|
auto_height: c.auto_height,
|
||||||
|
heights: c.heights.iter().map(|height| *height as f32).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,7 +123,14 @@ pub fn glyph_boxes(t: &MText, document: &acadrust::CadDocument) -> Vec<GlyphBox>
|
||||||
attach_h_anchor,
|
attach_h_anchor,
|
||||||
v_anchor,
|
v_anchor,
|
||||||
line_spacing_factor: t.line_spacing_factor as f32,
|
line_spacing_factor: t.line_spacing_factor as f32,
|
||||||
vertical_text: matches!(t.drawing_direction, DrawingDirection::TopToBottom),
|
exact_line_spacing: matches!(
|
||||||
|
t.line_spacing_style,
|
||||||
|
acadrust::entities::LineSpacingStyle::Exactly
|
||||||
|
),
|
||||||
|
rectangle_height: t.rectangle_height.unwrap_or(0.0) as f32,
|
||||||
|
vertical_text: matches!(t.drawing_direction, DrawingDirection::TopToBottom)
|
||||||
|
|| matches!(t.drawing_direction, DrawingDirection::ByStyle)
|
||||||
|
&& resolved_style.is_vertical,
|
||||||
want_glyph_boxes: true,
|
want_glyph_boxes: true,
|
||||||
columns: columns_of(t),
|
columns: columns_of(t),
|
||||||
});
|
});
|
||||||
|
|
@ -169,7 +179,14 @@ fn to_render(t: &MText, document: &acadrust::CadDocument) -> RenderEntity {
|
||||||
attach_h_anchor,
|
attach_h_anchor,
|
||||||
v_anchor,
|
v_anchor,
|
||||||
line_spacing_factor: t.line_spacing_factor as f32,
|
line_spacing_factor: t.line_spacing_factor as f32,
|
||||||
vertical_text: matches!(t.drawing_direction, DrawingDirection::TopToBottom),
|
exact_line_spacing: matches!(
|
||||||
|
t.line_spacing_style,
|
||||||
|
acadrust::entities::LineSpacingStyle::Exactly
|
||||||
|
),
|
||||||
|
rectangle_height: t.rectangle_height.unwrap_or(0.0) as f32,
|
||||||
|
vertical_text: matches!(t.drawing_direction, DrawingDirection::TopToBottom)
|
||||||
|
|| matches!(t.drawing_direction, DrawingDirection::ByStyle)
|
||||||
|
&& resolved_style.is_vertical,
|
||||||
want_glyph_boxes: false,
|
want_glyph_boxes: false,
|
||||||
columns: columns_of(t),
|
columns: columns_of(t),
|
||||||
});
|
});
|
||||||
|
|
@ -231,8 +248,43 @@ fn grips(t: &MText) -> Vec<GripDef> {
|
||||||
t.insertion_point.z,
|
t.insertion_point.z,
|
||||||
);
|
);
|
||||||
let (dir, k) = width_grip_axis(t);
|
let (dir, k) = width_grip_axis(t);
|
||||||
let width_grip = p + dir * (k * t.rectangle_width.max(0.0));
|
let columns_active = t.column_data.column_type != 0
|
||||||
vec![square_grip(0, p), triangle_grip(1, width_grip)]
|
&& t.column_data.column_count > 1
|
||||||
|
&& t.column_data.width > 0.0;
|
||||||
|
let block_width = if columns_active {
|
||||||
|
t.column_data.width * t.column_data.column_count as f64
|
||||||
|
+ t.column_data.gutter * (t.column_data.column_count - 1) as f64
|
||||||
|
} else {
|
||||||
|
t.rectangle_width.max(0.0)
|
||||||
|
};
|
||||||
|
let width_grip = p + dir * (k * block_width);
|
||||||
|
let mut result = vec![square_grip(0, p), triangle_grip(1, width_grip)];
|
||||||
|
|
||||||
|
let height = t
|
||||||
|
.rectangle_height
|
||||||
|
.or_else(|| t.column_data.heights.first().copied())
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
if height > 0.0 && !(columns_active && t.column_data.auto_height) {
|
||||||
|
let (sin, cos) = t.rotation.sin_cos();
|
||||||
|
let down = glam::DVec3::new(sin, -cos, 0.0);
|
||||||
|
let (_, vertical) = attach_anchors(t);
|
||||||
|
let factor = match vertical {
|
||||||
|
MTextVAnchor::Top
|
||||||
|
| MTextVAnchor::MiddleOfTopLine
|
||||||
|
| MTextVAnchor::BottomOfTopLine => 1.0,
|
||||||
|
MTextVAnchor::Middle => 0.5,
|
||||||
|
MTextVAnchor::Bottom | MTextVAnchor::MiddleOfBottomLine => -1.0,
|
||||||
|
};
|
||||||
|
result.push(triangle_grip(2, p + down * (factor * height)));
|
||||||
|
}
|
||||||
|
if columns_active {
|
||||||
|
result.push(triangle_grip(3, p + dir * (k * t.column_data.width)));
|
||||||
|
result.push(triangle_grip(
|
||||||
|
4,
|
||||||
|
p + dir * (k * (t.column_data.width + t.column_data.gutter)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn columns_str(c: &acadrust::entities::MTextColumnData) -> &'static str {
|
fn columns_str(c: &acadrust::entities::MTextColumnData) -> &'static str {
|
||||||
|
|
@ -371,20 +423,6 @@ fn properties(t: &MText, text_style_names: &[String]) -> Vec<PropSection> {
|
||||||
.collect(),
|
.collect(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Count / width / gutter are live only when columns are on.
|
|
||||||
num_row(
|
|
||||||
t!("Column count").as_ref(),
|
|
||||||
"col_count",
|
|
||||||
t.column_data.column_count as f64,
|
|
||||||
col_type != 0,
|
|
||||||
),
|
|
||||||
num_row(t!("Column width").as_ref(), "col_width", t.column_data.width, col_type != 0),
|
|
||||||
num_row(
|
|
||||||
t!("Column gutter").as_ref(),
|
|
||||||
"col_gutter",
|
|
||||||
t.column_data.gutter,
|
|
||||||
col_type != 0,
|
|
||||||
),
|
|
||||||
Property {
|
Property {
|
||||||
label: t!("Text frame").into_owned(),
|
label: t!("Text frame").into_owned(),
|
||||||
field: "text_frame",
|
field: "text_frame",
|
||||||
|
|
@ -502,21 +540,21 @@ fn apply_geom_prop(t: &mut MText, field: &str, value: &str) {
|
||||||
"ins_y" => t.insertion_point.y = v,
|
"ins_y" => t.insertion_point.y = v,
|
||||||
"ins_z" => t.insertion_point.z = v,
|
"ins_z" => t.insertion_point.z = v,
|
||||||
"height" if v > 0.0 => t.height = v,
|
"height" if v > 0.0 => t.height = v,
|
||||||
"rect_w" if v > 0.0 => t.rectangle_width = v,
|
"rect_w" if v >= 0.0 => t.rectangle_width = v,
|
||||||
"rect_h" if v > 0.0 => t.rectangle_height = Some(v),
|
"rect_h" if v >= 0.0 => t.rectangle_height = (v > 0.0).then_some(v),
|
||||||
"rotation" => t.rotation = v.to_radians(),
|
"rotation" => t.rotation = v.to_radians(),
|
||||||
"line_spacing" if v > 0.0 => t.line_spacing_factor = v,
|
"line_spacing" if (0.25..=4.0).contains(&v) => t.line_spacing_factor = v,
|
||||||
// Editing the absolute distance back-solves the line-spacing factor so
|
// Editing the absolute distance back-solves the line-spacing factor so
|
||||||
// the two stay consistent (distance = height × 5/3 × factor).
|
// the two stay consistent (distance = height × 5/3 × factor).
|
||||||
"line_space_distance" if v > 0.0 => {
|
"line_space_distance" if v > 0.0 => {
|
||||||
let denom = t.height * 1.666_666_666_666_667;
|
let denom = t.height * 1.666_666_666_666_667;
|
||||||
if denom > 0.0 {
|
if denom > 0.0 {
|
||||||
t.line_spacing_factor = v / denom;
|
let factor = v / denom;
|
||||||
|
if (0.25..=4.0).contains(&factor) {
|
||||||
|
t.line_spacing_factor = factor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"col_count" if v >= 1.0 => t.column_data.column_count = v.round() as i32,
|
|
||||||
"col_width" if v > 0.0 => t.column_data.width = v,
|
|
||||||
"col_gutter" if v >= 0.0 => t.column_data.gutter = v,
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -541,7 +579,57 @@ fn apply_grip(t: &mut MText, grip_id: usize, apply: GripApply) {
|
||||||
let dx = p.x as f64 - t.insertion_point.x;
|
let dx = p.x as f64 - t.insertion_point.x;
|
||||||
let dy = p.y as f64 - t.insertion_point.y;
|
let dy = p.y as f64 - t.insertion_point.y;
|
||||||
let projected = dx * dir.x + dy * dir.y;
|
let projected = dx * dir.x + dy * dir.y;
|
||||||
t.rectangle_width = (projected / k).max(0.01);
|
let width = (projected / k).max(0.0);
|
||||||
|
if t.column_data.column_type != 0 && t.column_data.column_count > 1 {
|
||||||
|
let gaps = (t.column_data.column_count - 1) as f64;
|
||||||
|
t.column_data.width =
|
||||||
|
((width - t.column_data.gutter * gaps) / t.column_data.column_count as f64)
|
||||||
|
.max(0.01);
|
||||||
|
} else {
|
||||||
|
t.rectangle_width = width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(2, GripApply::Absolute(p)) => {
|
||||||
|
let (sin, cos) = t.rotation.sin_cos();
|
||||||
|
let down = glam::DVec3::new(sin, -cos, 0.0);
|
||||||
|
let delta = glam::DVec3::new(
|
||||||
|
p.x as f64 - t.insertion_point.x,
|
||||||
|
p.y as f64 - t.insertion_point.y,
|
||||||
|
p.z as f64 - t.insertion_point.z,
|
||||||
|
);
|
||||||
|
let (_, vertical) = attach_anchors(t);
|
||||||
|
let factor = match vertical {
|
||||||
|
MTextVAnchor::Top
|
||||||
|
| MTextVAnchor::MiddleOfTopLine
|
||||||
|
| MTextVAnchor::BottomOfTopLine => 1.0,
|
||||||
|
MTextVAnchor::Middle => 0.5,
|
||||||
|
MTextVAnchor::Bottom | MTextVAnchor::MiddleOfBottomLine => -1.0,
|
||||||
|
};
|
||||||
|
let height = (delta.dot(down) / factor).max(0.01);
|
||||||
|
t.rectangle_height = Some(height);
|
||||||
|
if t.column_data.column_type != 0 && !t.column_data.auto_height {
|
||||||
|
t.column_data.heights =
|
||||||
|
vec![height; t.column_data.column_count.max(1) as usize];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(3, GripApply::Absolute(p)) => {
|
||||||
|
let (dir, k) = width_grip_axis(t);
|
||||||
|
let delta = glam::DVec3::new(
|
||||||
|
p.x as f64 - t.insertion_point.x,
|
||||||
|
p.y as f64 - t.insertion_point.y,
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
t.column_data.width = (delta.dot(dir) / k).max(0.01);
|
||||||
|
}
|
||||||
|
(4, GripApply::Absolute(p)) => {
|
||||||
|
let (dir, k) = width_grip_axis(t);
|
||||||
|
let delta = glam::DVec3::new(
|
||||||
|
p.x as f64 - t.insertion_point.x,
|
||||||
|
p.y as f64 - t.insertion_point.y,
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
t.column_data.gutter =
|
||||||
|
(delta.dot(dir) / k - t.column_data.width).max(0.0);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,11 @@ fn to_render(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<Rende
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
line_spacing_factor: ctx.line_spacing_factor as f32,
|
line_spacing_factor: ctx.line_spacing_factor as f32,
|
||||||
|
exact_line_spacing: matches!(
|
||||||
|
ctx.line_spacing_style,
|
||||||
|
acadrust::entities::LineSpacingStyle::Exactly
|
||||||
|
),
|
||||||
|
rectangle_height: 0.0,
|
||||||
vertical_text: false,
|
vertical_text: false,
|
||||||
want_glyph_boxes: false,
|
want_glyph_boxes: false,
|
||||||
}))
|
}))
|
||||||
|
|
@ -412,6 +417,7 @@ fn text_box_geom(ml: &MultiLeader) -> ([f64; 2], [f64; 3]) {
|
||||||
oblique_angle: 0.0,
|
oblique_angle: 0.0,
|
||||||
is_backward: false,
|
is_backward: false,
|
||||||
is_upside_down: false,
|
is_upside_down: false,
|
||||||
|
is_vertical: false,
|
||||||
};
|
};
|
||||||
let layout = layout_mtext(&MTextRenderOpts {
|
let layout = layout_mtext(&MTextRenderOpts {
|
||||||
columns: Default::default(),
|
columns: Default::default(),
|
||||||
|
|
@ -424,6 +430,11 @@ fn text_box_geom(ml: &MultiLeader) -> ([f64; 2], [f64; 3]) {
|
||||||
attach_h_anchor: 0.0,
|
attach_h_anchor: 0.0,
|
||||||
v_anchor: MTextVAnchor::Top,
|
v_anchor: MTextVAnchor::Top,
|
||||||
line_spacing_factor: ctx.line_spacing_factor as f32,
|
line_spacing_factor: ctx.line_spacing_factor as f32,
|
||||||
|
exact_line_spacing: matches!(
|
||||||
|
ctx.line_spacing_style,
|
||||||
|
acadrust::entities::LineSpacingStyle::Exactly
|
||||||
|
),
|
||||||
|
rectangle_height: 0.0,
|
||||||
vertical_text: vertical,
|
vertical_text: vertical,
|
||||||
want_glyph_boxes: false,
|
want_glyph_boxes: false,
|
||||||
});
|
});
|
||||||
|
|
@ -2039,6 +2050,11 @@ impl MultiLeaderTess for MultiLeader {
|
||||||
attach_h_anchor: h_anchor,
|
attach_h_anchor: h_anchor,
|
||||||
v_anchor,
|
v_anchor,
|
||||||
line_spacing_factor: ctx.line_spacing_factor as f32,
|
line_spacing_factor: ctx.line_spacing_factor as f32,
|
||||||
|
exact_line_spacing: matches!(
|
||||||
|
ctx.line_spacing_style,
|
||||||
|
acadrust::entities::LineSpacingStyle::Exactly
|
||||||
|
),
|
||||||
|
rectangle_height: 0.0,
|
||||||
// Vertical flow (top-to-bottom) is stored per-context.
|
// Vertical flow (top-to-bottom) is stored per-context.
|
||||||
vertical_text: matches!(
|
vertical_text: matches!(
|
||||||
ctx.text_flow_direction,
|
ctx.text_flow_direction,
|
||||||
|
|
|
||||||
|
|
@ -1187,6 +1187,7 @@ impl RenderConvertible for Table {
|
||||||
oblique_angle: style.map(|s| s.oblique_angle as f32).unwrap_or(0.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_backward: style.map(|s| s.is_backward()).unwrap_or(false),
|
||||||
is_upside_down: style.map(|s| s.is_upside_down()).unwrap_or(false),
|
is_upside_down: style.map(|s| s.is_upside_down()).unwrap_or(false),
|
||||||
|
is_vertical: style.map(|s| s.is_vertical).unwrap_or(false),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1287,6 +1288,8 @@ impl RenderConvertible for Table {
|
||||||
attach_h_anchor,
|
attach_h_anchor,
|
||||||
v_anchor,
|
v_anchor,
|
||||||
line_spacing_factor: 1.0,
|
line_spacing_factor: 1.0,
|
||||||
|
exact_line_spacing: false,
|
||||||
|
rectangle_height: 0.0,
|
||||||
vertical_text: false,
|
vertical_text: false,
|
||||||
want_glyph_boxes: false,
|
want_glyph_boxes: false,
|
||||||
});
|
});
|
||||||
|
|
@ -1453,6 +1456,7 @@ pub fn tessellate_table(
|
||||||
oblique_angle: style.map(|s| s.oblique_angle as f32).unwrap_or(0.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_backward: style.map(|s| s.is_backward()).unwrap_or(false),
|
||||||
is_upside_down: style.map(|s| s.is_upside_down()).unwrap_or(false),
|
is_upside_down: style.map(|s| s.is_upside_down()).unwrap_or(false),
|
||||||
|
is_vertical: style.map(|s| s.is_vertical).unwrap_or(false),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1920,6 +1924,8 @@ pub fn tessellate_table(
|
||||||
attach_h_anchor,
|
attach_h_anchor,
|
||||||
v_anchor,
|
v_anchor,
|
||||||
line_spacing_factor: 1.0,
|
line_spacing_factor: 1.0,
|
||||||
|
exact_line_spacing: false,
|
||||||
|
rectangle_height: 0.0,
|
||||||
vertical_text: false,
|
vertical_text: false,
|
||||||
want_glyph_boxes: false,
|
want_glyph_boxes: false,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ pub struct ResolvedTextStyle {
|
||||||
pub oblique_angle: f32,
|
pub oblique_angle: f32,
|
||||||
pub is_backward: bool,
|
pub is_backward: bool,
|
||||||
pub is_upside_down: bool,
|
pub is_upside_down: bool,
|
||||||
|
pub is_vertical: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_text_style(style_name: &str, document: &CadDocument) -> ResolvedTextStyle {
|
pub fn resolve_text_style(style_name: &str, document: &CadDocument) -> ResolvedTextStyle {
|
||||||
|
|
@ -76,6 +77,7 @@ pub fn resolve_text_style(style_name: &str, document: &CadDocument) -> ResolvedT
|
||||||
oblique_angle: style.map(|s| s.oblique_angle as f32).unwrap_or(0.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_backward: style.map(|s| s.is_backward()).unwrap_or(false),
|
||||||
is_upside_down: style.map(|s| s.is_upside_down()).unwrap_or(false),
|
is_upside_down: style.map(|s| s.is_upside_down()).unwrap_or(false),
|
||||||
|
is_vertical: style.map(|s| s.is_vertical).unwrap_or(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1012,6 +1014,11 @@ pub struct MTextRenderOpts<'a> {
|
||||||
pub v_anchor: MTextVAnchor,
|
pub v_anchor: MTextVAnchor,
|
||||||
/// DXF code 44 — multiplier on the default 5/3-em baseline gap.
|
/// DXF code 44 — multiplier on the default 5/3-em baseline gap.
|
||||||
pub line_spacing_factor: f32,
|
pub line_spacing_factor: f32,
|
||||||
|
/// `true` fixes every baseline advance to the entity spacing. `false`
|
||||||
|
/// treats it as a minimum and expands for taller inline runs.
|
||||||
|
pub exact_line_spacing: bool,
|
||||||
|
/// User-defined text-box/column height. Zero means content-driven.
|
||||||
|
pub rectangle_height: f32,
|
||||||
/// `true` when the entity is laid out top-to-bottom (DXF code 71 = 2).
|
/// `true` when the entity is laid out top-to-bottom (DXF code 71 = 2).
|
||||||
pub vertical_text: bool,
|
pub vertical_text: bool,
|
||||||
/// When true, `layout_mtext` also fills `MTextLayout::glyph_boxes` with
|
/// When true, `layout_mtext` also fills `MTextLayout::glyph_boxes` with
|
||||||
|
|
@ -1025,10 +1032,9 @@ pub struct MTextRenderOpts<'a> {
|
||||||
|
|
||||||
/// An MTEXT's column layout, flattened from its `column_data`.
|
/// An MTEXT's column layout, flattened from its `column_data`.
|
||||||
///
|
///
|
||||||
/// Content moves to the next column at a `\N` break. Filling a column and
|
/// Content moves to the next column at an explicit `\N` break or when the
|
||||||
/// spilling into the next on its own is not modelled: `heights` / `auto_height`
|
/// configured manual/automatic column height is exhausted.
|
||||||
/// are not read, so a column runs as long as its content does.
|
#[derive(Clone, Debug, Default)]
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
pub struct MTextColumns {
|
pub struct MTextColumns {
|
||||||
/// Column count. 0 or 1 both mean "no column layout".
|
/// Column count. 0 or 1 both mean "no column layout".
|
||||||
pub count: usize,
|
pub count: usize,
|
||||||
|
|
@ -1036,6 +1042,12 @@ pub struct MTextColumns {
|
||||||
pub width: f32,
|
pub width: f32,
|
||||||
/// Gap between two adjacent columns, world units.
|
/// Gap between two adjacent columns, world units.
|
||||||
pub gutter: f32,
|
pub gutter: f32,
|
||||||
|
/// Whether logical column flow starts at the opposite side.
|
||||||
|
pub flow_reversed: bool,
|
||||||
|
/// Dynamic-column height is balanced from content when true.
|
||||||
|
pub auto_height: bool,
|
||||||
|
/// Optional manual height for each logical column.
|
||||||
|
pub heights: Vec<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MTextColumns {
|
impl MTextColumns {
|
||||||
|
|
@ -1046,7 +1058,12 @@ impl MTextColumns {
|
||||||
|
|
||||||
/// Left edge of column `i`, relative to the text block's own left edge.
|
/// Left edge of column `i`, relative to the text block's own left edge.
|
||||||
pub fn offset_of(&self, i: usize) -> f32 {
|
pub fn offset_of(&self, i: usize) -> f32 {
|
||||||
i as f32 * (self.width + self.gutter)
|
let physical = if self.flow_reversed {
|
||||||
|
self.count.saturating_sub(1).saturating_sub(i)
|
||||||
|
} else {
|
||||||
|
i
|
||||||
|
};
|
||||||
|
physical as f32 * (self.width + self.gutter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1128,7 +1145,7 @@ pub fn layout_mtext(opts: &MTextRenderOpts) -> MTextLayout {
|
||||||
line_spacing: Option<ParaLineSpacing>,
|
line_spacing: Option<ParaLineSpacing>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let cols = opts.columns;
|
let cols = opts.columns.clone();
|
||||||
// A `\N` past the last column has nowhere to go — keep those lines in the
|
// A `\N` past the last column has nowhere to go — keep those lines in the
|
||||||
// last one rather than laying them out beyond the block.
|
// last one rather than laying them out beyond the block.
|
||||||
let last_col = cols.count.saturating_sub(1);
|
let last_col = cols.count.saturating_sub(1);
|
||||||
|
|
@ -1366,15 +1383,55 @@ pub fn layout_mtext(opts: &MTextRenderOpts) -> MTextLayout {
|
||||||
};
|
};
|
||||||
// A paragraph's own `\psm#`/`\pse#` overrides the entity factor for
|
// A paragraph's own `\psm#`/`\pse#` overrides the entity factor for
|
||||||
// its lines; `Exact` fixes the baseline gap outright.
|
// its lines; `Exact` fixes the baseline gap outright.
|
||||||
|
let entity_gap = entity_h * ls_factor * (5.0 / 3.0) * base_font.line_spacing();
|
||||||
match s.line_spacing {
|
match s.line_spacing {
|
||||||
Some(ParaLineSpacing::Exact(e)) if e > 0.0 => e,
|
Some(ParaLineSpacing::Exact(e)) if e > 0.0 => e,
|
||||||
Some(ParaLineSpacing::Multiple(m)) if m > 0.0 => {
|
Some(ParaLineSpacing::Multiple(m)) if m > 0.0 => {
|
||||||
mh * m * (5.0 / 3.0) * base_font.line_spacing()
|
mh * m * (5.0 / 3.0) * base_font.line_spacing()
|
||||||
}
|
}
|
||||||
_ => mh * ls_factor * (5.0 / 3.0) * base_font.line_spacing(),
|
_ if opts.exact_line_spacing => entity_gap,
|
||||||
|
_ => (mh * ls_factor * (5.0 / 3.0) * base_font.line_spacing())
|
||||||
|
.max(entity_gap),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
// Flow lines into the next column when the active column's configured
|
||||||
|
// height is exhausted. Explicit `\N` breaks remain authoritative. Dynamic
|
||||||
|
// auto-height balances the content; static/manual columns use their stored
|
||||||
|
// height (falling back to the entity rectangle height).
|
||||||
|
if cols.active() {
|
||||||
|
let total_advance: f32 = per_line_h.iter().sum();
|
||||||
|
let balanced = (total_advance / cols.count.max(1) as f32).max(entity_h);
|
||||||
|
let mut current_column = 0usize;
|
||||||
|
let mut used = 0.0_f32;
|
||||||
|
for (index, line) in sub_lines.iter_mut().enumerate() {
|
||||||
|
if line.column > current_column {
|
||||||
|
current_column = line.column.min(cols.count - 1);
|
||||||
|
used = 0.0;
|
||||||
|
}
|
||||||
|
let limit = if cols.auto_height {
|
||||||
|
balanced
|
||||||
|
} else {
|
||||||
|
cols.heights
|
||||||
|
.get(current_column)
|
||||||
|
.copied()
|
||||||
|
.filter(|height| *height > 0.0)
|
||||||
|
.unwrap_or(opts.rectangle_height)
|
||||||
|
};
|
||||||
|
let advance = per_line_h.get(index).copied().unwrap_or(entity_h);
|
||||||
|
if limit > 0.0
|
||||||
|
&& used > 0.0
|
||||||
|
&& used + advance > limit
|
||||||
|
&& current_column + 1 < cols.count
|
||||||
|
{
|
||||||
|
current_column += 1;
|
||||||
|
used = 0.0;
|
||||||
|
}
|
||||||
|
line.column = current_column;
|
||||||
|
used += advance;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Per-line text height — the tallest Word on the line, an empty line
|
// Per-line text height — the tallest Word on the line, an empty line
|
||||||
// inheriting the previous line's height (same rule as `per_line_h`). Unlike
|
// inheriting the previous line's height (same rule as `per_line_h`). Unlike
|
||||||
// `line_max_h` it has no `entity_h` floor, so it reflects the ACTUAL text
|
// `line_max_h` it has no `entity_h` floor, so it reflects the ACTUAL text
|
||||||
|
|
@ -2169,6 +2226,7 @@ mod tests {
|
||||||
oblique_angle: 0.0,
|
oblique_angle: 0.0,
|
||||||
is_backward: false,
|
is_backward: false,
|
||||||
is_upside_down: false,
|
is_upside_down: false,
|
||||||
|
is_vertical: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2199,6 +2257,8 @@ mod tests {
|
||||||
attach_h_anchor: 0.0,
|
attach_h_anchor: 0.0,
|
||||||
v_anchor: MTextVAnchor::Top,
|
v_anchor: MTextVAnchor::Top,
|
||||||
line_spacing_factor: 1.0,
|
line_spacing_factor: 1.0,
|
||||||
|
exact_line_spacing: false,
|
||||||
|
rectangle_height: 0.0,
|
||||||
vertical_text: false,
|
vertical_text: false,
|
||||||
want_glyph_boxes: false,
|
want_glyph_boxes: false,
|
||||||
});
|
});
|
||||||
|
|
@ -2222,6 +2282,8 @@ mod tests {
|
||||||
attach_h_anchor: 0.0,
|
attach_h_anchor: 0.0,
|
||||||
v_anchor: MTextVAnchor::Top,
|
v_anchor: MTextVAnchor::Top,
|
||||||
line_spacing_factor: 1.0,
|
line_spacing_factor: 1.0,
|
||||||
|
exact_line_spacing: false,
|
||||||
|
rectangle_height: 0.0,
|
||||||
vertical_text: false,
|
vertical_text: false,
|
||||||
want_glyph_boxes: false,
|
want_glyph_boxes: false,
|
||||||
});
|
});
|
||||||
|
|
@ -2378,6 +2440,7 @@ mod v_anchor_tests {
|
||||||
oblique_angle: 0.0,
|
oblique_angle: 0.0,
|
||||||
is_backward: false,
|
is_backward: false,
|
||||||
is_upside_down: false,
|
is_upside_down: false,
|
||||||
|
is_vertical: false,
|
||||||
};
|
};
|
||||||
layout_mtext(&MTextRenderOpts {
|
layout_mtext(&MTextRenderOpts {
|
||||||
columns: Default::default(),
|
columns: Default::default(),
|
||||||
|
|
@ -2390,6 +2453,8 @@ mod v_anchor_tests {
|
||||||
attach_h_anchor: 0.0,
|
attach_h_anchor: 0.0,
|
||||||
v_anchor: MTextVAnchor::Top,
|
v_anchor: MTextVAnchor::Top,
|
||||||
line_spacing_factor: 1.0,
|
line_spacing_factor: 1.0,
|
||||||
|
exact_line_spacing: false,
|
||||||
|
rectangle_height: 0.0,
|
||||||
vertical_text: false,
|
vertical_text: false,
|
||||||
want_glyph_boxes: true,
|
want_glyph_boxes: true,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
4
src/scene/cache/properties.rs
vendored
4
src/scene/cache/properties.rs
vendored
|
|
@ -94,7 +94,9 @@ pub fn general_section(entity: &EntityType) -> PropSection {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
if matches!(entity, EntityType::LwPolyline(polyline) if crate::entities::lwpolyline::is_rectangle(polyline)) {
|
if matches!(entity, EntityType::LwPolyline(polyline) if crate::entities::lwpolyline::is_rectangle(polyline))
|
||||||
|
|| matches!(entity, EntityType::MText(_))
|
||||||
|
{
|
||||||
section.props.retain(|prop| prop.field != "handle");
|
section.props.retain(|prop| prop.field != "handle");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue