mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 06:01:18 +00:00
Merge pull request #5397 from storytold/fix/sec-followup-5288
DXF import: charge every dash copy and symbol name, name the block-work limit
This commit is contained in:
@@ -43,6 +43,7 @@ const NESTED: &str = "blocks nested more than 8 deep, or inside themselves, are
|
||||
const XREFS: &str = "blocks referencing other drawings (external references) are left out";
|
||||
const TOO_MANY: &str = "the drawing has more than 2,000,000 objects: the rest are left out";
|
||||
const TOO_MANY_POINTS: &str = "the drawing has more than 4,000,000 path points: the rest are left out";
|
||||
const TOO_MUCH_BLOCK_WORK: &str = "the drawing's blocks take too much work to place: the rest are left out";
|
||||
const TOO_MANY_DASHES: &str = "the drawing has more dashed lines than can be kept: the rest are drawn solid";
|
||||
const TOO_MUCH_TEXT: &str = "the drawing has more than 64 MB of text and block names: the rest are left out";
|
||||
|
||||
@@ -499,6 +500,9 @@ impl<'d, 'a> Build<'d, 'a> {
|
||||
super::curve::bounds(&bp)?;
|
||||
let paint = Paint::solid(rgb_color(self.rgb(p, inh)));
|
||||
let scale = map.determinant().abs().sqrt();
|
||||
let path = PathData::from_bezpath(&bp);
|
||||
// A compound path's loops each keep a copy of its appearance, beside its own.
|
||||
let copies = if path.subpaths.len() > 1 { path.subpaths.len() + 1 } else { 1 };
|
||||
let appearance = if fill {
|
||||
Appearance::basic(paint, Paint::None, 0.0)
|
||||
} else {
|
||||
@@ -514,7 +518,7 @@ impl<'d, 'a> Build<'d, 'a> {
|
||||
if let Some(AppearanceItem::Stroke(s)) = ap.items.last_mut()
|
||||
&& let Some((dash, dots)) = self.dash(&self.linetype(p, inh), p, scale)
|
||||
{
|
||||
match self.dash_values.checked_add(dash.pattern.len()).filter(|n| *n <= MAX_DASH_VALUES) {
|
||||
match dash.pattern.len().checked_mul(copies).and_then(|v| self.dash_values.checked_add(v)).filter(|n| *n <= MAX_DASH_VALUES) {
|
||||
Some(n) => {
|
||||
self.dash_values = n;
|
||||
s.dash = Some(dash);
|
||||
@@ -528,7 +532,6 @@ impl<'d, 'a> Build<'d, 'a> {
|
||||
ap
|
||||
};
|
||||
let rule = if fill { FillRule::EvenOdd } else { FillRule::NonZero };
|
||||
let path = PathData::from_bezpath(&bp);
|
||||
let kind = |path: PathData| NodeKind::Path { path, rule, live: None, clipping: false, guide: false };
|
||||
let mut n = if path.subpaths.len() > 1 {
|
||||
// Several loops (a hatch with islands) paint as one compound path. Each loop is an
|
||||
@@ -630,7 +633,7 @@ impl<'d, 'a> Build<'d, 'a> {
|
||||
/// The art of block `name` drawn by `map`, as one object.
|
||||
fn art(&mut self, name: &str, map: Affine, inh: &Inherit, depth: u32) -> Option<Node> {
|
||||
if self.visits >= MAX_VISITS {
|
||||
self.warn(TOO_MANY);
|
||||
self.warn(TOO_MUCH_BLOCK_WORK);
|
||||
return None;
|
||||
}
|
||||
let items = self.block(name);
|
||||
@@ -638,7 +641,7 @@ impl<'d, 'a> Build<'d, 'a> {
|
||||
// makes nothing, so the node budget doesn't bound it).
|
||||
self.visits = self.visits.saturating_add(1 + items.as_ref().map_or(0, |i| i.len()));
|
||||
if self.visits > MAX_VISITS {
|
||||
self.warn(TOO_MANY);
|
||||
self.warn(TOO_MUCH_BLOCK_WORK);
|
||||
return None;
|
||||
}
|
||||
let items = items?;
|
||||
@@ -671,12 +674,16 @@ impl<'d, 'a> Build<'d, 'a> {
|
||||
let base = self.d.blocks.get(name).map_or(Point::ZERO, |b| b.base);
|
||||
let map = self.symbol_map * Affine::translate(-base.to_vec2());
|
||||
let art = self.art(name, map, &key.1, depth);
|
||||
let made = art.map(|art| {
|
||||
let made = art.and_then(|art| {
|
||||
let label = self.d.blocks.get(name).map_or(name, |b| b.name.as_str());
|
||||
// The symbol keeps its name: text, as its instances' copies of it are.
|
||||
if !self.charge_text(label.len()) {
|
||||
return None;
|
||||
}
|
||||
let symbol = unique(label, &mut self.next_suffix, &self.symbol_names);
|
||||
self.symbol_names.insert(symbol.clone());
|
||||
self.doc.symbols.push(Symbol { dynamic: false, name: symbol.clone(), art: Arc::new(art) });
|
||||
symbol
|
||||
Some(symbol)
|
||||
});
|
||||
self.symbols.insert(key, made.clone());
|
||||
made
|
||||
|
||||
@@ -814,7 +814,7 @@ fn nested_array_inserts_of_nothing_stop_at_the_work_budget() {
|
||||
blocks.extend(block("*C", None));
|
||||
let top = [(0, "INSERT"), (2, "*A"), (10, "0"), (20, "0"), (70, "300"), (44, "1")];
|
||||
let r = open(&drawing(&[], &[], &blocks, &top), &points());
|
||||
assert!(r.warnings.iter().any(|w| w.contains("more than")), "{:?}", r.warnings);
|
||||
assert!(r.warnings.iter().any(|w| w.contains("too much work")), "{:?}", r.warnings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1273,5 +1273,77 @@ fn arrays_of_blocks_of_hidden_art_stop_at_the_work_budget() {
|
||||
blocks.push((0, "ENDBLK"));
|
||||
let insert = [(0, "INSERT"), (2, "*U1"), (10, "0"), (20, "0"), (70, "50"), (71, "50"), (44, "1"), (45, "1")];
|
||||
let r = open(&drawing(&[], &tables, &blocks, &insert), &points());
|
||||
assert!(r.warnings.iter().any(|w| w.contains("more than")), "{:?}", r.warnings);
|
||||
assert!(r.warnings.iter().any(|w| w.contains("too much work")), "{:?}", r.warnings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dashed_compound_paths_charge_every_copy_of_their_pattern() {
|
||||
// 100 pattern hatches of 9 loops in a two-length linetype: each keeps its dashes on the
|
||||
// compound path and on each loop, 2,000 dash lengths, past the 1,000 tests import with.
|
||||
let tables = [(0, "TABLE"), (2, "LTYPE"), (0, "LTYPE"), (2, "DASHED"), (73, "2"), (40, "15"), (49, "10"), (49, "-5"), (0, "ENDTAB")];
|
||||
let corners = ["0", "10", "20", "30", "40", "50", "60", "70", "80", "90"];
|
||||
let mut hatch = vec![(0, "HATCH"), (6, "DASHED"), (10, "0"), (20, "0"), (30, "0"), (2, "ANSI31"), (70, "0"), (71, "0"), (91, "9")];
|
||||
for i in 0..9 {
|
||||
let (a, b) = (corners[i], corners[i + 1]);
|
||||
hatch.extend([
|
||||
(92, "2"),
|
||||
(72, "0"),
|
||||
(73, "1"),
|
||||
(93, "4"),
|
||||
(10, a),
|
||||
(20, "0"),
|
||||
(10, b),
|
||||
(20, "0"),
|
||||
(10, b),
|
||||
(20, "5"),
|
||||
(10, a),
|
||||
(20, "5"),
|
||||
(97, "0"),
|
||||
]);
|
||||
}
|
||||
hatch.extend([(75, "0"), (76, "1"), (98, "0")]);
|
||||
let e: Vec<(i32, &str)> = (0..100).flat_map(|_| hatch.iter().copied()).collect();
|
||||
let r = open(&drawing(&[], &tables, &[], &e), &points());
|
||||
let (mut compounds, mut kept) = (0, 0);
|
||||
r.document.walk(|n| {
|
||||
compounds += usize::from(matches!(n.kind, NodeKind::Compound { .. }));
|
||||
kept += n.appearance.stroke().and_then(|s| s.dash.as_ref()).map_or(0, |d| d.pattern.len());
|
||||
});
|
||||
assert_eq!(compounds, 100, "{:?}", r.warnings);
|
||||
assert!(kept > 0 && kept <= 1_000, "{kept} dash lengths kept");
|
||||
assert!(r.warnings.iter().any(|w| w.contains("drawn solid")), "{:?}", r.warnings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbols_of_a_long_block_name_stop_at_the_text_budget() {
|
||||
// A 1,000-byte block name inserted in 100 colours its art takes: a symbol per colour, each
|
||||
// keeping its own copy of the name, past the 64 KB tests import with.
|
||||
let name = "N".repeat(1000);
|
||||
let blocks = [
|
||||
(0, "BLOCK"),
|
||||
(2, name.as_str()),
|
||||
(10, "0"),
|
||||
(20, "0"),
|
||||
(0, "LINE"),
|
||||
(8, "0"),
|
||||
(62, "0"),
|
||||
(10, "0"),
|
||||
(20, "0"),
|
||||
(11, "1"),
|
||||
(21, "0"),
|
||||
(0, "ENDBLK"),
|
||||
];
|
||||
let colours: Vec<String> = (1..=100).map(|c| c.to_string()).collect();
|
||||
let inserts: Vec<(i32, &str)> =
|
||||
colours.iter().flat_map(|c| [(0, "INSERT"), (2, name.as_str()), (62, c.as_str()), (10, "0"), (20, "0")]).collect();
|
||||
let r = open(&drawing(&[], &[], &blocks, &inserts), &points());
|
||||
let mut bytes: usize = r.document.symbols.iter().map(|s| s.name.len()).sum();
|
||||
r.document.walk(|n| {
|
||||
if let NodeKind::SymbolInstance { symbol, .. } = &n.kind {
|
||||
bytes += symbol.len();
|
||||
}
|
||||
});
|
||||
assert!(!r.document.symbols.is_empty());
|
||||
assert!(bytes <= 64 << 10, "{bytes} bytes of symbol names");
|
||||
assert!(r.warnings.iter().any(|w| w.contains("64 MB of text")), "{:?}", r.warnings);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user