Commit Graph
437 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
BFlat bde7be4390 Merge pull request #7447 from dotCooCoo/fix/text-inset-skips-seams
Type: area type insets stop at the frame's edges, not at an edge its contours share
2026-11-14 21:53:24 +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
dotCooCoo fb13d2045d Merge main into feat/cjk-tsume 2026-11-14 01:56:33 -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
BFlat 2985970a84 Merge pull request #7403 from dotCooCoo/feat/colr-gradient-repeat
Type: COLR glyphs with repeating or reflecting gradients draw as vector art
2026-11-14 16:50:37 +07:00
ruchirguitar 22fc487cb5 #6613: reconcile main and preserve Czech parameter literals 2026-11-14 14:05:01 +05:30
dotCooCoo bfdf50ffb0 Merge main into fix/text-inset-skips-seams 2026-11-13 23:05:09 -08:00
dotCooCoo ae05540f70 Merge main into feat/cjk-tsume 2026-11-13 22:46:57 -08:00
dotCooCoo 7bb88deb2c Merge main into fix/warichu-rtl-gap-caret 2026-11-13 22:42:56 -08:00
dotCooCoo 1494e2c230 Type: Tsume narrows the space around each character's ink
Character panel > Tsume, shown with Show East Asian Options, takes away a
percentage of the blank space on each side of a character's ink, inside
its own width, in horizontal type. The value is from 0 to 100, 0 by
default. 50 halves that space and 100 sets the ink against the
characters before and after it; the glyph keeps its shape. A character
without ink, such as a space, keeps its width, and a ligature or a
letter with its marks takes the value of its first character. The
font's pair kerning, tracking, manual kerning and Insert Aki are added
after it, and justification keeps it. Vertical type keeps its spacing.

- text.setFormat and text.setRangeStyle take `tsume`; a value that is
  not a number or is outside 0 to 100 is refused, naming the key.
  Character styles carry it, and a file holding a value outside the
  range opens with the nearer end.
- The Character panel's Tsume field has 0, 25, 50, 75 and 100 in its
  menu, and Reset Panel sets it to 0. Character Style Options shows it
  in Advanced Character Formats.
- Live SVG text places each character Tsume moves, and type on a path
  with Tsume is written as outlines.

Docs: docs/mcp.md (Tsume, Character Style Options fields),
docs/target-app-parity.md.
2026-11-13 21:50:05 -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
ruchirguitar 6f6746bd72 #6613: reconcile Gujarati catalog and complete current interface coverage 2026-11-14 09:44:24 +05:30
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 3d40ba6c8e Type: COLR glyphs with repeating or reflecting gradients draw as vector art
A COLRv1 linear or radial gradient whose color line repeats or reflects
made the whole glyph an image. Such a gradient now reads into a vector
gradient with the same spread. The COLR specification repeats the
interval from the first color stop to the last, so one run of the stops
spans the gradient's vector:
https://learn.microsoft.com/en-us/typography/opentype/spec/colr

A repeating or reflecting radial gradient that starts at a circle stays
an image, and so does a repeating or reflecting gradient with a stop
whose palette index is outside the palette. A color line with an extend
value the specification does not define now pads, as the specification
says, instead of making the glyph an image.
2026-11-13 19:30:58 -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 64b4cb1ad6 Merge pull request #7300 from dotCooCoo/fix/composite-fonts-per-document
Type: documents that define one composite font name differently each draw in their own
2026-11-14 05:40:49 +07:00
BFlat c135ba1f9b Merge pull request #7279 from dotCooCoo/fix/path-type-probe-wider
Type: threaded type on a path aligned Justify All is measured by paragraphs
2026-11-14 05:40:38 +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 4bdfc44b26 Type: documents that define one composite font name differently each draw in their own
A document's composite fonts were lent by writing them into the font
database's shared map, and a second lend of another definition of the same
name was skipped while the first was held. A document drawn in a tiled or
floating window while the front document drew, a document placed in one
that defines the name differently, and an export made while another
document is in front could draw their type in the other document's
definition. Registering the front document's fonts during a render could
also change the definition mid-render.

Lending now applies on the lending thread only: each guard pushes the
document's definitions onto a per-thread stack that lookups read before the
database's own fonts, latest loan first, and removes them when it drops,
also when the thread unwinds. The guard is neither Send nor Sync. Lending no
longer changes the database or its generation; while a thread lends
definitions other than the database's, FontDb::generation gives a number of
its own for them, so kept layouts and render caches made in one document's
definitions are not used for another's.
2026-11-13 13:24:38 -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 ab1b7a974a Type: threaded type on a path aligned Justify All is measured by paragraphs
Type on a path whose first paragraph is aligned Justify All places its line
from the start bracket, the same as Left, so a threaded story is now measured
with the paragraphs it needs rather than the whole rest. A 1,500-paragraph
story through 40 paths aligned Justify All was laid out over 2,135,202 bytes
while measuring and is now laid out over 20,583. The frames take the same
text as before.

Center, Right, Auto right to left, hanging punctuation, Dynamic Text and
horizontal right-to-left text are still measured with the whole rest: a
centered or right-aligned line starts where the width of all its glyphs puts
it, and negative tracking can make an advance negative.

Tests compare every frame's share with the whole-rest share for Justify All
with spacing, on closed paths, with negative tracking and with punctuation
ending paragraphs.
2026-11-13 12:40:31 -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 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