A recolor of a graph (Invert and Complement on the proxy, the Edit Colors
commands, Recolor Artwork, the color filters, Apply Nearest Swatch and the
ink limit of Print Ink Fixes) stored the new paint of the series marks but
not of the labels. stroke.set, the stroke weight steps, the gradient edits,
Swap Fill and Stroke and Default Fill and Stroke stored nothing when they
changed a label's characters. When the graph was drawn again, the labels
went back to their stored style.
- These commands now store, for every label of the kind they reach, the
label's object rows when they change, and the fill, stroke, stroke weight,
caps, joins, miter limit, dashes and stroke alignment of its characters
when all of them show the change alike.
- A gradient is stored without its placement, and each label fits it to its
own box. A stroke weight and dashes are stored as a scaled label shows
them.
- When labels of one kind change differently, the frontmost one sets what
the kind keeps.
- On a pie with Legends in Wedges, the legend labels' fill is stored only
when every legend label reached ends up with the same fill, so a recolor
of the whole pie leaves them black or white by their wedge.
- Axis lines, ticks and radar rings are not stored and are drawn again with
their default paint.
Docs: docs/mcp.md, the commands' params text, docs/target-app-parity.md.
The Limited palette's median cut ranks cuts by pixel-weighted gain. A
large near-black ground with faint noise therefore took two colors less
than 0.02 apart in Oklab, and the trace drew blotches of one over the
other.
With 9 to 16 colors, a cut whose two sides both have a dark gray mean
(Oklab lightness under 0.30, chroma under 0.03) and lie less than 0.03
apart now has its gain scaled by (distance / 0.03)^2 while the cut is
chosen. The ground keeps one color, and the color it would have taken
goes to another cut. Document Library without swatches follows Limited.
Other color counts, Full Tone and Automatic choose the same cuts as
before.
A graph draws the art of a column or marker design inside a series again
from the stored design. Rows, effects and graphic styles that a command
gave that art were lost the next time the graph's data, type or size
changed. The paint commands already left the art out.
- The appearance.* row and whole-appearance commands, effect.apply,
appearance.addEffect and the effect.* commands that edit, move or
remove an effect, threeD.mapGraphic, graphicStyle.apply,
graphicStyle.addFromLibrary with apply, transparency.set with a fill
or stroke item, and appearance.copyFrom (as the target) leave out any
object inside a series group other than a path directly in it, given
by id, selected or reached through target: "contents". They edit the
other objects given, and a command whose objects are all design art
is an error.
- appearance.transfer with design art as its target, or as its source
without copy, is an error.
- A Pathfinder effect no longer groups selected design art.
- The paint commands given a design's copy with a fill or stroke item
leave it out as well.
docs/mcp.md states the rule, and the Graphs line in
docs/target-app-parity.md names it.
A PDF-compatible .ai saved with Include Editing Data for Other Apps
wrote no text wrap options, so a wrap object opened from the file no
longer wrapped type. An object with only wrap options got no
dictionary at all.
The writer now writes each wrap object's options in its dictionary as
`AI11TextWrapOffset` (`/Real`) and `AI11TextWrapInvert` (`/Bool`, 1 or
0), the entries `text_wrap` in crates/eps/src/import/ai/read/document.rs
reads, beside the object's name, variables and opacity mask.
- The offset is the one in the art's space, so on a large canvas it is
written at 1/10 and read back at its size. An offset that is not
finite is written as the default 6 pt, and one past the -1000 to
1000 pt the reader keeps as the nearer end of that range.
- Art the writer draws for an object (a blend's steps, a symbol
instance written as its art, a brushed stroke's pieces, pattern
tiles, an object's drawn look) is written without the options of the
objects it is made from. The object itself and a symbol's own art
keep theirs.
- A clipping path written as its fills and its strokes has its options
once.
Docs: docs/file-format-parity.md, docs/target-app-parity.md.
On Linux and the BSDs, craft-window reads the desktop's window-button
layout (`button-layout` in `org.gnome.desktop.wm.preferences`) into
`Frame::App` when the desktop leaves the window's frame to the app, but
the app bar ignored it and always drew Minimize, Maximize and Close at
its right end.
- `CaptionLayout` lists the buttons at each end of the bar;
`caption_buttons` draws them there in the listed order, first one
leftmost, and the bar's other controls keep clear of both groups.
- The desktop app copies the layout of an app frame the desktop left to
it. An app frame turned on by `VECTORCRAFT_TITLEBAR=app` or by
VectorCraft Title Bar on Every Desktop, a window opened through
XWayland, macOS and Windows keep all three at the right.
Docs: docs/control-protocol.md, docs/target-app-parity.md.
A graph series kept a gradient with the document coordinates it had on
the one mark it was stored from, and gave that paint unchanged to every
mark when the graph was drawn again. The other marks showed the gradient
placed over the edited mark, and the placement stayed behind when the
data or the graph moved the marks.
The graph now stores a gradient relative to the unit box of the mark it
comes from (its geometric bounds, or its stroke's box for a stroke).
Each path of the series, the legend swatch and a Filled Lines band
included, takes it on its own box. Every store route does this:
paint.setFill and paint.setStroke, the Eyedropper, graphic styles,
Appearance commands, paint.setGradientGeom and the freeform point
edits. A command that writes a mark's paint as the graph drew it leaves
the stored gradient as it is and records no undo step.
The series paints save this as fillUnitBox and strokeUnitBox. A
gradient in a file saved without these flags is drawn where it was
stored. So is a gradient stored from a path with no width or height.
Docs: docs/mcp.md, the paint.setGradientGeom params text and the Graphs
line in docs/target-app-parity.md.
it is in COMPLETE_MESSAGES, but the untranslated report listed 1,842
status and error messages with no Italian row, so with the interface in
Italian those messages showed in English.
- it.tsv gains 1,802 `@msg` rows. The translations are VectorCraft's
own, written from the English meaning with the catalog's terms, and
await native review. Placeholders, backticked names, command ids,
double-quoted values and bare true, false and null stay as the
English writes them.
- The untranslated report now lists 40 Italian messages. 38 of those
keys get Italian rows from open pull requests 7211, 7214 and 7116,
so they are left out here. Menu strings, `tl!` literals, undo labels
and swatch names are unchanged.
- The Italian row of docs/localization-parity.md gives the catalog's
current counts, and docs/target-app-parity.md has a line for the
change.
Session::replace_document on a tab other than the current one cleared
the bounds of type set in a font named like a composite font and left
that type with estimated bounds until the document came to the front.
- The replacement's type without bounds is measured at once, in the
document's own composite fonts, lent for the measuring. The front
document stays the one whose composite fonts are registered.
- When the document then comes to the front unchanged, its type in its
own composite fonts is not measured again. Type set in a composite
font only another document defines is still measured again, as
missing, and so is type set in a composite font name that another
thread's loan held with a different definition.
addon.shapes.polygon and addon.shapes.star rounded a fractional side or
point count and clamped it to 3..1000, so 2.5, 2 or 5000 drew a shape
with a different count than asked for. addon.shapes.configure read
sides only as an unsigned integer, ignored a fraction and cut a large
value to 32 bits before clamping it, so 4294967299 gave a triangle.
The commands now refuse a supplied value outside its range with an
error that names the parameter and the range, and draw or change
nothing; an omitted or null value still takes its default:
- sides and points: a whole number from 3 to 1000
- rounded and randomized: -10 to 10; spokeRatio: 0.01 to 1
- turns: 0.01 to 1024; divergence: 0 to 1000; innerRadius: 0 to 0.999
- width, height, radius and radius1: 0 or more (a negative size was
made positive before, and a negative corner radius became 0)
- radius2: 0.01 to 1 times radius1 (the ratio was clamped before)
- radii: four numbers, each 0 or more (a malformed array was ignored)
- arcMode: whole, pie (slice), chord or arc (open); any other value
drew a whole shape before
addon.shapes.configure checks every value before it changes any of the
selected shapes, keeps fitting a corner radius to half each rectangle's
shorter side, and records no undo step when no shape changes.
Docs: docs/mcp.md (3D Box and shapes add-on), docs/target-app-parity.md.
The German catalog lacked a row for 1,842 of the status and error
messages that the untranslated report finds in the sources, so the
status bar showed those messages in English.
- de.tsv gains 1,842 `@msg` rows: the errors of commands and their
parameter checks, scripts, file import and export, printing and
plug-ins. The translations are VectorCraft's own, written from the
English meaning with the catalog's conventions and its existing
feature names. Placeholders, parameter names, command ids and
literal values stay as the English writes them.
- The untranslated report now lists no German messages. Menu strings,
`tl!` literals, undo labels and swatch names are unchanged.
- Two of the messages have two placeholders in a row. The catalog does
not load such a row, so those two are still shown in English.
- The German row of docs/localization-parity.md gives the catalog's
current message counts.
Docs: docs/target-app-parity.md, docs/localization-parity.md.
A legacy .ai graph (%_Gs ... %_GS) opened as plain art, with its table,
type, axes and settings left out. A grouped column, stacked column, line
or area graph now keeps them on its group when every record has the
shape and values the version 7 specification's appendix A gives, the
table holds at least one number, the drawn art strokes the plot's left
and bottom edges, and each series group is tagged once as a whole
column, holds only paths and paints them with one fill and one stroke.
The saved art stays as it is on open; Graph Data redraws the graph from
its data afterward, with each series' fill and stroke kept.
Any other graph (pie and scatter graphs, designs, customizations, record
shapes of later versions, tables past 10,000 categories, 256 series or
2,600,000 cells) keeps its art as plain objects with the existing note.
Saving a graph to .ai, in either form, now warns that its data and
settings are not written.
fr is in COMPLETE_MESSAGES, but the untranslated report listed 1,842
status and error messages with no French row, so with the interface in
French those messages showed in English.
- fr.tsv gains 1,842 `@msg` rows in one block after the last row. The
translations are VectorCraft's own, written from the English meaning
with the catalog's terms and typography (no-break space before
: ; ? ! and inside « »). Placeholders, backticked names, command ids,
double-quoted values and bare true, false and null stay as the
English writes them.
- The untranslated report now lists no French messages. Menu strings,
`tl!` literals, undo labels and swatch names are unchanged.
- The French row of docs/localization-parity.md gives the catalog's
current counts.
Right-to-left type on a path that overflows searched for the start of its
text that fits with a binary search, which assumed that a longer start
never fits once a shorter one does not. A letter tracked to a negative
advance breaks that: with steps of 6, 6, -3 and 6 points in a 10 point
span, the first three letters fit while the first two do not, and the
search showed only the first letter.
- Without hanging punctuation, and with no spacing or on a path of one
straight segment, every start is measured in O(n log n): each glyph
keeps its place in the whole text's visual order, except the trailing
whitespace and controls that UAX #9 rule L1 sets to the paragraph's
level.
- With hanging punctuation, or spacing on a curve, when a letter has a
negative advance, a start is a candidate only when its glyphs, in its
own visual order and each at the least step it can take, reach no
farther than the path's length. This lower bound holds with negative
advances.
- At most 32 candidates are arranged from the longest. When none of
them fits, a binary search over the rest shows a start that fits,
which is not always the longest.
- With hanging punctuation, or spacing on a curve, text with no negative
advance keeps the binary search.
Right-aligned and centered type on a curve with Spacing was placed using
the width its glyphs have at the start bracket, while the steps were
measured where the line was placed, so a line that fits could end past
the end bracket and lose glyphs. It is now placed using the width of the
steps it has where it is placed.
A path directly in a graph series lost what these commands gave it when
the graph was drawn again, for example after graph.setData:
- While Global Edit is on in an isolated graph, a match that is a series
path, or a series group whose paths take the edit, now has what
changed on its paths stored for their series: the paint, weight,
opacity and blend mode of the topmost visible fill and stroke, and the
object's opacity and blend mode. The edited match is stored by its own
command as before.
- addon.arrange.randomizeOpacity and addon.arrange.sort with apply
"opacity" now store the object opacity, with the blend mode, of the
series paths whose opacity they change. When they change several
paths of one series, the frontmost of them sets what the graph
stores, as for the other commands.
The graph stores them in the same undo step as the edit. The params
text of addon.arrange.randomizeOpacity and addon.arrange.sort,
docs/mcp.md and docs/target-app-parity.md describe the new behavior.
The check after each edit that the selected objects stay within the
canvas measured the whole of every selected path. Moving one anchor of
a 10,000-anchor path measured all 9,999 segments. A path or compound
path whose anchors and handles are all finite and inside the canvas
range now passes without being measured, because its curves stay inside
their control points. A path with a point outside that range is still
measured, so the check accepts and refuses the same edits as before.
path.moveAnchor to the point the anchor is already at, and
path.setHandle to the point the handle is already at, now leave the
document as it is and record no undo step.
vectorcraft_geom::path::measured_segments counts the segments that
PathData::bounds measures, and the new engine tests use it.