Add-ons: Artwork Cleanup — text tools

Object › Plug-ins › Artwork Cleanup and a hideable Text row in its panel
gain text tools, each one undo step:
- Break Text into Lines / Paragraphs / Words / Glyphs: point type pieces
  placed by the glyph of their first character, keeping their styles.
- Combine Point Text: one-line point type merged into as few objects as
  it can, every glyph staying where it was (tracking and spaces, checked
  against the layout).
- Split Text at Cursor, Remove Line Breaks, Remove Paragraph Breaks,
  Set Alignment, Keep Position, Reset Text Transform and Replace
  Missing Fonts.
This commit is contained in:
bflatastic
2026-10-31 02:05:58 +07:00
parent a0da3faffe
commit e556dacbb6
34 changed files with 2637 additions and 15 deletions
+7 -3
View File
@@ -1,10 +1,12 @@
//! Artwork Cleanup (`addon.cleanup.*`), a built-in add-on: Simplify Anchors (Keep Shape), the
//! Document Health Check (check, show and fix the issues it finds), Deep Ungroup and Rejoin Path
//! Pieces. Its panel and its submenu, Object › Plug-ins › Artwork Cleanup, are the UI's.
//! Pieces, and its text tools ([`text`]). Its panel and its submenu, Object › Plug-ins › Artwork
//! Cleanup, are the UI's.
mod checks;
#[cfg(test)]
mod tests;
mod text;
use serde_json::{Map, Value, json};
use vectorcraft_doc::{Document, Node, NodeId, NodeKind};
@@ -30,7 +32,7 @@ const FIX_ROUNDS: usize = 3;
const MAX_LISTED: usize = 1000;
pub fn specs() -> Vec<CommandSpec> {
vec![
let mut v = vec![
cmd!(
"addon.cleanup.simplify",
"Simplify Anchors (Keep Shape)…",
@@ -85,7 +87,9 @@ pub fn specs() -> Vec<CommandSpec> {
has_selection,
rejoin
),
]
];
v.extend(text::specs());
v
}
/// The paths (not guides) under `roots`, compound path members included.
@@ -0,0 +1,865 @@
//! Artwork Cleanup's text tools: Break Text into Lines / Paragraphs / Words / Glyphs, Combine Point
//! Text, Split Text at Cursor, Remove Line / Paragraph Breaks, Set Alignment, Keep Position, Reset
//! Text Transform and Replace Missing Fonts. Each is one undo step. Pieces keep their characters'
//! styles and stay where the layout drew them: each is moved so the glyph of its first character
//! lands where it was.
#[cfg(test)]
mod tests;
use serde_json::{Value, json};
use vectorcraft_doc::{CharStyle, Document, Justify, Node, NodeId, NodeKind, ParaStyle, RubySpan, TextKind, TextObject};
use vectorcraft_geom::{Affine, Point, Vec2};
use vectorcraft_text::{FontDb, FontMatch, TextLayout, edit};
use crate::cmd::textedit::editing_range;
use crate::cmd::typecmd::refresh_bounds;
use crate::cmd::{CommandSpec, bad, checked_ids_or_selection, cmd, has_doc, has_selection, switch_or, text_param};
use crate::{EngineError, Result, Session};
/// Most text objects one command takes.
const MAX_TEXTS: usize = 10_000;
/// Most pieces Break Text and Split Text at Cursor make at once.
const MAX_PIECES: usize = 20_000;
/// Most breaks Remove Line / Paragraph Breaks removes at once.
const MAX_BREAKS: usize = 200_000;
/// Most pieces Combine Point Text tries to join, counted over every try: past it, what it joined
/// so far is the result.
const MAX_JOIN_TRIES: usize = 200_000;
/// How far (pt) a glyph Combine Point Text sets may land from where it was.
const TOLERANCE: f64 = 0.01;
/// Combine Point Text: pieces whose baselines are within this many ems of each other share a line
/// (the difference becomes baseline shift); a gap wider than `MAX_GAP` ems, or an overlap of more
/// than `MAX_OVERLAP`, starts another object.
const SAME_LINE: f64 = 0.4;
const MAX_GAP: f64 = 10.0;
const MAX_OVERLAP: f64 = 1.0;
/// Tracking Combine Point Text may give a character (thousandths of an em).
const TRACKING: std::ops::RangeInclusive<f64> = -1000.0..=10000.0;
pub fn specs() -> Vec<CommandSpec> {
vec![
cmd!(
"addon.cleanup.breakText",
"Break Text",
["Object", "Plug-ins", "Artwork Cleanup"],
None,
"{by?: \"lines\"|\"paragraphs\"|\"words\"|\"glyphs\" (lines), ids?: [id…] (default: the selection)} each horizontal point or area type object becomes point type objects, one per line, paragraph, word or glyph, each where its characters were and with their character styles, as one undo step; spaces between pieces go, a paragraph's lines stay lines (a wrapped line ends in a line break), justified lines close up, and a paragraph broken out whole keeps its bullet (not its number; other pieces have no list markers). Type on a path, vertical type, threaded type and area type whose text doesn't fit stay as they are → {ids (the pieces, selected), skipped}",
has_selection,
break_text
),
cmd!(
"addon.cleanup.combineText",
"Combine Point Text",
["Object", "Plug-ins", "Artwork Cleanup"],
None,
"{ids?: [id…] (default: the selection)} merge one-line horizontal point type into as few objects as it can, every glyph staying where it was: pieces turned and sheared alike whose baselines line up (within 0.4 em; the difference becomes baseline shift) join left to right, a space goes between pieces at least half a space apart, and tracking on the character before each piece keeps the spacing; each object keeps its character styles, and the leftmost piece of each run its place and paint, as one undo step → {ids (the objects made, selected), combined (pieces merged into them)}",
has_selection,
combine_text
),
cmd!(
"addon.cleanup.removeBreaks",
"Remove Breaks",
["Object", "Plug-ins", "Artwork Cleanup"],
None,
"{kind: \"line\"|\"paragraph\", ids?: [id…]} remove the forced line breaks (line) or paragraph returns (paragraph) in the characters selected with a Type tool, else in all the text of the selected type, as one undo step; a break between two words becomes a space → {ids, removed}",
has_selection,
remove_breaks
),
cmd!(
"addon.cleanup.splitText",
"Split Text at Cursor",
["Object", "Plug-ins", "Artwork Cleanup"],
None,
"{} while a Type tool edits horizontal point or area type: the text before the selected characters, they and the text after become point type objects of their own (at a cursor: before and after it), each where its characters were, as one undo step; spaces at the ends of each piece go, and editing ends with the pieces selected → {ids}",
editing_text,
split_text
),
cmd!(
"addon.cleanup.alignInPlace",
"Set Alignment, Keep Position",
["Object", "Plug-ins", "Artwork Cleanup"],
None,
"{align: \"left\"|\"center\"|\"right\", ids?: [id…] (default: the selection)} align every paragraph of each horizontal point type object so, moving the object so its longest line stays where it is (one line: the text doesn't move), as one undo step → {ids (the objects changed)}",
has_selection,
align_in_place
),
cmd!(
"addon.cleanup.resetTextTransform",
"Reset Text Transform",
["Object", "Plug-ins", "Artwork Cleanup"],
None,
"{scale?: bool (false: the horizontal scale stays; true: it goes too, the object's and every character's), ids?: [id…] (default: the selection)} take the rotation, shear and reflection off each point type object, its size and the centre of its text staying, as one undo step → {ids (the objects changed)}",
has_selection,
reset_transform
),
cmd!(
"addon.cleanup.replaceFonts",
"Replace Missing Fonts…",
["Object", "Plug-ins", "Artwork Cleanup"],
None,
"{font?: family (default: the default type font, Source Sans 3; any of its names)} set every character of the document's type whose font family isn't available in `font`, in the style it has closest to the one each had, as one undo step → {runs (style runs changed), font}",
has_doc,
replace_fonts
),
]
}
/// Enabled while a Type tool edits text.
fn editing_text(s: &Session) -> std::result::Result<(), String> {
match s.tool_options().get("editing") {
Some(v) if !v.is_null() => Ok(()),
_ => Err("Click in type with a Type tool to split it".into()),
}
}
/// The type objects under `roots` (inside groups and layers, not live art) that can be edited.
fn texts_under(d: &Document, roots: &[NodeId]) -> Result<Vec<NodeId>> {
fn visit(n: &Node, out: &mut Vec<NodeId>) {
match &n.kind {
NodeKind::Text(_) => out.push(n.id),
NodeKind::Group { children, .. } | NodeKind::Layer { children, .. } => children.iter().for_each(|c| visit(c, out)),
_ => {}
}
}
let mut out = vec![];
roots.iter().filter_map(|id| d.node(*id)).for_each(|n| visit(n, &mut out));
let editable = d.editable();
out.retain(|id| editable(*id));
out.dedup();
if out.len() > MAX_TEXTS {
return Err(EngineError::Other("Too many type objects to change at once".into()));
}
Ok(out)
}
fn text_of(d: &Document, id: NodeId) -> Option<&TextObject> {
match &d.node(id)?.kind {
NodeKind::Text(t) => Some(t),
_ => None,
}
}
fn layout(t: &TextObject) -> TextLayout {
vectorcraft_text::layout(FontDb::global(), t)
}
/// Where the layout draws the character at `byte` (the one it starts in): its glyph's pen position.
fn glyph_at(lay: &TextLayout, byte: usize) -> Option<Point> {
let start = lay.glyphs.iter().filter(|g| g.len > 0 && g.byte <= byte).map(|g| g.byte).max()?;
lay.glyphs.iter().find(|g| g.len > 0 && g.byte == start).map(|g| g.origin)
}
/// The pen position of the glyph that starts at `byte`, if one does.
fn glyph_starting(lay: &TextLayout, byte: usize) -> Option<Point> {
lay.glyphs.iter().find(|g| g.len > 0 && g.byte == byte).map(|g| g.origin)
}
/// The first glyph of line `l`, in text order.
fn line_start(lay: &TextLayout, l: usize) -> Option<Point> {
let line = lay.lines.get(l)?;
let g = lay.glyphs.get(line.glyph_start..line.glyph_end)?.iter().filter(|g| g.len > 0).min_by_key(|g| g.byte)?;
Some(g.origin)
}
/// `a..b` of `text` without the white space at its ends; None when nothing else is left.
fn trimmed(text: &str, a: usize, b: usize) -> Option<(usize, usize)> {
let s = text.get(a..b)?;
let start = a + (s.len() - s.trim_start().len());
let end = a + s.trim_end().len();
(start < end).then_some((start, end))
}
/// Paragraph alignment as point type keeps it: justified lines align to their side.
fn point_justify(j: Justify) -> Justify {
match j {
Justify::JustifyLeft | Justify::JustifyAll => Justify::Left,
Justify::JustifyCenter => Justify::Center,
Justify::JustifyRight => Justify::Right,
j => j,
}
}
#[derive(Clone, Copy, PartialEq)]
enum By {
Lines,
Paragraphs,
Words,
Glyphs,
}
/// A type object being cut into point type pieces: its text as drawn (Shrink Text to Fit's sizes
/// taken in) and its layout.
struct Source {
t: TextObject,
lay: TextLayout,
text: String,
}
impl Source {
/// Horizontal point or area type that isn't threaded and whose text all fits; None otherwise.
fn of(d: &Document, id: NodeId) -> Option<Self> {
let t = text_of(d, id)?;
if t.vertical || matches!(t.kind, TextKind::OnPath { .. }) || d.text_threads.iter().any(|th| th.contains(&id)) {
return None;
}
let lay = layout(t);
// Text left out of a frame would be lost with it.
if lay.overflow {
return None;
}
let t = if (lay.fit_scale - 1.0).abs() > 1e-9 { vectorcraft_text::scaled(t, lay.fit_scale) } else { t.clone() };
Some(Self { text: t.plain_text(), t, lay })
}
/// The byte ranges of the pieces `by` cuts the text into.
fn ranges(&self, by: By) -> Vec<(usize, usize)> {
let text = &self.text;
match by {
By::Paragraphs => {
let mut out = vec![];
let mut a = 0;
for (i, _) in text.match_indices('\n') {
out.push((a, i));
a = i + 1;
}
out.push((a, text.len()));
out
}
By::Lines => self.lay.lines.iter().map(|l| (l.start, l.end)).collect(),
By::Words => {
let mut out = vec![];
for l in &self.lay.lines {
let Some(s) = text.get(l.start..l.end) else { continue };
let mut word: Option<usize> = None;
for (i, c) in s.char_indices() {
match (c.is_whitespace(), word) {
(true, Some(w)) => {
out.push((l.start + w, l.start + i));
word = None;
}
(false, None) => word = Some(i),
_ => {}
}
}
if let Some(w) = word {
out.push((l.start + w, l.end));
}
}
out
}
By::Glyphs => {
// One piece per cluster (a ligature's characters stay together).
let mut clusters = std::collections::BTreeMap::new();
for g in self.lay.glyphs.iter().filter(|g| g.len > 0) {
let end = clusters.entry(g.byte).or_insert(g.byte + g.len);
*end = (*end).max(g.byte + g.len);
}
clusters.into_iter().collect()
}
}
}
/// Point type holding bytes `a..b` (white space at its ends left out) where they were drawn.
/// Lines the layout wrapped end in a forced line break; `lists` keeps bulleted paragraphs'
/// bullets where the piece starts its paragraph.
fn piece(&self, (a, b): (usize, usize), lists: bool) -> Option<TextObject> {
let (a, b) = trimmed(&self.text, a, b)?;
let mut p = TextObject::point(Point::ZERO, "", CharStyle::default());
p.runs = edit::slice_runs(&self.t.runs, a, b);
p.xf = self.t.xf;
p.optical_margin = self.t.optical_margin;
p.system_layout = self.t.system_layout;
p.fractional_widths = self.t.fractional_widths;
p.ruby =
self.t.ruby.iter().filter(|r| a <= r.start && r.end <= b).map(|r| RubySpan { start: r.start - a, end: r.end - a, ..r.clone() }).collect();
// The paragraphs it holds, with their styles; the first keeps its bullet only from its start.
let starts_para = self.text.get(..a).is_some_and(|s| s.rsplit('\n').next().is_some_and(|line| line.trim().is_empty()));
let styles = self.t.paragraph_styles();
let mut paras: Vec<ParaStyle> = styles.get(self.t.paragraphs_in(a, b))?.to_vec();
for (i, para) in paras.iter_mut().enumerate() {
para.justify = point_justify(para.justify);
if !(lists && (i > 0 || starts_para) && para.list.as_ref().is_some_and(|l| l.number.is_none())) {
para.list = None;
}
}
p.set_paragraph_styles(paras);
// Lines the layout wrapped end in a forced line break, in place of the spaces they ended in.
let mut wraps = vec![];
for next in self.lay.lines.iter().skip(1).filter(|l| a < l.start && l.start < b) {
let content = self.text.get(..next.start)?.trim_end_matches([' ', '\t']);
// A line that ends in a break of its own keeps it.
if !content.ends_with(['\n', '\u{2028}']) {
wraps.push((content.len().max(a) - a, next.start - a));
}
}
for &(s, e) in wraps.iter().rev() {
edit::replace_range(&mut p.runs, s, e, "\u{2028}");
p.ruby_after_replace(s, e, '\u{2028}'.len_utf8());
}
self.place(&mut p, a)?;
Some(p)
}
/// Move `p` (cut from byte `a`) so the glyph of its first character lands where it was drawn.
/// A piece of several lines that starts inside a left-aligned line takes a first-line indent,
/// so its later lines stay where they were too.
fn place(&self, p: &mut TextObject, a: usize) -> Option<()> {
let lay = layout(p);
let mut shift: Vec2 = glyph_at(&self.lay, a)? - glyph_at(&lay, 0)?;
let first = self.lay.lines.iter().rposition(|l| l.start <= a);
if let (Some(now), Some(was)) = (line_start(&lay, 1), first.and_then(|l| line_start(&self.lay, l + 1))) {
let off = was.x - (now.x + shift.x);
let left = match p.para.justify {
Justify::Left => true,
Justify::Auto => lay.lines.first().is_some_and(|l| !l.rtl),
_ => false,
};
if left && off.abs() > 1e-6 {
p.edit_paras(Some(0..1), |para| para.first_line_indent -= off);
shift.x += off;
}
}
p.xf = self.t.xf * Affine::translate(shift);
Some(())
}
}
/// Put each object of `items` (id, the node, its pieces) out of the document, its pieces in its
/// place, as one undo step labelled `label`, the pieces selected → their ids.
fn replace_with_pieces(s: &mut Session, label: &str, items: Vec<(NodeId, Node, Vec<TextObject>)>) -> Result<Vec<u64>> {
s.edit(label, |d, sel| {
let (mut made, mut places) = (vec![], vec![]);
for (id, mut node, pieces) in items {
// A light template: the pieces take its paint and attributes, not its text.
node.kind = NodeKind::Text(Box::new(TextObject::point(Point::ZERO, "", CharStyle::default())));
let nodes: Vec<Node> = pieces
.into_iter()
.map(|mut t| {
refresh_bounds(&mut t);
let mut n = d.reid(&node);
n.kind = NodeKind::Text(Box::new(t));
n
})
.collect();
made.extend(nodes.iter().map(|n| n.id));
places.push((id, nodes));
}
d.insert_beside(places, true)?;
sel.set(made.iter().copied());
Ok(made.iter().map(|i| i.0).collect())
})
}
fn ids_json(ids: &[NodeId]) -> Vec<u64> {
ids.iter().map(|i| i.0).collect()
}
fn break_text(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "addon.cleanup.breakText";
let by = match text_param(p, "by", C)?.unwrap_or("lines") {
"lines" => By::Lines,
"paragraphs" => By::Paragraphs,
"words" => By::Words,
"glyphs" => By::Glyphs,
other => return Err(bad(C, format!("`by` must be lines, paragraphs, words or glyphs, not {other:?}"))),
};
let d = &s.doc()?.doc;
let (mut items, mut skipped, mut count) = (vec![], vec![], 0);
for id in texts_under(d, &checked_ids_or_selection(s, p, C)?)? {
let (Some(src), Some(node)) = (Source::of(d, id), d.node(id)) else {
skipped.push(id);
continue;
};
let ranges = src.ranges(by);
count += ranges.len();
if count > MAX_PIECES {
return Err(EngineError::Other("Too many pieces to make at once".into()));
}
let pieces: Vec<TextObject> = ranges.into_iter().filter_map(|r| src.piece(r, by == By::Paragraphs)).collect();
if pieces.is_empty() {
skipped.push(id);
} else {
items.push((id, node.clone(), pieces));
}
}
if items.is_empty() {
return Err(EngineError::Other("Select horizontal point or area type whose text fits".into()));
}
let ids = replace_with_pieces(s, "Break Text", items)?;
Ok(json!({ "ids": ids, "skipped": ids_json(&skipped) }))
}
fn split_text(s: &mut Session, _: &Value) -> Result<Value> {
let Some((id, a, b)) = editing_range(s)? else {
return Err(EngineError::Other("Click in type with a Type tool to split it".into()));
};
let d = &s.doc()?.doc;
let (Some(src), Some(node)) = (Source::of(d, id), d.node(id).filter(|_| d.is_editable(id))) else {
return Err(EngineError::Other("Select horizontal point or area type whose text fits".into()));
};
let text = &src.text;
let (a, b) = (edit::floor_char(text, a.min(b)), edit::floor_char(text, a.max(b)));
let cuts: Vec<(usize, usize)> = if a == b { vec![(0, a), (a, text.len())] } else { vec![(0, a), (a, b), (b, text.len())] };
let pieces: Vec<TextObject> = cuts.into_iter().filter_map(|r| src.piece(r, true)).collect();
if pieces.len() < 2 {
return Err(EngineError::Other("Nothing to split at the cursor".into()));
}
let node = node.clone();
let ids = replace_with_pieces(s, "Split Text at Cursor", vec![(id, node, pieces)])?;
// The text edited is gone: the pieces stay selected.
s.set_tool_option("stopEditing", &Value::Bool(true));
Ok(json!({ "ids": ids }))
}
/// One piece of point type Combine Point Text takes: its text with its object's scale taken into
/// its characters, and where it starts and ends along its line in the frame of its turn and shear.
struct Piece {
id: NodeId,
t: TextObject,
/// The object's turn and shear (`xf` without its move).
turn: [f64; 4],
/// Where its first character's glyph starts on its baseline, in that frame.
at: Point,
/// Where its last glyph ends along the line, in that frame.
end: f64,
/// Its first character's size.
size: f64,
/// Each character's pen position from the first's (byte, x), as it lays out alone.
pens: Vec<(usize, f64)>,
}
impl Piece {
fn of(d: &Document, id: NodeId) -> Option<(Self, &Node)> {
let node = d.node(id)?;
let NodeKind::Text(t) = &node.kind else { return None };
let text = t.plain_text();
if t.vertical || !matches!(t.kind, TextKind::Point) || text.trim().is_empty() || text.contains(['\n', '\u{2028}']) {
return None;
}
let mut t = (**t).clone();
t.scale_text_attributes();
t.style_scale()?;
let lay = layout(&t);
let line = lay.lines.first().filter(|l| !l.rtl && lay.lines.len() == 1)?;
let first = glyph_at(&lay, 0)?;
// The first glyph of each cluster (a mark drawn over it shares its byte).
let mut seen = std::collections::HashSet::new();
let pens = lay.glyphs.iter().filter(|g| g.len > 0 && seen.insert(g.byte)).map(|g| (g.byte, g.origin.x - first.x)).collect();
let end = lay.glyphs.iter().map(|g| g.origin.x + g.advance).fold(first.x, f64::max) - first.x;
let [a, b, c, dd, _, _] = t.xf.as_coeffs();
let turn = Affine::new([a, b, c, dd, 0.0, 0.0]);
let at = turn.inverse() * (t.xf * Point::new(first.x, line.baseline));
let size = t.runs.first()?.style.size;
if !(at.x.is_finite() && at.y.is_finite() && size > 0.0) {
return None;
}
Some((Self { id, t, turn: [a, b, c, dd], at, end: at.x + end, size, pens }, node))
}
/// Whether `o` is turned and sheared alike.
fn turned_like(&self, o: &Piece) -> bool {
self.turn.iter().zip(&o.turn).all(|(x, y)| (x - y).abs() <= 1e-6 * x.abs().max(y.abs()).max(1.0))
}
}
/// Whether two objects look alike apart from their text (paint, opacity, blending, effects).
fn same_look(a: &Node, b: &Node) -> bool {
a.appearance == b.appearance
&& a.opacity == b.opacity
&& a.blend == b.blend
&& a.isolate == b.isolate
&& a.knockout == b.knockout
&& a.extras == b.extras
}
/// How wide a space of `style` is.
fn space_width(style: &CharStyle) -> f64 {
let probe = TextObject::point(Point::ZERO, " ", CharStyle { tracking: 0.0, ..style.clone() });
layout(&probe).glyphs.first().map_or(style.size * 0.25, |g| g.advance)
}
/// `chain` (pieces left to right on one line) as one point type object whose glyphs land where
/// theirs were, or the index of the first piece that can't join the ones before it so.
fn merge(chain: &[&Piece]) -> std::result::Result<TextObject, usize> {
let Some(first) = chain.first() else { return Err(0) };
let mut runs = vec![];
// Each piece's first byte, and the byte range of the character before it (whose tracking sets
// the gap).
let (mut starts, mut before) = (vec![], vec![]);
let mut text = String::new();
for (k, p) in chain.iter().enumerate() {
if let Some(prev) = k.checked_sub(1).and_then(|i| chain.get(i)) {
let starts_blank = p.t.plain_text().starts_with(char::is_whitespace);
let last = prev.t.runs.iter().rev().find(|r| !r.text.is_empty()).map(|r| r.style.clone()).unwrap_or_default();
if !text.ends_with(char::is_whitespace) && !starts_blank && p.at.x - prev.end >= 0.5 * space_width(&last) {
runs.push(vectorcraft_doc::TextRun::new(" ", last));
text.push(' ');
}
let c = text.chars().next_back().map_or(1, char::len_utf8);
before.push((text.len() - c, text.len()));
}
starts.push(text.len());
// Baselines a little apart: the difference is baseline shift (up is positive).
let shift = first.at.y - p.at.y;
for r in &p.t.runs {
let mut r = r.clone();
r.style.baseline_shift += shift;
text.push_str(&r.text);
runs.push(r);
}
}
let mut m = first.t.clone();
m.set_all_paras(ParaStyle { justify: Justify::Left, ..first.t.para.clone() });
let [a, b, c, d] = first.turn;
let base = Affine::new([a, b, c, d, 0.0, 0.0]) * Affine::translate(first.at.to_vec2());
m.xf = base;
m.runs = runs;
m.ruby.clear();
// Tracking after the character before each piece, until each lands where it was.
let targets: Vec<f64> = chain.iter().map(|p| p.at.x - first.at.x).collect();
let mut lay = layout(&m);
for _ in 0..4 {
// A piece whose first character joins the one before it (a ligature) can't keep its place.
let xs: Vec<f64> = match starts.iter().enumerate().map(|(k, b)| glyph_starting(&lay, *b).map(|g| g.x).ok_or(k)).collect() {
Ok(xs) => xs,
Err(k) => return Err(k.max(1)),
};
let x0 = xs.first().copied().unwrap_or(0.0);
let off: Vec<f64> = targets.iter().zip(&xs).map(|(t, x)| t - (x - x0)).collect();
if off.iter().all(|o| o.abs() <= TOLERANCE * 0.5) {
break;
}
// Moving piece k moves those after it as far.
for (k, w) in off.windows(2).enumerate() {
let (delta, Some(&(s, e))) = (w[1] - w[0], before.get(k)) else { continue };
if delta.abs() > 1e-9 {
edit::style_range(&mut m.runs, s, e, |st| st.tracking += delta / st.size.max(1e-6) * 1000.0);
if !TRACKING.contains(&edit::style_at(&m.runs, s).tracking) {
return Err(k + 1);
}
}
}
lay = layout(&m);
}
// Every glyph where it was, within the tolerance; else the run ends before the piece that moved.
let x0 = glyph_starting(&lay, 0).map_or(0.0, |g| g.x);
for (k, p) in chain.iter().enumerate() {
let (Some(&start), Some(&target)) = (starts.get(k), targets.get(k)) else { return Err(k.max(1)) };
let moved = p.pens.iter().any(|(byte, x)| glyph_starting(&lay, start + byte).is_none_or(|g| (g.x - x0 - target - x).abs() > TOLERANCE));
if moved {
return Err(k.max(1));
}
}
m.xf = base * Affine::translate((-x0, 0.0));
Ok(m)
}
fn combine_text(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "addon.cleanup.combineText";
let d = &s.doc()?.doc;
let mut pieces: Vec<(Piece, &Node)> =
texts_under(d, &checked_ids_or_selection(s, p, C)?)?.into_iter().filter_map(|id| Piece::of(d, id)).collect();
// Lines: pieces turned alike and looking alike, by baseline, then left to right.
pieces.sort_by(|x, y| x.0.at.y.total_cmp(&y.0.at.y).then(x.0.at.x.total_cmp(&y.0.at.x)));
let mut lines: Vec<Vec<(&Piece, &Node)>> = vec![];
for (piece, node) in &pieces {
let fits = |l: &Vec<(&Piece, &Node)>| {
l.first()
.is_some_and(|(f, n)| f.turned_like(piece) && same_look(n, node) && (f.at.y - piece.at.y).abs() <= SAME_LINE * f.size.min(piece.size))
};
match lines.iter_mut().find(|l| fits(l)) {
Some(l) => l.push((piece, node)),
None => lines.push(vec![(piece, node)]),
}
}
// Runs along each line: a wide gap or an overlap starts another.
let mut chains: Vec<Vec<&Piece>> = vec![];
for mut l in lines {
l.sort_by(|x, y| x.0.at.x.total_cmp(&y.0.at.x));
let mut chain: Vec<&Piece> = vec![];
for (piece, _) in l {
if let Some(prev) = chain.last() {
let gap = piece.at.x - prev.end;
if gap > MAX_GAP * prev.size || gap < -MAX_OVERLAP * prev.size {
chains.push(std::mem::take(&mut chain));
}
}
chain.push(piece);
}
chains.push(chain);
}
// Each run as one object, or as several where a piece can't keep its place joined.
let mut made: Vec<(NodeId, TextObject, Vec<NodeId>)> = vec![];
let mut tries = 0;
'chains: for chain in chains {
let mut rest: &[&Piece] = &chain;
while rest.len() > 1 {
let mut upto = rest.len();
while let Some(part) = rest.get(..upto) {
tries += part.len();
if tries > MAX_JOIN_TRIES {
break 'chains;
}
match merge(part) {
Ok(m) => {
let gone = part.iter().skip(1).map(|p| p.id).collect();
if let Some(first) = part.first() {
made.push((first.id, m, gone));
}
break;
}
// A run of one stays as it is.
Err(k) if k <= 1 => {
upto = 1;
break;
}
Err(k) => upto = k,
}
}
rest = rest.get(upto.max(1)..).unwrap_or_default();
}
}
if made.is_empty() {
return Err(EngineError::Other("Select two or more one-line point type objects on one baseline to combine".into()));
}
let combined: usize = made.iter().map(|m| m.2.len()).sum();
let ids: Vec<u64> = made.iter().map(|m| m.0.0).collect();
s.edit("Combine Point Text", |d, sel| {
let mut gone = vec![];
for (id, mut t, others) in made {
refresh_bounds(&mut t);
if let Some(n) = d.node_mut(id) {
n.kind = NodeKind::Text(Box::new(t));
}
gone.extend(others);
}
d.remove_many(&gone)?;
sel.set(ids.iter().map(|i| NodeId(*i)));
Ok(())
})?;
Ok(json!({ "ids": ids, "combined": combined }))
}
/// Remove the breaks `brk` in bytes `a..b` of `t`: one between two words becomes a space → how many
/// went.
fn strip_breaks(t: &mut TextObject, a: usize, b: usize, brk: char) -> usize {
let text = t.plain_text();
let (a, b) = (edit::floor_char(&text, a), edit::floor_char(&text, b));
let Some(range) = text.get(a..b) else { return 0 };
let mut prev = text.get(..a).and_then(|s| s.chars().next_back());
let mut cuts = vec![];
for (i, c) in range.char_indices() {
let at = a + i;
if c != brk {
prev = Some(c);
continue;
}
// The next character left: past the breaks that go too.
let after = at + c.len_utf8();
let next = text.get(after..).and_then(|rest| rest.char_indices().find(|(j, n)| *n != brk || after + j >= b).map(|(_, n)| n));
let space = prev.is_some_and(|p| !p.is_whitespace()) && next.is_some_and(|n| !n.is_whitespace());
if space {
prev = Some(' ');
}
cuts.push((at, space));
}
for &(at, space) in cuts.iter().rev() {
let insert = if space { " " } else { "" };
let end = at + brk.len_utf8();
t.splice_paras(at, end, insert);
edit::replace_range(&mut t.runs, at, end, insert);
t.ruby_after_replace(at, end, insert.len());
}
cuts.len()
}
fn remove_breaks(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "addon.cleanup.removeBreaks";
let (brk, label, none) = match text_param(p, "kind", C)? {
Some("line") => ('\u{2028}', "Remove Line Breaks", "No line breaks to remove"),
Some("paragraph") => ('\n', "Remove Paragraph Breaks", "No paragraph breaks to remove"),
Some(other) => return Err(bad(C, format!("`kind` must be \"line\" or \"paragraph\", not {other:?}"))),
None => return Err(bad(C, "missing `kind`")),
};
// The characters a Type tool has selected, else all the text of the selected type.
let editing = if p.get("ids").is_some() { None } else { editing_range(s)?.filter(|(_, a, b)| a != b) };
let d = &s.doc()?.doc;
let targets: Vec<(NodeId, usize, usize)> = match editing {
Some((id, a, b)) => vec![(id, a.min(b), a.max(b))].into_iter().filter(|(id, ..)| d.is_editable(*id)).collect(),
None => texts_under(d, &checked_ids_or_selection(s, p, C)?)?.into_iter().map(|id| (id, 0, usize::MAX)).collect(),
};
let count: usize = targets
.iter()
.filter_map(|(id, a, b)| {
let text = text_of(d, *id)?.plain_text();
let (a, b) = (edit::floor_char(&text, *a), edit::floor_char(&text, *b));
Some(text.get(a..b)?.matches(brk).count())
})
.sum();
if count == 0 {
return Err(EngineError::Other(none.into()));
}
if count > MAX_BREAKS {
return Err(EngineError::Other("Too many breaks to remove at once".into()));
}
let (removed, ids) = s.edit(label, |d, _| {
let (mut removed, mut ids) = (0, vec![]);
for (id, a, b) in targets {
let Some(NodeKind::Text(t)) = d.node_mut(id).map(|n| &mut n.kind) else { continue };
let n = strip_breaks(t, a, b, brk);
if n > 0 {
refresh_bounds(t);
removed += n;
ids.push(id.0);
}
}
Ok((removed, ids))
})?;
Ok(json!({ "ids": ids, "removed": removed }))
}
/// Horizontal point type among the objects `p` names.
fn point_texts(s: &Session, p: &Value, cmd: &str) -> Result<Vec<NodeId>> {
let d = &s.doc()?.doc;
let ids: Vec<NodeId> = texts_under(d, &checked_ids_or_selection(s, p, cmd)?)?
.into_iter()
.filter(|id| text_of(d, *id).is_some_and(|t| matches!(t.kind, TextKind::Point)))
.collect();
if ids.is_empty() {
return Err(EngineError::Other("Select point type".into()));
}
Ok(ids)
}
/// Replace the type objects of `changed` as one undo step labelled `label` → their ids.
fn set_texts(s: &mut Session, label: &str, changed: Vec<(NodeId, TextObject)>, reset_angle: bool) -> Result<Value> {
let ids: Vec<u64> = changed.iter().map(|c| c.0.0).collect();
s.edit(label, |d, _| {
for (id, mut t) in changed {
refresh_bounds(&mut t);
if let Some(n) = d.node_mut(id) {
n.kind = NodeKind::Text(Box::new(t));
if reset_angle {
n.bbox_angle = 0.0;
}
}
}
Ok(())
})?;
Ok(json!({ "ids": ids }))
}
fn align_in_place(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "addon.cleanup.alignInPlace";
let justify = match text_param(p, "align", C)? {
Some("left") => Justify::Left,
Some("center") => Justify::Center,
Some("right") => Justify::Right,
Some(other) => return Err(bad(C, format!("`align` must be \"left\", \"center\" or \"right\", not {other:?}"))),
None => return Err(bad(C, "missing `align`")),
};
let ids = point_texts(s, p, C)?;
let d = &s.doc()?.doc;
let mut changed = vec![];
for id in ids {
let Some(t) = text_of(d, id).filter(|t| !t.vertical && t.para_styles().any(|para| para.justify != justify)) else { continue };
let was = layout(t);
// The longest line stays where it is.
let Some((i, line)) = was.lines.iter().enumerate().max_by(|a, b| (a.1.x1 - a.1.x0).total_cmp(&(b.1.x1 - b.1.x0))) else { continue };
let mut n = t.clone();
n.edit_paras(None, |para| para.justify = justify);
let Some(now) = layout(&n).lines.get(i).map(|l| l.x0) else { continue };
n.xf *= Affine::translate((line.x0 - now, 0.0));
changed.push((id, n));
}
if changed.is_empty() {
return Err(EngineError::Other("The point type is aligned that way already".into()));
}
set_texts(s, "Set Alignment, Keep Position", changed, false)
}
fn reset_transform(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "addon.cleanup.resetTextTransform";
let scale = switch_or(p, "scale", false, C)?;
let ids = point_texts(s, p, C)?;
let d = &s.doc()?.doc;
let mut changed = vec![];
for id in ids {
let Some(t) = text_of(d, id) else { continue };
let [a, b, c, dd, _, _] = t.xf.as_coeffs();
// The object's own axes: its width along its baseline, and its height square to it.
let (sx, det) = (a.hypot(b), a * dd - b * c);
if !(sx > 1e-12 && sx.is_finite() && det.is_finite() && det.abs() > 1e-12) {
continue;
}
let sy = det.abs() / sx;
let unscaled = |t: &TextObject| t.runs.iter().all(|r| r.style.h_scale == 100.0);
let upright = b.abs() <= 1e-9 * sx && c.abs() <= 1e-9 * sy && a > 0.0 && dd > 0.0;
if upright && (!scale || ((sx - sy).abs() <= 1e-9 * sy && unscaled(t))) {
continue;
}
let mut n = t.clone();
let w = if scale {
n.runs.iter_mut().for_each(|r| r.style.h_scale = 100.0);
sy
} else {
sx
};
// The centre of the text stays where it is.
let centre = t.xf * layout(t).bounds.center();
let turn = Affine::scale_non_uniform(w, sy);
let now = turn * layout(&n).bounds.center();
n.xf = Affine::translate(centre - now) * turn;
changed.push((id, n));
}
if changed.is_empty() {
return Err(EngineError::Other("The point type has no transform to reset".into()));
}
set_texts(s, "Reset Text Transform", changed, true)
}
fn replace_fonts(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "addon.cleanup.replaceFonts";
let default = CharStyle::default().font_family;
let wanted = text_param(p, "font", C)?.filter(|f| !f.trim().is_empty()).unwrap_or(&default);
let db = FontDb::global();
// Any name of the family is stored as its own name.
let family = match db.resolve(wanted, "Regular") {
Some((f, m)) if m != FontMatch::Missing => f.family.clone(),
_ => return Err(bad(C, "the replacement font is not available")),
};
let missing = |st: &CharStyle| db.resolve(&st.font_family, &st.font_style).is_none_or(|(_, m)| m == FontMatch::Missing);
let d = &s.doc()?.doc;
let mut ids = vec![];
for l in &d.layers {
l.walk(&mut |n| {
if let NodeKind::Text(t) = &n.kind
&& t.runs.iter().any(|r| missing(&r.style))
{
ids.push(n.id);
}
});
}
if ids.is_empty() {
return Err(EngineError::Other("No fonts are missing".into()));
}
let runs = s.edit("Replace Missing Fonts", |d, _| {
let mut runs = 0;
for id in &ids {
let Some(NodeKind::Text(t)) = d.node_mut(*id).map(|n| &mut n.kind) else { continue };
for r in t.runs.iter_mut().filter(|r| missing(&r.style)) {
// The style the font has closest to the one the character had.
r.style.font_style = db.face(&family, &r.style.font_style).map_or_else(|| r.style.font_style.clone(), |f| f.style.clone());
r.style.font_family = family.clone();
r.style.font_version = None;
r.style.font_axes.clear();
runs += 1;
}
edit::normalize(&mut t.runs);
refresh_bounds(t);
}
Ok(runs)
})?;
Ok(json!({ "runs": runs, "font": family }))
}
@@ -0,0 +1,307 @@
//! Artwork Cleanup's text tools: pieces stay where their glyphs were drawn and keep their styles,
//! each command is one undo step and refuses cleanly when there is nothing to do.
use serde_json::{Value, json};
use vectorcraft_doc::{Justify, NodeId, NodeKind, TextKind, TextObject};
use vectorcraft_geom::Point;
use crate::{EngineError, Session};
fn session() -> Session {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 600, "height": 400})).unwrap();
s
}
fn text(s: &mut Session, p: Value) -> NodeId {
NodeId(s.execute("text.create", &p).unwrap()["id"].as_u64().unwrap())
}
fn obj(s: &Session, id: NodeId) -> TextObject {
match &s.doc().unwrap().doc.node(id).unwrap().kind {
NodeKind::Text(t) => (**t).clone(),
_ => panic!("not text"),
}
}
fn select(s: &mut Session, ids: &[NodeId]) {
s.select(|_, sel| sel.set(ids.iter().copied())).unwrap();
}
fn ids(v: &Value) -> Vec<NodeId> {
v["ids"].as_array().unwrap().iter().map(|i| NodeId(i.as_u64().unwrap())).collect()
}
/// Each drawn character of `ids` (spaces left out) with where its glyph starts on the page, in
/// the order of the objects and their text.
fn glyphs(s: &Session, ids: &[NodeId]) -> Vec<(char, Point)> {
let mut out = vec![];
for id in ids {
let t = obj(s, *id);
let text = t.plain_text();
let lay = vectorcraft_text::layout(vectorcraft_text::FontDb::global(), &t);
let mut gs: Vec<_> = lay.glyphs.iter().filter(|g| g.len > 0).collect();
gs.sort_by_key(|g| g.byte);
gs.dedup_by_key(|g| g.byte);
for g in gs {
let c = text[g.byte..].chars().next().unwrap();
if !c.is_whitespace() {
out.push((c, t.xf * g.origin));
}
}
}
out
}
fn assert_same_places(was: &[(char, Point)], now: &[(char, Point)]) {
assert_eq!(was.iter().map(|g| g.0).collect::<String>(), now.iter().map(|g| g.0).collect::<String>());
for (a, b) in was.iter().zip(now) {
assert!((a.1 - b.1).hypot() < 0.02, "{:?} moved to {:?}", a, b);
}
}
fn undo_steps(s: &Session) -> usize {
s.doc().unwrap().history.undo.len()
}
fn doc_json(s: &Session) -> String {
serde_json::to_string(&s.doc().unwrap().doc.layers).unwrap()
}
fn other(r: Result<Value, EngineError>) -> String {
match r {
Err(EngineError::Other(m)) => m,
r => panic!("expected a refusal, got {r:?}"),
}
}
#[test]
fn break_into_words_keeps_places_styles_and_is_one_step() {
let mut s = session();
let id = text(&mut s, json!({"x": 40, "y": 100, "text": "Hello big World", "size": 24}));
s.execute("text.setRangeStyle", &json!({"id": id.0, "start": 6, "end": 9, "size": 36, "tracking": 50})).unwrap();
let rotated = text(&mut s, json!({"x": 300, "y": 200, "text": "Turned words", "size": 18, "rotation": 30}));
let was = glyphs(&s, &[id, rotated]);
let before = doc_json(&s);
let steps = undo_steps(&s);
select(&mut s, &[id, rotated]);
let r = s.execute("addon.cleanup.breakText", &json!({"by": "words"})).unwrap();
let pieces = ids(&r);
assert_eq!(pieces.len(), 5);
assert_eq!(undo_steps(&s), steps + 1);
assert_eq!(s.doc().unwrap().selection.objects.len(), 5, "the pieces are selected");
let texts: Vec<String> = pieces.iter().map(|p| obj(&s, *p).plain_text()).collect();
assert_eq!(texts, ["Hello", "big", "World", "Turned", "words"]);
assert_same_places(&was, &glyphs(&s, &pieces));
let big = obj(&s, pieces[1]);
assert!(matches!(big.kind, TextKind::Point));
assert_eq!((big.runs[0].style.size, big.runs[0].style.tracking), (36.0, 50.0));
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(doc_json(&s), before);
}
#[test]
fn break_area_type_into_lines_paragraphs_and_glyphs() {
for (by, count) in [("lines", None), ("paragraphs", Some(2)), ("glyphs", None)] {
let mut s = session();
let id = text(
&mut s,
json!({"x": 40, "y": 40, "text": "A paragraph long enough to wrap in its frame\nSecond one", "size": 14, "area": {"width": 140, "height": 200}}),
);
let t = obj(&s, id);
let lay = vectorcraft_text::layout(vectorcraft_text::FontDb::global(), &t);
assert!(lay.lines.len() >= 3, "the first paragraph wraps");
let was = glyphs(&s, &[id]);
select(&mut s, &[id]);
let r = s.execute("addon.cleanup.breakText", &json!({"by": by})).unwrap();
let pieces = ids(&r);
let want = count.unwrap_or(if by == "lines" { lay.lines.len() } else { was.len() });
assert_eq!(pieces.len(), want, "{by}");
assert_same_places(&was, &glyphs(&s, &pieces));
if by == "paragraphs" {
// The wrapped lines end in line breaks.
assert!(obj(&s, pieces[0]).plain_text().contains('\u{2028}'));
}
}
}
#[test]
fn break_text_refuses_what_it_cannot_break() {
let mut s = session();
s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 40, "height": 40})).unwrap();
assert!(other(s.execute("addon.cleanup.breakText", &json!({}))).contains("point or area type"));
let r = s.execute("addon.cleanup.breakText", &json!({"by": "sentences"}));
assert!(matches!(r, Err(EngineError::BadParams { .. })), "{r:?}");
}
#[test]
fn combine_puts_glyph_pieces_back_together_where_they_were() {
let mut s = session();
let id = text(&mut s, json!({"x": 50, "y": 120, "text": "Water Lily", "size": 30}));
s.execute("text.setRangeStyle", &json!({"id": id.0, "start": 6, "end": 10, "fill": "#ff0000"})).unwrap();
let was = glyphs(&s, &[id]);
select(&mut s, &[id]);
let pieces = ids(&s.execute("addon.cleanup.breakText", &json!({"by": "glyphs"})).unwrap());
assert_eq!(pieces.len(), 9);
let steps = undo_steps(&s);
let r = s.execute("addon.cleanup.combineText", &json!({})).unwrap();
assert_eq!(undo_steps(&s), steps + 1);
let made = ids(&r);
assert_eq!((made.len(), r["combined"].as_u64()), (1, Some(8)));
let t = obj(&s, made[0]);
assert_eq!(t.plain_text(), "Water Lily", "a space goes back between the words");
assert_same_places(&was, &glyphs(&s, &made));
let red = t.runs.iter().find(|r| r.text.contains('L')).unwrap();
assert_ne!(red.style.fill, t.runs[0].style.fill, "styles stay");
s.execute("edit.undo", &json!({})).unwrap();
assert!(s.doc().unwrap().doc.node(pieces[1]).is_some(), "undo brings the pieces back");
}
#[test]
fn combine_joins_only_pieces_on_one_line_turned_alike() {
let mut s = session();
let a = text(&mut s, json!({"x": 50, "y": 100, "text": "Left", "size": 20}));
let b = text(&mut s, json!({"x": 110, "y": 100, "text": "Right", "size": 20}));
let c = text(&mut s, json!({"x": 50, "y": 200, "text": "Below", "size": 20}));
let d = text(&mut s, json!({"x": 300, "y": 100, "text": "Turned", "size": 20, "rotation": 45}));
let was = glyphs(&s, &[a, b]);
select(&mut s, &[a, b, c, d]);
let r = s.execute("addon.cleanup.combineText", &json!({})).unwrap();
assert_eq!(ids(&r), [a]);
assert_eq!(obj(&s, a).plain_text(), "Left Right");
assert_same_places(&was, &glyphs(&s, &[a]));
let doc = &s.doc().unwrap().doc;
assert!(doc.node(b).is_none() && doc.node(c).is_some() && doc.node(d).is_some());
select(&mut s, &[c, d]);
assert!(other(s.execute("addon.cleanup.combineText", &json!({}))).contains("combine"));
}
#[test]
fn remove_line_and_paragraph_breaks() {
let mut s = session();
let id = text(&mut s, json!({"x": 40, "y": 60, "text": "one\u{2028}two \u{2028}three\nfour\n\nfive", "size": 12}));
select(&mut s, &[id]);
let before = doc_json(&s);
let r = s.execute("addon.cleanup.removeBreaks", &json!({"kind": "line"})).unwrap();
assert_eq!(r["removed"], 2);
assert_eq!(obj(&s, id).plain_text(), "one two three\nfour\n\nfive");
let r = s.execute("addon.cleanup.removeBreaks", &json!({"kind": "paragraph"})).unwrap();
assert_eq!(r["removed"], 3);
let t = obj(&s, id);
assert_eq!((t.plain_text().as_str(), t.paragraph_count()), ("one two three four five", 1));
assert_eq!(other(s.execute("addon.cleanup.removeBreaks", &json!({"kind": "line"}))), "No line breaks to remove");
s.execute("edit.undo", &json!({})).unwrap();
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(doc_json(&s), before);
assert!(matches!(s.execute("addon.cleanup.removeBreaks", &json!({})), Err(EngineError::BadParams { .. })));
}
/// The Type tool editing `id` with bytes `anchor..caret` selected.
fn edit_text(s: &mut Session, id: NodeId, anchor: usize, caret: usize) {
s.select_tool("type", Default::default()).unwrap();
s.set_tool_option("textSelection", &json!({"editing": id.0, "anchor": anchor, "caret": caret}));
}
#[test]
fn remove_breaks_in_the_characters_a_type_tool_selected() {
let mut s = session();
let id = text(&mut s, json!({"x": 40, "y": 60, "text": "a\nb\nc", "size": 12}));
select(&mut s, &[id]);
edit_text(&mut s, id, 0, 2);
s.execute("addon.cleanup.removeBreaks", &json!({"kind": "paragraph"})).unwrap();
assert_eq!(obj(&s, id).plain_text(), "a b\nc");
}
#[test]
fn split_at_the_cursor_and_around_a_selection() {
for (anchor, caret, want) in [(6, 6, &["Split", "here please"][..]), (6, 10, &["Split", "here", "please"][..])] {
let mut s = session();
let id = text(&mut s, json!({"x": 40, "y": 60, "text": "Split here please", "size": 20, "justify": "center"}));
let was = glyphs(&s, &[id]);
select(&mut s, &[id]);
let r = s.execute("addon.cleanup.splitText", &json!({}));
assert!(matches!(&r, Err(EngineError::Disabled(_, why)) if why.contains("Type tool")), "{r:?}");
edit_text(&mut s, id, anchor, caret);
let steps = undo_steps(&s);
let r = s.execute("addon.cleanup.splitText", &json!({})).unwrap();
assert_eq!(undo_steps(&s), steps + 1);
let pieces = ids(&r);
let texts: Vec<String> = pieces.iter().map(|p| obj(&s, *p).plain_text()).collect();
assert_eq!(texts, want);
assert_same_places(&was, &glyphs(&s, &pieces));
assert!(s.tool_options()["editing"].is_null(), "editing ends");
}
}
#[test]
fn split_keeps_the_later_lines_of_a_wrapped_piece() {
let mut s = session();
let id = text(
&mut s,
json!({"x": 40, "y": 40, "text": "First words then a long run of text that wraps in the frame", "size": 14, "area": {"width": 160, "height": 200}}),
);
let was = glyphs(&s, &[id]);
select(&mut s, &[id]);
edit_text(&mut s, id, 6, 6);
let pieces = ids(&s.execute("addon.cleanup.splitText", &json!({})).unwrap());
assert_eq!(pieces.len(), 2);
assert_same_places(&was, &glyphs(&s, &pieces));
}
#[test]
fn set_alignment_keeps_the_text_in_place() {
let mut s = session();
let id = text(&mut s, json!({"x": 100, "y": 60, "text": "Stay put", "size": 20}));
let multi = text(&mut s, json!({"x": 100, "y": 160, "text": "Short\u{2028}The longest line", "size": 20}));
let was = glyphs(&s, &[id]);
let longest: Vec<_> = glyphs(&s, &[multi]).into_iter().skip(5).collect();
select(&mut s, &[id, multi]);
for align in ["center", "right", "left"] {
s.execute("addon.cleanup.alignInPlace", &json!({"align": align})).unwrap();
assert_same_places(&was, &glyphs(&s, &[id]));
assert_same_places(&longest, &glyphs(&s, &[multi]).into_iter().skip(5).collect::<Vec<_>>());
}
assert_eq!(obj(&s, id).para.justify, Justify::Left);
assert!(other(s.execute("addon.cleanup.alignInPlace", &json!({"align": "left"}))).contains("already"));
assert!(matches!(s.execute("addon.cleanup.alignInPlace", &json!({"align": "up"})), Err(EngineError::BadParams { .. })));
}
#[test]
fn reset_text_transform_takes_off_rotation_and_with_scale_the_horizontal_scale() {
let mut s = session();
let id = text(&mut s, json!({"x": 100, "y": 100, "text": "Tilted", "size": 20, "rotation": 30}));
s.execute("text.setRangeStyle", &json!({"id": id.0, "hScale": 150})).unwrap();
let centre = |s: &Session| {
let t = obj(s, id);
t.xf * vectorcraft_text::layout(vectorcraft_text::FontDb::global(), &t).bounds.center()
};
let was = centre(&s);
let size = obj(&s, id).effective_char_style(&obj(&s, id).runs[0].style).unwrap().size;
select(&mut s, &[id]);
s.execute("addon.cleanup.resetTextTransform", &json!({})).unwrap();
let t = obj(&s, id);
let [_, b, c, _, _, _] = t.xf.as_coeffs();
assert!(b.abs() < 1e-9 && c.abs() < 1e-9, "{:?}", t.xf);
assert!((centre(&s) - was).hypot() < 1e-6);
assert!((t.effective_char_style(&t.runs[0].style).unwrap().size - size).abs() < 1e-6, "the size stays");
assert!(t.runs[0].style.h_scale > 100.0, "the horizontal scale stays");
assert!(other(s.execute("addon.cleanup.resetTextTransform", &json!({}))).contains("no transform"));
s.execute("addon.cleanup.resetTextTransform", &json!({"scale": true})).unwrap();
assert_eq!(obj(&s, id).runs[0].style.h_scale, 100.0);
}
#[test]
fn replace_missing_fonts_with_the_default_or_a_chosen_font() {
let mut s = session();
let gone = text(&mut s, json!({"x": 40, "y": 60, "text": "Lost", "size": 20, "font": "No Such Font Family", "style": "Bold"}));
let kept = text(&mut s, json!({"x": 40, "y": 120, "text": "Fine", "size": 20}));
let fine = obj(&s, kept);
let r = s.execute("addon.cleanup.replaceFonts", &json!({})).unwrap();
assert_eq!((r["runs"].as_u64(), r["font"].as_str()), (Some(1), Some("Source Sans 3")));
let t = obj(&s, gone);
assert_eq!((t.runs[0].style.font_family.as_str(), t.runs[0].style.font_style.as_str()), ("Source Sans 3", "Bold"));
assert_eq!(obj(&s, kept), fine);
assert_eq!(other(s.execute("addon.cleanup.replaceFonts", &json!({}))), "No fonts are missing");
let r = s.execute("addon.cleanup.replaceFonts", &json!({"font": "No Such Font Family"}));
assert!(matches!(r, Err(EngineError::BadParams { .. })), "{r:?}");
}
+54
View File
@@ -559,6 +559,23 @@ fn structured_junk() {
("addon.pen.reshape", json!({"id": 2, "segment": 0, "t": "x", "dx": 1, "dy": 1, "mode": "symmetric"})),
("addon.pen.nextSegment", json!({"curved": "yes"})),
("addon.pen.closeJoin", json!({"ids": [u64::MAX]})),
("addon.cleanup.breakText", json!({"by": "glyphs", "ids": [0, 1, 2, 3, 4, 5, 6, 7, 8, u64::MAX]})),
("addon.cleanup.breakText", json!({"by": ["lines"]})),
("addon.cleanup.breakText", json!({"by": "paragraphs"})),
("addon.cleanup.breakText", json!({"by": "words", "ids": "all"})),
("addon.cleanup.combineText", json!({"ids": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]})),
("addon.cleanup.combineText", json!({"ids": [-1, 1e308]})),
("addon.cleanup.removeBreaks", json!({"kind": "paragraph", "ids": [1, 2, 3, 4, 5, 6, 7, 8, u64::MAX]})),
("addon.cleanup.removeBreaks", json!({"kind": 3})),
("addon.cleanup.removeBreaks", json!({"kind": "line", "ids": null})),
("addon.cleanup.splitText", json!({"at": 1e308})),
("addon.cleanup.alignInPlace", json!({"align": "right", "ids": [1, 2, 3, 4, 5, 6, 7, 8]})),
("addon.cleanup.alignInPlace", json!({"align": null, "ids": [-1]})),
("addon.cleanup.resetTextTransform", json!({"scale": "yes"})),
("addon.cleanup.resetTextTransform", json!({"scale": true, "ids": [1, 2, 3, 4, 5, 6, 7, 8]})),
("addon.cleanup.replaceFonts", json!({"font": ""})),
("addon.cleanup.replaceFonts", json!({"font": ["Source Sans 3"]})),
("addon.cleanup.replaceFonts", json!({"font": "\u{0}"})),
];
let mut failures = vec![];
for fx in Fixture::ALL {
@@ -571,6 +588,43 @@ fn structured_junk() {
assert!(failures.is_empty(), "{} failures:\n{}", failures.len(), failures.join("\n"));
}
/// Artwork Cleanup's text tools while a Type tool edits type with junk ranges (past the end,
/// reversed, inside a character): never a panic, and the document stays sound.
#[test]
fn cleanup_text_tools_with_junk_editing_ranges() {
let ranges = [(0, u64::MAX / 4), (u64::MAX, 1), (5, 5), (1, 2), (u64::MAX / 4, u64::MAX / 4)];
let mut failures = vec![];
for fx in Fixture::ALL {
let mut texts = vec![];
for l in &fx.session().doc().unwrap().doc.layers {
l.walk(&mut |n| {
if matches!(n.kind, vectorcraft_doc::NodeKind::Text(_)) {
texts.push(n.id.0);
}
});
}
for id in texts {
for (anchor, caret) in ranges {
for (cmd, p) in [("addon.cleanup.splitText", json!({})), ("addon.cleanup.removeBreaks", json!({"kind": "paragraph"}))] {
let mut s = fx.session();
let r = catch_quiet(|| {
let _ = s.execute("select.set", &json!({"ids": [id]}));
let _ = s.select_tool("type", Default::default());
s.set_tool_option("textSelection", &json!({"editing": id, "anchor": anchor, "caret": caret}));
no_panic(s.execute(cmd, &p))
});
if let Err(msg) = r {
failures.push(format!("PANIC {cmd} {id} {anchor}..{caret} [{fx:?}]: {msg}"));
} else if let Err(e) = check_session(&s) {
failures.push(format!("{cmd} {id} {anchor}..{caret} [{fx:?}]: {e}"));
}
}
}
}
}
assert!(failures.is_empty(), "{} failures:\n{}", failures.len(), failures.join("\n"));
}
/// Shape Builder Tool Options (Gap Detection) with junk values: never a panic, and the tool still
/// answers with options and works on a drag afterwards.
#[test]
+140 -10
View File
@@ -1,7 +1,8 @@
//! Artwork Cleanup's panel and submenu (Object › Plug-ins › Artwork Cleanup). The panel runs the
//! Document Health Check on the selection, or the document while nothing is selected, lists what it
//! found, and shows or fixes the issues picked in the list (Fix All: every issue listed), each fix
//! one undo step. Below, the add-on's one-click tools open their options as their menu items do.
//! one undo step. Below, the add-on's one-click tools open their options as their menu items do,
//! and its text tools, which the panel can hide, act on the selected type.
use egui::{Align, Layout, Ui};
use serde_json::{Value, json};
@@ -16,6 +17,8 @@ pub const ID: &str = "artworkCleanup";
/// Where the panel keeps its last check (panel state).
const STATE: &str = "artwork-cleanup";
/// Where the panel keeps whether its text tools are hidden (panel state).
const TEXT_HIDDEN: &str = "artwork-cleanup-text-hidden";
/// The one-click tools: (menu label, command, the options their dialog opens with).
fn tools() -> [(&'static str, &'static str, Value); 3] {
@@ -26,11 +29,92 @@ fn tools() -> [(&'static str, &'static str, Value); 3] {
]
}
/// Replace Missing Fonts' options: the default type font.
fn replace_fonts() -> Value {
json!({ "font": vectorcraft_doc::CharStyle::default().font_family })
}
/// A row of the panel's text tools: a label above (if any), then its buttons (label, what a
/// modifier click does, command, params).
type TextRow = (Option<&'static str>, Vec<(&'static str, Option<&'static str>, &'static str, Value)>);
fn text_rows() -> [TextRow; 4] {
let align = |a: &str| json!({ "align": a });
let by = |b: &str| json!({ "by": b });
[
(
Some("Set Alignment, Keep Position:"),
vec![
("Left", None, "addon.cleanup.alignInPlace", align("left")),
("Center", None, "addon.cleanup.alignInPlace", align("center")),
("Right", None, "addon.cleanup.alignInPlace", align("right")),
],
),
(
Some("Break Text into:"),
vec![
("Lines", Some("Shift-click: break into paragraphs"), "addon.cleanup.breakText", by("lines")),
("Words", None, "addon.cleanup.breakText", by("words")),
("Glyphs", None, "addon.cleanup.breakText", by("glyphs")),
],
),
(
None,
vec![
("Split at Cursor", None, "addon.cleanup.splitText", json!({})),
("Reset Transform", Some("Alt-click: reset the horizontal scale too"), "addon.cleanup.resetTextTransform", json!({})),
],
),
(None, vec![("Combine Point Text", None, "addon.cleanup.combineText", json!({}))]),
]
}
/// The text tools one to a row below the others: (label, command, params).
fn text_singles() -> [(&'static str, &'static str, Value); 3] {
[
("Remove Line Breaks", "addon.cleanup.removeBreaks", json!({ "kind": "line" })),
("Remove Paragraph Breaks", "addon.cleanup.removeBreaks", json!({ "kind": "paragraph" })),
("Replace Missing Fonts…", "addon.cleanup.replaceFonts", replace_fonts()),
]
}
/// `p` for a click with `held`: Shift-click on Lines breaks into paragraphs, Alt-click on Reset
/// Transform resets the horizontal scale too.
fn modified(id: &str, mut p: Value, held: egui::Modifiers) -> Value {
match id {
"addon.cleanup.breakText" if held.shift && p["by"] == "lines" => p["by"] = json!("paragraphs"),
"addon.cleanup.resetTextTransform" if held.alt => p["scale"] = json!(true),
_ => {}
}
p
}
/// Object › Plug-ins › Artwork Cleanup.
pub fn menu() -> Vec<Item> {
let mut items = vec![Item::Cmd("Document Health Check", "window.panel", json!({ "panel": ID })), Item::Sep];
items.extend(tools().into_iter().map(|(label, id, p)| Item::Cmd(label, id, p)));
items.extend([Item::Sep, Item::Cmd("Fix All Issues", "addon.cleanup.fix", Value::Null)]);
items.extend([Item::Sep, Item::Cmd("Fix All Issues", "addon.cleanup.fix", Value::Null), Item::Sep]);
let by = |b: &str| json!({ "by": b });
let align = |a: &str| json!({ "align": a });
items.extend([
Item::Cmd("Combine Point Text", "addon.cleanup.combineText", json!({})),
Item::Cmd("Break Text into Lines", "addon.cleanup.breakText", by("lines")),
Item::Cmd("Break Text into Paragraphs", "addon.cleanup.breakText", by("paragraphs")),
Item::Cmd("Break Text into Words", "addon.cleanup.breakText", by("words")),
Item::Cmd("Break Text into Glyphs", "addon.cleanup.breakText", by("glyphs")),
Item::Cmd("Split Text at Cursor", "addon.cleanup.splitText", json!({})),
Item::Sub(
"Set Alignment, Keep Position",
vec![
Item::Cmd("Left", "addon.cleanup.alignInPlace", align("left")),
Item::Cmd("Center", "addon.cleanup.alignInPlace", align("center")),
Item::Cmd("Right", "addon.cleanup.alignInPlace", align("right")),
],
),
Item::Cmd("Reset Text Transform", "addon.cleanup.resetTextTransform", json!({})),
Item::Cmd("Reset Text Transform and Scale", "addon.cleanup.resetTextTransform", json!({ "scale": true })),
]);
items.extend(text_singles().into_iter().map(|(label, id, p)| Item::Cmd(label, id, p)));
items
}
@@ -94,7 +178,8 @@ enum Action {
Check,
Show(String),
Fix(Vec<String>),
Tool(usize),
/// A tool's button: (its label, command, params), run as its menu item is.
Run(&'static str, &'static str, Value),
}
pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
@@ -121,12 +206,21 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
}
widgets::divider(ui);
ui.add_enabled_ui(selected, |ui| {
for (i, (label, ..)) in tools().iter().enumerate() {
for (label, id, p) in tools() {
if widgets::flat_button(ui, tl!(label), width).clicked() {
action = Some(Action::Tool(i));
action = Some(Action::Run(label, id, p));
}
}
});
let hidden: bool = pstate(ui.ctx(), TEXT_HIDDEN);
widgets::divider(ui);
if widgets::section_toggle(ui, "Text", !hidden) {
set_pstate(ui.ctx(), TEXT_HIDDEN, !hidden);
}
if !hidden {
let editing = app.session.tool_options().get("editing").is_some_and(|v| !v.is_null());
text_tools(ui, width, selected, editing, &mut action);
}
match action {
Some(Action::Check) => r = check(app, None),
Some(Action::Show(kind)) => {
@@ -135,17 +229,53 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
}
}
Some(Action::Fix(kinds)) => r = fix(app, &r, kinds),
Some(Action::Tool(i)) => {
if let Some((label, id, p)) = tools().into_iter().nth(i) {
let (id, p) = crate::menus::click_target(label, id, &p);
crate::menus::invoke(app, &id, p);
}
Some(Action::Run(label, id, p)) => {
let (id, p) = crate::menus::click_target(label, id, &p);
crate::menus::invoke(app, &id, p);
}
None => {}
}
set_pstate(ui.ctx(), STATE, r);
}
/// The text tools: rows of buttons, then one to a row. Each is enabled when its command can act:
/// Split at Cursor while a Type tool edits text, Replace Missing Fonts always, the others with a
/// selection.
fn text_tools(ui: &mut Ui, width: f32, selected: bool, editing: bool, action: &mut Option<Action>) {
let enabled = |id: &str| match id {
"addon.cleanup.splitText" => editing,
"addon.cleanup.replaceFonts" => true,
_ => selected,
};
let mut button = |ui: &mut Ui, label: &'static str, tip: Option<&str>, id: &'static str, p: Value, w: f32| {
// Lines and words of text, not lines drawn: their own catalog rows (the button translates
// the others).
let shown = if id == "addon.cleanup.breakText" { crate::i18n::tr_ctx(crate::i18n::current(), "breakText", label) } else { label };
let mut resp = ui.add_enabled_ui(enabled(id), |ui| widgets::flat_button(ui, shown, w)).inner;
if let Some(tip) = tip {
resp = resp.on_hover_text(tl!(tip));
}
if resp.clicked() {
*action = Some(Action::Run(label, id, modified(id, p, ui.input(|i| i.modifiers))));
}
};
for (heading, buttons) in text_rows() {
if let Some(h) = heading {
widgets::subheader(ui, h);
}
ui.horizontal(|ui| {
let n = buttons.len() as f32;
let each = ((width - ui.spacing().item_spacing.x * (n - 1.0)) / n).max(40.0);
for (label, tip, id, p) in buttons {
button(ui, label, tip, id, p, each);
}
});
}
for (label, id, p) in text_singles() {
button(ui, label, None, id, p, width);
}
}
/// The list of what the check found (click picks a row, Ctrl/Cmd- or Shift-click adds one), and
/// Show, Fix Selected and Fix All.
fn results(ui: &mut Ui, r: &mut Results, action: &mut Option<Action>) {
+108
View File
@@ -65,3 +65,111 @@ fn check_lists_what_it_found_and_fix_clears_it() {
});
assert!(text.contains("No issues found."), "{text}");
}
/// An app with the point type `text`, selected.
fn type_app(text: &str) -> (VectorcraftApp, u64) {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 300, "height": 200})).unwrap();
let id = app.run("text.create", json!({"x": 20, "y": 60, "text": text, "size": 20})).unwrap()["id"].as_u64().unwrap();
app.run("select.set", json!({"ids": [id]})).unwrap();
(app, id)
}
fn texts(app: &VectorcraftApp) -> Vec<String> {
let mut out = vec![];
for l in &app.session.doc().unwrap().doc.layers {
l.walk(&mut |n| {
if let vectorcraft_doc::NodeKind::Text(t) = &n.kind {
out.push(t.plain_text());
}
});
}
out
}
#[test]
fn the_plugins_submenu_runs_the_text_tools() {
let (mut app, _) = type_app("one two");
let entries = menus::menu_entries(&app);
let under = |path: &[&str], label: &str| entries.iter().find(|e| e.path == path && e.label == label);
let cleanup = ["Object", "Plug-ins", "Artwork Cleanup"];
for (label, id) in [
("Combine Point Text", "addon.cleanup.combineText"),
("Break Text into Lines", "addon.cleanup.breakText"),
("Break Text into Paragraphs", "addon.cleanup.breakText"),
("Break Text into Glyphs", "addon.cleanup.breakText"),
("Split Text at Cursor", "addon.cleanup.splitText"),
("Reset Text Transform", "addon.cleanup.resetTextTransform"),
("Reset Text Transform and Scale", "addon.cleanup.resetTextTransform"),
("Remove Line Breaks", "addon.cleanup.removeBreaks"),
("Remove Paragraph Breaks", "addon.cleanup.removeBreaks"),
("Replace Missing Fonts…", "addon.cleanup.replaceFonts"),
] {
assert_eq!(under(&cleanup, label).and_then(|e| e.command.as_deref()), Some(id), "{label}");
}
let center = under(&["Object", "Plug-ins", "Artwork Cleanup", "Set Alignment, Keep Position"], "Center").expect("listed");
assert_eq!((center.command.as_deref(), &center.params), (Some("addon.cleanup.alignInPlace"), &json!({"align": "center"})));
// Replace Missing Fonts opens its options first, with the default type font.
let fonts = under(&cleanup, "Replace Missing Fonts…").unwrap();
let (target, p) = menus::click_target(&fonts.label, "addon.cleanup.replaceFonts", &fonts.params);
assert_eq!((target.as_str(), &p["params"]["font"]), ("ui.paramDialog", &json!("Source Sans 3")));
let words = under(&cleanup, "Break Text into Words").unwrap();
menus::invoke(&mut app, "addon.cleanup.breakText", words.params.clone());
assert_eq!(texts(&app), ["one", "two"]);
let combine = under(&cleanup, "Combine Point Text").unwrap();
menus::invoke(&mut app, "addon.cleanup.combineText", combine.params.clone());
assert_eq!(texts(&app), ["one two"]);
}
/// One frame of the panel in `ctx` with `events` and `modifiers` held: the texts drawn and where.
fn frame_with(app: &mut VectorcraftApp, ctx: &egui::Context, events: Vec<egui::Event>, modifiers: egui::Modifiers) -> Vec<(String, egui::Rect)> {
fn collect(s: &egui::Shape, out: &mut Vec<(String, egui::Rect)>) {
match s {
egui::Shape::Text(t) => out.push((t.galley.text().to_string(), t.visual_bounding_rect())),
egui::Shape::Vec(v) => v.iter().for_each(|s| collect(s, out)),
_ => {}
}
}
let time = ctx.input(|i| i.time) + 0.1;
let events = std::iter::once(egui::Event::ModifiersChanged(modifiers)).chain(events).collect();
let mut out = ctx.run_ui(egui::RawInput { events, time: Some(time), ..Default::default() }, |ui| show(app, ui));
out.textures_delta.clear();
let mut v = vec![];
out.shapes.iter().for_each(|c| collect(&c.shape, &mut v));
v
}
fn panel_frame(app: &mut VectorcraftApp, ctx: &egui::Context, events: Vec<egui::Event>) -> Vec<(String, egui::Rect)> {
frame_with(app, ctx, events, egui::Modifiers::NONE)
}
/// Click the text `label` the panel draws, with `modifiers` held.
fn click(app: &mut VectorcraftApp, ctx: &egui::Context, label: &str, modifiers: egui::Modifiers) {
let shown = panel_frame(app, ctx, vec![]);
let at = shown.iter().find(|(s, _)| s == label).unwrap_or_else(|| panic!("no {label:?} in {shown:?}")).1.center();
let button = |pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers };
for events in [vec![egui::Event::PointerMoved(at)], vec![button(true)], vec![button(false)]] {
frame_with(app, ctx, events, modifiers);
}
}
#[test]
fn the_panels_text_row_runs_the_text_tools_and_hides() {
let (mut app, _) = type_app("Hello there\nworld");
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let steps = app.session.doc().unwrap().history.undo.len();
// Shift-click on Lines breaks into paragraphs.
click(&mut app, &ctx, "Lines", egui::Modifiers::SHIFT);
assert_eq!(texts(&app), ["Hello there", "world"]);
assert_eq!(app.session.doc().unwrap().history.undo.len(), steps + 1, "one undo step");
click(&mut app, &ctx, "Words", egui::Modifiers::NONE);
assert_eq!(texts(&app), ["Hello", "there", "world"]);
click(&mut app, &ctx, "Combine Point Text", egui::Modifiers::NONE);
assert_eq!(texts(&app), ["Hello there", "world"]);
// The row hides and shows again.
click(&mut app, &ctx, "Text", egui::Modifiers::NONE);
assert!(!panel_frame(&mut app, &ctx, vec![]).iter().any(|(s, _)| s == "Words"));
click(&mut app, &ctx, "Text", egui::Modifiers::NONE);
assert!(panel_frame(&mut app, &ctx, vec![]).iter().any(|(s, _)| s == "Words"));
}
+44
View File
@@ -5772,3 +5772,47 @@ anchor Points: Точки:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` задайте `inkLimit`, `removeWhiteOverprint` или `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first Корекциите на мастилата за печат работят върху CMYK документи: първо изберете Файл › Цветен режим на документа › CMYK цвят
@msg `{key}` must be 0..{max} `{key}` трябва да е 0..{max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Разделяне на текст
Combine Point Text Обединяване на точков текст
Remove Breaks Премахване на прекъсвания
Split Text at Cursor Разделяне на текст при курсора
Set Alignment, Keep Position Задаване на подравняване със запазване на позицията
Reset Text Transform Нулиране на трансформацията на текста
Replace Missing Fonts… Замяна на липсващите шрифтове…
Replace Missing Fonts Замяна на липсващите шрифтове
Remove Line Breaks Премахване на прекъсванията на редове
Remove Paragraph Breaks Премахване на прекъсванията на абзаци
Break Text into Lines Разделяне на текста на редове
Break Text into Paragraphs Разделяне на текста на абзаци
Break Text into Words Разделяне на текста на думи
Break Text into Glyphs Разделяне на текста на глифи
Reset Text Transform and Scale Нулиране на трансформацията и мащаба на текста
Set Alignment, Keep Position: Подравняване със запазване на позицията:
Break Text into: Разделяне на текста на:
Split at Cursor Разделяне при курсора
Reset Transform Нулиране на трансформацията
Shift-click: break into paragraphs Shift-щракване: разделяне на абзаци
Alt-click: reset the horizontal scale too Alt-щракване: нулиране и на хоризонталния мащаб
breakText Lines Редове
breakText Words Думи
breakText Glyphs Глифи
@msg Click in type with a Type tool to split it Щракнете в текст с инструмента „Текст“, за да го разделите
@msg Too many type objects to change at once Твърде много текстови обекти за промяна наведнъж
@msg Too many pieces to make at once Твърде много части за създаване наведнъж
@msg Select horizontal point or area type whose text fits Изберете хоризонтален точков текст или текст в зона, чийто текст се побира
@msg Nothing to split at the cursor При курсора няма какво да се раздели
@msg Select two or more one-line point type objects on one baseline to combine Изберете два или повече едноредови обекта с точков текст на една базова линия, за да ги обедините
@msg No line breaks to remove Няма прекъсвания на редове за премахване
@msg No paragraph breaks to remove Няма прекъсвания на абзаци за премахване
@msg Too many breaks to remove at once Твърде много прекъсвания за премахване наведнъж
@msg Select point type Изберете точков текст
@msg The point type is aligned that way already Точковият текст вече е подравнен така
@msg The point type has no transform to reset Точковият текст няма трансформация за нулиране
@msg No fonts are missing Няма липсващи шрифтове
@msg the replacement font is not available заместващият шрифт не е наличен
@msg missing `align` липсва `align`
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` трябва да е lines, paragraphs, words или glyphs, а не {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` трябва да е "line" или "paragraph", а не {other}
@msg `align` must be "left", "center" or "right", not {other} `align` трябва да е "left", "center" или "right", а не {other}
Can't render this file because it contains an unexpected character in line 2 and column 73.
+44
View File
@@ -5873,3 +5873,47 @@ anchor Points: Punts:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` indiqueu `inkLimit`, `removeWhiteOverprint` o `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first Les correccions de tinta per a impressió funcionen en documents CMYK: trieu primer Fitxer › Mode de color del document › Color CMYK
@msg `{key}` must be 0..{max} `{key}` ha de ser 0..{max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Divideix el text
Combine Point Text Combina el text de punt
Remove Breaks Elimina els salts
Split Text at Cursor Divideix el text al cursor
Set Alignment, Keep Position Defineix l’alineació i manté la posició
Reset Text Transform Restableix la transformació del text
Replace Missing Fonts… Reemplaça els tipus de lletra que falten…
Replace Missing Fonts Reemplaça els tipus de lletra que falten
Remove Line Breaks Elimina els salts de línia
Remove Paragraph Breaks Elimina els salts de paràgraf
Break Text into Lines Divideix el text en línies
Break Text into Paragraphs Divideix el text en paràgrafs
Break Text into Words Divideix el text en paraules
Break Text into Glyphs Divideix el text en glifs
Reset Text Transform and Scale Restableix la transformació i l’escala del text
Set Alignment, Keep Position: Defineix l’alineació i manté la posició:
Break Text into: Divideix el text en:
Split at Cursor Divideix al cursor
Reset Transform Restableix la transformació
Shift-click: break into paragraphs Maj+clic: divideix en paràgrafs
Alt-click: reset the horizontal scale too Alt-clic: restableix també l’escala horitzontal
breakText Lines Línies
breakText Words Paraules
breakText Glyphs Glifs
@msg Click in type with a Type tool to split it Feu clic en un text amb una eina de text per dividir-lo
@msg Too many type objects to change at once Massa objectes de text per canviar-los alhora
@msg Too many pieces to make at once Massa peces per crear-les alhora
@msg Select horizontal point or area type whose text fits Seleccioneu text de punt o d’àrea horitzontal el text del qual hi càpiga
@msg Nothing to split at the cursor No hi ha res a dividir al cursor
@msg Select two or more one-line point type objects on one baseline to combine Seleccioneu dos o més objectes de text de punt d’una línia sobre una mateixa línia de base per combinar-los
@msg No line breaks to remove No hi ha salts de línia per eliminar
@msg No paragraph breaks to remove No hi ha salts de paràgraf per eliminar
@msg Too many breaks to remove at once Massa salts per eliminar-los alhora
@msg Select point type Seleccioneu text de punt
@msg The point type is aligned that way already El text de punt ja està alineat així
@msg The point type has no transform to reset El text de punt no té cap transformació per restablir
@msg No fonts are missing No falta cap tipus de lletra
@msg the replacement font is not available el tipus de lletra de reemplaçament no està disponible
@msg missing `align` falta `align`
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` ha de ser lines, paragraphs, words o glyphs, no {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` ha de ser "line" o "paragraph", no {other}
@msg `align` must be "left", "center" or "right", not {other} `align` ha de ser "left", "center" o "right", no {other}
Can't render this file because it contains an unexpected character in line 2 and column 73.
+44
View File
@@ -5882,3 +5882,47 @@ anchor Points: Body:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` zadejte `inkLimit`, `removeWhiteOverprint` nebo `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first Opravy tiskových barev fungují v dokumentech CMYK: nejprve zvolte Soubor › Barevný režim dokumentu › Barvy CMYK
@msg `{key}` must be 0..{max} `{key}` musí být 0..{max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Rozdělit text
Combine Point Text Sloučit bodový text
Remove Breaks Odstranit zalomení
Split Text at Cursor Rozdělit text u kurzoru
Set Alignment, Keep Position Nastavit zarovnání, zachovat polohu
Reset Text Transform Obnovit transformaci textu
Replace Missing Fonts… Nahradit chybějící písma…
Replace Missing Fonts Nahradit chybějící písma
Remove Line Breaks Odstranit zalomení řádků
Remove Paragraph Breaks Odstranit zalomení odstavců
Break Text into Lines Rozdělit text na řádky
Break Text into Paragraphs Rozdělit text na odstavce
Break Text into Words Rozdělit text na slova
Break Text into Glyphs Rozdělit text na glyfy
Reset Text Transform and Scale Obnovit transformaci a měřítko textu
Set Alignment, Keep Position: Nastavit zarovnání, zachovat polohu:
Break Text into: Rozdělit text na:
Split at Cursor Rozdělit u kurzoru
Reset Transform Obnovit transformaci
Shift-click: break into paragraphs Shift+kliknutí: rozdělit na odstavce
Alt-click: reset the horizontal scale too Alt+kliknutí: obnovit i vodorovné měřítko
breakText Lines Řádky
breakText Words Slova
breakText Glyphs Glyfy
@msg Click in type with a Type tool to split it Klikněte do textu nástrojem Text, abyste jej rozdělili
@msg Too many type objects to change at once Příliš mnoho textových objektů ke změně najednou
@msg Too many pieces to make at once Příliš mnoho částí k vytvoření najednou
@msg Select horizontal point or area type whose text fits Vyberte vodorovný bodový text nebo text v oblasti, jehož text se vejde
@msg Nothing to split at the cursor U kurzoru není co rozdělit
@msg Select two or more one-line point type objects on one baseline to combine Vyberte dva nebo více jednořádkových objektů bodového textu na jednom účaří, abyste je sloučili
@msg No line breaks to remove Žádná zalomení řádků k odstranění
@msg No paragraph breaks to remove Žádná zalomení odstavců k odstranění
@msg Too many breaks to remove at once Příliš mnoho zalomení k odstranění najednou
@msg Select point type Vyberte bodový text
@msg The point type is aligned that way already Bodový text je již takto zarovnán
@msg The point type has no transform to reset Bodový text nemá žádnou transformaci k obnovení
@msg No fonts are missing Žádná písma nechybějí
@msg the replacement font is not available náhradní písmo není k dispozici
@msg missing `align` chybí `align`
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` musí být lines, paragraphs, words nebo glyphs, ne {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` musí být "line" nebo "paragraph", ne {other}
@msg `align` must be "left", "center" or "right", not {other} `align` musí být "left", "center" nebo "right", ne {other}
Can't render this file because it contains an unexpected character in line 2 and column 73.
+44
View File
@@ -5787,3 +5787,47 @@ anchor Points: Punkter:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` angiv `inkLimit`, `removeWhiteOverprint` eller `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first Trykfarverettelser virker på CMYK-dokumenter: vælg først Filer › Dokumentets farvetilstand › CMYK-farve
@msg `{key}` must be 0..{max} `{key}` skal være 0..{max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Opdel tekst
Combine Point Text Kombiner punkttekst
Remove Breaks Fjern skift
Split Text at Cursor Opdel tekst ved markøren
Set Alignment, Keep Position Angiv justering, bevar placering
Reset Text Transform Nulstil teksttransformation
Replace Missing Fonts… Erstat manglende skrifttyper…
Replace Missing Fonts Erstat manglende skrifttyper
Remove Line Breaks Fjern linjeskift
Remove Paragraph Breaks Fjern afsnitsskift
Break Text into Lines Opdel tekst i linjer
Break Text into Paragraphs Opdel tekst i afsnit
Break Text into Words Opdel tekst i ord
Break Text into Glyphs Opdel tekst i glyffer
Reset Text Transform and Scale Nulstil teksttransformation og skalering
Set Alignment, Keep Position: Angiv justering, bevar placering:
Break Text into: Opdel tekst i:
Split at Cursor Opdel ved markøren
Reset Transform Nulstil transformation
Shift-click: break into paragraphs Skift-klik: opdel i afsnit
Alt-click: reset the horizontal scale too Alt-klik: nulstil også den vandrette skalering
breakText Lines Linjer
breakText Words Ord
breakText Glyphs Glyffer
@msg Click in type with a Type tool to split it Klik i tekst med et tekstværktøj for at opdele den
@msg Too many type objects to change at once For mange tekstobjekter til at ændre på én gang
@msg Too many pieces to make at once For mange dele til at oprette på én gang
@msg Select horizontal point or area type whose text fits Vælg vandret punkt- eller områdetekst, hvis tekst kan være der
@msg Nothing to split at the cursor Intet at opdele ved markøren
@msg Select two or more one-line point type objects on one baseline to combine Vælg to eller flere punkttekstobjekter på én linje på samme grundlinje for at kombinere dem
@msg No line breaks to remove Ingen linjeskift at fjerne
@msg No paragraph breaks to remove Ingen afsnitsskift at fjerne
@msg Too many breaks to remove at once For mange skift til at fjerne på én gang
@msg Select point type Vælg punkttekst
@msg The point type is aligned that way already Punktteksten er allerede justeret sådan
@msg The point type has no transform to reset Punktteksten har ingen transformation at nulstille
@msg No fonts are missing Ingen skrifttyper mangler
@msg the replacement font is not available erstatningsskrifttypen er ikke tilgængelig
@msg missing `align` `align` mangler
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` skal være lines, paragraphs, words eller glyphs, ikke {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` skal være "line" eller "paragraph", ikke {other}
@msg `align` must be "left", "center" or "right", not {other} `align` skal være "left", "center" eller "right", ikke {other}
Can't render this file because it contains an unexpected character in line 2 and column 73.
+44
View File
@@ -5467,3 +5467,47 @@ anchor Points: Punkte:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` geben Sie `inkLimit`, `removeWhiteOverprint` oder `overprintBlack` an
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first Druckfarbkorrekturen wirken in CMYK-Dokumenten: Wählen Sie zuerst Datei › Dokumentfarbmodus › CMYK-Farbe
@msg `{key}` must be 0..{max} `{key}` muss 0..{max} sein
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Text aufteilen
Combine Point Text Punkttext zusammenfügen
Remove Breaks Umbrüche entfernen
Split Text at Cursor Text an der Einfügemarke teilen
Set Alignment, Keep Position Ausrichtung festlegen, Position beibehalten
Reset Text Transform Texttransformation zurücksetzen
Replace Missing Fonts… Fehlende Schriften ersetzen…
Replace Missing Fonts Fehlende Schriften ersetzen
Remove Line Breaks Zeilenumbrüche entfernen
Remove Paragraph Breaks Absatzumbrüche entfernen
Break Text into Lines Text in Zeilen aufteilen
Break Text into Paragraphs Text in Absätze aufteilen
Break Text into Words Text in Wörter aufteilen
Break Text into Glyphs Text in Glyphen aufteilen
Reset Text Transform and Scale Texttransformation und Skalierung zurücksetzen
Set Alignment, Keep Position: Ausrichtung festlegen, Position beibehalten:
Break Text into: Text aufteilen in:
Split at Cursor An Einfügemarke teilen
Reset Transform Transformation zurücksetzen
Shift-click: break into paragraphs Shift-Klick: in Absätze aufteilen
Alt-click: reset the horizontal scale too Alt-Klick: auch die horizontale Skalierung zurücksetzen
breakText Lines Zeilen
breakText Words Wörter
breakText Glyphs Glyphen
@msg Click in type with a Type tool to split it Klicken Sie mit einem Text-Werkzeug in Text, um ihn zu teilen
@msg Too many type objects to change at once Zu viele Textobjekte, um sie auf einmal zu ändern
@msg Too many pieces to make at once Zu viele Teile, um sie auf einmal zu erstellen
@msg Select horizontal point or area type whose text fits Wählen Sie horizontalen Punkt- oder Flächentext aus, dessen Text hineinpasst
@msg Nothing to split at the cursor An der Einfügemarke gibt es nichts zu teilen
@msg Select two or more one-line point type objects on one baseline to combine Wählen Sie zwei oder mehr einzeilige Punkttextobjekte auf einer Grundlinie aus, um sie zusammenzufügen
@msg No line breaks to remove Keine Zeilenumbrüche zu entfernen
@msg No paragraph breaks to remove Keine Absatzumbrüche zu entfernen
@msg Too many breaks to remove at once Zu viele Umbrüche, um sie auf einmal zu entfernen
@msg Select point type Wählen Sie Punkttext aus
@msg The point type is aligned that way already Der Punkttext ist bereits so ausgerichtet
@msg The point type has no transform to reset Der Punkttext hat keine Transformation zum Zurücksetzen
@msg No fonts are missing Es fehlen keine Schriften
@msg the replacement font is not available die Ersatzschrift ist nicht verfügbar
@msg missing `align` `align` fehlt
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` muss lines, paragraphs, words oder glyphs sein, nicht {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` muss "line" oder "paragraph" sein, nicht {other}
@msg `align` must be "left", "center" or "right", not {other} `align` muss "left", "center" oder "right" sein, nicht {other}
Can't render this file because it contains an unexpected character in line 2 and column 73.
+44
View File
@@ -5834,3 +5834,47 @@ anchor Points: Σημεία:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` δώστε `inkLimit`, `removeWhiteOverprint` ή `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first Οι διορθώσεις μελανιών εκτύπωσης ισχύουν σε έγγραφα CMYK: επιλέξτε πρώτα Αρχείο › Λειτουργία χρώματος εγγράφου › Χρώμα CMYK
@msg `{key}` must be 0..{max} το `{key}` πρέπει να είναι 0..{max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Διαίρεση κειμένου
Combine Point Text Συνδυασμός κειμένου σημείου
Remove Breaks Αφαίρεση αλλαγών
Split Text at Cursor Διαίρεση κειμένου στον δρομέα
Set Alignment, Keep Position Ορισμός στοίχισης με διατήρηση θέσης
Reset Text Transform Επαναφορά μετασχηματισμού κειμένου
Replace Missing Fonts… Αντικατάσταση γραμματοσειρών που λείπουν…
Replace Missing Fonts Αντικατάσταση γραμματοσειρών που λείπουν
Remove Line Breaks Αφαίρεση αλλαγών γραμμής
Remove Paragraph Breaks Αφαίρεση αλλαγών παραγράφου
Break Text into Lines Διαίρεση κειμένου σε γραμμές
Break Text into Paragraphs Διαίρεση κειμένου σε παραγράφους
Break Text into Words Διαίρεση κειμένου σε λέξεις
Break Text into Glyphs Διαίρεση κειμένου σε γλύφους
Reset Text Transform and Scale Επαναφορά μετασχηματισμού και κλίμακας κειμένου
Set Alignment, Keep Position: Στοίχιση με διατήρηση θέσης:
Break Text into: Διαίρεση κειμένου σε:
Split at Cursor Διαίρεση στον δρομέα
Reset Transform Επαναφορά μετασχηματισμού
Shift-click: break into paragraphs Shift-κλικ: διαίρεση σε παραγράφους
Alt-click: reset the horizontal scale too Alt-κλικ: επαναφορά και της οριζόντιας κλίμακας
breakText Lines Γραμμές
breakText Words Λέξεις
breakText Glyphs Γλύφοι
@msg Click in type with a Type tool to split it Κάντε κλικ σε κείμενο με ένα εργαλείο κειμένου για να το διαιρέσετε
@msg Too many type objects to change at once Πάρα πολλά αντικείμενα κειμένου για αλλαγή ταυτόχρονα
@msg Too many pieces to make at once Πάρα πολλά κομμάτια για δημιουργία ταυτόχρονα
@msg Select horizontal point or area type whose text fits Επιλέξτε οριζόντιο κείμενο σημείου ή περιοχής του οποίου το κείμενο χωράει
@msg Nothing to split at the cursor Δεν υπάρχει τίποτα για διαίρεση στον δρομέα
@msg Select two or more one-line point type objects on one baseline to combine Επιλέξτε δύο ή περισσότερα μονογραμμικά αντικείμενα κειμένου σημείου στην ίδια γραμμή βάσης για να τα συνδυάσετε
@msg No line breaks to remove Δεν υπάρχουν αλλαγές γραμμής για αφαίρεση
@msg No paragraph breaks to remove Δεν υπάρχουν αλλαγές παραγράφου για αφαίρεση
@msg Too many breaks to remove at once Πάρα πολλές αλλαγές για αφαίρεση ταυτόχρονα
@msg Select point type Επιλέξτε κείμενο σημείου
@msg The point type is aligned that way already Το κείμενο σημείου είναι ήδη στοιχισμένο έτσι
@msg The point type has no transform to reset Το κείμενο σημείου δεν έχει μετασχηματισμό για επαναφορά
@msg No fonts are missing Δεν λείπει καμία γραμματοσειρά
@msg the replacement font is not available η γραμματοσειρά αντικατάστασης δεν είναι διαθέσιμη
@msg missing `align` λείπει το `align`
@msg `by` must be lines, paragraphs, words or glyphs, not {other} το `by` πρέπει να είναι lines, paragraphs, words ή glyphs, όχι {other}
@msg `kind` must be "line" or "paragraph", not {other} το `kind` πρέπει να είναι "line" ή "paragraph", όχι {other}
@msg `align` must be "left", "center" or "right", not {other} το `align` πρέπει να είναι "left", "center" ή "right", όχι {other}
Can't render this file because it contains an unexpected character in line 164 and column 5.
+44
View File
@@ -5825,3 +5825,47 @@ anchor Points: Puntos:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` indique `inkLimit`, `removeWhiteOverprint` u `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first Las correcciones de tinta para impresión funcionan en documentos CMYK: elija primero Archivo › Modo de color del documento › Color CMYK
@msg `{key}` must be 0..{max} `{key}` debe ser 0..{max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Dividir texto
Combine Point Text Combinar texto de punto
Remove Breaks Quitar saltos
Split Text at Cursor Dividir texto en el cursor
Set Alignment, Keep Position Definir alineación manteniendo la posición
Reset Text Transform Restablecer transformación del texto
Replace Missing Fonts… Reemplazar fuentes que faltan…
Replace Missing Fonts Reemplazar fuentes que faltan
Remove Line Breaks Quitar saltos de línea
Remove Paragraph Breaks Quitar saltos de párrafo
Break Text into Lines Dividir texto en líneas
Break Text into Paragraphs Dividir texto en párrafos
Break Text into Words Dividir texto en palabras
Break Text into Glyphs Dividir texto en glifos
Reset Text Transform and Scale Restablecer transformación y escala del texto
Set Alignment, Keep Position: Definir alineación manteniendo la posición:
Break Text into: Dividir texto en:
Split at Cursor Dividir en el cursor
Reset Transform Restablecer transformación
Shift-click: break into paragraphs Shift+clic: dividir en párrafos
Alt-click: reset the horizontal scale too Alt+clic: restablecer también la escala horizontal
breakText Lines Líneas
breakText Words Palabras
breakText Glyphs Glifos
@msg Click in type with a Type tool to split it Haga clic en un texto con una herramienta Texto para dividirlo
@msg Too many type objects to change at once Demasiados objetos de texto para cambiarlos a la vez
@msg Too many pieces to make at once Demasiadas piezas para crearlas a la vez
@msg Select horizontal point or area type whose text fits Seleccione texto de punto o de área horizontal cuyo texto quepa
@msg Nothing to split at the cursor No hay nada que dividir en el cursor
@msg Select two or more one-line point type objects on one baseline to combine Seleccione dos o más objetos de texto de punto de una línea sobre la misma línea base para combinarlos
@msg No line breaks to remove No hay saltos de línea que quitar
@msg No paragraph breaks to remove No hay saltos de párrafo que quitar
@msg Too many breaks to remove at once Demasiados saltos para quitarlos a la vez
@msg Select point type Seleccione texto de punto
@msg The point type is aligned that way already El texto de punto ya está alineado así
@msg The point type has no transform to reset El texto de punto no tiene ninguna transformación que restablecer
@msg No fonts are missing No falta ninguna fuente
@msg the replacement font is not available la fuente de reemplazo no está disponible
@msg missing `align` falta `align`
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` debe ser lines, paragraphs, words o glyphs, no {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` debe ser "line" o "paragraph", no {other}
@msg `align` must be "left", "center" or "right", not {other} `align` debe ser "left", "center" o "right", no {other}
Can't render this file because it contains an unexpected character in line 2 and column 73.
+44
View File
@@ -5913,3 +5913,47 @@ anchor Points: Pisteet:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` anna `inkLimit`, `removeWhiteOverprint` tai `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first Painovärien korjaukset toimivat CMYK-asiakirjoissa: valitse ensin Tiedosto › Asiakirjan väritila › CMYK-väri
@msg `{key}` must be 0..{max} `{key}` on oltava 0..{max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Jaa teksti
Combine Point Text Yhdistä pisteteksti
Remove Breaks Poista vaihdot
Split Text at Cursor Jaa teksti kohdistimen kohdalta
Set Alignment, Keep Position Aseta tasaus, säilytä sijainti
Reset Text Transform Palauta tekstin muunnos
Replace Missing Fonts… Korvaa puuttuvat fontit…
Replace Missing Fonts Korvaa puuttuvat fontit
Remove Line Breaks Poista rivinvaihdot
Remove Paragraph Breaks Poista kappaleenvaihdot
Break Text into Lines Jaa teksti riveiksi
Break Text into Paragraphs Jaa teksti kappaleiksi
Break Text into Words Jaa teksti sanoiksi
Break Text into Glyphs Jaa teksti merkeiksi
Reset Text Transform and Scale Palauta tekstin muunnos ja skaalaus
Set Alignment, Keep Position: Aseta tasaus, säilytä sijainti:
Break Text into: Jaa teksti:
Split at Cursor Jaa kohdistimen kohdalta
Reset Transform Palauta muunnos
Shift-click: break into paragraphs Vaihto-napsautus: jaa kappaleiksi
Alt-click: reset the horizontal scale too Alt-napsautus: palauta myös vaakaskaalaus
breakText Lines Rivit
breakText Words Sanat
breakText Glyphs Merkit
@msg Click in type with a Type tool to split it Napsauta tekstiä tekstityökalulla jakaaksesi sen
@msg Too many type objects to change at once Liian monta tekstiobjektia muutettavaksi kerralla
@msg Too many pieces to make at once Liian monta osaa luotavaksi kerralla
@msg Select horizontal point or area type whose text fits Valitse vaakasuora piste- tai alueteksti, jonka teksti mahtuu
@msg Nothing to split at the cursor Kohdistimen kohdalla ei ole jaettavaa
@msg Select two or more one-line point type objects on one baseline to combine Valitse kaksi tai useampia yksirivisiä pistetekstiobjekteja samalla perusviivalla yhdistääksesi ne
@msg No line breaks to remove Ei poistettavia rivinvaihtoja
@msg No paragraph breaks to remove Ei poistettavia kappaleenvaihtoja
@msg Too many breaks to remove at once Liian monta vaihtoa poistettavaksi kerralla
@msg Select point type Valitse pisteteksti
@msg The point type is aligned that way already Pisteteksti on jo tasattu näin
@msg The point type has no transform to reset Pistetekstillä ei ole palautettavaa muunnosta
@msg No fonts are missing Fontteja ei puutu
@msg the replacement font is not available korvaava fontti ei ole käytettävissä
@msg missing `align` `align` puuttuu
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` on oltava lines, paragraphs, words tai glyphs, ei {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` on oltava "line" tai "paragraph", ei {other}
@msg `align` must be "left", "center" or "right", not {other} `align` on oltava "left", "center" tai "right", ei {other}
Can't render this file because it contains an unexpected character in line 2 and column 73.
+44
View File
@@ -5881,3 +5881,47 @@ anchor Points: Points :
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` indiquez `inkLimit`, `removeWhiteOverprint` ou `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first Les corrections d’encres pour l’impression s’appliquent aux documents CMJN : choisissez d’abord Fichier › Mode colorimétrique du document › Couleur CMJN
@msg `{key}` must be 0..{max} `{key}` doit être compris entre 0 et {max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Diviser le texte
Combine Point Text Combiner le texte de point
Remove Breaks Supprimer les sauts
Split Text at Cursor Diviser le texte au curseur
Set Alignment, Keep Position Définir l’alignement en conservant la position
Reset Text Transform Réinitialiser la transformation du texte
Replace Missing Fonts… Remplacer les polices manquantes…
Replace Missing Fonts Remplacer les polices manquantes
Remove Line Breaks Supprimer les sauts de ligne
Remove Paragraph Breaks Supprimer les sauts de paragraphe
Break Text into Lines Diviser le texte en lignes
Break Text into Paragraphs Diviser le texte en paragraphes
Break Text into Words Diviser le texte en mots
Break Text into Glyphs Diviser le texte en glyphes
Reset Text Transform and Scale Réinitialiser la transformation et l’échelle du texte
Set Alignment, Keep Position: Définir l’alignement en conservant la position :
Break Text into: Diviser le texte en :
Split at Cursor Diviser au curseur
Reset Transform Réinitialiser la transformation
Shift-click: break into paragraphs Maj+clic : diviser en paragraphes
Alt-click: reset the horizontal scale too Alt+clic : réinitialiser aussi l’échelle horizontale
breakText Lines Lignes
breakText Words Mots
breakText Glyphs Glyphes
@msg Click in type with a Type tool to split it Cliquez dans du texte avec un outil Texte pour le diviser
@msg Too many type objects to change at once Trop d’objets texte à modifier en une fois
@msg Too many pieces to make at once Trop de morceaux à créer en une fois
@msg Select horizontal point or area type whose text fits Sélectionnez du texte de point ou captif horizontal dont le texte tient dans son cadre
@msg Nothing to split at the cursor Rien à diviser au curseur
@msg Select two or more one-line point type objects on one baseline to combine Sélectionnez au moins deux objets texte de point d’une ligne sur une même ligne de base pour les combiner
@msg No line breaks to remove Aucun saut de ligne à supprimer
@msg No paragraph breaks to remove Aucun saut de paragraphe à supprimer
@msg Too many breaks to remove at once Trop de sauts à supprimer en une fois
@msg Select point type Sélectionnez du texte de point
@msg The point type is aligned that way already Le texte de point est déjà aligné ainsi
@msg The point type has no transform to reset Le texte de point n’a aucune transformation à réinitialiser
@msg No fonts are missing Aucune police ne manque
@msg the replacement font is not available la police de remplacement n’est pas disponible
@msg missing `align` `align` manquant
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` doit valoir lines, paragraphs, words ou glyphs, et non {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` doit valoir "line" ou "paragraph", et non {other}
@msg `align` must be "left", "center" or "right", not {other} `align` doit valoir "left", "center" ou "right", et non {other}
Can't render this file because it contains an unexpected character in line 2 and column 73.
+44
View File
@@ -5869,3 +5869,47 @@ anchor Points: Pontok:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` adja meg: `inkLimit`, `removeWhiteOverprint` vagy `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first A nyomdai festékjavítások CMYK-dokumentumokon működnek: előbb válassza a Fájl › Dokumentum színmódja › CMYK-szín parancsot
@msg `{key}` must be 0..{max} `{key}` értéke 0..{max} lehet
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Szöveg felosztása
Combine Point Text Pontszöveg egyesítése
Remove Breaks Törések eltávolítása
Split Text at Cursor Szöveg felosztása a kurzornál
Set Alignment, Keep Position Igazítás beállítása, helyzet megtartása
Reset Text Transform Szövegátalakítás visszaállítása
Replace Missing Fonts… Hiányzó betűtípusok cseréje…
Replace Missing Fonts Hiányzó betűtípusok cseréje
Remove Line Breaks Sortörések eltávolítása
Remove Paragraph Breaks Bekezdéstörések eltávolítása
Break Text into Lines Szöveg felosztása sorokra
Break Text into Paragraphs Szöveg felosztása bekezdésekre
Break Text into Words Szöveg felosztása szavakra
Break Text into Glyphs Szöveg felosztása glifákra
Reset Text Transform and Scale Szövegátalakítás és méretezés visszaállítása
Set Alignment, Keep Position: Igazítás beállítása, helyzet megtartása:
Break Text into: Szöveg felosztása:
Split at Cursor Felosztás a kurzornál
Reset Transform Átalakítás visszaállítása
Shift-click: break into paragraphs Shift+kattintás: felosztás bekezdésekre
Alt-click: reset the horizontal scale too Alt+kattintás: a vízszintes méretezés visszaállítása is
breakText Lines Sorok
breakText Words Szavak
breakText Glyphs Glifák
@msg Click in type with a Type tool to split it Kattintson a szövegbe egy szöveg eszközzel a felosztáshoz
@msg Too many type objects to change at once Túl sok szövegobjektum az egyszerre történő módosításhoz
@msg Too many pieces to make at once Túl sok darab az egyszerre történő létrehozáshoz
@msg Select horizontal point or area type whose text fits Jelöljön ki vízszintes pont- vagy területszöveget, amelynek szövege elfér
@msg Nothing to split at the cursor A kurzornál nincs mit felosztani
@msg Select two or more one-line point type objects on one baseline to combine Az egyesítéshez jelöljön ki legalább két egysoros pontszöveg-objektumot egy alapvonalon
@msg No line breaks to remove Nincs eltávolítandó sortörés
@msg No paragraph breaks to remove Nincs eltávolítandó bekezdéstörés
@msg Too many breaks to remove at once Túl sok törés az egyszerre történő eltávolításhoz
@msg Select point type Jelöljön ki pontszöveget
@msg The point type is aligned that way already A pontszöveg már így van igazítva
@msg The point type has no transform to reset A pontszövegnek nincs visszaállítandó átalakítása
@msg No fonts are missing Nem hiányzik betűtípus
@msg the replacement font is not available a helyettesítő betűtípus nem érhető el
@msg missing `align` hiányzik az `align`
@msg `by` must be lines, paragraphs, words or glyphs, not {other} a `by` értéke lines, paragraphs, words vagy glyphs lehet, nem {other}
@msg `kind` must be "line" or "paragraph", not {other} a `kind` értéke "line" vagy "paragraph" lehet, nem {other}
@msg `align` must be "left", "center" or "right", not {other} az `align` értéke "left", "center" vagy "right" lehet, nem {other}
Can't render this file because it contains an unexpected character in line 2 and column 73.
+44
View File
@@ -5787,3 +5787,47 @@ anchor Points: Titik:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` berikan `inkLimit`, `removeWhiteOverprint`, atau `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first Perbaikan Tinta Cetak bekerja pada dokumen CMYK: pilih dulu Berkas › Mode warna dokumen › Warna CMYK
@msg `{key}` must be 0..{max} `{key}` harus 0..{max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Pisahkan teks
Combine Point Text Gabungkan teks titik
Remove Breaks Hapus pemisah
Split Text at Cursor Pisahkan teks di kursor
Set Alignment, Keep Position Atur perataan, pertahankan posisi
Reset Text Transform Atur ulang transformasi teks
Replace Missing Fonts… Ganti font yang hilang…
Replace Missing Fonts Ganti font yang hilang
Remove Line Breaks Hapus putus baris
Remove Paragraph Breaks Hapus putus paragraf
Break Text into Lines Pisahkan teks menjadi baris
Break Text into Paragraphs Pisahkan teks menjadi paragraf
Break Text into Words Pisahkan teks menjadi kata
Break Text into Glyphs Pisahkan teks menjadi glif
Reset Text Transform and Scale Atur ulang transformasi dan skala teks
Set Alignment, Keep Position: Atur perataan, pertahankan posisi:
Break Text into: Pisahkan teks menjadi:
Split at Cursor Pisahkan di kursor
Reset Transform Atur ulang transformasi
Shift-click: break into paragraphs Shift+klik: pisahkan menjadi paragraf
Alt-click: reset the horizontal scale too Alt+klik: atur ulang juga skala horizontal
breakText Lines Baris
breakText Words Kata
breakText Glyphs Glif
@msg Click in type with a Type tool to split it Klik di dalam teks dengan alat teks untuk memisahkannya
@msg Too many type objects to change at once Terlalu banyak objek teks untuk diubah sekaligus
@msg Too many pieces to make at once Terlalu banyak bagian untuk dibuat sekaligus
@msg Select horizontal point or area type whose text fits Pilih teks titik atau teks area horizontal yang teksnya muat
@msg Nothing to split at the cursor Tidak ada yang dapat dipisahkan di kursor
@msg Select two or more one-line point type objects on one baseline to combine Pilih dua atau lebih objek teks titik satu baris pada satu garis dasar untuk menggabungkannya
@msg No line breaks to remove Tidak ada putus baris untuk dihapus
@msg No paragraph breaks to remove Tidak ada putus paragraf untuk dihapus
@msg Too many breaks to remove at once Terlalu banyak pemisah untuk dihapus sekaligus
@msg Select point type Pilih teks titik
@msg The point type is aligned that way already Teks titik sudah diratakan seperti itu
@msg The point type has no transform to reset Teks titik tidak memiliki transformasi untuk diatur ulang
@msg No fonts are missing Tidak ada font yang hilang
@msg the replacement font is not available font pengganti tidak tersedia
@msg missing `align` `align` tidak ada
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` harus lines, paragraphs, words, atau glyphs, bukan {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` harus "line" atau "paragraph", bukan {other}
@msg `align` must be "left", "center" or "right", not {other} `align` harus "left", "center", atau "right", bukan {other}
Can't render this file because it contains an unexpected character in line 2169 and column 5.
+44
View File
@@ -5501,3 +5501,47 @@ anchor Points: Punti:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` indica `inkLimit`, `removeWhiteOverprint` o `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first Le correzioni inchiostri di stampa funzionano nei documenti CMYK: scegli prima File › Metodo colore documento › Colore CMYK
@msg `{key}` must be 0..{max} `{key}` deve essere 0..{max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Dividi testo
Combine Point Text Combina testo con punto
Remove Breaks Rimuovi interruzioni
Split Text at Cursor Dividi testo al cursore
Set Alignment, Keep Position Imposta allineamento mantenendo la posizione
Reset Text Transform Reimposta trasformazione testo
Replace Missing Fonts… Sostituisci font mancanti…
Replace Missing Fonts Sostituisci font mancanti
Remove Line Breaks Rimuovi interruzioni di riga
Remove Paragraph Breaks Rimuovi interruzioni di paragrafo
Break Text into Lines Dividi testo in righe
Break Text into Paragraphs Dividi testo in paragrafi
Break Text into Words Dividi testo in parole
Break Text into Glyphs Dividi testo in glifi
Reset Text Transform and Scale Reimposta trasformazione e scala testo
Set Alignment, Keep Position: Imposta allineamento mantenendo la posizione:
Break Text into: Dividi testo in:
Split at Cursor Dividi al cursore
Reset Transform Reimposta trasformazione
Shift-click: break into paragraphs Shift+clic: dividi in paragrafi
Alt-click: reset the horizontal scale too Alt+clic: reimposta anche la scala orizzontale
breakText Lines Righe
breakText Words Parole
breakText Glyphs Glifi
@msg Click in type with a Type tool to split it Fai clic nel testo con uno strumento Testo per dividerlo
@msg Too many type objects to change at once Troppi oggetti di testo da modificare in una volta
@msg Too many pieces to make at once Troppi pezzi da creare in una volta
@msg Select horizontal point or area type whose text fits Seleziona testo con punto o in area orizzontale il cui testo ci sta
@msg Nothing to split at the cursor Niente da dividere al cursore
@msg Select two or more one-line point type objects on one baseline to combine Seleziona due o più oggetti di testo con punto su una riga sulla stessa linea di base per combinarli
@msg No line breaks to remove Nessuna interruzione di riga da rimuovere
@msg No paragraph breaks to remove Nessuna interruzione di paragrafo da rimuovere
@msg Too many breaks to remove at once Troppe interruzioni da rimuovere in una volta
@msg Select point type Seleziona testo con punto
@msg The point type is aligned that way already Il testo con punto è già allineato così
@msg The point type has no transform to reset Il testo con punto non ha trasformazioni da reimpostare
@msg No fonts are missing Non mancano font
@msg the replacement font is not available il font sostitutivo non è disponibile
@msg missing `align` `align` mancante
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` deve essere lines, paragraphs, words o glyphs, non {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` deve essere "line" o "paragraph", non {other}
@msg `align` must be "left", "center" or "right", not {other} `align` deve essere "left", "center" o "right", non {other}
Can't render this file because it contains an unexpected character in line 2 and column 73.
+44
View File
@@ -5570,3 +5570,47 @@ anchor Points: ポイント:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` `inkLimit`、`removeWhiteOverprint` または `overprintBlack` を指定してください
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first 印刷インキの修正は CMYK ドキュメントで使用できます。先にファイル › ドキュメントのカラーモード › CMYK カラーを選択してください
@msg `{key}` must be 0..{max} `{key}` は 0..{max} にしてください
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text テキストを分割
Combine Point Text ポイント文字を結合
Remove Breaks 改行を削除
Split Text at Cursor カーソル位置でテキストを分割
Set Alignment, Keep Position 位置を保って行揃えを設定
Reset Text Transform テキストの変形をリセット
Replace Missing Fonts… 見つからないフォントを置換…
Replace Missing Fonts 見つからないフォントを置換
Remove Line Breaks 強制改行を削除
Remove Paragraph Breaks 段落の区切りを削除
Break Text into Lines テキストを行に分割
Break Text into Paragraphs テキストを段落に分割
Break Text into Words テキストを単語に分割
Break Text into Glyphs テキストを字形に分割
Reset Text Transform and Scale テキストの変形と比率をリセット
Set Alignment, Keep Position: 位置を保って行揃えを設定:
Break Text into: テキストを分割:
Split at Cursor カーソル位置で分割
Reset Transform 変形をリセット
Shift-click: break into paragraphs Shift+クリック:段落に分割
Alt-click: reset the horizontal scale too Alt+クリック:水平比率もリセット
breakText Lines 行
breakText Words 単語
breakText Glyphs 字形
@msg Click in type with a Type tool to split it 分割するには、文字ツールでテキスト内をクリックしてください
@msg Too many type objects to change at once 一度に変更するテキストオブジェクトが多すぎます
@msg Too many pieces to make at once 一度に作成する部分が多すぎます
@msg Select horizontal point or area type whose text fits テキストが収まっている横組みのポイント文字またはエリア内文字を選択してください
@msg Nothing to split at the cursor カーソル位置で分割するものがありません
@msg Select two or more one-line point type objects on one baseline to combine 結合するには、同じベースライン上にある 1 行のポイント文字オブジェクトを 2 つ以上選択してください
@msg No line breaks to remove 削除する強制改行がありません
@msg No paragraph breaks to remove 削除する段落の区切りがありません
@msg Too many breaks to remove at once 一度に削除する改行が多すぎます
@msg Select point type ポイント文字を選択してください
@msg The point type is aligned that way already ポイント文字はすでにその行揃えです
@msg The point type has no transform to reset ポイント文字にリセットする変形がありません
@msg No fonts are missing 見つからないフォントはありません
@msg the replacement font is not available 置換用のフォントを使用できません
@msg missing `align` `align` がありません
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` は lines、paragraphs、words、glyphs のいずれかにしてください({other} は使用できません)
@msg `kind` must be "line" or "paragraph", not {other} `kind` は "line" または "paragraph" にしてください({other} は使用できません)
@msg `align` must be "left", "center" or "right", not {other} `align` は "left"、"center"、"right" のいずれかにしてください({other} は使用できません)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+44
View File
@@ -5872,3 +5872,47 @@ anchor Points: წერტილები:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` მიუთითეთ `inkLimit`, `removeWhiteOverprint` ან `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first საბეჭდი მელნის შესწორებები მუშაობს CMYK დოკუმენტებზე: ჯერ აირჩიეთ ფაილი › დოკუმენტის ფერის რეჟიმი › CMYK ფერი
@msg `{key}` must be 0..{max} `{key}` უნდა იყოს 0..{max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text ტექსტის დაყოფა
Combine Point Text წერტილის ტექსტის გაერთიანება
Remove Breaks წყვეტების წაშლა
Split Text at Cursor ტექსტის დაყოფა კურსორთან
Set Alignment, Keep Position სწორების დაყენება მდებარეობის შენარჩუნებით
Reset Text Transform ტექსტის გარდაქმნის ჩამოყრა
Replace Missing Fonts… დაკარგული შრიფტების ჩანაცვლება…
Replace Missing Fonts დაკარგული შრიფტების ჩანაცვლება
Remove Line Breaks სტრიქონის წყვეტების წაშლა
Remove Paragraph Breaks აბზაცის წყვეტების წაშლა
Break Text into Lines ტექსტის დაყოფა სტრიქონებად
Break Text into Paragraphs ტექსტის დაყოფა აბზაცებად
Break Text into Words ტექსტის დაყოფა სიტყვებად
Break Text into Glyphs ტექსტის დაყოფა გლიფებად
Reset Text Transform and Scale ტექსტის გარდაქმნისა და მასშტაბის ჩამოყრა
Set Alignment, Keep Position: სწორების დაყენება მდებარეობის შენარჩუნებით:
Break Text into: ტექსტის დაყოფა:
Split at Cursor დაყოფა კურსორთან
Reset Transform გარდაქმნის ჩამოყრა
Shift-click: break into paragraphs Shift-დაწკაპუნება: დაყოფა აბზაცებად
Alt-click: reset the horizontal scale too Alt-დაწკაპუნება: ჰორიზონტალური მასშტაბის ჩამოყრაც
breakText Lines სტრიქონები
breakText Words სიტყვები
breakText Glyphs გლიფები
@msg Click in type with a Type tool to split it დასაყოფად დააწკაპუნეთ ტექსტში ტექსტის ხელსაწყოთი
@msg Too many type objects to change at once ერთდროულად შესაცვლელად ძალიან ბევრი ტექსტური ობიექტია
@msg Too many pieces to make at once ერთდროულად შესაქმნელად ძალიან ბევრი ნაწილია
@msg Select horizontal point or area type whose text fits აირჩიეთ ჰორიზონტალური წერტილის ან არის ტექსტი, რომლის ტექსტიც ეტევა
@msg Nothing to split at the cursor კურსორთან დასაყოფი არაფერია
@msg Select two or more one-line point type objects on one baseline to combine გასაერთიანებლად აირჩიეთ ერთ საბაზისო ხაზზე მდებარე ორი ან მეტი ერთსტრიქონიანი წერტილის ტექსტის ობიექტი
@msg No line breaks to remove წასაშლელი სტრიქონის წყვეტები არ არის
@msg No paragraph breaks to remove წასაშლელი აბზაცის წყვეტები არ არის
@msg Too many breaks to remove at once ერთდროულად წასაშლელად ძალიან ბევრი წყვეტაა
@msg Select point type აირჩიეთ წერტილის ტექსტი
@msg The point type is aligned that way already წერტილის ტექსტი უკვე ასეა სწორებული
@msg The point type has no transform to reset წერტილის ტექსტს ჩამოსაყრელი გარდაქმნა არ აქვს
@msg No fonts are missing დაკარგული შრიფტები არ არის
@msg the replacement font is not available ჩამნაცვლებელი შრიფტი მიუწვდომელია
@msg missing `align` `align` აკლია
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` უნდა იყოს lines, paragraphs, words ან glyphs და არა {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` უნდა იყოს "line" ან "paragraph" და არა {other}
@msg `align` must be "left", "center" or "right", not {other} `align` უნდა იყოს "left", "center" ან "right" და არა {other}
Can't render this file because it is too large.
+44
View File
@@ -5464,3 +5464,47 @@ anchor Points: 포인트:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` `inkLimit`, `removeWhiteOverprint` 또는 `overprintBlack`을 지정하십시오
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first 인쇄 잉크 수정은 CMYK 문서에서 작동합니다. 먼저 파일 › 문서 색상 모드 › CMYK 색상을 선택하십시오
@msg `{key}` must be 0..{max} `{key}`은(는) 0..{max}이어야 합니다
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text 텍스트 나누기
Combine Point Text 포인트 문자 결합
Remove Breaks 나누기 제거
Split Text at Cursor 커서 위치에서 텍스트 나누기
Set Alignment, Keep Position 위치를 유지하며 정렬 설정
Reset Text Transform 텍스트 변형 재설정
Replace Missing Fonts… 누락된 글꼴 바꾸기…
Replace Missing Fonts 누락된 글꼴 바꾸기
Remove Line Breaks 강제 줄 바꿈 제거
Remove Paragraph Breaks 단락 나누기 제거
Break Text into Lines 텍스트를 줄로 나누기
Break Text into Paragraphs 텍스트를 단락으로 나누기
Break Text into Words 텍스트를 단어로 나누기
Break Text into Glyphs 텍스트를 글리프로 나누기
Reset Text Transform and Scale 텍스트 변형 및 비율 재설정
Set Alignment, Keep Position: 위치를 유지하며 정렬 설정:
Break Text into: 텍스트 나누기:
Split at Cursor 커서 위치에서 나누기
Reset Transform 변형 재설정
Shift-click: break into paragraphs Shift-클릭: 단락으로 나누기
Alt-click: reset the horizontal scale too Alt-클릭: 가로 비율도 재설정
breakText Lines 줄
breakText Words 단어
breakText Glyphs 글리프
@msg Click in type with a Type tool to split it 나누려면 문자 도구로 텍스트 안을 클릭하십시오
@msg Too many type objects to change at once 한 번에 변경할 문자 오브젝트가 너무 많습니다
@msg Too many pieces to make at once 한 번에 만들 조각이 너무 많습니다
@msg Select horizontal point or area type whose text fits 텍스트가 모두 들어가는 가로 포인트 문자 또는 영역 문자를 선택하십시오
@msg Nothing to split at the cursor 커서 위치에서 나눌 내용이 없습니다
@msg Select two or more one-line point type objects on one baseline to combine 결합하려면 같은 기준선에 있는 한 줄짜리 포인트 문자 오브젝트를 두 개 이상 선택하십시오
@msg No line breaks to remove 제거할 강제 줄 바꿈이 없습니다
@msg No paragraph breaks to remove 제거할 단락 나누기가 없습니다
@msg Too many breaks to remove at once 한 번에 제거할 나누기가 너무 많습니다
@msg Select point type 포인트 문자를 선택하십시오
@msg The point type is aligned that way already 포인트 문자가 이미 그렇게 정렬되어 있습니다
@msg The point type has no transform to reset 포인트 문자에 재설정할 변형이 없습니다
@msg No fonts are missing 누락된 글꼴이 없습니다
@msg the replacement font is not available 대체 글꼴을 사용할 수 없습니다
@msg missing `align` `align`이 없습니다
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by`는 lines, paragraphs, words 또는 glyphs여야 합니다({other}는 사용할 수 없음)
@msg `kind` must be "line" or "paragraph", not {other} `kind`는 "line" 또는 "paragraph"여야 합니다({other}는 사용할 수 없음)
@msg `align` must be "left", "center" or "right", not {other} `align`은 "left", "center" 또는 "right"여야 합니다({other}는 사용할 수 없음)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+44
View File
@@ -5831,3 +5831,47 @@ anchor Points: Punten:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` geef `inkLimit`, `removeWhiteOverprint` of `overprintBlack` op
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first Drukinktcorrecties werken in CMYK-documenten: kies eerst Bestand › Documentkleurmodus › CMYK-kleur
@msg `{key}` must be 0..{max} `{key}` moet 0..{max} zijn
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Tekst splitsen
Combine Point Text Punttekst combineren
Remove Breaks Einden verwijderen
Split Text at Cursor Tekst splitsen bij cursor
Set Alignment, Keep Position Uitlijning instellen, positie behouden
Reset Text Transform Teksttransformatie herstellen
Replace Missing Fonts… Ontbrekende lettertypen vervangen…
Replace Missing Fonts Ontbrekende lettertypen vervangen
Remove Line Breaks Regeleinden verwijderen
Remove Paragraph Breaks Alinea-einden verwijderen
Break Text into Lines Tekst splitsen in regels
Break Text into Paragraphs Tekst splitsen in alinea's
Break Text into Words Tekst splitsen in woorden
Break Text into Glyphs Tekst splitsen in glyphs
Reset Text Transform and Scale Teksttransformatie en schaal herstellen
Set Alignment, Keep Position: Uitlijning instellen, positie behouden:
Break Text into: Tekst splitsen in:
Split at Cursor Splitsen bij cursor
Reset Transform Transformatie herstellen
Shift-click: break into paragraphs Shift-klik: splitsen in alinea's
Alt-click: reset the horizontal scale too Alt-klik: ook de horizontale schaal herstellen
breakText Lines Regels
breakText Words Woorden
breakText Glyphs Glyphs
@msg Click in type with a Type tool to split it Klik met een Type-gereedschap in tekst om deze te splitsen
@msg Too many type objects to change at once Te veel tekstobjecten om in één keer te wijzigen
@msg Too many pieces to make at once Te veel stukken om in één keer te maken
@msg Select horizontal point or area type whose text fits Selecteer horizontale punt- of vlaktekst waarvan de tekst past
@msg Nothing to split at the cursor Niets te splitsen bij de cursor
@msg Select two or more one-line point type objects on one baseline to combine Selecteer twee of meer punttekstobjecten van één regel op één basislijn om ze te combineren
@msg No line breaks to remove Geen regeleinden om te verwijderen
@msg No paragraph breaks to remove Geen alinea-einden om te verwijderen
@msg Too many breaks to remove at once Te veel einden om in één keer te verwijderen
@msg Select point type Selecteer punttekst
@msg The point type is aligned that way already De punttekst is al zo uitgelijnd
@msg The point type has no transform to reset De punttekst heeft geen transformatie om te herstellen
@msg No fonts are missing Er ontbreken geen lettertypen
@msg the replacement font is not available het vervangende lettertype is niet beschikbaar
@msg missing `align` `align` ontbreekt
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` moet lines, paragraphs, words of glyphs zijn, niet {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` moet "line" of "paragraph" zijn, niet {other}
@msg `align` must be "left", "center" or "right", not {other} `align` moet "left", "center" of "right" zijn, niet {other}
Can't render this file because it contains an unexpected character in line 2 and column 73.
+44
View File
@@ -5737,3 +5737,47 @@ anchor Points: Punkty:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` podaj `inkLimit`, `removeWhiteOverprint` lub `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first Poprawki farb drukarskich działają w dokumentach CMYK: najpierw wybierz Plik › Tryb koloru dokumentu › Kolor CMYK
@msg `{key}` must be 0..{max} `{key}` musi wynosić 0..{max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Podziel tekst
Combine Point Text Połącz tekst punktowy
Remove Breaks Usuń podziały
Split Text at Cursor Podziel tekst w miejscu kursora
Set Alignment, Keep Position Ustaw wyrównanie, zachowaj położenie
Reset Text Transform Resetuj przekształcenie tekstu
Replace Missing Fonts… Zastąp brakujące czcionki…
Replace Missing Fonts Zastąp brakujące czcionki
Remove Line Breaks Usuń podziały wiersza
Remove Paragraph Breaks Usuń podziały akapitu
Break Text into Lines Podziel tekst na wiersze
Break Text into Paragraphs Podziel tekst na akapity
Break Text into Words Podziel tekst na słowa
Break Text into Glyphs Podziel tekst na glify
Reset Text Transform and Scale Resetuj przekształcenie i skalę tekstu
Set Alignment, Keep Position: Ustaw wyrównanie, zachowaj położenie:
Break Text into: Podziel tekst na:
Split at Cursor Podziel w miejscu kursora
Reset Transform Resetuj przekształcenie
Shift-click: break into paragraphs Kliknięcie z Shift: podziel na akapity
Alt-click: reset the horizontal scale too Kliknięcie z Alt: resetuj także skalę poziomą
breakText Lines Wiersze
breakText Words Słowa
breakText Glyphs Glify
@msg Click in type with a Type tool to split it Kliknij w tekst narzędziem Tekst, aby go podzielić
@msg Too many type objects to change at once Zbyt wiele obiektów tekstowych do zmiany naraz
@msg Too many pieces to make at once Zbyt wiele części do utworzenia naraz
@msg Select horizontal point or area type whose text fits Zaznacz poziomy tekst punktowy lub akapitowy, którego tekst się mieści
@msg Nothing to split at the cursor Nie ma nic do podzielenia w miejscu kursora
@msg Select two or more one-line point type objects on one baseline to combine Zaznacz co najmniej dwa jednowierszowe obiekty tekstu punktowego na jednej linii bazowej, aby je połączyć
@msg No line breaks to remove Brak podziałów wiersza do usunięcia
@msg No paragraph breaks to remove Brak podziałów akapitu do usunięcia
@msg Too many breaks to remove at once Zbyt wiele podziałów do usunięcia naraz
@msg Select point type Zaznacz tekst punktowy
@msg The point type is aligned that way already Tekst punktowy jest już tak wyrównany
@msg The point type has no transform to reset Tekst punktowy nie ma przekształcenia do zresetowania
@msg No fonts are missing Nie brakuje żadnych czcionek
@msg the replacement font is not available czcionka zastępcza jest niedostępna
@msg missing `align` brak `align`
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` musi mieć wartość lines, paragraphs, words lub glyphs, a nie {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` musi mieć wartość "line" lub "paragraph", a nie {other}
@msg `align` must be "left", "center" or "right", not {other} `align` musi mieć wartość "left", "center" lub "right", a nie {other}
Can't render this file because it contains an unexpected character in line 2 and column 73.
+44
View File
@@ -5800,3 +5800,47 @@ anchor Points: Pontos:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` informe `inkLimit`, `removeWhiteOverprint` ou `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first As Correções de Tinta para Impressão funcionam em documentos CMYK: escolha antes Arquivo › Modo de Cor do Documento › Cor CMYK
@msg `{key}` must be 0..{max} `{key}` deve ser 0..{max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Dividir Texto
Combine Point Text Combinar Texto de Ponto
Remove Breaks Remover Quebras
Split Text at Cursor Dividir Texto no Cursor
Set Alignment, Keep Position Definir Alinhamento, Manter Posição
Reset Text Transform Redefinir Transformação do Texto
Replace Missing Fonts… Substituir Fontes Ausentes…
Replace Missing Fonts Substituir Fontes Ausentes
Remove Line Breaks Remover Quebras de Linha
Remove Paragraph Breaks Remover Quebras de Parágrafo
Break Text into Lines Dividir Texto em Linhas
Break Text into Paragraphs Dividir Texto em Parágrafos
Break Text into Words Dividir Texto em Palavras
Break Text into Glyphs Dividir Texto em Glifos
Reset Text Transform and Scale Redefinir Transformação e Escala do Texto
Set Alignment, Keep Position: Definir Alinhamento, Manter Posição:
Break Text into: Dividir Texto em:
Split at Cursor Dividir no Cursor
Reset Transform Redefinir Transformação
Shift-click: break into paragraphs Clique com Shift: dividir em parágrafos
Alt-click: reset the horizontal scale too Alt-clique: redefinir também a escala horizontal
breakText Lines Linhas
breakText Words Palavras
breakText Glyphs Glifos
@msg Click in type with a Type tool to split it Clique em um texto com uma ferramenta Texto para dividi-lo
@msg Too many type objects to change at once Objetos de texto demais para alterar de uma vez
@msg Too many pieces to make at once Partes demais para criar de uma vez
@msg Select horizontal point or area type whose text fits Selecione texto de ponto ou de área horizontal cujo texto caiba
@msg Nothing to split at the cursor Nada a dividir no cursor
@msg Select two or more one-line point type objects on one baseline to combine Selecione dois ou mais objetos de texto de ponto de uma linha na mesma linha de base para combiná-los
@msg No line breaks to remove Nenhuma quebra de linha a remover
@msg No paragraph breaks to remove Nenhuma quebra de parágrafo a remover
@msg Too many breaks to remove at once Quebras demais para remover de uma vez
@msg Select point type Selecione texto de ponto
@msg The point type is aligned that way already O texto de ponto já está alinhado assim
@msg The point type has no transform to reset O texto de ponto não tem transformação a redefinir
@msg No fonts are missing Nenhuma fonte está ausente
@msg the replacement font is not available a fonte substituta não está disponível
@msg missing `align` `align` ausente
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` deve ser lines, paragraphs, words ou glyphs, não {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` deve ser "line" ou "paragraph", não {other}
@msg `align` must be "left", "center" or "right", not {other} `align` deve ser "left", "center" ou "right", não {other}
Can't render this file because it contains an unexpected character in line 6 and column 47.
+44
View File
@@ -5860,3 +5860,47 @@ anchor Points: Точки:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` задайте `inkLimit`, `removeWhiteOverprint` или `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first Исправления печатных красок работают в документах CMYK: сначала выберите Файл › Цветовой режим документа › Цвет CMYK
@msg `{key}` must be 0..{max} `{key}` должен быть 0..{max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Разбить текст
Combine Point Text Объединить текст из точки
Remove Breaks Удалить разрывы
Split Text at Cursor Разбить текст у курсора
Set Alignment, Keep Position Задать выравнивание, сохранив положение
Reset Text Transform Сбросить трансформацию текста
Replace Missing Fonts… Заменить отсутствующие шрифты…
Replace Missing Fonts Заменить отсутствующие шрифты
Remove Line Breaks Удалить разрывы строк
Remove Paragraph Breaks Удалить разрывы абзацев
Break Text into Lines Разбить текст на строки
Break Text into Paragraphs Разбить текст на абзацы
Break Text into Words Разбить текст на слова
Break Text into Glyphs Разбить текст на глифы
Reset Text Transform and Scale Сбросить трансформацию и масштаб текста
Set Alignment, Keep Position: Выравнивание с сохранением положения:
Break Text into: Разбить текст на:
Split at Cursor Разбить у курсора
Reset Transform Сбросить трансформацию
Shift-click: break into paragraphs Shift+щелчок: разбить на абзацы
Alt-click: reset the horizontal scale too Alt+щелчок: сбросить и масштаб по горизонтали
breakText Lines Строки
breakText Words Слова
breakText Glyphs Глифы
@msg Click in type with a Type tool to split it Щёлкните в тексте инструментом «Текст», чтобы разбить его
@msg Too many type objects to change at once Слишком много текстовых объектов для изменения за один раз
@msg Too many pieces to make at once Слишком много частей для создания за один раз
@msg Select horizontal point or area type whose text fits Выделите горизонтальный текст из точки или в области, текст которого помещается
@msg Nothing to split at the cursor У курсора нечего разбивать
@msg Select two or more one-line point type objects on one baseline to combine Выделите два или более однострочных объекта текста из точки на одной базовой линии, чтобы объединить их
@msg No line breaks to remove Нет разрывов строк для удаления
@msg No paragraph breaks to remove Нет разрывов абзацев для удаления
@msg Too many breaks to remove at once Слишком много разрывов для удаления за один раз
@msg Select point type Выделите текст из точки
@msg The point type is aligned that way already Текст из точки уже так выровнен
@msg The point type has no transform to reset У текста из точки нет трансформации для сброса
@msg No fonts are missing Отсутствующих шрифтов нет
@msg the replacement font is not available шрифт для замены недоступен
@msg missing `align` не указан `align`
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` должен быть lines, paragraphs, words или glyphs, а не {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` должен быть "line" или "paragraph", а не {other}
@msg `align` must be "left", "center" or "right", not {other} `align` должен быть "left", "center" или "right", а не {other}
Can't render this file because it contains an unexpected character in line 2 and column 73.
+44
View File
@@ -5938,3 +5938,47 @@ shape Open Öppen
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` ange `inkLimit`, `removeWhiteOverprint` eller `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first Tryckfärgskorrigeringar fungerar i CMYK-dokument: välj först Fil › Dokumentets färgläge › CMYK-färg
@msg `{key}` must be 0..{max} `{key}` måste vara 0..{max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Dela upp text
Combine Point Text Kombinera punkttext
Remove Breaks Ta bort brytningar
Split Text at Cursor Dela text vid markören
Set Alignment, Keep Position Ställ in justering, behåll position
Reset Text Transform Återställ texttransformering
Replace Missing Fonts… Ersätt saknade typsnitt…
Replace Missing Fonts Ersätt saknade typsnitt
Remove Line Breaks Ta bort radbrytningar
Remove Paragraph Breaks Ta bort styckebrytningar
Break Text into Lines Dela upp text i rader
Break Text into Paragraphs Dela upp text i stycken
Break Text into Words Dela upp text i ord
Break Text into Glyphs Dela upp text i glyfer
Reset Text Transform and Scale Återställ texttransformering och skala
Set Alignment, Keep Position: Ställ in justering, behåll position:
Break Text into: Dela upp text i:
Split at Cursor Dela vid markören
Reset Transform Återställ transformering
Shift-click: break into paragraphs Shift-klicka: dela upp i stycken
Alt-click: reset the horizontal scale too Alt-klick: återställ även den horisontella skalan
breakText Lines Rader
breakText Words Ord
breakText Glyphs Glyfer
@msg Click in type with a Type tool to split it Klicka i text med ett textverktyg för att dela den
@msg Too many type objects to change at once För många textobjekt att ändra på en gång
@msg Too many pieces to make at once För många delar att skapa på en gång
@msg Select horizontal point or area type whose text fits Markera vågrät punkt- eller områdestext vars text får plats
@msg Nothing to split at the cursor Inget att dela vid markören
@msg Select two or more one-line point type objects on one baseline to combine Markera två eller fler enradiga punkttextobjekt på samma baslinje för att kombinera dem
@msg No line breaks to remove Inga radbrytningar att ta bort
@msg No paragraph breaks to remove Inga styckebrytningar att ta bort
@msg Too many breaks to remove at once För många brytningar att ta bort på en gång
@msg Select point type Markera punkttext
@msg The point type is aligned that way already Punkttexten är redan justerad så
@msg The point type has no transform to reset Punkttexten har ingen transformering att återställa
@msg No fonts are missing Inga typsnitt saknas
@msg the replacement font is not available ersättningstypsnittet är inte tillgängligt
@msg missing `align` `align` saknas
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` måste vara lines, paragraphs, words eller glyphs, inte {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` måste vara "line" eller "paragraph", inte {other}
@msg `align` must be "left", "center" or "right", not {other} `align` måste vara "left", "center" eller "right", inte {other}
Can't render this file because it contains an unexpected character in line 5 and column 25.
+44
View File
@@ -5840,3 +5840,47 @@ anchor Points: Noktalar:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` `inkLimit`, `removeWhiteOverprint` veya `overprintBlack` verin
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first Baskı Mürekkebi Düzeltmeleri CMYK belgelerinde çalışır: önce Dosya › Belge Renk Modu › CMYK Rengi'ni seçin
@msg `{key}` must be 0..{max} `{key}` 0..{max} olmalıdır
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Metni Böl
Combine Point Text Nokta Metnini Birleştir
Remove Breaks Kesmeleri Kaldır
Split Text at Cursor Metni İmleçte Böl
Set Alignment, Keep Position Hizalamayı Ayarla, Konumu Koru
Reset Text Transform Metin Dönüşümünü Sıfırla
Replace Missing Fonts… Eksik Yazı Tiplerini Değiştir…
Replace Missing Fonts Eksik Yazı Tiplerini Değiştir
Remove Line Breaks Satır Sonlarını Kaldır
Remove Paragraph Breaks Paragraf Sonlarını Kaldır
Break Text into Lines Metni Satırlara Böl
Break Text into Paragraphs Metni Paragraflara Böl
Break Text into Words Metni Sözcüklere Böl
Break Text into Glyphs Metni Gliflere Böl
Reset Text Transform and Scale Metin Dönüşümünü ve Ölçeğini Sıfırla
Set Alignment, Keep Position: Hizalamayı Ayarla, Konumu Koru:
Break Text into: Metni Böl:
Split at Cursor İmleçte Böl
Reset Transform Dönüşümü Sıfırla
Shift-click: break into paragraphs Shift tuşuyla tıklayın: paragraflara böl
Alt-click: reset the horizontal scale too Alt tuşuyla tıklayın: yatay ölçeği de sıfırla
breakText Lines Satırlar
breakText Words Sözcükler
breakText Glyphs Glifler
@msg Click in type with a Type tool to split it Bölmek için bir Yazım Aracıyla metnin içine tıklayın
@msg Too many type objects to change at once Bir kerede değiştirilecek çok fazla metin nesnesi var
@msg Too many pieces to make at once Bir kerede oluşturulacak çok fazla parça var
@msg Select horizontal point or area type whose text fits Metni sığan yatay nokta veya alan metni seçin
@msg Nothing to split at the cursor İmleçte bölünecek bir şey yok
@msg Select two or more one-line point type objects on one baseline to combine Birleştirmek için aynı taban çizgisinde iki veya daha fazla tek satırlı nokta metni nesnesi seçin
@msg No line breaks to remove Kaldırılacak satır sonu yok
@msg No paragraph breaks to remove Kaldırılacak paragraf sonu yok
@msg Too many breaks to remove at once Bir kerede kaldırılacak çok fazla kesme var
@msg Select point type Nokta metni seçin
@msg The point type is aligned that way already Nokta metni zaten bu şekilde hizalı
@msg The point type has no transform to reset Nokta metninin sıfırlanacak dönüşümü yok
@msg No fonts are missing Eksik yazı tipi yok
@msg the replacement font is not available yedek yazı tipi kullanılamıyor
@msg missing `align` `align` eksik
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by`, lines, paragraphs, words veya glyphs olmalı, {other} değil
@msg `kind` must be "line" or "paragraph", not {other} `kind`, "line" veya "paragraph" olmalı, {other} değil
@msg `align` must be "left", "center" or "right", not {other} `align`, "left", "center" veya "right" olmalı, {other} değil
Can't render this file because it contains an unexpected character in line 5 and column 25.
+44
View File
@@ -5481,3 +5481,47 @@ anchor Points: Точки:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` задайте `inkLimit`, `removeWhiteOverprint` або `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first Виправлення друкарських фарб працюють у документах CMYK: спершу виберіть Файл › Колірний режим документа › Колір CMYK
@msg `{key}` must be 0..{max} `{key}` має бути 0..{max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Розбити текст
Combine Point Text Об’єднати текст із точки
Remove Breaks Видалити розриви
Split Text at Cursor Розбити текст біля курсора
Set Alignment, Keep Position Задати вирівнювання, зберігши положення
Reset Text Transform Скинути трансформацію тексту
Replace Missing Fonts… Замінити відсутні шрифти…
Replace Missing Fonts Замінити відсутні шрифти
Remove Line Breaks Видалити розриви рядків
Remove Paragraph Breaks Видалити розриви абзаців
Break Text into Lines Розбити текст на рядки
Break Text into Paragraphs Розбити текст на абзаци
Break Text into Words Розбити текст на слова
Break Text into Glyphs Розбити текст на гліфи
Reset Text Transform and Scale Скинути трансформацію та масштаб тексту
Set Alignment, Keep Position: Вирівнювання зі збереженням положення:
Break Text into: Розбити текст на:
Split at Cursor Розбити біля курсора
Reset Transform Скинути трансформацію
Shift-click: break into paragraphs Shift+клацання: розбити на абзаци
Alt-click: reset the horizontal scale too Alt+клацання: скинути й горизонтальний масштаб
breakText Lines Рядки
breakText Words Слова
breakText Glyphs Гліфи
@msg Click in type with a Type tool to split it Клацніть у тексті інструментом «Текст», щоб розбити його
@msg Too many type objects to change at once Забагато текстових об’єктів, щоб змінити їх за раз
@msg Too many pieces to make at once Забагато частин, щоб створити їх за раз
@msg Select horizontal point or area type whose text fits Виділіть горизонтальний текст із точки або в області, текст якого вміщується
@msg Nothing to split at the cursor Біля курсора нічого розбивати
@msg Select two or more one-line point type objects on one baseline to combine Виділіть два або більше однорядкових об’єктів тексту з точки на одній базовій лінії, щоб об’єднати їх
@msg No line breaks to remove Немає розривів рядків для видалення
@msg No paragraph breaks to remove Немає розривів абзаців для видалення
@msg Too many breaks to remove at once Забагато розривів, щоб видалити їх за раз
@msg Select point type Виділіть текст із точки
@msg The point type is aligned that way already Текст із точки вже так вирівняно
@msg The point type has no transform to reset Текст із точки не має трансформації для скидання
@msg No fonts are missing Відсутніх шрифтів немає
@msg the replacement font is not available шрифт для заміни недоступний
@msg missing `align` не вказано `align`
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` має бути lines, paragraphs, words або glyphs, а не {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` має бути "line" або "paragraph", а не {other}
@msg `align` must be "left", "center" or "right", not {other} `align` має бути "left", "center" або "right", а не {other}
Can't render this file because it contains an unexpected character in line 178 and column 5.
+44
View File
@@ -5483,3 +5483,47 @@ anchor Points: Điểm:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` hãy cho `inkLimit`, `removeWhiteOverprint` hoặc `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first Sửa mực in hoạt động trên tài liệu CMYK: trước hết chọn Tệp › Chế độ màu tài liệu › Màu CMYK
@msg `{key}` must be 0..{max} `{key}` phải là 0..{max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text Tách văn bản
Combine Point Text Kết hợp chữ điểm
Remove Breaks Xóa ngắt
Split Text at Cursor Tách văn bản tại con trỏ
Set Alignment, Keep Position Đặt căn lề, giữ vị trí
Reset Text Transform Đặt lại biến đổi văn bản
Replace Missing Fonts… Thay thế phông chữ thiếu…
Replace Missing Fonts Thay thế phông chữ thiếu
Remove Line Breaks Xóa xuống dòng
Remove Paragraph Breaks Xóa ngắt đoạn
Break Text into Lines Tách văn bản thành dòng
Break Text into Paragraphs Tách văn bản thành đoạn
Break Text into Words Tách văn bản thành từ
Break Text into Glyphs Tách văn bản thành glyph
Reset Text Transform and Scale Đặt lại biến đổi và tỷ lệ văn bản
Set Alignment, Keep Position: Đặt căn lề, giữ vị trí:
Break Text into: Tách văn bản thành:
Split at Cursor Tách tại con trỏ
Reset Transform Đặt lại biến đổi
Shift-click: break into paragraphs Shift-bấm: tách thành đoạn
Alt-click: reset the horizontal scale too Alt-bấm: đặt lại cả tỷ lệ ngang
breakText Lines Dòng
breakText Words Từ
breakText Glyphs Glyph
@msg Click in type with a Type tool to split it Nhấp vào văn bản bằng công cụ Chữ để tách văn bản
@msg Too many type objects to change at once Quá nhiều đối tượng chữ để thay đổi cùng lúc
@msg Too many pieces to make at once Quá nhiều phần để tạo cùng lúc
@msg Select horizontal point or area type whose text fits Chọn chữ điểm hoặc chữ vùng nằm ngang có văn bản vừa khung
@msg Nothing to split at the cursor Không có gì để tách tại con trỏ
@msg Select two or more one-line point type objects on one baseline to combine Chọn hai đối tượng chữ điểm một dòng trở lên trên cùng một đường cơ sở để kết hợp
@msg No line breaks to remove Không có xuống dòng nào để xóa
@msg No paragraph breaks to remove Không có ngắt đoạn nào để xóa
@msg Too many breaks to remove at once Quá nhiều ngắt để xóa cùng lúc
@msg Select point type Chọn chữ điểm
@msg The point type is aligned that way already Chữ điểm đã được căn như vậy
@msg The point type has no transform to reset Chữ điểm không có biến đổi nào để đặt lại
@msg No fonts are missing Không thiếu phông chữ nào
@msg the replacement font is not available phông chữ thay thế không có sẵn
@msg missing `align` thiếu `align`
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` phải là lines, paragraphs, words hoặc glyphs, không phải {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` phải là "line" hoặc "paragraph", không phải {other}
@msg `align` must be "left", "center" or "right", not {other} `align` phải là "left", "center" hoặc "right", không phải {other}
Can't render this file because it contains an unexpected character in line 200 and column 5.
+44
View File
@@ -5724,3 +5724,47 @@ anchor Points: 点:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` 请指定 `inkLimit`、`removeWhiteOverprint` 或 `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first 印刷油墨修正适用于 CMYK 文档:请先选择 文件 › 文档颜色模式 › CMYK 颜色
@msg `{key}` must be 0..{max} `{key}` 必须为 0..{max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text 拆分文本
Combine Point Text 合并点文字
Remove Breaks 删除换行
Split Text at Cursor 在光标处拆分文本
Set Alignment, Keep Position 设置对齐并保持位置
Reset Text Transform 重置文本变换
Replace Missing Fonts… 替换缺失的字体…
Replace Missing Fonts 替换缺失的字体
Remove Line Breaks 删除强制换行
Remove Paragraph Breaks 删除段落换行
Break Text into Lines 将文本拆分为行
Break Text into Paragraphs 将文本拆分为段落
Break Text into Words 将文本拆分为单词
Break Text into Glyphs 将文本拆分为字形
Reset Text Transform and Scale 重置文本变换和缩放
Set Alignment, Keep Position: 设置对齐并保持位置:
Break Text into: 将文本拆分为:
Split at Cursor 在光标处拆分
Reset Transform 重置变换
Shift-click: break into paragraphs Shift+单击:拆分为段落
Alt-click: reset the horizontal scale too Alt+点击:同时重置水平缩放
breakText Lines 行
breakText Words 单词
breakText Glyphs 字形
@msg Click in type with a Type tool to split it 请用文字工具在文字中单击以拆分
@msg Too many type objects to change at once 一次要更改的文字对象过多
@msg Too many pieces to make at once 一次要创建的部分过多
@msg Select horizontal point or area type whose text fits 请选择文本能完全容纳的横排点文字或区域文字
@msg Nothing to split at the cursor 光标处没有可拆分的内容
@msg Select two or more one-line point type objects on one baseline to combine 请选择同一基线上两个或更多单行点文字对象以合并
@msg No line breaks to remove 没有要删除的强制换行
@msg No paragraph breaks to remove 没有要删除的段落换行
@msg Too many breaks to remove at once 一次要删除的换行过多
@msg Select point type 请选择点文字
@msg The point type is aligned that way already 点文字已经是这种对齐方式
@msg The point type has no transform to reset 点文字没有可重置的变换
@msg No fonts are missing 没有缺失的字体
@msg the replacement font is not available 替换字体不可用
@msg missing `align` 缺少 `align`
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` 必须是 lines、paragraphs、words 或 glyphs,而不是 {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` 必须是 "line" 或 "paragraph",而不是 {other}
@msg `align` must be "left", "center" or "right", not {other} `align` 必须是 "left"、"center" 或 "right",而不是 {other}
Can't render this file because it contains an unexpected character in line 2 and column 73.
+44
View File
@@ -5520,3 +5520,47 @@ anchor Points: 點:
@msg give `inkLimit`, `removeWhiteOverprint` or `overprintBlack` 請指定 `inkLimit`、`removeWhiteOverprint` 或 `overprintBlack`
@msg Print Ink Fixes works on CMYK documents: choose File › Document Color Mode › CMYK Color first 印刷油墨修正適用於 CMYK 文件:請先選擇 檔案 › 文件色彩模式 › CMYK 色彩
@msg `{key}` must be 0..{max} `{key}` 必須為 0..{max}
# Artwork Cleanup add-on: its text tools (Object › Plug-ins › Artwork Cleanup and the panel's Text row) and their messages.
Break Text 分割文字
Combine Point Text 合併點文字
Remove Breaks 移除換行
Split Text at Cursor 在游標處分割文字
Set Alignment, Keep Position 設定對齊並保持位置
Reset Text Transform 重設文字變形
Replace Missing Fonts… 取代遺失的字體…
Replace Missing Fonts 取代遺失的字體
Remove Line Breaks 移除強制換行
Remove Paragraph Breaks 移除段落換行
Break Text into Lines 將文字分割為行
Break Text into Paragraphs 將文字分割為段落
Break Text into Words 將文字分割為單字
Break Text into Glyphs 將文字分割為字符
Reset Text Transform and Scale 重設文字變形與縮放
Set Alignment, Keep Position: 設定對齊並保持位置:
Break Text into: 將文字分割為:
Split at Cursor 在游標處分割
Reset Transform 重設變形
Shift-click: break into paragraphs Shift-按一下:分割為段落
Alt-click: reset the horizontal scale too Alt+點擊:一併重設水平縮放
breakText Lines 行
breakText Words 單字
breakText Glyphs 字符
@msg Click in type with a Type tool to split it 請用文字工具在文字中按一下以分割
@msg Too many type objects to change at once 一次要變更的文字物件過多
@msg Too many pieces to make at once 一次要建立的部分過多
@msg Select horizontal point or area type whose text fits 請選取文字能完整容納的橫排點文字或區域文字
@msg Nothing to split at the cursor 游標處沒有可分割的內容
@msg Select two or more one-line point type objects on one baseline to combine 請選取同一基線上兩個或更多單行點文字物件以合併
@msg No line breaks to remove 沒有要移除的強制換行
@msg No paragraph breaks to remove 沒有要移除的段落換行
@msg Too many breaks to remove at once 一次要移除的換行過多
@msg Select point type 請選取點文字
@msg The point type is aligned that way already 點文字已經是這種對齊方式
@msg The point type has no transform to reset 點文字沒有可重設的變形
@msg No fonts are missing 沒有遺失的字體
@msg the replacement font is not available 替代字體無法使用
@msg missing `align` 未提供 `align`
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` 必須是 lines、paragraphs、words 或 glyphs,而不是 {other}
@msg `kind` must be "line" or "paragraph", not {other} `kind` 必須是 "line" 或 "paragraph",而不是 {other}
@msg `align` must be "left", "center" or "right", not {other} `align` 必須是 "left"、"center" 或 "right",而不是 {other}
Can't render this file because it contains an unexpected character in line 6 and column 47.
+43
View File
@@ -5731,6 +5731,49 @@ topmost piece.
{"name":"run_command","arguments":{"command":"addon.cleanup.simplify","params":{"tolerance":40,"protectCorners":true}}}
```
### Text tools
The add-on's text tools act on the selected type (or `ids`), each as one undo step, and refuse with an error when there
is nothing for them to do. The pieces they make are point type that keeps its characters' styles, each placed so the
glyph of its first character lands where it was drawn.
- `addon.cleanup.breakText {by?, ids?}` turns each horizontal point or area type object into point type objects, one
per line (`by: "lines"`, the default), paragraph (`"paragraphs"`), word (`"words"`) or glyph (`"glyphs"`; a ligature
stays one piece). Spaces between the pieces go. A paragraph keeps its lines: where the layout wrapped one, the piece
has a forced line break. Justified lines close up, and a paragraph broken out whole keeps its bullet (not its
number; the other pieces have no list markers). Type on a path, vertical type, threaded type and area type whose text doesn't fit stay as they are and are
listed in `skipped`.
- `addon.cleanup.combineText {ids?}` merges one-line horizontal point type into as few objects as it can, every glyph
staying where it was (within 0.01 pt). Pieces turned and sheared alike whose baselines lie within 0.4 em of each
other join left to right (a baseline a little off becomes baseline shift); a space goes between pieces at least half
a space apart, and tracking on the character before each piece keeps its place. A gap wider than 10 em, an overlap of
more than 1 em, another look (paint, opacity, effects) or a piece that can't keep its place once joined (a ligature
forming across the join) starts another object. Each object made takes the place and paint of its leftmost piece. The
main use is type from a PDF that arrives one glyph per object.
- `addon.cleanup.removeBreaks {kind, ids?}` removes the forced line breaks (`kind: "line"`) or paragraph returns
(`"paragraph"`) in the characters selected with a Type tool, else in all the text of the selected type. A break
between two words becomes a space; merged paragraphs take the first one's paragraph style.
- `addon.cleanup.splitText {}` works while a Type tool edits horizontal point or area type: the text before the
selected characters, those characters and the text after them become point type objects (at a bare cursor, the text
before and after it). Spaces at the ends of each piece go; a piece that starts inside a left-aligned line takes a
first-line indent, so its later lines stay where they were. Editing ends with the pieces selected.
- `addon.cleanup.alignInPlace {align, ids?}` aligns every paragraph of each horizontal point type object `left`,
`center` or `right` and moves the object so its longest line stays where it is: a single line doesn't move at all.
- `addon.cleanup.resetTextTransform {scale?, ids?}` takes the rotation, shear and reflection off point type, keeping its
size and the centre of its text. With `scale: true` the horizontal scale goes too, the object's and each character's.
- `addon.cleanup.replaceFonts {font?}` sets every character of the document's type whose font family isn't available
in `font` (by default the default type font, Source Sans 3), in that family's style closest to the one it had.
In the panel, the Text row (which the panel can hide) runs them: Shift-click Lines to break into paragraphs, and
Alt-click Reset Transform to reset the horizontal scale too. Recovering live text from outlines, combining vertical
type and evening out horizontal scales are not there yet.
```json
{"name":"run_command","arguments":{"command":"addon.cleanup.breakText","params":{"by":"words"}}}
{"name":"run_command","arguments":{"command":"addon.cleanup.combineText","params":{}}}
{"name":"run_command","arguments":{"command":"addon.cleanup.removeBreaks","params":{"kind":"paragraph"}}}
```
## Path Precision Tools add-on
Path Precision Tools is a built-in add-on for exact anchor and path work: its commands are `addon.pathtools.*`, and in
+10 -1
View File
@@ -1,6 +1,6 @@
# Parity with Adobe Illustrator
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (Color Effects add-on: Duotone and Print Ink Fixes; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-31 · **Change:** minor (Artwork Cleanup add-on: text tools; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
## Feature submission evidence
@@ -1222,6 +1222,14 @@ Show, Fix Selected and Fix All (each one undo step), Deep Ungroup and Rejoin Pat
alone; Fix All as one step; scopes; rejoin within the gap only) and `addons::cleanup::tests` in `vectorcraft-ui-egui`
(the submenu opens the panel; Check lists what it finds). Commands: `docs/mcp.md` › Artwork Cleanup add-on.
The add-on's text tools (2026-10-31) work on the shared text layout: Break Text into lines, paragraphs, words or glyphs
and Split Text at Cursor make point type pieces placed by the glyph of their first character, Combine Point Text merges
one-line point type back with every glyph where it was (tracking and spaces, checked against the layout), and Remove
Line / Paragraph Breaks, Set Alignment, Keep Position, Reset Text Transform and Replace Missing Fonts. Tests:
`cmd::addons::cleanup::text::tests` in `vectorcraft-engine` (glyphs kept in place, styles kept, one undo step,
refusals), `addons::cleanup::tests` in `vectorcraft-ui-egui` (the submenu and the panel's Text row run them) and
`cleanup_text_tools_with_junk_editing_ranges` in the command sweep.
## Path Precision Tools add-on (2026-10-30)
A VectorCraft addition, not an Illustrator feature, so no area score moves. Object › Plug-ins › Path Precision Tools,
@@ -1296,6 +1304,7 @@ add-on.
| Date | Change | Summary |
|---|---|---|
| 2026-10-31 | minor | Artwork Cleanup add-on (a VectorCraft addition): text tools; evidence added; scores unchanged. |
| 2026-10-30 | minor | Color Effects add-on: live Duotone (one to four inks, spot tones kept) and Print Ink Fixes; scores and estimates unchanged |
| 2026-10-30 | minor | 3D Extrude & Bevel, Inflate and Rotate apply to type, which stays editable (#4715); scores and estimates unchanged |
| 2026-10-30 | minor | Color Effects add-on: live Vector Halftone; scores and estimates unchanged |
+3 -1
View File
@@ -1,6 +1,6 @@
# UI and interaction parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (Color Effects add-on: Duotone and Print Ink Fixes; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-31 · **Change:** minor (Artwork Cleanup add-on: text tools; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
## Feature submission evidence
@@ -330,6 +330,7 @@ next unit in the order a right-click on a ruler lists them, from Feet back to Po
| Smart Pen add-on (VectorCraft addition): Object › Plug-ins › Smart Pen selects the Smart Pen Tool, which the All Tools drawer lists under its own heading but neither toolbar shows as shipped (no shortcut), and opens the Smart Pen panel (next segment straight or curved; Remove Points Keep Shape, Make Smooth, Make Corner, Retract Handles, Close or Join). The tool draws (click: straight or curved segment, a curve after a straight one tangent; drag: smooth anchor, Alt breaks the handle, Shift 45°, Space moves it; Tab switches a segment; rubber band; close, join, continue) and edits without switching tools (click selects a point, drag moves it, Alt-click removes it keeping the shape, Shift+Alt-click deletes it, double-click toggles smooth/corner; handle drags with Shift and Alt; segment drags reshape, Alt-click adds a point); an Alt-drag away from paths selects the points inside a marquee | done | `addon.pen.*`, `crates/tools/src/addons/smart_pen.rs`, `crates/ui-egui/src/addons/pen.rs` and their tests; not an Illustrator feature, so it is not scored |
| Path Precision Tools add-on (VectorCraft addition): Object › Plug-ins › Path Precision Tools selects its three tools (Anchor Eraser Brush, Extend Path, Measure & Inspect), which the All Tools drawer lists under their own heading but neither toolbar shows as shipped, opens the Anchor Inspector panel (the selected anchor's X/Y and handle lengths and angles as fields; Make Smooth, Make Corner, Equalize Handles, Remove Points Keep Shape, Add Points, the three ways to close) and runs Add Points at / Split at Crossings; a double-click on the brush or Extend Path tool opens its options | done | `addon.pathtools.*`, `crates/tools/src/addons/`, `crates/ui-egui/src/addons/pathtools.rs` and their tests; not an Illustrator feature, so it is not scored |
| Artwork Cleanup add-on (VectorCraft addition): Object › Plug-ins › Artwork Cleanup opens its panel, which checks the selection or the document for 16 kinds of issue, lists them with counts, and shows or fixes the picked rows or all of them, each fix one undo step; Simplify Anchors (Keep Shape), Rejoin Path Pieces and Deep Ungroup open their options first | done | `addon.cleanup.*`, `crates/ui-egui/src/addons/cleanup.rs` and its tests; not an Illustrator feature, so it is not scored |
| Artwork Cleanup add-on text tools (VectorCraft addition): the panel's Text row, which the panel can hide, and Object › Plug-ins › Artwork Cleanup set the alignment of point type keeping its place, break type into lines (Shift-click: paragraphs), words or glyphs, split type at the cursor, reset a text transform (Alt-click: and its horizontal scale), combine point type, remove line or paragraph breaks and replace missing fonts, each one undo step | done | `addon.cleanup.*` text commands, `crates/ui-egui/src/addons/cleanup.rs` and its tests; a VectorCraft addition, so it is not scored |
| Color Effects add-on (VectorCraft addition): live Exposure, Desaturate and Invert Colors in Effect › Color Effects and Object › Plug-ins › Color Effects; its panel picks a colour adjustment and its values and adds it as a live effect or applies it as a filter, into the colours, as one undo step; Select by Color Kind selects the objects that use spot colours, carry colour effects, or are RGB, CMYK or grayscale images; Vector Halftone draws an object as halftone dots live (the dots Object › Vector Halftone makes), which Expand Appearance and the exporters write as paths; Duotone prints each colour's tone with one to four inks (a tint of a spot tone's swatch, or the CMYK mix of several), live or as a filter, its dialog a row per tone and the picked tone's curve; Print Ink Fixes (a CMYK document's ink limit, white overprint removed, black overprint set) opens its options from the submenu and the panel's Inks | done | `addon.color.*`, `crates/ui-egui/src/addons/color.rs` and its tests; not an Illustrator feature, so it is not scored |
## Look and feel
@@ -371,6 +372,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development
| Date | Change | Summary |
|---|---|---|
| 2026-10-31 | minor | Artwork Cleanup add-on (a VectorCraft addition): its text tools in the panel and Object › Plug-ins › Artwork Cleanup; scores and estimates unchanged |
| 2026-10-30 | minor | Color Effects add-on: live Duotone (one to four inks, spot tones kept) and Print Ink Fixes; scores and estimates unchanged |
| 2026-10-30 | minor | Color Effects add-on: live Vector Halftone; scores and estimates unchanged |
| 2026-10-30 | minor | Path Precision Tools add-on: the Parametric Shape Tool (ten re-editable shape kinds), its panel and Convert to Parametric Shape; scores and estimates unchanged |