Commit Graph
583 Commits
Author SHA1 Message Date
skylar taylor-barrick faed705607 Merge contributor branch codex/pdf-layer-explicit-visibility (PR #1671) into the paired integration branch 2026-10-24 15:30:04 -06:00
skylar taylor-barrick 9fc02f8f7b Merge contributor branch codex/placed-graphic-layer-overrides (PR #1647) into main
Conflict resolutions:
- crates/pdf/src/export.rs: keep main's `graphic(.., hidden)` signature from
  #1241 and report retained graphic-layer overrides that drawing cannot apply.
- docs/gaps.md: keep main's change list and add the graphic-layer entry.
2026-10-24 15:30:03 -06:00
dotCooCoo 6bc5ac36fb Merge main into feat/file-info 2026-10-24 11:30:55 -07:00
dotCooCoo bfc36622e0 File Info: keywords with a comma keep their place, and the dialog stores into its own document
Editing one keyword in File Info re-split the whole field, so a stored
keyword holding a comma or semicolon became two. A stored keyword that is
still in the field whole now stays one keyword.

An Export PDF title equal to the document's name was dropped, so the PDF
took File Info's title. The dialog's title now starts empty, and a title
given there is always the PDF's title; without one the file is named
after the document.

file.info refuses parameters that are not an object; null reads the
fields like {}.

File Info's OK stores the fields in the document the dialog was opened
for, even when another document became active meanwhile, and stores
nothing once that document has closed.
2026-10-24 11:25:22 -07:00
Brandon Thomas a57d4cea1b Merge pull request #1752 from Hamza0101/fix/import-value-ranges
import: a value a file gives is one the engine can mean (#1593)
2026-10-24 10:23:05 -04:00
Brandon Thomas 8d6989c508 Merge pull request #1740 from Silvertree2010/fix/h1918-footnote-numbering-overflows-u32
Saturate footnote and endnote numbering at u32::MAX
2026-10-24 10:22:43 -04:00
aiongg 5def4a8c01 Story Optical Margin Alignment with a size, and Ignore Optical Margin (Story panel, engine and IDML) (#1705)
* Type: story Optical Margin Alignment with a size, and Ignore Optical Margin (engine, IDML)

A story's Optical Margin Alignment applies to every paragraph except those
with Ignore Optical Margin; hang amounts scale with the story's size.
story.options sets it, type.para and paragraph styles take
ignoreOpticalMargin. IDML imports and exports StoryPreference
OpticalMarginAlignment/OpticalMarginSize and IgnoreEdgeAlignment. The
per-paragraph opticalMargin switch keeps hanging at the text's own size.

Part of #947.

* Type: the Story panel, and Ignore Optical Margin in the Paragraph panel menu and Paragraph Style Options

Window > Type & Tables > Story sets Optical Margin Alignment and its size
for the stories of the text selection or the selected text frames through
story.options, and is disabled without either. type.selectionAttrs
reports the stories' values as storyOptions, null where they differ.
The Paragraph panel menu toggles ignoreOpticalMargin, and Paragraph Style
Options > Indents and Spacing has an Ignore Optical Margin checkbox.

Part of #947.

* ROADMAP: the story Optical Margin Alignment entry names its default and scope
2026-10-24 10:08:40 -04:00
Brandon ThomasandClaude Opus 5.5 5fca302ff6 Endnotes: saturate the remaining numbering sites at u32::MAX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 10:01:04 -04:00
aiongg d767fcf41e Text frames: a custom baseline grid's Relative To (page, margin, frame or inset) (#1758) 2026-10-24 09:55:47 -04:00
andeecollard 8d295b101e PDF: distinguish all-on layer choices from file defaults 2026-10-24 13:23:02 +01:00
andeecollard 3d362d0bdf M6: preserve nested graphic layers and retain export diagnostics 2026-10-24 13:22:08 +01:00
andeecollard 7074dc8a85 M6: preserve per-placement graphic layer options 2026-10-24 13:22:08 +01:00
moppeeer 39bf3da8a4 Merge remote-tracking branch 'origin/main' into codex/toc-right-indent-separator
# Conflicts:
#	ROADMAP.md
#	docs/ui-parity.md
2026-10-24 14:19:44 +02:00
moppeeer 0f653bcc73 Merge remote-tracking branch 'origin/main' into effect-blend-modes
# Conflicts:
#	ROADMAP.md
2026-10-24 14:18:42 +02:00
moppeeer 7e7346ee9b Merge remote-tracking branch 'origin/main' into paragraph-rule-types
# Conflicts:
#	ROADMAP.md
#	crates/ui-egui/src/i18n.rs
#	crates/ui-egui/src/i18n/ar.rs
#	crates/ui-egui/src/i18n/cs.rs
#	crates/ui-egui/src/i18n/it.rs
#	crates/ui-egui/src/i18n/ka.rs
#	crates/ui-egui/src/i18n/pl.rs
#	crates/ui-egui/src/i18n/pt_br.rs
#	crates/ui-egui/src/i18n/ru.rs
#	crates/ui-egui/src/i18n/uk.rs
2026-10-24 14:18:32 +02:00
moppeeer 0132c0347a Merge remote-tracking branch 'origin/main' into find-change-format
# Conflicts:
#	ROADMAP.md
#	crates/ui-egui/src/i18n/ar.rs
#	crates/ui-egui/src/i18n/cs.rs
#	crates/ui-egui/src/i18n/it.rs
#	crates/ui-egui/src/i18n/ka.rs
#	crates/ui-egui/src/i18n/pl.rs
#	crates/ui-egui/src/i18n/pt_br.rs
#	crates/ui-egui/src/i18n/ru.rs
#	crates/ui-egui/src/i18n/uk.rs
2026-10-24 14:18:21 +02:00
moppeeer dfd7091f64 Merge remote-tracking branch 'origin/main' into paragraph-shading-indesign
# Conflicts:
#	ROADMAP.md
#	crates/ui-egui/src/i18n.rs
#	crates/ui-egui/src/i18n/ar.rs
#	crates/ui-egui/src/i18n/cs.rs
#	crates/ui-egui/src/i18n/de.rs
#	crates/ui-egui/src/i18n/it.rs
#	crates/ui-egui/src/i18n/ja.rs
#	crates/ui-egui/src/i18n/ka.rs
#	crates/ui-egui/src/i18n/pl.rs
#	crates/ui-egui/src/i18n/pt_br.rs
#	crates/ui-egui/src/i18n/ru.rs
#	crates/ui-egui/src/i18n/uk.rs
#	crates/ui-egui/src/icons.rs
2026-10-24 14:17:53 +02:00
moppeeer bce454453f Merge remote-tracking branch 'origin/main' into table-convert-dialogs
# Conflicts:
#	ROADMAP.md
#	crates/ui-egui/src/i18n.rs
#	crates/ui-egui/src/i18n/ar.rs
#	crates/ui-egui/src/i18n/cs.rs
#	crates/ui-egui/src/i18n/de.rs
#	crates/ui-egui/src/i18n/it.rs
#	crates/ui-egui/src/i18n/ja.rs
#	crates/ui-egui/src/i18n/ka.rs
#	crates/ui-egui/src/i18n/pl.rs
#	crates/ui-egui/src/i18n/pt_br.rs
#	crates/ui-egui/src/i18n/ru.rs
#	crates/ui-egui/src/i18n/uk.rs
2026-10-24 14:17:45 +02:00
aiongg d4c1322d97 Merge pull request #1709 from aiongg/w65-batch
Integration batch: book-chapter IDML baseline grids, hyphenation, and the Paragraph panel stack
2026-10-24 08:14:29 -04:00
moppeeerandClaude Opus 5.5 c6a0e1e813 Change Format: empty matches get the paragraph style; style helpers bounded
- A GREP match that is empty (^) was counted but got no paragraph style; the
  paragraph style now goes to the paragraphs of every match, empty ones too.
  Character formatting still needs characters.
- apply_char_style and apply_para_style listed every paragraph of the story
  for each match; Story::paras_touching gives the ones a range reaches, from
  the range itself.

Found by CodeRabbit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 13:50:38 +02:00
moppeeerandClaude Opus 5.5 713579a7b8 Convert Text to Table: resolved separators, the count as given, Insert's column limit
Merges #1676 (a null table style is no style), and:
- The dialog compares separators as table.convertFromText resolves them, so
  "comma" and a typed "," are the same and Number of Columns applies.
- The column count goes to the engine as given: 0 is refused (the dialog stays
  open) instead of becoming 1.
- At most 200 columns, table.insert's MAX_COLUMNS, in the engine and the field.
- The help says the words tab, comma and paragraph name those separators.
- A short last row's padding cells take the text's paragraph and character
  format (Table::from_strings formats every cell).

Found by CodeRabbit and @skylartaylor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 13:23:45 +02:00
Hamza 7808816e9a import: a value a file gives is one the engine can mean (#1593)
The importers each had their own spelling of "is this a number we can use", or
only checked that it was finite. Measured before the change:

    <cSize:1e300>  -> Some(1e300) pt           tagged text
    <cSize:-5>     -> Some(-5.0) pt
    <cSize:0>      -> Some(0.0) pt
    w:spacing before=1e300 -> 5e298 pt indent  DOCX

DC-12 asks for a shared clamp helper used by every importer, so there are two
in `doc::attrs`, beside the tab limits:

* `imported_type_size` drops a size the engine cannot mean, so the text keeps
  the size its style gives it -- which is what RTF and DOCX already did.
* `imported_measure` clamps an indent, spacing or offset to the widest page.
  Clamped rather than dropped on purpose: a file that says "indent everything"
  still indents as far as the engine goes, where dropping would quietly restore
  the style's own indent and hide what the file asked for.

Both are now used by tagged text, DOCX runs and paragraphs, DOCX numbering
levels and RTF -- the last two had the 0.1-1296 pt range as a literal, which is
the duplication that let tagged text drift.

Control: with the helpers back to a finite-only test, both new tests fail, the
DOCX one printing the 5e298 pt spacing in full.

Stale in the issue: its first location, `docx.rs` `w:sz`, is already clamped --
that came with #1574. DC-12's remaining holes are the ones fixed here.
2026-10-24 16:17:49 +05:00
moppeeer 52c6ac3bf6 Merge remote-tracking branch 'origin/main' into paragraph-shading-indesign
# Conflicts:
#	ROADMAP.md
#	crates/compose/src/parabox.rs
2026-10-24 12:13:58 +02:00
moppeeer d2f6f7c338 Merge remote-tracking branch 'origin/main' into table-convert-dialogs 2026-10-24 11:13:15 +02:00
NetterSilverandClaude Haiku 5.5 f0ce96138c Saturate footnote and endnote numbering at u32::MAX
Found while testing crates/compose/src/notes.rs:133 (also crates/doc/src/notes.rs:265 footnote_start and crates/doc/src/endnotes.rs:150 endnote_start)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 09:07:37 +00:00
Dylan M. TaylorandClaude Sonnet 5.5 cb763c2583 refactor: split complex functions in psd, table, parabox, index, trace, pdf export, render (Sonar S3776) (#1724)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 04:51:26 -04:00
Dylan M. TaylorandClaude Sonnet 5.5 8299d26400 chore: replace trivial closures with method paths (Sonar S1612) (#1714)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 04:30:43 -04:00
Dylan M. TaylorandClaude Sonnet 5.5 bfd95b258b chore: drop redundant 'static lifetimes in statics and consts (Sonar S8863) (#1713)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 04:30:39 -04:00
dotCooCoo a525894355 File Info: PDF export writes the document's title, author, description and keywords
File › File Info… opens a dialog with Document Title, Author,
Description, Keywords, Copyright Notice and Copyright Info URL, and the
document saves them. The new file.info command reads them with {} and
sets the fields it is given; "" or null clears a field, and keywords
are a list or one line split at semicolons and commas. file.info.get
returns the fields without editing the document, so opening the dialog
does not end a Data Merge preview.

Export PDF, PDF/X-4 and Print Booklet write the title, author,
description and keywords into the PDF's document information and XMP.
EPUB export takes the title and author from File Info, and HTML export
takes the title. A title or author given to the export replaces File
Info's, and an empty Document Title leaves the document's name as the
title. Export PDF's dialog no longer sends the document's name as the
PDF title, so the PDF takes its title from File Info; the file name
still comes from the document's name.
2026-10-24 00:20:21 -07:00
aiongg c428083d74 Merge #1483: Bulleted and Numbered List buttons give InDesign's default hanging indent
# Conflicts:
#	crates/ui-egui/src/i18n/ar.rs
#	crates/ui-egui/src/i18n/cs.rs
#	crates/ui-egui/src/i18n/de.rs
#	crates/ui-egui/src/i18n/it.rs
#	crates/ui-egui/src/i18n/ja.rs
#	crates/ui-egui/src/i18n/ka.rs
#	crates/ui-egui/src/i18n/pl.rs
#	crates/ui-egui/src/i18n/pt_br.rs
#	crates/ui-egui/src/i18n/ru.rs
#	crates/ui-egui/src/i18n/uk.rs
#	docs/target-app-parity.md
2026-10-24 06:00:59 +00:00
aiongg e79da41ce9 Merge #1704: Text frames: a custom baseline grid keeps its start and increment through IDML
# Conflicts:
#	crates/compose/src/lib.rs
#	crates/idml/src/tests.rs
2026-10-24 05:52:12 +00:00
aiongg e11f9f6202 Merge #1703: Baseline grid: Relative To Top Margin starts the grid at each page's top margin
# Conflicts:
#	crates/compose/src/lib.rs
2026-10-24 05:51:57 +00:00
aiongg d8d7885172 Anchored objects: Relative to Spine mirrors custom positions on right-hand pages (#1702)
* Anchored objects: Relative to Spine mirrors custom positions on right-hand pages

* Anchored objects: page side from the frame's spread position; footnote cache keyed by page side
2026-10-24 01:34:05 -04:00
aiongg df6ba617d3 Text frames: a custom baseline grid keeps its start and increment through IDML 2026-10-24 04:58:04 +00:00
aiongg 14da000a35 Baseline grid: Relative To Top Margin starts the grid at each page's top margin
The setting was imported and editable but never read: the composer, the
canvas grid and baseline snapping measured the grid from the page top.
BaselineGrid::origin gives the first line on a page (start, plus the
page's top margin for Top Margin), and all three use it. The composition
cache tracks the setting and the pages' top margins.

Also adds a left-to-right facing-pages guard test for mirrored margins
and the right parent page's guides and running head.
2026-10-24 04:28:34 +00:00
moppeeerandClaude Opus 5.5 cbb2a974d1 Tables: InDesign's Convert Text to Table and Convert Table to Text dialogs
Table › Convert Text to Table… and Convert Table to Text… open dialogs laid
out as InDesign's instead of converting at once with tabs and paragraphs:
- Column and Row Separator: Tab, Comma, Paragraph from the field's menu, or
  any characters typed.
- Number of Columns (Convert Text to Table), available when both separators
  are the same: the cells fill rows of that many.
- Table Style (Convert Text to Table), applied to the new table in the same
  undo step.

table.convertFromText takes rowSeparator, columns and tableStyle, and any
characters as separators; table.convertToText takes both separators
(Table::text_with). Convert Text to Table is available only with text
selected outside a table, as in InDesign. New strings are translated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 21:21:32 +02:00
makesomethingshit 4f45a0127a #1620: Resize selected parent pages from page controls 2026-10-23 20:58:19 +09:00
aiongg 3dbbcf2d07 Merge branch 'print-color' into print-color-separations
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	crates/engine/src/cmd/export.rs
#	crates/ui-egui/src/lib.rs
2026-10-23 05:52:56 +00:00
aiongg a30b71cd36 Merge remote-tracking branch 'upstream/main' into print-color
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	xtask/src/pdf_deps.rs
2026-10-23 05:40:13 +00:00
moppeeerandClaude Opus 5.5 028e950f97 Paragraph Shading and Border pages laid out as InDesign's, with Overprint
Paragraph Style Options › Paragraph Shading and Paragraph Border are headed
with the page's name; Tint is a spinner with InDesign's presets and has
Overprint beside it (shading, border stroke and gap: new paragraph
attributes, read and written in IDML as ParagraphShadingOverprint,
ParagraphBorderOverprint and ParagraphBorderGapOverprint; PDF export warns
that it doesn't overprint yet, as for objects). Corner Size and Shape is
InDesign's two-by-two grid of corner icons, size spinners and shape menus
showing each shape's glyph, with the link in the middle; the offsets have
spinners, with the edges and width beside them; the weights have spinners.

Found in the Learn Skills Daily course, chapter 41.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:00:30 +02:00
moppeeerandClaude Opus 5.5 b3896d8122 List buttons: track the hanging indent they set (listHang)
A paragraph attribute records that the Bulleted or Numbered List button set
the hanging indent; turning the list off takes the indent away only then
(and while it is still as applied), and clears the mark. A paragraph whose
own indents were 18/-18 before the list keeps them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 17:30:14 +02:00
moppeeer 9036034707 Merge remote-tracking branch 'origin/main' into codex/toc-right-indent-separator
# Conflicts:
#	ROADMAP.md
2026-10-22 09:25:37 +02:00
moppeeer ffd74840c8 Merge remote-tracking branch 'origin/main' into paragraph-rule-types
# Conflicts:
#	ROADMAP.md
2026-10-22 09:06:25 +02:00
moppeeer 5d2db6f958 Merge remote-tracking branch 'origin/main' into effect-blend-modes
# Conflicts:
#	ROADMAP.md
2026-10-22 08:59:41 +02:00
aionggandHamza a912438ab8 Layout: autoflow, inserted pages and new parents follow dragged columns, mirrored across sides (#1096) (#1173)
* IDML: keep column guides that were dragged off centre (#1096)

InDesign records dragged column guides in `MarginPreference ColumnsPositions`,
as pairs of offsets from the margin box's left edge. The importer discarded them
and divided the columns evenly, so a page whose gutter the designer had moved
came back with it in the middle: on a 450 pt margin box, `0 150 170 450` was
read as columns at 215 and 235.

`Columns::positions` already existed, documented as "Custom column positions
(after dragging column guides)", but nothing set it and nothing read it. Now
`Page::column_rects` returns the custom rects when they are set — that one
method is what the canvas guides, snapping, threading and the first frame of a
placed story all go through, so they follow the dragged gutter — the importer
fills it, and the exporter writes the page's real positions instead of
recomputing an even split, so the round trip keeps them.

`layout.marginsAndColumns` clears them when a count or gutter is set, which is
what InDesign's Margins and Columns does. Without that the stale positions would
outrank the new value and the dialog would look broken.

Verified against InDesign-authored IDML rather than assumed, which settled two
things. The attribute is written whether or not anything was dragged — a real
page's values are exactly its even split — so an even set is dropped rather than
marking every imported page custom and freezing its columns; the tolerance for
that comparison only absorbs float noise, about 1e-14 pt on that page. And
`<Guide>` is a `<Page>` child, not a `<Spread>` one, which is where this
importer already looks: the reporter's missing ruler guide was 099fbd2, landed
the day the ticket was filed, not a second bug.

That commit left `lib.rs` still listing guides as ignored on import; corrected.

* IDML: custom column positions are validated and round-trip (#1096)

Columns::positions are used only when they are 2 x count finite edges,
ascending from 0 to the margin box width (within 0.01 pt); anything else
is the even split, in Page::column_rects, on IDML import (bounded parse,
checked against the page's margin box) and on IDML export.
Columns::with_gutter_moved moves one gutter's pair of guides between its
neighbours for the coming column-guide command.

ROADMAP: drop four progress-log lines the #1121 merge duplicated and move
its line to the top.

* Layout: autoflow, inserted pages and new parents follow dragged columns (#1096)

* IDML: a page without its own margins checks the default's column positions against its width (#1096)

* Layout: dragged columns mirror when a page changes side (#1096)

* Layout: pages that follow a one-page parent take its dragged columns mirrored (#1096)

* ROADMAP: the progress line lands in one commit after the guide PRs

---------

Co-authored-by: Hamza <mail.hamzanaveed@gmail.com>
2026-10-22 02:26:38 -04:00
aiongg d7b9f716f1 View: Lock Guides and Lock Column Guides are commands (#1096) (#1174)
* View: Lock Guides and Lock Column Guides are commands (#1096)

view.lockGuides {on?} (Cmd+Alt+;) sets the Lock Guides UI state the Properties panel and ruler-guide drags use. view.lockColumnGuides {on?} is a document setting, on by default, saved with the document, not an undo step, and read from and written to IDML ColumnGuideLocked. Both are in View > Grids & Guides with checkmarks.

* View: Lock Column Guides is translated wherever Lock Guides is (#1096)

* View: Lock Column Guides is undoable (#1096)

* ROADMAP: the progress line lands in one commit after the guide PRs
2026-10-22 02:12:42 -04:00
aionggandHamza 6f7fc7134a IDML: dragged column guides import, validated, and round-trip (#1096) (#1171)
* IDML: keep column guides that were dragged off centre (#1096)

InDesign records dragged column guides in `MarginPreference ColumnsPositions`,
as pairs of offsets from the margin box's left edge. The importer discarded them
and divided the columns evenly, so a page whose gutter the designer had moved
came back with it in the middle: on a 450 pt margin box, `0 150 170 450` was
read as columns at 215 and 235.

`Columns::positions` already existed, documented as "Custom column positions
(after dragging column guides)", but nothing set it and nothing read it. Now
`Page::column_rects` returns the custom rects when they are set — that one
method is what the canvas guides, snapping, threading and the first frame of a
placed story all go through, so they follow the dragged gutter — the importer
fills it, and the exporter writes the page's real positions instead of
recomputing an even split, so the round trip keeps them.

`layout.marginsAndColumns` clears them when a count or gutter is set, which is
what InDesign's Margins and Columns does. Without that the stale positions would
outrank the new value and the dialog would look broken.

Verified against InDesign-authored IDML rather than assumed, which settled two
things. The attribute is written whether or not anything was dragged — a real
page's values are exactly its even split — so an even set is dropped rather than
marking every imported page custom and freezing its columns; the tolerance for
that comparison only absorbs float noise, about 1e-14 pt on that page. And
`<Guide>` is a `<Page>` child, not a `<Spread>` one, which is where this
importer already looks: the reporter's missing ruler guide was 099fbd2, landed
the day the ticket was filed, not a second bug.

That commit left `lib.rs` still listing guides as ignored on import; corrected.

* IDML: custom column positions are validated and round-trip (#1096)

Columns::positions are used only when they are 2 x count finite edges,
ascending from 0 to the margin box width (within 0.01 pt); anything else
is the even split, in Page::column_rects, on IDML import (bounded parse,
checked against the page's margin box) and on IDML export.
Columns::with_gutter_moved moves one gutter's pair of guides between its
neighbours for the coming column-guide command.

ROADMAP: drop four progress-log lines the #1121 merge duplicated and move
its line to the top.

* IDML: a page without its own margins checks the default's column positions against its width (#1096)

* ROADMAP: the progress line lands in one commit after the guide PRs

---------

Co-authored-by: Hamza <mail.hamzanaveed@gmail.com>
2026-10-22 01:59:10 -04:00
aionggandSBaksa 3be2736d0a Guides: select ruler guides and set their position (#1282) (#1445)
* Guides: a dragged guide shows where it is (#1282)

While a guide is dragged out of a ruler or moved, a label beside the
pointer reads X: or Y: and the guide's place, in the ruler units and
measured the way the rulers measure (from the zero point, and from the
page origin when rulers restart per page).

* Guides: keep the drag readout inside the canvas

Near the canvas's right or bottom edge the readout flips to the other
side of the pointer, and it is kept inside the canvas in every case
(readout_at).

* Guides: the drag readout measures the spread under the pointer

The readout measured from the spread in the middle of the view (where the
rulers measure), but a guide is dropped on the spread under the pointer,
so with spreads above one another the number shown could differ from the
guide's place. It now measures that spread (ruler_pieces_of).

* Guides: no readout for an existing guide over another spread

An existing guide moves only on its own spread (guide_drag), so dropped
over another it stays where it was: the readout no longer shows a place
there it won't go.

* #1282: guide selection commands (guide.select, guide.selectAll, guide.deselect, guide.moveBy)

* #1282: select ruler guides on the canvas, X/Y in the Control bar and Properties

* #1282: guides take clicks over objects, a new guide is selected, group drag preview

* ROADMAP: the progress line lands in one commit after the guide PRs

---------

Co-authored-by: SBaksa <sabaksa11@gmail.com>
2026-10-22 01:47:49 -04:00
Zuujis 438d7b6d79 Dedupe the 400-year-cycle UTC conversion and the EPUB package writer (#1537)
* Dedupe the 400-year-cycle UTC conversion and the EPUB package writer

SonarCloud flagged new-code duplication on main (17%): five copies of the
"move the instant into chrono's range by whole 400-year cycles" conversion
and two copies of the EPUB OPF/container/zip writer.

- designcraft-doc gains a `time` module: `date_parts(days)` and
  `utc_parts(secs, nanos)`. App logging, EPUB `iso8601`, PDF `civil`, PPTX
  `civil` and text-variable `civil` are now thin callers; each keeps its own
  format string and year clamp. The app reaches it through
  `designcraft_engine::doc`, so its direct chrono dependency is dropped.
- EPUB: `package_opf` + `write_package` shared by fixed-layout and
  reflowable export (same entry order and compression).
- Boundary tests on the helper: pre-1970, year 9999 -> 10000, i64 extremes,
  nanos past a second.

Output is unchanged: existing date tests pass, and fixed/reflowable EPUBs
(with/without cover, author, pre-1970 and i64::MAX dates) are byte-identical
to origin/main.

* time: drop the unreachable i128 clamp and match arms in utc_parts

Whole 400-year cycles times 400 stay under 2.6e16 for any i64 day count,
so the year can't overflow i64 and the i128 + saturate was dead code
(carried over from the copies this replaced). The date and time lookups
can't fail either; each keeps one unwrap_or_default instead of a match
with a fake 1970 fallback. Output and tests unchanged.
2026-10-21 21:46:38 -04:00
moppeeer a4641fe022 Merge remote-tracking branch 'origin/main' into codex/toc-right-indent-separator
# Conflicts:
#	ROADMAP.md
2026-10-22 02:46:33 +02:00