Commit Graph
68 Commits
Author SHA1 Message Date
BFlat 7dd71a6972 Merge pull request #6708 from dotCooCoo/feat/mojikumi-line-end-fit
Type: Mojikumi spacing at line ends counts when lines break
2026-11-12 09:32:21 +07:00
BFlat 7739fdbe50 Merge pull request #6677 from storytold/fix/sec-text-fallback-scans
Text: bounded font fallback file searches, COLR layer outlines, frame baseline search and glyph outline size
2026-11-12 09:23:32 +07:00
dotCooCoo 4185a16a0d Type: a line's first word is measured up to a forced line break
A forced line break ends the first word when looking for room, inside a
No Break run too, as the line is set. A soft hyphen just before a forced
line break adds no hyphen to the measured width.
2026-11-11 17:37:26 -08:00
dotCooCoo b14831d964 Type: a soft hyphen inside a No Break run does not end the first word
When layout looked for room for a line's first word in a frame that is
not a rectangle, a soft hyphen ended the word even inside a No Break
run, although the line does not break there. The word is now measured
across a soft hyphen where kinsoku or a No Break run keeps the line from
breaking after it.
2026-11-11 16:20:48 -08:00
dotCooCoo b89a6e8ba1 Type: Block's word gap leaves the paragraphs' word spacing as set
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.
2026-11-11 15:49:16 -08:00
dotCooCoo 253c60a23e Type: a line's first word is measured as set when looking for room
In a frame that is not a rectangle, or with text wrap, layout moves a
line down to the first span wide enough for its first word. The word
was measured at full width before Mojikumi set an opening bracket at
the line's start flush, and with its last character at full width, so
a word that fit as set moved down to a wider line. It also ended at any
break opportunity, so an opening bracket counted without the character
kinsoku keeps with it and could be placed alone on a line too narrow
for that character.

- word_width ends the word only where break_line may end a line: at a
  cluster end that kinsoku allows, not inside a No Break run, whose
  spaces count. It leaves out the spaces at the word's end and narrows
  its last glyph by the Mojikumi line-end credit.
- place_flow_line measures the word after the opening-bracket trim, and
  the Every-line Composer's first-line placement takes off the same
  start credit, so the composed widths match the placed lines.
2026-11-11 15:20:50 -08:00
dotCooCoo a5bfa524dc Type: Mojikumi spacing at line ends counts when lines break
With Line-end Punctuation Half Width, a closing bracket, comma or full
stop ending a line is set half width, but both composers measured it at
full width. A closing bracket that fit only at half width went to the
next line with the character before it, and so did a comma or full stop
with Burasagari None.

The single-line and every-line composers now measure each candidate
line with its last character set as narrow as Mojikumi sets it, using
the same rule the line is drawn with. A mark that already lost its half
to the punctuation after it and a line ending in a hyphen take nothing
more off, and the quarter em between Japanese and Latin text counts only
where the line ends. Kinsoku Shori Type measures the pushed-in and
pushed-out lines the same way. Right-to-left lines and Mojikumi None
break as before.

