Commit Graph
318 Commits
Author SHA1 Message Date
Ahmed Gomaa a3e626600c Merge remote-tracking branch 'upstream/main' into fix/arabic-mark-colour-ownership 2026-11-09 12:36:54 +02: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
BFlat 636ffe78ce Merge pull request #5070 from dotCooCoo/feat/kinsoku-shori-type
Paragraph: Kinsoku Shori Type chooses Push In First, Push Out First or Push Out Only
2026-11-09 09:22:52 +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
dotCooCoo 9a312e8ada Type: type in a composite font is measured in its own document's definition
- A file read for opening is laid out in the composite fonts registered
  then. Opening a document and reverting the front one now measure again
  the type set in a font named like a composite font that an open document
  defines, the font database holds or the session registered earlier. Type
  in other fonts, missing ones included, keeps the bounds it was read with.
- Switching to a document or closing the front one measures again the type
  set in a composite font whose definition changed, and any type without
  bounds.
- A document reverted while in the back measures its type in composite
  fonts when it comes to the front, not in the front document's.
- Text holding an inline graphic whose symbol's type was measured again
  takes the graphic's new size.
- A failed or panicked command puts the font database's composite fonts
  back with the documents.
- Setting a composite font to the definition an export lends keeps it after
  the export ends.
2026-11-08 14:23:43 -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 97ac14ca7d 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-08 19:34:58 +01:00
M Balazs PC 7f17207d67 Merge remote-tracking branch 'origin/main' into fix/ai-type-leading-forced-breaks
# Conflicts:
#	docs/roadmap.md
#	docs/target-app-parity.md
2026-11-08 19:33:41 +01:00
dotCooCoo 8b4ef958ac Type: a ligature's font caret on a vertical path divides the proportional cell
Proportional Metrics changes an upright glyph's cell on a vertical path, and its width runs along that changed cell. Its font ligature carets were still divided by the unchanged cell, so the caret, a click and the selection missed the drawn stop. They now divide the same length the width uses.
2026-11-08 23:51:42 +07:00
dotCooCoo f4e51d33d3 Type: spell center in the upright path glyph comment 2026-11-08 23:51:42 +07:00
dotCooCoo 4882488145 Type: upright glyphs on a vertical path face the path's start
In vertical type on a path, every glyph was turned a quarter turn with
its top toward the path's end. An upright glyph's characters then read
backwards along the path, so a ligature's font caret other than its
middle did not line up with the glyph as drawn.

An upright glyph whose top faces the path's end now turns half a turn
about the centre of its cell, so its top faces the path's start. It
keeps the same stretch of the path. Glyphs lying on their side, and
upright glyphs that the Skew, 3D Ribbon and Stair Step effects already
turn this way on a path running to the left, are unchanged. The ligature
stops follow the top of the cell, so the caret, a click and the
selection meet the drawn caret line.
2026-11-08 23:51:42 +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 0ad4d4f06c Fix #5816: CJK fallback faces in the interface sit on the UI font's baseline
An installed face added for characters the interface fonts lack (Microsoft
YaHei for Simplified Chinese on Windows, for example) now gets a FontTweak
y_offset_factor computed from its ascent, descent and line gap against the
first face of the Proportional family, so its glyphs share that face's
baseline instead of centering the difference in row height. The bundled
faces and Latin text do not move.

This follows designcraft#394 (baseline_tweak from line metrics) and
effectcraft#1395. vectorcraft-text gains line_metrics, and a test face with
a CJK system face's vertical proportions checks the baseline at 13, 24 and
40 pt in the interface families.
2026-11-08 12:21:20 +07:00
dotCooCoo 1d32aff63f Tests: the CSS, glow and .ai Zstandard guards count work instead of timing it
Three tests failed on a loaded machine because they asserted elapsed
time. Each now counts the work that the slow behavior it guards
against would repeat, with a counter compiled only into tests:

- The CSS cascade test counts selector tests, at most two per element
  (testing every rule against every element tests N x N). It styles
  5,000 elements, so the slow behavior fails on the count rather than
  on the hang guard, which now waits 300 s.
- The stacked-glow test counts the silhouettes drawn, 30 for 30 glows
  with and without the shadow cache.
- The Zstandard frames test counts the bytes looked at to find the
  zero padding.

The hang guards for invalid dash patterns, text layout and script
checks of a pipe wait 300 s instead of 20 s.
2026-11-08 12:21:06 +07:00
Ahmed Gomaa f3e5cfad26 Type: reuse harakat ownership shaping metadata
Reuse resolved features and borrowed GDEF classes for mark ownership,
skip the character-level pass without eligible mark glyphs, and reuse
paint-only boundary classification. Compare non-paint style attributes
without cloning while preserving the previous PartialEq behavior.

