use pdfboss_core::{Dict, Document, Name, Object};
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
use pdfboss_core::{Encryptor, Permissions};
use crate::error::{Error, Result};
use crate::importer::Importer;
use crate::pdf::Metadata;
use crate::update::{
catalog_metadata_ref, core_error, merge_metadata, resolve_dict, xmp_metadata_stream,
};
use crate::writer::{WriteOptions, Writer};
pub fn merge_documents(
inputs: &[(&Document, Option<&[usize]>)],
options: WriteOptions,
) -> Result<Vec<u8>> {
let mut writer = Writer::new(options);
let pages_ref = writer.reserve();
let mut kids = Vec::new();
for (source, selection) in inputs {
let mut importer = Importer::new(&mut writer, source)?;
let indices: Vec<usize> = match selection {
Some(indices) => indices.to_vec(),
None => (0..source.page_count()).collect(),
};
for index in indices {
kids.push(importer.page(index, pages_ref)?);
}
}
if kids.is_empty() {
return Err(Error::Other(
"a document needs at least one page".to_string(),
));
}
let mut tree = Dict::new();
tree.insert(name("Type"), Object::Name(name("Pages")));
tree.insert(
name("Kids"),
Object::Array(kids.iter().copied().map(Object::Ref).collect()),
);
tree.insert(name("Count"), Object::Int(kids.len() as i64));
writer.fill(pages_ref, Object::Dict(tree))?;
let mut catalog = Dict::new();
catalog.insert(name("Type"), Object::Name(name("Catalog")));
catalog.insert(name("Pages"), Object::Ref(pages_ref));
let root = writer.put(Object::Dict(catalog));
writer.finish(root)
}
fn name(text: &str) -> Name {
Name(text.to_string())
}
pub fn rotate_rewrite(
doc: &Document,
pages: &[usize],
by: i32,
options: WriteOptions,
) -> Result<Vec<u8>> {
if by % 90 != 0 {
return Err(Error::Other(
"rotation must be a multiple of 90 degrees".to_string(),
));
}
let mut writer = Writer::new(options);
let mut importer = Importer::new(&mut writer, doc)?;
let new_info = doc
.xref()
.trailer
.get_ref("Info")
.map(|info| importer.reference(info));
for &index in pages {
let page = doc.page(index).map_err(core_error)?;
let Some(page_ref) = page.object_ref() else {
return Err(Error::Other(format!(
"page {} is inlined into /Kids and cannot be edited in place; \
pdfboss does not yet restructure such pages to rotate them",
index + 1
)));
};
let mut dict = page.dict().clone();
let rotate = (page.rotate + by).rem_euclid(360);
dict.insert(name("Rotate"), Object::Int(i64::from(rotate)));
let body = importer.copy(&Object::Dict(dict))?;
importer.substitute(page_ref, body);
}
let new_root = importer.document()?;
if let Some(new_info) = new_info {
writer.set_info(new_info);
}
writer.finish(new_root)
}
pub fn rewrite_document(doc: &Document, options: WriteOptions) -> Result<Vec<u8>> {
rewrite_into(Writer::new(options), doc)
}
fn rewrite_into(mut writer: Writer, doc: &Document) -> Result<Vec<u8>> {
let mut importer = Importer::new(&mut writer, doc)?;
let new_info = doc
.xref()
.trailer
.get_ref("Info")
.map(|info| importer.reference(info));
let new_root = importer.document()?;
if let Some(new_info) = new_info {
writer.set_info(new_info);
}
writer.finish(new_root)
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
pub fn encrypt_document(
doc: &Document,
user_password: &str,
owner_password: &str,
permissions: Permissions,
options: WriteOptions,
) -> Result<Vec<u8>> {
if user_password.is_empty() && owner_password.is_empty() {
return Err(Error::Other(
"user_password and owner_password cannot both be empty".to_string(),
));
}
let owner_password = if owner_password.is_empty() {
user_password
} else {
owner_password
};
let (encryptor, encrypt_dict) = Encryptor::aes256(user_password, owner_password, permissions);
rewrite_into(Writer::new_encrypted(options, encryptor, encrypt_dict), doc)
}
pub fn decrypt_document(doc: &Document, options: WriteOptions) -> Result<Vec<u8>> {
rewrite_document(doc, options)
}
pub fn rewrite_with_metadata(
doc: &Document,
meta: Metadata,
options: WriteOptions,
) -> Result<Vec<u8>> {
let mut writer = Writer::new(options);
let mut importer = Importer::new(&mut writer, doc)?;
let trailer = &doc.xref().trailer;
let root = trailer.get_ref("Root").ok_or(Error::MissingRoot)?;
let info_ref = trailer.get_ref("Info");
let existing_dict = info_ref.and_then(|r| {
let dict = doc.get(r).ok()?.as_dict()?.clone();
Some(resolve_dict(doc, &dict))
});
let xmp_ref = catalog_metadata_ref(doc, root);
let (dict, merged) = merge_metadata(existing_dict, &meta);
let new_info_target = match info_ref {
Some(r) => {
let target = importer.reference(r);
let body = importer.copy(&Object::Dict(dict.clone()))?;
importer.substitute(r, body);
Some(target)
}
None => None,
};
if let Some(r) = xmp_ref {
importer.substitute(r, xmp_metadata_stream(&merged));
}
let new_root = importer.document()?;
let new_info = match new_info_target {
Some(target) => target,
None => writer.put(Object::Dict(dict)),
};
writer.set_info(new_info);
writer.finish(new_root)
}
pub fn split_document(doc: &Document, every: usize, options: WriteOptions) -> Result<Vec<Vec<u8>>> {
if every == 0 {
return Err(Error::Other(
"every must be at least 1 page per part".to_string(),
));
}
let total = doc.page_count();
let mut parts = Vec::new();
let mut start = 0;
while start < total {
let end = (start + every).min(total);
let indices: Vec<usize> = (start..end).collect();
parts.push(merge_documents(&[(doc, Some(&indices))], options)?);
start = end;
}
Ok(parts)
}
#[cfg(test)]
mod tests {
use pdfboss_core::xref::{parse_section_at, startxref, XrefEntry};
use pdfboss_output::{extract_text, ReadingOrder};
use pdfboss_testkit::{encrypted_rc4_doc, multi_page_doc, PdfBuilder};
use crate::pdf::{Metadata, Page, PageSize, Pdf};
use crate::update::Update;
use crate::writer::XrefStyle;
use super::*;
#[test]
fn merge_keeps_sources_in_argument_order() {
let a = Document::load(multi_page_doc(&["a1", "a2"])).expect("doc a loads");
let b = Document::load(multi_page_doc(&["b1", "b2"])).expect("doc b loads");
let bytes = merge_documents(&[(&a, None), (&b, None)], WriteOptions::default())
.expect("merge succeeds");
let merged = Document::load(bytes).expect("merged document loads");
assert_eq!(merged.page_count(), 4);
let texts: Vec<String> = (0..4)
.map(|i| {
let page = merged.page(i).expect("page exists");
extract_text(&merged, &page, ReadingOrder::Content).expect("text extracts")
})
.collect();
assert!(texts[0].contains("a1"), "page 0: {:?}", texts[0]);
assert!(texts[1].contains("a2"), "page 1: {:?}", texts[1]);
assert!(texts[2].contains("b1"), "page 2: {:?}", texts[2]);
assert!(texts[3].contains("b2"), "page 3: {:?}", texts[3]);
}
#[test]
fn a_range_selects_and_reorders_pages() {
let doc = Document::load(multi_page_doc(&["one", "two", "three"])).expect("doc loads");
let bytes = merge_documents(&[(&doc, Some(&[2, 0]))], WriteOptions::default())
.expect("merge succeeds");
let merged = Document::load(bytes).expect("merged document loads");
assert_eq!(merged.page_count(), 2);
let first = merged.page(0).expect("first page exists");
let second = merged.page(1).expect("second page exists");
assert!(extract_text(&merged, &first, ReadingOrder::Content)
.unwrap()
.contains("three"));
assert!(extract_text(&merged, &second, ReadingOrder::Content)
.unwrap()
.contains("one"));
}
#[test]
fn merge_documents_accepts_a_password_opened_encrypted_source() {
let doc = Document::load(encrypted_rc4_doc("secret")).expect("empty-password doc loads");
let bytes = merge_documents(&[(&doc, None)], WriteOptions::default())
.expect("a password-opened encrypted source is not locked, so merge succeeds");
let merged = Document::load(bytes).expect("merged document loads");
assert!(
!merged.is_encrypted(),
"the merged output carries no /Encrypt"
);
let page = merged.page(0).expect("page 0 exists");
let text = extract_text(&merged, &page, ReadingOrder::Content).expect("text extracts");
assert!(text.contains("secret"), "{text:?}");
}
#[test]
fn split_makes_parts_of_the_requested_size_and_a_shorter_last_part() {
let doc = Document::load(multi_page_doc(&["one", "two", "three"])).expect("doc loads");
let parts = split_document(&doc, 2, WriteOptions::default()).expect("split succeeds");
assert_eq!(parts.len(), 2);
let first = Document::load(parts[0].clone()).expect("first part loads");
assert_eq!(first.page_count(), 2);
let texts: Vec<String> = (0..2)
.map(|i| {
let page = first.page(i).expect("page exists");
extract_text(&first, &page, ReadingOrder::Content).expect("text extracts")
})
.collect();
assert!(texts[0].contains("one"), "page 0: {:?}", texts[0]);
assert!(texts[1].contains("two"), "page 1: {:?}", texts[1]);
let second = Document::load(parts[1].clone()).expect("second part loads");
assert_eq!(second.page_count(), 1);
let page = second.page(0).expect("page exists");
let text = extract_text(&second, &page, ReadingOrder::Content).expect("text extracts");
assert!(text.contains("three"), "page 0: {:?}", text);
}
#[test]
fn split_larger_than_the_page_count_makes_one_part() {
let doc = Document::load(multi_page_doc(&["one", "two", "three"])).expect("doc loads");
let parts = split_document(&doc, 10, WriteOptions::default()).expect("split succeeds");
assert_eq!(parts.len(), 1);
let only = Document::load(parts[0].clone()).expect("part loads");
assert_eq!(only.page_count(), 3);
}
#[test]
fn split_rejects_zero_pages_per_part_with_an_honest_message() {
let doc = Document::load(multi_page_doc(&["one", "two", "three"])).expect("doc loads");
let result = split_document(&doc, 0, WriteOptions::default());
let Err(Error::Other(message)) = result else {
panic!("expected Error::Other, got {result:?}");
};
assert!(message.contains("every"), "message: {message}");
}
#[test]
fn rotate_rewrite_rotates_the_selected_pages() {
let doc = Document::load(multi_page_doc(&["one", "two", "three"])).expect("doc loads");
let bytes =
rotate_rewrite(&doc, &[0, 2], 90, WriteOptions::default()).expect("rotate succeeds");
let rotated = Document::load(bytes).expect("rotated document loads");
for (index, expected) in [90, 0, 90].iter().enumerate() {
let page = rotated.page(index).expect("page exists");
assert_eq!(page.rotate, *expected, "page {index}");
}
}
#[test]
fn rotate_rewrite_refuses_an_inline_page() {
let mut b = PdfBuilder::new();
b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
b.object(
2,
"<< /Type /Pages /Count 1 /Kids [ << /Type /Page /Parent 2 0 R \
/MediaBox [0 0 612 792] >> ] >>",
);
let doc = Document::load(b.build(1)).expect("doc loads");
let result = rotate_rewrite(&doc, &[0], 90, WriteOptions::default());
let Err(Error::Other(message)) = result else {
panic!("expected Error::Other, got {result:?}");
};
assert!(message.contains("page 1"), "message: {message}");
assert!(
message.contains("cannot be edited in place"),
"message: {message}"
);
assert!(
message.contains("does not yet restructure"),
"message: {message}"
);
}
#[test]
fn rotate_rewrite_refuses_a_non_multiple_of_90() {
let doc = Document::load(multi_page_doc(&["one"])).expect("doc loads");
let result = rotate_rewrite(&doc, &[0], 45, WriteOptions::default());
let Err(Error::Other(message)) = result else {
panic!("expected Error::Other, got {result:?}");
};
assert!(message.contains("multiple of 90"), "message: {message}");
}
#[test]
fn rotate_rewrite_accepts_a_negative_multiple_of_90() {
let doc = Document::load(multi_page_doc(&["one"])).expect("doc loads");
let bytes =
rotate_rewrite(&doc, &[0], -90, WriteOptions::default()).expect("rotate succeeds");
let rotated = Document::load(bytes).expect("rotated document loads");
let page = rotated.page(0).expect("page exists");
assert_eq!(page.rotate, 270);
}
#[test]
fn rotate_rewrite_carries_info_along() {
let base = Pdf {
pages: vec![Page::new(PageSize::A4)],
metadata: Some(Metadata {
title: Some("Rotated Title".to_string()),
..Metadata::default()
}),
..Pdf::default()
}
.to_bytes()
.expect("base builds");
let doc = Document::load(base).expect("base loads");
assert!(
doc.xref().trailer.get_ref("Info").is_some(),
"the base's trailer must carry /Info for this test to exercise the carry"
);
let bytes =
rotate_rewrite(&doc, &[0], 90, WriteOptions::default()).expect("rotate succeeds");
let rotated = Document::load(bytes).expect("rotated document loads");
assert!(
rotated.xref().trailer.get_ref("Info").is_some(),
"the rewritten trailer still names an /Info dictionary"
);
assert_eq!(rotated.metadata().title.as_deref(), Some("Rotated Title"));
}
fn live_count(doc: &Document) -> usize {
doc.xref()
.iter()
.filter(|(_, entry)| !matches!(entry, XrefEntry::Free))
.count()
}
#[test]
fn rewrite_document_carries_info_along() {
let base = Pdf {
pages: vec![Page::new(PageSize::A4)],
metadata: Some(Metadata {
title: Some("Rewritten Title".to_string()),
..Metadata::default()
}),
..Pdf::default()
}
.to_bytes()
.expect("base builds");
let doc = Document::load(base).expect("base loads");
assert!(
doc.xref().trailer.get_ref("Info").is_some(),
"the base's trailer must carry /Info for this test to exercise the carry"
);
let bytes = rewrite_document(&doc, WriteOptions::default()).expect("rewrite succeeds");
let rewritten = Document::load(bytes).expect("rewritten document loads");
assert!(
rewritten.xref().trailer.get_ref("Info").is_some(),
"the rewritten trailer still names an /Info dictionary"
);
assert_eq!(
rewritten.metadata().title.as_deref(),
Some("Rewritten Title")
);
}
#[test]
fn rewrite_document_drops_an_unreferenced_object_and_keeps_text() {
let options = WriteOptions {
xref: XrefStyle::Table,
compress: false,
object_streams: false,
version: (1, 7),
};
let mut b = PdfBuilder::new();
b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
b.object(
3,
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] \
/Resources << /Font << /F1 5 0 R >> >> /Contents 4 0 R >>",
);
b.stream(4, "", b"BT /F1 12 Tf 72 720 Td (hello) Tj ET");
b.object(
5,
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>",
);
b.object(6, "<< /Extra (unreferenced) >>");
let doc = Document::load(b.build(1)).expect("doc loads");
assert_eq!(
live_count(&doc),
6,
"the fixture carries the extra object alongside the reachable five"
);
let bytes = rewrite_document(&doc, options).expect("rewrite succeeds");
let rewritten = Document::load(bytes).expect("rewritten document loads");
assert_eq!(
live_count(&rewritten),
5,
"the unreferenced object must not survive the rewrite"
);
let page = rewritten.page(0).expect("page exists");
let text = extract_text(&rewritten, &page, ReadingOrder::Content).expect("text extracts");
assert!(text.contains("hello"), "text: {text:?}");
}
#[test]
fn rewrite_document_collapses_an_appended_update_into_one_section() {
let base = Pdf {
pages: vec![Page::new(PageSize::A4)],
..Pdf::default()
}
.to_bytes()
.expect("base builds");
let base_doc = Document::load(base).expect("base loads");
let mut update = Update::new(&base_doc).expect("update opens");
let extra = update.reserve();
let mut dict = Dict::new();
dict.insert(name("Marker"), Object::Int(1));
update.set(extra, Object::Dict(dict));
let appended = update.bytes().expect("update appends");
let control_offset = startxref(&appended).expect("startxref present in the input");
let control_section =
parse_section_at(&appended, control_offset).expect("input section parses");
assert!(
control_section.prev.is_some(),
"the input must really carry a /Prev chain for this test to exercise the collapse"
);
let appended_doc = Document::load(appended).expect("appended document loads");
let bytes =
rewrite_document(&appended_doc, WriteOptions::default()).expect("rewrite succeeds");
let offset = startxref(&bytes).expect("startxref present");
let section = parse_section_at(&bytes, offset).expect("section parses");
assert!(
section.prev.is_none(),
"the rewrite must collapse the update chain into one section"
);
}
#[test]
fn rewrite_with_metadata_merges_fields_into_a_fresh_file() {
let base = Pdf {
pages: vec![Page::new(PageSize::A4)],
metadata: Some(Metadata {
title: Some("Old".to_string()),
author: Some("Keep".to_string()),
..Metadata::default()
}),
..Pdf::default()
}
.to_bytes()
.expect("base builds");
let doc = Document::load(base.clone()).expect("base loads");
let bytes = rewrite_with_metadata(
&doc,
Metadata {
title: Some("New".to_string()),
..Metadata::default()
},
WriteOptions::default(),
)
.expect("rewrite succeeds");
assert!(
!bytes.starts_with(&base[..]),
"a metadata rewrite must not merely append an update onto the base"
);
let rewritten = Document::load(bytes).expect("rewritten document loads");
let meta = rewritten.metadata();
assert_eq!(meta.title.as_deref(), Some("New"));
assert_eq!(meta.author.as_deref(), Some("Keep"));
let new_root = rewritten
.xref()
.trailer
.get_ref("Root")
.expect("rewritten trailer names /Root");
let catalog = rewritten.get(new_root).expect("catalog resolves");
let metadata_ref = catalog
.as_dict()
.expect("catalog is a dictionary")
.get_ref("Metadata")
.expect("the base's XMP packet must still be named");
let stream = rewritten
.get(metadata_ref)
.expect("metadata stream resolves");
let text = String::from_utf8(
stream
.as_stream()
.expect("metadata is a stream")
.data
.clone(),
)
.expect("packet is utf-8");
assert!(text.contains("New"), "packet: {text}");
assert!(text.contains("Keep"), "packet: {text}");
assert!(!text.contains("Old"), "packet: {text}");
}
#[test]
fn rewrite_with_metadata_creates_info_when_absent() {
let base = Pdf {
pages: vec![Page::new(PageSize::A4)],
..Pdf::default()
}
.to_bytes()
.expect("base builds");
let doc = Document::load(base).expect("base loads");
let bytes = rewrite_with_metadata(
&doc,
Metadata {
title: Some("Fresh".to_string()),
..Metadata::default()
},
WriteOptions::default(),
)
.expect("rewrite succeeds");
let rewritten = Document::load(bytes).expect("rewritten document loads");
assert_eq!(rewritten.metadata().title.as_deref(), Some("Fresh"));
let new_root = rewritten
.xref()
.trailer
.get_ref("Root")
.expect("rewritten trailer names /Root");
let catalog = rewritten.get(new_root).expect("catalog resolves");
assert!(
catalog
.as_dict()
.expect("catalog is a dictionary")
.get("Metadata")
.is_none(),
"a base with no XMP packet must not gain one from a metadata rewrite"
);
}
#[test]
fn rewrite_with_metadata_translates_a_kept_indirect_info_value() {
let mut w = Writer::new(WriteOptions {
xref: XrefStyle::Table,
..WriteOptions::default()
});
let pages_root = w.reserve();
let page = w.reserve();
let mut page_dict = Dict::new();
page_dict.insert(name("Type"), Object::Name(name("Page")));
page_dict.insert(name("Parent"), Object::Ref(pages_root));
page_dict.insert(name("Resources"), Object::Dict(Dict::new()));
page_dict.insert(
name("MediaBox"),
Object::Array(vec![
Object::Int(0),
Object::Int(0),
Object::Int(612),
Object::Int(792),
]),
);
w.fill(page, Object::Dict(page_dict))
.expect("page slot fills");
let mut pages_dict = Dict::new();
pages_dict.insert(name("Type"), Object::Name(name("Pages")));
pages_dict.insert(name("Kids"), Object::Array(vec![Object::Ref(page)]));
pages_dict.insert(name("Count"), Object::Int(1));
w.fill(pages_root, Object::Dict(pages_dict))
.expect("pages slot fills");
let title_ref = w.put(Object::String(b"Indirect Title".to_vec()));
let mut info = Dict::new();
info.insert(name("Title"), Object::Ref(title_ref));
let info_ref = w.put(Object::Dict(info));
w.set_info(info_ref);
let mut catalog = Dict::new();
catalog.insert(name("Type"), Object::Name(name("Catalog")));
catalog.insert(name("Pages"), Object::Ref(pages_root));
let root = w.put(Object::Dict(catalog));
let base = w.finish(root).expect("base finishes");
let doc = Document::load(base).expect("base loads");
let bytes = rewrite_with_metadata(
&doc,
Metadata {
author: Some("New Author".to_string()),
..Metadata::default()
},
WriteOptions::default(),
)
.expect("rewrite succeeds");
let rewritten = Document::load(bytes).expect("rewritten document loads");
let meta = rewritten.metadata();
assert_eq!(
meta.title.as_deref(),
Some("Indirect Title"),
"a kept indirect /Info value must translate rather than alias"
);
assert_eq!(meta.author.as_deref(), Some("New Author"));
let page = rewritten.page(0).expect("page still resolves");
assert_eq!(
page.dict().get_name("Type"),
Some(&Name("Page".to_string())),
"the page object must not have been aliased by an untranslated /Info reference"
);
}
#[test]
fn rewrite_with_metadata_translates_a_reference_nested_in_an_info_value() {
let mut w = Writer::new(WriteOptions {
xref: XrefStyle::Table,
..WriteOptions::default()
});
let pages_root = w.reserve();
let page = w.reserve();
let mut page_dict = Dict::new();
page_dict.insert(name("Type"), Object::Name(name("Page")));
page_dict.insert(name("Parent"), Object::Ref(pages_root));
page_dict.insert(name("Resources"), Object::Dict(Dict::new()));
page_dict.insert(
name("MediaBox"),
Object::Array(vec![
Object::Int(0),
Object::Int(0),
Object::Int(612),
Object::Int(792),
]),
);
w.fill(page, Object::Dict(page_dict))
.expect("page slot fills");
let mut pages_dict = Dict::new();
pages_dict.insert(name("Type"), Object::Name(name("Pages")));
pages_dict.insert(name("Kids"), Object::Array(vec![Object::Ref(page)]));
pages_dict.insert(name("Count"), Object::Int(1));
w.fill(pages_root, Object::Dict(pages_dict))
.expect("pages slot fills");
let witness = w.put(Object::String(b"Witness".to_vec()));
let mut info = Dict::new();
info.insert(name("Title"), Object::String(b"Plain Title".to_vec()));
info.insert(
name("CustomRefs"),
Object::Array(vec![Object::Ref(witness)]),
);
let info_ref = w.put(Object::Dict(info));
w.set_info(info_ref);
let mut catalog = Dict::new();
catalog.insert(name("Type"), Object::Name(name("Catalog")));
catalog.insert(name("Pages"), Object::Ref(pages_root));
let root = w.put(Object::Dict(catalog));
let base = w.finish(root).expect("base finishes");
let doc = Document::load(base).expect("base loads");
let bytes = rewrite_with_metadata(
&doc,
Metadata {
author: Some("New Author".to_string()),
..Metadata::default()
},
WriteOptions::default(),
)
.expect("rewrite succeeds");
let rewritten = Document::load(bytes).expect("rewritten document loads");
let new_info_ref = rewritten
.xref()
.trailer
.get_ref("Info")
.expect("rewritten trailer names /Info");
let info_dict = rewritten.get(new_info_ref).expect("info resolves");
let custom = info_dict
.as_dict()
.expect("info is a dictionary")
.get("CustomRefs")
.expect("CustomRefs survives the merge, untouched by the recognized fields");
let Object::Array(items) = custom else {
panic!("CustomRefs must still be an array, got {custom:?}");
};
let Object::Ref(witness_target) = items[0] else {
panic!(
"CustomRefs[0] must still be a reference, got {:?}",
items[0]
);
};
let resolved = rewritten
.get(witness_target)
.expect("the translated reference must resolve to a real object");
assert_eq!(
resolved.as_str_bytes(),
Some(&b"Witness"[..]),
"a reference nested inside an /Info value must translate into the \
target's own numbering, not alias whatever the rewrite happens to \
number the same"
);
}
}