diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/eval/library.rs | 2 | ||||
| -rw-r--r-- | src/export/pdf/mod.rs | 29 | ||||
| -rw-r--r-- | src/export/pdf/outline.rs | 89 | ||||
| -rw-r--r-- | src/ide/analyze.rs | 8 | ||||
| -rw-r--r-- | src/model/content.rs | 67 | ||||
| -rw-r--r-- | src/model/element.rs | 6 |
6 files changed, 119 insertions, 82 deletions
diff --git a/src/eval/library.rs b/src/eval/library.rs index b93aa0dd..0c635864 100644 --- a/src/eval/library.rs +++ b/src/eval/library.rs @@ -72,6 +72,8 @@ pub struct LangItems { ) -> Vec<(EcoString, Option<EcoString>)>, /// A section heading: `= Introduction`. pub heading: fn(level: NonZeroUsize, body: Content) -> Content, + /// The heading function. + pub heading_func: ElemFunc, /// An item in a bullet list: `- ...`. pub list_item: fn(body: Content) -> Content, /// An item in an enumeration (numbered list): `+ ...` or `1. ...`. 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(); diff --git a/src/ide/analyze.rs b/src/ide/analyze.rs index 27c6c2a4..62816cbc 100644 --- a/src/ide/analyze.rs +++ b/src/ide/analyze.rs @@ -96,16 +96,16 @@ pub fn analyze_labels( // Labels in the document. for elem in introspector.all() { - let Some(label) = elem.label() else { continue }; + let Some(label) = elem.label().cloned() else { continue }; let details = elem .field("caption") - .or_else(|| elem.field("body")) .and_then(|field| match field { Value::Content(content) => Some(content), _ => None, }) - .and_then(|content| (items.text_str)(&content)); - output.push((label.clone(), details)); + .unwrap_or(elem) + .plain_text(); + output.push((label, Some(details))); } let split = output.len(); diff --git a/src/model/content.rs b/src/model/content.rs index 3f02369f..4300790c 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -3,12 +3,12 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::iter::Sum; use std::ops::{Add, AddAssign}; -use comemo::{Prehashed, Tracked}; +use comemo::Prehashed; use ecow::{eco_format, EcoString, EcoVec}; use super::{ - element, Behave, Behaviour, ElemFunc, Element, Fold, Guard, Introspector, Label, - Locatable, Location, Recipe, Selector, Style, Styles, Synthesize, + element, Behave, Behaviour, ElemFunc, Element, Fold, Guard, Label, Locatable, + Location, PlainText, Recipe, Selector, Style, Styles, Synthesize, }; use crate::diag::{SourceResult, StrResult}; use crate::doc::Meta; @@ -359,52 +359,53 @@ impl Content { /// Queries the content tree for all elements that match the given selector. /// - /// # Show rules /// Elements produced in `show` rules will not be included in the results. - pub fn query( - &self, - introspector: Tracked<Introspector>, - selector: Selector, - ) -> Vec<&Content> { + pub fn query(&self, selector: Selector) -> Vec<&Content> { let mut results = Vec::new(); - self.query_into(introspector, &selector, &mut results); + self.traverse(&mut |element| { + if selector.matches(element) { + results.push(element); + } + }); results } - /// Queries the content tree for all elements that match the given selector - /// and stores the results inside of the `results` vec. - fn query_into<'a>( - &'a self, - introspector: Tracked<Introspector>, - selector: &Selector, - results: &mut Vec<&'a Content>, - ) { - if selector.matches(self) { - results.push(self); - } + /// Extracts the plain text of this content. + pub fn plain_text(&self) -> EcoString { + let mut text = EcoString::new(); + self.traverse(&mut |element| { + if let Some(textable) = element.with::<dyn PlainText>() { + textable.plain_text(&mut text); + } + }); + text + } + + /// Traverse this content. + fn traverse<'a, F>(&'a self, f: &mut F) + where + F: FnMut(&'a Content), + { + f(self); for attr in &self.attrs { match attr { - Attr::Child(child) => child.query_into(introspector, selector, results), - Attr::Value(value) => walk_value(introspector, value, selector, results), + Attr::Child(child) => child.traverse(f), + Attr::Value(value) => walk_value(value, f), _ => {} } } /// Walks a given value to find any content that matches the selector. - fn walk_value<'a>( - introspector: Tracked<Introspector>, - value: &'a Value, - selector: &Selector, - results: &mut Vec<&'a Content>, - ) { + fn walk_value<'a, F>(value: &'a Value, f: &mut F) + where + F: FnMut(&'a Content), + { match value { - Value::Content(content) => { - content.query_into(introspector, selector, results) - } + Value::Content(content) => content.traverse(f), Value::Array(array) => { for value in array { - walk_value(introspector, value, selector, results); + walk_value(value, f); } } _ => {} diff --git a/src/model/element.rs b/src/model/element.rs index 4c825a20..e26848b1 100644 --- a/src/model/element.rs +++ b/src/model/element.rs @@ -151,3 +151,9 @@ impl Debug for Label { /// Indicates that an element cannot be labelled. pub trait Unlabellable {} + +/// Tries to extract the plain-text representation of the element. +pub trait PlainText { + /// Write this element's plain text into the given buffer. + fn plain_text(&self, text: &mut EcoString); +} |
