diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-04-17 13:25:31 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-04-17 13:26:49 +0200 |
| commit | 9bdc4a7de0fb685fa2b8d02280e70aa0b5d92bf9 (patch) | |
| tree | e67c75c32183e734272943107ad2d034b1e8e818 /src/export | |
| parent | 428c55b6eed3536bb228924c6fb0ad6cea6d6d4b (diff) | |
Write PDF outline
Diffstat (limited to 'src/export')
| -rw-r--r-- | src/export/pdf/mod.rs | 29 | ||||
| -rw-r--r-- | src/export/pdf/outline.rs | 89 |
2 files changed, 73 insertions, 45 deletions
diff --git a/src/export/pdf/mod.rs b/src/export/pdf/mod.rs index bdbb2bb7..8fc76471 100644 --- a/src/export/pdf/mod.rs +++ b/src/export/pdf/mod.rs @@ -13,7 +13,6 @@ use pdf_writer::types::Direction; use pdf_writer::{Finish, Name, PdfWriter, Ref, TextStr}; use xmp_writer::{LangId, RenditionClass, XmpWriter}; -use self::outline::HeadingNode; use self::page::Page; use crate::doc::{Document, Lang}; use crate::font::Font; @@ -54,7 +53,6 @@ pub struct PdfContext<'a> { image_map: Remapper<Image>, glyph_sets: HashMap<Font, HashSet<u16>>, languages: HashMap<Lang, usize>, - heading_tree: Vec<HeadingNode>, } impl<'a> PdfContext<'a> { @@ -76,36 +74,12 @@ impl<'a> PdfContext<'a> { image_map: Remapper::new(), glyph_sets: HashMap::new(), languages: HashMap::new(), - heading_tree: vec![], } } } /// Write the document catalog. fn write_catalog(ctx: &mut PdfContext) { - // Build the outline tree. - let outline_root_id = (!ctx.heading_tree.is_empty()).then(|| ctx.alloc.bump()); - let outline_start_ref = ctx.alloc; - let len = ctx.heading_tree.len(); - let mut prev_ref = None; - - for (i, node) in std::mem::take(&mut ctx.heading_tree).iter().enumerate() { - prev_ref = Some(outline::write_outline_item( - ctx, - node, - outline_root_id.unwrap(), - prev_ref, - i + 1 == len, - )); - } - - if let Some(outline_root_id) = outline_root_id { - let mut outline_root = ctx.writer.outline(outline_root_id); - outline_root.first(outline_start_ref); - outline_root.last(Ref::new(ctx.alloc.get() - 1)); - outline_root.count(ctx.heading_tree.len() as i32); - } - let lang = ctx .languages .iter() @@ -118,6 +92,9 @@ fn write_catalog(ctx: &mut PdfContext) { Direction::L2R }; + // Write the outline tree. + let outline_root_id = outline::write_outline(ctx); + // Write the document information. let mut info = ctx.writer.document_info(ctx.alloc.bump()); let mut xmp = XmpWriter::new(); diff --git a/src/export/pdf/outline.rs b/src/export/pdf/outline.rs index 1b335474..03ca3b27 100644 --- a/src/export/pdf/outline.rs +++ b/src/export/pdf/outline.rs @@ -1,32 +1,76 @@ -use ecow::EcoString; +use std::num::NonZeroUsize; + use pdf_writer::{Finish, Ref, TextStr}; use super::{AbsExt, PdfContext, RefExt}; -use crate::geom::{Abs, Point}; +use crate::geom::Abs; +use crate::model::Content; +use crate::util::NonZeroExt; + +/// Construct the outline for the document. +pub fn write_outline(ctx: &mut PdfContext) -> Option<Ref> { + let mut tree: Vec<HeadingNode> = vec![]; + for heading in ctx.introspector.query(&item!(heading_func).select()) { + let leaf = HeadingNode::leaf(heading); + if let Some(last) = tree.last_mut() { + if last.try_insert(leaf.clone(), NonZeroUsize::ONE) { + continue; + } + } + + tree.push(leaf); + } + + if tree.is_empty() { + return None; + } + + let root_id = ctx.alloc.bump(); + let start_ref = ctx.alloc; + let len = tree.len(); + + let mut prev_ref = None; + for (i, node) in tree.iter().enumerate() { + prev_ref = Some(write_outline_item(ctx, node, root_id, prev_ref, i + 1 == len)); + } + + ctx.writer + .outline(root_id) + .first(start_ref) + .last(Ref::new(ctx.alloc.get() - 1)) + .count(tree.len() as i32); + + Some(root_id) +} /// A heading in the outline panel. #[derive(Debug, Clone)] -pub struct HeadingNode { - pub content: EcoString, - pub level: usize, - pub position: Point, - pub page: Ref, - pub children: Vec<HeadingNode>, +struct HeadingNode { + element: Content, + level: NonZeroUsize, + children: Vec<HeadingNode>, } impl HeadingNode { - pub fn len(&self) -> usize { + fn leaf(element: Content) -> Self { + HeadingNode { + level: element.expect_field::<NonZeroUsize>("level"), + element, + children: Vec::new(), + } + } + + fn len(&self) -> usize { 1 + self.children.iter().map(Self::len).sum::<usize>() } - #[allow(unused)] - pub fn try_insert(&mut self, child: Self, level: usize) -> bool { + fn try_insert(&mut self, child: Self, level: NonZeroUsize) -> bool { if level >= child.level { return false; } if let Some(last) = self.children.last_mut() { - if last.try_insert(child.clone(), level + 1) { + if last.try_insert(child.clone(), level.saturating_add(1)) { return true; } } @@ -37,7 +81,7 @@ impl HeadingNode { } /// Write an outline item and all its children. -pub fn write_outline_item( +fn write_outline_item( ctx: &mut PdfContext, node: &HeadingNode, parent_ref: Ref, @@ -65,12 +109,19 @@ pub fn write_outline_item( outline.count(-(node.children.len() as i32)); } - outline.title(TextStr(&node.content)); - outline.dest_direct().page(node.page).xyz( - node.position.x.to_f32(), - (node.position.y + Abs::pt(3.0)).to_f32(), - None, - ); + outline.title(TextStr(node.element.plain_text().trim())); + + let loc = node.element.location().unwrap(); + let pos = ctx.introspector.position(loc); + let index = pos.page.get() - 1; + if let Some(&height) = ctx.page_heights.get(index) { + let y = (pos.point.y - Abs::pt(10.0)).max(Abs::zero()); + outline.dest_direct().page(ctx.page_refs[index]).xyz( + pos.point.x.to_f32(), + height - y.to_f32(), + None, + ); + } outline.finish(); |
