mirror of
https://github.com/storytold/cadcraft.git
synced 2026-11-10 12:30:16 -06:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9d4c37021 | ||
|
|
e91d6d7625 |
@@ -157,7 +157,7 @@ fn write_audit_log(_: &Session, _: &crate::audit::Report) {}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn read_path(cmd: &str, path: &str) -> Result<Vec<u8>> {
|
||||
crate::capped_read::read_capped(std::path::Path::new(path), file::MAX_DRAWING_BYTES).map_err(|e| bad(cmd, format!("{path}: {e}")))
|
||||
std::fs::read(path).map_err(|e| bad(cmd, format!("{path}: {e}")))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
||||
@@ -85,8 +85,7 @@ fn read_drawing(path: &str) -> Result<Drawing> {
|
||||
fn read_file(_path: &str) -> Result<Vec<u8>> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
crate::capped_read::read_capped(std::path::Path::new(_path), crate::cmd::file::MAX_DRAWING_BYTES)
|
||||
.map_err(|e| bad("insert", format!("{_path}: {e}")))
|
||||
std::fs::read(_path).map_err(|e| bad("insert", format!("{_path}: {e}")))
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Err(bad("insert", "paths are not available on the web"))
|
||||
|
||||
@@ -204,8 +204,7 @@ fn import_drawing(s: &mut Session, src: &Drawing, at: Vec2, scale: f64, file: &s
|
||||
fn read_file(_path: &str) -> Result<Vec<u8>> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
crate::capped_read::read_capped(std::path::Path::new(_path), crate::cmd::file::MAX_DRAWING_BYTES)
|
||||
.map_err(|e| bad("import", format!("{_path}: {e}")))
|
||||
std::fs::read(_path).map_err(|e| bad("import", format!("{_path}: {e}")))
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Err(bad("import", "paths are not available on the web; pass `data`"))
|
||||
|
||||
@@ -24,7 +24,7 @@ pub use template::{qnew_template, set_qnew_template};
|
||||
/// endless device must be rejected before it exhausts memory (CWE-400), so native opens read
|
||||
/// through [`crate::capped_read`] rather than buffering to EOF.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) const MAX_DRAWING_BYTES: usize = 512 << 20;
|
||||
const MAX_DRAWING_BYTES: usize = 512 << 20;
|
||||
/// Largest `.scr` script accepted from a local path.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const MAX_SCRIPT_BYTES: usize = 64 << 20;
|
||||
|
||||
@@ -110,7 +110,7 @@ fn other_drawing(s: &Session, p: &Value) -> Result<(Arc<Drawing>, String)> {
|
||||
fn read_path(_path: &str) -> Result<Vec<u8>> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
crate::capped_read::read_capped(std::path::Path::new(_path), super::MAX_DRAWING_BYTES).map_err(|e| bad("compare", format!("{_path}: {e}")))
|
||||
std::fs::read(_path).map_err(|e| bad("compare", format!("{_path}: {e}")))
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Err(bad("compare", "paths are not available on the web: open the other drawing and compare with it by name (`document`)"))
|
||||
|
||||
@@ -73,7 +73,7 @@ fn read_template(path: &str) -> Result<Drawing> {
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn read_bytes(path: &str) -> Result<Vec<u8>> {
|
||||
crate::capped_read::read_capped(std::path::Path::new(path), super::MAX_DRAWING_BYTES).map_err(|e| bad("new", format!("template {path}: {e}")))
|
||||
std::fs::read(path).map_err(|e| bad("new", format!("template {path}: {e}")))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
||||
@@ -303,9 +303,11 @@ fn run_stylesmanager(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
(None, Some(_path)) => {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let bytes = crate::capped_read::read_capped(std::path::Path::new(_path), cadcraft_doc::MAX_TABLE_FILE)
|
||||
.map_err(|e| bad(cmd, format!("{_path}: {e}")))?;
|
||||
String::from_utf8(bytes).map_err(|e| bad(cmd, format!("{_path}: {e}")))?
|
||||
let meta = std::fs::metadata(_path).map_err(|e| bad(cmd, format!("{_path}: {e}")))?;
|
||||
if meta.len() > cadcraft_doc::MAX_TABLE_FILE as u64 {
|
||||
return Err(bad(cmd, format!("{_path}: the plot style table file is too large")));
|
||||
}
|
||||
std::fs::read_to_string(_path).map_err(|e| bad(cmd, format!("{_path}: {e}")))?
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
return Err(bad(cmd, "files can't be read here: pass `text`"));
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
|
||||
|
||||
/// Largest linked file read (bytes).
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const MAX_LINK_BYTES: usize = 8 << 20;
|
||||
const MAX_LINK_BYTES: u64 = 8 << 20;
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
@@ -238,9 +238,11 @@ fn read_link(id: &str, file: &std::path::Path) -> Result<String> {
|
||||
let path = file.display();
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
// The limit is enforced on the bytes read, not on reported metadata, so a device or FIFO
|
||||
// that misreports its length can't be buffered to EOF.
|
||||
let bytes = crate::capped_read::read_capped(file, MAX_LINK_BYTES).map_err(|e| bad(id, format!("{path}: {e}")))?;
|
||||
let meta = std::fs::metadata(file).map_err(|e| bad(id, format!("{path}: {e}")))?;
|
||||
if meta.len() > MAX_LINK_BYTES {
|
||||
return Err(bad(id, format!("{path}: larger than {} MB", MAX_LINK_BYTES >> 20)));
|
||||
}
|
||||
let bytes = std::fs::read(file).map_err(|e| bad(id, format!("{path}: {e}")))?;
|
||||
let text = String::from_utf8_lossy(&bytes);
|
||||
Ok(text.strip_prefix('\u{feff}').unwrap_or(&text).to_owned())
|
||||
}
|
||||
@@ -368,16 +370,6 @@ fn run_update(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// A linked path naming an endless device is refused instead of buffered to EOF.
|
||||
#[cfg(all(unix, not(target_arch = "wasm32")))]
|
||||
#[test]
|
||||
fn link_to_endless_device_is_rejected() {
|
||||
let dev = std::path::Path::new("/dev/zero");
|
||||
if dev.exists() {
|
||||
assert!(read_link("datalink", dev).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn csv_parsing() {
|
||||
assert_eq!(parse_csv("a,b\r\n\"c,d\",\"e\"\"f\"\n", ','), vec![vec!["a", "b"], vec!["c,d", "e\"f"]]);
|
||||
|
||||
@@ -484,6 +484,9 @@ pub struct Polyline {
|
||||
pub closed: bool,
|
||||
}
|
||||
|
||||
/// Sampled points one [`Polyline::tessellate`] call produces before arcs become chords.
|
||||
pub const MAX_TESSELLATED_POINTS: usize = 4_000_000;
|
||||
|
||||
impl Polyline {
|
||||
pub fn from_points(pts: &[Vec2], closed: bool) -> Self {
|
||||
Polyline { vertices: pts.iter().map(|p| PolyVertex::new(*p)).collect(), closed }
|
||||
@@ -516,10 +519,24 @@ impl Polyline {
|
||||
b
|
||||
}
|
||||
pub fn tessellate(&self, tol: f64) -> Vec<Vec2> {
|
||||
let mut budget = MAX_TESSELLATED_POINTS;
|
||||
self.tessellate_within(tol, &mut budget)
|
||||
}
|
||||
/// [`Polyline::tessellate`] spending about `budget` sampled points (shared by the caller
|
||||
/// across many polylines). The budget is checked before each segment, so the last segment
|
||||
/// started may overshoot it by that one segment's samples. Once it is spent, the remaining arcs are drawn as chords, so the
|
||||
/// output stays proportional to the input vertices. Each arc can sample thousands of points,
|
||||
/// so a compact polyline could otherwise expand into gigabytes (CWE-400).
|
||||
pub fn tessellate_within(&self, tol: f64, budget: &mut usize) -> Vec<Vec2> {
|
||||
let mut out: Vec<Vec2> = Vec::new();
|
||||
for s in self.segments() {
|
||||
let mut pts = Vec::new();
|
||||
s.tessellate(tol, &mut pts);
|
||||
if *budget > 0 {
|
||||
s.tessellate(tol, &mut pts);
|
||||
*budget = budget.saturating_sub(pts.len());
|
||||
} else {
|
||||
pts.extend([s.start(), s.end()]);
|
||||
}
|
||||
let skip = usize::from(out.last().is_some_and(|l| pts.first().is_some_and(|f| l.near(*f, 1e-9))));
|
||||
out.extend(pts.into_iter().skip(skip));
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ mod vec;
|
||||
|
||||
pub use bounds::Bounds2;
|
||||
pub use curve::{
|
||||
Arc, Circle, Ellipse, Line, PolyVertex, Polyline, Segment, arc_to_bulge, bulge_to_arc, point_in_polygon, polygon_centroid, shoelace,
|
||||
Arc, Circle, Ellipse, Line, MAX_TESSELLATED_POINTS, PolyVertex, Polyline, Segment, arc_to_bulge, bulge_to_arc, point_in_polygon,
|
||||
polygon_centroid, shoelace,
|
||||
};
|
||||
pub use fillet::{CornerFit, TanCurve, tangent_circles};
|
||||
pub use intersect::{circle_circle, intersect_ext, intersect_segments, line_circle, line_line, line_line_infinite};
|
||||
|
||||
@@ -221,6 +221,18 @@ fn segment_offset() {
|
||||
assert!(s.offset(6.0).is_none());
|
||||
}
|
||||
|
||||
/// A polyline of many fine arcs samples at most the point budget, then draws chords.
|
||||
#[test]
|
||||
fn polyline_tessellation_is_bounded() {
|
||||
let vertices: Vec<PolyVertex> = (0..3000).map(|i| PolyVertex { bulge: 1.0, ..PolyVertex::new(Vec2::new(f64::from(i) * 1e6, 0.0)) }).collect();
|
||||
let pl = Polyline { vertices, closed: false };
|
||||
let pts = pl.tessellate(1e-9);
|
||||
assert!(pts.len() <= MAX_TESSELLATED_POINTS + 2 * 3000 + 4096, "{}", pts.len());
|
||||
assert!(pts.len() > 3000);
|
||||
let mut budget = 10;
|
||||
assert!(pl.tessellate_within(1e-9, &mut budget).len() <= 4096 + 2 * 3000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arc_segments_bounded() {
|
||||
assert_eq!(arc_segments(0.0, 1.0, 0.1), 1);
|
||||
|
||||
@@ -128,7 +128,10 @@ const MAX_DEDUP_COMPARISONS: u64 = 50_000_000;
|
||||
/// within islands alternate), 1 outer keeps the outermost loops and their first islands, 2
|
||||
/// ignore keeps only the outermost loops. Nesting depth = how many other loops enclose a loop.
|
||||
pub fn fill_loops(h: &Hatch, tol: f64) -> Vec<Vec<Vec2>> {
|
||||
let mut loops: Vec<Vec<Vec2>> = h.loops.iter().map(|l| Polyline { vertices: l.vertices.clone(), closed: true }.tessellate(tol)).collect();
|
||||
// One point budget for all the loops: many short arcs across loops must not add up unbounded.
|
||||
let mut budget = cadcraft_geom::MAX_TESSELLATED_POINTS;
|
||||
let mut loops: Vec<Vec<Vec2>> =
|
||||
h.loops.iter().map(|l| Polyline { vertices: l.vertices.clone(), closed: true }.tessellate_within(tol, &mut budget)).collect();
|
||||
dedup_polygons(&mut loops);
|
||||
let max_depth = match h.style {
|
||||
1 => 1,
|
||||
|
||||
Reference in New Issue
Block a user