Conflicts:
- crates/pdf/src/text.rs run_text: the branch's two generated_text
branches first, then #392's kashida branch (no text), then the cmap
fallback for other inserted glyphs.
- crates/compose/src/lib.rs: next's kashida PlacedGlyph (with dx) gets
generated_text: None.
- crates/compose/src/shape.rs hyphen_after: next's rendered_char/moji
resets and generated_text = None.
- crates/compose/src/ruby.rs: next's ruby module (#363); the branch's
edit to the old ruby set() has nothing to apply to.
- crates/pdf/src/tests.rs: tests from both.
Also, so the merge compiles: next's ruby (ruby.rs annotate) and kenten
(kenten.rs shape_mark) PlacedGlyph literals take generated_text: None;
they map back through the font like other inserted glyphs. Kanji page
numbers, composite-font note numbers and fallback-face label segments
go through shape_segment's replacement and list labels through
prepend_label, so they carry their generated text.
next's shatai path draws a slanted glyph on its own through tagged_run,
which opens a Span; inside #392's right-to-left line span that nested
marked content and krilla panicked ("can't start marked content twice")
in tagged exports. While a line span is open, the glyph is drawn with
run like the line's other runs. Test:
shatai_glyphs_in_right_to_left_lines_stay_in_the_line_span (panicked
before).
Lines with right-to-left text are wrapped in a marked span whose
ActualText is the line's text in reading order, so viewers that order
text by position (Preview, PDFKit) no longer return it reversed.
A base letter and its marks (Hebrew points) map glyph by glyph when
each glyph stands for one of the cluster's characters, and are drawn in
text order, so ToUnicode-only extraction reads each letter once with
its points after it. Marks stay in their run, placed by glyph offsets
(compensated for skew), instead of starting runs of their own.
Kashidas inserted by justification carry no text.
Fixes#392.
Generated glyphs (page numbers, section markers, list labels) carry the
text they were shaped from, so a figure the font substitutes under small
caps or other OpenType features extracts as the digit instead of U+FFFD.
- table.setCell starts each chosen edge from the stroke rendering draws there (the winning
copy of a shared edge, the row/column pattern, or the table border) and writes the result
to both copies; table.getCellStroke reports drawn edges, counting each shared edge once.
compose::table::cell_side_strokes shares the resolution with the renderer.
- Commands accept dash patterns of 1-64 lengths, each 0 or 0.1-10000 pt. Screen and PDF
draw a dash or dot pattern that would repeat more than 20000 times along a path as solid.
- Stripes, wavy, hash and gap colour extend by half the weight at table corners.
- Cell Options: an edge change reseeds every stroke field and tolerates a non-object seed.
- Edge proxy: a triple-click decides from the choice before the first click.
Conflicts: compose lib.rs imports (both; Composer unused), idml tests.rs (both test modules), ui-egui
panels/mod.rs (next's ruler origin with the branch's graphic wrap for Direct Selection).
A right-to-left bound spread lists its right page first, parent spreads
included. The parent page shown behind a document page was picked by list
index (first = left), so every page of an imported right-to-left document
showed the other side's parent items. Pick it by position instead: the
leftmost parent page for a left page, the rightmost for a right page.
New parent spreads in a right-to-left document list their pages right
first, and changing the binding reorders existing parent spreads, so IDML
export writes them in binding order. Spread bleed on the canvas and in
spread PDF export takes the outer edges by position as well.
A placed graphic carries its own text wrap (Graphic.wrap). IDML import
reads TextWrapPreference on the Image/PDF element and export writes it
back there. Layout treats it as an obstacle around the part of the
graphic its frame shows, plus the offsets, and also collects wraps of
items inside groups. object.textWrap acts on the graphic when the
selection is the frame's content, and the Text Wrap panel shows its wrap.
Conflicts: ui-egui dialogs.rs (next's numberingSection arm and the branch's cellOptions arm; the branch's
copies of the dialog test helpers already on next dropped, its cell options test kept); idml tests.rs and
the i18n tables appended at the end on both sides (both kept; the branch's Gap Tint: and Tint: rows that
next already had dropped).
CellStroke and cell/table style edge attributes carry gap colour and gap tint
(IDML ...EdgeStrokeGapColor/GapTint, ...BorderStrokeGapColor/GapTint). The table
renderer and PDF export draw every stroke type on cell edges, with the gap colour
under dashes, dots and stripes.
table.setCell validates stroke values, takes gapColor/gapTint and an edges list
(top/left/bottom/right are the target range's outer sides; innerHorizontal and
innerVertical the lines between its rows and columns). table.getCellStroke reports
the shown values of the chosen edges, null where they differ.
Conflict: ROADMAP.md: next's FONT1-3 lines with the branch's CJK1 line
Conflict: docs/cjk.md: the branch's rows, with next's aki below and Japanese composer text kept in the leading model row
Conflict: crates/fonts/src/fontdb.rs: both fields (family/style aliases and the ICF cache)
Conflict: crates/compose/src/ruby.rs: the branch's ruby module; its PlacedGlyph (and kenten.rs's) carry next's rendered_char
Conflict: crates/compose/src/lib.rs: modules both; rtl and para_block both; the branch's grid placement (gyoudori, grid references) replaces next's snap, its paragraph-gyoudori span uses next's line_metrics with mark leading and #330's aki below advance; a wrap push moves the line by whole grid lines and its grid end with it; next's trimmed spaces and drop cap, then the branch's ruby and kenten annotation
Conflict: crates/compose/src/shape.rs: next's pieces loop and StyleRuns with the branch's auto tate-chu-yoko split (after marks join their base) and fit, and Upright mode in every shape_segment call; next's label fallback passes the mode on
Conflict: crates/compose/src/tests.rs: both sets of tests appended at the end
Conflict: crates/idml/src/export.rs: next's metadata and text variable writers, then the branch's CJK settings writers
Conflict: crates/idml/src/tests.rs: both tests
Conflict: crates/ui-egui/src/dialogs.rs: the branch's Ruby dialog default section (next's duplicate characterStyleOptions default dropped); next's section arms with the branch's style_cjk::section_ui
Conflict: crates/compose/src/lib.rs: imports from both (ListAlign, ListCounter; ParaBox beside next's hit_note_with); the branch's list labels (ListCounter, named_labels, label_chars, label alignment, Tab Position stop) with next's list_label marking from prepend_label's count; the branch's align_label before next's warichu placement, trimmed spaces and drop cap; next's rule_color for rules above and below in the branch's Deco literals; the branch's split block end (ends_split, span space after) in place of next's two identical copies; next's drop cap helpers, then the branch's label_chars and align_label, then prepend_label
Conflict: crates/compose/src/tests.rs: both sides' tests
Conflict: crates/idml/src/export.rs: next's para_attrs parent name argument with the branch's with_list_tab (its parent attributes renamed parent_props) for styles and paragraph ranges
Conflict: crates/idml/src/import.rs: next's INDESIGN_CORNER_SIZE and tint doc; the branch's corners() placed before tint's doc comment
Conflict: crates/idml/src/tests.rs: both sides' tests
Conflict: crates/ui-egui/src/dialogs.rs: next's deleteParagraphStyle and the branch's paragraphLocal confirm arms
Conflict: crates/ui-egui/src/panels/glyphs.rs: the branch's grid() split, with next's scroll bar room in its column count
Conflict: ROADMAP.md: next's IDML interchange line with the branch's sentence on placed PDFs, $ID/ font names and packaged links appended; next's INDD line kept
Conflict: crates/engine/src/tests_idml.rs: next's drop cap test and the branch's two placed PDF tests
Conflict: crates/fonts/src/fontdb.rs: next's plausible/measured_top/typo_ascent and the branch's plain_family; next's canonical_family lookups, with plain_family in place of without_format_suffix in style_traits, face (two) and has_family; the branch's has_exact_family dropped (has_family uses canonical_family)
Conflict: crates/idml/src/import.rs: next's char_attrs (tab-split family and composite font names), falling back to names::font_family_in
Borders gain a stroke type (built-in or a document stroke style), cap,
join, gap colour and tint, a shape and size per corner, column or text
width, top and bottom edges, closed boxes in each part of a split
paragraph, and one box for consecutive paragraphs with the same
settings. Shading gains the same corners, edges and width, Clip to
Frame, and Do not Print or Export. Boxes are drawn once the lines are in
place, as filled paths where they aren't plain rectangles.
Span and split paragraphs keep at least their space before and after
from the column text. Lists have levels that number on their own and
restart after a higher level, ^1 to ^9 in the number text, a character
style for the label, a bullet font, left, centre or right alignment at
the line start, and a tab position for the text after the label.
All attributes load from older files with their defaults and
round-trip through IDML, apart from the tab position.
The placed PDF's asset stores its Crop to choice (crop, art, trim, bleed,
media, or the content bounds with visible or all layers) and where that
box sits on the page. The graphic spans the box: the screen, PDF export,
rasterised exports and clipping-path tracing draw only that part of the
page, so a larger frame doesn't reveal the bleed.
IDML export writes the choice as PDFCrop, with GraphicBounds spanning the
box. Import reads PDFCrop back; the engine finds the box on the PDF page
after import (and after relink or update) and maps it onto GraphicBounds,
or gives a graphic without bounds the box's size. Documents saved without
a choice load with the crop box. The Place PDF dialog offers the content
bounds too, and links.list reports the choice.
Export writes a PDFAttribute with the page number and PDFCrop="CropPDF":
the graphic bounds already span the page's crop box, so the frame clips
to the chosen trim, bleed or media box as before. Import reads the page
number back instead of always showing page 1.
A placed PDF page that the PDF does not have (a hostile PageNumber, or
any document holding one) is left out of PDF export with a warning
instead of failing the whole export.
Upstream main moved 9e4f694 -> 736acff by 10 squashes: #258, #285, #153, #280, #288, #281,
#176, #299, #298, #297. New to next: #258 (datamerge test), #153 (temporary files and release
inputs), #280 (warichu), #176 (PDF image conversion cache), #297 (graphics backend before the
window). Already on next: #285, #288, #281, #299, #298.
Conflict: ROADMAP.md: next's lines; CJK1 takes main's wording (ruby unfinished, warichu set after the line is broken)
Conflict: crates/compose/src/lib.rs: list labels (#298): next's list_label structure; paragraph start / break moves: next's `go` (with #295's sub-columns) under the maintainer's #285 comment; Cursor: next's start/next_page/next_baseline with tops; line placement: #280's warichu::place right after layout_line, before next's trimmed trailing spaces and the drop cap splice (so neither is stacked and the spaces follow the closed-up end_x); caret(): next's drop-cap branch first, then #280's stacked-row metrics
Conflict: crates/compose/src/tests.rs: next's tests kept; #280's tests auto-merged
Conflict: crates/engine/src/cmd/text.rs: both (next's TypingFormat/typing_format, #280's warichu_cmd)
Conflict: crates/idml/src/tests.rs: next's side; #280's warichu round-trip test auto-merged
Conflict: crates/pdf/src/export.rs: #176's content cache and test counter with next's RasterMode (no Exporter `interpolate` field on next, so the maintainer's `mode.interpolate &= self.interpolate` is not carried); test exporter gets next's drawing_items field
Conflict: crates/ui-egui/src/canvas.rs: #280's compose::highlight_quads (its RTL branch equals next's mixed-direction spans) followed by next's drop-cap highlight
Conflict: crates/ui-egui/src/menus.rs: next's formatting_affects_text test kept
Auto-merge duplicates removed: #288's table-style tests and #298's list test in idml tests.rs,
#288's tests in doc styles.rs and #285's docx test (both files equal next's), #281's dialog
tests in dialogs.rs (equals next's) and its menu_button/menu_max_height/clipped_submenu in
menus.rs. contributors/people.toml takes main's (next had a duplicated
[people.emanuelebonaventura] table from the 9e4f694 merge, which broke the ui-egui build
script's TOML parse).
Maintainer edits carried over: #285 break characters and Next Column through one closure
(next's `go`, comment added); #281 viewport-aware menu_button in the app bar and the Paragraph
menu, per-dialog widths clamped to the window (all already on next); #280 warichu after the
last-line rules (layout_line with ends_para/forced_mid) and rules at each warichu row's
baseline (rule_baseline in render and pdf text_rules); #176 CMYK fallback through
lossless(.., mode) and its updated contract tests; #258 test fixes; #153 and #297 as landed.
A linked image that wasn't found on open has no data; the export warning said it could not be decoded. It now says it is missing and names the link.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Set warichu as smaller lines inside the parent line.
Type › Warichu stacks the selected run after the line is broken, closes up the rest of the line, and round-trips the IDML warichu properties. The line break itself still sees the full-size run.
* Keep clicks, the caret, and Up/Down on each warichu row.
The glyphs were already stacked inside the parent line, but hit testing, the caret, and vertical movement still used that parent line. Break minimums, row widths, spacing, and tab leaders now follow the small lines, and underlines sit on the small baseline.
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Text frames: column rules draw between columns on screen and in PDF
A frame with Column Rule on gets a bar centred in each gutter, inside the
insets, broken around paragraphs that span columns; right-to-left and
vertical frames included. object.textFrameOptions takes columnRuleWeight
and columnRuleColor (unknown swatches are an error) and an inset array
whose null sides stay; document.inspect reports the rule.
* Text Frame Options: Column Rules fields; OK changes only edited fields
The dialog shows the selected frame's options, with an inset field per
side, and sends only what the user changed, so unequal insets survive.
The rule weight is read in points.
* Column rules: tint, horizontal offset and top/bottom insets; IDML import and export
Text Frame Options gains Tint %, Top Inset, Bottom Inset and Horizontal
Offset for the rule, and object.textFrameOptions and document.inspect
take and report them. IDML reads the ColumnRule attributes of
TextFramePreference on frames and object styles and writes them when a
rule is set.
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- An RGB image with no ICC profile of its own was written as DeviceRGB
under the CMYK output intent, and the export only warned. In PDF/X-4
it now carries the sRGB profile (assets/icc/sRGB-v2-magic.icc, ICC
v2.1, CC0). JPEGs stay passed through. Untagged PNG, GIF and WebP,
and the decoded TIFF, BMP and PSD fallback, go in as RGB with the
profile. A re-encoded JPEG keeps the source's RGB profile or gets
sRGB. Grey images and images with their own profile are unchanged.
- Every image constructor asked for interpolation. PDF/X-4 and PDF/A
exports now write images without it, which also removes the
ImageInterpolation error that stopped PDF/A exports with a placed
image. Plain exports keep it.
- make_pdfx4 writes pdf:Trapped False into the XMP, matching
/Trapped/False in the document information.
- check_pdfx4 reports an image that asks for interpolation.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Upstream squash-merged #142#147#254#122#224#229#230#244#247#269#249#200#276#283#268#236#272#143 after 4fbf893. next already
holds every one of them except #200 (PDF/X-4 validation command:
designcraft-cli validate-pdf) and #268 (GitHub Actions version bumps),
which this merge brings in unchanged. Apart from those two, the merged
tree equals next's.
Each squash commit S equals the clean 3-way merge of S^ with the PR's
current head (refs/pull/N/head). For #142#147#254#236#143 that head
is newer than the one on next: the maintainer merged upstream main into
the PR before squashing.
Maintainer edits (all already on next, so nothing to carry over):
- #142 (7dd948a): control bar keeps #112's horizontal ScrollArea with
#142's horizontal_top inside it. next has the same (plus #255's
object_control and integration 00cf932's tests).
- #147 (fa4c605): the full-decode check at the top of lossless() kept
with #128's interpolate flag. next has the check at the top of #223's
lossless() with RasterMode.
- #254 (80c0ada): USAGE keeps #127's export-options note. next has the
same USAGE line.
- #236 (504ea4a): uk entries for "Can't Open the File" and "Can't Place
the File". next has both with the same translations (integration
92b1a96); kept at next's position, not duplicated.
- #143 (eaa76a4, d6c9762): one test_window harness with open, wheel,
panel_rect and click_at. next has the same module.
Conflict: ROADMAP.md: next's lines (CJK-MOJI block, Text engine line, RTL and Interface language with Italian and Ukrainian)
Conflict: apps/designcraft/src/main.rs: next's `mod graphics` (#168)
Conflict: crates/compose/src/lib.rs: next's paragraph start via `go` (#285 with #276's next_column), ends_para via is_break_char, in-paragraph break through break_start, Cursor without the second resume(); git auto-merged #276's band-closing block a second time, kept once
Conflict: crates/compose/src/tests.rs: next's tests; #283's line_before_a_break_character_is_a_last_line, auto-merged a second time, kept once
Conflict: crates/engine/src/cmd/fonts.rs: tests only; next's composite_tests (#126) kept
Conflict: crates/engine/src/cmd/interchange.rs: tests only; next's INDD routing test kept
Conflict: crates/fonts/src/fontdb.rs: next's plausible/typo_ascent (#81), style_traits/traits_of with exact_style_traits and canonical_family (#81, #126, #247), face() through canonical_family and find_or_load
Conflict: crates/idml/src/import.rs: next's tint() helper (#83) for #230's colour tint
Conflict: crates/idml/src/tests.rs: tests only; next's drop cap and anchored frame tests kept
Conflict: crates/pdf/src/export.rs: next's RasterMode image path (#223 with #147's decode check)
Conflict: crates/ui-egui/src/dialogs.rs: next's paragraph style dialog (new style, #144's basic_character_formats and character_color sections), Find Font FALLBACK_FAMILY default (#126)
Conflict: crates/ui-egui/src/i18n.rs: next's Italian (#218) beside #236 and #272
Conflict: crates/ui-egui/src/i18n/uk.rs: next's table (see #236 above)
Conflict: crates/ui-egui/src/lib.rs: next's Control bar second-row test via the object_control group (00cf932), browser import tests (#119)
Conflict: crates/ui-egui/src/menus.rs: next's language lists with it; #236's ukrainian_language test, auto-merged a second time, kept once
A placed PNG, GIF or WebP that is cut off (an interrupted download) or
has a bad checksum made the whole PDF export fail with
"PDF writer error: Image(Image(ImageRepr {..}), None, "unexpected end of
file")", which names no file. A damaged TIFF or BMP in the same place is
skipped with the warning "image `name` could not be decoded and was
skipped", and the rest of the document is exported.
The exporter decides whether an image can be embedded by asking krilla
to open it. krilla opens PNG, GIF and WebP lazily: it reads the header,
accepts the file, and decodes the pixels only when the PDF is written,
where an error ends the export. TIFF and BMP are decoded by us first, so
they took the skip path.
lossless() now decodes the image first for every format. A file that
does not decode returns None and takes the existing skip-with-warning
path. This costs one extra decode per PNG, GIF or WebP asset: the sample
magazine (two PNGs, 820 KB) exports in 0.19 s instead of 0.17 s in a
debug build.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* PDF: PDF/A exports keep placed PNG, JPEG, GIF and WebP images by writing them without interpolation (#71)
* Tables: new tables take their paragraph's direction, and the Table panel sets a table's direction (#100)
* Text: stories made in a right-to-left-bound document fill their columns from the right (#100)
Conflict: crates/idml/src/import.rs: next's text_frame_options(&[&El]) object-style chain with #274's column rule fields; #274's frame_options takes the prefs list and looks up ColumnRuleStrokeColor through Prefs; the frame site resolves the rule colour's swatch after building options from the chain (the chain borrows the importer)
Conflict: crates/idml/src/tests.rs: tests only; kept both
Conflict: crates/compose/src/tests.rs: tests only; kept both
Conflict: crates/pdf/src/tests.rs: tests only; kept both
A frame with Column Rule on gets a bar centred in each gutter, inside the
insets, broken around paragraphs that span columns; right-to-left and
vertical frames included. object.textFrameOptions takes columnRuleWeight
and columnRuleColor (unknown swatches are an error) and an inset array
whose null sides stay; document.inspect reports the rule.
Conflict: crates/compose/src/shape.rs: inline object on the baseline: #124's rule (its box alone sets the extents, not max with the font's) applied to every metric #83 tracks (ascent, typo_ascent, cap, xh); the above-line arm keeps #83's four fields
Conflict: crates/compose/src/tests.rs: tests only; kept both
Conflict: crates/engine/src/tests.rs: tests only; kept both
Conflict: crates/idml/src/tests.rs: tests only; kept both
Conflict: crates/idml/src/import.rs: kept #230's Underline/StrikeThru colour line (Text Color resets) and #83's tint() helper
Conflict: ROADMAP.md: kept the chain's Text engine line and added #230's FID2 line
Conflict: crates/pdf/src/export.rs: took #223's side (RasterMode with #147's decode check) and dropped #128's Exporter interpolate field; #128's RTL story/table work and its PDF/A test are kept
Conflict: crates/pdf/src/export.rs: kept #223's lossless() (RasterMode, sRGB tagging) with #147's full-decode check at its top; the fallback reuses the decoded raster
- An RGB image with no ICC profile of its own was written as DeviceRGB
under the CMYK output intent, and the export only warned. In PDF/X-4
it now carries the sRGB profile (assets/icc/sRGB-v2-magic.icc, ICC
v2.1, CC0). JPEGs stay passed through. Untagged PNG, GIF and WebP,
and the decoded TIFF, BMP and PSD fallback, go in as RGB with the
profile. A re-encoded JPEG keeps the source's RGB profile or gets
sRGB. Grey images and images with their own profile are unchanged.
- Every image constructor asked for interpolation. PDF/X-4 and PDF/A
exports now write images without it, which also removes the
ImageInterpolation error that stopped PDF/A exports with a placed
image. Plain exports keep it.
- make_pdfx4 writes pdf:Trapped False into the XMP, matching
/Trapped/False in the document information.
- check_pdfx4 reports an image that asks for interpolation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A placed PNG, GIF or WebP that is cut off (an interrupted download) or
has a bad checksum made the whole PDF export fail with
"PDF writer error: Image(Image(ImageRepr {..}), None, "unexpected end of
file")", which names no file. A damaged TIFF or BMP in the same place is
skipped with the warning "image `name` could not be decoded and was
skipped", and the rest of the document is exported.
The exporter decides whether an image can be embedded by asking krilla
to open it. krilla opens PNG, GIF and WebP lazily: it reads the header,
accepts the file, and decodes the pixels only when the PDF is written,
where an error ends the export. TIFF and BMP are decoded by us first, so
they took the skip path.
lossless() now decodes the image first for every format. A file that
does not decode returns None and takes the existing skip-with-warning
path. This costs one extra decode per PNG, GIF or WebP asset: the sample
magazine (two PNGs, 820 KB) exports in 0.19 s instead of 0.17 s in a
debug build.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Resolve forward story references before importing embedded frames and groups.
Compose embedded stories, retain anonymous-story objects for screen and PDF
rendering, and invalidate layout caches when embedded frame geometry changes.
Measure inline objects by geometry, fill open contours, reject cyclic frame
references, and distinguish unplaced stories from located overset text.
Add synthetic round-trip, composition, preflight, pixel-rendering, and PDF
regressions. Update the roadmap with the supported import and rendering paths.
Resolve conflicts with the CJK/Arabic composition work: line metrics keep
the first-baseline tops and add the CJK alignment shift; the CJK line
reference adjustment applies before baseline-grid snapping; layout_line
takes the bidi info and the drop cap is spliced in after it; vertical text
is now the story's direction (FrameSpec::vertical); IDML insets use main's
validated inset_spacing over the object style chain; format's JSON upgrade
(vertical frames) and model upgrade (format v2 defaults) both run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>