path.freehand takes redraw: {id, within}: the stroke's ends are placed on the path, anchors added there, and the section between them replaced by the fitted stroke, the shorter way round a closed subpath; a stroke that doesn't end on the path still draws a new one. The Pencil and Paintbrush send it for a stroke that starts on a selected path's outline away from its ends, within Edit Selected Paths' distance.
style_weight reads W0–W9 style names (Hiragino, Morisawa…) as their weight classes before the English words, so SVG and CSS export write font-weight 600 for W6, and nearest-style matching weighs them too.
A plug-in's dialog (Effect › Plug-ins, Object › Plug-ins) lays out each
parameter of its manifest as a row, and a manifest may declare up to 64.
With many of them the dialog ran off the top and the bottom of the
window, its heading and its OK and Cancel buttons off screen. A plug-in
of 64 number parameters got a dialog 2,329 px tall, its top at y = -755
in a 900 px window.
modal::scrolled_rows() puts rows in a vertical scroll area as tall as
the screen less the rest of the modal window and its edge gaps.
modal::show_at() measures that rest once it has drawn the window, for
the next frame, and asks for that frame at once if the rest changed.
The dialog then fits without waiting for input. schema_fields() puts the
parameter grid in it, for filters and effects alike. A dialog of a few
parameters is as tall as before.
a_plug_in_dialog_of_many_settings_fits_the_window opens an effect's and
a filter's dialog of 64 parameters in a 1280 × 900 window, and one of 4.
Before the change it failed with "effect: the dialog is at
[[457.0 -755.0] - [823.0 1574.0]] in the [1280.0 900.0] window".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
While an export runs in the background (Background Export), a dialog shows
what is being exported and how far it has got: artboard by artboard for a
raster export of several artboards, busy otherwise. Cancel (also Esc) stops
it and writes no file; Continue in Background leaves it to the status bar.
It opens after 0.4 s and never over another dialog.
Built on the shared vectorcraft_doc::cancel (#2546): fileio::progress::Progress
wraps its CancelToken and a progress sink, so a cancelled export stops
drawing and keeps no half-drawn pattern tile or effect raster.
- ui: background jobs carry their Progress; export_progress draws the
dialog; UI commands file.cancelExport and file.exportInBackground.
- i18n: the strings in every language with complete menus, Danish, Dutch
and Indonesian included; "{n} more exports waiting" is a plural (trn).
- docs: ui-parity, gaps (G12) and ROADMAP rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dt49z1ubK9x1bPq51APgz3
The Paragraph panel menu's Justification… was a disabled stub, and justified
lines stretched word spaces between fixed limits (80% to 150%).
Each paragraph now has Justification options: Word Spacing, Letter Spacing
and Glyph Scaling (Minimum, Desired, Maximum, in percent), Auto Leading and
Single Word Justification. Desired applies to every line; a justified line
fills its measure with word spacing first, then letter spacing, then glyph
scaling, each within its range, and past them its word spaces take the
rest. The Every-line Composer scores lines by that flexibility. Auto
Leading sets the leading of characters whose leading is Auto. A justified
line holding one word is spread between its letters (Full Justify) or
aligned left, centre or right.
The defaults are the documented ones (80/100/133, 0/0/0, 100/100/100, 120,
Full Justify), saved only when they differ. Because the default maximum
word spacing is 133%, justified paragraphs break differently than before;
the Every-line Composer's second pass is now unbounded for justified text
(it used to give up past four times the stretch and fall back to the
Single-line Composer, which the narrower default range would make common),
and lines that can't stretch at all are still refused.
text.setFormat takes wordSpacing, letterSpacing, glyphScaling ([min,
desired, max] or {min?, desired?, max?}), autoLeading and
singleWordJustification; Minimum ≤ Desired ≤ Maximum must hold or nothing
changes. Paragraph styles carry the options (New and Redefine capture
them), and Reset Panel resets them. Justification… opens a dialog
(ui.justification) with Preview; OK is disabled while a row is out of
order. Live SVG text places characters the options move, and a glyph that
Glyph Scaling scales is stretched to its width; PDF uses the glyphs'
transforms. Vertical type isn't glyph scaled; type on a path doesn't use
the options.
Docs: docs/mcp.md, docs/type-parity.md, docs/gaps.md.
Closes#2755.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Evidence list in docs/target-app-parity.md had two or three
versions of several items.
Seven items now have one line each: the Actions panel, Editing type,
Releasing ruler guides, the Layers panel, choosing the Artboard tool,
Image Trace Flat Logo mode and Create Strokes. Each kept line holds
every number, path and pull request reference of the lines it
replaces. The Layers panel line gains the #2260 note on layers dropped
under the last row. The Create Strokes line keeps the newer of its two
statements, which no longer lists the skeleton graph among the
remaining work.
Open pull requests edit the lines of seven other items, or the lines
beside them (Preferences that act, Initial live Revolve, Live effects
with previewing dialogs, Type, Masks and clipping, SVG/SVGZ, and EPS
and DXF), and those items are unchanged.
The blend, envelope and gradient mesh commands read their optional
numbers with f64_or or as_f64, which fall back to a default for a value
that is not a JSON number. object.blend.make {"steps": "8"} made a
Smooth Color blend and reported success. Blend Options did the same
with steps, distance and value. Make with Warp, Reset with Warp and
Envelope Options did the same with bend, h and v (and their long
names). Make with Mesh, Reset with Mesh and Create Gradient Mesh did
the same with rows, cols and columns. Envelope Options did the same
with fidelity, Create Gradient Mesh did the same with highlight, and
Set Mesh Point Color did the same with opacity.
These commands now fail with "`<key>` must be a number, not <value>"
for a value that is not a number or null, as the transform commands
do, and change nothing. Envelope Options refuses the warp keys whether
or not a warp envelope is selected. Create Gradient Mesh refuses an
`at` that is not exactly two numbers with "`at` must be [x, y]".
Before, it took the first two entries of a longer array, and for any
other value it made a mesh with no lines through the point.
Docs: docs/mcp.md, docs/target-app-parity.md.
Dragging a user slice's edge or corner handle with the Slice Selection
tool never snapped, while drawing a slice with the Slice tool snaps to
the grid, to whole pixels, to the art and to the ruler guides.
The handle drag now gathers its targets when the handle is pressed and
snaps what the handle moves through PointSnap::snap_scale, as the
Selection tool's bounding-box handles do: to the nearest gridline with
Snap to Grid, to whole pixels with Snap to Pixel, to the art and the
artboards with Smart Guides, and with Smart Guides off to the art's
anchors and the ruler guides with Snap to Point. Then a shown, unlocked
ruler guide within 5 px moves the side or corner onto it, as the Slice
tool moves a drawn slice's corner onto a guide. Shift still keeps the
proportions, and Alt still resizes about the center.
Docs: docs/target-app-parity.md.
A move that Shift kept level, upright or on a diagonal left that line
when it snapped. With View › Snap to Grid on, a level Shift-drag also
moved the selection up or down onto a gridline, and Snap to Pixel,
Smart Guides and Snap to Point moved it off the line in the same way.
Each axis snapped on its own after Shift had constrained the move.
With the Selection tool, and with Group Selection and Direct Selection
moving whole objects, a Shift-constrained move now slides along its
line until it snaps. With Snap to Grid or Snap to Pixel, the move
slides along the line by the shorter of the two distances that put it
on a gridline or a whole pixel on either axis. The bounds line up with
the other objects, or space evenly with them, only on the axes the line
runs along, and the grabbed point stops where the line passes an anchor
or crosses a ruler guide. A level or upright move snaps along its own
axis only. Moves without Shift snap as before.
Docs: docs/target-app-parity.md.
PathData::bounds measured each segment by its start, end and extrema,
as kurbo's bounding_box does. For a curve with a handle at about 1e308
in size, or an infinite one, kurbo's extrema computation overflows and
drops the extreme. The box then held only the anchors. The canvas check
after an edit reads that box for the selected objects, so path.create
kept a curve with handles at 1e308.
The new segment_bounds measures a segment by its control points when
one of them has an x or y that is infinite or larger in size than
f64::MAX / 16, and by its extrema otherwise, bit for bit as before.
curve_bounds joins those boxes as kurbo does, and PathData::bounds uses
it, so such a curve's box holds the whole curve and the canvas check
refuses an edit that leaves one selected.
Docs: docs/mcp.md, docs/target-app-parity.md.
The History panel menu had no Clear History, and VectorCraft had no
command that removes the undo and redo steps. In the reference app,
Clear History in the History panel menu removes the states after the
selected state, and it removes the states before it while their
changes remain in the document.
edit.clearHistory (Clear History) removes every step to undo and to
redo in the active document and returns how many it removed. The
document and the selection stay as they are, and clearing is not an
undo step. Typing or a drag still in progress becomes a step first, as
Undo makes it one, so its change stays in the document. The command is
disabled when there is no step to clear, and it waits while an input
method composes, as Undo does.
The History panel menu has Clear History after Step Forward. It runs
the command and is disabled when there is no step to clear.
Docs: docs/mcp.md, docs/target-app-parity.md.
A .ai can keep its variables and data sets in a variable library in
its editing data. VectorCraft doesn't read that library, and such a
file opened with an empty Variables panel and no warning about it.
The reader now counts the library's variables and data sets in the
editing data's /Document dictionary, and opening the file adds a
warning: "the file's variables and data sets weren't read (2
variables, 3 data sets): the Variables panel doesn't list them". The
same warning appears when the layers can't be read and the file opens
from its PDF part. A PDF that opens from the editing data it carries
gets the warning too. A library with no variables and no data sets
gives no warning.
Docs: docs/target-app-parity.md.
The Export for Screens dialog built its Example line from its fields as
typed, so it often showed a name the export does not write. It used the
artboard's name with its spaces (Artboard 1.png where the export writes
Artboard-1.png) and the first artboard whatever the range, kept a
prefix, a suffix or a device name such as CON as typed, used the scale
text as the row folder instead of the row's own folder or its size,
kept a vector row's @2x suffix, left out the folder that Create
Sub-folders adds, and read a counter set as text as 1.
The line now comes from the export's own naming. The naming part of
document.exportForScreens moves into a Naming type that the export
uses for every file, and fileio::screen_first_file returns the path of
the first file the export writes for the given params, without encoding
or writing anything. The dialog passes it the params it exports with,
without the destination, so the line shows before a folder is chosen
and is left out while the settings choose nothing to export. The Start
at field shows the counter the export uses when it was set as text.
Docs: docs/target-app-parity.md.
The engine built the PDF options without a creation date, so the
writer dated every PDF and PDF-compatible .ai file with the time it
was written. A document created on 2020-01-01 (File › File Info
shows the date) exported as a PDF with today's date in /CreationDate
and xmp:CreateDate. SVG export with the metadata option already
writes the document's own date.
The options now carry the document's File Info created date and the
time of writing as the modification date. A document without a
creation date is dated with the time of writing, as before, so PDF/X
files still get their required date. The PDF writer writes one date
for both, so the export then sets /ModDate, xmp:ModifyDate and the
PDF/A history dates to the time of writing; /CreationDate and
xmp:CreateDate keep the document's date. The page information mark
and the embedded editing data's modification date keep the time of
writing.
Docs: docs/mcp.md (File Info), docs/target-app-parity.md.
Object > Expand and Object > Create Gradient Mesh mixed a freeform
gradient's points in display RGB and turned the result back into the
points' model with the profile-free formula. CMYK inks changed: points
of C50 M20 Y0 K10 gave mesh points of C45 M24 Y0 K22. RGB points were
stored as display sRGB, so with a working RGB space other than sRGB
the mesh showed other colors. Points colored along CMYK stops, a point
paint.freeform.splitLine added between CMYK points and a point
paint.freeform.addPoint added without a color came out as RGB.
Points that share a color model now mix in it: the C, M, Y and K
values mix as inks, and Gray, RGB and Lab components mix as they are.
Points in different models still mix in display RGB. Object > Expand
and Object > Create Gradient Mesh store the result in the working RGB
space, and in a CMYK document they store it as CMYK through the color
settings, as paint.setFill stores an RGB color. Freeform::recolor and
split_line mix with the new Color::lerp_keep_model, which
vectorcraft_doc::blend::lerp_color now calls.
Docs: docs/mcp.md, docs/target-app-parity.md.
On Linux the save dialog is the XDG desktop portal's, shown through
rfd. rfd sends no `current_filter` and reads only the file name from
the portal's answer, and the GTK file chooser keeps the typed name
when another file type is picked. With "PDF-compatible .ai" picked,
`poster` typed for a new document was saved as a native
`poster.vectorcraft`, and so was the suggested
`Untitled-1.vectorcraft` left as it was.
When a save offers two or more file types (Save As, Save a Copy, Save
as Template and the first Save of a new document), the app now calls
the portal's SaveFile itself over D-Bus. The dialog starts on the
suggested file's type, and its answer names the type picked. A name
typed without an extension, or the suggested name left as it was,
gets that type's extension. A name typed with another save format's
extension, other than the suggested file's (for Save as Template, a
template format's), is saved in that format, and any other extension
gets the picked type's added. The dialog keeps the window as its
parent on X11 and Wayland, and rfd shows the dialog when the portal
can't. The same code runs on the BSDs; Windows, macOS and the web
build keep their dialogs.
Docs: docs/target-app-parity.md.
pattern.options refused a new name that another pattern had, but not
one that a color swatch, a gradient swatch, a second swatch of the
pattern or a color group had. The rename then gave the pattern's swatch
that name, so two swatches, or a swatch and a color group, had one
name. The rename also gave the new name to every swatch that had the
old one, a color swatch included, and art linked to that color swatch
kept a link to a name no swatch had. Make and Save a Copy numbered a
pattern's or a swatch's name, but not a color group's.
pattern.options now refuses a name that a swatch or a color group has,
with "a swatch or color group named `<name>` exists". It takes a name
that only the pattern's own swatches have, such as a name Swatch
Options gave the pattern's swatch, when none of them still has the old
name. A rename gives the new name only to the pattern's swatches that
had the old one. Make and Save a Copy number a color group's name as
they number a swatch's. The new message has a row in the 17 catalogs
that translate every message (COMPLETE_MESSAGES) and in bg, da, id and
nl.
Docs: docs/mcp.md, docs/target-app-parity.md.
file.saveAsTemplate with format ait and aiVersion: "7" wrote the
legacy PostScript .ai of version 7 under the .ait name: for a
one-artboard test document, a 7,556-byte file starting %!PS-Adobe-3.0.
The web's Save as Template dialog showed the .ai Version menu for
.ait, whose Legacy (version 7) choice set aiVersion 7.
aiVersion 7 for a .ait is now an error, "aiVersion 7 writes legacy .ai
files; a .ait template is always PDF-compatible", and nothing is
written. The 17 catalogs that translate every message have a row for
it. The dialog shows no Version row for .ait, and aiVersion "pdf"
still writes the PDF-compatible template.
Docs: docs/mcp.md, docs/target-app-parity.md, docs/file-format-parity.md
(two stale rows for .ai and .ait removed).
document.export, document.serialize, document.exportSelection and
Export for Screens rows answered "PDF-compatible .ait template files
can be opened but not written" for a .ait, although
file.saveAsTemplate writes them. The CLI's convert and run --export
call document.export, so vectorcraft-cli convert in.pdf out.ait
failed with that error.
The error now reads "PDF-compatible .ait template files are written
by file.saveAsTemplate", with a row in the 17 catalogs that translate
status messages and in bg, da, id and nl. convert and run --export
write a .ait with file.saveAsTemplate, which writes every artboard
with the .ai options. convert passes --replace-lossy and its .ai flags
on, and refuses --artboard and --range for a .ait.
Docs: docs/mcp.md, docs/target-app-parity.md.
In a CMYK document, paint.setFill stores an RGB color as CMYK through
the color settings, but the other commands that take a color stored it
as RGB: text.create, text.createInPath, text.setStyle and
text.setRangeStyle colors, a shapeBuilder.merge hex fill,
paint.editGradient stops, freeform gradient points, gradient mesh
points, the Symbol Stainer's color, and swatch.new {colors},
swatch.newGroup, swatch.editGroup and swatch.edit. The Mesh tool sent
the fill as a hex value, so a CMYK fill came in as RGB.
These commands now store such a color through paint::new_color, as
paint.setFill does, and take keepModel to keep RGB (shapeBuilder.merge
inside its fill object). A color the swatch
or gradient already has keeps its model, so stops sent back with a
moved offset keep their colors (the Gradient panel's ramp and the
Gradient tool send keepModel), and a stop that names a process
swatch takes the swatch's color as it is, in paint.setFill gradients
too. Where the app copies an existing color, it keeps the color's
model: the Color panel's sliders for gradient stops and freeform
points, a swatch put on a stop or a point (the stop popover, Alt-click
in the Swatches panel, a drop on the Gradient panel's ramp), the fill
the Mesh tool gives a new point or the Symbol Stainer applies, and the
colors the Color Guide, Color Themes and Recolor Artwork save as
swatches.
swatch.new {color, mode} converted RGB into CMYK before converting it
into mode, so the New Swatch dialog in RGB mode stored #00ff00 as
R101 G176 B62 in a CMYK document. With mode, the color is now
converted once.
Docs: docs/mcp.md, docs/target-app-parity.md.
The History panel and Edit › Undo and Redo did not translate the steps
that apply or redefine a character or paragraph style, or redefine a
graphic style. The engine built their names with format! ("Apply Body",
"Redefine Body", "Redefine Graphic Style “S”"), and the panel looks a
name up as a whole, so no catalog row matched a name with a style's
name in it. A name cannot be matched against template rows instead: a
character style named Brush gives "Apply Brush", which is also the name
of the brush step.
The engine now names these steps with Session::edit_named, and each
such step keeps its template and the style's name
(HistoryEntry::named) through undo and redo. The History panel and
Edit › Undo and Redo show the template's row with the name in it
(panels::history::entry_name). The name stays as the document holds
it, and the Normal styles' names are shown in the interface language.
Every complete catalog has rows for Apply {name} and Redefine {name},
and Redefine Graphic Style “{name}” had one.
every_undo_label_is_translated also requires a row for each template,
and the only step name it allows to be built with format! is the
Liquify tools' "{} Tool".
Docs: docs/target-app-parity.md.
A marquee measured an Intertwine group and a repeat by their bounding
box. A marquee in the gap between two members of an Intertwine group,
or between two copies of a repeat, selected the object, although a
click there misses and a plain group needs a marquee to meet one of its
members. Direct and Group Selection's marquees also selected the
Intertwine group whole beside the anchor points of its members.
The marquee now touches an Intertwine group by its members, as it
touches a plain group, and it touches a repeat by the copies the repeat
draws, as clicks hit them. Direct and Group Selection's marquees select
the anchor points of an Intertwine group's members, and Group
Selection's E marquee selects the members inside it. Blends, and
envelopes outside Edit Contents, keep their box, as clicks on them do.
Docs: docs/target-app-parity.md.
A Shaper Group keeps its original shapes as its first child, at no
opacity, and clicks do not pick them outside Construction mode. The
Lasso, the Add Anchor Point, Delete Anchor Point, Anchor Point and
Scissors tools still picked their anchor points and segments, also
where the group erased the face, and Smart Guides, Snap to Point and
the transform tools' reference point snapped to them. The Lasso and
the anchor tools also picked art outside the isolated group or object
in isolation mode.
The Lasso and the anchor tools now visit only the art a click can pick:
the isolated object in isolation mode, and a Shaper Group without its
original shapes unless they are isolated (Construction mode). Smart
Guides and Snap to Point leave the original shapes out of their
targets, and Spacing Guides leave out their bounds when a face moves.
The transform tools' reference point snaps to an original shape only
when that shape is selected, or under the pointer in Construction
mode. The Shaper Group test document moves into the shared test
helpers.
Docs: docs/shaper.md, docs/target-app-parity.md.
The Appearance panel's inline effect editor still showed eight choice
parameters as text fields holding the stored value: Revolve's edge,
Extrude & Bevel's bevel, every Warp style's orientation, Rasterize's
Color Model, Background and Anti-aliasing, and the channel of Levels
and Curves. Their dialogs built these lists in their own bodies, so
effect::choices did not hold them. Nothing checked a channel's value:
a typo was stored, and Levels or Curves drew it as RGB.
effect::own_choices now holds these lists, each label as its dialog
passes it to its dropdown or radio buttons. The dialogs read the lists
from it (Rasterize's Anti-aliasing from the AntiAlias list it is built
from), and the inline editor shows each one as a dropdown with the
dialog's translated labels; Rasterize's Color Model reads CMYK or RGB
by the document's mode. effect.apply and effect.setParams refuse a
Levels or Curves channel other than rgb, red, green or blue (r, g, b
and any letter case are read too) with "`channel` must be one of rgb,
red, green, blue", and the document and its undo history stay as they
were.
Docs: docs/mcp.md (effect params), docs/target-app-parity.md.
The color and channel sliders gave screen readers a name but no value.
Each caller maps its own range onto the thumb's position, so a screen
reader announced the Color panel's G slider, Transform Each's Scale or
the Navigator's Zoom without the setting the field beside it shows.
color_slider_valued and channel_slider_valued take an optional
SliderValue: the number and text of the field beside the slider and
the slider's range. The accessibility node gets the number, the text
and the range, which extends to a number past the slider's end.
color_slider_named and channel_slider_named keep their arguments and
report no value. Every caller outside the tests uses the new variants:
the Color panel, Swatch Options, the Color Picker, the Navigator, the
Character panel's variable font axes and the dialog sliders.
form::slider_rail_named reports the number it edits, and the harmony
wheel's Brightness slider, which has no field, reports its brightness
in percent.
Docs: docs/target-app-parity.md.
Shift+Enter (Shift+Return) in the Pattern Options panel's Name field and
in the name fields that a double-click opens in the Layers, Artboards
and Asset Export panels ended the editing, as Enter does. These fields
draw their own text box and do not use the shared field widgets that
#2431 changed.
- Each of them now calls widgets::own_field_shift_enter, built on
#2431's shift_enter_keeps_focus and after_shift_enter. Shift+Enter
applies the name as Enter does and the field keeps the focus. In the
next frame the field shows the name as applied, all selected, so
typing replaces it.
- Enter, Escape and a click elsewhere work as before.
Docs: docs/target-app-parity.md.
With Direct Selection, the Pen or the anchor tools, a group, compound
path or clip group selected whole drew the anchors of its paths hollow,
the look of unselected anchors, while a path selected whole drew its
anchors filled. After two Group Selection clicks selected a path and
then its group, the group's hollow anchors covered the path's filled
ones.
With these tools, the selection overlay now draws the anchors of the
paths inside a group, compound path or clip group selected whole
filled, as it draws the anchors of a path selected whole. Direction
handles still show only on a path that is selected itself, and Direct
Selection drags only those.
Docs: docs/target-app-parity.md.
In type that holds right-to-left characters, Left and Right move the
caret through the visual caret stops (`caret_horizontal`). A cluster
drawn with no advance, such as a zero width non-joiner that the font
shapes as a glyph of its own, puts two stops at one x. A step kept the
stop with the lower byte, and the next step passed over the other one,
so the arrow keys never reached it. In Persian میخواهم laid out on
macOS, the caret could not be put between the non-joiner and خ with
the arrow keys.
Stops at one x are now visited one at a time in byte order. Going
right, the caret takes them in increasing order on left-to-right
glyphs and in decreasing order on right-to-left glyphs; going left,
it takes them in the reverse order. The end of a soft-wrapped line,
which the caret shows on the next line, is not one of those stops.
Where no two stops share an x, a step goes where it went before.
Repeat Tab on a paragraph's first tab stop spaced the copies by the
stop's distance from the ruler's 0. The reference app measures that
distance from the paragraph's left indent. With a left indent of 20 pt
and one stop at 50 pt, the copies were 50 pt apart instead of 30.
Repeat Tab now spaces the copies of the first stop by its distance
from the left indent of the paragraph whose stops the Tabs panel
shows: the paragraph the caret is in while the Type tool edits text,
otherwise the first paragraph of the first selected type object. A
stop left of the indent repeats at its distance from the indent: a
stop at 5 pt in a paragraph indented 20 pt repeats every 15 pt. The
reference app's help page does not cover this case, so this follows
VectorCraft's own rule. The copies of a later stop are still spaced by
its distance from the stop before it, and the limits of 20 copies and
2,000 pt stay.
Docs: docs/target-app-parity.md.
With crop "bounding", file.place measured the placed art by each
object's visual bounds, which include the hidden members of a group.
A group holding a 40 pt square and a hidden 170 x 42 pt rectangle was
measured as 251 x 123 pt: at, rect, the place cursor and
file.place.info all used that box. A group whose only part on the page
or artboard was a hidden member was also placed, and symbol instances
were measured as a 20 pt square.
The art's box now follows the rule exports use
(vectorcraft_render::encode::drawn_bounds, made public): hidden
objects, guides and template layers do not count; the reach of effects
such as a drop shadow now counts; symbol instances are measured by
their art. The same rule picks the objects on the page or artboard.
Hidden objects on the page or artboard are still placed, and stay
hidden. Replace and the width and height that file.place answers also
measure symbol instances by their art.
Docs: docs/mcp.md (Place), docs/target-app-parity.md.
When the PDF writer keeps a PNG, GIF or WebP image as it is, it accepts
the image once its header reads, and it reports pixel data that it
can't decode only when it finishes the file. An image whose pixel data
was cut short or damaged therefore failed the whole export with "PDF
writer error: Image(...)", and nothing was written. This happened in
Save as PDF, .ai saves, Save a Copy, Print, Export for Screens and
native files with PDF compatibility, and with On Copy: PDF turned on,
Copy left the PDF off the clipboard.
When the writer fails on an image's pixel data, the export now runs
again with that image left out. Its warnings hold the note that raster
exports give, once: "an image couldn't be decoded and is missing from
the picture". This note replaces the writer's "image '<key>' could not
be decoded and was skipped", which named the image by an internal key.
An export without such an image runs once, as before. A .ai file keeps
the image in its editing data and reopens with it.
Docs: docs/mcp.md (PDF options).
Saving a version 7 .ai expands raster effects into images and flattens
images with see-through pixels, opacity or a blending mode, and both
draw the art with the renderer. An image the renderer couldn't decode
drew nothing, so it was missing from the result, and the reply carried
no note. A semi-transparent image cut short was removed without one.
The save now compares decode_failures() before and after it writes the
artboards, as Expand Appearance and the raster exports do, and adds
"an image couldn't be decoded and is missing from the picture" to its
warnings once.
Docs: docs/mcp.md (aiVersion "7").
When a .ai or EPS file opens from its editing data, the import left
out the page's type that no text object owns whenever the regions of
the text objects made from the text document covered at least half of
it, without comparing letters. The region of point type reaches its
longest line's length in characters times its size, plus its size, to
the left and right, and one and a half times its size above and below.
A 21-character line at 10 pt reaches 220 pt to each side. The page's
other type inside that box was lost.
Now the region of a made text object counts toward that half only
when its story has the Latin letters and digits of one of the type's
pieces, compared without case, and only when such stories between
them have the letters of every piece. Only those text objects count
as drawn by the page. The check that decides whether the text
document is trusted still counts every region.
Docs: docs/target-app-parity.md.
When the editing data of an EPS or .ai file was for a page of another
size than the file's own page, the file opened as its page or its PDF
part with the reason "its editing data is for a different page". The
reason did not say which page the editing data was for, or that it was
the same page at another scale.
The reason now gives both sizes in points: "its editing data is for a
600 × 400 pt page, not the file's 612 × 792 pt page". When the two
pages are in proportion (within the 0.5 pt per side of the check), it
gives the scale of the editing data's page to the file's instead:
"its editing data is for a 100 × 100 pt page, the file's
1000 × 1000 pt page at 1:10". Every catalog that had the old reason
has rows for both new ones.
Docs: docs/mcp.md, docs/target-app-parity.md.
A PDF-compatible .ai, or a PDF saved with the editing data, whose
editing data can't be decompressed (damaged zlib or Zstandard data, or
data that decompresses to more than 256 MB) opened from its PDF part
with no note about its layers. layered_ai returned on the
decompression error without a note. Data that decompresses to layers
that can't be read gets one, and so does an EPS.
layered_ai now gives the same note with the decompression error as
its reason: "the file's layers weren't read from its editing data (its
zlib compression is damaged): they come from its PDF part". The note
counts as a loss, so document.export or Save As over a PDF saved with
the editing data asks first (acknowledgeLoss), as it already did for
a .ai. The three reasons have rows in the 17 catalogs that translate
every message.
Docs: docs/mcp.md, docs/target-app-parity.md.
When a PDF, PDF-compatible .ai or .ait opens, crates/pdf/src/file_info.rs
reads its File Info from the XMP packet with roxmltree, which parses
nested elements recursively and has no limit on their depth. A packet
with elements nested 10,000 deep (70 KB) overflowed a 1 MB stack, and
one nested 100,000 deep (700 KB) overflowed an 8 MB stack. A stack
overflow aborts the app.
File Info now counts how deep the packet's elements nest before roxmltree
parses it, and leaves out a packet nested more than 256 deep, as it
leaves out a packet that is not XML. The document information
dictionary then gives the File Info.
document.exportForWeb renders the artboard or the art and writes GIF,
JPEG, PNG-8 or PNG-24 files. When the art held an image that the
renderer could not decode, the renderer drew nothing for it, and the
files lacked the image with no message: the reply had no warnings at
all. A raster export of the same art warns that "an image couldn't be
decoded and is missing from the picture".
document.exportForWeb and document.exportForWeb.preview now compare
decode_failures before and after they render, as raster exports do,
and their replies carry that text in `warnings`. The files are still
written. Save, Download and Preview in Browser in the Save for Web
dialog show the first warning in the status bar as Save As shows a
save's notes.
Docs: docs/mcp.md (Save for Web), docs/control-protocol.md.
charStyle.new and paraStyle.new kept the name they were given as it
was, while charStyle.rename and paraStyle.rename trim the white space
around the new name. So a script could make a style " Body" beside
"Body", and a blank name was refused with the message for a name that
is taken.
New Character Style and New Paragraph Style now trim the name as rename
does, compare the trimmed name with the styles there are, and refuse a
name that is then empty with rename's message. Duplicate names the copy
from the trimmed name. symbol.new and symbol.duplicate trim the name as
symbol.options trims newName, and a blank newName gives "<name> copy"
instead of a symbol named with the blank. library.use adds a library's
text style under its trimmed name, so a library file whose style is
named with spaces around it still applies.
Docs: docs/mcp.md, docs/target-app-parity.md.
The Tabs panel menu passed false as each item's enabled argument and
its condition as the checked argument, so Clear All Tabs, Delete Tab
and Repeat Tab were always dimmed and, with a stop selected, showed a
check mark.
The three items are now enabled while the ruler shows tab stops and
show no check mark. Delete Tab and Repeat Tab act on the stop the ruler
shows selected. After Delete Tab removes the last stop, the ruler shows
the stop before it selected, and the next Delete Tab removes that one.
Clear All Tabs runs text.tabs.clear on the paragraphs the panel acts
on, so its undo step is named Clear All Tabs.
Docs: docs/target-app-parity.md.
brush.new, brush.duplicate, object.pattern.make,
object.pattern.saveCopy, artboard.new and artboard.setProps kept the
name they were given as it was, and brush.options and pattern.options
kept the white space around a new name. So a script could make a brush
" Nib" beside "Nib", and a blank name made a brush, a pattern or an
artboard named with the blank. Artboard Options sends its Name field
as typed, so clearing it named the artboard "". The Brush Options
dialog, the Pattern Options panel and the Artboards panel trim the
names they send.
The eight commands now trim the name, and the brush and pattern
commands compare the trimmed name with the names there are. In
brush.new, brush.duplicate, object.pattern.make,
object.pattern.saveCopy and artboard.new, a blank name gives the name
the command gives when there is none: "New Calligraphic Brush" and the
other default brush names, "<name> copy", "New Pattern N",
"<pattern> copy" and "Artboard N". brush.options and pattern.options
keep the name when the new one is blank, and artboard.setProps gives a
blank one the artboard's default name, Artboard N for the Nth
artboard. Duplicate Brush and Save a Copy name the copy of a brush or
pattern whose saved name has white space around it from the trimmed
name (" Old " gives "Old copy").
Docs: docs/mcp.md, docs/target-app-parity.md.
Edit › Undo and Edit › Redo translated the step's name with its plain
catalog row only. Nine of the 22 catalogs have no plain row for
Feather, only the effect's menu row with an ellipsis, so in Japanese
Edit › Undo read "Featherを取り消す" after Feather while the History
panel listed the step as ぼかし.
The menu now names the step with panels::history::step_name, as the
History panel does, and keeps the name as recorded when the catalog
has no row for it. The in-window and native menus both show the new
label.
Docs: docs/target-app-parity.md.
A bounding-box handle dragged with the Selection or Free Transform tool
snapped only with Smart Guides on, and then only to the other objects
and the artboards. View > Snap to Grid, Snap to Pixel, Snap to Point
with Smart Guides off and the ruler guides did not move it, while a
move snaps to all of them.
The corner or side a handle moves now snaps as a dragged point does.
With Snap to Grid on it lines up with the nearest gridline, and with
Snap to Pixel on it lines up with whole pixels. With Smart Guides off,
Snap to Point lands it on the other objects' anchors and on ruler
guides within its distance. With Smart Guides and Snap to Point both
on, it lines up with the ruler guides as well as with the other
objects. Shift still keeps the proportions. Without Alt the opposite
side stays put, and with Alt the box still scales about its center.
Docs: docs/target-app-parity.md.
On a Wayland session a drawing tablet's pen could not draw or click:
winit 0.30 does not bind the Wayland tablet protocol, and neither KWin
(since Plasma 6.3) nor GNOME's Mutter turns tablet input into pointer
input. Linux Display Server's X11 (XWayland) choice let the pen work as
a mouse, but it opened the window through XWayland on every start, also
without a tablet.
Linux Display Server has a third choice, X11 (XWayland) When a Pen Is
Attached (x11WithPen). It opens the window through XWayland on a
Wayland session only when /proc/bus/input/devices lists a pen as the
app starts: a device with the BTN_TOOL_PEN key and the ABS_X and ABS_Y
axes, the check PhotoCraft's tablet.rs makes. Automatic stays the
default. On Linux the Performance page shows a note for tablet pens
that don't draw.
README.md named KDE alone; the KWin commit's message says Mutter does
not emulate tablet events either, so README.md and docs/development.md
now name GNOME. The Pen pressure row of docs/hardware-parity.md said
Linux has pressure through XWayland; winit 0.30 reports none on X11
either.
Docs: README.md, docs/development.md, docs/hardware-parity.md,
docs/target-app-parity.md.
dataset.import refused every header cell that wasn't the name of a
variable the document defines. A data source file could only fill
variables made beforehand, and `#` and `@` in a header were read as part
of a variable's name.
A header cell that names no variable now defines a new, unbound
variable in the import's undo step. `#Shown` defines a visibility
variable `Shown`, `@Photo` defines an image variable `Photo`, and a
cell without a prefix defines a text variable. A prefixed cell whose
name is already a variable of the prefix's kind names it. A cell that
is a variable's exact name keeps naming it, even when the name starts
with `#`, `@` or `%`, so files that dataset.export writes import back
as before. The import fails, naming the column, on a prefixed cell
whose name is a variable of another kind and on a `%` cell that names
no variable (graph data files). While the variables are locked, a
header that would define a variable fails the import with "variables
are locked", as New Variable does. The reply's `defined` lists the new
variables.
Docs: docs/mcp.md, docs/target-app-parity.md.