Commit Graph
470 Commits
Author SHA1 Message Date
Don Omondi 499a78b0bf Merge remote-tracking branch 'upstream/main' into fix/ai-import-brushes
# Conflicts:
#	docs/file-format-parity.md
#	docs/target-app-parity.md
2026-11-08 19:21:57 -05:00
BFlat 7724463e00 Merge pull request #5991 from mockydevs/fix/eps01-editing-overlay
EPS import: gradients drawn as abutting strips come in as one gradient, without seams
2026-11-09 07:57:02 +08:00
BFlat 22f5ede66f Merge pull request #6161 from dotCooCoo/feat/ai-editing-data-s6b2
.ai save: the editing copy keeps graphic styles and links to them
2026-11-09 07:52:12 +08:00
BFlat 7adc2f66a3 Merge pull request #6109 from dotCooCoo/feat/ai-symbol-libraries
.ai import: symbol libraries open with their symbols
2026-11-09 07:47:36 +08:00
Don Omondi abeb7e3167 Merge remote-tracking branch 'upstream/main' into fix/ai-import-brushes
# Conflicts:
#	docs/file-format-parity.md
#	docs/target-app-parity.md
2026-11-08 18:18:16 -05:00
dotCooCoo 230fbb96a3 Merge main into feat/ai-editing-data-s6b2 2026-11-08 15:00:10 -08:00
dotCooCoo b8ab981058 Merge main into feat/ai-symbol-libraries 2026-11-08 14:56:51 -08:00
Don Omondi 8383b2af6d Merge remote-tracking branch 'upstream/main' into fix/eps01-editing-overlay
# Conflicts:
#	docs/file-format-parity.md
#	docs/gaps.md
2026-11-08 17:39:34 -05:00
dotCooCoo 1940d3e529 .ai import: opacity mask art in pattern and brush art takes document ids
A pattern's or a brush's art was copied into the document with new ids,
but the art of an opacity mask inside it kept the ids of the reader that
read the definition. Those ids could repeat ids of objects in the
layers. The mask art now takes document ids, as a symbol's art already
does.
2026-11-08 14:06:24 -08:00
dotCooCoo 11f1d228de Type: Align to Path moves each character by its own font's metrics
Align to Path moved the whole line by the largest ascent and descent
among its characters. Each character now moves by its own font, size
and vertical scale. Ascender runs the top of the font's em box along
the path: the ideographic em box that the font's BASE table gives, or
in a Chinese, Japanese or Korean font the one that its typographic or
hhea box, a one-em box-drawing cross or its ideographs give; else an
em box centered on the cap height. Descender runs the bottom of the font's
bounding box along the path, and Center runs a line at half the height
of the top of the font's H along it. In vertical type, Ascender and
Descender move the characters half an em, and Center leaves them on
the path.

Existing documents with Align to Path set to Ascender, Descender or
Center lay their type out at a new distance from the path.
2026-11-09 03:14:41 +07:00
dotCooCoo c94269ff72 Type: Composite Fonts… sets each class of character in a font of its own
Squashed from #6244 onto main.
2026-11-09 03:08:23 +07:00
dotCooCoo b5adb679e4 .ai import: images past 2^25 pixels in the editing data keep the layers
Squashed from #5143 onto main.
2026-11-09 02:43:13 +07:00
Don Omondi 21e6464e6f EPS import: scaling a named descendant counts its dictionary's size; test Type 3 descendants and that unreadable Type 42 data is read once 2026-11-09 02:24:54 +07:00
Don Omondi 70353f003e EPS import: reading sfnts data is charged before the copy, whatever its result
Joining a Type 42 font's strings is charged per byte (as other bulk
copies are) before it runs, so data that turns out unreadable, read
again after leaving the cache, no longer copies for free.
2026-11-09 02:24:54 +07:00
Don Omondi e61f0940d0 EPS import: composite fonts' Type 42 review fixes
widthshow matches a composite character by font number times 256 plus
its code; measuring Type 42 glyphs counts against the operation budget;
the Type 42 data cache keeps the least recently used order, remembers
fonts whose data can't be read and counts each font's data against the
memory budget once; composite font arrays are borrowed, not copied.
2026-11-09 02:24:54 +07:00
Don Omondi 3bb43b2fe6 EPS import: composite fonts draw their Type 42 descendants' glyphs
A composite (Type 0) font with the 8/8 mapping picks a descendant font
and a code in it with each pair of bytes (PLRM 3rd ed., 5.10). The reader
set its strings as type in a font of the composite font's name, the
bytes read as Latin-1 and at a thousand times the size, so EPS from
pdftops of a PDF with embedded TrueType fonts showed nothing, or one
black shape, where its type was.