Add deterministic font variants and a field-by-field style oracle
to preserve glyph geometry, conservative ownership and NaN handling.
2026-11-08 06:55:03 +02: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
Ahmed Gomaa 11cfde2661 Type: harakat tests tell letter dots from authored marks
Noto Sans Arabic draws a letter's dots as zero-advance GDEF marks, so the tests expected a beh's dot to take its kasra's colour. Compare against the glyphs of the bare letters instead. Strict colour, geometry and kashida ownership assertions stay as they were.
2026-11-07 22:24:41 +02: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
Ahmed Gomaa e3d5fee42c Type: colouring Arabic marks preserves joining and glyph ownership
Paint-only harakat runs stay inside one shaping cluster so letters keep
their joining forms and marks their attachment. When a character-level
shaping pass matches the monotone glyphs, a glyph may take a later
in-cluster character's run only if GDEF classifies it as a mark (class 3)
and that character is a harakat; otherwise ownership stays at the cluster
start. Conservative: a mark may miss its colour; a base letter must never
take a mark's. Fonts without GDEF and AAT faces fall back the same way.
Insert Kashidas tatweels take the base letter's paint, not a trailing
mark's. Private harakat module; type-parity, target-app-parity and roadmap
M7 evidence updated (scores unchanged). Tests cover paint_only / ranges /
own_chars (wide mark, equal advance, multi-component, single-glyph
ligature, review counterexample), colour, hide, repeated glyph ids,
kashida, forced mark-last emit_kashida, and installed fonts.
2026-11-07 20:41:37 +02: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 d889390731 Tests: the right-aligned tab test uses its own example text 2026-11-07 19:14:21 +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
dotCooCoo 77d5f8bbea Type: tests compare opened documents with and without kept shaping plans
A new test binary opens a document of 13 type objects with the plans a
font face keeps and again while every shaped segment makes its own plan.
The document, its cached bounds and baselines included, and the first
frame's pixels must be the same. `test_fonts::keep_shape_plans` switches
the plans off; it exists only in tests and with the `test-fonts` feature.

The engine's open test now builds objects of one script and direction
that differ in kerning and language, and lays each one out in a font
database of its own, so a plan used for the wrong features fails its
comparison. It no longer sets Arabic, which could fall back to a system
font in one database and not the other.
2026-11-07 05:14:59 +07:00
dotCooCoo 25b42b2246 Type: layout keeps each font face's shaping plans
Every shaped segment built its own harfrust shaping plan, although a
plan depends only on the face, direction, script, language and features.
Opening a document lays out each type object once, so a document of many
short objects built thousands of identical plans.

A font face now keeps up to 32 plans, oldest dropped first, and shapes
with the one that matches. Feature ranges still come from the shape
call, so text with the same features over other ranges shares a plan.
The glyphs, bounds and baselines do not change.

Docs: docs/target-app-parity.md.
2026-11-07 05:14:59 +07:00
Hamza 090667fb26 Languages: Bulgarian, with its quote marks and the fonts' Bulgarian forms
Bulgarian was not among the text languages, so Bulgarian text could not
be tagged and fonts drew their Russian-style Cyrillic. Bulgarian (bg)
is now in the language list (Character panel, character styles,
text.setFormat by name or tag, the Hyphenation preference's default
language), with CLDR's quotes, „…“ at either level. Shaped as bg, the
fonts' locl forms for BGR apply: in the bundled Source Sans 3 Italic,
в, д, ж, з, к, л and ю take their Bulgarian forms.

Fixes #5945
2026-11-07 00:17:16 +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
dotCooCoo f48ac15d08 Text: a restyled empty frame keeps its style before an unedited empty frame
When a frame's text ends with a paragraph break and the next frame is empty, restyling that empty frame was undone on reflow: an unedited empty frame after it gave the story's last paragraph its own style again. A restyled empty frame now keeps its style against unedited empty frames that follow it. Unedited empty frames before it, later frames with text and later restyled frames still give the paragraph their style, as before.
2026-11-06 14:56:28 +07:00
dotCooCoo b0f383a1ab Text: empty later frames preserve an edited paragraph style 2026-11-06 14:08:37 +07:00
ruchirguitar 70f798b71d i18n: add Hindi interface and message catalog 2026-11-06 12:40:10 +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 e33be0eea9 Glyphs: a character's glyph is the first of its glyphs in its cluster
Ka, the halant, Ka again and the vowel sign I are shaped as one cluster:
the vowel sign, the first Ka's half form, then the second Ka's default
glyph. The glyph lookup looked for the character's default glyph across
the whole cluster before its alternates, so for the first Ka it found
the second Ka's default glyph instead of the half form. It again goes
through the cluster in order, and takes the first glyph that is the
character's default glyph or one of its alternates and is not the
default glyph of another character of the grapheme, so a vowel sign's
glyph that the font also lists as an alternate of the consonant is not
reported for the consonant.
2026-11-05 07:30:43 +07:00
dotCooCoo 8f8f71f803 Glyphs: the Glyphs query reports the glyph of an overflowing character
text.glyphAlternates reported the default glyph as `chosen` for any
character that overflows its area. A character whose style substitutes
another glyph (`salt`, or `vert` in vertical type) was reported with a
glyph it is not drawn with. The query now shapes such a character alone,
with the rest of its grapheme in their own styles and in the text's
writing direction, by the same helper text.setGlyphAlternate uses.

The helper also shapes the character in the direction its paragraph
sets it in, from its bidi level, so a parenthesis set right to left is
reported with its mirrored glyph in both commands.
2026-11-05 06:55:55 +07:00