fix: correct viewport stretch and SHX plots
Anchor the opposite viewport corner during grip and STRETCH resizing. Match plotted stroke-font pen widths to the SDF atlas unless CTB overrides. Closes #435
This commit is contained in:
parent
ceddb287f5
commit
ece5598372
4 changed files with 127 additions and 26 deletions
|
|
@ -1677,6 +1677,10 @@ impl OpenCADStudio {
|
|||
stretched = true;
|
||||
}
|
||||
}
|
||||
acadrust::EntityType::Viewport(vp) => {
|
||||
stretched =
|
||||
crate::entities::viewport::stretch(vp, win_min, win_max, delta);
|
||||
}
|
||||
acadrust::EntityType::Dimension(dim) => {
|
||||
use acadrust::entities::Dimension;
|
||||
// Move every definition point that falls inside the
|
||||
|
|
|
|||
|
|
@ -351,24 +351,96 @@ fn apply_grip(vp: &mut Viewport, grip_id: usize, apply: GripApply) {
|
|||
vp.center.z = p.z as f64;
|
||||
}
|
||||
(1..=4, GripApply::Absolute(p)) => {
|
||||
let new_hw = (p.x as f64 - vp.center.x).abs();
|
||||
let new_hh = (p.y as f64 - vp.center.y).abs();
|
||||
if new_hw > 0.01 {
|
||||
vp.width = new_hw * 2.0;
|
||||
}
|
||||
if new_hh > 0.01 {
|
||||
let new_h = new_hh * 2.0;
|
||||
// Keep scale constant: view_height must scale with the viewport height.
|
||||
if vp.height > 1e-9 && vp.view_height.abs() > 1e-9 {
|
||||
vp.view_height = vp.view_height * (new_h / vp.height);
|
||||
}
|
||||
vp.height = new_h;
|
||||
}
|
||||
let hw = vp.width * 0.5;
|
||||
let hh = vp.height * 0.5;
|
||||
let opposite = match grip_id {
|
||||
1 => (vp.center.x - hw, vp.center.y - hh),
|
||||
2 => (vp.center.x + hw, vp.center.y - hh),
|
||||
3 => (vp.center.x + hw, vp.center.y + hh),
|
||||
4 => (vp.center.x - hw, vp.center.y + hh),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
resize_from_corners(vp, opposite, (p.x as f64, p.y as f64));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stretch a rectangular paper-space viewport by the corners captured by a
|
||||
/// crossing window. Bounds that have no captured corner stay anchored.
|
||||
pub fn stretch(
|
||||
vp: &mut Viewport,
|
||||
win_min: glam::DVec3,
|
||||
win_max: glam::DVec3,
|
||||
delta: glam::DVec3,
|
||||
) -> bool {
|
||||
let hw = vp.width * 0.5;
|
||||
let hh = vp.height * 0.5;
|
||||
let (left, right) = (vp.center.x - hw, vp.center.x + hw);
|
||||
let (bottom, top) = (vp.center.y - hh, vp.center.y + hh);
|
||||
let inside =
|
||||
|x: f64, y: f64| x >= win_min.x && x <= win_max.x && y >= win_min.y && y <= win_max.y;
|
||||
|
||||
let bottom_left = inside(left, bottom);
|
||||
let bottom_right = inside(right, bottom);
|
||||
let top_left = inside(left, top);
|
||||
let top_right = inside(right, top);
|
||||
if !(bottom_left || bottom_right || top_left || top_right) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let moved_left = if bottom_left || top_left {
|
||||
left + delta.x
|
||||
} else {
|
||||
left
|
||||
};
|
||||
let moved_right = if bottom_right || top_right {
|
||||
right + delta.x
|
||||
} else {
|
||||
right
|
||||
};
|
||||
let moved_bottom = if bottom_left || bottom_right {
|
||||
bottom + delta.y
|
||||
} else {
|
||||
bottom
|
||||
};
|
||||
let moved_top = if top_left || top_right {
|
||||
top + delta.y
|
||||
} else {
|
||||
top
|
||||
};
|
||||
resize_from_corners(vp, (moved_left, moved_bottom), (moved_right, moved_top))
|
||||
}
|
||||
|
||||
/// Rebuild center and size from two opposite corners while preserving viewport
|
||||
/// scale as its paper-space height changes.
|
||||
fn resize_from_corners(vp: &mut Viewport, a: (f64, f64), b: (f64, f64)) -> bool {
|
||||
let new_width = (b.0 - a.0).abs();
|
||||
let new_height = (b.1 - a.1).abs();
|
||||
let mut changed = false;
|
||||
|
||||
if new_width > 0.01 {
|
||||
let new_center_x = (a.0 + b.0) * 0.5;
|
||||
changed |=
|
||||
(vp.center.x - new_center_x).abs() > 1e-12 || (vp.width - new_width).abs() > 1e-12;
|
||||
vp.center.x = new_center_x;
|
||||
vp.width = new_width;
|
||||
}
|
||||
if new_height > 0.01 {
|
||||
let old_height = vp.height;
|
||||
let new_center_y = (a.1 + b.1) * 0.5;
|
||||
changed |=
|
||||
(vp.center.y - new_center_y).abs() > 1e-12 || (old_height - new_height).abs() > 1e-12;
|
||||
vp.center.y = new_center_y;
|
||||
if old_height > 1e-9 && vp.view_height.abs() > 1e-9 {
|
||||
vp.view_height *= new_height / old_height;
|
||||
}
|
||||
vp.height = new_height;
|
||||
}
|
||||
|
||||
changed
|
||||
}
|
||||
|
||||
fn apply_transform(vp: &mut Viewport, t: &EntityTransform) {
|
||||
crate::scene::view::transform::apply_standard_entity_transform(vp, t, |entity, p1, p2| {
|
||||
crate::scene::view::transform::reflect_xy_point(
|
||||
|
|
|
|||
|
|
@ -635,9 +635,6 @@ fn emit_text(
|
|||
.map(|mm| (mm * MM_TO_PT).max(0.1) / scale.max(1e-6));
|
||||
}
|
||||
}
|
||||
let lw_pt = lw_override
|
||||
.unwrap_or_else(|| (wire.line_weight_px * LW_PX_TO_PT).max(0.1) / scale.max(1e-6));
|
||||
|
||||
let mut gi = 0;
|
||||
while gi + 6 <= verts.len() {
|
||||
let quad = &verts[gi..gi + 6];
|
||||
|
|
@ -703,13 +700,33 @@ fn emit_text(
|
|||
});
|
||||
}
|
||||
} else {
|
||||
// Stroke (LFF pen) font or hollow glyph: polylines. Bold bakes
|
||||
// at a 1.7× pen over the same centrelines, so widen to match or
|
||||
// a bold run prints at regular weight.
|
||||
// Stroke (LFF/SHX pen) font or hollow glyph: polylines.
|
||||
// Match the SDF atlas' nominal glyph-space pen instead of
|
||||
// borrowing the entity lineweight: Roman Duplex and similar
|
||||
// multi-stroke faces rely on that band to close the narrow
|
||||
// gaps between parallel centrelines. An explicit CTB
|
||||
// lineweight still wins and stays absolute under the plot CTM.
|
||||
ops.push(Op::SetOutlineColor {
|
||||
col: Color::Rgb(Rgb { r, g, b, icc_profile: None }),
|
||||
col: Color::Rgb(Rgb {
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
icc_profile: None,
|
||||
}),
|
||||
});
|
||||
let pen = if ge.bold { lw_pt * 1.7 } else { lw_pt };
|
||||
let pen = if let Some(ctb_pen) = lw_override {
|
||||
if ge.bold {
|
||||
ctb_pen * 1.7
|
||||
} else {
|
||||
ctb_pen
|
||||
}
|
||||
} else {
|
||||
let glyph_unit_mm = (((tl[0] - bl[0]).powi(2) + (tl[1] - bl[1]).powi(2))
|
||||
.sqrt()
|
||||
/ sy.abs() as f64) as f32;
|
||||
(2.0 * sdf_atlas::stroke_pen_half_units(ge.bold) * glyph_unit_mm * MM_TO_PT)
|
||||
.max(0.1)
|
||||
};
|
||||
ops.push(Op::SetOutlineThickness { pt: Pt(pen) });
|
||||
for stroke in &ge.strokes {
|
||||
if stroke.len() < 2 {
|
||||
|
|
|
|||
|
|
@ -105,6 +105,18 @@ const SPREAD_UNITS: f32 = 1.5;
|
|||
/// built around. Scales with text height like the strokes themselves.
|
||||
const PEN_HALF_UNITS: f32 = 0.35;
|
||||
|
||||
/// Nominal half-width of the SDF pen for a stroke glyph.
|
||||
///
|
||||
/// CPU exporters cannot sample the atlas, so they use this to stroke the same
|
||||
/// LFF/SHX centrelines at the thickness shown on screen.
|
||||
pub(crate) fn stroke_pen_half_units(bold: bool) -> f32 {
|
||||
if bold {
|
||||
PEN_HALF_UNITS * 1.7
|
||||
} else {
|
||||
PEN_HALF_UNITS
|
||||
}
|
||||
}
|
||||
|
||||
/// Upper bound on a single glyph tile's dimension (texels), a guard against a
|
||||
/// pathologically wide/tall glyph blowing up the atlas.
|
||||
const MAX_TILE_PX: u32 = 512;
|
||||
|
|
@ -264,11 +276,7 @@ impl GlyphAtlas {
|
|||
return *cached;
|
||||
}
|
||||
// Bold stroke glyphs bake with a wider pen band (same shapes, thicker).
|
||||
let pen_half = if bold {
|
||||
PEN_HALF_UNITS * 1.7
|
||||
} else {
|
||||
PEN_HALF_UNITS
|
||||
};
|
||||
let pen_half = stroke_pen_half_units(bold);
|
||||
let face = Face::resolve(family);
|
||||
let entry = face
|
||||
.glyph(ch)
|
||||
|
|
|
|||
Loading…
Reference in a new issue