mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-25 02:07:09 -06:00
Merge pull request #2308 from dotCooCoo/feat/print-overflow-warning
Print: Print and Print Preview warn about type that doesn't fit its frame
This commit is contained in:
@@ -114,9 +114,12 @@ pub(super) fn used_fonts(d: &Document) -> BTreeSet<UsedFont> {
|
||||
fonts
|
||||
}
|
||||
|
||||
/// The fonts (family, style, version) of the type of `d` that prints on pages showing `areas`
|
||||
/// (document space): visible type on `layers` (the layers that print) but not on a template layer
|
||||
/// (of area type and type on a path that don't fit, the runs that their layout draws),
|
||||
/// The type of `d` that prints on pages showing `areas` (document space): the fonts (family, style,
|
||||
/// version) it is set in, and the ids of the area type and type on a path among the objects of the
|
||||
/// document whose text doesn't fit (not those in the art of symbols, opacity masks, patterns,
|
||||
/// brushes or placed documents).
|
||||
/// The type that prints is visible type on `layers` (the layers that print) but not on a template
|
||||
/// layer (of area type and type on a path that don't fit, the runs that their layout draws),
|
||||
/// where what it paints ([`vectorcraft_render::painted_bounds`], its effects applied) meets an area
|
||||
/// inside the clipping paths around it, the type in the opacity masks and patterns of the objects
|
||||
/// that print and in the pieces their brushes draw, and the type of the symbols that their
|
||||
@@ -125,27 +128,32 @@ pub(super) fn used_fonts(d: &Document) -> BTreeSet<UsedFont> {
|
||||
/// fills or strokes of its own, are taken with it when what it paints meets an area, and so is the
|
||||
/// art of a placed document (`d` as [`Document::with_placed_art`] gives it, for that art's
|
||||
/// resources).
|
||||
pub(super) fn printed_fonts(d: &Document, layers: &[Arc<Node>], areas: &[Rect]) -> BTreeSet<UsedFont> {
|
||||
let mut walk = Printed { d, fonts: BTreeSet::new(), symbols: BTreeSet::new(), patterns: BTreeSet::new() };
|
||||
pub(super) fn printed_type(d: &Document, layers: &[Arc<Node>], areas: &[Rect]) -> (BTreeSet<UsedFont>, BTreeSet<NodeId>) {
|
||||
let mut walk = Printed { d, own: true, fonts: BTreeSet::new(), overflow: BTreeSet::new(), symbols: BTreeSet::new(), patterns: BTreeSet::new() };
|
||||
for l in layers {
|
||||
walk.node(l, Some(areas));
|
||||
}
|
||||
walk.fonts
|
||||
(walk.fonts, walk.overflow)
|
||||
}
|
||||
|
||||
/// The walk of [`printed_fonts`].
|
||||
/// The walk of [`printed_type`].
|
||||
struct Printed<'a> {
|
||||
d: &'a Document,
|
||||
/// Whether the walk is among the objects of the document, not in art drawn for one.
|
||||
own: bool,
|
||||
fonts: BTreeSet<UsedFont>,
|
||||
/// The objects of the document whose text doesn't fit.
|
||||
overflow: BTreeSet<NodeId>,
|
||||
/// The symbols and patterns whose art was walked.
|
||||
symbols: BTreeSet<String>,
|
||||
patterns: BTreeSet<String>,
|
||||
}
|
||||
|
||||
impl Printed<'_> {
|
||||
/// Adds the fonts of `n` and its descendants that print: those whose bounds meet one of `areas`,
|
||||
/// or with `None` (in the art of a symbol, opacity mask, pattern or placed document, in brush
|
||||
/// pieces, or in a live object) all of them but those outside the clipping path of a clip group.
|
||||
/// Adds the fonts of `n` and its descendants that print, and the ids of those that don't fit
|
||||
/// while [`Printed::own`] is set: those whose bounds meet one of `areas`, or with `None` (in the
|
||||
/// art of a symbol, opacity mask, pattern or placed document, in brush pieces, or in a live
|
||||
/// object) all of them but those outside the clipping path of a clip group.
|
||||
fn node(&mut self, n: &Node, areas: Option<&[Rect]>) {
|
||||
if !n.visible || matches!(n.kind, NodeKind::Layer { template: true, .. }) {
|
||||
return;
|
||||
@@ -162,9 +170,9 @@ impl Printed<'_> {
|
||||
}
|
||||
// The art that its opacity mask, pattern paints and brushed strokes draw.
|
||||
let mask = n.extras().mask.as_deref().filter(|m| !m.disabled);
|
||||
let runs = match &n.kind {
|
||||
let (runs, overflows) = match &n.kind {
|
||||
NodeKind::Text(t) => drawn_runs(t),
|
||||
_ => vec![],
|
||||
_ => (vec![], false),
|
||||
};
|
||||
let items: Vec<&AppearanceItem> =
|
||||
n.appearance.items.iter().filter(|i| i.visible() && !matches!(i, AppearanceItem::Stroke(s) if s.width <= 0.0)).collect();
|
||||
@@ -180,7 +188,7 @@ impl Printed<'_> {
|
||||
let brushed = vectorcraft_brush::has_brush(n);
|
||||
if (mask.is_some() || !patterns.is_empty() || brushed) && areas.is_none_or(|a| !open || self.meets(n, a)) {
|
||||
if let Some(m) = mask {
|
||||
self.node(&m.art, None);
|
||||
self.art(&m.art);
|
||||
}
|
||||
let d = self.d;
|
||||
for name in patterns {
|
||||
@@ -188,19 +196,22 @@ impl Printed<'_> {
|
||||
&& let Some(p) = d.pattern(name)
|
||||
{
|
||||
for art in &p.art {
|
||||
self.node(art, None);
|
||||
self.art(art);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The pieces its brushes draw along the path (art and pattern brushes leave type out).
|
||||
if brushed {
|
||||
for piece in vectorcraft_brush::node_pieces(d, n).unwrap_or_default() {
|
||||
self.node(&piece, None);
|
||||
self.art(&piece);
|
||||
}
|
||||
}
|
||||
}
|
||||
match &n.kind {
|
||||
NodeKind::Text(_) => {
|
||||
if overflows && self.own {
|
||||
self.overflow.insert(n.id);
|
||||
}
|
||||
for r in runs {
|
||||
self.fonts.insert((r.style.font_family.clone(), r.style.font_style.clone(), r.style.font_version.clone()));
|
||||
// Inline graphics draw their symbols' art.
|
||||
@@ -212,7 +223,7 @@ impl Printed<'_> {
|
||||
NodeKind::SymbolInstance { symbol, .. } => self.symbol(symbol),
|
||||
NodeKind::PlacedDocument(p) => {
|
||||
for art in p.art(n) {
|
||||
self.node(&art, None);
|
||||
self.art(&art);
|
||||
}
|
||||
}
|
||||
NodeKind::Layer { children, clip, .. } | NodeKind::Group { children, clip } if !moved => {
|
||||
@@ -240,10 +251,19 @@ impl Printed<'_> {
|
||||
if self.symbols.insert(name.to_string())
|
||||
&& let Some(s) = d.symbols.iter().find(|s| s.name == name)
|
||||
{
|
||||
self.node(&s.art, None);
|
||||
self.art(&s.art);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the fonts of `art`, drawn for an object of the document but not one of its objects (the
|
||||
/// art of a symbol, opacity mask, pattern or placed document, or a brush piece), with
|
||||
/// [`Printed::node`]'s `None`.
|
||||
fn art(&mut self, art: &Node) {
|
||||
let own = std::mem::replace(&mut self.own, false);
|
||||
self.node(art, None);
|
||||
self.own = own;
|
||||
}
|
||||
|
||||
/// Whether what `n` paints, its effects applied, meets one of `areas`.
|
||||
fn meets(&self, n: &Node, areas: &[Rect]) -> bool {
|
||||
vectorcraft_render::painted_bounds(self.d, n).is_some_and(|b| areas.iter().any(|r| b.intersect(*r).area() > 0.0 || r.contains_rect(b)))
|
||||
@@ -256,14 +276,15 @@ impl Printed<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The runs of `t` that print: all of them, or of area type and type on a path that don't fit, the
|
||||
/// runs that their layout draws glyphs or inline graphics of.
|
||||
fn drawn_runs(t: &vectorcraft_doc::TextObject) -> Vec<&TextRun> {
|
||||
/// The runs of `t` that print, and whether its text doesn't fit: all of them, or of area type and
|
||||
/// type on a path that don't fit, the runs that their layout draws glyphs or inline graphics of.
|
||||
fn drawn_runs(t: &vectorcraft_doc::TextObject) -> (Vec<&TextRun>, bool) {
|
||||
let drawn: Option<BTreeSet<usize>> = (!matches!(t.kind, TextKind::Point))
|
||||
.then(|| vectorcraft_text::layout(vectorcraft_text::FontDb::global(), t))
|
||||
.filter(|lay| lay.overflow)
|
||||
.map(|lay| lay.glyphs.iter().map(|g| g.run).chain(lay.inlines.iter().map(|i| i.run)).collect());
|
||||
t.runs.iter().enumerate().filter(|(i, _)| drawn.as_ref().is_none_or(|d| d.contains(i))).map(|(_, r)| r).collect()
|
||||
let runs = t.runs.iter().enumerate().filter(|(i, _)| drawn.as_ref().is_none_or(|d| d.contains(i))).map(|(_, r)| r).collect();
|
||||
(runs, drawn.is_some())
|
||||
}
|
||||
|
||||
/// What an export that draws type (as outlines, embedded fonts or pixels) says when `d` uses font
|
||||
|
||||
@@ -31,7 +31,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Print Preview",
|
||||
[],
|
||||
None,
|
||||
"{settings?: {…settings of print.setup} (over the document's)} → {pages (copies included), sheets: [{artboard (0-based; null with artboards ignored), tile? (1-based), ink? (separations), width, height (pt, the page), orientation, scale: [width %, height %], transform: [a, b, c, d, e, f] (document space → the page, pt, y down), area: [x0, y0, x1, y1] (the document region the page prints), trim: [x0, y0, x1, y1] (the artboard on the page)}] (one copy, in order), tiles: [{artboard, columns, rows, printed: [1-based tiles], tiles: [[x0, y0, x1, y1]…] (document space)}], inks: [{name, spot, print, frequency, angle}] (separations), warnings (art larger than the imageable area, type that prints in the fallback font, options not applied yet…), settings} without printing",
|
||||
"{settings?: {…settings of print.setup} (over the document's)} → {pages (copies included), sheets: [{artboard (0-based; null with artboards ignored), tile? (1-based), ink? (separations), width, height (pt, the page), orientation, scale: [width %, height %], transform: [a, b, c, d, e, f] (document space → the page, pt, y down), area: [x0, y0, x1, y1] (the document region the page prints), trim: [x0, y0, x1, y1] (the artboard on the page)}] (one copy, in order), tiles: [{artboard, columns, rows, printed: [1-based tiles], tiles: [[x0, y0, x1, y1]…] (document space)}], inks: [{name, spot, print, frequency, angle}] (separations), warnings (art larger than the imageable area, type that prints in the fallback font, type that doesn't fit its frame, options not applied yet…), settings} without printing",
|
||||
has_doc,
|
||||
preview
|
||||
),
|
||||
@@ -92,7 +92,7 @@ fn preview(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let set = settings(C, doc, p)?;
|
||||
super::printadvanced::flattener(C, &set, &s.prefs.flattener_presets)?;
|
||||
let mut pv = vectorcraft_pdf::preview(doc, &set).map_err(|e| pdf_error(C, e))?;
|
||||
pv.warnings.extend(fonts_warning(doc, set.print_layers, &areas(&pv)));
|
||||
pv.warnings.extend(type_warnings(doc, set.print_layers, &areas(&pv)));
|
||||
let mut v = serde_json::to_value(pv).map_err(|e| EngineError::Other(e.to_string()))?;
|
||||
v["settings"] = to_json(&set)?;
|
||||
Ok(v)
|
||||
@@ -118,14 +118,16 @@ fn on_page(area: Rect, to_page: Affine, (w, h): (f64, f64)) -> Option<Rect> {
|
||||
(r.area() > 0.0).then_some(r)
|
||||
}
|
||||
|
||||
/// The warning about type in `doc` that prints in the fallback font: on the layers `which` prints,
|
||||
/// on pages that print `areas`.
|
||||
fn fonts_warning(doc: &Document, which: PrintLayers, areas: &[Rect]) -> Option<String> {
|
||||
/// The warnings about the type in `doc` that prints (on the layers `which` prints, on pages that
|
||||
/// print `areas`): type in the fallback font, and type that doesn't fit its frame.
|
||||
fn type_warnings(doc: &Document, which: PrintLayers, areas: &[Rect]) -> Vec<String> {
|
||||
// Placed documents print their files' art, which names the resources this adds.
|
||||
let doc = doc.with_placed_art();
|
||||
let mut layers = doc.layers.clone();
|
||||
vectorcraft_pdf::keep_layers(&mut layers, which);
|
||||
super::fonts::substitution_warning_of(super::fonts::printed_fonts(&doc, &layers, areas))
|
||||
let (fonts, overflow) = super::fonts::printed_type(&doc, &layers, areas);
|
||||
let overflow: Vec<_> = overflow.into_iter().collect();
|
||||
super::fonts::substitution_warning_of(fonts).into_iter().chain(super::typecmd::overflow_warning(&overflow)).collect()
|
||||
}
|
||||
|
||||
const PRINT: &str = "file.print";
|
||||
@@ -162,13 +164,13 @@ fn print(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
// Print as Bitmap and the flattener preset (Advanced) render and flatten it first.
|
||||
let prepared = super::printadvanced::prepare(PRINT, doc, &set, &s.prefs.flattener_presets, s.prefs.black_output == "rich")?;
|
||||
let path = str_param(p, "path");
|
||||
// Fonts are read from the type before raster effects and Print as Bitmap turn it into images.
|
||||
// The type is read before raster effects and Print as Bitmap turn it into images.
|
||||
let which = set.print_layers;
|
||||
if !postscript {
|
||||
let printed = prepared.as_ref().unwrap_or(doc);
|
||||
let fonts = fonts_warning(&full, which, &areas(&vectorcraft_pdf::preview(printed, &set).map_err(|e| pdf_error(PRINT, e))?));
|
||||
let typed = type_warnings(&full, which, &areas(&vectorcraft_pdf::preview(printed, &set).map_err(|e| pdf_error(PRINT, e))?));
|
||||
let mut r = vectorcraft_pdf::print(printed, &PrintOptions { settings: set, ..Default::default() }).map_err(|e| pdf_error(PRINT, e))?;
|
||||
r.warnings.extend(fonts);
|
||||
r.warnings.extend(typed);
|
||||
return write_or_return(path, &r.bytes, json!({ "pages": r.pages, "format": "pdf", "warnings": r.warnings }));
|
||||
}
|
||||
let level = match p.get("level").filter(|v| !v.is_null()) {
|
||||
@@ -212,6 +214,6 @@ fn print(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
}
|
||||
let areas: Vec<Rect> = plan.sheets.iter().filter_map(|sh| on_page(sh.area, sh.view * sh.place, sh.size)).collect();
|
||||
warnings.extend(fonts_warning(&full, which, &areas));
|
||||
warnings.extend(type_warnings(&full, which, &areas));
|
||||
write_or_return(path, &out.bytes, json!({ "pages": plan.order.len(), "format": "postscript", "warnings": warnings }))
|
||||
}
|
||||
|
||||
@@ -269,6 +269,23 @@ pub(crate) fn with_overflow(s: &Session, ids: &[NodeId], mut result: Value) -> V
|
||||
result
|
||||
}
|
||||
|
||||
/// The warning that Print gives for `ids`, area type and type on a path whose text doesn't fit
|
||||
/// (as [`with_overflow`] finds it): the text past the end of their frames doesn't print. It names
|
||||
/// the first 20 ids and counts the rest; `None` when `ids` is empty.
|
||||
pub(crate) fn overflow_warning(ids: &[NodeId]) -> Option<String> {
|
||||
const NAMED: usize = 20;
|
||||
let (named, rest) = ids.split_at(ids.len().min(NAMED));
|
||||
let mut list = named.iter().map(|i| i.0.to_string()).collect::<Vec<_>>().join(", ");
|
||||
if !rest.is_empty() {
|
||||
list = format!("{list} and {} more", rest.len());
|
||||
}
|
||||
match ids {
|
||||
[] => None,
|
||||
[_] => Some(format!("1 text object doesn't fit its frame: the text past the end of the frame doesn't print (id {list})")),
|
||||
_ => Some(format!("{} text objects don't fit their frames: the text past the end of each frame doesn't print (ids {list})", ids.len())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Text objects among `ids` and their descendants.
|
||||
fn text_ids(d: &Document, ids: &[NodeId]) -> Vec<NodeId> {
|
||||
let mut out = vec![];
|
||||
|
||||
@@ -130,3 +130,25 @@ fn type_in_a_missing_family_warns_as_pdf_export_does() {
|
||||
assert_eq!(fallback(&v), export, "{settings}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_that_does_not_fit_its_frame_is_named() {
|
||||
let (mut s, _) = session();
|
||||
let p = json!({"x": 10, "y": 100, "text": "Ab\nCd\nEf\nGh", "size": 12, "area": {"width": 100, "height": 20}});
|
||||
let id = s.execute("text.create", &p).unwrap()["id"].clone();
|
||||
let over = |v: &Value| -> Vec<String> {
|
||||
v["warnings"].as_array().unwrap().iter().filter_map(Value::as_str).filter(|w| w.contains("fit its frame")).map(str::to_string).collect()
|
||||
};
|
||||
let (_, v) = print(&mut s, json!({}));
|
||||
assert_eq!(over(&v), [format!("1 text object doesn't fit its frame: the text past the end of the frame doesn't print (id {id})")]);
|
||||
assert_eq!(over(&v), over(&s.execute("print.preview", &json!({})).unwrap()));
|
||||
// Type printed as an image is named too.
|
||||
s.execute("effect.apply", &json!({"effect": "stylize.dropShadow", "ids": [id]})).unwrap();
|
||||
for settings in [json!({}), json!({"advanced": {"printAsBitmap": true}})] {
|
||||
let (_, v) = print(&mut s, json!({ "settings": settings }));
|
||||
assert_eq!(over(&v).len(), 1, "{settings}");
|
||||
}
|
||||
s.execute("text.areaOptions", &json!({"ids": [id], "height": 150})).unwrap();
|
||||
let (_, v) = print(&mut s, json!({}));
|
||||
assert_eq!(over(&v), Vec::<String>::new());
|
||||
}
|
||||
|
||||
@@ -140,6 +140,97 @@ fn type_that_does_not_fit_its_frame_counts_only_where_it_shows() {
|
||||
assert_eq!(preview_fallback(&mut s, json!({})), Vec::<String>::new());
|
||||
}
|
||||
|
||||
/// Area type 100 × 20 pt at (`x`, `y`) holding four lines, of which the first shows → its id.
|
||||
fn overset(s: &mut Session, x: f64, y: f64) -> u64 {
|
||||
let v = s.execute("text.create", &json!({"x": x, "y": y, "text": "Ab\nCd\nEf\nGh", "size": 12, "area": {"width": 100, "height": 20}})).unwrap();
|
||||
assert_eq!(v["overflow"], json!([v["id"]]), "{v}");
|
||||
v["id"].as_u64().unwrap()
|
||||
}
|
||||
|
||||
/// `print.preview {settings}`'s warnings about type that doesn't fit its frame.
|
||||
fn preview_overflow(s: &mut Session, settings: Value) -> Vec<String> {
|
||||
let v = s.execute("print.preview", &json!({ "settings": settings })).unwrap();
|
||||
let over = |w: &&str| w.contains("fit its frame") || w.contains("fit their frames");
|
||||
v["warnings"].as_array().unwrap().iter().filter_map(Value::as_str).filter(over).map(str::to_string).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_that_does_not_fit_its_frame_is_named() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 300, "height": 200})).unwrap();
|
||||
let id = overset(&mut s, 10.0, 10.0);
|
||||
let one = format!("1 text object doesn't fit its frame: the text past the end of the frame doesn't print (id {id})");
|
||||
assert_eq!(preview_overflow(&mut s, json!({})), [one]);
|
||||
// A frame that holds its text, or a hidden object, leaves nothing out.
|
||||
s.execute("text.areaOptions", &json!({"ids": [id], "height": 150})).unwrap();
|
||||
assert_eq!(preview_overflow(&mut s, json!({})), Vec::<String>::new());
|
||||
s.execute("text.areaOptions", &json!({"ids": [id], "height": 20})).unwrap();
|
||||
s.execute("object.setProps", &json!({"ids": [id], "visible": false})).unwrap();
|
||||
assert_eq!(preview_overflow(&mut s, json!({})), Vec::<String>::new());
|
||||
s.execute("object.setProps", &json!({"ids": [id], "visible": true})).unwrap();
|
||||
// Type on a path that runs past the path's end.
|
||||
let path = s.execute("path.create", &json!({"d": "M10 150 L60 150"})).unwrap()["id"].clone();
|
||||
let on = s.execute("text.createInPath", &json!({"path": path, "mode": "onPath", "text": "Longer than its path"})).unwrap();
|
||||
assert_eq!(on["overflow"], json!([on["id"]]), "{on}");
|
||||
let both = format!("2 text objects don't fit their frames: the text past the end of each frame doesn't print (ids {id}, {})", on["id"]);
|
||||
assert_eq!(preview_overflow(&mut s, json!({})), [both]);
|
||||
// Past 20, the first 20 ids and how many more.
|
||||
let mut ids = vec![id, on["id"].as_u64().unwrap()];
|
||||
ids.extend((0..20).map(|i| overset(&mut s, 150.0, f64::from(i) * 5.0)));
|
||||
let named: Vec<String> = ids[..20].iter().map(u64::to_string).collect();
|
||||
let many =
|
||||
format!("22 text objects don't fit their frames: the text past the end of each frame doesn't print (ids {} and 2 more)", named.join(", "));
|
||||
assert_eq!(preview_overflow(&mut s, json!({})), [many]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_that_does_not_fit_counts_only_layers_and_artboards_that_print() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 300, "height": 200, "artboards": 2})).unwrap();
|
||||
let r = s.doc().unwrap().doc.artboards[1].rect;
|
||||
overset(&mut s, r.x0 + 10.0, r.y0 + 10.0);
|
||||
assert_eq!(preview_overflow(&mut s, json!({"artboards": "range", "range": "1"})), Vec::<String>::new());
|
||||
assert_eq!(preview_overflow(&mut s, json!({"artboards": "range", "range": "2"})).len(), 1);
|
||||
// On a hidden layer, the type prints only with Print Layers: All.
|
||||
let layer = s.execute("layer.new", &json!({})).unwrap()["id"].as_u64().unwrap();
|
||||
overset(&mut s, 10.0, 10.0);
|
||||
s.execute("layer.setProps", &json!({"id": layer, "visible": false})).unwrap();
|
||||
for (layers, warns) in [("visiblePrintable", false), ("visible", false), ("all", true)] {
|
||||
let warned = preview_overflow(&mut s, json!({"artboards": "range", "range": "1", "printLayers": layers}));
|
||||
assert_eq!(!warned.is_empty(), warns, "{layers}");
|
||||
}
|
||||
// A layer that doesn't print.
|
||||
s.execute("layer.setProps", &json!({"id": layer, "visible": true, "printable": false})).unwrap();
|
||||
assert_eq!(preview_overflow(&mut s, json!({"artboards": "range", "range": "1"})), Vec::<String>::new());
|
||||
assert_eq!(preview_overflow(&mut s, json!({"artboards": "range", "range": "1", "printLayers": "visible"})).len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_that_does_not_fit_is_named_only_among_the_objects_of_the_document() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 300, "height": 200})).unwrap();
|
||||
let id = overset(&mut s, 10.0, 10.0);
|
||||
assert_eq!(preview_overflow(&mut s, json!({})).len(), 1);
|
||||
s.execute("symbol.new", &json!({"name": "Label", "ids": [id]})).unwrap();
|
||||
assert_eq!(preview_overflow(&mut s, json!({})), Vec::<String>::new(), "the art of a symbol");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_threaded_story_is_named_only_when_its_last_frame_does_not_hold_it() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 600, "height": 300})).unwrap();
|
||||
let story = "The quick brown fox jumps over the lazy dog. ".repeat(8);
|
||||
let a = s.execute("text.create", &json!({"x": 10, "y": 10, "text": story, "area": {"width": 150, "height": 60}})).unwrap();
|
||||
assert_eq!(a["overflow"], json!([a["id"]]), "{a}");
|
||||
let b = s.execute("shape.rectangle", &json!({"x": 200, "y": 10, "width": 150, "height": 200})).unwrap()["id"].clone();
|
||||
s.execute("select.set", &json!({"ids": [a["id"], b]})).unwrap();
|
||||
s.execute("text.thread.create", &json!({})).unwrap();
|
||||
assert_eq!(preview_overflow(&mut s, json!({})), Vec::<String>::new(), "the second frame holds the rest");
|
||||
s.execute("text.editRange", &json!({"id": a["id"], "start": 0, "end": 0, "insert": story.repeat(4)})).unwrap();
|
||||
let warned = preview_overflow(&mut s, json!({}));
|
||||
assert!(warned.len() == 1 && warned[0].ends_with(&format!("(id {b})")), "{warned:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_of_an_artboard_larger_than_the_paper_counts_only_on_the_paper() {
|
||||
let mut s = Session::new();
|
||||
|
||||
@@ -111,3 +111,17 @@ fn type_in_a_missing_family_is_named_when_its_artboard_prints() {
|
||||
let (_, v) = print_ps(&mut s, json!({ "settings": paper }));
|
||||
assert_eq!(fallback(&v), 0, "{v}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_that_does_not_fit_its_frame_is_named_when_its_artboard_prints() {
|
||||
let mut s = session(2);
|
||||
let r = s.doc().unwrap().doc.artboards[1].rect;
|
||||
let p = json!({"x": r.x0 + 10.0, "y": r.y0 + 10.0, "text": "Ab\nCd\nEf\nGh", "size": 12, "area": {"width": 100, "height": 20}});
|
||||
let id = s.execute("text.create", &p).unwrap()["id"].clone();
|
||||
let over = |v: &Value| v["warnings"].as_array().unwrap().iter().filter(|w| w.as_str().unwrap().contains("fit its frame")).count();
|
||||
let (_, v) = print_ps(&mut s, json!({}));
|
||||
assert_eq!(over(&v), 1, "{v}");
|
||||
assert!(v["warnings"].to_string().contains(&format!("(id {id})")), "{v}");
|
||||
let (_, v) = print_ps(&mut s, json!({"settings": {"artboards": "range", "range": "1"}}));
|
||||
assert_eq!(over(&v), 0, "{v}");
|
||||
}
|
||||
|
||||
+5
-2
@@ -2682,7 +2682,9 @@ as they do for art larger than the imageable area. Type in a font family that is
|
||||
font, and the warnings of `file.print` and `print.preview` name those families. They count only type that prints:
|
||||
visible type that fits its frame or path, on the layers `printLayers` picks, on the paper of the artboards or tiles
|
||||
that print, and the type in the symbols, patterns, scatter brushes, opacity masks and placed documents of the art that
|
||||
prints.
|
||||
prints. The warnings also give the ids of the area type and type on a path that print and whose text doesn't fit
|
||||
(objects of the document only, not type in the art of symbols, patterns, brushes, opacity masks or placed documents;
|
||||
see [Fitting area type](#fitting-area-type-auto-size-shrink-text-to-fit-overflow)).
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"print.setup","params":{"settings":{"media":"a4","scaling":"fit","marks":{"trim":true}}}}}
|
||||
@@ -3124,7 +3126,8 @@ Text that doesn't fit is drawn up to the last line that does, so a one-line head
|
||||
draws nothing. The commands that make type or change what it holds say so in their own reply: `text.create`,
|
||||
`text.createInPath`, `text.setText` and `text.setStyle` add `overflow: [id…]` (the area type and type on a path among
|
||||
the objects they made or changed whose text doesn't fit), and the `add_text` tool adds `overflow: true`. The key is
|
||||
left out when everything fits.
|
||||
left out when everything fits. The warnings of `file.print` and `print.preview` give the ids of the objects that print
|
||||
and whose text doesn't fit: the first 20 ids, then how many more.
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"text.areaOptions","params":{"fit":"shrinkText","fitMinPercent":60}}}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user