Commit Graph
5197 Commits
Author SHA1 Message Date
aiongg eefdfbb688 Docs: image cache keeps the levels drawn; canvas decodes in the background 2026-11-14 03:32:18 +00:00
aiongg 28fdd9a50e Canvas: render again for finished image decodes in batches
The canvas renders with RenderOptions::defer_images and keeps the image
generation each texture was rendered at. Finished background decodes
are taken up at most every 250 ms and not while a render is in flight,
so a page of photos decoding one after another re-renders a few times
instead of once per photo, and a newer request doesn't drop the render
in flight before it is shown. The eyedropper still decodes the image it
samples.

Based on #2051 by texjer.
2026-11-14 03:32:08 +00:00
aiongg 9563e93bd2 Images: keep only the levels drawn and decode off the canvas thread
The image cache kept the full-resolution level 0 of every TIFF, PNG,
PSD and CMYK JPEG, and of any image shown at High Quality: about 48 MB
for a 12 MP photo, so after a dozen photos every edit evicted and
decoded them again on the render thread.

- mip keeps only the level drawn. It remembers each asset's full size
  (from its header, then from a decode), builds a coarser level from a
  finer cached one, and tries the reduced JPEG decode before a full one.
  When an image is zoomed into (a finer level asked for while a coarser
  one is cached) it keeps the full decode too, up to 96 MB, so the
  following steps halve it instead of decoding again.
- With RenderOptions::defer_images (the canvas) the image draw uses
  mip_now: a level that isn't cached is decoded on one of three
  background threads, newest request first, and a coarser cached level
  or a light box is drawn meanwhile. images::generation advances and
  the waker runs when a decode finishes. A decoder panic marks the
  image bad. Background decodes reserve their peak bytes at the same
  gate as warm, so they wait while decodes in flight hold half the
  budget.
- An image asked for again within 2 s of its decode (evicted at once:
  the view holds more than the cache) isn't decoded again until then,
  in place or in the background, so the canvas never decodes on its
  own thread.
- warm counts an asset as cached when the level each drawing takes, or
  a finer one, is cached, and sizes a job by the levels it keeps. It
  leaves a decode larger than the whole budget to the render, and the
  canvas skips it.
- A render that deferred an image isn't kept as the CMYK view snapshot.

Based on #2051 by texjer.
2026-11-14 03:32:00 +00:00
aiongg 445eb502ec Render: a PDF layer variant keeps its identity while anything holds it
The image cache keys decodes by the bytes' address, so a variant rebuilt
as a new Arc misses the decodes it already has. With decodes in the
background, more variants on screen than the variant cache kept would
decode and re-render without end. Variants are now looked up weakly,
with the 64 most recent held strongly.

