The document keeps its outlines sorted by image and threshold, and the
lookup uses a binary search. The data merge test now checks that the
merged document has the image's outline, and that a bounding box wrap
leaves the same caption overset.
Each copied story with text anchors walked every story copied before it
to retarget their cross-references, so copying many such frames took
time quadratic in their number. The copies' cross-references are now
pointed at the copied anchors once, after the last item is copied.
data.placeholder.add with role qr and the insertion point in text bound
the QR code to the first selected frame, which the caret was not in. It
now returns "qr placeholders bind to a frame", as a QR field there does.
A field inserted at the insertion point left the caret before its
marker, so a second field went in front of the first. The caret now
moves past the marker, as after typing.
Docs: docs/agents.md.
renumber and its helpers return false when a copy nests deeper than
MAX_COPY_DEPTH and must not be inserted. They now carry #[must_use], so
a caller that drops the result fails to build under -D warnings.
* Automation roots: refuse COM¹ to LPT³ and a device name before a colon
is_device_name missed COM¹, COM², COM³ and LPT¹, LPT², LPT³, which Windows reserves as it does COM1.
It also cut the name only at a period, so COM1: and NUL:x were not seen as device names.
* Credit my name in the About window
* Automation roots: document the device-name tests
CodeRabbit's docstring check counts the touched tests. same_component stays without a doc comment, because #2205 deletes that function and a doc line above it conflicts.
* Automation roots: record the device-name refusal
The MCP roots description and the roadmap now name COM¹ to LPT³ and a device name before a colon.
With the insertion point in a table cell or a note, data.placeholder.add
with an image field returned "a hyperlink field can't go in a table cell
or a note". It now returns a message about image fields that names the
two ways on: bind the field to a frame, or pass role text to merge its
path. Nothing is inserted on the error, and role text still puts the
marker in the cell.
Docs: docs/agents.md and the command's params text.
* fix(engine): index lookups that were quadratic in a document's styles, tags, swatches and runs
* address review: first swatch per name in book sync; bound spelling input before collecting
* address review: keep the last source style per name in book sync; bound GREP replacement bytes while generating them
* address review: write GREP replacements into a byte-bounded buffer; gate keep_last test off wasm
* merge main into fix/sec-engine-linear-lookups
* address review: make the suggestion length-cap test deterministic
* fix(export): leave out text hidden by inherited conditions or deletion in every exporter
* address review: skip hidden frames in page text and hidden article items; first visible paragraph as nav label; read table anchor's own format
* address review: skip nonprinting frames and non-printable layers in fixed-layout page text
* Apply review fixes
- crates/epub/src/html.rs
- crates/engine/src/cmd/export/formats.rs
* Apply review fixes
- crates/epub/src/html.rs
- crates/engine/src/cmd/export/formats.rs
* address review: fixed-layout page text skips frames in inactive object states
* address review: place a threaded story at its first shown frame; skip it only when every frame is hidden
---------
Co-authored-by: autocraft-bot[bot] <354215646+autocraft-bot[bot]@users.noreply.github.com>
* idml: story runs and marker offsets found once, object-style overlay by position, embedded image budget
* address review: saturating embedded-bytes counter
* address review: IDML export past the embed limit succeeds and warns for each image written as a link
* address review: an unlinked image is always embedded, only linked ones fall back to their link
edit.pasteImage sizes a new frame as File › Place does, which reads a
raster at one point per pixel and does not read a stored resolution.
The params text and the comment said otherwise.
Before, an image with no transparency wrapped like its frame, so a smaller image or one moved within its frame kept text out of the frame's empty part.
- edit.pasteImage decodes the whole raster before it stores the image,
so a file whose pixels are cut off after a whole header is refused.
- edit.pasteImage is the only journal entry for a paste; the file.place
it runs no longer adds a second one, so a replay places the image once.
- Empty clipboard text beside an image counts as no text, so Edit >
Paste pastes the image.
- With a native menu, a pasted image event is ignored while the
clipboard still holds what it held when objects were copied here.
- each traced Alpha Channel outline has a serial number, and the composition signatures include the outlines in use, so a relink or link update that changes only the image data no longer leaves text laid out around the old image
- a wrap with the default alpha threshold is saved without it, as before thresholds were stored
- docs/typography-parity.md says Alpha Channel contours follow the image's visible pixels
Follows #2645. Its test said a misspelled key used to apply Align Left.
Only empty params and a bare string did; the parameter check already
refused a misspelled key, and the value parser refused an unknown
value. Each case now asserts the refusal message it gets.
Paste ignored a bitmap on the system clipboard: a screenshot or an image
copied in a browser had to be saved and placed. Paste now places it.
- edit.pasteImage {base64, name?, spread?, at?} embeds the image in the
selected empty frame, or in a new frame centered on `at` at its stored
resolution (72 ppi when none) and at most 60% of the page width, as
File > Place sizes it. Save and reopen and IDML export keep the pixels.
Images over 64 million pixels and names without an image extension are
refused.
- Ctrl/Cmd+V pastes the image egui delivers with no text, and Edit >
Paste reads the clipboard's bitmap, or the first image file of a copied
file list, when there is no text or the text is only an address.
- The menu read waits at most 1.5 s on its own thread, with one read in
flight at a time.
- With a native menu, objects copied here still paste as objects until
the pasteboard's change count shows a newer copy.
- ui.paste takes {image} for the control channel.
- Align Left, Center, Right, Justify Left, Justify All Lines and
type.align leave the document and the undo list alone when every
target paragraph already has that alignment as its own value.
- On the Mac, Em Space, En Space and Current Page Number from the menu
bar also do nothing while Quick Apply or a dialog is open, an input
method is composing or a canvas context menu is open, the cases that
stop the window's own shortcuts.
- Tests for both, and for Justify All Lines across threaded frames,
over a paragraph style, through IDML export and import, and with
parameters it does not take.
The image search stopped at the first file that could be read and
checked whether it decodes afterwards. A non-image file at an earlier
candidate (the drive root, or beside the data file) then hid a valid
image beside the data file or the document, and the field was reported
missing. The decode check now runs inside the search, so only an image
ends it.
A contour's zero-width spans (a vertical open path) are kept until the
wrap offset widens them, so text no longer crosses such a path. A record
previewed into an image placeholder gets the image's Alpha Channel
outline, as an edit would.
An outline with more path elements than composition follows is traced on
a coarser grid, so a fragmented image keeps an Alpha Channel contour
instead of falling back to the frame. An image with no pixel more opaque
than the threshold leaves the text alone.
Outlines are traced for images anchored in text as well as for those on
the spreads, so an anchored image's Alpha Channel contour follows its
visible pixels instead of falling back to the frame.
Tracing reads the pixels once: a coarser grid, used when the outline has
too many edges, is made from the finer grid's mask when each side halves,
and the cell of each pixel is counted along the row instead of divided.
The status bar's error count ran the whole-document preflight check on
the interface thread for every new state of the document, so every drag
step and every typed character waited for it. The Preflight panel ran
the same check on every frame. The check composes every story and asks
the file system about every linked graphic, so its cost grows with the
document: on a 300-page document with 900 placed images it took 137 ms
on a fast machine, and moving or resizing a frame and typing became
unusable on large documents.
The check now runs on a worker thread that takes the newest state of
the document; the issues last found stay shown until it answers. An
unchanged document is checked again every few seconds for linked files
changed outside the app, a save under a new path or a font reload is
checked again, and a check blocked on another document's linked file
is handed off to a fresh worker after 200 ms. Tests and the web build
check on the interface thread, once per state of the document.
preflight::check_document takes the document, the composition cache and
the save path, so the worker needs no session.
object.textFrameOptions refuses a gutter outside 0 to 15552 pt, a column
count outside 1 to 40 (it was clamped) and an on/off option that is not
true or false (it was skipped). The Control bar's Columns field stops at
40, as the other column fields do. The params text gives the ranges and
lists flexibleWidth.
type.para and paragraph style create and edit refuse left and right
indents outside 0 to 15552 pt and first and last line indents outside
-15552 to 15552 pt. A style made from the selection is checked with the
values it takes from the selection.
style.paragraph.apply and style.paragraph.redefine check that Minimum,
Desired and Maximum stay in order in each justification row they change,
over the overrides paragraphs keep, and refuse the change otherwise.
Insert Field with a QR column and the text cursor in a frame added a text placeholder, so a merge wrote the QR payload as plain text. It now returns "qr placeholders bind to a frame" and leaves the story unchanged.
type.para, style.paragraph.create and style.paragraph.edit accepted any
number for paragraph spacing and justification. They now refuse Space
Before, Space After and same-style spacing outside 0 to 15552 pt, word
spacing outside 0% to 1000%, letter spacing outside -100% to 500%, glyph
scaling outside 50% to 200%, Auto Leading outside 0% to 500%, and a
Single Word Justification other than left, center, right or
fullyJustified. Each justification row needs Minimum <= Desired <=
Maximum over the values the paragraph or style resolves to after the
change, including the styles based on an edited style and the
paragraphs, table cells and notes that use them.
object.textFrameOptions refuses an inset outside 0 to 15552 pt or a list
that is not four sides, and a verticalJustification, firstBaseline,
autoSize or columnsKind that is not one of its names. These were applied
or skipped without an error before. IDML import reads these values as
before.
An image field inserted at a text insertion point, or typed there as
<<name>>, merged as text, so each record showed the image's path.
It now merges as an inline image at its natural size, scaled down to
fit the smallest text column of the story's frames. A missing file is
listed in missingImages and its marker is removed. An explicit image
role at a text insertion point places an inline image too.
An image path that starts with one separator (\Photos\a.jpg) is tried
from the drive's root first, then beside the data file and the
document.
type.align with no `align` key applied Align Left, so a call that
misspelled the key, or passed a bare string, changed the paragraphs to
left alignment instead of failing. The command now returns an error,
leaves the text unchanged and records no undo step. A named value still
applies, and `align: null` still removes the override.
With nothing in a table selected, a table command given a `table` that is not a nonnegative integer (`null`, a string, a negative number) was refused as disabled, since the enablement check read it as no table. It is now refused with "`table` must be a nonnegative integer", as it is with a table selected.
* fix(engine): whole-file reads take regular files only, up to the format limit
* Apply review fixes
- crates/engine/src/io.rs
* Apply review fixes
- crates/engine/src/io.rs
* Fix failing CI check
- crates/engine/src/io.rs
* address review: refuse to save an encoded document longer than the open limit
* address review: open files for reading without blocking (FIFOs, stream-backed pseudo-files)
---------
Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>