Compare commits

...
Author SHA1 Message Date
Martin BallandClaude Opus 5.5 ec6ad47f69 #6005: glossary terms for strict export
Strict export, Missing glyph and Missing picture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCX8G9hbs6XYZFsgjbL
2026-11-08 12:27:38 +00:00
Dylan M. TaylorandMartin Ball 661a790540 Render: skip drawing paths far past every canvas before flattening (#5453)
* fix(render): skip drawing paths far past every canvas before flattening

* fix(render): refuse paths with non-finite points too

---------

Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 08:15:55 +01:00
4 changed files with 46 additions and 1 deletions
+17
View File
@@ -48,3 +48,20 @@ _Avoid_: User dictionary (in the interface)
A folder of Hunspell dictionaries you supply, used before the system's (an installed Language pack
comes after it).
_Avoid_: Custom dictionaries
## Export
**Strict export**:
An export that writes nothing, and reports why, when the output would leave out a character or a
picture the document contains.
_Avoid_: Safe mode, validated export
**Missing glyph**:
A character the output draws that no face in the font fallback chain can draw, so it would appear
as an empty box.
_Avoid_: Tofu, box, notdef character
**Missing picture**:
A picture the document refers to that the output cannot include: its part is absent, its data
cannot be decoded, its format is unsupported, or it is linked from outside the file.
_Avoid_: Broken image, dropped image
+1
View File
@@ -260,6 +260,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm
- 2026-11-03: Security: exported hyperlinks (ODT, LaTeX, HTML, Markdown, RTF) holding a control character or noncharacter that the writer drops are written as plain text, so `file\u{85}:///…` can't pass the link check and come out as a `file:` link. Readiness unchanged.
- 2026-11-03: Security: pictures are measured, and checked for transparency for tight text wrapping, from a PNG's chunk headers, instead of through the decoder, whose header read inflates an embedded ICC profile of up to 512 MiB for every picture it only measures. Readiness unchanged.
- 2026-11-03: Security: saving an embedded object (a native document's kept markup included) collects the relationship ids its markup uses in a set and checks them against the writable ones in one pass, instead of scanning every id collected so far for each attribute. Readiness unchanged.
- 2026-11-03: Security: the renderer leaves out a drawing path reaching more than a million device pixels (64 times the largest canvas) or of no finite size, which the rasteriser would otherwise flatten into points by the billion before clipping it. Readiness unchanged.
- 2026-11-03: Language packs: language-packs-2 is published and WordCraft pins its index: 75 languages, adding Danish, German (Germany, Austria, Switzerland), Esperanto, Estonian, Persian, Croatian, Hungarian, Norwegian (Bokmål, Nynorsk), Dutch (#4145, #2505) and Swedish (Sweden, Finland), which need craft-proof's flag-based compounding (storytold/craft-libs#497, pinned). English (Australia) is checked against SCOWL's en-AU list as well as en-GB's (storytold/craft-libs#496). Mongolian and Thai stay hidden. Readiness unchanged.
- 2026-11-03: Language lists (CP4.3): a language installed from a Language pack (Bulgarian, say) can now be chosen. Set Proofing Language lists every built-in language and every installed or available pack's, each once and sorted by name; Review › Language and the status bar's menu add the installed packs' languages to the short list, with one way of working out the check mark, by tag. A pack's language is stored as a tag that finds the pack again (`sr-Latn-RS` is proofed by the Latin `sr-latn` pack, not the Cyrillic one; Valencian is Word's `ca-ES-valencia`). Every pack in language-packs-2 now has an English name (Belarusian used to show its own name, Беларуская). Readiness unchanged.
- 2026-11-03: Language packs (CP4.3): on a fresh install (or after an upgrade, with the list cached for an older pin) the missing-pack bar still offers a document language that has no dictionary, "Turkish proofing isn't installed. Install": the person's click turns on downloading, fetches the list and installs the pack if it lists one (if not, the bar goes). With downloading already on, a missing or stale list is fetched once in the background. Choosing a language with only the caret counts before anything is typed, and the spelling context menu's heading (Possible spelling mistake) is in the interface language, not the text's. Closing the bar is remembered per document, per pack and, before the list is known, per language tag. Readiness unchanged.
+13 -1
View File
@@ -842,10 +842,22 @@ fn draw_image(ctx: &mut RenderContext, fr: &Frame, it: &Draw) {
}
}
/// Widest path drawn, in device pixels a side: 64 times the largest canvas. The rasteriser
/// flattens a curve into as many points as its size asks for, before clipping, so a path with
/// coordinates of 1e15 (file data in a native document) would take it forever or run out of
/// memory; one this far past every canvas shows nothing a real drawing would.
const MAX_PATH_PIXELS: f64 = 1_048_576.0;
/// Whether `path` under `view` is small enough to draw ([`MAX_PATH_PIXELS`]) and finite.
fn path_extent_ok(view: Affine, path: &kurbo::BezPath) -> bool {
let b = view.transform_rect_bbox(path.bounding_box());
path.is_finite() && [b.x0, b.y0, b.x1, b.y1].iter().all(|v| v.is_finite() && v.abs() <= MAX_PATH_PIXELS)
}
fn draw_path(ctx: &mut RenderContext, fr: &Frame, it: &Draw) {
let Draw::Path { segs, fill, stroke, stroke_width, dash, cap } = it else { return };
let path = wordcraft_layout::display::seg_path(segs);
if path.elements().is_empty() {
if path.elements().is_empty() || !path_extent_ok(fr.view, &path) {
return;
}
ctx.set_transform(fr.view);
+15
View File
@@ -1411,3 +1411,18 @@ fn a_line_runs_from_top_left_to_bottom_right() {
let p = shape_path(ShapeKind::Line, kurbo::Rect::new(10.0, 20.0, 30.0, 60.0), None);
assert_eq!(p.elements(), &[kurbo::PathEl::MoveTo((10.0, 20.0).into()), kurbo::PathEl::LineTo((30.0, 60.0).into())]);
}
#[test]
fn paths_far_past_every_canvas_are_not_flattened() {
let line = |x: f64| {
let mut p = kurbo::BezPath::new();
p.move_to((0.0, 0.0));
p.curve_to((x, 0.0), (x, x), (0.0, x));
p
};
assert!(path_extent_ok(Affine::IDENTITY, &line(500.0)));
assert!(path_extent_ok(Affine::scale(4.0), &line(200_000.0)), "a large page at a high zoom");
assert!(!path_extent_ok(Affine::IDENTITY, &line(1e15)));
assert!(!path_extent_ok(Affine::scale(1e12), &line(10.0)));
assert!(!path_extent_ok(Affine::IDENTITY, &line(f64::NAN)));
}