forked from 1b-agent/opencadstudio-upstream
Merge pull request #1340 from CiberNin/fix/editor-crashes
Fix editor crashes from typed input, text, and degenerate geometry
This commit is contained in:
commit
3c1ad62e9b
12 files changed
+391
-27
No files matched your search
@@ -283,6 +283,20 @@ fn surveyor(value_rad: f64, prec: usize) -> String {
|
||||
/// A leading `-` is a sign, but the `-` inside `5'-9"` is a separator — after
|
||||
/// feet there is nothing left to subtract from.
|
||||
pub fn parse_length(text: &str) -> Option<f64> {
|
||||
parse_length_unbounded(text).filter(|value| typed_value_in_range(*value))
|
||||
}
|
||||
|
||||
/// Largest magnitude a typed length or angle may have. Rust's float parser
|
||||
/// accepts `inf`, `nan` and overflowing exponents; geometry built from those,
|
||||
/// or from values whose differences overflow, breaks downstream tolerance and
|
||||
/// intersection math.
|
||||
pub const MAX_TYPED_MAGNITUDE: f64 = 1e15;
|
||||
|
||||
pub fn typed_value_in_range(value: f64) -> bool {
|
||||
value.is_finite() && value.abs() <= MAX_TYPED_MAGNITUDE
|
||||
}
|
||||
|
||||
fn parse_length_unbounded(text: &str) -> Option<f64> {
|
||||
let text = text.trim();
|
||||
let (sign, rest) = match text.strip_prefix('-') {
|
||||
Some(rest) => (-1.0, rest.trim_start()),
|
||||
@@ -339,6 +353,10 @@ fn parse_inches(text: &str) -> Option<f64> {
|
||||
/// number is read in whatever convention the drawing is set to, so what the
|
||||
/// readout shows can be typed straight back.
|
||||
pub fn parse_angle(text: &str) -> Option<f64> {
|
||||
parse_angle_unbounded(text).filter(|value| typed_value_in_range(*value))
|
||||
}
|
||||
|
||||
fn parse_angle_unbounded(text: &str) -> Option<f64> {
|
||||
let text = text.trim();
|
||||
if text.is_empty() {
|
||||
return None;
|
||||
@@ -679,11 +697,16 @@ pub fn parse_f64(value: &str) -> Option<f64> {
|
||||
.ok()
|
||||
.or_else(|| parse_length(t))
|
||||
.or_else(|| parse_angle_deg(t))
|
||||
.filter(|value| typed_value_in_range(*value))
|
||||
}
|
||||
|
||||
/// Parse an angle string the panel displayed via AUNITS back to DEGREES:
|
||||
/// "30", "30°"/"30d", DMS "30°15'20.5\"", grads "33.33g", radians "0.52r".
|
||||
pub fn parse_angle_deg(value: &str) -> Option<f64> {
|
||||
parse_angle_deg_unbounded(value).filter(|value| typed_value_in_range(*value))
|
||||
}
|
||||
|
||||
fn parse_angle_deg_unbounded(value: &str) -> Option<f64> {
|
||||
let s = value.trim();
|
||||
if s.is_empty() {
|
||||
return None;
|
||||
@@ -1161,6 +1184,24 @@ pub(crate) fn polyline_segment_fill(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod typed_range_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn non_finite_and_huge_typed_values_are_rejected() {
|
||||
for text in ["inf", "-inf", "nan", "1e400", "1e308"] {
|
||||
assert_eq!(parse_length(text), None, "length {text}");
|
||||
assert_eq!(parse_angle(text), None, "angle {text}");
|
||||
assert_eq!(parse_f64(text), None, "property {text}");
|
||||
assert_eq!(parse_angle_deg(text), None, "angle degrees {text}");
|
||||
}
|
||||
assert_eq!(parse_length("1e16"), None);
|
||||
assert_eq!(parse_length("1e15"), Some(1e15));
|
||||
assert_eq!(parse_length("-5'-6\""), Some(-66.0));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod length_format_tests {
|
||||
use super::*;
|
||||
|
||||
+28
-1
@@ -80,7 +80,14 @@ fn boundary_edges(points: &[[f64; 3]]) -> Vec<Edge> {
|
||||
}
|
||||
|
||||
fn kernel_tolerance(points: &[[f64; 3]]) -> f64 {
|
||||
cadkernel::space::coplanarity_tolerance(points).max(1.0e-12)
|
||||
let tolerance = cadkernel::space::coplanarity_tolerance(points);
|
||||
// Corners whose extent overflows f64 give an infinite tolerance, which
|
||||
// `Tolerance::new` rejects by panicking.
|
||||
if tolerance.is_finite() {
|
||||
tolerance.max(1.0e-12)
|
||||
} else {
|
||||
1.0e-12
|
||||
}
|
||||
}
|
||||
|
||||
fn extrusion_body(
|
||||
@@ -366,3 +373,23 @@ impl Transformable for Solid {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod overflow_tests {
|
||||
use super::*;
|
||||
use acadrust::types::Vector3;
|
||||
|
||||
/// Corners 1e308 apart overflow the coplanarity tolerance to infinity,
|
||||
/// which the kernel's `Tolerance::new` rejects by panicking.
|
||||
#[test]
|
||||
fn corners_with_overflowing_extent_do_not_panic() {
|
||||
let solid = Solid::new(
|
||||
Vector3::new(0.0, 0.0, 0.0),
|
||||
Vector3::new(1e308, 0.0, 0.0),
|
||||
Vector3::new(0.0, 1e308, 0.0),
|
||||
Vector3::new(1e308, 1e308, 0.0),
|
||||
);
|
||||
let _ = wcs_bounds(&solid);
|
||||
let _ = solid_geometry(&solid);
|
||||
}
|
||||
}
|
||||
@@ -798,9 +798,11 @@ pub fn reorder_line_atoms(atoms: Vec<LayoutAtom>, is_rtl: bool) -> Vec<LayoutAto
|
||||
.iter()
|
||||
.map(|r| {
|
||||
let mut lvl = None;
|
||||
for i in r.clone() {
|
||||
// `bidi.levels` is indexed by byte, so walk characters rather than
|
||||
// bytes to avoid slicing inside a multi-byte character.
|
||||
for (offset, c) in line_text[r.clone()].char_indices() {
|
||||
let i = r.start + offset;
|
||||
if i < bidi.levels.len() {
|
||||
let c = line_text[i..].chars().next().unwrap_or(' ');
|
||||
match unicode_bidi::bidi_class(c) {
|
||||
unicode_bidi::BidiClass::L
|
||||
| unicode_bidi::BidiClass::R
|
||||
@@ -2966,6 +2968,12 @@ mod v_anchor_tests {
|
||||
reordered_b,
|
||||
vec!["2026 ", "is ", "here!", "שנה ", "שלום ", "مرحبا! ", "ہے۔ ", "اردو ", "یہ "]
|
||||
);
|
||||
|
||||
// Case 6: a word starting with multi-byte neutral punctuation must
|
||||
// not be sliced inside that character.
|
||||
let words6 = ["שלום", " ", "«x"];
|
||||
let reordered6 = reorder_atoms(&words6, false);
|
||||
assert_eq!(reordered6, vec!["שלום", " ", "«x"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user