Commit Graph
396 Commits
Author SHA1 Message Date
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 4cad65c603 Merge pull request #7150 from dotCooCoo/fix/thread-frame-measure-needed-paragraphs
Type: a threaded story measures each frame with the paragraphs it needs
2026-11-13 23:15:01 +07: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
BFlat 39940f5568 Merge pull request #7133 from dotCooCoo/fix/font-menu-scrollbar-test-flaky
Tests: the font menu scroll bar test lists fonts of its own
2026-11-13 23:14:43 +07:00
BFlat 2d91c860a7 Merge pull request #7087 from dotCooCoo/feat/every-line-composer-shaped-area
Type: the Every-line Composer sets mixed sizes in shaped and wrapped area type
2026-11-13 23:03:29 +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 7f18643ada Type: measuring a threaded frame copies its own text once
Each measurement of a frame's share started from a copy of the whole frame, its own runs included, and then replaced the runs. The frame is now copied once without its own text, and each measurement copies that. A test counts the frame text copied to measure frames that already hold text.
2026-11-13 07:12:59 -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 6d0556e60f Type: a threaded story measures each frame with the paragraphs it needs
Distributing a story through threaded frames measured every frame but the
last with the whole rest of the story, and copied that rest several times
per frame, so a long story through many frames cost frames times story
length.

- The rest of the story is a borrowed view of the given runs (run index,
  byte offset, length). Each frame's share is copied once; the slices,
  styles of empty shares and the run left once the story is used up are
  the same as before.
- Area type without Shrink Text to Fit whose lines align to the top is
  first measured with the text through the end of the paragraph that
  reaches about twice the previous frame's share (at least 512 bytes),
  doubling while the story goes on. The share is taken when its text
  stops fitting before the last paragraph break measured; layout places
  lines in order, stops at the first that does not fit and shapes each
  paragraph on its own text. Type on a path, Shrink Text to Fit, other
  vertical alignments and a share ending in the story's last paragraph
  are measured with the whole rest.

Tests compare every slice with the earlier algorithm, kept in the tests,
for many kinds of frames, styles and paragraphs and 12 seeded stories.
A story of 1,500 paragraphs (55,892 bytes) through 40 frames was laid
out over 2,126,762 bytes while measuring and is now laid out over
21,035.
2026-11-13 06:31:37 -08:00
dotCooCoo eb2f3ba71a Tests: the font menu scroll bar test lists fonts of its own
font_menu::tests::the_scroll_bar_leaves_the_stars_clear starred every
family in the process-wide font database and listed that database, which
tests running alongside add fonts and composite fonts to. In full
parallel runs it sometimes failed with "8 families, no row stars drawn".
With the four bundled families alone it never reached the scroll bar
check, which ran only for seven or more families.

- The menu's header takes the font database it lists and hands it to
  the rows and the name-width measurement. The app passes the
  process-wide database as before.
- Each FontDb has an id of its own (FontDb::id), and the widest family
  name the menu keeps is stored with that id as well as the generation,
  so a width measured for one database is not used for another at the
  same generation.
- The test lists a database of the bundled fonts, stars its four
  families, opens on Inter, and runs the frames the scroll bar takes to
  appear. It checks that the rows overflow the list's scroll area, that
  a row star is drawn inside that area, and that each star stays left
  of the scroll bar's column (#2277).

With the list's scroll bar set to floating, the test fails with a star
under the bar. Without the id in the widest name's key,
each_font_database_has_its_own_widest_name fails.
2026-11-13 05:41:46 -08:00
dotCooCoo 130f914868 Type: a threaded frame's empty last paragraph keeps a format given to it
A paragraph format given to the empty paragraph after a frame's last
paragraph break was lost when the story flowed again: the paragraph is
shared with the next frame's first paragraph, and the next frame's old
style replaced it, in that frame and in the frames after it.

- The thread reflow also notes which frames' last paragraph changed
  style, and story_paras_restyled keeps that style against the next
  frame's unedited first paragraph, across unedited empty frames too.
  When the same edit formats the next frame's first paragraph, that
  format applies, as before.
- story_paras keeps its behavior for its other callers.
- Tests cover area type and type on a path on either side, a next frame
  holding text, three frames, undo and redo, a format that changes
  nothing, and numbered list counts.
