mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-24 10:56:51 -06:00
Text, raster images, <use> copies, <style> sheets and rounded rectangle corners aren't read yet. They were dropped without a word; the reader now counts them and adds one read note (the channel DWG reading already uses), which OPEN echoes and returns as a warning. Content that isn't drawn (in <defs> or hidden) isn't counted.
This commit is contained in:
@@ -10,9 +10,10 @@
|
||||
//! Not read (each is a follow-up): CSS class rules (`<style>`), `use` references, fills,
|
||||
//! gradients and hatch patterns, text, embedded raster images, rounded rectangle corners
|
||||
//! (`rx`/`ry`), and elliptical arcs under non-uniform transforms (those are flattened into
|
||||
//! line segments instead of losing the shape).
|
||||
//! line segments instead of losing the shape). What the drawing shows of those (all but fills,
|
||||
//! gradients and patterns) is named in a read note, so it is never left out silently.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
|
||||
use cadcraft_color::Color;
|
||||
use cadcraft_doc::*;
|
||||
@@ -127,6 +128,40 @@ struct Parser {
|
||||
elements: usize,
|
||||
/// > 0 while inside a skipped subtree; counts the starts seen there.
|
||||
skip: usize,
|
||||
/// What was left out, by kind ([`LeftOut`]).
|
||||
left_out: BTreeMap<LeftOut, usize>,
|
||||
}
|
||||
|
||||
/// Drawn content the reader leaves out, named in the drawing's read note.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum LeftOut {
|
||||
Text,
|
||||
Image,
|
||||
Use,
|
||||
Style,
|
||||
RoundedCorners,
|
||||
}
|
||||
|
||||
impl LeftOut {
|
||||
/// The kind of a skipped element ([`SKIP`]) that the drawing shows.
|
||||
fn of(name: &[u8]) -> Option<Self> {
|
||||
match name {
|
||||
b"text" => Some(Self::Text),
|
||||
b"image" => Some(Self::Image),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(self, n: usize) -> String {
|
||||
let (one, many) = match self {
|
||||
Self::Text => ("text", "texts"),
|
||||
Self::Image => ("raster image", "raster images"),
|
||||
Self::Use => ("`<use>` copy", "`<use>` copies"),
|
||||
Self::Style => ("`<style>` sheet (its class rules aren't applied)", "`<style>` sheets (their class rules aren't applied)"),
|
||||
Self::RoundedCorners => ("rectangle with rounded corners (drawn square)", "rectangles with rounded corners (drawn square)"),
|
||||
};
|
||||
format!("{n} {}", if n == 1 { one } else { many })
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
@@ -139,9 +174,23 @@ impl Parser {
|
||||
layer_color: 1,
|
||||
elements: 0,
|
||||
skip: 0,
|
||||
left_out: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn left_out(&mut self, kind: LeftOut) {
|
||||
*self.left_out.entry(kind).or_default() += 1;
|
||||
}
|
||||
|
||||
/// The read note on what was left out, if anything was.
|
||||
fn note(&self) -> Option<String> {
|
||||
if self.left_out.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let list: Vec<String> = self.left_out.iter().map(|(k, n)| k.describe(*n)).collect();
|
||||
Some(format!("SVG: CADCraft doesn't read these yet, so they were left out: {}", list.join(", ")))
|
||||
}
|
||||
|
||||
fn parse(&mut self, text: &str) -> Result<()> {
|
||||
let mut reader = Reader::from_str(text);
|
||||
reader.config_mut().trim_text(true);
|
||||
@@ -163,21 +212,29 @@ impl Parser {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
self.drawing.read_notes.extend(self.note());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A start tag: open a transform frame, descend into a skipped subtree, or treat a
|
||||
/// shape written as start+end the same as a self-closed one.
|
||||
fn on_start(&mut self, name: &[u8], e: &BytesStart) -> Result<()> {
|
||||
// A style sheet applies wherever it is (Inkscape keeps it in `<defs>`).
|
||||
if name == b"style" {
|
||||
self.left_out(LeftOut::Style);
|
||||
}
|
||||
if self.skip > 0 {
|
||||
self.skip += 1;
|
||||
return Ok(());
|
||||
}
|
||||
if SKIP.contains(&name) {
|
||||
if hidden(e) {
|
||||
self.skip = 1;
|
||||
return Ok(());
|
||||
}
|
||||
if hidden(e) {
|
||||
if SKIP.contains(&name) {
|
||||
if let Some(kind) = LeftOut::of(name) {
|
||||
self.left_out(kind);
|
||||
}
|
||||
self.skip = 1;
|
||||
return Ok(());
|
||||
}
|
||||
@@ -189,9 +246,18 @@ impl Parser {
|
||||
return Ok(());
|
||||
}
|
||||
if SKIP.contains(&name) || hidden(e) {
|
||||
if !hidden(e)
|
||||
&& let Some(kind) = LeftOut::of(name)
|
||||
{
|
||||
self.left_out(kind);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
match name {
|
||||
b"use" => {
|
||||
self.left_out(LeftOut::Use);
|
||||
Ok(())
|
||||
}
|
||||
b"rect" => self.rect(e),
|
||||
b"circle" => self.circle(e),
|
||||
b"ellipse" => self.ellipse(e),
|
||||
@@ -279,6 +345,9 @@ impl Parser {
|
||||
if w <= 0.0 || h <= 0.0 {
|
||||
return Ok(());
|
||||
}
|
||||
if num(e, b"rx", 0.0) > 0.0 || num(e, b"ry", 0.0) > 0.0 {
|
||||
self.left_out(LeftOut::RoundedCorners);
|
||||
}
|
||||
let pts = [Vec2::new(x, y), Vec2::new(x + w, y), Vec2::new(x + w, y + h), Vec2::new(x, y + h)];
|
||||
self.add_polyline(&pts.map(|p| self.map(p)), true)
|
||||
}
|
||||
|
||||
@@ -2120,6 +2120,28 @@ fn svg_hidden_and_defs_subtrees_are_skipped() {
|
||||
assert_eq!(kinds(&d), ["Polyline"]);
|
||||
}
|
||||
|
||||
/// #627: what the reader doesn't read yet (text, raster images, `<use>` copies, style sheets,
|
||||
/// rounded corners) is named in a read note instead of being left out silently; what isn't drawn
|
||||
/// (in `<defs>` or hidden) isn't counted.
|
||||
#[test]
|
||||
fn svg_left_out_content_is_noted() {
|
||||
let svg = format!(
|
||||
"{SVG_PAGE}<defs><style>.a {{ stroke: red }}</style><text>in defs</text><rect id=\"r\" width=\"1\" height=\"1\"/></defs>\
|
||||
<text x=\"0\" y=\"5\">one</text><text x=\"0\" y=\"8\"><tspan>two</tspan></text><text display=\"none\">hidden</text>\
|
||||
<image href=\"a.png\" width=\"2\" height=\"2\"/><use href=\"#r\"/><use href=\"#r\" x=\"3\"></use>\
|
||||
<rect x=\"1\" y=\"1\" width=\"4\" height=\"3\" rx=\"1\"/><rect x=\"6\" y=\"1\" width=\"4\" height=\"3\"/></svg>"
|
||||
);
|
||||
let d = read(svg.as_bytes(), "left-out.svg").unwrap();
|
||||
assert_eq!(kinds(&d), ["Polyline", "Polyline"]);
|
||||
assert_eq!(
|
||||
d.read_notes,
|
||||
["SVG: CADCraft doesn't read these yet, so they were left out: 2 texts, 1 raster image, 2 `<use>` copies, \
|
||||
1 `<style>` sheet (its class rules aren't applied), 1 rectangle with rounded corners (drawn square)"]
|
||||
);
|
||||
let plain = read(format!("{SVG_PAGE}<rect x=\"1\" y=\"1\" width=\"4\" height=\"3\"/></svg>").as_bytes(), "a.svg").unwrap();
|
||||
assert!(plain.read_notes.is_empty(), "{:?}", plain.read_notes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn svgz_gunzips_to_the_same_drawing() {
|
||||
let svg = format!("{SVG_PAGE}<rect x=\"1\" y=\"1\" width=\"4\" height=\"3\"/></svg>");
|
||||
|
||||
@@ -117,7 +117,7 @@ early (#175), but the throughput problem remains. **DWG ≈ 30% read, ≈ 15% wr
|
||||
| Plot styles (`.ctb`, `.stb`), plotter configs (`.pc3`), `.pmp` | yes | plot style tables in our own JSON format ([plot-styles.md](plot-styles.md)) and kept in the drawing; plotter devices with papers and printable areas in our own JSON format ([plotters.md](plotters.md), #564); binary `.ctb`/`.stb`/`.pc3`/`.pmp` not read (no public spec) | 4–8 |
|
||||
| Scripts (`.scr`) | SCRIPT | script text through the CLI, MCP and control channel; no SCRIPT command reading a `.scr` file | 1–2 |
|
||||
| Aliases (`.pgp`), CUI(x), workspaces | yes | user alias files and ALIASEDIT (#552); no CUI(x) or workspaces | 4–8 |
|
||||
| SVG import (`.svg`, `.svgz`) | yes (via scripts) | OPEN reads shapes, paths, groups and Inkscape layers into entities (#636); no CSS classes, `use`, fills, text or images | 2–4 |
|
||||
| SVG import (`.svg`, `.svgz`) | yes (via scripts) | OPEN reads shapes, paths, groups and Inkscape layers into entities (#636); no CSS classes, `use`, fills, text or images (what the drawing loses is named in a note on open, #627) | 2–4 |
|
||||
| AutoLISP (`.lsp`, `.fas`, `.vlx`), `.dll`/`.arx` plug-ins | yes | `.lsp` loads (APPLOAD, `(load)`, a dropped file; #758); no Visual LISP ActiveX (`vlax-`), DCL dialogs or reactors; `.fas`/`.vlx` are compiled and undocumented; no `.dll`/`.arx` | see [gaps.md](gaps.md) (E1) |
|
||||
| Data extraction (`.csv`, `.xls`) and data links | yes | DATAEXTRACTION/EATTEXT to CSV or a table (#433); table data links read CSV (#534); no `.xls`, no `.dxe` templates | 3–5 |
|
||||
| eTransmit (`.zip` packages) | yes | no | 4–6 |
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ Detail: [file-format-parity.md](file-format-parity.md).
|
||||
| FF11 | DWF/DWFx, DGN, WMF, EPS, ACIS/STL and other exchange formats | Low-medium | 50–100 |
|
||||
| FF12 | DWG in the web build | Medium | 6–10 |
|
||||
| FF13 | R12 binary DXF, pre-R13 DWG | Low | 10–20 |
|
||||
| FF14 | SVG import leftovers (shapes, paths, groups and Inkscape layers landed, #636): no CSS classes (`<style>`), `use` references, fills and gradients, text or embedded images; rounded rectangle corners (`rx`/`ry`) are ignored | Low-medium | 2–4 |
|
||||
| FF14 | SVG import leftovers (shapes, paths, groups and Inkscape layers landed, #636): no CSS classes (`<style>`), `use` references, fills and gradients, text or embedded images; rounded rectangle corners (`rx`/`ry`) are ignored. Opening such a file names what was left out (#627) | Low-medium | 2–4 |
|
||||
|
||||
## Hardware
|
||||
|
||||
|
||||
Reference in New Issue
Block a user