Commit Graph
6463 Commits
Author SHA1 Message Date
FredApps 070432ca44 i18n: move the craft-i18n pin to 866aff71 (#7417)
Pick up craft-i18n main, including French menu paths that use ">"
instead of "▸" (#296) and Portuguese (Brazil) byte-unit rows in the
shared catalog (#205).
2026-11-15 12:23:39 +01:00
Hector Villarreal aef22858a9 Select a table by rows when a drag crosses its columns (#3669)
## What this changes

Fixes #3850.

Partly addresses #3682: drags within one column of its two-column page now select that column's text (the tester's harness measured 3/9 → 7/9 on `cols.pdf`). Drags from one column into the next still follow reading order, as on `main`; they need column-correct reading order (#3676).

Selecting text in a table selected the wrong things. A table reads column by column, so a plain drag along one row, or down and across a few rows, took the rest of the first column and the top of the next ones between its ends. On some rows the highlight jumped to the column headers and half the table. Copy gave the cells out of order. Preview selects these by rows, and this makes PdfCraft do the same.

Rebased on `main` after the module split (#4889): the row selection is in `crates/render/src/text.rs`, the padded-cell join and barcode rows in `text/order.rs` and `text/layout.rs`, the drag's pointer end in `crates/ui-egui/src/canvas/selection.rs` and `canvas.rs`.

- **Visual rows** (`PageText::visual_between`, used by `Selection::rects` and `Selection::text`). The drag selects by rows when reading order between its ends would not follow them: the rest of the first row, the rows between, and the start of the last. Copy gives the cells tab-separated, as Column select does. Rows are used when either of these holds:
  - Reading order strays above or below the rows dragged over.
  - The drag ends in another column. That means across a gutter that no text of those rows crosses (checked a few lines above and below as well, so a justified line's wide word gap does not count), or with the pointer past the end of both lines.
- **Limits on the rows.** Rows never take lines left of everywhere the drag has been, and they stay in the column when the drag does. A drag in the right column of side-by-side text that ends on a hanging list marker does not pull in the left column. Running text keeps reading order whenever it covers the same glyphs, and so does a column of running text beside a sidebar. A drag down one article or table column keeps reading order too. A line's extent for these limits is its ink: spaces padding it out don't count.
- **Pointer position.** `Span::Run` records where the drag's pointer ended (`head_x`). It can lie well past the nearest glyph, as in the empty end of a table row. Select all, a clicked word, line or page, and four-click page selection keep reading order.
- **Hit testing.** `PageText::nearest` still takes the glyph whose em box holds the point first (#3693). Failing that, it prefers the row level with the pointer, so past the end of a short row a longer row just below no longer captures the drag. Between rows, the old distance rule still applies.
- **Padded cells.** Report generators pad each cell of a row with trailing spaces. When a cell's padding stops short of the next cell (more than the 3-height continuation gap), the next cell on its baseline now joins the row, within 8 line heights (`PADDED_FIELD`, `PADDED_REACH`). Without this, the row's last cells were read after the whole table, in selection, copy and `text_extract` alike.
- **Barcodes drawn with a font** (new since the last review). Mexican CFDI invoices and receipts print their QR or Aztec code with a barcode font: rows of glyphs stacked flush, 5 pt from the seal's text and on nearly the same baselines. Each code row joined the seal line beside it, so the seal read interleaved with the code, some seal lines came after the whole code, and a drag down the seal jumped through it. Rows of at least 8 glyphs, 4 or more stacked flush with one glyph count and the same edges (`barcode_rows`), now join no other text and read after the page's text. A point beside the code is not taken by the code row level with it.

## How you verified it

- [x] `cargo fmt --all --check`
- [x] `cargo clippy --workspace --all-targets --locked -- -D warnings`
- [x] tests pass on the crates I changed, and on the crates that use text extraction:

  | Crate or suite | Passed |
  |---|---|
  | `pdfcraft-render` (lib + integration) | 476 + 64 |
  | `pdfcraft-ui-egui --lib` | 696 |
  | `pdfcraft-ui-egui --test it` (`column_select`, `text_select`, `drag_selects`, `selection_works`) | 33 |
  | `pdfcraft-automation` | 359 |
  | `pdfcraft-engine` | 442 |
  | `pdfcraft-export` | 43 |

- [x] `cargo xtask layers`, `cargo xtask assets`, `cargo check -p pdfcraft-render --target wasm32-unknown-unknown`, `cargo xtask parity`

**Tests.** I reverted each fix and watched these fail:

- `column_select.rs::a_plain_drag_along_one_table_row_selects_only_that_row` (UI, select tool and highlighter). Before the fix it got `"Apple\nPear\nQty\n12"` instead of `"Apple\t12"`.
- `column_select.rs::a_drag_ending_between_the_columns_selects_the_rows` (UI). Without `head_x` it got `"Name\nApple\nPear"`.
- `column_select.rs::a_drag_down_text_beside_a_barcode_selects_only_the_text` (UI). Without the barcode rows it got `"ABCDEFGHIJKL one two three\nFGHIJKLMNOPQ\n…"`.
- `text/tests.rs::a_barcode_drawn_with_a_font_reads_apart_from_the_text_beside_it`
- `text/tests.rs::a_point_beside_a_barcode_takes_the_text_not_the_code_row`
- `text/tests.rs::a_point_level_with_a_row_takes_that_rows_nearest_glyph`
- `text/tests.rs::a_drag_in_the_right_column_takes_nothing_from_the_left`

Also new in `text/tests.rs`: `a_drag_along_one_table_row_selects_that_row`, `a_drag_over_table_rows_selects_those_rows`, `a_drag_that_reading_order_already_covers_keeps_it`, `a_pointer_ending_beside_the_column_takes_the_rows`, `a_cell_padded_with_spaces_joins_the_next_cell_of_its_row`, `a_justified_gap_is_not_a_gutter`, and the running-text, sidebar, heading and vertical-text cases from earlier review rounds. In `column_select.rs`: double-click drags, select all and four-click page selection on uneven columns.

All fixtures are synthetic.

**Corpus.** Before the rebase, text extraction of the pinned pdf.js corpus was byte-identical with and without this PR. The barcode change leaves it identical too: the plain text of the first 60 pages of each of its 989 PDFs (1,525 pages) is the same with and without it.

**Real-world files** (private, not attached):

- **Text extraction:** on a personal folder of PDFs (bank and hotel reports, payslips, policies, contracts, slide decks), the only files that differed from `main` before the rebase were bank statements whose padded rows now keep their amounts. The barcode change alters one page across 105 files: a statement's CFDI page, whose seal now reads in order.
- **Simulated drags:** about 53,700 random drags across those files. Selection takes at most about 145 µs per frame.
- **Two-column contract:** on a bilingual two-column contract, 2,649 drags kept inside one column leak into the other column less often than before (1,224 vs 1,591). In no file does the new code leak a two-column drag where the old one did not, except across table rows, where rows running across the page is intended.

## Never-crash

- [x] No new `unwrap`/`expect`/`panic!`/`unreachable!`/`todo!` outside tests
- [x] No indexing with input-derived positions in the new code (`get`). Allocations are bounded by the page's glyphs, and `side_by_side` compares at most `SIDE_BY_SIDE_LINES` lines. Barcode rows are found with one hash map over the page's rows. NaN boxes rank last in `nearest` (`total_cmp`) and are never barcode rows. No recursion.
- [x] No `unsafe`
- [ ] Any crash this fixes has a small synthetic regression test (n/a, no crash)

## Provenance

- [x] No asset from an Adobe product, and no Adobe icon recreated, traced or imitated
- [x] No font files committed here
- [x] No GPL/AGPL code copied in
- [x] Behaviour comes from black-box observation (Preview's selection on the same files), not from reading another implementation's source
- [x] No new assets

## Security

- [x] `--root` confinement untouched
- [x] MCP and the UI control channel untouched
- [x] No new outbound network access

## Parity and reach

- [x] `parity/acrobat-features.toml`: `view.text-select` gains the new evidence and a note. Selection across pages is still missing, as before.
- [x] Reachable headlessly: the padded-cell join and barcode rows change `text_extract` too. Visual-row selection is a pointer interaction, like the rest of plain drag selection.
- [x] `ROADMAP.md` progress log line

## Anything a reviewer should look at first

1. **A deliberate behaviour change.** `alt_drag_selects_only_the_text_inside_the_rectangle` asserted that a plain drag from "Name" to "12" selects in reading order: `"Name\nApple\nPear\nQty\n12"`. It now selects the two rows dragged over, `"Name\tQty\nApple\t12"`, and I changed the assertion. Reading order itself is unchanged (`a_column_selection_takes_one_table_column` still asserts it), and so is a drag down one column.
2. **The gutter test is a heuristic.** "Another column" means a vertical strip at least one line height wide that no text in the dragged rows, or 4 line heights around them, crosses. That catches table columns and side-by-side text, and ignores justified word gaps in practice. Thin columns with ragged edges may still fool it.
3. **The padded-cell join changes reading order.** It applies only to a segment whose box runs on at least 2 line heights of trailing spaces past its last visible glyph, and only towards a cell on the same baseline within 8 line heights.
4. **Barcode rows change reading order.** A stack of 4+ flush rows of 8+ glyphs, each with the same glyph count, height and edges, reads after the page's text. Text set solid still has lines of different lengths and counts; a hex dump set solid in a monospaced font would also match, and would then read last.
5. **Overlap with #2431** (narrow article gutters, #2387). The two PRs touch different code paths and should rebase cleanly.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **New Features**
  * Text drags across table-like layouts now select cells by visual row, with copied cells separated by tabs. Multi-click and Shift-click selections can also follow visual rows where applicable; select-all and other non-row selections retain reading order.
  * Text selection and pointer placement work more accurately around columns, gutters, and barcode-like text, reducing unintended selection of nearby content. Barcode text is kept separate from adjacent text.
* **Documentation**
  * Updated feature coverage and progress notes to reflect text-selection behavior and regression testing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-11-15 12:11:23 +01:00
Dylan M. Taylor 27287c297d Merge pull request #7378 from storytold/dot/pcx-shared-cutover
Create: decode PCX through craft-image-codecs
2026-11-15 03:53:06 -05:00
Dylan M. Taylor 0a11922520 fix(create): preserve version-four PCX compatibility 2026-11-14 23:59:16 -05:00
Dylan M. Taylor e9f7aeaeee refactor(create): decode PCX through shared image codecs 2026-11-14 23:56:51 -05:00
FredApps c944f106dd Merge pull request #7350 from christianlappin/fix/7175-update-download-test
Tests: keep the update download test stable in Linux coverage
2026-11-15 01:06:17 +01:00
FredApps b5327e03f6 Merge branch 'main' into fix/7175-update-download-test 2026-11-15 00:04:42 +01:00
Dylan M. Taylor 7d44e65412 fix(xfa): resolve clippy lints on main (#7354)
* fix(xfa): resolve clippy needless_update and field_reassign_with_default

* fix(create,xfdf): resolve remaining clippy lints

* fix(export): remove needless borrow in pptx and as_docx

* fix(engine): resolve clippy lints in signature_image

* fix(automation): resolve clippy lints in tests

* fix(create): avoid clippy::unreachable in tests

* chore(deny): allow IJG license for jpeg-encoder via craft-image-codecs
2026-11-14 16:37:43 -05:00
FredAppsandFredApps 2524889a6b i18n: move the craft-i18n pin to e56d262 (#7319)
* i18n: move the craft-i18n pin to e56d262

Brings in the translations merged to craft-i18n since the last pin.
The flatpak manifests name the same commit.

* i18n: read the newer catalog's rows in three UI tests

The craft-i18n pin brings French rows for the hotbar settings button and
its search field, a reworded German password warning, and French rows
that keep the source's menu-path arrow. The hotbar and script-prompt
tests now take those strings from the catalog. The font coverage test
still checks every letter, and skips a symbol only when the row's English
text has it too.

---------

Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-14 22:26:17 +01:00
FredApps 961bda29f1 CI: preserve font notice regressions through shared gate (#7218) 2026-11-14 22:26:12 +01:00
FredApps 1a5cf6b023 fix(ui): distinguish insert text annotation icon (#7170) 2026-11-14 22:26:03 +01:00
FredApps 5f8cc76cd0 fix(create): export JPEG images behind stream filters (#7168) 2026-11-14 22:25:58 +01:00
FredApps 54b91598a3 Move selected comments together with arrow keys (#7165)
* Move selected comments together with arrow keys

* Test keyboard nudging of selected lines

* test(ui): cover locked and duplicate nudge selections
2026-11-14 22:25:50 +01:00
Christian Lappin 0cb03234f6 Keep the download update test stable in Linux coverage 2026-11-14 14:53:31 -05:00
Dylan M. Taylor eead454105 Cut over remaining codecs, xfa XML, and export packaging to craft-libs (#7326)
* chore: cut over image, png, tiff, and zune-jpeg to craft-image-codecs

* chore: cut over xfa XML parsing from roxmltree to craft-xml

* chore: cut over export DOCX and PPTX packaging to craft-opc and craft-xml

* style: cargo fmt
2026-11-14 13:49:46 -05:00
FredApps 346d803080 Merge pull request #7203 from christianlappin/fix/7050-macos-save-xattrs
Fix macOS saves for PDFs with extended attributes
2026-11-14 17:13:09 +01:00
358e33a307 M7: replacing a line keeps the source font for the words it can show (#2378, re-lands #2955) (#6842)
* M7: replacing a line keeps the source font for the words it can show (#2378) (#2955)

* Fit #2955's line edit to #4514's /ActualText spaces

Since #4514, source_show wraps a run whose spaces the subset lacks in a
/Span /ActualText BDC ... EMC, so the moves standing in for those spaces
read back. #2955's two-font /PCEdLine path writes its source runs through
source_show, so they now carry that span too. Three of its tests were
written against the operator stream from before that:

- replacing_a_mixed_font_line_again_replaces_its_sequence counted BMC and
  EMC as the /PCEdLine sequences alone. It now also counts each edit's
  own /ActualText spans (BDC, and EMC = sequences + spans), so a sequence
  that nests or survives a re-edit still fails it.
- a_leading_space_does_not_make_a_source_font_run looked for the leading
  move right after the Tf; it now sits inside the run's span.
- a_row_replaced_inside_a_shared_sequence_starts_in_its_own_font put its
  second row before the first EMC, which is now the span's: the row was
  read as part of the span's text. It now goes before the EMC that closes
  /PCEdLine, the case the test describes. A paragraph rewrite drops an
  /ActualText span with its line (the span's BDC and EMC are among the
  line's operators), so it never leaves a row inside one.

The code needs no change: re-editing drops the spans inside a /PCEdLine
sequence with it (BDC/EMC are in the dropped set), and a span's merged
run keeps the sequence it is in, so the line still reads back as one.
Also drops the same_font import lines.rs no longer uses.

* Tests: fit #2955's three line-edit tests to #4514's /ActualText spans

* Tests: the replaced sequence's check allows the new text's own /ActualText span

---------

Co-authored-by: mtlcl410 <2368091+mtlcl410@users.noreply.github.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-14 13:25:27 +01:00
FredApps dd6c625274 Merge pull request #4417 from FredApps/fix/sonar-fonts-quality-gate
fix(ci): use pinned fonts for Sonar coverage
2026-11-14 12:16:14 +01:00
Dylan M. Taylor e98dc1712c Merge pull request #7261 from storytold/craftlibs-deep-cutover
Move remaining file formats and codecs to craft-libs (#778)
2026-11-13 23:29:36 -05:00
Dylan M. Taylor b7ffe8732f xtask, export: update attribution, parity evidence, and RTF test for craft-libs cutover 2026-11-14 04:29:32 +00:00
Dylan M. Taylor bd2120909a cos: retain pdfcraft-crypt and pdfcraft-filters in dev-dependencies 2026-11-14 04:10:43 +00:00
Dylan M. Taylor 937c3fc7e1 print: initialize driver_settings on craft-print Job 2026-11-14 04:10:10 +00:00
Dylan M. Taylor ed18238d74 crypt, filters, cos, export, xfdf: migrate to craft-pdf, craft-rtf, craft-csv 2026-11-14 04:02:21 +00:00
Dylan M. Taylor 524d9f6b64 prc: remove crates/prc and cut over to craft-prc (#778) 2026-11-14 03:44:47 +00:00
Dylan M. Taylor 9475e50cf1 Merge pull request #7260 from storytold/feat/pdfcraft-craftlibs
export: migrate zip writer to craft-zip (#778)
2026-11-13 22:12:33 -05:00
Dylan M. Taylor 93a5e1545a export: migrate zip writer to craft-zip (#778) 2026-11-14 00:06:39 +00:00
FredApps e2c90c33cb sonarcloud: use the craft-fonts commit the gate and the release use 2026-11-13 20:53:28 +01:00
FredApps 616c71492d Merge branch 'main' into fix/sonar-fonts-quality-gate 2026-11-13 20:43:14 +01:00
FredApps 0f22627bff Merge pull request #7144 from FredApps/port-2081
Link JavaScript actions and app.openDoc (port of #2081)
2026-11-13 19:47:48 +01:00
FredApps 0aed40d670 Merge branch 'main' into port-2081 2026-11-13 19:45:32 +01:00
FredApps c83c9ddb90 Merge pull request #7202 from Ethunxxx/fix/recent-reveal-test-race
test(ui): run the Home recent Reveal test inline so its toast check can't race
2026-11-13 19:45:11 +01:00
FredApps 2a639868dd Merge branch 'main' into port-2081 2026-11-13 18:59:56 +01:00
FredApps 1771844b84 fix(link-js): address the review on the #2081 port
- A link that fails puts the realm's timers back as they were before its
  own script ran, and turns no layer: timers it set are dropped, timers it
  cleared come back (timers::save/restore in Realm::run_job).
- In a read-only document a link's requests still apply when its field
  changes can't; the outcome carries the read-only error.
- app.openDoc during a calculation (calculateNow) is noted on the console
  and not queued, as for a field script running on its own.
- Link script streams are decoded once per stream, whatever their source
  length, and /Filter [] reads as the raw source.
- The web build sets the view's zoom, page and highlight before a link
  script runs, as the desktop does.
2026-11-13 18:48:33 +01:00
FredApps 8034990072 Merge pull request #7141 from FredApps/fix-openssl-ia32cap
fix(startup): an OPENSSL_ia32cap naming a missing CPU feature no longer stops PdfCraft (#2916)
2026-11-13 17:31:17 +01:00
FredApps ec31c55763 fix(ocr): main builds on Windows again: the executable check is its own function per platform (#7222)
Since Rust 1.100, clippy's needless_bool refused program_is_executable on Windows: with the
#[cfg(unix)] block gone it read if ... { return false; } true, and the Windows gate failed on
every main commit since 13 Nov 09:38. The check is now one expression with the platform part
in has_execute_permission; behaviour is unchanged on every platform.
2026-11-13 16:28:29 +01:00
FredApps e6730be289 test: scope CPU override regressions to supported platforms 2026-11-13 14:08:57 +01:00
FredApps fd4d60ff4a CI: use the shared git-sources check (#7042)
ci.yml ran its own copy of the git-sources job; it now calls
craft-common-actions git-sources.yml, under the same full-ci label rule.
2026-11-13 14:04:39 +01:00
Dylan M. Taylor 785e7e510c fix(forms): finding the first character a simple font cannot hold encodes the whole string once, then each distinct character once (#7047) 2026-11-13 12:23:58 +01:00
Dylan M. Taylor b167422b2e fix(sign): every signature's VRI entry shares the store's evidence arrays instead of copying them (#7038) 2026-11-13 12:22:54 +01:00
Dylan M. Taylor b0607f5cd3 fix(optimize): a destination name given by reference is copied once however many dictionaries name it (#7035)
* fix(optimize): a destination name given by reference is copied once however many dictionaries name it

* fix(optimize): aliases of one destination-name object are copied once
2026-11-13 12:21:26 +01:00
Christian Lappin 11a7f02cc0 Fix Finder association attribute sizing on macOS 2026-11-13 05:58:15 -05:00
Christian Lappin d6579c2bef Fix macOS extended attribute sizing during save 2026-11-13 05:58:15 -05:00
Richard Buschbeck 7f754fc9fd test(ui): run the Home recent Reveal test inline so its toast check can't race
home_recent_context_menu_reveals_the_target_without_opening_or_removing_it
never set run_inline, so reveal_path checked the missing file on a
pdfcraft-reveal worker thread while the test waited a fixed three frames
before asserting the "The file no longer exists" toast. On a loaded runner
the worker can report after those frames and the assertion fails. Set
run_inline, as the reveal unit test in tab_context.rs already does.
2026-11-13 11:40:55 +01:00
FredApps 0c15e3de71 Merge pull request #7115 from jonathanwxh-cell/fix/measure-compound-number-formats
fix(measure): read compound and fractional measurement number formats (ISO 32000-2 12.9.2)
2026-11-13 10:38:39 +01:00
FredApps 623c53f887 Merge pull request #7073 from mtlcl410/fix/test-moddate-second-boundary
fix(tests): saves compared byte for byte don't fail when the clock crosses a second between them
2026-11-13 10:37:57 +01:00
FredApps eb2a2eddac Merge pull request #7056 from mtlcl410/perf/form-edit-reads-fields-once
M7: a form edit reads the document's fields once (form.fill-text)
2026-11-13 10:37:09 +01:00
FredApps a080e1ff27 Merge pull request #7053 from mtlcl410/fix/lone-sign-token
fix(hayro-syntax): a lone sign in a dictionary or array is unreadable, not a panic
2026-11-13 10:32:49 +01:00
FredApps 6d2c006f02 Merge pull request #7051 from mtlcl410/fix/js-session-test-worker-cap
fix(js): each unit-test thread counts its own document script workers
2026-11-13 10:29:31 +01:00
asadosteaksnlandasadosteaksnl 175992b5a2 fix(ui): Recognize Text's settings scroll in a short window, its buttons stay in reach (#6599) (#6760)
* fix(ui): Recognize Text's settings scroll in a short window, its buttons stay in reach (#6599)

On a small screen or at a large display scale the dialog was taller than the window: it was
centred with its title and its Cancel / Recognize text row off the screen, and nothing scrolled.

The four settings groups (Pages, Settings, Skip, Output folder) now sit in a vertical scroll area
capped to the window's height, as Preferences does (#3557). The title, the engine notice and the
button row stay outside it, so they are always on screen; when the settings fit, nothing changes.

The test opens the dialog in 400 to 700 pt windows and checks the title and both buttons are on
screen, and that the last group scrolls in between the title and the buttons while they stay put.
Without the fix it fails at 400 pt with the title at y = -267.

* contributors: credit asadosteaksnl as Kas

* fix(ui): Recognize Text's settings scroll in as little as 40 pt, so its buttons fit the smallest window (#6599)

A 120 pt floor still pushed the dialog off a window shorter than about 340 pt: at 173 pt (the
smallest window, 520 pt, at the largest interface size, 300%) the title sat at y = -32. The test
now checks 173, 208 and 260 pt windows too, as the Preferences layout test does.

* test(ui): the short-window Recognize Text test clicks Cancel and checks a 640 pt wide window (#6599)

Cancel being on screen isn't proof it can be clicked: the test now clicks it at every height and checks the dialog closes. The window is 640 pt across, a 1280-pixel screen at 200%, and every control is checked to fit across as well as down.

* test(ui): the Recognize Text title stays put while the settings scroll (#6599)

* Revert "contributors: credit asadosteaksnl as Kas"

This reverts commit 52470f66a7.

* docs(ui): document the Recognize Text dialog's body (#6599)

* test(ui): the Recognize Text test also covers tall windows where nothing scrolls (#6599)

The dialog is about 935 pt tall, so it scrolls up to a 900 pt window; at 1100 pt everything fits. Both now run, so the test also pins the case where the settings fit and the layout is unchanged.

---------

Co-authored-by: asadosteaksnl <353856209+asadosteaksnl@users.noreply.github.com>
2026-11-13 02:42:37 +01:00
FredApps 330cde8035 Merge pull request #5559 from vangel76/feat/recent-reveal-5452
Reveal recent files in the system file manager from Home (#5452)
2026-11-13 02:19:00 +01:00