mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-12 05:22:48 +00:00
Plug-ins: only File › Open lists importers' files, and imported art at the canvas edge keeps an editable artboard
New from Template, Load Character/Paragraph/All Styles, Recolor's Other Library… and the library panels' Other Library… read files with the built-in readers, so their open dialogs now use builtin_open_filters(), which leaves out the extensions only importer plug-ins read. File › Open keeps open_filters() with the importers' files. The artboard of a document an importer opens is now kept within ±4,000,000 pt. Art at the edge (a zero-width path at x = 4,000,000, or a stroke past the edge) gave an artboard beyond it, and every later edit of the document was rejected.
This commit is contained in:
@@ -130,9 +130,9 @@ pub(crate) use export_names::{default_name, merge, unique_file_names, with_black
|
||||
use export_names::{distinct_names_by, write_encoded};
|
||||
use formats::{ARTBOARD, ARTBOARDS, RANGE, USE_ARTBOARDS, import_name, writable};
|
||||
pub use formats::{
|
||||
DOC_EXTS, DOCX_EXTS, FORMATS, Format, FormatOption, OPEN_EXTS, PLACE_EXTS, RTF_EXTS, TEXT_EXTS, UNASSOCIATED_EXTS, export_extensions, extension,
|
||||
file_stem, format, format_for_name, has_own_title, importer_formats, library_open_filters, open_extensions, open_filters, place_filters,
|
||||
plugin_formats, read_extensions, readable_format, readable_format_for_name, titled_for_export, writable_format,
|
||||
DOC_EXTS, DOCX_EXTS, FORMATS, Format, FormatOption, OPEN_EXTS, PLACE_EXTS, RTF_EXTS, TEXT_EXTS, UNASSOCIATED_EXTS, builtin_open_filters,
|
||||
export_extensions, extension, file_stem, format, format_for_name, has_own_title, importer_formats, library_open_filters, open_extensions,
|
||||
open_filters, place_filters, plugin_formats, read_extensions, readable_format, readable_format_for_name, titled_for_export, writable_format,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -693,7 +693,7 @@ pub fn open_extensions() -> &'static [&'static str] {
|
||||
exts
|
||||
}
|
||||
|
||||
/// Open-dialog filters: "All readable files" first, then one per readable format, then one per
|
||||
/// File › Open's filters: "All readable files" first, then one per readable format, then one per
|
||||
/// importer plug-in (under its name), then swatch libraries (which open in the library panel),
|
||||
/// Libraries panel libraries, flattener, PDF, print and export presets (imported), scripts (checked
|
||||
/// without running them) and plug-ins (installed).
|
||||
@@ -701,7 +701,20 @@ pub fn open_filters() -> impl Iterator<Item = (&'static str, &'static [&'static
|
||||
std::iter::once(("All readable files", open_extensions()))
|
||||
.chain(format_filters())
|
||||
.chain(importer_formats().into_iter().map(|f| (f.label, f.extensions)))
|
||||
.chain(std::iter::once(("Swatch libraries", crate::cmd::swatchlib::LIBRARY_EXTS)))
|
||||
.chain(other_open_filters())
|
||||
}
|
||||
|
||||
/// [`open_filters`] without the importer plug-ins: "All readable files" lists [`OPEN_EXTS`]. For
|
||||
/// open dialogs whose files the built-in readers read (New from Template, Load Styles, a document
|
||||
/// picked for its swatches, symbols, brushes or graphic styles).
|
||||
pub fn builtin_open_filters() -> impl Iterator<Item = (&'static str, &'static [&'static str])> {
|
||||
std::iter::once(("All readable files", OPEN_EXTS)).chain(format_filters()).chain(other_open_filters())
|
||||
}
|
||||
|
||||
/// The open-dialog filters after the document formats: swatch libraries, Libraries panel
|
||||
/// libraries, presets, shortcut sets, scripts and plug-ins.
|
||||
fn other_open_filters() -> impl Iterator<Item = (&'static str, &'static [&'static str])> {
|
||||
std::iter::once(("Swatch libraries", crate::cmd::swatchlib::LIBRARY_EXTS))
|
||||
.chain(std::iter::once(("Libraries", crate::cmd::library::LIBRARY_EXTS)))
|
||||
.chain(std::iter::once(("Flattener presets", crate::cmd::flatten::PRESET_EXTS)))
|
||||
.chain(std::iter::once(("PDF presets", crate::cmd::pdfcmds::PRESET_EXTS)))
|
||||
@@ -715,12 +728,13 @@ pub fn open_filters() -> impl Iterator<Item = (&'static str, &'static [&'static
|
||||
/// Open-dialog filters for Swatch Libraries and Graphic Style Libraries Other Library… and Locate
|
||||
/// Library…. The library formats come first so Linux file portals (which grey out files that don't
|
||||
/// match the default filter) still list `.ase` and the other library extensions; then the same
|
||||
/// filters as [`open_filters`] so a document can be picked for its swatches or graphic styles.
|
||||
/// filters as [`builtin_open_filters`] so a document can be picked for its swatches or graphic
|
||||
/// styles.
|
||||
pub fn library_open_filters(
|
||||
library_label: &'static str,
|
||||
library_exts: &'static [&'static str],
|
||||
) -> impl Iterator<Item = (&'static str, &'static [&'static str])> {
|
||||
std::iter::once((library_label, library_exts)).chain(open_filters())
|
||||
std::iter::once((library_label, library_exts)).chain(builtin_open_filters())
|
||||
}
|
||||
|
||||
/// File → Place dialog filters: "All placeable files", then one per placeable format, then text,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde_json::Value;
|
||||
use vectorcraft_doc::Document;
|
||||
use vectorcraft_doc::{Document, MAX_COORD};
|
||||
use vectorcraft_geom::Rect;
|
||||
use vectorcraft_plugins::{Plugin, export, import, objects};
|
||||
|
||||
@@ -101,15 +101,24 @@ pub(super) fn load(name: &str, bytes: &[u8], plugin: &Plugin, p: &Value) -> Resu
|
||||
let ids =
|
||||
objects::apply_output(&mut doc, &[], imported.objects, layer, &admit).map_err(|e| err(format!("can't open `{}`: {e}", file_name(name))))?;
|
||||
if let (Some(bounds), Some(board)) = (doc.bounds_of(&ids, true), doc.artboards.first_mut()) {
|
||||
board.rect = at_least_a_point(bounds);
|
||||
board.rect = artboard_rect(bounds);
|
||||
}
|
||||
doc.title = file_name(name);
|
||||
Ok(Loaded { doc, format, warnings: imported.warnings, restored: false, converted: false, preview_only: false, editing_data: false })
|
||||
}
|
||||
|
||||
/// `r` at least 1 pt wide and high, about its center: the artboard of a single line or point.
|
||||
fn at_least_a_point(r: Rect) -> Rect {
|
||||
let grow = |lo: f64, hi: f64| if hi - lo >= 1.0 { (lo, hi) } else { ((lo + hi) / 2.0 - 0.5, (lo + hi) / 2.0 + 0.5) };
|
||||
let ((x0, x1), (y0, y1)) = (grow(r.x0, r.x1), grow(r.y0, r.y1));
|
||||
/// The artboard of art with bounds `r`: `r` within ±[`MAX_COORD`], at least 1 pt wide and high
|
||||
/// about its center (the artboard of a single line or point). A side that would pass the edge
|
||||
/// moves inward, so art at the edge keeps an artboard the document's edits accept.
|
||||
fn artboard_rect(r: Rect) -> Rect {
|
||||
let side = |lo: f64, hi: f64| {
|
||||
let (lo, hi) = (lo.max(-MAX_COORD), hi.min(MAX_COORD));
|
||||
if hi - lo >= 1.0 {
|
||||
return (lo, hi);
|
||||
}
|
||||
let mid = ((lo + hi) / 2.0).clamp(-MAX_COORD + 0.5, MAX_COORD - 0.5);
|
||||
(mid - 0.5, mid + 0.5)
|
||||
};
|
||||
let ((x0, x1), (y0, y1)) = (side(r.x0, r.x1), side(r.y0, r.y1));
|
||||
Rect::new(x0, y0, x1, y1)
|
||||
}
|
||||
|
||||
@@ -109,6 +109,21 @@ fn a_file_an_importer_reads_opens_as_editable_art() {
|
||||
open(&mut s, "Line.tso", LINE.as_bytes(), json!({})).unwrap();
|
||||
assert_eq!(artboard(&s), (0.0, 4.5, 50.0, 5.5));
|
||||
|
||||
// Art at the edge of the canvas keeps its artboard within ±4,000,000 pt, so the document can
|
||||
// still be edited: a vertical line at x = 4,000,000 (its 1 pt moves inward) and a stroked line
|
||||
// along y = -4,000,000 (its stroke passes the edge).
|
||||
let edge = r#"{"objects":[{"type":"path","path":{"subpaths":[{"closed":false,"anchors":[{"p":[4000000,0]},{"p":[4000000,50]}]}]}}]}"#;
|
||||
open(&mut s, "Edge.tso", edge.as_bytes(), json!({})).unwrap();
|
||||
assert_eq!(artboard(&s), (3_999_999.0, 0.0, 4_000_000.0, 50.0));
|
||||
s.execute("select.all", &json!({})).unwrap();
|
||||
s.execute("object.move", &json!({"dx": -5, "dy": 0})).unwrap();
|
||||
let stroked = r#"{"objects":[{"type":"path","path":{"subpaths":[{"closed":false,"anchors":[{"p":[0,-4000000]},{"p":[50,-4000000]}]}]},"strokes":[{"paint":{"type":"solid","color":{"model":"rgb","r":0,"g":0,"b":0}},"width":10}]}]}"#;
|
||||
open(&mut s, "Stroked.tso", stroked.as_bytes(), json!({})).unwrap();
|
||||
let (x0, y0, x1, y1) = artboard(&s);
|
||||
assert!(x0 < 0.0 && x1 > 50.0 && y0 == -4_000_000.0 && y1 > y0 + 1.0, "{:?}", artboard(&s));
|
||||
s.execute("select.all", &json!({})).unwrap();
|
||||
s.execute("object.move", &json!({"dx": 0, "dy": 5})).unwrap();
|
||||
|
||||
// From a path: the document keeps no path, so Save asks for a name.
|
||||
let path = dir.join("Shapes.tso");
|
||||
std::fs::write(&path, RECT).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user