Commit Graph
116 Commits
Author SHA1 Message Date
BFlat 01a796e817 Merge pull request #7415 from dotCooCoo/fix/warichu-rtl-gap-caret
Type: justification spaces a right-to-left warichu block, and the caret keeps to its block line
2026-11-14 21:55:12 +07:00
dotCooCoo 35a9045612 Merge main into fix/text-inset-skips-seams 2026-11-14 02:15:45 -08:00
dotCooCoo c752edc902 Merge main into fix/warichu-rtl-gap-caret 2026-11-14 02:04:02 -08:00
BFlat 78cb105961 Merge pull request #7481 from dotCooCoo/feat/every-line-composer-shaped-overflow
Type: the Every-line Composer sets the visible lines of a mixed-size paragraph that overflows a shaped frame
2026-11-14 16:51:54 +07:00
dotCooCoo bfdf50ffb0 Merge main into fix/text-inset-skips-seams 2026-11-13 23:05:09 -08:00
dotCooCoo 7bb88deb2c Merge main into fix/warichu-rtl-gap-caret 2026-11-13 22:42:56 -08:00
dotCooCoo 396796ff9b Type: the Every-line Composer sets the visible lines of a mixed-size paragraph that overflows a shaped frame
A paragraph whose characters differ in line metrics, in area type that is
not a rectangle or that wraps around an object, was broken line by line
with the Single-line Composer when the frame could not hold all of it.
The lines the frame holds are now composed with the whole paragraph: a
line the frame has no room for, and every line after it, is measured as
wide as the span of the last line set in the frame, less its indents.
Those lines overflow, or go on in the next threaded frame, which composes
them again at its own widths. A paragraph whose first line has no room,
and a search that passes its limits, still fall back to Single-line.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-13 21:46:30 -08:00
dotCooCoo ab0ae9f880 Type: area type insets stop at the frame's edges, not at an edge its contours share
An edge that two contours of an area type frame share lies inside the
frame, and lines already run across it. Inset Spacing still kept text
clear of it, so a frame of contours that meet along an edge left a gap
of twice the inset at the seam. The inset now applies only beside the
stretches of edges that bound the frame, counted by parity as the line
spans are. A negative text wrap offset no longer shrinks a wrap object
beside an edge two of its contours share.
2026-11-13 21:00:03 -08:00
dotCooCoo be8fafde78 Type: justification spaces a right-to-left warichu block, and the caret keeps to its block line
A justified line opened no gap before a right-to-left warichu block: the
glyph drawn first after the text before it is not the block's first
glyph, and it was taken as continuing the block. The block now starts at
the first of its glyphs the line draws, and blocks the bidi order
interleaves keep their starts.

The caret next to a character of a block spanned the whole line. It now
spans the band of the character's block line, as the selection does.
2026-11-13 19:58:57 -08:00
dotCooCoo 1dc8b72bbf Type: hyphenated type lays out again when the hyphenation exceptions change
Changing Preferences › Hyphenation › Exceptions bumped each open
document's revision, but the caches of laid-out type outside the text
crate were keyed on the font generation alone. The renderer kept the
glyph geometry, raster-effect pixels, live-effect and envelope art,
pattern tiles and placed symbol art of unchanged type objects, so
hyphenated area type kept its old line breaks on the canvas. The
hit-test art of blends and envelopes, the Fit to Artwork Bounds answer,
the layout caches of the Type and Touch Type tools and the out port's
overflow mark were keyed the same way.

vectorcraft_text::layout_inputs() returns a number that changes with
the fonts and with the hyphenation exceptions. Every one of those
caches now keys on it instead of FontDb::generation(), and envelope ink
uses it in place of the private envelope_ink_inputs(). The hit-test key
hashes it together with the plug-in revision.

