Commit Graph
25 Commits
Author SHA1 Message Date
dotCooCoo ba2a6fc1f3 Type: a further span beside a wrap object is measured across its line's height
Text wrap can split a line into spans that share a baseline. The spans
after the first were measured across the height of the line set in the
first one, so a taller line set in a later span could run into a wrap
object the shorter line stayed clear of. Each further span is now
measured again across the height of the line that goes in it. The line
gets the part that is free, and a span with no free part, or with a part
narrowed to less than the line's first word, is left empty. The
Every-line Composer and the line loop both place lines through
`Pen::place`, so they agree.

The Every-line Composer also keeps a line start whose line found no room
while a longer line from it would be taller. A taller line steps down
the frame by its own leading and can find room the shorter line's steps
missed.

Tests: a_further_span_is_measured_across_the_height_of_its_line,
composition_tries_a_longer_line_after_one_that_cannot_be_set.
2026-11-13 04:48:43 -08:00
dotCooCoo 6c773585fa Type: the Every-line Composer sets mixed sizes in shaped and wrapped area type
A paragraph whose characters differ in line metrics (type size, font,
leading, vertical scale, or an inline graphic taller than the text) was
broken with the Single-line Composer in area type of a shape other than
a rectangle, or with text wrap. The every-line composer now composes it
there too.

- The composer takes each line's width from a measure that follows
  where the lines above it went (`composer::Measure`,
  `compose_with`). The fixed widths by line number are one such measure.
- In a frame whose lines are not all as wide, each line the composer
  tries is placed from the pen the lines above it left, at its own
  tallest metrics, as the line loop places it (`layout/shaped.rs`).
  Starts with the same placement are shared, and each start keeps the
  placements it was tried with.
- A line too long for its room does not end the search from its start
  while a glyph after it could make the line taller and set it lower,
  where the frame may be wider.
- The line loop places a composed line in such a frame at the line's
  tallest metrics, so it gets the room the composer measured for it.
- The search for one paragraph stops after 1,000,000 lines tried or
  2,000,000 baseline steps. The searches of all the paragraphs of one
  frame share a budget of 2,000,000 lines and 4,000,000 steps, kept
  apart from the line loop's own steps. A paragraph whose search stops
  is broken line by line. A paragraph the frame has no room for is
  broken line by line too, and overflows as before.

