The Type tool's anchor and caret were moved past the inserted markers
only by the menu item in the desktop app. When text.convertListToText
came over MCP or the control channel while the Type tool was editing,
they kept their old byte offsets, and the inserted characters (a bullet
is 3 bytes) could leave an offset inside a character.
The command now ends the Type tool's typing, then moves its anchor and
caret past the characters inserted at or before them. In threaded type,
when the caret's character moved to another frame, the tool edits that
frame, which becomes the selection when it was not selected. The menu
item runs the command without adjusting the selection itself.
Docs: docs/mcp.md.
The reference app's Type › Bullets and Numbering › Convert to Text turns
the bullets and numbers of the selected list paragraphs into editable
text. VectorCraft drew list markers but could not convert them.
Type › Bullets and Numbering › Convert to Text (type.convertListToText,
text.convertListToText) puts each list paragraph's marker at the start
of the paragraph as characters: the bullet, or the number and its
period as the paragraph showed it, then a tab, in the marker's font,
size and color. The paragraph leaves its list. Where the marker's tab
went to the left indent, the paragraph gets a plain left tab stop
there, so the text after the tab starts where it did. Where the
marker's tab went to a stop with a leader, or to a right, center or
decimal stop, a plain stop where the tab ended keeps the converted tab
there without the leader; right, center and decimal stops between the
marker and that place are removed. Past the left indent this applies
only to a paragraph with no tab of its own. In threaded type the
numbers are those the story counted, and a paragraph that goes on into
the next frame is converted once, where it starts. A right-to-left
paragraph with no direction set gets the Right to Left direction when a
letter or roman numeral in front of it would make it run left to right.
The change is one undo step. The Type tool's caret stays next to the
character it was next to, and in threaded type it goes with that
character to the frame it moved to, which becomes the selection when it
was not selected.
With the Every-line Composer a paragraph that holds a tab is broken
line by line, so a converted paragraph can break its lines at other
places than the list paragraph did.
Docs: docs/mcp.md, docs/target-app-parity.md.
i18n: Convert to Text in the 24 catalogs.
An image whose pixels can't be decoded, such as a PNG cut short by an
interrupted download, drew nothing on the canvas and the status bar
said nothing. Every render decoded it again, so each frame that drew it
paid for a decode that fails.
The renderer now notes such an image by its content and does not decode
it again. Each render still counts it in decode_failures, so an export
reports it as before. An image refused only because the images being
drawn leave it no room within twice the decoding allowance is not
noted, and the next render tries it again.
On the canvas the image draws as a light gray box crossed from corner
to corner, and the status bar says once for each image that it couldn't
be decoded. Exports still draw nothing there.
Docs: docs/target-app-parity.md.
## What and why
Hostile `.ai` art styles and gradients (out-of-range caps, joins, miter limits, dash arrays, stroke widths, width profiles, blend opacity, paint flags, operand counts, too many gradient stops) must be clamped or ignored on import. The tests only: the limits and checks they cover are already on main, so the tests pin that behaviour so a later change can't silently drop it.
## How it was tested
The new tests pass, along with the full `cargo xtask ci` on the branch.
Combines the tests from: stream/vc-featD-g8-art-style-blend-opacity, stream/vc-featD-g8-art-style-cap-join, stream/vc-featD-g8-art-style-miter-cap, stream/vc-featD-g8-art-style-operands-cap, stream/vc-featD-g8-art-style-paint-flags, stream/vc-featD-g8-art-style-stroke-width, stream/vc-featD-g8-gradient-stops-cap, stream/vc-featD-g8-art-style-dash-cap, stream/vc-featD-g8-art-style-width-profile-cap
A space the file draws between two words gets tracking of its own so the
word after it stays where the file put it, but only U+0020 counted as a
space. A drawn no-break space (U+00A0, U+202F) or fixed-width space
(U+2000 to U+200A, thin and hair spaces included) took the old path: the
gaps on both sides of it also read as spaces, so the line came in with
an extra space before and after it and the words after it out of place.
These characters now count as spaces in the line's gap and drawn-space
rules, and a space of more than one byte is split into its own run whole.
The wrapped-line space tolerance in area type, a fiftieth of an em, is
named SPACE_TRACKING_TOLERANCE.
Given a family and no font style, text.create and text.createInPath
kept the default Regular, and text.setStyle kept the text's previous
style, even when the family has no such style. A family whose styles
are numbered (Hiragino W3, W6) has no Regular, so the file named a
font that isn't installed and opened with Missing Fonts.
They now keep the style when the family has it, else take the
family's closest one, as text.setRangeStyle already did. Its rule
moves to `textedit::fit_font_style`, which the three commands share;
Graph labels styled through text.setStyle follow it too. A style the
caller names is kept as given.
The test builds a family with W3 and W6 and no Regular in code
(`test_fonts::numbered_font`), so no font file is added.
A document keeps up to 25 saved views, but the View menu listed only the
first 10, so views 11 to 25 could be saved and never chosen. All 25 are
listed now, by name, and choosing one changes the current window to its
zoom, centre and rotation (within the canvas's zoom range).
Edit Views… used to open the generic parameter dialog on the first view,
where the name of the view to change had to be typed. It now opens a list
of the document's views, like Edit Selection…: pick one to rename it or
delete it; OK applies every change as one undo step and Cancel leaves the
views as they were. view.saved.edit takes {edits: [{name, newName?,
delete?}…]} for that, the one-view form still works, and the command is
off while the document has no saved view.
A file's views are untrusted: on open at most 25 are kept, names are
trimmed, capped at 255 characters and unique, and views without a finite
centre and a positive zoom are dropped. New View… starts from the first
free "View N" and caps names the same way.
Behaviour from the public help page "Use multiple windows and views":
https://helpx.adobe.com/illustrator/using/viewing-artwork.html
docs/gaps.md (G7) and docs/target-app-parity.md take New View/Edit Views
off the missing list; docs/mcp.md documents the view.saved commands; the
catalogs get the Saved View 11-25 rows in the pattern of 1-10.
When an undo scope that joined an open undo group was canceled, the
journal was cut back to where the scope began, but the group still
listed the cut entries' numbers. The next command took one of those
numbers, so a later cancel of the group also removed that command's
entry, even when it ran in another document, and a replay of the
journal no longer matched the documents. end_undo_scope now removes
the cut numbers from every open undo group's journal entries and batch
steps.
The brush cache dropped brush art that can show a symbol or a pattern
only when the art of the document's symbols or patterns changed. Renaming
one, for example swapping the names of two symbols, left their art as it
was, so a brush whose art holds an instance of one of those names kept
drawing the old symbol. The cache now compares the names along with the
art.
## What and why
Hostile EPS programs that give an operator the wrong operand type (a non-integer count, a non-procedure body, a non-boolean condition) must stop the import with a `typecheck` error instead of misbehaving. One test per operator: copy, for, idiv, if, ifelse, index, loop, mod, repeat, roll and srand. The tests only: the limits and checks they cover are already on main, so the tests pin that behaviour so a later change can't silently drop it.
## How it was tested
The new tests pass, along with the full `cargo xtask ci` on the branch.
Combines the tests from: stream/vc-featD-g8-eps-copy-typecheck-count, stream/vc-featD-g8-eps-for-typecheck-procedure, stream/vc-featD-g8-eps-idiv-typecheck-operand, stream/vc-featD-g8-eps-if-typecheck-condition, stream/vc-featD-g8-eps-if-typecheck-procedure, stream/vc-featD-g8-eps-ifelse-typecheck-condition, stream/vc-featD-g8-eps-ifelse-typecheck-procedure, stream/vc-featD-g8-eps-index-typecheck-count, stream/vc-featD-g8-eps-loop-typecheck-procedure, stream/vc-featD-g8-eps-mod-typecheck-operand, stream/vc-featD-g8-eps-repeat-typecheck-count, stream/vc-featD-g8-eps-repeat-typecheck-procedure, stream/vc-featD-g8-eps-roll-typecheck-j, stream/vc-featD-g8-eps-roll-typecheck-n, stream/vc-featD-g8-eps-srand-typecheck-operand
A double-click on the Type on a Path or Vertical Type on a Path tool
reported that the tool has no options. It now opens Type on a Path
Options for the selected type on a path, the same dialog as Type ›
Type on a Path › Type on a Path Options…. With nothing selected, or
with no type on a path selected, nothing opens and `tool.options`
returns the menu command's error, as the Rotate tool's double-click
does without a selection.
Docs: docs/control-protocol.md, the `tool.options` params doc.
Review follow-ups to the live blend import (#4127):
- The check's grid saturates its neighbours' keys: a step far out (a
coordinate like 1e300) saturates its cell, and the cells round it
overflowed (a panic in debug builds, a wrong cell in release).
- Each laid object matches one drawn step: two drawn steps near one laid
object no longer both take it, so a drawing the blend doesn't lay out
isn't taken for it (the drawn steps stay).
- A blend's record is read to the length its XP gives: hex digits after
it on the last line no longer spoil it.
Tests: a laid object matches one drawn step, 1e300 coordinates in the
check and in the import fuzz property, and a record line longer than
the record.
Review follow-ups for the live blend import:
- The check of a blend against its drawn steps matched every drawn step
against every laid one and measured centres inside the loop, so a
crafted blend (64 keys, 1,000 steps between each) could stall the
import. Centres and fills are now measured once, and each drawn step
finds its neighbours through a grid of cells the tolerance wide.
- A blend lays out at most 5,000 objects to be made live, and all the
blends of one import at most 50,000 (each laid out twice, once per
orientation); past either, the drawn steps stay, with the note. The
live steps take the place of drawn steps already counted against the
reader's object limit.
- A fill the check can't compare (a gradient, a pattern) or a missing
one no longer passes: the fills must both be none or solid colours
within the tolerance.
- When what follows the blend's own art isn't a group of drawn steps,
that art (its keys and spine) goes too, with the note, instead of
staying drawn.
- The spine test can no longer pass without asserting: a spine written
backwards keeps the keys' order (live), and a curved spine is read
exactly while its straight-drawn steps stay drawn. New tests for steps
with a gradient, for the blend's art when its steps aren't a group,
and for the budget; import_fuzz gains a mutated-blend property and a
large-blend test (998 steps live, 1,500 kept drawn, both quickly).
A blend in a .ai file's editing data came in as plain art twice over:
the blend's own art (its key objects and spine, on `%_` lines) and its
drawn steps were both kept visible, so the keys lay doubled under the
drawn ones and the spine showed as a stray path, and nothing was live.
The spine itself was read exactly (its line and curve segments match).
The `(Adobe Path Blends) 1 0 n XP` record is now read (n bytes of hex on
the `%` lines after it, capped, checked: `DCBA`, version 1, the key
count and a record per key starting with its id). Each object's
dictionary names its part (`ShapeBlendPathType` K or S) and a key's id
(`ShapeBlendPathID`); the keys the record lists and the spine become one
live blend, the keys sitting on the spine's anchors. The record's other
fields aren't known, so the spacing is the drawn steps between two keys
(Specified Steps) and the orientation whichever lays the blend out as
drawn; the blend replaces the drawn steps only when it comes out as
drawn (each step within 0.5 pt and 0.02 of its colour). Otherwise the
drawn steps stay, without the blend's own art over them, with a note.
Behaviour as documented: https://helpx.adobe.com/illustrator/using/blending-objects.html
Save As .ai writes the PDF-compatible part only (no editing data), so a
blend is written back as its steps drawn, not live.
Hand-written tests: two keys and a straight spine as one live blend with
three steps and interpolated colours; a curved spine (l, c, L) in both
directions keeping its points and the keys' order; a garbled or short
record, a key without an id and ids the record doesn't list keeping the
drawn steps with a note; moving a key moving the steps; record parsing
on hostile input. The import fuzz target gains the record and the new
mesh keys.
With the Selection tool, a single selected grid repeat shows a handle below the grid (rows), one right of it (columns), one past its bottom-right corner (both) and sliders between its first two columns and rows (horizontal and vertical spacing), as the public Set grid repeat options page describes. Each drag previews object.repeat.options and commits as one Repeat Options undo step; Escape cancels, and a drag that ends where it began leaves no step. Bounding-box handles keep priority where they overlap, and the controls hide with the edges or the bounding box. Before, a drag there scaled the source or started a marquee.
Part of #2691.