Characters now go to their descendants: a Type 42 font's glyphs are drawn
from the TrueType outlines its sfnts strings carry (Adobe Technical Note
5012), by the glyph index CharStrings gives their names, spaced by their
own widths or the show operator's numbers; charpath and stringwidth read
them too. A Type 3 descendant runs its procedures, and a descendant found
by name sets its character as type at the composite font's size.
2026-11-09 02:24:54 +07:00
el-j d499fb25da Fix #6205: support Registration and named swatches in AI art styles
Resolve missing Registration (Xs, XS, Xz, XZ) and named color (Xx, XX)
operators in AI ArtStyle filter definitions so objects styled with
Registration swatches correctly resolve their paint instead of falling
back to the previously set graphics state color.

Also exclude Registration paints from color-model inference when read via
Xx/XX, and correctly extract tint (penultimate operand) for name-free Xz.
2026-11-09 02:03:09 +07:00
M Balazs PC 7f7c4cf9ad .ai import: test every paint mask art takes, and that it stays in the mask
The mask test now checks the whole fill and stroke (colour, overprint,
width, cap, join, miter, dash) and the fill rule against the owner's,
and that an object after the mask's dictionary paints with the owner's
paint, not the paint the mask's art set. open_mask_object gets a doc
comment.
2026-11-09 02:03:02 +07:00
M Balazs PC 500ff6ed4c .ai import: opacity mask art without its own paint takes the paint before its dictionary
A dictionary's state starts afresh, with a black fill. Mask art is written
going on from the paint before the mask's dictionary, which the writer leaves
out when it doesn't change, so a mask rectangle with no colour of its own
came in black and hid the white letters it masks. Mask art now starts from
the paint (fill, stroke, line style, fill rule, overprint) before the
dictionaries it is in; lock, visibility and transparency still start afresh.
2026-11-09 02:03:02 +07:00
Don Omondi d4f6fa0744 EPS/.ai editing data: art-style strokes aligned inside or outside keep their alignment
Squashed from #6010 onto main.
2026-11-09 02:03:01 +07:00
NetterSilverandClaude Opus 5.5 05a242ff77 Scale the EPS operation budget with the program's length
Fixes #5363

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 02:03:00 +07:00
dotCooCoo fb848d28ac .ai import: objects in pattern and brush art take the file's art styles
A pattern's definition (in either form) and a brush's art were read
without the file's art styles, so an object there with a style came in
as the look it draws, with a "drawn look" note, while the same object on
a layer or in a symbol came in as itself with its effects live. The
three definition readers now take the art styles as the symbol reader
does, and an object in a pattern's art with a style in the file's style
list keeps that graphic style's id.

An art styles section inside a symbol's, a pattern's or a brush's art
is now skipped, so it no longer replaces the file's own art styles.
2026-11-09 00:36:09 +07:00
dotCooCoo 7b06cc070b .ai save: the editing copy keeps Standard Vertical Roman Alignment
The editing-data writer wrote a run's key 35 only for Tate-chu-yoko (3).
Type with Standard Vertical Roman Alignment, on or off, was saved with no
key, so the setting was lost from the editing data. Every run now carries
key 35 in the form the reader reads it (crates/eps/src/import/ate.rs,
Texts::style, lines 669-670): 1 when the setting is on, 2 when it is off
and 3 for Tate-chu-yoko. The document's defaults and the normal character
style carry 1.

Key 35 holds one value, so a run with both Tate-chu-yoko and the setting
is written as Tate-chu-yoko, and the save reports the type attributes
warning for it.

- writer/texts.rs: baseline_direction() gives each run's value;
  normal_char() writes 1; loses() and TYPE_ATTRIBUTES name the setting.
- tests.rs: a vertical and a horizontal object with the setting on, off
  and Tate-chu-yoko come back the same through the reader, lay out as
  before, and carry the expected keys; a run with both is reported.
- docs/file-format-parity.md: the .ai save section lists the written
  values.
