On a shaped frame, Inset Spacing keeps text that far from the frame's
edge, measured from the edge itself rather than along the line. Before,
the inset was trimmed from each end of a line after the frame's edge was
found, so on a sloped edge the text came closer than the inset: a
triangle (0,0), (100,0), (0,100) with a 10 pt inset gave the band
40..60 the span 10..30, where a 10 pt clearance gives 10..25.86. Holes
keep the same clearance.
A rectangular frame keeps its inset from each side as before, a
column's inner edges keep it once, and the gap kept beside a wrap
object along the line is unchanged.
With one Objects on Path group selected and an object chosen with its
Select/Move widget, Detach in the Properties panel's Objects on Path
Options section takes that object out of the group
(`object.objectsOnPath.detach {member?}`). The object goes just above
the group in the Layers panel, stays where it is drawn and is selected
alone. The other objects move up the path in their order: Auto Space
spreads them evenly again, and a Space value and the start are kept.
The group and its path stay with one object or none left. Hidden or
locked objects and groups are refused, and Detach is one undo step.
When the group is itself on another group's path, the object goes
above the outer group so it does not join that path.
Docs: docs/mcp.md, docs/target-app-parity.md. Detach and its undo name
have rows in every catalog, and the new message in the complete-message
catalogs.
The profiles Edit > Assign Profile gives a document stayed the active
color settings after another document came to the front, so a new,
opened or switched-to document drew and converted its colors through the
previous document's profile, and closing the last document left it in
use.
The session now puts the front document's profiles in use (the working
spaces where it has none, or no document is open) after a new or opened
document, a tab switch, a close, File > Revert, every command including
each batch step and Undo or Redo of Assign Profile, a failed batch or
plug-in call, a script's rollback and slice boundaries, a canceled
preview, undo group or undo scope, and for each document Export All Open
and the update of placed documents work in. The active settings are
rebuilt, and the documents redrawn, only when they change.
document.export and document.serialize to .ai with useArtboards: false
wrote the one page of the visible art, but embedded the one-artboard
stand-in as the native document and as the editing copy for other apps.
Reopened, the file replaced the document's artboards with one artboard
around the art.
- The .ai of the art's bounds now leaves out both editing copies and
returns the warning "Preserve editing was left out: an .ai file of the
art's bounds would replace the document's artboards when reopened",
as the PDF of the art's bounds does.
- useArtboards: false with pdfCompatible: false is refused, with or
without editingData, since the file would have blank pages and no
editing copy.
- Save, Save As, Save a Copy and exports of the artboards keep both
copies. Legacy version 7 .ai files are written as before.
Docs: docs/mcp.md, docs/target-app-parity.md, document.export params.
Session::place_read and Session::place_queue_read placed a file, or
loaded the place cursor, in whichever document was active when the read
finished. A job prepared in one document could land in another one that
was activated meanwhile, or that stayed open after the first was closed.
Replace did not stop it when the other document held an equal object
under the same id.
PlaceJob, QueueJob and QueueRead now keep the uid of the document they
were prepared in. Both calls refuse a read whose document is not the
active one with "the document it was prepared for is no longer active",
before any selection, edit, tool change or journal entry. Switching back
to the original document places the file as before.
Preferences > Performance > Use Monitor Profile for Canvas (on by default,
`monitorProfileDisplay`) converts the canvas's art, artboard paper and
transparency grid from sRGB into the ICC profile of the display that shows
most of the main window, after any proof. Arranged panes and the render
worker's frames are converted too; the worker keeps its frame in sRGB.
- Windows: the display's default profile for its standard color mode
(ColorProfileGetDisplayDefault, GetICMProfileW where it is missing). A
display with Advanced Color on is left to Windows.
- X11: colord's profile for the RandR output, then the output's
`_ICC_PROFILE`, then the screen's when only one output is active.
- A Wayland session stays sRGB; macOS converts the window itself.
- A profile that is missing, larger than 1 MiB, not an RGB display
profile or unreadable leaves the canvas sRGB, with the reason in
`ui.inspect`'s `displayProfile`.
The conversion is sampled into a 33x33x33 table when a profile is read, on
a background thread. Exports, `ui.render` and the document are unchanged.
Join removed each merged anchor from the path's anchor list as it went,
which moved every anchor after it, so a long selected run in front of
many other anchors took time quadratic in the path's length. It also
looked up each anchor's selection in the selected-anchor set.
Join now marks the merged anchors and removes them in one pass at the
end, reading the selection from one flag per anchor. Each corner or
smooth conversion sees the same neighbors as before, so the resulting
anchors are unchanged. Anchor::smooth_between holds the smoothing that
SubPath::smooth_anchor applies.
Tests compare the new merge bit for bit with the old one on 30,000
generated paths, and count the anchors moved when 300,000 selected
anchors in front of 300,000 others merge: each of the others moves
once.
A graph draws its series groups again with no rows of their own, so a
fill, stroke, opacity or graphic style that a command put on a series
group was lost the next time the graph's data, type or size changed,
and the graph stored nothing. paint.setFill and paint.setStroke already
pass a series group's id on to the paths directly inside it.
- appearance.addFill, addStroke, setItem, removeItem, duplicateItem and
moveItem, graphicStyle.apply and transparency.set with a fill or
stroke item now edit the paths directly in a series group given by id
or picked with Group Selection, and the graph stores what they change.
A series group with no such path, or with a fill or stroke row of its
own, is still edited itself, and appearance.moveItem from "contents"
still moves the group's own Contents row.
- appearance.clear, appearance.reduceToBasic, appearance.showAllHidden
and graphicStyle.breakLink edit the group as well as its paths.
- graphicStyle.apply without add and appearance.transfer onto a series
group clear the group's own opacity, blend mode, isolation, knockout,
effects and style link, and graphicStyle.apply with add unlinks the
group. A transfer source that is one of the group's paths keeps its
look, and a source that is the group's only path is an error.
- graphicStyle.redefine passes the style to the paths of a linked series
group, other than the source and the linked paths whose look was
edited, and clears and unlinks the group.
- graphicStyle.new, graphicStyle.redefine and appearance.transfer read a
series group that holds no row, effect, opacity, blend mode,
isolation, knockout or style link of its own through the topmost path
directly in it, and a move clears each of those paths. A series group
that holds one of them is read itself, as before.
- The Graphic Styles panel reads the style link of such a selected
series group from that path.
Docs: docs/mcp.md, docs/target-app-parity.md. Not yet: the Appearance
panel and appearance.setActiveItem still read the selected group's own
rows, and the effect commands and the paint, stroke and gradient
commands with an item still edit the group itself.
Follows #5501.
- When the faces that overlap form a cycle of depth orders, the order
broke the cycle only after drawing every face with nothing to wait
for, so a cap could be drawn before the walls and they painted over
it. A face drawn last now keeps its place: no face moves across it.
- A face with a corner at or past the eye, which the perspective
projection holds just in front of the eye, no longer gets a plane,
so its depth is not measured on a plane its drawing does not match.
- docs/three-d.md states the masked member rule in Groups as the
Fills and strokes section does, and docs/target-app-parity.md cites
the Inflate test by its name.
Open and Place store some images again instead of keeping their bytes:
a photo turned by its orientation, a TIFF the image decoder reads, and
a CMYK TIFF with one plane per ink. Those images lost the ICC profile
their file embeds, and 16-bit samples were cut to 8 bits.
- raster_image keeps gray and RGB samples at 8 or 16 bits in the
stored PNG, with the gray or RGB profile in its iCCP chunk. A
profile of another color space, such as the CMYK profile of a CMYK
image read as RGB, is left out. A TIFF's profile is read with the
tiff decoder, which reads it whatever the image's size.
- stored_cmyk_tiff and turned_cmyk write CMYK8 or CMYK16 as the file
has it, with the CMYK profile as TIFF tag 34675 (UNDEFINED). A CMYK
JPEG's profile is put together from all its APP2 segments.
- The docs/target-app-parity.md line on EXIF orientation says 8 or 16
bits, and a line after it describes the kept profile.
- Each contour of a graphic is cut only at the faces its own bounds reach,
and the points cut at the faces count toward a limit of 64,000,000.
- The paint copies of a graphic's parts count toward a budget of their own,
one copy for a part's fill and one for its seam stroke when it takes one.
- A part filled with a pattern, or whose gradient has a transparent stop or
point, takes no seam stroke.
- Under several lights an opaque solid part also covers its seams, with its
lit art clipped to a band of the seam stroke's width.
- A line of a graphic that reaches farther than any surface is refused, as a
curve already was.
- A full turn places each segment by its own middle, so the cut at the back
falls on the segment edge nearest half a turn from the middle.
- A group's members are measured for graphics only when it has some.
- docs/three-d.md: Revolve shows no Face control, and the new limits.
While an action records, a script run, a tool's drag or a panel's or
dialog's commit could journal steps past the 10,000-step or 8 MiB limit
of an action set file without any check: the limit showed only when
Stop Playing/Recording left the steps out, and a finished script then
showed "Ran <name>".
The recording is now checked whenever the journal changed: after every
engine command the app runs (a synchronous script.run and a batch
included), when a script run ends, after a tool's result, and on the
next frame for edits committed directly. The edit and the journal
stay, the status bar names the limit at once, later recorded commands
are refused as before, and Stop keeps only the steps that fit. A
journal rewrite is checked again.
`document.export`, `document.serialize` and `document.exportSelection`
accept a key that only another format takes. `{"format": "pdf",
"quality": 80}` wrote the PDF and gave no warning about `quality`.
The reply's `warnings` now name each such key, in the wording of a
save's note: "`quality` was not used: PDF files don't read it".
- The format's own options, the commands' own keys, null values and an
exporter plug-in's format get no note.
- The note names the kind of file written. A legacy `.ai` file
(`aiVersion: "7"`) reads `outlineText`, `hiddenObjects`,
`transparentImages`, the artboard choice and `useArtboards`, and
beside `aiVersion` no other option. The other `.ai` files don't read
those three options or `preserveEditing`, which is always on for
them.
- An export doesn't read `separateArtboards`, or `range` for a
`vectorcraft` or `template` file. A save reads them to write each
artboard to a file of its own.
- In the app, an export that automation asked for (`app.export` with a
path, `file.exportAs` run as a command) gives the same notes in its
reply and in the status bar. A Background Export replies before the
file is written, and that reply has the notes. An export that a
person starts from the File menu gets none.
- Export As gives a template export no `useArtboards` or `range`: a
template doesn't read them.
- `vectorcraft-cli convert` sends `outlineText` only with
`--outline-text`, and `hiddenLayers` only to the formats that read
it. A conversion then warns only about an option the command line
gave.
`document.save` gives its notes as before: it does not apply the `.ai`
rules above.
Docs: docs/mcp.md, docs/control-protocol.md,
docs/target-app-parity.md, the params text of the three commands.
Clear, path.deleteAnchors, Cut, Copy and Cut Path scanned every selected
anchor or segment of a path once for each of its subpaths, so a path with
thousands of subpaths took time that grew with the square of their count.
The selection sets are ordered by subpath, so a new helper, subpath_refs,
reads one subpath's entries with a range lookup instead. The results are
unchanged.
With one selected interior anchor on each of 8,000 open subpaths of one
path, Clear took a median of 74 ms of thread CPU time before and 3 ms
after (release build, five runs each; the timings are noisy because the
CPU was shared and loaded).
Tests in delete_path_pieces.rs check exact surviving subpaths and their
order for anchor and segment selections across open and closed subpaths,
an untouched subpath, references to missing subpaths and indexes, one
Undo/Redo step and a second Clear, and Cut Path on several subpaths.
Without a plug-in store, plugin.install read the process grant once
for its later check and again for its approval. A grant another session
recorded between the two reads approved the module; when that session
revoked it before the install, the check saw the grant it first read
and the install recorded the revoked grant again. The approval now comes
from the first read.
plugin.install published the plug-in before it wrote the module and its
grant to the Plug-ins folder. A plugin.remove in another session in
between removed the plug-in, its file and its grant, and the install then
wrote both back, so the plug-in loaded with its grant at the next launch.
A session attaching the folder in between loaded the old version, which
kept running while the folder held the new one. plugin.remove, loads of
the stored plug-ins and loads of a plug-ins folder now wait until an
install has written the folder, and an install waits for them.
Docs: docs/plugins.md, docs/target-app-parity.md.
Assigning to a named child counts its list of the matching children, and
assigning through an empty XMLList counts the chain of targets it climbs,
before either changes the tree. copy(), toXMLString(), normalize(), the
`..` selector and the depth check on insertion hold the stack they keep
for each open level of the tree. Inserting an XMLList, and assigning one
to an item of an XMLList, hold their copy of the list's items.
copy() reads attributes in place, and toXMLString() reads attributes and
namespace declarations in place, instead of copying the lists. copy()
charges the declarations it copies before it copies them. Deleting by
name or assigning an attribute compacts the remaining links in place
instead of building a second list.
toXMLString() holds the set of prefixes a namespace lookup passes, and
picks a new nsN prefix in one pass over the declarations in scope
instead of rescanning them for each candidate.
The parser holds its open elements and their names, the text it gathers,
each tag's attribute values, the sets that check declarations and
attribute names, and its one copy of an element's namespaces, and it
charges the strings of the namespace declarations it makes. The
allowance it holds from the source's length covers the copy a value
takes as it becomes a string.
Comparing XML values, converting them to strings, assigning an XMLList
to an attribute, and reading a name, a qualifier, a namespace URI or
embedded text from a string built by joining strings flatten it with
flat_charged. An attribute name read from a string reserves its copy,
and an element name shares the string.
Docs: docs/target-app-parity.md.
An <image> whose EXIF orientation or TIFF Orientation tag turns or flips
its pixels, linked or as a data URL, is turned before usvg reads it: the
pixels are stored as they are shown, the width and height swap, and the
box fits the turned image under preserveAspectRatio, as CSS
image-orientation: from-image describes. A turned CMYK JPEG or TIFF keeps
its inks as a CMYK TIFF, as does a CMYK TIFF that is not turned.
- Data URLs of type image/tiff, and untyped TIFF bytes, are read.
- TIFFs with extra channels, CCITT Group 3 TIFFs and planar CMYK TIFFs
are read as Open and Place read them.
- Converted images decode within the decode allowance, and a TIFF that
claims more strips or tiles than the decoder can count is refused
before any decoder opens it.
- exif_orientation moves to vectorcraft-doc so Open/Place and SVG import
share it.
Tests: crates/svg/tests/it/image_orientation.rs.
Two findings in the review of #7146, in how layers whose differing
objects come in as the page's art treat envelopes of type:
- The envelopes of type the page holds against the layers were weighed
on the layers as read only, and that choice was also applied at the
placed files' other placement, which can move a placed file over or
under an envelope. Each placement is now weighed on its own. The
places of those left live, for layers that open alone, still come
from the layers as read: a .ai that opens alone has no other
placement.
- A placement that draws as the page with only the held envelopes put
back was dropped, as nothing in it differs for its objects to come in
as the page's art. It is now used as it is, as layers that draw as the
page with every live envelope's drawn art put back already are. The
try with every envelope of type put back keeps its order: objects
that differ come in as the page's art, as before.
Part of #7141.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spine::at_length walked the first subpath's segments from the start
until one ended at or past the arc length, so placing every blend step,
spread key, blend widget and Objects on Path attachment cost one
comparison per earlier segment. It now searches the anchors' arc
lengths, which never decrease, and picks the same segment: the first
whose end is at or past the length, so an exact anchor length and
repeated lengths from zero-length segments keep their segment. The
point and tangent evaluation is unchanged.
Tests compare the search with the old walk on open and closed spines,
one segment, curves, zero-length segments, every anchor, both ends,
out-of-range and non-finite lengths, and check that a 4,096-segment
spine needs at most 13 comparisons.
Extrude & Bevel, Extrude & Bevel (Classic) and Inflate sorted the fill
walls and the faces of a stroke that leaves gaps (dashed, profiled,
brushed) by one depth each, so a far stroke face could draw over a near
fill wall and the reverse. Two such strokes on one shape drew over each
other the same way, and Inflate drew a plain stroke as if it covered
the fill's edge, which an inflated fill can rise in front of.
- Each flat facet now carries its plane, so its depth can be measured
at any point under it, with orthographic and perspective views.
- Where faces of one shape's fill and strokes overlap in the wrong
order, the overlapping faces are ordered by their depths where they
overlap. A fill wall and its covering stroke keep their order.
- A face of a stroke that leaves gaps that passes through a fill wall,
a covering stroke's face or an earlier such stroke's face is cut
along the line where their depths meet, keeping its curved outline,
within the face limit and the paint allowance.
- Under Inflate, no stroke counts as covering the fill's edge.
- The faces go in one order when the fill and strokes are at full
opacity with Normal blending. A member of a 3D group with an opacity
mask or a blending mode of its own orders its faces inside it when
its own paints qualify. A gradient with transparent stops keeps the
one order with graphics on its cap, and so does Inflate in gradients
under a colored light. A solid color under a colored light, drawn as
one composite per surface, still draws each surface whole.
193 Hindi rows outside the plug-in messages said something other than
their English: "a number of points" read as "many digits", swatches as
clocks, crop as a harvest, resolution as a resolve, and saving used
words the glossary replaces. Others left English prose in place,
dropped a clause ("to keep them all", "what follows it") or ran the
sentence backwards.
- Rows use सेव, स्वॉच, क्रॉप, रिज़ॉल्यूशन, पाथ and टाइप (for a text
object) as docs/localization-hi.md sets them.
- Values a command reads (enum values, preset names, OpenType tags)
stay as the English writes them.
- A test checks those terms and the point unit in every Hindi row, and
another pins rows whose quantity or clause was lost.
- docs/localization-hi.md, docs/localization-parity.md and
docs/target-app-parity.md carry the new counts and a wording note.
A justified area type line counted a tab as a word space, so it gave
the tab a share of its room that no glyph drew, and the line ended
short of its measure. A word-tab-word line ended short too.
- The text up to a line's last tab keeps its spacing, each tab keeps
its stop and its leader stays in its gap. The text after the last
tab takes the line's room: word spacing, letter spacing and glyph
scaling within their ranges, then its word spaces, or the gaps
between its letters when it has none. Kashidas go in that text when
it is one run.
- A tab separates two words, so Single Word Justification does not
align a word-tab-word line.
- Right-to-left lines take the text drawn after the last tab drawn,
measured as the line is drawn, and vertical lines work down the
column. Point type keeps its natural width.
Docs: docs/target-app-parity.md, docs/type-parity.md.
The message scan read every message as a format string, so a plain
literal such as `expected {"dataBase64": "…"}` became the catalog key
`expected {"dataBase64"}`, a template whose placeholder took the rest of
any message starting with "expected". The plug-in output error
`expected value at line 1 column 1` was then shown in German as
"erwartet: value at line 1 column 1".
- The scan doubles every brace of a message that is not a format string
(a plain literal or a `&str` constant); `format!` strings and
`#[error(...)]` strings keep their `{{`, `}}` and placeholders.
- 150 `@msg` rows in 23 catalogs are keyed by the full message with its
braces doubled, and their translations write those braces doubled and
carry the full JSON example of the English.
- An `@msg` row with no placeholder is an exact message under its text
with `{{` and `}}` read as braces, so it is found at any length, and a
nested JSON part translated by such a row shows single braces.
- The Hindi row for `give {key, value} or {values}` translates the whole
message; it kept the English "or" and left out "give".
Docs: docs/development.md, the zh-hant.tsv header, docs/target-app-parity.md.
Font Details, Linked Images and Embedded Images, and the Objects counts
Compound Paths, Text Objects, Placed Documents, Symbol Instances, Gradient
Meshes, Blends, Envelopes, Intertwine Groups, Opacity Masks and Objects
with Effects had no catalog rows, so the panel and its category menu
showed them in English in every other language. Each complete catalog
now has them, and the Document rows Pattern Swatches (de, it, ja, ko,
pl, uk, vi, zh-hant) and Path Length (bg, el) are added where missing.
The catalog test now checks every category title, every Objects count
label and Document's fixed row labels. document.info and the saved
report keep the English labels.
Distributing a story through threaded frames measured every frame but the
last with the whole rest of the story, and copied that rest several times
per frame, so a long story through many frames cost frames times story
length.
- The rest of the story is a borrowed view of the given runs (run index,
byte offset, length). Each frame's share is copied once; the slices,
styles of empty shares and the run left once the story is used up are
the same as before.
- Area type without Shrink Text to Fit whose lines align to the top is
first measured with the text through the end of the paragraph that
reaches about twice the previous frame's share (at least 512 bytes),
doubling while the story goes on. The share is taken when its text
stops fitting before the last paragraph break measured; layout places
lines in order, stops at the first that does not fit and shapes each
paragraph on its own text. Type on a path, Shrink Text to Fit, other
vertical alignments and a share ending in the story's last paragraph
are measured with the whole rest.
Tests compare every slice with the earlier algorithm, kept in the tests,
for many kinds of frames, styles and paragraphs and 12 seeded stories.
A story of 1,500 paragraphs (55,892 bytes) through 40 frames was laid
out over 2,126,762 bytes while measuring and is now laid out over
21,035.
The Live Paint Bucket drew the crosshair in place of its own cursor. It
now shows what a click paints: a filled paint bucket over a face it
fills (of a Live Paint group, or a region of the selected paths), a
brush over an edge it paints, and an empty bucket where it paints
nothing. The cursor follows Paint Fills, Paint Strokes and Shift, and
shows with Highlight off too. Alt still shows the Eyedropper, and Use
Precise Cursors shows the crosshair.
With a Live Paint group and ordinary paths selected, a region of the
paths outside the group is now highlighted, as a click there fills it.
The help page: https://helpx.adobe.com/illustrator/desktop/paint-and-fill/learn-painting-basics/paint-with-the-live-paint-bucket-tool.html