Print: Print and Print Preview warn about type that doesn't fit its frame

Area type and type on a path whose text doesn't fit print without the
text past the end of the frame. text.create, text.createInPath,
text.setText and text.setStyle report it in their replies, but
file.print and print.preview gave no warning, and the Print dialog's
Summary showed none.

Both commands now add a warning that counts the area type and type on
a path that prints and doesn't fit, and gives their ids (the first 20,
then how many more). It counts the objects that the fallback font
warning counts: visible objects on the layers that Print Layers picks,
on the paper of the artboards or tiles that print. The walk that finds
the fonts of the printed type also collects these ids. Type in the art
of symbols, patterns, brushes, opacity masks and placed documents is
not named.

Docs: docs/mcp.md (Print, Fitting area type), docs/target-app-parity.md
(Type).
This commit is contained in:
dotCooCoo
2026-10-16 15:27:54 +07:00
committed by bflatastic
parent dfe215ca30
commit adea2d307d
8 changed files with 204 additions and 34 deletions
+42 -21
View File
@@ -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
+12 -10
View File
@@ -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 }))
}
+17
View File
@@ -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![];
+22
View File
@@ -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());
}
+91
View File
@@ -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();
+14
View File
@@ -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
View File
@@ -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