A test font sets ideographs and Japanese punctuation one em wide, so
the layout tests run without system fonts.
2026-11-11 15:20:34 -08:00
Dylan M. Taylor 21e6524f06 Text: colour layers stop drawing outlines once over budget, one frame's baseline search has a total step budget, a glyph outline keeps at most a million elements 2026-11-11 20:24:43 +00:00
BFlat f9ff1caf54 Merge pull request #6648 from dotCooCoo/feat/dynamic-text-on-path
Type: Dynamic Text sizes type on a path to fill its brackets
2026-11-12 02:45:39 +07:00
dotCooCoo 2606b70604 Type: Dynamic Text sizes type on a path to fill its brackets
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.
2026-11-11 10:42:49 -08:00
jmvergesandClaude Opus 5.5 9f80b4daae Type: aligned lines leave the tracking after their last character out (#6610)
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>
2026-11-11 16:07:40 +00:00
dotCooCoo 5a8a57d5f6 Type: upright glyphs on a vertical path keep vertical point type's cells, and the caret spans the column
On a vertical path, an upright glyph is now placed in its column as
vertical point type places it, then the column is centered on the path.

- Its cell starts at its place on the path; it started 0.12 em earlier
  with fonts whose cell top is less than a full cell above the baseline.
- A baseline shift moves it across the column instead of along the path.
- A horizontal scale widens it evenly about the column's center.
- A combining mark stays on its upright letter.
- The caret and the selection highlight span the column, half its em on
  each side of the path, instead of the font's ascent and descent.

With Skew, 3D Ribbon and Stair Step on a path that runs to the left,
letters are a quarter turn counterclockwise from upright glyphs: Skew and
3D Ribbon mirror both kinds there, and Stair Step keeps letters level. The
test, the progress doc and the params text of Tate-chu-yoko Settings now
state these conditions and the block's directions on a path.
2026-11-10 22:27:53 -08:00
dotCooCoo f8307b19cd Type: on a vertical path, letters lie a quarter turn clockwise from upright glyphs, in a column centered on the path
Follows #5557. Letters and digits lying on their side in vertical type on
a path kept their top toward the path's end, opposite the upright glyphs.
They are now set as horizontal type on the path sets them: baseline along
the path, top to the path's left, a quarter turn clockwise from upright
glyphs (CSS Writing Modes 3, Unicode UAX #50).

- The column is centered on the path: upright glyphs, letters on their
  side and tate-chu-yoko blocks have their em box centers on the path
  line instead of a column one em wide to the path's right.
- Upright glyphs are turned in the path effect's glyph space, so with
  Skew on a curve their top runs back along the tangent and their x axis
  stays up the page, as letters keep theirs.
- Tate-chu-yoko blocks on a path stand upright, stay whole on a curve and
  move by Tate-chu-yoko Settings toward the path's start and left.
- A tate-chu-yoko block of mixed sizes sets every glyph in its first
  character's em, on a path and off it.

Existing vertical type on a path redraws with these placements.
2026-11-10 21:36:30 -08:00
M Balazs PC b64cfed610 Merge remote-tracking branch 'origin/main' into fix/ai-right-aligned-tabs
# Conflicts:
#	crates/text/src/layout/flow.rs
#	docs/file-format-parity.md
#	docs/roadmap.md
#	docs/target-app-parity.md
2026-11-09 04:48:48 +01:00
dotCooCoo 6e8e1e213e Type: a pushed-in line leaves the gap after inline art open
Kinsoku push-in room does not count a gap after inline art or inside a list marker, but the squeeze also closed those gaps. The squeeze now applies only to the gaps the room counts.
2026-11-08 18:47:54 -08:00
dotCooCoo 3ea13d88d3 Merge main into feat/kinsoku-shori-type
# Conflicts:
#	crates/engine/src/cmd/panelcmds.rs
2026-11-08 16:47:29 -08:00
dotCooCoo 0ce616da4b Merge main into feat/kinsoku-shori-type
# Conflicts:
#	crates/engine/src/cmd/panelcmds.rs
#	crates/text/src/layout/breaking.rs
#	crates/ui-egui/src/panels/paragraph.rs
#	docs/target-app-parity.md
2026-11-08 16:13:35 -08:00
M Balazs PC b3fb745575 Merge remote-tracking branch 'origin/main' into fix/ai-right-aligned-tabs
# Conflicts:
#	crates/text/src/layout/flow.rs
2026-11-08 22:25:23 +01:00
dotCooCoo 7b8d5ba8f5 Type: Tab and Shift+Tab move a list item down and up a level
Squashed from #5037 onto main.
2026-11-09 03:39:05 +07:00
dotCooCoo 11f1d228de Type: Align to Path moves each character by its own font's metrics
Align to Path moved the whole line by the largest ascent and descent
among its characters. Each character now moves by its own font, size
and vertical scale. Ascender runs the top of the font's em box along
the path: the ideographic em box that the font's BASE table gives, or
in a Chinese, Japanese or Korean font the one that its typographic or
hhea box, a one-em box-drawing cross or its ideographs give; else an
em box centered on the cap height. Descender runs the bottom of the font's
bounding box along the path, and Center runs a line at half the height
of the top of the font's H along it. In vertical type, Ascender and
Descender move the characters half an em, and Center leaves them on
the path.

Existing documents with Align to Path set to Ascender, Descender or
Center lay their type out at a new distance from the path.
2026-11-09 03:14:41 +07:00
dotCooCoo c94269ff72 Type: Composite Fonts… sets each class of character in a font of its own
Squashed from #6244 onto main.
2026-11-09 03:08:23 +07:00
M Balazs PC a24ab06c6d Merge branch 'fix/ai-type-leading-forced-breaks' into fix/ai-right-aligned-tabs
# Conflicts:
#	crates/engine/tests/import_fuzz.rs
#	crates/eps/src/import/ate.rs
2026-11-08 14:58:13 +01:00
M Balazs PC f5c3f47612 Merge remote-tracking branch 'origin/main' into fix/ai-type-leading-forced-breaks
# Conflicts:
#	crates/eps/src/import/ate.rs
#	crates/text/src/layout.rs
2026-11-08 14:57:14 +01:00
dotCooCoo c94aec3fff Envelopes: type maps from its glyph outline bounds
An envelope mapped type from its laid-out box: ascent to descent for
point type, the frame for area type. The distorted glyphs then filled
less of the envelope than other art does, with room above the capitals
and below the baseline.

The envelope now maps type from the bounds of its glyph outlines,
underlines and strikethroughs when the type is on its own, in groups
and layers, or used as a clipping path. Inline graphics count by their
boxes. Rotated type and type in perspective map from their outlines as
they are drawn. Make with Mesh,
Make with Top Object and Reset with Mesh without Maintain Shape use
those bounds. Type that draws nothing keeps its laid-out box, and so
does type inside any other object, such as a blend, a repeat or a
symbol instance. Shrink Text to Fit type is measured as it is laid out
on the page.

vectorcraft-text's envelope_ink measures the type, and
register_envelope_ink registers it with the document crate. The engine
session, Renderer::new, live evaluation in vectorcraft-effects and the
.ai reader register it. The document crate keeps the measured boxes per
thread, by object and by a hash of the content, and measures again when
the type, the fonts, the hyphenation exceptions or the transform's
scale or rotation change.

This is VectorCraft's own rule. Envelopes of type made in VectorCraft
and in saved files map from the glyph outline bounds instead of the
type's laid-out box, so they redraw differently.
2026-11-08 13:20:19 +07:00
dotCooCoo f42fdf900f Type: layout looks up each style run's face once
Layout kept no record of a run's face, so each paragraph looked up the face
of its first character three times for its line metrics and once more to
shape it. The layout now keeps each run's face the first time it is looked
up and uses it for shaping, for paragraph metrics, for on-path type's empty
line and for the column offset of vertical point type. Glyphs, positions,
bounds and baselines do not change.

A test-only helper lays text out looking every face up at each use, and a
new test requires point, vertical point, area and path type laid out both
ways to match exactly.

The layouts of 5,000 short point type objects take a median of 215 million
CPU cycles of the laying-out thread instead of 236 million in a release
build (11 alternating runs of each build on Windows 11, measured while
other builds were running).
2026-11-08 12:21:32 +07:00
dotCooCoo dbcdbf2466 Open: the first frame uses the layouts the open made for type objects
Opening a document laid out every type object for its bounds and
baselines and threw the layouts away, and the first frame laid the same
objects out again. The open now keeps them, up to 128 MiB, and a frame
takes each one once.

A frame takes a layout only for a text object equal to the one laid out,
and only while the fonts, the system fallback setting and the hyphenation
exceptions are unchanged. When the last frame being drawn ends, the rest
are dropped if a frame took one, and all of them are dropped after eight
frames that take none. A layout with bitmap or color glyphs and a text
object with a gradient are not kept.
2026-11-08 11:42:45 +07:00
M Balazs PC c78d4c4402 Merge branch 'fix/ai-type-leading-forced-breaks' into fix/ai-right-aligned-tabs
# Conflicts:
#	docs/file-format-parity.md
#	docs/roadmap.md
#	docs/target-app-parity.md
2026-11-07 19:45:08 +01:00
M Balazs PC 1e43455d7c .ai/EPS import: the docs give frame fitting's exact limit and qualify the file format status line (#6137) 2026-11-07 19:44:48 +01:00
M Balazs PC 8561ef351c Docs: the line layout functions the tab fix touches say what they do 2026-11-07 19:34:47 +01:00
M Balazs PC 0234de1302 Merge branch 'fix/ai-type-leading-forced-breaks' into fix/ai-right-aligned-tabs
# Conflicts:
#	docs/roadmap.md
#	docs/target-app-parity.md
2026-11-07 19:31:52 +01:00
M Balazs PC 4bfd7a207d Docs: the functions the line layout fixes touch say what they do 2026-11-07 19:30:49 +01:00
M Balazs PC d9fdd7a072 Type: the last tab of right-aligned area type leaves room for the hyphen a hyphenated line ends with 2026-11-07 19:25:01 +01:00
M Balazs PC 36511e1bbd Merge branch 'fix/ai-type-leading-forced-breaks' into fix/ai-right-aligned-tabs 2026-11-07 19:10:42 +01:00
M Balazs PC c0341c3fff Type: a paragraph ending in a soft return justifies the line the soft return ends
A paragraph that ends in a soft return ends with the empty line after it,
so the line the soft return ends is justified like any other; it was taken
for the paragraph's last line and left aligned.
2026-11-07 19:10:21 +01:00
M Balazs PC a4e315796b Type: right-aligned area type sets the text after a line's last tab against the edge
In right-aligned area type, the text before a line's last tab sits at its
stops, measured from the left edge, and the text after it (none when the
tab ends the line) against the right edge. The line was laid out from the
left and aligned as a whole, which moved its start off the left edge and
its tabs' text off their stops. Illustrator's own layout in a user's .ai
sets its price lists so. Centred and justified lines are aligned as a
whole as before.
2026-11-07 18:49:19 +01:00
M Balazs PC eb03eacba6 .ai/EPS import: type keeps auto leading, hyphenation options and the file's last lines
- Auto leading stays auto: each line takes the paragraph's share (key 7)
  of the largest type on it. The leading was the first two lines'
  distance for every line, so 10 pt lines under a 15 pt heading came in
  18 pt apart.
- The paragraph's hyphenation options (keys 10 to 15) are read.
- Area type of one frame whose last lines the file sets, but which don't
  fit here (the fallback font, or a descent past the frame's bottom),
  gets a frame taller from its bottom (vectorcraft_text::fit_source_lines),
  with a warning.

A user's .ai whose type differed from its page by 6.6%, so that it opened
from its PDF part with each line its own text object, now opens from its
layers.
2026-11-07 16:57:21 +01:00
M Balazs PC 0f0e707e60 Type: a line a soft return ends is justified as a wrapped line is
In Justify Left, Center or Right, only the paragraph's last line keeps its
alignment; a line that a soft return (U+2028) ends is spread to the frame's
edges like any wrapped line. Illustrator's own layout in a user's .ai sets
such lines justified, and VectorCraft left them ragged.
2026-11-07 16:57:21 +01:00
ruchirguitar 5bb21cdddf Add hyphenation for eleven Indian languages
Squashed from #5946 onto main.
2026-11-07 05:15:17 +07:00
jayzed80andClaude Opus 5.5 7d465d4fa3 fix(text): a gutter too huge to step past no longer stalls area type layout
Past a gutter of 1e300 a step no longer moves the baseline, so every cell of
a 100 x 100 frame was searched for all MAX_STEPS steps: over 40 s per layout.
AreaOptions::gutter has no limit, so a saved file can do this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 23:04:37 +07:00
jo-quast bddb834231 Part of #394: each run hyphenates in its own language; Default Language sets new type's
Squashed from #2818 onto main.
2026-11-05 12:17:33 +07:00
dotCooCoo befc4b959d SVG export: type with Mojikumi and no Japanese characters is laid out once
To find the characters that justification or Mojikumi moves, SVG export
without Fewer <tspan> Elements laid out every live type object a second
time, as point type with
Mojikumi None and default Justification options, whenever its paragraph
had Mojikumi on. New type gets Line-end Punctuation Half Width, so every
new type object was laid out twice. Mojikumi only spaces Japanese
punctuation and Japanese letters, so type without them lays out the same
either way.

- TextLayout::mojikumi_applies says whether the text or a list marker
  holds a character Mojikumi spaces.
- reader_advances keeps such a paragraph's Mojikumi, and reads the glyphs
  from the layout it was given when the object it would lay out is the
  same; otherwise it lays the object out again as before.

The SVG is unchanged. Exporting 5,000 point type objects takes a median
of 109.4 ms instead of 171.5 ms on Windows 11.
2026-11-05 08:24:33 +07:00
dotCooCoo e0ebc13a2e Type: right-to-left manual kerning keeps whole-point spacing on both sides
With System Layout on or Fractional Widths off, each advance is rounded
to whole points, but a right-to-left character with manual kerning
still moved by the unrounded kerning value. The difference between the
two changed the space after the character before it in reading order.
The character now moves by the change the kerning makes to its rounded
advance, so only the space before the next character changes.
2026-11-04 22:29:14 +07:00
dotCooCoo 6d38e94f19 Merge current main into Kinsoku Shori Type 2026-11-03 12:25:55 -08:00
dotCooCoo 63ac1395fd Type: control Kashidas for selected characters 2026-11-03 23:29:52 +07:00
dotCooCoo 2b63bf1b67 Type: keep composition spacing along the line when characters rotate 2026-11-03 21:38:02 +07:00
dotCooCoo e1ea0ecad1 Type: the caret, a click and the arrow keys reach the characters inside a ligature
Squashed from #5225 onto main.
2026-11-03 16:38:36 +07:00
dotCooCoo e13cf73ae0 Type: Tate-chu-yoko Settings move a block up, down, left or right
Squashed from #5206 onto main.
2026-11-02 14:32:59 +07:00
dotCooCoo 0895eccfe1 Type: right-to-left manual kerning moves a rotated character along the line
Right-to-left manual kerning was added to the glyph's offset, which
Character Rotation turns, so a rotated character with kerning moved
across the line (up at 90 degrees) instead of away from the next
character in reading order. The shift is now applied to the pen
position along the baseline, outside the rotation, and the rotation
turns the glyph about that moved pen position.
2026-11-02 14:32:54 +07:00
dotCooCoo beb89ebec4 .ai import: vertical type keeps its orientation and Tate-chu-yoko runs
Squashed from #5086 onto main.
2026-11-02 12:04:08 +07:00
dotCooCoo f46427167c .ai import: area type keeps its rows, columns, gutters and first baseline
Area type made from the editing data's text document came in with one
row, one column, the 18 pt gutter and the Ascent first baseline,
whatever its frame kept. Each area frame now keeps its rows and columns,
the gutter between rows and the gutter between columns, and its first
baseline (Fixed, Cap Height, Leading or x Height) with its minimum,
scaled with the art's units. Each frame of a thread keeps its own, and a
key that is missing or can't be read leaves that option as it was. A
first baseline the frame keeps is no longer replaced by the fixed one
that matches the file's first line.

AreaOptions gets row_gutter (rowGutter, left out while unset), which
layout spaces rows by; text.areaOptions takes and reports rowGutter, a
gutter given alone sets both, and the Area Type Options dialog shows
Row Gutter.
2026-11-02 12:04:07 +07:00