forked from 1b-agent/opencadstudio-upstream
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:
1 parent
ceddb287f5
commit
ece5598372
4 files changed
+127
-26
No files matched your search
@@ -1677,6 +1677,10 @@ impl OpenCADStudio {
|
|||||||
stretched = true;
|
stretched = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
acadrust::EntityType::Viewport(vp) => {
|
||||||
|
stretched =
|
||||||
|
crate::entities::viewport::stretch(vp, win_min, win_max, delta);
|
||||||
|
}
|
||||||
acadrust::EntityType::Dimension(dim) => {
|
acadrust::EntityType::Dimension(dim) => {
|
||||||
use acadrust::entities::Dimension;
|
use acadrust::entities::Dimension;
|
||||||
// Move every definition point that falls inside the
|
// Move every definition point that falls inside the
|
||||||
|
|||||||
+85
-13
@@ -351,24 +351,96 @@ fn apply_grip(vp: &mut Viewport, grip_id: usize, apply: GripApply) {
|
|||||||
vp.center.z = p.z as f64;
|
vp.center.z = p.z as f64;
|
||||||
}
|
}
|
||||||
(1..=4, GripApply::Absolute(p)) => {
|
(1..=4, GripApply::Absolute(p)) => {
|
||||||
let new_hw = (p.x as f64 - vp.center.x).abs();
|
let hw = vp.width * 0.5;
|
||||||
let new_hh = (p.y as f64 - vp.center.y).abs();
|
let hh = vp.height * 0.5;
|
||||||
if new_hw > 0.01 {
|
let opposite = match grip_id {
|
||||||
vp.width = new_hw * 2.0;
|
1 => (vp.center.x - hw, vp.center.y - hh),
|
||||||
}
|
2 => (vp.center.x + hw, vp.center.y - hh),
|
||||||
if new_hh > 0.01 {
|
3 => (vp.center.x + hw, vp.center.y + hh),
|
||||||
let new_h = new_hh * 2.0;
|
4 => (vp.center.x - hw, vp.center.y + hh),
|
||||||
// Keep scale constant: view_height must scale with the viewport height.
|
_ => unreachable!(),
|
||||||
if vp.height > 1e-9 && vp.view_height.abs() > 1e-9 {
|
};
|
||||||
vp.view_height = vp.view_height * (new_h / vp.height);
|
resize_from_corners(vp, opposite, (p.x as f64, p.y as f64));
|
||||||
}
|
|
||||||
vp.height = new_h;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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) {
|
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::apply_standard_entity_transform(vp, t, |entity, p1, p2| {
|
||||||
crate::scene::view::transform::reflect_xy_point(
|
crate::scene::view::transform::reflect_xy_point(
|
||||||
|
|||||||
+25
-8
@@ -635,9 +635,6 @@ fn emit_text(
|
|||||||
.map(|mm| (mm * MM_TO_PT).max(0.1) / scale.max(1e-6));
|
.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;
|
let mut gi = 0;
|
||||||
while gi + 6 <= verts.len() {
|
while gi + 6 <= verts.len() {
|
||||||
let quad = &verts[gi..gi + 6];
|
let quad = &verts[gi..gi + 6];
|
||||||
@@ -703,13 +700,33 @@ fn emit_text(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Stroke (LFF pen) font or hollow glyph: polylines. Bold bakes
|
// Stroke (LFF/SHX pen) font or hollow glyph: polylines.
|
||||||
// at a 1.7× pen over the same centrelines, so widen to match or
|
// Match the SDF atlas' nominal glyph-space pen instead of
|
||||||
// a bold run prints at regular weight.
|
// 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 {
|
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) });
|
ops.push(Op::SetOutlineThickness { pt: Pt(pen) });
|
||||||
for stroke in &ge.strokes {
|
for stroke in &ge.strokes {
|
||||||
if stroke.len() < 2 {
|
if stroke.len() < 2 {
|
||||||
|
|||||||
@@ -105,6 +105,18 @@ const SPREAD_UNITS: f32 = 1.5;
|
|||||||
/// built around. Scales with text height like the strokes themselves.
|
/// built around. Scales with text height like the strokes themselves.
|
||||||
const PEN_HALF_UNITS: f32 = 0.35;
|
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
|
/// Upper bound on a single glyph tile's dimension (texels), a guard against a
|
||||||
/// pathologically wide/tall glyph blowing up the atlas.
|
/// pathologically wide/tall glyph blowing up the atlas.
|
||||||
const MAX_TILE_PX: u32 = 512;
|
const MAX_TILE_PX: u32 = 512;
|
||||||
@@ -264,11 +276,7 @@ impl GlyphAtlas {
|
|||||||
return *cached;
|
return *cached;
|
||||||
}
|
}
|
||||||
// Bold stroke glyphs bake with a wider pen band (same shapes, thicker).
|
// Bold stroke glyphs bake with a wider pen band (same shapes, thicker).
|
||||||
let pen_half = if bold {
|
let pen_half = stroke_pen_half_units(bold);
|
||||||
PEN_HALF_UNITS * 1.7
|
|
||||||
} else {
|
|
||||||
PEN_HALF_UNITS
|
|
||||||
};
|
|
||||||
let face = Face::resolve(family);
|
let face = Face::resolve(family);
|
||||||
let entry = face
|
let entry = face
|
||||||
.glyph(ch)
|
.glyph(ch)
|
||||||
|
|||||||
Reference in new issue
Block a user