2026-11-09 00:35:51 +07:00
dotCooCoo cae5b356c7 .ai import: a palette gradient swatch's paint is not linked to itself
The editing-data reader set the paint of each gradient swatch in the
palette to link to the swatch's own name. The default gradient swatches
and a gradient swatch given a new gradient with swatch.edit hold their
gradient without a link, so a document saved as .ai and opened again
had swatches that differed from the ones it was saved with. The swatch
now holds the gradient alone. Art filled with the gradient is not
changed: it still links to a swatch by the gradient's name.

The fix also applies to a .ai whose layers are not read, such as a
gradient library, where only the palette comes in.

Three tests cover a hand-written palette with a gradient swatch named
by its gradient and one with a name of its own, a new document's four
gradient swatches written by the editing-data writer and read back, and
a gradient library without layers.
2026-11-09 00:35:12 +07:00
dotCooCoo a37ad90e6c .ai import: type keeps Standard Vertical Roman Alignment from its editing data
The modern type reader read key 35 of a character style only for
Tate-chu-yoko (3). A run whose key 35 is 1 (Standard), in the run, its
character style or the document's defaults, now opens with Standard
Vertical Roman Alignment on, so its half-width characters stand upright
in vertical type. Horizontal type keeps the mark with no change to its
layout. A value of 2 (rotated), 3, any other value and no key at all
leave it off, VectorCraft's default.

- ate.rs: Style reads the flag and passes it to CharStyle, with a unit
  test over defaults, character styles, run overrides and bad values.
- tests_native.rs: a vertical and a horizontal story with Standard,
  rotated and unset runs; the test of values the reader ignores also
  checks the flag.
- import_fuzz.rs: the vertical fixture also checks the flag.
- docs/file-format-parity.md: one line on the mapping.
2026-11-09 00:35:02 +07:00
dotCooCoo 867c836fe1 Legacy .ai: the type attributes warning covers kerning, margins, Align to Path and end brackets
The legacy .ai writer gives the type attributes warning when a text
object loses something it cannot hold. It did not give it for several
attributes that it does not write:

- Align to Path set to Ascender, Descender or Center.
- An end bracket before the end of an open path, or short of once
  around a closed path. A legacy text path holds the path and the start
  point only (AI7 File Format Specification, section 15).
- Manual kerning other than 0, which is written as kerning off, and
  optical kerning.
- Optical Margin Alignment, System Layout and fractional widths off.
- A second subpath in an area type frame or a type path. The writer
  writes the first subpath only.

The warning now names each of these.
2026-11-09 00:34:44 +07:00
dotCooCoo 63dfcba8ba Envelopes: a top object's sides share the content by control polygon length
A top-object envelope splits its outline into four sides at four corners
and maps the content onto the Coons patch of those sides. The sides
shared out the content by length, and the corners were the anchors or
curve points nearest the bounding box corners.

Each piece of a side between anchors and corners now takes a share of the
content's width or height in proportion to the length of its control
polygon, spread along the piece by its curve parameter. A straight
segment spreads it evenly by length. A rounded rectangle stays close to
the identity, and the content follows the curves of a side whose segments
differ in size.

Each corner is now the nearest anchor to its bounding box corner among
the sharp anchors that point out of the outline and lie within 47.5% of
the box's width and height from that corner. A handle shorter than
0.01 pt, or tangent unit vectors that differ by 0.01 or more in x or y,
makes an anchor sharp, and the first one along the outline wins a tie.
Without such an anchor, the corner is the outline's nearest point to the
box corner, moved onto the end anchor of its segment within 0.5% of the
segment's parameter. Envelopes opened from .ai files keep the corners the
file saves.

These are VectorCraft's own rules. Existing top-object envelopes redraw by
them. Gradient meshes made from a path keep their sides measured by length.

- crates/doc/src/live/mesh.rs: Coons::from_top_object and the corner
  search, used for top-object envelopes.
- EnvelopeKind::TopObject gains an optional `corners` field (anchor
  indices); the .ai importer fills it from the mesh's corners.
- Params text of object.envelope.makeWithTopObject, docs/mcp.md
  (Envelopes) and a progress line in docs/target-app-parity.md.