Based on #2051 by texjer.
2026-11-14 03:29:47 +00:00
aiongg 34f0b702e5 Images: a decoder panic in a warm worker marks the image bad; checked level lookup 2026-11-14 03:15:24 +00:00
autocraft-bot[bot] 0f924b71ae Merge main into fix/parallel-sync-image-decode 2026-11-13 21:12:07 +00:00
Zinadin Zidan 40cc78d8f8 Text Frame Options: insets and gutter in the ruler units (#2739) (#2754)
The Text Frame Options dialog filled its fields from the document's
ruler unit but never set the dialog's own unit, so it stayed at picas.
The spinner fields (Top, Bottom, Left, Right, a non-rectangular frame's
Inset, the column width and gutter, Paragraph Spacing Limit) showed
their values in picas whatever Preferences > Units & Increments said,
and a bare number typed in any measure field, Min under First Baseline
included, was read as picas.

The dialog now takes the document's horizontal ruler unit when it opens
and keeps it while open, as the Rectangle dialog does (#1964).
2026-11-13 21:08:26 +00:00
AlPaka1234 618c191166 Window: restore hidden panels when selecting panels, control bar or workspaces (#2742) (#2746)
Fixes #2742.

Make UiState::hidden_panels session-only (#[serde(skip)]) and clear hidden_panels when opening or revealing panels, toggling the Control bar while hidden, or loading/resetting workspaces. Treat hidden panels and flyouts as not visible in checked/toggle checks so Window commands unhide them instead of toggling them off invisibly.
2026-11-13 20:58:53 +01:00
AlPaka1234 dbdc5b697a Tools: snap Gap tool drags to active snapping settings (#2637) (#2749)
* Tools: snap Gap tool drags to active snapping settings (#2637)

* Tools: reject unreachable Gap tool snap targets beyond resize clamp

* Tools: preserve raw Gap tool delta when no reachable snap matches
2026-11-13 20:58:31 +01:00
.CooCoo cf4efa1833 Text Frame Options: every dropdown and field has an accessible name (#2733)
* Text Frame Options: every dropdown and field has an accessible name

The Columns and Align dropdowns on the General tab, and the dropdowns and
text fields on the Column Rules, Baseline Options and Auto-Size tabs, had
no accessible name, so a screen reader announced them without a caption
and kittest's accessibility check failed on the dialog. Each one now takes
the caption shown next to it. The non-rectangular frame test runs with the
check on and opens every tab.

* Text Frame Options: numeric fields take their captions as names

The Number, Width, Gutter, Maximum, inset and Paragraph Spacing Limit
fields on the General tab were named by their internal ids, so a screen
reader announced tfo_columnWidth and its spinner read as Increase
tfo_columnWidth. Each field and its spinner buttons now take the caption
shown beside it.

* Text Frame Options: test the numeric editors' accessible names

The test checked only the spinner buttons' names. It now also finds each
numeric editor of the General tab (Number, Width, Gutter, Maximum, Paragraph
Spacing Limit, the four sides and Inset) by the text-input role and its
caption.
2026-11-13 16:27:26 +00:00
FredApps f24fc1e72b CI: drop release.yml's copy of the git-sources job (#2634)
The shared release.yml runs the same check before any build.
2026-11-13 14:05:02 +01:00
FredApps ed43b4a6ab Merge pull request #2732 from dotCooCoo/fix/followup-2648
Compose: vertical justification in a shaped frame finds a band shorter than one scan step
2026-11-13 10:28:01 +01:00
dotCooCoo 7ea1dff1f8 Compose: vertical justification in a shaped frame finds a band shorter than one scan step
In a shaped frame, Center, Bottom and Justify searched for the room the
text can take in at most 512 even steps, so a band that the lines fit in
over less room than one step could be missed, and the text then stayed
higher than it could sit. In a frame with more than 512 pt of room one
step is more than 1 pt.

The search now also tries the room at which a moved line's band reaches
one of the outline's corner heights, and a room on each side of each of
those. When there are more than 256 such breaks (a curved outline with
many corners), the search keeps the even steps alone.
2026-11-12 18:38:14 -08:00
dotCooCoo 37a7076daf Images: count a JPEG cached at the reduced levels it is drawn at as cached
A JPEG drawn small is decoded at a reduced level and has no full-size entry, so every later render queued it again and started decode threads that found it cached.
2026-11-12 15:31:18 -08:00
dotCooCoo dbb334c529 Images: pick the images to decode ahead with the scaled stroke and effect bounds
The draw path scales an item's stroke extent and effect outset by its transform before culling; the pass that picks images to decode ahead used the unscaled values, so a scaled image whose stroke or shadow reaches the view decoded on the render thread.
2026-11-12 15:27:11 -08:00
dotCooCoo 28d8528283 Images: fit the one-at-a-time warm test to the decode headroom
With the 3/2 headroom on each decode, a 6,500 pixel square PSD reserves more than half the budget, so the small image can no longer decode beside it and the test failed before calling warm. 5,000 pixel square images still reserve more than half the budget as a pair and leave room for the small image beside one.
2026-11-12 15:23:04 -08:00
Dylan M. Taylor 2eff7e822f Name reduced JPEG estimate API 2026-11-12 17:55:36 -05:00
Dylan M. Taylor 1c3285d8a3 Count progressive JPEG buffers in warm budget 2026-11-12 17:55:11 -05:00
Dylan M. Taylor 067fc97128 Use iterator for reduced mip accounting 2026-11-12 17:45:02 -05:00
Dylan M. Taylor eb4fdc3096 Merge latest upstream into parallel image decode 2026-11-12 17:42:28 -05:00
.CooCooandDylan M. Taylor 437c532511 Compose: count endnote, anchored object and cross-reference marks once per story (#2705)
* Compose: count endnote, anchored object and cross-reference marks once per story

Each paragraph of a story with endnotes, anchored objects or
cross-references counted the marks before it from the story start, so
composing the story read its text once per paragraph. The marks before
each paragraph are now counted in one pass when composition starts.

* Compose: test cross-reference mark counts past the first paragraph

The mark-count fixture now puts a cross-reference in every paragraph, so the
test checks nonzero cross-reference counts and that each paragraph shows its
own cross-reference's text.

---------

Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
2026-11-12 17:42:10 -05:00
Dylan M. Taylor 764b06526b Estimate reduced JPEG warm reservations 2026-11-12 17:41:55 -05:00
Dylan M. Taylor 33f0c21510 Estimate reduced JPEG cache and decode sizes 2026-11-12 17:40:51 -05:00
.CooCoo 0f792e1fe9 Compose: vertical justification moves an object kept within its column with its line (#2720)
* Text wrap: an object kept within its column moves with its line

Vertical justification treated an object positioned from its anchor and
kept within its column as fixed, so its wrap stopped Center, Bottom and
Justify where the object started, although the object moves down with
its anchor's line until it reaches the column's bottom. A line now moves
down at most the room above the object plus the distance the object can
move before the column stops it.

The story's object-mark positions are now collected once per composition
instead of once per frame that holds an anchored object.

* Compose: Justify keeps the room under an object held at the column's top

An object kept within its column and held at the column's top moves only
after its anchor's row has moved down far enough to bring it off the top.
Justify took that distance off a later line's room even when the anchor's
row moved less, so with the anchor in the first row, which stays in place,
the later line stopped that distance short of the object's wrap. A line now
moves its room plus how far the anchor's row moves past that distance.
2026-11-12 17:32:12 -05:00
Dylan M. Taylor f90cd8835d Merge latest upstream into parallel image decode 2026-11-12 17:28:47 -05:00
.CooCoo 67a5cb273e Tables: table commands are disabled with the caret in footnote text (#2706)
Footnote text is addressed as a cell, so with the insertion point in a
footnote the table commands (Insert Row, Delete Column, Merge Cells,
Table Options, Select Table, table styles) were enabled and then failed
with "no table 18446744073709551615". They are now disabled with "place
the insertion point in a table", as the cell commands already were. The
caret's footnote offset is no longer looked up in the main story either,
where it could land on a table's paragraph. table.get and the other
commands that take any selection report the same message instead of the
footnote address. A call that names a table still runs.
2026-11-12 17:27:13 -05:00
.CooCooandDylan M. Taylor 5fb75aaefc Properties: Text Frame section shows inset spacing and vertical justification (#2661)
* Properties: Text Frame section shows inset spacing and vertical justification

Properties › Text Frame now shows the frame's inset spacing under the
columns: Top and Bottom, a link that makes all four the same, then Left
and Right. A non-rectangular frame shows one Inset, the distance its text
keeps from the outline. Four buttons set Vertical Justification to Top,
Center, Bottom or Justify. Type on a path shows neither.

The section also shows with the Type tool in a frame. There it changes
the frame that shows the insertion point, or every frame that holds part
of the selected text, as Object › Text Frame Options does.

* Properties: one Inset for a non-rectangular frame keeps the other sides

The single Inset sets only the top inset, the one kept from the
outline. A rectangular frame that holds part of the same selected text
keeps its own sides.

* Properties: the one Inset field and the composer share the frame outline test

FrameOutline::of_item builds an item's outline with its corner options
applied. The composer uses it to keep the top inset from the outline,
and the Properties panel uses it to decide whether a frame shows one
Inset, so the two give the same answer for every frame.

* Text Frame Options: frames taken while typing stay with their document

OK is refused when another document became active after the dialog opened, so the saved frame ids do not change frames in that document.

* Properties: the inset link starts linked again after Revert

The link's state is kept per document state instead of per document, so
a frame with equal insets in the reverted document starts linked.

* Text Frame Options: frames taken while typing are refused after Revert

Revert keeps the document's id but replaces its state, so a new frame
can reuse a saved frame id. The dialog now ties the frames to the
document state, and OK is refused after Revert as it is in another
document.

* Properties: the one Inset of a non-rectangular frame sets all four sides

The composer bounds a shaped frame's text by its rectangle less all four
insets as well as by the outline, so a single Inset that changed only the
top left hidden sides in place: lowering a uniform 96 pt inset to 0 kept
text overset. When every frame the field changes is shaped, it now sets
all four sides. With a rectangular frame among them, only the top inset
changes and each frame keeps its other sides.

* Fix text frame dialog merge resolution

* Keep text frame preview targets fixed

* Fix target field key inference

* Tests: Text Frame Options show four sides for text through an oval and a rectangle

With the Type tool and text selected through an oval and a threaded
rectangle, with the oval as the caret's stored frame, the dialog shows
the four inset sides, a changed Top leaves the rectangle's other sides
as they were, and the link stays off while the rectangle's sides are
uneven.

* Properties: the inset link starts off when another frame has uneven sides

With selected text that runs through several frames, the link of Text
Frame > Inset Spacing started linked when the frame the section shows had
four equal insets, and a changed Top then set all four sides of every
frame. The link now starts linked only when every frame the field changes
has four equal insets. Its state is kept for that set of frames, so a link
turned on for one frame does not carry over to selected text that also
runs through a frame with uneven sides.

---------

Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
2026-11-12 17:27:08 -05:00
.CooCoo 66b8c8ba3e Object styles: checks and fixes for corners, strokes and the style dialog (#2721)
- Corner options use path order for a path with more than one subpath, so
  a second rectangle no longer takes the first one's corner order.
- IDML export writes EnableStrokeAndCornerOptions="false" for a style
  without corners, so it does not take corners from its based-on style on
  import.
- style.object.create and style.object.edit refuse a stroke gap tint
  outside 0 to 1 and a stroke type field that its kind does not take, as
  object.stroke does. An edit that changes nothing returns restyled: 0.
- New Object Style and Object Style Options read a bare number in the
  document's ruler unit, keep a Text Frame General measurement that was
  not edited at its exact value, and New Object Style refuses a name
  another style has.
- Clear Overrides in the Object Styles panel is enabled when any selected
  object differs from its style.
2026-11-12 17:25:39 -05:00
.CooCoo 6923c220ad Text wrap: Bottom and Center move a line down between a contour's arms (#2719)
A line set in the open middle of a U-shaped contour now goes down to the
contour's base. The clearance check tests the occupied parts of the
contour in the line's band, as line setting does, instead of its outer
extent across both arms.
2026-11-12 17:24:40 -05:00
.CooCoo c803ea80b1 Compose: vertical justification in a shaped frame tests lines to their baselines (#2708)
Center, Bottom and Justify tested every moved line except the last against
the frame's outline down to its descent, so an outline that narrows under an
earlier line held the text higher by that line's descent. Each moved line is
now tested from its top down to its baseline, and its descenders may cross
the outline, as the last line's already did. Above footnotes and in vertical
frames the descenders still stay inside.
2026-11-12 17:24:34 -05:00
.CooCoo 9595f3d62e Anchored text frames: test nested hosts in groups, footnotes and table cells (#2722)
Adds three tests for a text frame anchored inside another anchored text
frame: the outer frame as a member of an anchored group with a rotation
and a scale, the inner frame in a footnote of the outer frame, and the
inner frame in a table cell of the outer frame. Each checks the inner
frame's spread, transform and page.
2026-11-12 17:23:50 -05:00
Dylan M. Taylor 2794a60bf0 Account for image decode buffers in warm gate 2026-11-12 17:21:20 -05:00
Dylan M. Taylor a656db2be4 Merge upstream changes into parallel image decode 2026-11-12 17:19:59 -05:00
Dylan M. Taylor 448ac91e72 Merge pull request #2707 from dotCooCoo/fix/jpeg-screen-proxies
Images: decode JPEG screen proxies at a reduced size
2026-11-12 17:16:24 -05:00
Dylan M. Taylor 3149db327a Merge pull request #1834 from dotCooCoo/feat/duplicate-layer
Layers: Duplicate Layer copies a layer's settings and objects above it
2026-11-12 17:10:50 -05:00
Dylan M. Taylor 982dbc6c3c Merge main into parallel image decoding 2026-11-12 17:04:58 -05:00
Dylan M. Taylor 49764b0b37 Merge main into reduced JPEG proxy decoding 2026-11-12 17:03:41 -05:00
Dylan M. Taylor 30752d0130 Merge pull request #2709 from dotCooCoo/fix/opaque-image-premultiply
Render: placed images are premultiplied in their decoded buffer
2026-11-12 17:01:32 -05:00
Dylan M. Taylor 6bb9907de5 Use shared French duplicate layer translation 2026-11-12 16:54:46 -05:00
dotCooCoo 2b6fb6fa06 Images: count a decode's buffers at their peak before decoding in parallel
The gate that limits parallel decodes counted each image at its cached
size (4 bytes a pixel and a third more for the smaller levels). A decode
holds more than that while it converts the pixels: an 8-bit image keeps
its RGBA copy beside the decoded or the premultiplied pixels, 8 bytes a
pixel. Two 5,500 pixel square images passed the gate together and could
hold more than half the cache budget at once.

- decode_sizes returns the cached size and the peak size from one size
  read. The peak counts the samples as stored beside their converted
  copy (designcraft_images::decode_bytes_per_pixel): 8 bytes a pixel for
  8-bit color, 10 for 16-bit RGB, 12 for 16-bit CMYK TIFFs and 16-bit
  RGBA, 10 for 8-bit CMYKA TIFFs, 15 for 16-bit CMYKA TIFFs, 9 for
  PSDs. An EPS counts as its TIFF preview. PDF pages, SVGs and EPS
  files without a preview count 8.
- warm budgets the cache with the cached size and gates decodes with
  the peak size.
- Every warm call in the process shares one gate, so the canvas
  worker, thumbnails and exports together stay within half the budget
  while they decode. warm returns the most its own call had decoding.
- An EPS without a preview is sized at the larger of its drawn
  PostScript and its placeholder. The placeholder is clamped to 4000
  pixels on each side, so a 10,000 by 4,000 point EPS that is not drawn
  takes 4000 by 4000 pixels, not the 4000 by 1600 counted before.
2026-11-12 13:53:38 -08:00
Dylan M. Taylor e6ab0d263e Merge branch 'main' into feat/duplicate-layer 2026-11-12 16:43:33 -05:00
Dylan M. Taylor 5518b65320 Merge pull request #2658 from dotCooCoo/feat/vj-baseline-grid
Compose: vertical justification keeps grid-aligned lines on the baseline grid
2026-11-12 16:43:08 -05:00
Dylan M. Taylor 2b305c9c00 Merge branch 'feat/vj-baseline-grid' of github.com:dotCooCoo/designcraft into codex/integrate-2658b 2026-11-12 16:28:06 -05:00
Dylan M. Taylor 9847f972da Update recovery test for explicit write conflict 2026-11-12 16:27:52 -05:00
Dylan M. Taylor 076e653ac3 Merge remote-tracking branch 'upstream/main' into codex/integrate-2658b 2026-11-12 16:27:39 -05:00
Dylan M. Taylor 5931b17bb6 Translate duplicate layer in remaining locales 2026-11-12 16:22:26 -05:00
Dylan M. Taylor 520aeeefe1 Merge pull request #2652 from dotCooCoo/feat/text-frame-options-type-tool
Text: Text Frame Options open from the Type tool and change the frame holding the caret
2026-11-12 16:09:26 -05:00
dotCooCoo fb77a73d93 Compose: Center on the grid takes the fitting step nearest the middle in a shaped frame
When the grid step nearest the centered place puts the lines outside a frame that narrows and widens again, Center now tries the steps on both sides of it, nearest first, instead of only the steps above it.
2026-11-12 13:05:34 -08:00
dotCooCoo 946e3ea4c1 Compose: grid-aligned vertical justification takes only grid steps that fit a shaped frame
In a frame that narrows and widens again, Bottom, Center and Justify now
try lower grid steps until the lines fit the outline, and leave the lines
in place when none does, instead of taking the next lower step unchecked.
A grid whose step is too small to count across the column is now treated
as no grid when a line is placed, so the line keeps a finite baseline.

Tests cover a waist-shaped frame on a 40 pt grid, a tiny-step grid that
starts away from the line, and Justify with a wrapped paragraph under
All Lines and First Line Only.
2026-11-12 13:05:34 -08:00
Dylan M. Taylor 9fe0dd5197 Merge pull request #2666 from dotCooCoo/fix/recovery-off-interface-thread
Recovery: the timer writes recovery data on a background thread
2026-11-12 16:01:55 -05:00