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>
* chore: resolve SonarCloud maintainability issues
- Replace lambda closures with method references (S1612)
- Replace wildcard imports with explicit imports (S2208)
- Refactor complex functions with extracted helpers (S3776)
- Replace OR pattern with range pattern in math parser (S9047)
* style: apply rustfmt to warichu helper
* fix(compose): revert slice conversion in tests and remove unused var in warichu
* Group warichu row layout scalars to satisfy S107 parameter limit
* style: satisfy rustfmt in warichu.rs
---------
Co-authored-by: Dylan Taylor <dylan@craftmaker.dev>
* 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
A group's transform can move a frame onto another page; the page lookup
used the frame's bounds without its parents' transforms, so the grid took
the wrong 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.
Merge upstream/main e74208dd into the working branch and retain PR #1254 unchanged. Resolve ROADMAP.md by preserving both progress histories.
Validation: fmt, pdf-deps and workspace Clippy passed; compose 364 passed, 1 ignored, including all 4 German tests. Full CI stops at 4 pre-existing Windows engine tests (paths and symlink privileges); their sources match upstream. Assets check flags the two untracked local chronicle documents.
The first-line indent was added to the left indent whatever the paragraph's
direction, so a right-to-left paragraph's first line moved the wrong way. With
the hanging indent a numbered list uses — right indent 10 mm, first line
−10 mm — the first line went 10 mm outside the frame on the left while the
wrapped lines sat where it should have been.
It is the paragraph's start-side indent, which is the right one in a
right-to-left paragraph; the drop-cap case beside it already made that
distinction.
Measured on a 200 pt column with that hanging indent:
first line right edge 200 (the frame's edge, as InDesign sets it)
wrapped right edge 171.65 (10 mm in)
every line left edge >= 0 (nothing outside the frame)
and the same paragraph left to right is unchanged, first line at 0 and wrapped
lines 10 mm in.
The test checks both directions; the right-to-left half fails without the
change. 359 composition tests pass unchanged.
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>
The points left out are recorded as story bytes, so the nested line styles
prepass (which indexes glyphs differently) leaves them out too, and a
style still covers exactly the lines it is set on. Their re-lays have a
bound of their own: each leaves out one more point of the paragraph, and
the keep options' budget no longer stops them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With Hyphenate Across Column off, a paragraph whose line ending at an
automatic hyphenation point would end a column or frame is re-laid
without that point, so the word moves on whole, as InDesign does. The
points left out are kept per paragraph across re-lays and cleared when an
earlier paragraph is re-laid. On by default, as in InDesign.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
French and Italian text is hyphenated with hyph-utf8's patterns, through
hypher's "french" and "italian" features (hyph-fr.tex 2.13, MIT; hyph-it.tex
4.9, LPPL or MIT, taken under MIT; NOTICE has both). The Character panel's
French, French: Canadian, French: Swiss and Italian, and fr / it locale codes,
select them.
A word with an elision hyphenates after its apostrophe (l'im-pri-me-rie,
dell'im-pa-gi-na-zio-ne) in the patterned languages; before, any word with an
apostrophe was left whole.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep In Frame holds a rule above inside the frame (its top), not below the
top inset. A rule's gap colour in Text Color (`Text Color` or
`$ID/Text Color`) imports as Text Color, as the rule's colour already did,
and exports as a string property rather than as no swatch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A tab draws nothing, so it was left out of the underline and strikethrough
bars: they stopped at the last letter before each tab and began again after it.
Forms rule their fill-in lines with underlined tabs — `Name ⇥⇥⇥⇥, city ⇥⇥⇥⇥` —
so an imported form lost every writing line, and an IDML `Underline="true"` on
a run of tabs drew nothing at all.
A tab is a character with an advance, and InDesign rules its whole width.
`PlacedGlyph::spans_rules` says which glyphs a rule runs across: the drawn
ones, and a tab or a right-indent tab that advances. The spaces a line break
trimmed off the end, the break characters and the markers' carriers are not
part of the line's text and are still skipped, so an underline stops at the
text and not at the frame edge.
Both places that draw the bars from the glyphs use it: the renderer
(`build_line`) and the PDF export (`text_rules`). The HTML and PPTX exports
mark up runs of text, which already included their tabs.
Measured on the repro in the issue, a 420×120 pt page at 2×:
before one underline, 63 px: the word "Name"
now three, 63 + 219 + 231 px: the word and both tab stretches
and the exported PDF gains the two fills for them. A tab with a leader is one
continuous bar across the leader dots to the right-aligned text after it.
Tests: the renderer's bars and the PDF export's bars for "Name<tab>City" are
one bar covering the tab's width, not two with a gap where it is. Both fail
without the change (two bars, 36→67.7 and 72→93.2, the tab's 67.7–72 unruled).
Resolve documentation conflicts while preserving upstream updates and the PR's German 2006 implementation. Validation: formatting, PDF dependency check, workspace Clippy, asset attribution, layering and German composition tests pass. Full CI reaches four existing Windows/path-related engine test failures in unchanged upstream files.
A paragraph rule has InDesign's Type: solid, the thick/thin stripes, dashed, dotted, straight hash,
wavy, or a stroke style of the document. Composition draws it as that shape in the rule's box
(dashes and dots as boxes and circles, stripes, waves and hashes as their outlines), with Gap
Color and Gap Tint underneath; a pattern that can't be laid out draws solid. Keep In Frame keeps a
rule above the first line of a frame inside the frame.
The Paragraph Rules page (paragraph style options and the Paragraph Rules dialog) is laid out as
InDesign's: Weight and Type, Color and Tint, Gap Color and Gap Tint, Width and Offset, Left and
Right Indent, Keep In Frame. IDML reads and writes RuleAboveType/RuleBelowType, the gap colour and
tint, and KeepRuleAboveInFrame. A rule's new settings at their defaults stay out of the JSON.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dialog follows InDesign's: General, Column Rules, Baseline Options, Auto-Size; on General,
framed Columns (Number and Width, Gutter and Maximum, Balance Columns), Inset Spacing (Top and
Bottom, the link, Left and Right) and Vertical Justification (Align, Paragraph Spacing Limit),
with spinner fields. With Fixed Number, Width shows each column's width as the number, gutter
and insets change.
Preview shows the options on the page while the dialog is open (redone as fields change); Cancel
puts the frames back, OK sets them once as one undo step.
Vertical Justification › Justify honours the Paragraph Spacing Limit: the extra space goes
between paragraphs, up to the limit each, and what is left spreads over every line (all of it,
with no limit, as before). object.textFrameOptions takes it as paragraphSpacingLimit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review fix (CodeRabbit): a story was placed on the page by its highest
hit, so a heading at the top of its later column put the whole story ahead
of another story whose heading sits higher than the first one's. Each story
is now placed by its first hit there in text order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgGsJkVe5GYG9qK6VFdECa
InDesign keeps text typed over a cross-reference's generated text (until the
cross-reference is updated), with its page number still live. The IDML
importer dropped that text and generated it again from the format, so a
credits list whose format reads "Last, First. ... YYYY. Volume X, issue Y."
showed those placeholders instead of the names and dates typed in.
- CrossRef gains `text` (edited text, `<pageNum />` for the live page) and
`page` (the page last shown). Import keeps saved text that no longer
reads as its format produces it; export writes it back with InDesign's
XRefPageNumber variable. Setting a format drops the edits, as InDesign does.
- Edited text whose destination is gone shows its saved page, not "??".
- Spaces in a cross-reference's text are word spaces the line may break at
(it was set as one unbreakable piece, so long ones broke mid-word).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgGsJkVe5GYG9qK6VFdECa
Cells and cell styles had no first-baseline setting, so every cell set its first line by the
Ascent offset (the font's typographic ascender). IDML documents that set FirstBaselineOffset on a
cell style (CapHeight is common, often through a table style's body region) composed every row
taller than the document asked for. With a font whose typographic ascender is a full em (Avenir),
each row grew about 2.6 pt and long tables overset, losing their last rows.
- doc: Cell.first_baseline / first_baseline_min; CellStyle.first_baseline / first_baseline_min,
applied by CellStyle::apply_to and carried through resolve_cell_style's BasedOn chain.
- idml: FirstBaselineOffset and MinimumFirstBaselineOffset read and written on cells and cell styles.
- compose: a cell's text frame uses the cell's first baseline offset and minimum.
- engine: table.setCell, style.cell.create/edit take firstBaseline / firstBaselineMin; table.get
reports them; style.cell.create fromSelection captures them.
- tests: import through BasedOn and a table style's body region, a cell's own override, IDML round
trip; composer cap height vs ascent and a fixed minimum; command parameters and validation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A multi-column page's "last on page" picked whichever matching paragraph sat
lowest, so a dictionary page whose final entry tops column 2 showed the last
entry of column 1. Hits on a page now order by their story's first position
on the page (top-left), then by position within the story.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgGsJkVe5GYG9qK6VFdECa
Opening brackets together, and closing brackets, commas and full stops
together, now touch (JLREQ 3.1.4): 」。 and 「『 no longer keep half an
em between them. A closing bracket and an opening one stay half an em
apart, and other neighbours keep the larger blank, as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013phQ3ekq6uGBEWD6FjFYG2