summaryrefslogtreecommitdiff
path: root/src/export/pdf
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-04-17 13:25:31 +0200
committerLaurenz <laurmaedje@gmail.com>2023-04-17 13:26:49 +0200
commit9bdc4a7de0fb685fa2b8d02280e70aa0b5d92bf9 (patch)
treee67c75c32183e734272943107ad2d034b1e8e818 /src/export/pdf
parent428c55b6eed3536bb228924c6fb0ad6cea6d6d4b (diff)
Write PDF outline
Diffstat (limited to 'src/export/pdf')
-rw-r--r--src/export/pdf/mod.rs29
-rw-r--r--src/export/pdf/outline.rs89
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();