2026-11-09 00:31:06 +07:00
dotCooCoo f2e6c6d219 .ai save: the editing copy keeps symbols and their instances
Squashed from #6136 onto main.
2026-11-09 00:28:03 +07:00
dotCooCoo a409f34ebe .ai import: placed PDF and .ai pages open as placed documents
Squashed from #6083 onto main.
2026-11-09 00:26:34 +07:00
dotCooCoo f5be571d47 .ai import: Bas Relief opens with its settings
Squashed from #5227 onto main.
2026-11-09 00:25:27 +07:00
dotCooCoo 95678340b0 .ai import: a symbol's linked color named like a color group is renamed
A global or spot color that a symbol library's art links to and whose
name a color group of the document has now comes in under a free variant
of the name, which the art then names, as for a name another swatch has.
Swatches and color groups share one set of names.
2026-11-08 04:34:49 -08:00
Don Omondi d87844bba6 Merge remote-tracking branch 'upstream/main' into fix/eps01-editing-overlay
# Conflicts:
#	docs/gaps.md
2026-11-08 06:36:42 -05:00
Don Omondi ba1abb73b0 .ai import: tests for Pattern brushes' number counts and an older Art brush's first number
A Pattern brush reads its scale from 3 or more numbers (the older form's
8 too) and isn't read with fewer; an 8-number Art brush is the older form
whatever its first number. The module notes say the count tells the form.
2026-11-08 03:51:30 -05:00
Don Omondi 6abe10583a Merge remote-tracking branch 'upstream/main' into fix/ai-import-brushes
# Conflicts:
#	docs/file-format-parity.md
2026-11-08 03:35:43 -05:00
dotCooCoo cdd2cc234f fmt: wrap the .ai reader's document imports as rustfmt does 2026-11-07 23:29:09 -08:00
dotCooCoo 272a34bee5 Merge main into feat/ai-editing-data-s6b2 2026-11-07 23:24:31 -08:00
dotCooCoo c85b286994 .ai import: Scatter brushes in Tints open in Tints, and brush art drops its frame
A Scatter brush in a .ai or EPS file's editing data always came into
the Brushes panel keeping its art's colors. A Scatter brush whose last
15 numbers start with 1 now opens in Tints, and one whose numbers start
with 0 keeps its art's colors. Other values are not read and keep the
art's colors. The field before the art's name does not decide it: an
object whose first field is 1 is read by its 15 numbers, and a Scatter
object with 8 numbers is still left out.

A brush's art usually comes in a group after its frame, an unpainted
rectangle around the art. The reader kept that rectangle as part of
the brush's art. It now leaves out a frame at the art's bounds (within
1/1000 pt): an unnamed, unpainted rectangle with its four corners in
order, closed, or open with its first corner again at the end, and no
settings of its own (shown, unlocked, at full opacity in Normal, with
no effects, note or other attributes). A wider frame stays in the art,
and so do a path that crosses the corners, an open path along three
sides and a rectangle with settings of its own. A group left with one
object becomes that object, unless it has a state or look of its own
(hidden, locked, isolated, knockout, an opacity, a blend mode, a name,
a mask, a note or another setting, or an appearance); then it stays a
group without the frame. The rule applies to every brush art section.

Existing files with such brushes now open with Tints and without the
extra frame path in the brush's art. With an exact frame, the pieces
that a Scatter or Art brush draws along a path stay where they were.

The Scatter, Art and Pattern brush colorization options, Tints among
them, are described on pages 281-282 of
https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf#page=281

Tests cover Scatter brushes whose numbers start with 1 and 0, with a
first field of 1 and 2, and an 8-number object. They cover frames kept
or left out (exact, wider, open, filled, named, crossing the corners,
along three sides, with a note, Show Center or hidden), a group's own
state and note kept, and the pieces a Scatter and an Art brush draw
along a path with and without the frame. The import fuzz test reads
brush art after frames of any corners and paint.
2026-11-08 13:23:23 +07:00
dotCooCoo c8701a18cd .ai import: legacy binding punctuation opens as Burasagari's Force setting
The legacy reader skipped `bindrepeat Tg` (section 15.3.4 of the version 7
file format specification). It now reads the binding punctuation bit:
1 and 3 set the paragraph's Burasagari to Force, and 0 and 2 set it to
none, which is VectorCraft's own mapping. The value is modal, lasts into
the next text object, and an operand other than 0 to 3 leaves it as it
was. The repeat character, `TG`, `Xu` and `Tu` are still skipped.