Tests: a renderer that drew hyphenated area type, alone, with a drop
shadow, in a group with a Transform effect, in a symbol instance and in
an envelope, draws it as a new renderer does after the exceptions
change; the out port of selected area type shows the overflow that the
new exceptions cause. The render test helpers that run a step again
when the fonts change during it now also run it again when the
exceptions change.
2026-11-13 18:56:48 -08:00
BFlat 239152b6c8 Merge pull request #7303 from dotCooCoo/fix/slanted-seam-tolerance
Area type: lines stop at a gap between slanted contours however narrow
2026-11-14 07:32:55 +07:00
BFlat f6e84df143 Merge pull request #7306 from dotCooCoo/fix/path-type-rtl-fit
Type on a path: overflowing right-to-left type shows the longest start that fits
2026-11-14 06:26:48 +07:00
BFlat efba8bfd89 Merge pull request #7295 from dotCooCoo/fix/negative-wrap-offset
Text Wrap: a negative Offset lets text run into the wrap object
2026-11-14 06:26:39 +07:00
dotCooCoo 0fc37a0ddf Merge main into fix/slanted-seam-tolerance 2026-11-13 15:17:12 -08:00
BFlat 795f01782f Merge pull request #7272 from dotCooCoo/fix/invert-wrap-one-pass
Text Wrap: Invert Wrap intersects a line's spans in one pass
2026-11-14 05:41:20 +07:00
BFlat 55fe8b62d4 Merge pull request #7278 from dotCooCoo/fix/rtl-tab-line-hyphenation
Type: right-to-left lines with tabs are measured as they are drawn
2026-11-14 05:39:52 +07:00
dotCooCoo 77e4dceb9d Type on a path: overflowing right-to-left type shows the longest start that fits
Right-to-left type on a path that overflows searched for the start of its
text that fits with a binary search, which assumed that a longer start
never fits once a shorter one does not. A letter tracked to a negative
advance breaks that: with steps of 6, 6, -3 and 6 points in a 10 point
span, the first three letters fit while the first two do not, and the
search showed only the first letter.

- Without hanging punctuation, and with no spacing or on a path of one
  straight segment, every start is measured in O(n log n): each glyph
  keeps its place in the whole text's visual order, except the trailing
  whitespace and controls that UAX #9 rule L1 sets to the paragraph's
  level.
- With hanging punctuation, or spacing on a curve, when a letter has a
  negative advance, a start is a candidate only when its glyphs, in its
  own visual order and each at the least step it can take, reach no
  farther than the path's length. This lower bound holds with negative
  advances.
- At most 32 candidates are arranged from the longest. When none of
  them fits, a binary search over the rest shows a start that fits,
  which is not always the longest.
- With hanging punctuation, or spacing on a curve, text with no negative
  advance keeps the binary search.

Right-aligned and centered type on a curve with Spacing was placed using
the width its glyphs have at the start bracket, while the steps were
measured where the line was placed, so a line that fits could end past
the end bracket and lose glyphs. It is now placed using the width of the
steps it has where it is placed.
2026-11-13 14:04:05 -08:00
dotCooCoo 08f7ce6670 Area type: lines stop at a gap between slanted contours however narrow
Two contours of an area-type frame now count as meeting along a slanted
edge only when their edges lie exactly on one line, checked with an exact
cross product, and the intervals on either side of a seam join only
where both edges cross the middle of the band at the same x, found along
the same line. Before, both checks allowed a few units in the last place
of the coordinates, which near the edge of the canvas (x = 3,999,000)
is wider than a real gap of one to four units, so a line ran across the
gap outside the frame.

