With repeated names numbered when a brush library is read, a stroke that
names a repeated brush draws the first brush of that name, and a later
brush of the name is read as "Name 2". Three tests built a library with a
repeated name and expected the later brush:
- Document Info lists a name two stored brushes share with the first
brush's type (art), not the last one's.
- The two render memory tests add their own `Dots` scatter brush beside
the default library's `Dots`. They now remove the default `Dots` first,
so the stroke draws their brush.
- The .ai writer test that leaves out a Pattern brush "X" gets the
Calligraphic brush after it back as "X 2", the name it had in the
document.
An envelope around type of only spaces and a rectangle mapped from the
union of the rectangle and the type's laid-out box, so the empty type
moved and scaled how the rectangle was distorted. Type that draws
nothing now adds no box while other content in the envelope has one,
in groups and layers and as a clipping path too. When nothing else in
the envelope has a box, the type keeps its laid-out box, so an envelope
of only empty type keeps its frame, Make with Mesh and Reset with Mesh
still work, and Release still gives back the warp shape.
This applies to Make with Warp, Make with Mesh, Make with Top Object,
Reset with Mesh, Edit Contents and the stroke margins. Type that has
not been measured yet keeps its laid-out box as before.
The PDF/.ai import's boxes for envelopes with Distort Appearance
measure each object as the whole contents measure it, so the margins
the import tries match the contents' geometric box. The three import
tests that measure type without registering the glyph measure now
register it first, so another test registering it while they run no
longer changes their result.
The Gradient panel's Method menu gets two methods of VectorCraft's own
beside Classic and Perceptual. They mix a linear, radial, conic or
diamond gradient's colors around the hue wheel: hue, saturation and
brightness each change evenly from one stop to the next, and the hue
angle keeps one direction, as the fixed-direction rules of
https://www.w3.org/TR/css-color-4/#hue-interpolation do. Hue Clockwise
lowers the angle (red, magenta, blue), which is clockwise on the color
wheels, and Hue Counterclockwise raises it (red, yellow, green).
Two stops whose hues are less than half a color level apart count as
one hue and keep it, so a color and a tint or shade of it do not turn
the whole wheel. A stop whose RGB numbers are less than half a color
level apart is gray and takes the hue of the stop it mixes with, and
black takes that stop's saturation too.
- `paint.editGradient` and `paint.setFill` take `method: hue-clockwise`
or `hue-counterclockwise`, in any case, with a space or an underscore
for the hyphen or without it; other spellings are an error. The native
file writes `HueClockwise` and `HueCounterclockwise`.
- The canvas and raster exports draw the gradient through stops on its
curve, within half a color level.
- Reverse turns the method the other way with the stops, and a Reflect
spread is repeated from the curve's stops, so both keep the colors.
- SVG and PDF write the gradient as an RGB image, CMYK and spot color
stops included, with the spot warning. EPS writes a shading in process
colors with a warning for spot stops. A legacy .ai file and the .ai
editing data get the colors between the stops as stops of their own,
in process colors, at most 256, with a warning. CSS gets those stops
too.
- The `method` error names the four values, in every catalog.
- The Method menu is as wide as its longest name in the interface
language, within the panel.
Docs: docs/mcp.md, docs/plugins.md, docs/target-app-parity.md, ROADMAP.
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>
An embedded image whose appearance is one Warp effect, with no opacity
mask, is written to the editing data with an art style of that Warp:
its bent pieces as its drawn look and the image itself on `%_` lines,
as other styled objects are written. The reader takes such an image
with its Warp live when the live warp draws as the drawn look, using
the comparison and budget it uses for warped type; otherwise the drawn
look stays, with the drawn-look note. A linked image with a Warp is
written as its drawn art, as before, without its live Warp. Images with
an opacity mask and images with another effect keep their drawn art.
An embedded image whose samples are on `%_` lines is now read as the
hidden copy it is. Before, it came in as a second visible object beside
its drawn look.
When the layers with their live envelopes didn't draw as the page, and
didn't with the drawn art of every live envelope put back either, every
envelope of type got its drawn art back wherever the layers were still
used: the editing data trusted, the objects that differ coming in as
the page's art, or a .ai whose layers would open alone opening alone.
Art that differed anywhere on the page cost every envelope of type its
editable type, though putting its drawn art back changed nothing.
Now each envelope of type is weighed in the page check's own drawing,
on the pixels that it and its drawn art ink (the two drawn together on
a layer of their own, without the rest of the layers). It keeps its
drawn art and hidden type where the layers with it differ from the page
in more of those pixels than its own check allows against its drawn art
(10%, or 3% with all their neighbours), or where the envelopes that stay
live would together differ there in more of a page than half of what
the page check allows (a budget of its own, the same size as the one
their own checks share). Art differing on its pixels still holds it.
It also keeps its drawn art, as before, where the layers don't draw it
where it is and as itself, on the page or off it: under a clip or an
opacity mask, in a group with an appearance of its own, or in another
envelope, a repeat or a blend. Another envelope on no page stays live,
except on a page that holds all the art (an EPS's art box), where one
reaching past the page keeps its drawn art. Past 12 on the pages, all
keep it.
A .ai whose layers would open alone still opens alone: its envelopes of
type are settled again as they were with the page, in the same order
and with the same checks, so each that opens live draws as it was
weighed, and those the page held get their drawn art back. Where the
differing objects are to come in as the page's art and can't with
envelopes of type live (an object that holds type isn't replaced),
every envelope of type gets its drawn art back and that is tried again,
as before. Where putting back the drawn art of every live envelope
makes the layers draw as the page, it is still put back.
On 224 of our own files (used locally), envelopes of type that open
live go from 174 to 186 in 8 files, none fewer; no file changes between
its layers and its PDF part.
Part of #3179.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An image placed several times in an EPS file (the pieces of an envelope
or a Warp effect, copies of one image, a symbol's instances) wrote its
whole ASCII85 data at every placement: 200 copies for one envelope at
the default fidelity, enough for a large photo to pass the 256 MB page
limit and lose pieces.
The samples of such an image are now written once in the setup as
VCim<i>, and each placement reads them from there:
- Level 3: a ReusableStreamDecode stream, rewound with resetfile and
read through a new FlateDecode filter at each placement.
- Level 2: an array of strings of at most 49,152 compressed bytes, fed
to RunLengthDecode by a procedure.
An image placed once is written where it is drawn, byte for byte as
before. The page limit counts a repeated image's samples once, and a
colorized image is reused per ink.
A top-object envelope gives each piece of a side a share of the content
in proportion to its control polygon's length. When anchors split a
curve without changing its shape, the pieces' polygons don't divide the
side where the curve's own parameter does, so the envelope bent its
contents differently although its outline was the same: Add Anchor
Points on a top object moved the art, and a top object whose .ai mesh
saves each side's curve split into thirds drew its type up to about
half a point off its drawn art, which follows the whole curve's
parameter.
Coons::from_top_object now checks whether a side's pieces, from corner
to corner, are all pieces of one cubic. The pieces' spans of that
curve's parameter come from the handle lengths either side of each
split, the curve from the outer ends and handles, and every piece must
lie within 0.02 pt of the curve's piece over its span (each control
point). Such a side runs along that curve, by its parameter. Any other
side keeps the polygon shares, including a side only partly made of one
curve and a smooth side drawn in many pieces that isn't one curve as a
whole. Corners found from the outline can still move when anchors are
added, as before. Gradient meshes made from a path are unchanged.
Existing top-object envelopes with such sides redraw.
a_split_curve_moves_the_top_object_share_to_its_new_anchor, which
pinned the earlier behaviour, becomes a_split_curve_keeps_the_top_object_patch.
Tests: a_split_curve_keeps_the_top_object_patch,
a_top_object_joins_a_split_curve_saved_with_rounded_coordinates,
a_top_object_keeps_smoothly_joined_curves_apart,
a_top_object_side_partly_of_one_curve_keeps_the_polygon_shares and
a_top_object_side_of_many_arcs_stays_on_them (vectorcraft-doc),
envelope_top_object_keeps_its_look_when_anchors_are_added
(vectorcraft-engine) and
a_top_object_whose_top_is_one_curve_split_in_thirds_opens_live
(vectorcraft-eps).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A PDF-compatible .ai saved with Include Editing Data for Other Apps now
writes warp, mesh and top-object envelopes as live envelope records in
its editing copy: the grid or contents, Envelope Options, Fidelity,
Edit Contents, a warp's settings and the contents' box, beside the
distorted art, in the form the .ai envelope reader reads. The file
opens with the same live envelopes.
Envelopes the reader would open in another shape or with other
settings are written as their distorted art with a warning: type in
the contents, a turned frame, an old warp map, a mesh laid out another
way round, a top object whose sides do not pair up, a Fidelity that is
not a whole number, bounds margins kept with Distort Appearance off,
envelopes in opacity masks, pattern tiles, clipped art or another
envelope's contents, on a large canvas, and after the first 256.
A gradient instance in a one-paint style marked both the fill and the
stroke as not read, so a style with a solid fill and a stroke gradient
was left out even when its `Xd` turns on only the fill. The instance's
flag (`1 Bb` for the stroke) now says which side the gradient paints, as
the object reader reads it; a gradient outside an instance still marks
both.
A one-paint style took a gradient (`Bg`) after a solid stroke color as
the solid stroke, and left out a style whose solid fill color came after
a pattern or a gradient. Each later paint operator now replaces the one
before it on its own side, and a gradient marks both sides as not read,
since its operands do not say which one it paints.
The Paragraph panel's Mojikumi Set lists None, YakumonoHankaku,
GyomatsuYakumonoHankaku, GyomatsuYakumonoZenkaku and YakumonoZenkaku,
then Line-end Punctuation Half Width, and text.setFormat {mojikumi}
takes yakumonoHankaku, gyomatsuYakumonoHankaku, gyomatsuYakumonoZenkaku
and yakumonoZenkaku besides none and lineEndHalf.
The spacing of each set is VectorCraft's own rule, built on the help
page's one-line description of each set:
- YakumonoHankaku sets every bracket, comma and full stop half width,
takes a quarter em off each side of a middle dot, and puts a quarter
em between Japanese and Latin letters or digits.
- GyomatsuYakumonoHankaku keeps punctuation full width inside a line,
sets an opening bracket starting a line flush and a closing mark
ending it half width, and takes the quarter em after a middle dot
ending a line off.
- GyomatsuYakumonoZenkaku keeps punctuation full width and adds the
quarter em between Japanese and Latin text.
- YakumonoZenkaku sets characters as None does.
Both composers and vertical type measure each line as it is set. A
right-to-left paragraph, whose line ends are measured untrimmed, also
draws them untrimmed, and a right-to-left line that ends between a
Japanese and a Latin character is measured without the quarter em the
set adds there. With a Desired glyph scaling other than 100%, the
space a set takes off before an opening bracket or middle dot, and the
Japanese-Latin space recorded at a line's end, scale with the glyph.
The sets save with the document and paragraph styles; new type and
Reset Panel keep Line-end Punctuation Half Width. SVG export lays type
with middle dots out again for its positioned spans. A .ai save
reports a paragraph with one of the new sets as left out.
Docs: docs/mcp.md, docs/target-app-parity.md, docs/type-parity.md,
docs/gaps.md.
A 9-slice guide can now be placed outside the symbol's art, by dragging
it past the art's edge in symbol editing mode, by symbol.options
nineSliceGuides, or by a Symbol Options field below 0% or above 100%.
A scaled instance then scales by the cells its art falls in.
- Each guide is a fraction from its edge in -16..16, and the two guides
of an axis keep their order (left + right < 1, top + bottom < 1).
- A guide past an edge cuts the art at that edge, so that side's outer
cell holds no art. With both guides of an axis past opposite edges,
the art scales as a whole along it. With both past the same edge, the
art keeps its size in the outer cell at the other edge.
- Symbol Options shows the guides with three decimals, and OK keeps a
field that was not changed at its exact value.
- Native files keep the guides exactly. SVG and .ai write such an
instance as its placed art, without guides.
Docs: docs/mcp.md, docs/target-app-parity.md, docs/gaps.md.
A style written as one paint (`/SimpleStyle` with its paint operators)
was read as a style with no fill and no stroke. A graphic style library
listed it, and applying it cleared the object's fill and stroke.
Such a style now reads as the fill and stroke its `stroke fill Xd`
turns on, with the stroke's width, caps, joins, miter limit and dashes,
and its `Xy` transparency. A style whose paint is not read here (a
gradient or pattern fill, a pattern stroke, no `Xd`) is left out of the
library instead of coming in empty.
Part of #3349.
- The link watch and links.updateChanged look at a DCS file's plate files
of their own, so a changed plate file is read again.
- links.update and links.relink apply the file's clipping path to the
images in symbols, graph designs, patterns and masks too. When the new
file has no clipping path, a clip group holding the old file's clipping
path loses that path and stays as a plain group of the image, keeping
its id, name, visibility and transparency. The image keeps that path with its plates to tell it from a
clipping mask made in the document; a clipping path given another fill
rule in the document stays. An even-odd clipping path in the file is
kept as the same area under the nonzero rule.
- links.update and links.relink return the swatch warnings file.place
gives, including two files that add one ink in two colors.
- links.relink with copy fails for a DCS file with plate files of its own.
- file.place answers the image id of a clipped DCS placement as image,
and checks page and crop for a DCS file as it does for an EPS file.
- Print separations and Separations Preview take a spot plate's color
from the document's swatch of its ink, and a reduced image's plates
keep their coverage.
- Show DCS Transparency Interactions counts a transparent DCS image and
characters filled or stroked with a transparent gradient.
- %%PlateFile names keep their spaces, and header bytes that are not
UTF-8 are read as Latin-1.
- A plate program that draws more than one image, art besides its image,
or its image through a clipping path is an error naming the plate
instead of printing the wrong ink.
- Nested clipping paths around the composite clip the image together.
- A kept image with more plates than are read, or more TIFF directories
than its composite and plates, is the CMYK image of its composite
only, not a set of plates cut short.
- swatch.unused counts a spot or process swatch as used only while an
image the art shows has a spot plate of its name.
Type read from a text document's font record was set in whichever installed
face first answered to the record's PostScript name (ignoring case and
punctuation), and its family was then swapped for any available family
spelled alike but for case and punctuation (`FontDb::find_family`): a record
naming `SampleInk`, the PostScript name of `Sample/Ink`, set its type in
`SAMPLE INK`. Copies of one family and style under other PostScript names or
versions were not told apart either, so the type could set at other widths
than the file's own page.
- `named_font` / `document_font` (crates/eps/src/import/text.rs) give the
family and style of the installed face of exactly that PostScript name,
else one that differs only in case or punctuation, and name its version
when the family and style alone resolve to another installed copy.
- The font record's key 5, its version string (name ID 5), is read and picks
that version among the faces of the name. Each record is resolved once.
- `FontDb::postscript_faces` lists every installed face a PostScript name
answers to, loading their families; the import prefers among them one of
exactly that name.
- Type a PostScript program sets (EPS import) and legacy .ai type take the
face of their font's name the same way.
- The .ai save writes a static font's version (`/5`) when its PostScript name
alone would give the reader another installed copy (another version, or the
same name in another family), keeps the version in the style it compares,
and checks that the type comes back in the same family, style and face,
taking the record's version as the reader does (up to 256 bytes), so such
type round-trips without a note.
Measured on 224 users' files (used locally, never committed): 16 files set
some type in another face; 12 render differently against their own page,
10 closer and 2 further by 0.03% of the page; envelopes of type that open
live go from 154 to 159, none fewer in any file; no file opens another way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The three envelope ink tests set "Type" in the default family and
unwrapped ink_bounds(). Without bundled fonts, craft-fonts or system
fonts (vectorcraft-effects' tests on a bare Linux host), no face draws
and the effects test panicked at the unwrap.
Each test now adds the Heighttest Sans face built from the bundled
Source Sans 3 (test-fonts feature, now also on vectorcraft-effects'
dev-dependency), sets the text in it, and computes the expected box
from the layout's glyph outlines. It checks that envelope_ink gives
that box as well as the before and after registration boxes, so a
change to the registered measure fails the test.