docs/mcp.md and the `ParaStyle::burasagari` doc comment name the legacy
`.ai` case as the exception to imported text keeping `none`.
2026-11-08 13:21:08 +07:00
dotCooCoo c94aec3fff Envelopes: type maps from its glyph outline bounds
An envelope mapped type from its laid-out box: ascent to descent for
point type, the frame for area type. The distorted glyphs then filled
less of the envelope than other art does, with room above the capitals
and below the baseline.

The envelope now maps type from the bounds of its glyph outlines,
underlines and strikethroughs when the type is on its own, in groups
and layers, or used as a clipping path. Inline graphics count by their
boxes. Rotated type and type in perspective map from their outlines as
they are drawn. Make with Mesh,
Make with Top Object and Reset with Mesh without Maintain Shape use
those bounds. Type that draws nothing keeps its laid-out box, and so
does type inside any other object, such as a blend, a repeat or a
symbol instance. Shrink Text to Fit type is measured as it is laid out
on the page.

vectorcraft-text's envelope_ink measures the type, and
register_envelope_ink registers it with the document crate. The engine
session, Renderer::new, live evaluation in vectorcraft-effects and the
.ai reader register it. The document crate keeps the measured boxes per
thread, by object and by a hash of the content, and measures again when
the type, the fonts, the hyphenation exceptions or the transform's
scale or rotation change.

This is VectorCraft's own rule. Envelopes of type made in VectorCraft
and in saved files map from the glyph outline bounds instead of the
type's laid-out box, so they redraw differently.
2026-11-08 13:20:19 +07:00
dotCooCoo 78f95c3ae9 Merge main into feat/ai-symbol-libraries 2026-11-07 22:03:00 -08:00
dotCooCoo 79dcf3b1ff .ai save: the editing copy keeps live effects
Squashed from #6150 onto main.
2026-11-08 12:59:34 +07:00
dotCooCoo 089d6c3e74 .ai import: warped type in a file with artboards is compared on the first artboard
The page that warped type is compared with was read after the artboard
list had been moved into the document, so a file with an artboard list
fell back to its crop marks or its art's box. The page is now read
before the list is moved.
2026-11-08 12:59:33 +07:00
dotCooCoo 7036d44603 .ai import: warped type bound to a text variable stays type
Warped type that a text variable binds stays type with the effect live,
so it keeps its binding. Before, a failed comparison replaced it with
its drawn art and the binding was skipped.

When drawn art replaces warped type with a wrap setting, the wrap shapes
are checked against the file's limit on wrap work again, so a file whose
drawn art would go past it opens from its page, as one with too many
text objects does.
2026-11-08 12:59:33 +07:00
dotCooCoo d54d4657b9 .ai import: warped type that draws differently from the file opens as its drawn art
A Warp on type, on the object or on one of its fills or strokes, opened
as a live effect with no check. Where the live warp draws a different
shape from the art saved in the file, the type moved and changed size.

Warped type is now compared with its saved art once it is laid out,
with the comparison and the per-file budget that envelopes use. Type
that draws as its saved art stays editable with the effect live. Other
warped type opens as that art, not as editable type, with one note that
counts them, and saving over the file asks first. The art's box goes
back into the comparison with the page. Type stays live without a
comparison when its saved art paints nothing or draws type of its own,
and in frames of threaded type.
2026-11-08 12:59:32 +07:00
dotCooCoo 888bf32f82 Blend: Smooth Color counts a step per level of color change
Smooth Color now counts the steps between each pair of keys from the
keys' colors and visible bounds, which changes the step count of most
Smooth Color blends. The counts are VectorCraft's own values:

- When the keys' solid fills or strokes differ (in groups, member by
  member; in text, run by run), the count is 255 times the largest
  change of one channel as a fraction of its range, a half step
  rounding down, from 1 to 254. An RGB channel's range is 256 values,
  CMYK and gray count in percent, and two tints of one swatch by their
  tints. It is at most eight per point of the largest of the keys'
  widths and heights and the distance between their centers. Black to
  white was 128 steps and is now 254.
- When the colors match, a solid paint meets no paint, or a key holds
  a gradient or a pattern, the count is the largest distance between
  corresponding visible bounding-box edges divided by the keys' mean
  width or height, rounded, less one, from 1 to 1000. Two 20 pt
  squares 200 pt apart were 100 steps and are now 9.

Keys without bounds make the color count when their colors differ,
and 1 step otherwise.