An edge divided by an anchor exactly on it still joins, including where
the divided edge's own crossing would round to another x.
2026-11-13 13:42:05 -08:00
dotCooCoo 395dd5b294 Text Wrap: a negative Offset lets text run into the wrap object
The help page says a wrap object's Offset may be positive or negative
(https://help.adobe.com/archive/en_US/illustrator/cs4/illustrator_cs4_help.pdf#page=307).
Text Wrap Options, the commands, the MCP tool and .ai import already
kept a negative value, but layout set it to 0.

- Layout keeps the signed offset, within -1000 to 1000 pt like the
  commands and .ai import (a value that is not a number counts as 0).
- Around a wrap object, a negative offset shrinks the object by that
  distance from each edge, holes included, so lines beside, above and
  below it run that far into it. A shape narrower than twice the
  distance no longer moves text aside. A line may get less room than
  the exact shrunk shape leaves, for example where several edges come
  close to one piece of the band, or where a band holds more than 64
  anchor heights. It never gets less room than an offset of 0 leaves.
- With Invert Wrap, a negative offset keeps the text that far inside
  the object.
- Zero and positive offsets up to 1000 pt lay out as before. A larger
  positive offset lays out as 1000 pt.
- The params text of object.textWrap.make and object.textWrap.options
  and the MCP text_wrap offset description give the range and what a
  negative value does.

Docs: docs/target-app-parity.md.
2026-11-13 13:19:43 -08:00
dotCooCoo 25857b2bce Type: right-to-left lines with tabs are measured as they are drawn
A right-to-left line draws the text before a tab after it, but its width
was measured in the order of its text. A right-aligned line such as
"אב גד<tab>הו זח" with a stop at 150 pt in a 300 pt frame ended 5.59 pt
past the frame's right edge, and right, center and decimal stops aligned
the text before the tab instead of the text drawn after it.

- The line's width is measured in drawing order, each tab up to its stop
  from where the glyphs drawn before it end. Right, Auto and centered
  lines are aligned by that width. Stops are still measured from the
  left edge.
- Right, center and decimal stops align the text drawn after the tab.
- Area type breaks a line with a tab where it fits as drawn, and wraps a
  line that fits only in the order of its text. Once the line holds a
  tab, it reads on while the least it can be drawn at fits, then ends at
  the latest break that fits as drawn: up to 256 breaks are measured
  from the latest back, and earlier breaks without a hyphen are
  bisected. Hyphenation points are measured as drawn the same way, so
  the line hyphenates at a point that fits.
- Only lines of horizontal paragraphs with right-to-left text keep a
  list of their break candidates.
- A tab that ends a wrapped right-to-left line is drawn before the line
  at its own width instead of pushing the line to the right.

Docs: docs/target-app-parity.md.
2026-11-13 12:40:40 -08:00
dotCooCoo dd5a9e7cbc Text Wrap: Invert Wrap intersects a line's spans in one pass
With Invert Wrap, each piece of a line's band intersected the line's
spans so far with the spans the wrap shape holds over that piece by
comparing every pair of them. A wrap shape of many parts (a comb or a
zigzag) gives a line many spans, and with an offset the band is cut
into up to 128 pieces, so a dense shape made layout quadratic in the
number of parts for every line.

intersect_intervals now walks the two sorted lists once, stepping past
whichever interval ends first, and gives the same spans in the same
order. Tests compare it with the pairwise intersection on every list of
non-overlapping intervals with ends among seven points, check that two
interleaved lists of 3,000 and 2,000 intervals take at most 5,000
steps, and lay out a line inside a 500-tooth Invert Wrap comb with an
offset.
2026-11-13 12:12:22 -08:00
dotCooCoo fdedfb0e59 Merge main into feat/warichu 2026-11-13 11:59:22 -08:00
BFlat 34457735e5 Merge pull request #7231 from dotCooCoo/fix/threaded-type-measures-paths-and-aligned-frames
Type: threaded paths and frames aligned off the top measure the paragraphs they need
2026-11-14 02:39:29 +07:00
dotCooCoo b513c631f9 Type: threaded paths and frames aligned off the top measure the paragraphs they need
A threaded story measured each type-on-a-path frame, and each area frame
whose lines align to the center, the bottom or justify vertically, with
the whole rest of the story. A story of 1,500 one-line paragraphs through
40 such frames was laid out over about 2.1 million bytes while the frames
were measured.

- Area type without Shrink Text to Fit now takes the paragraph probe for
  every vertical alignment. A reflow pass that places less text is
  undone, so when the final layout stops before the last paragraph break
  measured, every pass placed the same lines as with the whole rest.
- Type on a path takes it when its line starts at the start bracket: no
  Dynamic Text, Optical Margin Alignment or Roman Hanging Punctuation,
  the first paragraph aligned Left, Justify Left or Auto, and vertical
  type or no right-to-left text in the rest of the story. The story is
  scanned for right-to-left text once per distribution.
- Shrink Text to Fit, the other path settings and a share that ends in
  the story's last paragraph are still measured with the whole rest.

The same 40 frames are now measured over 21,035 bytes (aligned to the
center) and 20,583 bytes (paths aligned Left). Each frame's share is
unchanged: the tests compare every slice with the whole-rest result for
both distribution entry points, across alignments, frame shapes, wraps,
path settings, right-to-left text, styles, lists and seeded stories.
2026-11-13 10:31:42 -08:00
dotCooCoo a55116c144 Type: Invert Wrap with an offset applies to a band of no height
A band whose top and bottom are equal lost its only piece when the repeated cut was removed, so the line kept the frame's whole width. Anchor cuts now apply only to a band with height.
2026-11-13 10:16:46 -08:00
dotCooCoo 1f13d1f8b7 Merge main into fix/area-type-band-coverage 2026-11-13 10:04:24 -08:00
dotCooCoo 6664a9bdb1 Type: a hairline gap between vertical edges of a frame still splits the line
Crossings of the middle of a line's band that lay within a few units of rounding of each other were joined as a seam, so two contours of an area type frame 1e-7 pt apart at x = 1e8 held one line across the gap between them. A vertical edge crosses the middle at its own x with no rounding, so a gap at one is no longer joined.
2026-11-13 09:55:08 -08:00
dotCooCoo ef331e8fad Type: Invert Wrap keeps a line where two parts of the shape bridge a gap together
With Invert Wrap and an offset, each piece of a line's band kept only the x that one part of the shape held within the offset of every height of the piece. Where the offsets of two parts met across a gap (one above and one below), the heights between them held neither part, and the line lost that x although the grown shape covered the whole band.

The band is now also cut at every height an offset above or below an anchor of the shape (when there are at most 64 of them), and the horizontal edges at the ends of a piece's window count, so the window of the piece between the meeting offsets reaches both parts.
2026-11-13 09:51:19 -08:00
BFlat ae33ab04cb Merge pull request #7181 from dotCooCoo/fix/area-type-inset-from-edge
Area type: Inset Spacing on a shaped frame is measured from the frame's edge
2026-11-14 00:48:54 +07:00
dotCooCoo 487da4b52a Type: area type lines stay inside a frame that pinches in within their band
A line in area type of a shape other than a rectangle was measured at five heights of its band, so an edge that pinched in between two of them (a notch tip at y = 47.5 in a 40..60 band) left the line running outside the frame. The frame's intervals over a band are now the intervals at its middle minus the x range of every edge within the band, which is exact for any number of vertices, holes and crossing contours.

Edges that contours meet along do not shorten a line: horizontal ones count by coverage, and collinear ones by overlap, and an edge that only touches the top or bottom of the band is left out. Rectangles keep their fast path.

Text wrap shapes were sampled at the same five heights, so a wrap object whose tip reached into a band between two of them let the line run across it. A line now keeps clear of every x at which a wrap shape, grown by its offset, lies anywhere in the band: the shape's intervals at the middle of the band plus every edge within it. With Invert Wrap, the band is cut into pieces a quarter of the offset tall (64 at most), and a line keeps to the x within the offset of the shape at the heights each piece's offset window holds, so it stays inside the grown shape over the whole band; an offset of 0 takes the shape's intervals over the whole band.

A frame less than 0.0002 pt tall no longer panics when a line is measured in it.
2026-11-13 09:15:01 -08:00
BFlat dad9116ef7 Merge pull request #7156 from dotCooCoo/fix/justify-after-last-tab
Justify: a line with a tab spreads the text after its last tab to the edge
2026-11-13 23:14:52 +07:00
dotCooCoo 5f45e39108 Area type: Inset Spacing on a shaped frame is measured from the frame's edge
On a shaped frame, Inset Spacing keeps text that far from the frame's
edge, measured from the edge itself rather than along the line. Before,
the inset was trimmed from each end of a line after the frame's edge was
found, so on a sloped edge the text came closer than the inset: a
triangle (0,0), (100,0), (0,100) with a 10 pt inset gave the band
40..60 the span 10..30, where a 10 pt clearance gives 10..25.86. Holes
keep the same clearance.

A rectangular frame keeps its inset from each side as before, a
column's inner edges keep it once, and the gap kept beside a wrap
object along the line is unchanged.
2026-11-13 07:58:15 -08:00
dotCooCoo df975e6300 Justify: a line with a tab spreads the text after its last tab to the edge
A justified area type line counted a tab as a word space, so it gave
the tab a share of its room that no glyph drew, and the line ended
short of its measure. A word-tab-word line ended short too.

- The text up to a line's last tab keeps its spacing, each tab keeps
  its stop and its leader stays in its gap. The text after the last
  tab takes the line's room: word spacing, letter spacing and glyph
  scaling within their ranges, then its word spaces, or the gaps
  between its letters when it has none. Kashidas go in that text when
  it is one run.
- A tab separates two words, so Single Word Justification does not
  align a word-tab-word line.
- Right-to-left lines take the text drawn after the last tab drawn,
  measured as the line is drawn, and vertical lines work down the
  column. Point type keeps its natural width.

Docs: docs/target-app-parity.md, docs/type-parity.md.
2026-11-13 06:41:22 -08:00
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 f278364816 Type: a selection in a warichu block highlights its own block line
A selection inside a warichu block was highlighted across the whole
line of text, so selecting a character of the upper block line
highlighted the lower one too, and a selection from one block line to
the next spanned the wrong characters. Each block line now gets its own
band, out to halfway to the block lines next to it, with one highlight
over each run of selected characters drawn side by side on it. A
selection that runs past the line still covers the line break.

Glyphs now carry their block line, so the characters of one block line
stay together for the highlight and for a click whatever their own
Character Alignment.
2026-11-12 23:47:30 -08:00
dotCooCoo 06a283232e Type: no justification gap inside a right-to-left warichu block
Justification gaps follow the drawn order, and right to left the bidi
order draws a block's first glyph after the others. The glyph drawn
second then did not continue the block, so Full Justify and CJK
justification opened a gap inside it. Both gap rules now also skip a
pair whose glyphs belong to the same block.
2026-11-12 23:03:39 -08:00
dotCooCoo 199a9dbce3 Merge main into feat/warichu 2026-11-12 22:28:38 -08:00
dotCooCoo 36ab39a7f4 Type: Warichu blocks keep their places, clicks and line height in mixed text
- Insert Kashidas skips the characters of a warichu block.
- Blocks whose characters the bidi order draws in turn keep where each
  block starts.
- A click on a block line goes to that line's characters.
- Digits marked Warichu in vertical type on a path stay across the column.
- A block of several fonts gives its line the tallest ascent and the
  deepest descent among them.
- Warichu Settings on characters not marked Warichu do not split shaping.
- Warichu Settings leaves a value outside its range as it is unless it is
  edited.
2026-11-12 22:12:06 -08:00
dotCooCoo 226ac437b9 Type: Warichu stacks selected characters on lines inside their line
The Character panel menu has Warichu and Warichu Settings… with Show East
Asian Options. Warichu characters are set at a percentage of their size
and stacked on 1 to 6 lines inside their line: one above the other in
horizontal type and side by side in vertical type. Warichu Settings sets
Lines, Line Gap, Scale and Alignment.

- text.setFormat and text.setRangeStyle take warichu, warichuLines,
  warichuGap, warichuScale and warichuAlign; ui.warichuSettings opens
  the dialog.
- A stretch of warichu characters is one block that stays whole on one
  line. The canvas and every export draw the stacked lines; SVG, PDF and
  the .ai writers write such type as outlines, as they do ruby. Type on a
  path sets the characters at their own size.
- The attributes are saved natively, carried by character styles and
  translated in every complete catalog.

Docs: docs/mcp.md, docs/type-parity.md.
2026-11-12 21:13:21 -08:00
dotCooCoo 7b6b6af7b1 Type: the Mojikumi Set menu offers the predefined spacing sets
The Paragraph panel's Mojikumi Set lists None, YakumonoHankaku,
GyomatsuYakumonoHankaku, GyomatsuYakumonoZenkaku and YakumonoZenkaku,
then Line-end Punctuation Half Width, and text.setFormat {mojikumi}
takes yakumonoHankaku, gyomatsuYakumonoHankaku, gyomatsuYakumonoZenkaku
and yakumonoZenkaku besides none and lineEndHalf.

The spacing of each set is VectorCraft's own rule, built on the help
page's one-line description of each set:
- YakumonoHankaku sets every bracket, comma and full stop half width,
  takes a quarter em off each side of a middle dot, and puts a quarter
  em between Japanese and Latin letters or digits.
- GyomatsuYakumonoHankaku keeps punctuation full width inside a line,
  sets an opening bracket starting a line flush and a closing mark
  ending it half width, and takes the quarter em after a middle dot
  ending a line off.
- GyomatsuYakumonoZenkaku keeps punctuation full width and adds the
  quarter em between Japanese and Latin text.
- YakumonoZenkaku sets characters as None does.

Both composers and vertical type measure each line as it is set. A
right-to-left paragraph, whose line ends are measured untrimmed, also
draws them untrimmed, and a right-to-left line that ends between a
Japanese and a Latin character is measured without the quarter em the
set adds there. With a Desired glyph scaling other than 100%, the
space a set takes off before an opening bracket or middle dot, and the
Japanese-Latin space recorded at a line's end, scale with the glyph.

The sets save with the document and paragraph styles; new type and
Reset Panel keep Line-end Punctuation Half Width. SVG export lays type
with middle dots out again for its positioned spans. A .ai save
reports a paragraph with one of the new sets as left out.

Docs: docs/mcp.md, docs/target-app-parity.md, docs/type-parity.md,
docs/gaps.md.
2026-11-12 21:07:44 -08:00
dotCooCoo 705b688f2b Type: Insert Aki keeps a widened glyph's own width and an optical pair's letter
A glyph that justification widens to fill the line keeps its Insert Aki
before it at its width, and its own width, which ligature carets stand
in, now starts where the glyph is drawn. Optical kerning measures a pair
from the letter of a cluster rather than from a combining mark whose
advance holds the cluster's Aki.

The SVG test also checks centered and right-aligned lines with Insert Aki.
2026-11-12 15:48:12 -08:00
dotCooCoo b9946efb1e Merge main into feat/cjk-insert-aki 2026-11-12 12:46:09 -08:00
dotCooCoo 9d8df6f27f Type: Bunri-Kinshi finds a pair by its clusters in right-to-left justification
Single Word Justification adds no space inside an inseparable pair. A
right-to-left line draws the second half's cluster before the first half,
with the cluster's glyphs in logical order, so a dash followed by a
combining mark puts the mark next to the first dash. The pair is now found
from the two neighboring clusters instead of neighboring glyph indices.
2026-11-12 11:05:35 -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 89748b0360 Type: Insert Aki Left and Right add space before and after a character
Characters take two new attributes, `akiLeft` and `akiRight`: space
before and after each character in reading order, in thousandths of an
em from 0 to 1000, measured as tracking is. `text.setFormat`,
`text.setRangeStyle` and character styles set them; values that are not
numbers or are outside the range are refused, and a file holding one
outside the range opens with the nearer end. Both are saved only when
not 0.

- Layout: Left is the space above a character in vertical type and on
  its right in right-to-left type. Wrapping counts the space, a line's
  end keeps it when aligned, and justification and Optical kerning
  leave it at its set width. A ligature, a letter with its marks and a
  tate-chu-yoko block take the values of their first character.
- With Fractional Widths off or System Layout on, the space before and
  the space after a character are each set in whole points and added
  to its rounded width, so justification keeps them at that width. A
  tate-chu-yoko block rounds the space above and below it the same way.
- Character panel: with Show East Asian Options on, Insert Aki Left and
  Insert Aki Right fields with 0, 250, 500 and 1000 in their menus;
  Reset Panel sets both to 0.
- Character Style Options › Advanced Character Formats shows Insert
  Aki Left and Insert Aki Right, with the Character panel's 0, 250, 500
  and 1000 in their menus; a typed value below 0 or past 1000 sets 0 or
  1000, and Reset Panel unsets both.
- SVG export: live text places each character the space moves, and
  type on a path with Insert Aki is written as outlines.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-12 10:24:38 -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
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