- object.expand.info reports no gradient or fill to expand for a
gradient that repeats in more runs across its box than are written,
matching object.expand, which leaves it a gradient. For a path that a
painting live effect draws, it measures each piece the effect returns.
- CSS writes a repeating gradient's stop positions, and a repeating
radial gradient's radius, with enough decimals that their rounding
adds up to at most a hundredth of a period across the box (at least
four for positions and three for the radius).
- docs/mcp.md says that sharp steps and swatch-linked stops are the last
to be left out rather than always kept, and that the CSS conic limit
counts stops before midpoints and Perceptual curves add more.
When a repeated or reflected gradient needs more stops than a limit
allows, each run now leaves out the stops that change its colors
least, so every run is still written, instead of writing fewer runs
and padding the rest with the last color. Each run keeps its ends,
sharp steps, swatch-linked stops, tint and CMYK ink changes and spans
shaped by an off-center midpoint, and the colors are compared as the
gradient's method mixes them. This applies to gradients along or across
a stroke, CSS conic gradients, and PDF, EPS and .ai export.
- Outline Stroke's mesh fits the gradient to at most 1,024 stops, and
splits a stroke into several meshes when one would hold more than
2^20 points.
- Object > Expand leaves a gradient as it is when the box needs more
than 256 runs, instead of expanding only the first runs.
- CSS writes the stop positions of repeating gradients with four
decimals, so the period stays in place across the box.
- A swatch chip of a gradient that repeats gets at least four grid
cells a run along each side (at most one a point, and 128), so it
does not show one color when the count matches the chip's cells.
- docs/mcp.md states the stop limits and the PDF import sample budget.
SVG Options gets an Include Variable Data checkbox (the `variableData`
option, off by default). With it on, the saved SVG carries the document's
text variables, its data sets' text values and each text variable's bound
type object as a `variableSets` element, in the layout Load Variable
Library already reads. A bound object is named by its element id in a
`docRef="id('...')"` attribute; an object without an id gets one.
Opening such an SVG reads the variable data back: the text variables, the
data sets (none active) and the bindings, so choosing a data set changes
the type again. Variable data that Load Variable Library would refuse is
left out with a warning, and the art opens. Markup that only looks like
the element (in comments, CDATA sections, processing instructions or the
document type declaration) is skipped.
Variables of other kinds, names XML can't hold, more than 256 text
variables or 1,000 data sets, and variable data over 8 MiB or 2,000,000
XML nodes are left out of the file, each with a warning. Type written as
outlines opens as art, so its variables are written unbound, with a
warning.
Each writer marks its notes as a loss (something of the document that the
file leaves out or that reaches it changed) or as a note on the options
(an option it didn't apply or applied another way while the art reaches
the file as it looks, or something an option turns off). The PDF, SVG,
EPS, legacy .ai, DXF and HP-GL writers return their notes on the options
in a list of their own (`setting_notes`) beside `warnings`, and the
engine's `Encoded` does the same.
The replies of the save and export commands, and of
`clipboard.exportPdf` and `clipboard.exportPng`, list the losses first,
each kind in the order the writers gave them, across every file that
Export for Screens writes. The status bar names the first note, so an
export to lossy WebP of type in a font that isn't installed now names the
fallback-font note instead of the lossy WebP note. A print to a file
keeps the order its notes are given in.
A linear, radial, conic or diamond gradient has a spread: Pad (the
default, as before), Repeat or Reflect, with a count of 1 to 100
(VectorCraft's own limit). Repeat and Reflect run the stops that many
times along the vector (around the turn for a conic) and keep going past
both ends, Reflect running every other run backwards, as SVG's
spreadMethod does.
The Gradient panel has a Spread menu and a Repeats field, and the
Gradient tool marks where the runs meet. paint.setFill and
paint.editGradient take `spread` and `repeats`; bad values are errors.
Presets, swatches, blends, strokes along or across a path, Outline
Stroke and Object › Expand follow the spread; Expand gives each run the
same whole number of objects, and Outline Stroke's mesh keeps the color
on each side of a sharp step between two stops at one offset.
SVG writes and reads spreadMethod: SVG import now reads a repeating or
reflecting gradient as one that repeats or reflects once, instead of
expanding its stops over the shape. CSS writes repeating gradients.
PDF writes a conic's sweep shading over one run. PDF, EPS, legacy .ai
and the .ai editing data write the other kinds with their stops
repeated across the shape: at most 256 runs and 16,384 stops (256 stops
in .ai files), and for a radial or diamond no farther than 65,536 runs
from its center, with a warning past those. PDF import now finds sharp
steps in any axial or radial shading instead of blurring them.
Docs: docs/mcp.md (Spread), docs/target-app-parity.md, docs/gaps.md.
A text on a path that many <use> elements show was laid out along the
whole path once per <use>, and the path was found in the file and parsed
again each time, while the 1 MiB limit on texts built for <use> elements
counted only the text's nodes and characters. A file with a short text on
a very long path and many <use> elements of it could keep the importer
busy far past that limit.
- The elements of the path a <textPath> follows now count toward the
limit. Past it, the remaining <use> elements show the text with its own
properties, and the warning names text paths as well as text.
- The path of each <textPath> is found and parsed once and shared by
every <use> that shows its text.
Files within the limit import as before.
The element a <use> shows inherits from the <use> rather than from where it
is defined (SVG 2, https://www.w3.org/TR/SVG2/struct.html#UseElement). A
text shown by a <use> now takes the font, size, weight, fill, stroke and
other text properties the <use> or its parents set, in clip paths too,
while the text's own properties still win. Uses with the same parent and
properties share one text. A <use> inside the text it shows is ignored for
inheritance, and texts built for <use> elements stop at 1 MiB of text with
a warning.
A Warp effect on an image, or on a group that holds one, bent the image's
frame but drew the pixels unbent, cut by the bent frame. The image now
bends with the frame as a raster mesh warp, as an envelope bends it: after
the last effect in the list, the image is cut into 10 x 10 cells of two
triangles, each a clipped piece of the image under its own affine map,
through every Warp it took and the Transform moves between them. Effects
after a Warp reshape only the frame, and a placed gradient on a group
that holds the image follows the group's bounds as the pieces draw them.
The image itself is unchanged, so editing or removing the warp redraws
it. The images stay unbent when their pieces would make the art pass 2
million objects, and an image stays unbent when its clipping frame keeps
its shape because the warp would pass the effects' anchor budget.
- SVG export writes the image once and draws the pieces with <use> when a
group holds pieces of one image clipped by triangles. PDF, EPS, .ai and
PNG exports write the bent pieces.
- Envelopes: a grayscale image's fill color now colorizes the pieces an
envelope cuts. Before, the pieces drew the gray image.
- Geometry effects: an image under any geometry effect keeps its fill
color and its smoothing setting. Before, it drew the gray image,
smoothed.
Docs: Warp params text, docs/mcp.md, docs/target-app-parity.md.
A new gradient kind, `diamond`, runs its stops from the center (the
start) out to a square with a corner at the end of the vector; the
aspect stretches the square across the vector into a rhombus. The
Gradient panel's Type row has a Diamond button, the Gradient tool draws
the square with the aspect handle and center ring, and a gradient
fitted to an object covers its box.
- Model and canvas: the level is |x| + |y| in the square's frame; a
square with no area (no vector, an aspect of 0, numbers out of range)
paints the last stop. The canvas draws it as pixels, out to a
stroke's miters, caps and arrowheads. Transforms keep the vector and
take the mapped size across it as the aspect.
- Commands: `paint.setFill`, `paint.editGradient` and
`paint.setGradientGeom` take `kind: "diamond"`; a negative aspect or
a focal point is an error. A conic or diamond gradient that a plug-in
returns with a focal point loses the focal point.
- Gradient tool: dragging the aspect handle of a radial or diamond
gradient whose file gave it a negative aspect now sets a positive
aspect; only a conic's negative aspect mirrors it.
- Export: PDF, SVG, EPS and legacy .ai write four linear gradients, one
for each quarter of the square, clipped to the shape (PDF as axial
shadings). PDF and SVG write an image for translucent stops, a
dithered fill or a flat square, and for the Perceptual method unless
PDF writes the stops as spot color shadings. SVG writes a diamond on
type characters as a radial gradient through the corners, and so
does CSS. A swatch library saved as CSS gets four corner layers, a
value for the `background` property (a circle when the swatch is
turned by an angle that is not a multiple of 90 degrees). The .ai
editing data holds a radial gradient through the corners. The
document exports give a warning.
- SVG export: a stroke drawn as an image, as conic wedges or as diamond
quarters now covers the miters, caps and arrowheads that reach past
its paint box.
- PDF import reads a function-based shading whose color is the same
along each square about the origin of its space as a diamond.
- Object > Expand gives nested squares or a mesh of rings.
Docs: docs/mcp.md, docs/target-app-parity.md.
A fourth gradient kind, conic (also accepted as `angle` or `conical`),
sweeps the stops once around the center (the gradient's start),
clockwise on the page from the vector, with a seam where the last stop
meets the first. A negative aspect mirrors the sweep, and reflecting an
object turns its sweep the other way, also for a conic without a
vector or with a flat extent.
- Gradient panel: a Conic button in the Type row; the Aspect Ratio
field works for conic gradients and keeps a mirrored sweep mirrored.
- Gradient tool: the extent ellipse, aspect handle and center ring of a
radial gradient, without a focal point.
- Rendering, swatch chips, transforms, warps, plug-in round trips, and
Object > Expand into wedges or a mesh of columns around the center.
- Commands: a negative `aspect` is an error for linear and radial
gradients, and so is a `focal` point for a conic gradient.
- PDF export writes a function-based shading whose calculator function
turns the angle into the ramp, and PDF import reads a function-based
shading that turns around the origin of its space back as a conic
gradient, also when that origin lies outside the painted area. A
PDF/A file, or a conic with more than 97 stops, gets an image
instead.
- SVG export writes 360 solid wedges clipped to the shape for opaque
stops and an image otherwise, and a linear gradient on characters.
- CSS export writes `conic-gradient(from ...)`, with the stops mirrored
for a mirrored sweep.
- EPS and legacy .ai export write 360 solid wedges; the .ai editing data
holds a linear gradient along the vector with the same stops.
- .af files: conical gradients open as conic gradients.
- An export that writes the gradient in another form gives a warning.
Supersedes #2326 and includes switch87's code from it.
Co-authored-by: Gert Pellin <gert@pellin.be>
The word gap is now the Desired word spacing the layout uses for area type
with Block on (TextObject::layout_justification), with Minimum and Maximum
moved out to it when it lies outside them. The paragraphs keep their own
Justification values, so a wide gap no longer leaves a 1,000 % Maximum
behind, and turning Block off brings back the paragraph's own Desired
value. The SVG writer reads the same values.
- The Contextual Task Bar offers Dynamic Text for area type outside a
thread, beside Convert To Point Type.
- type.dynamicText with wordGap or lineGap and no area type in the
selection is an error instead of being ignored.
- Docs: docs/mcp.md, params text, docs/target-app-parity.md.
* fix(svg): first element of an id, shared linked file reads, bounded blank lines
* address review: cache failed linked-image reads but give each href its own placeholder and link
* address review: charge only blank lines to the line-break budget
* address review: a collapsed line break keeps its measured leading in tiny type
* address review: blank lines use the same scaled leading tolerance
- drawn_alike ignores kerning_before, so a space before the "f" of "fi"
no longer splits the ligature.
- SVG export takes a line's starting space from kerningBefore and the em
shaping sets the character in (vectorcraft_text::line_start_space),
not from where the glyph is drawn, which can include a shaping offset;
the reader layout for justified lines leaves the space out, so those
lines start there too.
- Type on a path writes the space as the first character's dx; a space
after a line break, which <textPath> can't write, is outlined.
At the Type tool's caret, kerning goes on the character before the caret.
At the start of the text there is none, so Alt+arrows, the Kerning field
and Reset Tracking and Kerning did nothing; at a later paragraph's start
the kerning landed on the invisible line break.
CharStyle::kerning_before (kerningBefore) is the space before a character
that starts a line by itself: the first character of the text, of a
paragraph, or after a forced line break. Shaping gives its cluster the
space and draws it that much later along the line (right to left the
space is at its right; in vertical type it moves down the column). The
caret at such a line's start sets it (edit::caret_kerning_target), in
type.step, type.resetTracking and the Character panel; text.setRangeStyle
takes it. SVG export starts the line where the character is drawn. The
Eyedropper and character styles leave it alone.
Type › Dynamic Text (`type.dynamicText`, also on the Contextual Task Bar
for type on a path) scales the text by one factor so it fills the span
between its start and end brackets, and the layout fits it again after
each edit to the text, the path or a bracket. The first character's
size moves in 0.1 pt steps within 0.1 to 1,296 pt. Turning it off, or
threading the type, keeps the sizes it was drawn at.
- The model saves `dynamicText` only when on.
- SVG, the legacy .ai writer and the .ai editing data write the drawn
sizes through `vectorcraft_text::drawn`; the editing data warns that
the setting is not written.
- Shrink Text to Fit and Dynamic Text step the size the Character panel
shows when a size measures the cap height, x-height or ICF box.
Docs: docs/mcp.md, docs/type-parity.md, docs/target-app-parity.md.
Part of #4134.
Ruler guides were a list kept beside the layers. Each guide is now an
object of its layer or sublayer, always directly in it and above the
layer's art. These behave as on main:
- Guides draw on screen only. Exports (SVG, PDF, PNG, PSD), Fit All,
Select All, the art bounds, clicks and marquees, and a layer's
Pathfinder and Transform effects leave them out. The Layers panel
lists them as guide rows of their layer, the highest guide.list
index first.
- Deleting a layer deletes its guides, and merging layers keeps their
guides in the merged layer.
- The guide commands number guides as guide.list does, in the order
they were made.
New: each guide takes an id from the document's object ids. An object
command given a guide's id fails with an error, and node.move cannot
put a guide into a group or out of its layers. A guide made while an
opacity mask, a symbol or a pattern is edited moves to the top
visible, unlocked layer when editing ends.
File format: native files still save the guides in the document's
guides list, in every file version, so older builds read them. Each
entry may now also carry the guide's id, name, locked and hidden
flags; older builds ignore those fields. Opening a file puts each
saved guide on its layer (on the top visible, unlocked layer when the
file names none or a layer it does not have), and each saved guide
counts against the object limit for opening a file. A ruler guide node
that a file holds below a layer's art moves up to the layer's guides,
and one anywhere else (in a group or another object, beside the
layers, or in a symbol) is removed. Ruler guides read from an .ai file
become guide objects on the layer they were on, numbered in the order
the file lists them.
A centred or right-aligned line of tracked type was measured with the
tracking after its last character, so it sat half that tracking
(centred) or the whole tracking (right-aligned) left of where the page
of users' .ai files draws it. That page aligns a line as wide as its
characters without that tracking.
Shaping now records the tracking each glyph's advance holds
(SGlyph::tracking), and a line that isn't justified leaves its last
character's out of the width it is aligned by. The last character is
the last one the line draws before a paragraph's end, a forced line
break or the spaces a wrap leaves; spaces or a tab typed before a
paragraph's end or a forced line break keep the letter's tracking, as
the page does. Forced line breaks and wraps aren't in the files: they
follow the rule so that turning a wrap into a paragraph or a forced line
break keeps the line in place. Hyphenated lines, lines whose last
character hangs, vertical and right-to-left lines and type on a path
keep it. The tracking stays in the glyph's advance, so the caret,
selections, line breaking and Fit Headline are unchanged.
LineInfo::trailing gives the amount. Right-aligned area type sets the
text after its last tab the same way.
What aligns by line widths follows. Convert To Area Type makes its
frame end where the lines are aligned, with room for the tracking, so
tracked type converted either way stays in place. Affinity import lines
up differently aligned paragraphs of artistic text by the aligned
widths. SVG import moves a centred or right-aligned letter-spaced line
back by its share of the last letter spacing, so it stays where a reader
draws it and SVG export, which anchors lines at the end of their
advances, reads back unchanged.
Centred and right-aligned tracked type lays out differently than before
in every document.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A <text> in a <clipPath> was dropped with a warning, so a clip path made
only of text had no content and the art it clipped was hidden. The
clipPath content model in CSS Masking Module Level 1 lists text and use
(https://www.w3.org/TR/css-masking-1/#ClipPathElement).
The importer now clips by the text's glyph outlines, together with any
shapes in the same clip path, and keeps clipPathUnits, the transforms,
clip-rule, visibility, the conditional processing attributes and a clip
path on the text. A <use> in a clip path that shows a text clips the
same way, placed by the <use>; a clip path on the <use> applies in the
<use>'s user space, and a reference goes to the first element with the
id. When the clip path holds only one visible text, with no clip path
of its own and no even-odd clip rule, the clipping path of the clip
group is the text object with no fill or stroke, the same structure
Make Clipping Mask produces for type. A text or a <use> with an id in a
clip path can still be shown by a <use> outside it. Each text's
outlines are laid out once, however many elements use the clip path.
A transform set by a style rule or a style attribute on a text now
places it, as it does a shape. Before, only the transform attribute was
read, inside and outside clip paths.
In horizontal type, tracking and manual kerning are now thousandths of
the em as the Horizontal Scale draws it: at 50%, a tracking of 100 adds
0.05 em after each character. They were added at the full em while the
glyph advances took the scale, so narrow tracked type drifted from the
page of users' .ai files letter by letter. That page writes such type's
character spacing as the tracking itself in a text space as wide as the
scale, which PDF scales by it (ISO 32000-1 9.4.4). Vertical type keeps
the full em: its advances down the column don't all take the scale.
vectorcraft_text::spacing_scale gives the factor. Shaping, inline
graphics and the Optical pairs use it, and so do the conversions from
points back to thousandths of an em: the automatic kerning the Character
panel reads and Touch Type's limits (glyph_em), Combine Point Text, and
PDF import, whose tracking is now the file's Tc. Scale Text Attributes
now keeps tracked type where a horizontal stretch drew it.
SVG export writes letter-spacing in thousandths of the font size it
writes (the vertical scale), so uniformly scaled type keeps its spacing
in a viewer.
Type with both a Horizontal Scale other than 100% and tracking or manual
kerning lays out differently than before, in every document.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each artboard can carry a guide label: one of the layer colors or a
custom RGB. Guides that belong to an artboard draw in its label color,
and guides without an artboard, or on an artboard without a label, keep
the guide color from the preferences. Selected guides still draw in the
selection color.
- Artboard Options has a Guide Label row (None, the layer colors with
swatches, or Custom with a color swatch).
- artboard.new and artboard.setProps take guideLabel: null, a layer
color's name, or #rrggbb. Any other value is an error. The inspect
summary lists guideLabel when one is set.
- The native format saves the label and skips it when unset; a preset
index outside the layer colors reads as no label.
- artboard.setProps no longer adds an undo step when nothing changes.
Docs: docs/mcp.md, docs/target-app-parity.md.
The existing entity tests are unit tests that place the references in
attributes. This adds an import-level test with the references inside
<desc>, which the import refuses, and checks that the namespace entities
drawing apps write (&ns_svg;) still import.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mask, inverted mask or knockout group over type now keeps the region
that covers any artwork. Type is measured by its layout or, on a path,
by the path, and a reader that sets live type in its own font can draw
it wider, so a region sized from that box could cut glyphs off.
A live object (blend, envelope, repeat, mesh, placed document,
intertwine) under a mask is measured as the art the writer writes for
it, expanded on its own with the same limits.
The writer keeps its symbol boxes and symbols' art reach in plain
fields instead of cells.