A blend nested in another blend's keys counts toward the step limit
by its color steps when its colors differ, and as up to 1000 steps
when they match (256 before). An outer blend of 200 steps between two
such same-color Smooth Color blends now draws 99 steps.

A Smooth Color blend in an .ai file whose drawn steps follow this rule
now opens as a live blend.

Blend Options: https://web.archive.org/web/20241217212108/https://helpx.adobe.com/illustrator/using/blending-objects.html
Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-08 12:59:32 +07:00
dotCooCoo 81a7a68388 .ai import: legacy type in CJK fonts decodes by the font's CMap
Legacy type in a composite font such as HeiseiMin-W3-83pv-RKSJ-H was
decoded one byte to a character, so Japanese, Chinese and Korean type
opened as unrelated single-byte characters, and the font name kept the
CMap.

Tf now finds a predefined CMap at the end of the font name, unless a TZ
gives the font encoding pairs, and the font is the name without it.
Strings in that font are read by the CMap's code space, mapped to CIDs
with the predefined CMaps craft-pdf carries, then to Unicode by the
character collection's CID-to-Unicode CMap. Codes the CMap doesn't map
are dropped. A -V CMap is read with its -H form's codes. Codes in the
Unicode CMaps (UCS2, UTF8, UTF16, UTF32) are read as Unicode; those
helpers follow mockydevs' #6067. A 0x0D byte ends a paragraph and a 0x09
byte is a tab in these fonts too.

The predefined CMaps craft-pdf doesn't carry (RKSJ, 78-RKSJ, 78ms-RKSJ,
78-EUC, 78, Add, Ext, NWP, GB, CNS1, KSC, B5 and the Hong Kong B5 sets
other than HKscs-B5) are read with the nearest
carried CMap, which can give a different character for some codes.
GBT, GBT-EUC, GBTpc-EUC, CNS2 and KSC-Johab type still decodes byte by
byte, with the CMap removed from the font name.

Docs: docs/file-format-parity.md.
2026-11-08 12:47:52 +07:00
dotCooCoo 0f39b54a05 .ai import: type on a later page of a PDF of some artboards goes to its artboard
A PDF saved with some of its artboards has a page for each of them, while
the editing data keeps them all. The importer took the pages after the
first to show the artboards right after the first page's artboard. In a
PDF of artboards 2 and 4 of 4, the second page's type went to artboard 3.
In a PDF of artboards 1 and 3 of 4, the second page's type went to the
left edge of artboard 2.

Each page after the first now goes to an artboard of its size that comes
after the previous page's artboard and leaves an artboard for each later
page. The artboard chosen for the first page also leaves one for each
later page. When several artboards fit a later page, the importer
compares their art with the page and takes the closest one, or the first
of them on a tie. It compares at most 16 artboards for all the later
pages together, the same limit it uses for the first page. A later page
with no comparisons left takes the first artboard that fits it.

When the PDF has fewer pages than artboards and its first page shows the
first artboard, the first page's type and page art still go to the art's
box or to the editing data's artboard.
2026-11-08 12:21:54 +07:00
dotCooCoo 1d32aff63f Tests: the CSS, glow and .ai Zstandard guards count work instead of timing it
Three tests failed on a loaded machine because they asserted elapsed
time. Each now counts the work that the slow behavior it guards
against would repeat, with a counter compiled only into tests:

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

The hang guards for invalid dash patterns, text layout and script
checks of a pipe wait 300 s instead of 20 s.
2026-11-08 12:21:06 +07:00
dotCooCoo db137418c0 Merge main into feat/ai-editing-data-s6b2 2026-11-07 20:55:44 -08:00
dotCooCoo 37245779aa .ai import: legacy overflow breaks keep the direction and paragraph attributes they were read with
The reader holds an overflow paragraph break (`(\r) TX`) until more type
follows it. When that type came, the break took the state in force then,
not the state where the break was read:

- A story that started with the break took the `TV` direction of the
  characters after it, so `1 TV (\r) TX 0 TV (Body) Tx` opened horizontal
  and gave no warning.
- A break read in the other direction inside a story did not count as a
  direction change.
- The empty paragraph that the break ended took the attributes of the
  paragraph after it, while `(\r) Tx` keeps its own.

The pending break now records `TV` and, when it ends an empty paragraph,
that paragraph's attributes.
2026-11-08 11:22:39 +07:00