2026-11-13 05:27:02 -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
BFlat 499903878e Merge pull request #7055 from dotCooCoo/feat/mojikumi-presets
Type: the Mojikumi Set menu offers the predefined spacing sets
2026-11-13 13:17:36 +07: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
jmverges f94eecd439 Merge remote-tracking branch 'upstream/main' into fix/ai-import-font-match
# Conflicts:
#	docs/file-format-parity.md
2026-11-13 01:55:35 +00:00
BFlat d0a8fc708f Merge pull request #7001 from dotCooCoo/fix/wasm-clippy-runtime-default
Updates: Runtime derives Default on wasm32, so wasm32 clippy passes
2026-11-13 08:29:29 +07:00
jmvergesandClaude Opus 5.5 bb50d438c8 .ai import: type from the text document takes the face of its exact PostScript name and recorded version (#6983)
Type read from a text document's font record was set in whichever installed
face first answered to the record's PostScript name (ignoring case and
punctuation), and its family was then swapped for any available family
spelled alike but for case and punctuation (`FontDb::find_family`): a record
naming `SampleInk`, the PostScript name of `Sample/Ink`, set its type in
`SAMPLE INK`. Copies of one family and style under other PostScript names or
versions were not told apart either, so the type could set at other widths
than the file's own page.

- `named_font` / `document_font` (crates/eps/src/import/text.rs) give the
  family and style of the installed face of exactly that PostScript name,
  else one that differs only in case or punctuation, and name its version
  when the family and style alone resolve to another installed copy.
- The font record's key 5, its version string (name ID 5), is read and picks
  that version among the faces of the name. Each record is resolved once.
- `FontDb::postscript_faces` lists every installed face a PostScript name
  answers to, loading their families; the import prefers among them one of
  exactly that name.
- Type a PostScript program sets (EPS import) and legacy .ai type take the
  face of their font's name the same way.
- The .ai save writes a static font's version (`/5`) when its PostScript name
  alone would give the reader another installed copy (another version, or the
  same name in another family), keeps the version in the style it compares,
  and checks that the type comes back in the same family, style and face,
  taking the record's version as the reader does (up to 256 bytes), so such
  type round-trips without a note.

Measured on 224 users' files (used locally, never committed): 16 files set
some type in another face; 12 render differently against their own page,
10 closer and 2 further by 0.03% of the page; envelopes of type that open
live go from 154 to 159, none fewer in any file; no file opens another way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 00:33:13 +00:00
dotCooCoo 7764da6ad8 Updates: Runtime derives Default on wasm32, so wasm32 clippy passes
`cargo clippy --target wasm32-unknown-unknown -p vectorcraft-ui-egui
-- -D warnings` stopped twice. In vectorcraft-text, MAX_FILE_SEARCHES
is used only by the system font search, which is not compiled for
wasm32, so it was dead code there. In vectorcraft-ui-egui, Runtime
holds only manual_window_open and status_message on wasm32, and the
hand-written Default set them to false and None, so clippy's
derivable_impls stopped the run.

- MAX_FILE_SEARCHES is compiled only for targets other than wasm32,
  like the SysFallback struct next to it.
- Runtime derives Default on wasm32, and the hand-written Default,
  which creates the updater, is compiled only for other targets.
2026-11-12 16:27:30 -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
BFlat e618783377 Merge pull request #6084 from Taha-Mahmoodi/feat/glyph-alternates
Glyphs: alternates keep the word joined (feature values, one-piece shaping, unencoded glyphs)
2026-11-12 23:53:13 +07: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
Taha-Mahmoodi ebad5d75c0 Use the unencoded fixture glyph in ligature range regression 2026-11-12 18:21:43 +04:30
Taha-Mahmoodi 4c0c6179c9 Fix contextual glyph face, cluster boundaries and insertion status 2026-11-12 17:22:47 +04:30
Taha-Mahmoodi 2590186b32 Merge remote-tracking branch 'origin/main' into feat/glyph-alternates 2026-11-12 16:45:00 +04:30
BFlat 7f9866f42a Merge pull request #6818 from dotCooCoo/feat/korean-line-break-anywhere
Type: Korean words break at spaces unless Korean Line Break Anywhere is on
2026-11-12 19:13:57 +07:00
Taha-Mahmoodi 63eacbd94a Fix glyph browser reset, variation and insertion behavior 2026-11-12 16:04:52 +04:30
Taha-Mahmoodi 9624b29dad Merge main into contextual glyph alternates 2026-11-12 15:56:45 +04:30
Sven Lafebre ab17837cba Merge remote-tracking branch 'origin/main' into fix/5111-kern-paragraph-start
# Conflicts:
#	docs/target-app-parity.md
2026-11-12 09:24:22 +01:00
Taha-Mahmoodi 5144f22976 Merge main 48e066f47 into glyph alternates #6084 2026-11-12 12:34:54 +04:30
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
Sven Lafebre 46e8f576cc Merge remote-tracking branch 'origin/main' into fix/5111-kern-paragraph-start
# Conflicts:
#	docs/target-app-parity.md
#	docs/type-parity.md
2026-11-12 03:56:06 +01: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
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