mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-09 04:22:20 +00:00
M2.6: accessibility marks no tag whose operator reaches past a stream not read whole
Marking a tag as an artifact, or a figure as decorative, refused content after a stream damaged partway, placing each operator by its first operand. A page may split its content between an operator's operands and its keyword. When a `BDC`'s operands end the stream before one damaged from its start, or one that doesn't decode, the keyword the page reads is in what wasn't read; joined without it, those operands and the next tag's read as one `BDC`, and marking rewrote the stream after the hidden one, the next tag's `BDC` taken out of it. An operator now counts after damage by its keyword, and one whose operands and keyword lie on either side of a stream not read whole is refused: that stream may end them itself. `a_tags_operands_before_a_stream_not_read_whole_are_not_rewritten` (damaged from its start, and not decoding). A tag entirely after a stream that doesn't decode is still marked: such a stream holds all of its content, between tokens. `accessibility_mark_tag_artifact`'s description names content a stream that doesn't decode may hide or change too, not only a damaged one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
This commit is contained in:
1 parent
3639fed12f
commit
1b8f3040ee
3 files changed
+40
-3
No files matched your search
@@ -452,8 +452,11 @@ fn artifact_marked_content(
|
||||
for op in pieces.parse().ops {
|
||||
if let Some(id) = marked_id(doc, &props, &op, ids) {
|
||||
// What follows the damage is unknown: a string or comment the damage left open runs
|
||||
// on into the next stream, so what reads as an operator after it may not be one.
|
||||
if damage.is_some_and(|d| pieces.piece_of(op.span.start) > d) {
|
||||
// on into the next stream, so what reads as an operator after it may not be one. An
|
||||
// operator whose operands start before a stream not read whole and whose keyword comes
|
||||
// after it takes them from around what wasn't read: that stream may end them itself.
|
||||
let (first, last) = pieces.pieces_of(&op);
|
||||
if damage.is_some_and(|d| last > d) || read.get(first..last).is_some_and(|r| r.iter().any(|r| *r != Read::Whole)) {
|
||||
return Ok(Marked::Damaged);
|
||||
}
|
||||
found.insert(id);
|
||||
|
||||
@@ -1220,6 +1220,40 @@ fn a_tags_content_after_a_damaged_stream_is_not_rewritten() {
|
||||
}
|
||||
}
|
||||
|
||||
/// A page may split its content between an operator's operands and its keyword. When the operands
|
||||
/// of the figure's `BDC` end the stream before one damaged from its start or one that doesn't
|
||||
/// decode, the `BDC` the page reads is in what wasn't read: joined without it, those operands and
|
||||
/// the next tag's read as one `BDC`, which reaches into the stream after it. Neither marking
|
||||
/// changes anything, the structure tree included.
|
||||
#[test]
|
||||
fn a_tags_operands_before_a_stream_not_read_whole_are_not_rewritten() {
|
||||
use pdfcraft_cos::{Dict, ObjRef, Object, Stream};
|
||||
let rest = b" BDC q 40 0 0 20 10 150 cm /Im Do Q EMC";
|
||||
for damaged in [true, false] {
|
||||
let mut doc = figures_doc();
|
||||
doc.set(ObjRef::new(4, 0), Stream::from_raw(Dict::new(), b"/Figure << /MCID 0 >>".to_vec()));
|
||||
let hidden = if damaged {
|
||||
damaged_flate(b"", rest)
|
||||
} else {
|
||||
let filter = doc.add(Object::name("FlateDecode"));
|
||||
let mut s = Stream::flate(Dict::new(), rest);
|
||||
s.dict.set(b"Filter".to_vec(), Object::Ref(filter));
|
||||
s
|
||||
};
|
||||
let hidden = doc.add(hidden);
|
||||
let after = doc.add(Object::Stream(Stream::from_raw(Dict::new(), b"/Photo << /MCID 1 >> BDC 100 100 50 30 re f EMC".to_vec())));
|
||||
let listed = Object::Array(vec![Object::Ref(ObjRef::new(4, 0)), Object::Ref(hidden), Object::Ref(after)]);
|
||||
doc.update_dict(ObjRef::new(3, 0), |p| p.set(b"Contents".to_vec(), listed)).unwrap();
|
||||
let state = |doc: &Document| {
|
||||
(doc.get(ObjRef::new(3, 0)).as_dict().unwrap().get(b"Contents").cloned(), doc.get(ObjRef::new(9, 0)), doc.get(ObjRef::new(6, 0)))
|
||||
};
|
||||
let before = state(&doc);
|
||||
assert_eq!(mark_tag_artifact(&mut doc, ObjRef::new(7, 0)), Err(ArtifactError::Damaged), "damaged: {damaged}");
|
||||
assert_eq!(mark_decorative(&mut doc, ObjRef::new(7, 0)), Err(AltError::Damaged), "damaged: {damaged}");
|
||||
assert_eq!(state(&doc), before, "damaged: {damaged}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A tag's content not found on the page may be in a stream that doesn't decode: it isn't known
|
||||
/// to be off the page, so neither marking changes anything, the structure tree included.
|
||||
#[test]
|
||||
|
||||
@@ -1285,7 +1285,7 @@ pub fn tools() -> Vec<ToolDef> {
|
||||
"property": { "type": "string", "enum": ["type", "title", "actual_text", "alternate_text", "language", "summary", "scope"] },
|
||||
"value": { "type": "string" },
|
||||
}), &["doc", "object", "generation", "property"])),
|
||||
t("accessibility_mark_tag_artifact", "Mark tag content as artifact", "Mark a reachable leaf structure tag's direct marked content as an artifact and remove the tag from the reading structure. Use object and generation from accessibility_tags. Tags with child structure elements, no direct marked content, content not found on its page, or content that may be in or after a damaged content stream are rejected. Undoable.")
|
||||
t("accessibility_mark_tag_artifact", "Mark tag content as artifact", "Mark a reachable leaf structure tag's direct marked content as an artifact and remove the tag from the reading structure. Use object and generation from accessibility_tags. Tags with child structure elements, no direct marked content, content not found on its page, or content that a damaged or undecodable content stream may hide or change are rejected. Undoable.")
|
||||
.with(schema(json!({
|
||||
"doc": doc(), "object": { "type": "integer", "minimum": 1 },
|
||||
"generation": { "type": "integer", "minimum": 0, "maximum": 65535 },
|
||||
|
||||
Reference in new issue
Block a user