chore: sync upstream (28 Commits) #19

Merged
alf merged 28 commits from chore/sync-upstream-20261105 into main 2026-11-05 21:55:59 +00:00
Owner

Upstream-Sync von upstream/main (28 Commits). Fast-Forward.

  • 4b63d26a A hyperlink can point at a text anchor (#1446) (#1555)
  • 59501a26 An item a parent overrode and deleted is gone from the pages that use it (#1469) (#1558)
  • e16785ea Fix CLI build warnings on Windows (#2356)
  • 0e85e9e5 Esperanto as a text language (#1142) (#1204)
  • 8aceec1f Merge pull request #2348 from Hamza0101/fix/copy-keeps-anchored-frame-text
  • 97e75f8f Apply review fixes
  • 3c8a3230 Copy: the anchored-frame scan skips a copy that keeps no story, and reads each anchored item once
  • 9083bd5a Copy: a text frame anchored in a copied frame keeps its text
  • 4f3bb534 Pages: parents and alternate layouts; export and print layouts and versions (#1342)
  • a7940377 About: credits tables fit without side-scrolling and their columns resize (#2264)
  • 019a89d9 Pass the Linux signing key to the release workflow (#2328)
  • b98687c3 M5: keep out-and-back cubic segments in striped strokes (#2323)
  • ef6c3686 Linux: let the shared makepkg job build the Arch package (#2322)
  • f3bcc886 Linux packages: require libxkbcommon-x11, which winit dlopens on X11 (#2320)
  • fb630909 Merge pull request #2304 from texjer/feat/finder-drop-pointer
Upstream-Sync von `upstream/main` (28 Commits). Fast-Forward. - 4b63d26a A hyperlink can point at a text anchor (#1446) (#1555) - 59501a26 An item a parent overrode and deleted is gone from the pages that use it (#1469) (#1558) - e16785ea Fix CLI build warnings on Windows (#2356) - 0e85e9e5 Esperanto as a text language (#1142) (#1204) - 8aceec1f Merge pull request #2348 from Hamza0101/fix/copy-keeps-anchored-frame-text - 97e75f8f Apply review fixes - 3c8a3230 Copy: the anchored-frame scan skips a copy that keeps no story, and reads each anchored item once - 9083bd5a Copy: a text frame anchored in a copied frame keeps its text - 4f3bb534 Pages: parents and alternate layouts; export and print layouts and versions (#1342) - a7940377 About: credits tables fit without side-scrolling and their columns resize (#2264) - 019a89d9 Pass the Linux signing key to the release workflow (#2328) - b98687c3 M5: keep out-and-back cubic segments in striped strokes (#2323) - ef6c3686 Linux: let the shared makepkg job build the Arch package (#2322) - f3bcc886 Linux packages: require libxkbcommon-x11, which winit dlopens on X11 (#2320) - fb630909 Merge pull request #2304 from texjer/feat/finder-drop-pointer
alf added 28 commits 2026-11-05 21:55:59 +00:00
Rebuilt on the split `fontdb/`: `KOREAN_SANS_FALLBACKS` and `sans_by_name` sit
in `fontdb/fallback.rs` beside `KOREAN_FALLBACKS`, `cjk_chain` takes the style,
`fallback_for_styled` is on both `ScopedFonts` and `FontDb`, and the tests live
in `fontdb/tests/fallback.rs` where that file's other fallback tests are.

`KOREAN_FALLBACKS` is serif-first (AppleMyungjo, Apple SD Gothic Neo, Batang,
Malgun Gothic...), so on Windows the first installed entry is Batang. A document
whose Korean text asks for faces that are not installed -- KoPub Dotum, KHNP
Han Dotum, BM Dohyeon -- came out in a serif face throughout: the report
measured 5,298 characters of one school document turning from sans to serif.

The font is not installed, so its name is all there is to go on. `sans_by_name`
reads the style out of the family name: Dotum (돋움), Gulim (굴림), Gothic
(고딕), Sans, Headline, Graphic and Dohyeon are sans; Batang (바탕),
Myungjo/Myeongjo (명조), Gungsuh (궁서) and Serif are serif. Serif is tested
first, so "Noto Serif KR" is not caught by the `sans` rule.

A run whose requested face is sans draws its fallback from the sans chain, which
leads with the system faces. If the requested name says neither, the
substitute's own name decides, which leaves today's chain in place.

The tests do not depend on which fonts the machine has: they pin the name
classification and the chain order -- the first sans entry comes before Batang,
and the serif chain still leads with a serif face, so a missing Myungjo is not
turned into a sans one.

`fallback_for` keeps its signature and delegates, so the ruby, kenten and image
callers are unchanged.
Review of #1755 caught a real over-reach in my own patch. When a family
cannot be resolved, db.face hands back a bundled face, and I was reading
"Sans" out of its name: FALLBACK_FAMILY is "Source Sans 3", so every
unclassified missing font took the sans chain -- picking Malgun Gothic on
Windows over the Batang fallback that was already there. DEFAULT_FAMILY,
"Source Serif 4", would have forced the other way.

A stand-in face says nothing about what the document asked for, so
style_of_substitute returns None for both and only a face that really
names itself gets a vote. The test pins both families by their constants;
with the old body it fails with Some(true) against None.
Review pointed out that the CSS generic "sans-serif" matched the bare
"serif" needle first and classified as serif. The same ordering hit real
family names: "Microsoft Sans Serif", "MS Sans Serif" and "Lucida Sans
Serif" are sans faces that a Word import can name, and epub's own
generic_family already maps Microsoft Sans Serif to sans-serif.

So rather than special-casing the one hyphenated spelling, the bare
"serif" word now sorts after the sans needles. The Korean serif names
stay ahead of everything, which is what keeps "Noto Sans Myungjo" and
"Myungjo Gothic" serif; the test pins those two along with both "sans
serif" spellings.
My own previous commit introduced this: moving the bare "serif" needle
after the sans list to let "Microsoft Sans Serif" read as sans also let
"gothic" win in "Sweet Gothic Serif", a real serif family, which review
caught.

The rule is narrower than either ordering. A name holding "serif" is
serif, and that outranks the sans indicators; the only exception is the
phrase it ends: "sans serif" and "sans-serif" are the name of a sans
face. So the phrase is removed before the word is looked for, which
keeps both spellings sans and gives every explicitly serif name back.

Korean serif names stay ahead of all of it, so "Noto Sans Myungjo" and
"Myungjo Gothic" are unchanged.
Split out of #2073. winit sends no pointer events during a drag from another
application on macOS, so a Finder drop had no pointer and never found the frame
under it. The host now gives the pointer's place on the screen
(`Services::pointer`, `PointerFn`: the system's points before the UI scale,
from `NSEvent.mouseLocation`), and `take_pending_drops` uses it for the files
that came without a pointer, divided by egui's zoom factor and made relative
to the window.
Fonts: a missing Korean sans face falls back to a sans one (#1692)
rename_swatch_refs visited every page item's fill and stroke but not the swatch applied to a placed greyscale or bitmap image (Graphic::fill), framed or in a table cell. Those images kept the old name, found no swatch, and drew in black.
Split out of #2073. A frame holding its image as an item pasted into it (how a
document from InDesign keeps a photo) takes a dropped graphic as a graphic
frame does: `canvas::drop_frame` uses `Item::takes_graphic` (#2226).
- crates/ui-egui/src/app/input.rs
- crates/ui-egui/src/services.rs
Place: a file dragged in from the Finder fills the frame it was dropped on
The binaries load their windowing libraries at run time, and xkbcommon's X11
keyboard module is a separate library from the core one. Nothing declared it,
so a package installed on a system without it crashes before opening a window:

  thread 'main' panicked at xkbcommon-dl-0.4.2/src/x11.rs:59:28:
  Library libxkbcommon-x11.so could not be loaded.

winit's X11 backend cannot fall back, so the dependency is required for deb,
rpm and Arch rather than recommended.
package.sh built its own Arch package with nfpm while the shared release
workflow also built one with makepkg, because has-arch defaults to true. Both
wrote designcraft-<version>-linux-<arch>.pkg.tar.zst, the draft release merges
the release-* artifacts last-writer-wins, and 0.17.0 shipped the nfpm copy,
which declared fewer dependencies than the makepkg template.

Drop arch from the formats this script builds and remove the archlinux section
from nfpm.yaml, so the shared action is the single builder. It names the
package after the .desktop bundle id, declares the same provides/conflicts
against the AUR packages, and adds the optdepends nfpm could not express.
* M5: preserve out-and-back cubics in striped strokes

A cubic can travel out and back when its endpoints and first control point coincide. Include the second control point in the zero-length guard and cover its offset extent and both stripe bands.

* M5: record striped cubic regression coverage

Record the scoped cubic stroke correction without changing readiness estimates or other progress entries.
* About: credits tables fit without side-scrolling and their columns resize

The Contributors table and the Models table were egui Grids inside a
two-way scroll area: they opened wider than the dialog, so they scrolled
sideways, and Company and Model sat almost touching. Both are now
egui_extras tables. Each column opens at the width of its header and widest
cell, the spare width goes to the text columns (Name; Company and Model),
and every column edge can be dragged. Widening a column grows the table
like a spreadsheet, and only then does it scroll sideways; the header stays
put while the rows scroll. Counts and dates are right-aligned and every
cell is vertically centred.

The sorted column stays highlighted and its direction shows on the
toolbar's sort-order button, so headers no longer reserve room for an
arrow. The opening widths come from fit_widths, a pure function with unit
tests that a fitted table fills its room exactly and that only the text
columns give way when room is short.

The About dialog opens 820 points wide, so every column fits at its natural width.

The workspace egui_extras entry, which no crate used, drops its svg feature, so the table
brings in no second SVG renderer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Apply review fixes

- crates/ui-egui/src/about.rs
- crates/ui-egui/src/credits.rs

* Let every credits column be dragged, and refit changed tables

The last column was egui's remainder column, which has no drag handle, so
it could only be resized from the edge before it. It is now an ordinary
column with its own handle.

A table keeps its column widths, so switching to a longer name left the
Name column at its old width and clipped the text. The table now goes
back to the opening fit whenever that fit changes: a longer widest cell
or a resized window. Dragged widths are dropped at that point.

fit_widths could overflow the room when one column hit the minimum,
because the other columns did not take up its share. Columns now give
way in proportion to their width above the minimum.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Refit credits tables on what they measure, not the fitted widths

Comparing the fitted widths missed the case it was meant for. Name is the
only growing column, so a longer name takes its extra width from the
spare room and the fit comes out the same: a dragged Name width stayed,
clipping the longer name.

It could also drop dragged widths by accident. The fit comes from the
available width inside the scroll area, which scrolling can nudge, so
scrolling a widened table could reset it.

The table now compares the natural widths and the clip rect's width,
which scrolling does not move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
* Pages: more parents and alternate layouts as columns in the Pages panel

Parents
- layout.parents.duplicate copies a parent spread (pages and items) under the next free prefix.
- layout.parents.options also renames the prefix (1-4 characters, no "-") and the name; basedOn is now optional.
- layout.parents.new validates prefix and name and takes basedOn.
- Pages panel: a "+ New Parent" row adds B, C… in one click. The parents' context menu and the panel menu have New Parent…, Duplicate Parent Spread, Parent Options… and Delete Parent, named after the selected parent, or a submenu of the parents when none is selected. A new Parent Options dialog has Prefix, Name and Based on Parent.

Alternate layouts
- Section.layout marks the section that starts a layout; Document::layouts() lists them.
- layout.createAlternate copies one layout (from), not every page; new layout.alternate.duplicate, .delete and .options (rename, resize).
- Each layout starts a spread of its own; sections now follow their pages when pages are inserted or deleted.
- Pages panel: with more than one layout, each is a column side by side with a header (name, size, ▾ menu: create, duplicate, options, delete) and a + column to add one.

Tests cover the commands' happy and error paths, undo, serde, and the panel's menus and columns.

* Pages: address review of parents and alternate layouts

- Deleting a layout's first page keeps the layout's name when another section lands on the same page.
- A parent's full label (prefix-name) must be unique; parents may still share a prefix.
- Alternate layout names round-trip through IDML (Section AlternateLayout / AlternateLayoutLength).
- The Pages panel's selected parent is per document and no longer saved in ui.json.
- Layout ▸ Parent Options… and Alternate Layout Options… open the Pages panel's dialogs.
- The Pages panel computes the layouts once per frame.

* Export: layouts and versions in one export, with presets; print chosen layouts

- file.exportBatch writes the chosen alternate layouts (a file per layout, or all in one PDF) in several versions at once: PDF with any marks, bleed and standard, PNG or JPEG at a resolution. Each version goes in a folder of its own, and file names come from a template ({document}, {layout}, {version}, {date}). Names are made safe and unique. dryRun lists the files; without a dir the files come back as base64 (web).
- Export presets: file.exportPresets, file.saveExportPreset, file.deleteExportPreset, kept in prefs.json. Built-ins: Print + Screen, Press PDF per Layout, Images per Layout, All Layouts in One PDF.
- file.print takes layouts and separateJobs (a job per layout, titled after it).
- File ▸ Export Layouts and Versions… dialog: preset, layouts, versions (add from templates, duplicate, remove, quick PDF choices or custom marks), name template, and a live list of the files it will write. It opens from a layout column's ▾ menu as "Export Layout…". The desktop asks for a folder; the web downloads each file.
- The Print dialog prints chosen layouts, together or separately; the Export PDF dialog fills the page range from a layout.
- crate::io::create_dir_all keeps folder creation inside the automation write root.

* Export: address review of layouts and versions

- An exported image is capped at 80 million pixels, the limit EPUB page images already use.
- The Print dialog stays open and says why when no layout is chosen.
- Clicking a layout column's header leaves Edit Parents before going to the layout's first page.

* Pages, Export: address maintainer review of layouts and versions

- Sections (and the layouts they start) follow a moved page, as they do inserted and deleted
  ones: moving a page across a layout boundary no longer moves the other pages between layouts.
  Delete and move share `settle_sections`.
- Alternate Layout Options resizes only pages whose size changes: renaming a layout no longer
  forces every page to the first page's size.
- Export file and folder names that are Windows device names (CON, NUL, COM1…) get a leading `_`.
- An export without `dir` refuses past 512 MB of files instead of building them all in memory.
- A file that fails partway through an export to a folder says how many were already written.
- Parent Options and Print keep the dialog open, with what was typed and why, when the engine
  refuses.
- A duplicated layout's default name stays within the 100-character limit.
- rustfmt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Pages: Alternate Layout Options sends the size only when it changed

The dialog starts at the layout's first page's size, so the engine couldn't tell a rename from a
resize to that size. The dialog now leaves out a width or height still at its starting value, and
`layout.alternate.options` resizes every page of the layout when a size is given, as before.
The test moves to the UI, where the size was coming from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
clip_doc kept the stories of the text frames among the copied items, and a text frame anchored in a frame's text (as IDML import makes them) is in its host's story, not among them. Its story was dropped from the clipboard, and the paste gave the anchored frame a new empty story. The stories of text frames anchored in the kept stories, and in theirs, are kept too.
From review: a copied graphic keeps no story, so there is nothing to scan; and anchored_items already lists a group's members, so walking each item again visited them twice through unbounded recursion. A text frame inside an anchored group (as IDML import anchors an InDesign group) is tested.
- crates/engine/src/cmd/edit.rs
Copy: a text frame anchored in a copied frame keeps its text
Esperanto in Character › Language (panel, Properties, style options),
mapped to the BCP 47 tag eo, with English-style typographer's quotes.
IDML keeps the name as written ($ID/Esperanto). Not hyphenated yet:
morpheme-based hyphenation follows in #1201.


Claude-Session: https://claude.ai/code/session_01Y9j7XJDCmPc8UZLs9cTxtu

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* An item a parent overrode and deleted is gone from the pages that use it (#1469)

In InDesign, removing an inherited item from a parent page means overriding
it there and deleting the override: the parent's page keeps the item in its
override list with no copy of its own. Only the page being drawn was checked,
so the pages that use that parent still drew the base's item -- a signature
block from parent B over the notice text of every page using parent E.

The override lists of the whole parent chain are checked, not just the page's
own. An override always names an item of a parent further back, so one set
covers the chain.

Rebased onto the post-split main: the parent-item loop moved from render's
lib.rs to draw.rs and the tests to tests.rs.

One thing this does not touch: blendspace.rs checks `page.overridden` the same
way in two places, for deciding which spreads hold transparency. A deleted
override there would count an absent item as present, which can only cost an
unnecessary flatten rather than draw the wrong thing, so it is left for a
maintainer to judge rather than widened into this fix.

* Parent items: one set of overridden items for drawing, PDF and PPTX export, hit-testing, snapping and frame edges

* Override All: an item a parent removed is not brought back as a copy
A hyperlink can point at a text anchor (#1446) (#1555)
Some checks failed
ci.yml / A hyperlink can point at a text anchor (#1446) (#1555) (pull_request) Failing after 0s
nix.yml / A hyperlink can point at a text anchor (#1446) (#1555) (pull_request) Failing after 0s
packaging-lint.yml / A hyperlink can point at a text anchor (#1446) (#1555) (pull_request) Failing after 0s
sonarcloud.yml / A hyperlink can point at a text anchor (#1446) (#1555) (pull_request) Failing after 0s
ci.yml / A hyperlink can point at a text anchor (#1446) (#1555) (push) Failing after 0s
nix.yml / A hyperlink can point at a text anchor (#1446) (#1555) (push) Failing after 0s
packaging-lint.yml / A hyperlink can point at a text anchor (#1446) (#1555) (push) Failing after 0s
sonarcloud.yml / A hyperlink can point at a text anchor (#1446) (#1555) (push) Failing after 0s
4b63d26ac6
* A hyperlink can point at a text anchor (#1446)

A frame or a text range could link to a URL, an email address or a page
number. A page number is the wrong thing to store for "this thumbnail links to
that photo": the photo moves and the link does not. InDesign stores the
destination, and IDML files do too — a `HyperlinkPageItemSource` pointing at a
`HyperlinkTextDestination` is how a report links its thumbnails to the
full-size pictures (#1446).

`HyperlinkDest::Anchor(id)` is a text anchor, the same destination a
cross-reference targets, and the PDF export resolves it the same way: through
the cross-reference index, to whichever page the anchor's text has landed on.
A link to an anchor that no longer exists is left out of the file rather than
pointing somewhere wrong.

`hyperlink.create` and `hyperlink.edit` take `anchor` — an anchor's id or its
name — and a link that is given no name of its own takes the anchor's.

Measured: a thumbnail on page 1 linked to an anchor in text on page 3 exports
`/Dest [<page 3> 0 R /XYZ 0 300 0]`; insert a page in front and the same link
exports page 4, with no edit to the link.

This is the groundwork for the IDML half of #1446 (import and export of
page-item and text hyperlinks), which is the next change and needs a
destination to put them in.

* An anchor a hyperlink cannot mean is refused, not ignored

This PR added anchors as link destinations and did not check that the anchor
exists, so the feature was wrong on its own and review was right not to take the
fix from a PR stacked above it.

`dest_param` returns `Result<Option<_>>` now. `None` still means no destination
was given, which `hyperlink.edit` needs for an edit that only renames; an
`anchor` that is given and names none is an error.

Two behaviours follow. An unknown *id* was accepted outright -- `as_u64`
answered for any number -- so a link was stored to an anchor nobody has and the
export quietly left it out. And on `hyperlink.edit` the `None` read as "no
destination given", so the command reported success and kept the old
destination, which is the case review reported.

An unknown *name* did already fail on `hyperlink.create`, but with "give `url`,
`email`, `page` or `anchor`", which named the wrong problem; it says "no text
anchor X (anchor.list names them)" now.

The test covers the id, the name, both on `edit`, that a refused edit leaves the
destination, the name and the exported page alone, and that a rename naming no
destination still works -- that last one is what `None` is for and it must not
have become an error on the way.

The page-number validation #1564 adds stays there; this is the anchor half,
which belongs with the anchor feature.

* A name two anchors share cannot pick one of them

`anchor.create` does not require an anchor's name to be unique, and an IDML
import can bring a document that already has two of the same. The lookup took
the first match, so a link made on a shared name went to whichever anchor came
first -- a different page, with nothing to say so (noted in review).

An id still names one anchor or none, and is checked as before. A name that two
anchors answer to is refused, with their ids in the message so the caller can
say which one they meant.

Not uniqueness at creation, which was the other way offered: a document can
already hold duplicates, so enforcing it there would refuse to open files that
are fine today, and the ambiguity only matters where a name is being resolved.

The test builds two anchors of one name on different pages, asserts both
`hyperlink.create` and `hyperlink.edit` refuse it and say what to do instead,
that the refused create made no link and the refused edit left the destination
alone, and that each anchor is still reachable by its id. It is a test of its
own because the twin anchor outlives the deletion the other test ends with.

* Test: two anchors sharing a name come from a file, now that anchor.create refuses a name in use

main's anchor.create now refuses a second anchor of a name, so the test makes the first and renames the second in the document, as a file opened with two can hold them. The comment on the guard says the same.
alf merged commit 4b63d26ac6 into main 2026-11-05 21:55:59 +00:00
alf deleted branch chore/sync-upstream-20261105 2026-11-05 21:56:00 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
iris/designcraft!19
No description provided.