Fix page range issue

This commit is contained in:
Laurenz Stampfl 2025-03-18 17:25:41 +01:00
parent 27c5e0081a
commit 9b5ee82c16
3 changed files with 54 additions and 27 deletions

View File

@ -52,10 +52,13 @@ pub fn convert(
}; };
let mut document = Document::new_with(settings); let mut document = Document::new_with(settings);
let page_index_converter = PageIndexConverter::new(&typst_document, &options);
let named_destinations = collect_named_destinations(&typst_document, &page_index_converter);
let mut gc = GlobalContext::new( let mut gc = GlobalContext::new(
typst_document, typst_document,
options, options,
collect_named_destinations(typst_document, options), named_destinations,
page_index_converter
); );
convert_pages(&mut gc, &mut document)?; convert_pages(&mut gc, &mut document)?;
@ -68,12 +71,9 @@ pub fn convert(
} }
fn convert_pages(gc: &mut GlobalContext, document: &mut Document) -> SourceResult<()> { fn convert_pages(gc: &mut GlobalContext, document: &mut Document) -> SourceResult<()> {
let mut skipped_pages = 0;
for (i, typst_page) in gc.document.pages.iter().enumerate() { for (i, typst_page) in gc.document.pages.iter().enumerate() {
if gc.page_excluded(i) { if gc.page_index_converter.pdf_page_index(i).is_none() {
// Don't export this page. // Don't export this page.
skipped_pages += 1;
continue; continue;
} else { } else {
let mut settings = PageSettings::new( let mut settings = PageSettings::new(
@ -93,7 +93,7 @@ fn convert_pages(gc: &mut GlobalContext, document: &mut Document) -> SourceResul
// the real (not logical) page numbers. Here, the final PDF page number // the real (not logical) page numbers. Here, the final PDF page number
// will differ, but we can at least use labels to indicate what was // will differ, but we can at least use labels to indicate what was
// the corresponding real page number in the Typst document. // the corresponding real page number in the Typst document.
(skipped_pages > 0).then(|| PageLabel::arabic(i + 1)) gc.page_index_converter.has_skipped_pages().then(|| PageLabel::arabic(i + 1))
}) })
{ {
settings = settings.with_page_label(label); settings = settings.with_page_label(label);
@ -219,35 +219,31 @@ pub(crate) struct GlobalContext<'a> {
/// Options for PDF export. /// Options for PDF export.
pub(crate) options: &'a PdfOptions<'a>, pub(crate) options: &'a PdfOptions<'a>,
/// Mapping between locations in the document and named destinations. /// Mapping between locations in the document and named destinations.
pub(crate) loc_to_named: HashMap<Location, NamedDestination>, pub(crate) loc_to_names: HashMap<Location, NamedDestination>,
/// The languages used throughout the document. /// The languages used throughout the document.
pub(crate) languages: BTreeMap<Lang, usize>, pub(crate) languages: BTreeMap<Lang, usize>,
pub(crate) page_index_converter: PageIndexConverter
} }
impl<'a> GlobalContext<'a> { impl<'a> GlobalContext<'a> {
pub(crate) fn new( pub(crate) fn new(
document: &'a PagedDocument, document: &'a PagedDocument,
options: &'a PdfOptions, options: &'a PdfOptions,
loc_to_named: HashMap<Location, NamedDestination>, loc_to_names: HashMap<Location, NamedDestination>,
page_index_converter: PageIndexConverter
) -> GlobalContext<'a> { ) -> GlobalContext<'a> {
Self { Self {
fonts_forward: HashMap::new(), fonts_forward: HashMap::new(),
fonts_backward: HashMap::new(), fonts_backward: HashMap::new(),
document, document,
options, options,
loc_to_named, loc_to_names,
image_to_spans: HashMap::new(), image_to_spans: HashMap::new(),
image_spans: HashSet::new(), image_spans: HashSet::new(),
languages: BTreeMap::new(), languages: BTreeMap::new(),
page_index_converter
} }
} }
pub(crate) fn page_excluded(&self, page_index: usize) -> bool {
self.options
.page_ranges
.as_ref()
.is_some_and(|ranges| !ranges.includes_page_index(page_index))
}
} }
pub(crate) fn handle_frame( pub(crate) fn handle_frame(
@ -556,7 +552,7 @@ fn finish(
fn collect_named_destinations( fn collect_named_destinations(
document: &PagedDocument, document: &PagedDocument,
options: &PdfOptions, pic: &PageIndexConverter
) -> HashMap<Location, NamedDestination> { ) -> HashMap<Location, NamedDestination> {
let mut locs_to_names = HashMap::new(); let mut locs_to_names = HashMap::new();
@ -582,11 +578,7 @@ fn collect_named_destinations(
let y = (pos.point.y - Abs::pt(10.0)).max(Abs::zero()); let y = (pos.point.y - Abs::pt(10.0)).max(Abs::zero());
// Only add named destination if page belonging to the position is exported. // Only add named destination if page belonging to the position is exported.
if options if let Some(index) = pic.pdf_page_index(index) {
.page_ranges
.as_ref()
.is_some_and(|ranges| !ranges.includes_page_index(index))
{
let named = NamedDestination::new( let named = NamedDestination::new(
label.resolve().to_string(), label.resolve().to_string(),
XyzDestination::new( XyzDestination::new(
@ -631,3 +623,38 @@ fn get_configuration(options: &PdfOptions) -> SourceResult<Configuration> {
Ok(config) Ok(config)
} }
pub(crate) struct PageIndexConverter {
page_indices: HashMap<usize, usize>,
skipped_pages: usize,
}
impl PageIndexConverter {
pub fn new(document: &PagedDocument, options: &PdfOptions) -> Self {
let mut page_indices = HashMap::new();
let mut skipped_pages = 0;
for i in 0..document.pages.len() {
if options
.page_ranges
.as_ref()
.is_some_and(|ranges| !ranges.includes_page_index(i))
{
skipped_pages += 1;
} else {
page_indices.insert(i, i - skipped_pages);
}
}
Self { page_indices, skipped_pages }
}
pub(crate) fn has_skipped_pages(&self) -> bool {
self.skipped_pages > 0
}
/// Get the PDF page index of a page index, if it's not excluded.
pub(crate) fn pdf_page_index(&self, page_index: usize) -> Option<usize> {
self.page_indices.get(&page_index).copied()
}
}

View File

@ -58,7 +58,7 @@ pub(crate) fn handle_link(
} }
Destination::Position(p) => *p, Destination::Position(p) => *p,
Destination::Location(loc) => { Destination::Location(loc) => {
if let Some(nd) = gc.loc_to_named.get(loc) { if let Some(nd) = gc.loc_to_names.get(loc) {
// If a named destination has been registered, it's already guaranteed to // If a named destination has been registered, it's already guaranteed to
// not point to an excluded page. // not point to an excluded page.
fc.push_annotation( fc.push_annotation(
@ -79,13 +79,13 @@ pub(crate) fn handle_link(
}; };
let page_index = pos.page.get() - 1; let page_index = pos.page.get() - 1;
if !gc.page_excluded(page_index) { if let Some(index) = gc.page_index_converter.pdf_page_index(page_index) {
fc.push_annotation( fc.push_annotation(
LinkAnnotation::new( LinkAnnotation::new(
rect, rect,
None, None,
Target::Destination(krilla::destination::Destination::Xyz( Target::Destination(krilla::destination::Destination::Xyz(
XyzDestination::new(page_index, pos.point.to_krilla()), XyzDestination::new(index, pos.point.to_krilla()),
)), )),
) )
.into(), .into(),

View File

@ -127,10 +127,10 @@ impl<'a> HeadingNode<'a> {
let pos = gc.document.introspector.position(loc); let pos = gc.document.introspector.position(loc);
let page_index = pos.page.get() - 1; let page_index = pos.page.get() - 1;
if !gc.page_excluded(page_index) { if let Some(index) = gc.page_index_converter.pdf_page_index(page_index) {
let y = (pos.point.y - Abs::pt(10.0)).max(Abs::zero()); let y = (pos.point.y - Abs::pt(10.0)).max(Abs::zero());
let dest = XyzDestination::new( let dest = XyzDestination::new(
page_index, index,
krilla::geom::Point::from_xy(pos.point.x.to_f32(), y.to_f32()), krilla::geom::Point::from_xy(pos.point.x.to_f32(), y.to_f32()),
); );