Tests: crates/text/src/tests/combos.rs (a stepped frame, a frame that
widens, text wrap with two spans on a baseline, overflow, a long
paragraph) and crates/text/src/layout/wrapping_tests.rs (the composer
with a measure, and the shared search budget).
2026-11-13 01:24:33 -08:00
dotCooCoo 40f4c920e7 Type: Bunri-Kinshi keeps inseparable character pairs on one line
Paragraph panel menu > Bunri-Kinshi (with the East Asian options) and
text.setFormat {bunriKinshi} turn it on per paragraph. A line then
never breaks inside two em dashes, two horizontal ellipses, two two-dot
leaders or the two halves of a vertical kana repeat mark, the pairs W3C
JLREQ lists for class cl-08 (https://www.w3.org/TR/jlreq/#cl-08), nor
inside a chain of em dashes. Both composers, vertical type, Kinsoku
Shori Type, hyphenation and No Break follow it, and a pair wider than
the line stays whole on it. Justification and letter spacing add no
space inside a pair.

The setting works only with a Kinsoku Set: with Kinsoku Set None the
paragraph keeps the value without effect and the menu item is
unavailable. It is off by default, saved with the paragraph only when
on, carried by paragraph styles (Paragraph Style Options > East Asian
Composition Settings), and turned off by Reset Panel. A value that is
not true or false is refused. The .ai editing data has no field for it,
so saving reports a paragraph with it on and a pair in it as left out.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-12 10:26:11 -08:00
dotCooCoo 3bcdbd7e71 Type: letter spacing and Full Justify keep shaped clusters together
Single Word Justification's Full Justify counted and placed a gap after
every drawn glyph, so a combining mark or the vowel sign of a Devanagari
syllable moved away from its letter. Desired Letter Spacing and the
letter spacing range of a justified line did the same between two glyphs
of one cluster.

- The Full Justify gap count and placement share one check that skips
  glyphs with the same cluster byte as the glyph drawn after them.
- Letter spacing goes after the last glyph of a cluster only.
- Letter spacing, Full Justify and CJK inter-character gaps pass over
  every default-ignorable character drawn with no width (soft hyphens,
  bidi marks, embeddings, overrides and isolates, joiners, word joiners,
  variation selectors, tags, the byte order mark) and put the space
  between the visible characters on either side. The Hangul fillers stay
  letters.
- A zero-width space takes no word-space room on a justified line and
  stays a line break opportunity. Letter spacing before it is set after
  it, so a line broken there ends at its last letter.
- A single word that Full Justify spreads and that breaks at a hyphen
  has the same gap before the drawn hyphen as between its letters.
- Kinsoku push-in counts the gaps it can close past format controls, as
  the layout closes them.

Tests cover horizontal, right-to-left and vertical type, Desired and
fitted letter spacing, format controls, zero-width spaces, the drawn
hyphen, push-in, and a Devanagari syllable when a Devanagari face is
installed.
2026-11-12 06:54:44 -08:00
dotCooCoo ff826ed1bd Type: Korean words break at spaces unless Korean Line Break Anywhere is on
Korean text broke after every Hangul syllable. A paragraph now has
Korean Line Break Anywhere, set from the Paragraph panel menu (with the
East Asian options), from the East Asian Composition Settings section of
Paragraph Style Options, and with `text.setFormat {koreanLineBreakAnywhere}`.

- Off, a word that contains Hangul (the characters between spaces) breaks
  only at its spaces and after East Asian punctuation, or where the word
  is wider than the line. Both composers follow it, and the word takes no
  hyphen. On, a line may break between any two syllables, as before.
- New type has it off. Paragraphs and text objects saved before the
  option read as on, so their lines do not move; the value is saved only
  when it is off.
- Paragraph styles carry it, Reset Panel turns it off, and the .ai writer
  reports it as lost for Korean paragraphs.

The help page: https://helpx.adobe.com/illustrator/desktop/design-with-text/fonts-and-scripts/set-line-breaks-korean-text.html

The command is described in docs/mcp.md, and docs/target-app-parity.md
lists the option.
2026-11-11 23:06:55 -08:00
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
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 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
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 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
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 0465fb8336 Type: push-in closes no gap inside a cluster and measures along the line
A push-in no longer counts or closes the gap between two glyphs of one
shaped character, and CJK justification adds no space there either.
The share of an em each gap closes by is measured along the line: at
the character's Horizontal Scale, or at its Vertical Scale for an
upright character in vertical type whose font's vertical metrics set
its length down the column. Before, horizontal text at 200% Vertical
Scale closed its gaps by twice as much.
2026-11-01 08:36:32 -08:00
dotCooCoo 932426eaad Type: a line holding right-to-left text pushes out
A line with right-to-left text is reordered before it is drawn, and
justification closes the gaps between its glyphs in that order, which
can differ from the gaps the push-in counted. Such a line could be
taken as fitting and then run past its frame. Kinsoku Shori Type now
pushes it out.
2026-11-01 08:22:06 -08:00
dotCooCoo b5cf31950b Merge main into feat/kinsoku-shori-type 2026-11-01 06:26:35 -08:00
dotCooCoo e1440a3a90 Paragraph: Kinsoku Shori Type chooses Push In First, Push Out First or Push Out Only
The Paragraph panel menu (with the East Asian options) gains Kinsoku Shori
Type, and text.setFormat takes kinsokuType. Where the Kinsoku Set keeps a
character off the start of the next line or the end of this one, Push In
First sets it on the line and closes the line's gaps next to CJK
characters to fit, each by at most a tenth of an em of the character
before it; a line that cannot close up that much, or that holds a tab,
pushes out. With Optical Margin Alignment or Roman Hanging Punctuation
on, the gaps that hanging punctuation keeps open are not counted. Push
Out First pushes in only when pushing out would leave the line more than
an em short. Push Out Only, the default, is the line breaking area type
had before, so existing documents lay out unchanged. The setting does
nothing with Kinsoku Set None, is saved only when it is not Push Out
Only, and paragraph styles carry it. Reset Panel sets it back to Push
Out Only.

Docs: docs/mcp.md, docs/type-parity.md, docs/target-app-parity.md.
2026-10-31 21:14:57 -07:00
dotCooCoo 5716a996ab Type: Touch Type moves a character rotated near 90 degrees across its line
Baseline shift now moves a character in the line's frame, outside
Character Rotation, so a positive shift moves a character up from its
line at any rotation, and the character turns about its shifted pen
position. The Touch Type tool splits a drag into its part along the line
(kerning) and its part up from the line (baseline shift) at every
rotation; before, within about 6 degrees of a quarter turn it dropped
the cross-line part.

Characters that already combine a rotation with a baseline shift are
drawn in a new position. Their stored values do not change.

- text: glyph transform order, PositionedGlyph::pivot (the turning
  point), tab leaders in the same order
- tools: Touch Type splits drags against the line's up axis and measures
  rotate-handle turns about the shifted pen position
- engine: text.touchType description; dy is up from the line
- docs: mcp.md Touch Type paragraph, target-app-parity.md progress line
2026-11-01 11:08:57 +07:00
Taha-Mahmoodi b6df1ef0b1 Type: handle soft return in no-break runs, event modifiers, trailing breaks, and tabs 2026-10-29 20:36:05 +07:00
Taha-Mahmoodi d89031848c Fix #2754: Shift+Enter inserts a soft return (forced line break)
In typography and Illustrator, Shift+Enter inserts a soft return (forced
line break) rather than ending the paragraph. Text wraps to the next line
without applying paragraph-level spacing (space before/after, first-line
indents), list continuation lines keep their parent item without repeating
markers, and justified text does not stretch lines ending with a soft return.

- Forward active modifier keys for Enter in ui-egui shortcuts so the tool
  detects Shift+Enter.
- Type tool inserts U+2028 (LINE SEPARATOR) on Shift+Enter.
- Text shaping and layout treat U+2028 as an invisible zero-advance control
  character, breaking the line in point and area type.
- Paragraph spacing and list markers apply once per paragraph as intended.
2026-10-29 20:36:05 +07:00
Dylan M. Taylor 0e2815629f refactor(text): split flow and ruby layout phases 2026-10-27 17:47:12 +00:00
aiongg a8935c0ada refactor(text): move layout.rs items to layout/{bidi,breaking,driver,flow,on_path,region,ruby,vertical}.rs 2026-10-26 22:01:25 +00:00