From 72d3f3fffabe6872eb7839585bea925b89aac6a4 Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Wed, 8 Jun 2022 18:43:00 +0200 Subject: [PATCH] CR: Whoever said orange is the new pink was seriously disturbed. --- src/export/pdf.rs | 228 ++++++++++++++----------------- src/frame.rs | 41 +++--- src/library/graphics/shape.rs | 4 +- src/library/layout/grid.rs | 63 ++------- src/library/layout/page.rs | 14 +- src/library/structure/heading.rs | 5 +- src/library/structure/list.rs | 4 +- src/library/text/par.rs | 1 - src/model/content.rs | 2 +- src/model/styles.rs | 7 - 10 files changed, 148 insertions(+), 221 deletions(-) diff --git a/src/export/pdf.rs b/src/export/pdf.rs index f8144ad93..b8fc1e39d 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -43,34 +43,42 @@ const SRGB_GRAY: Name<'static> = Name(b"srgbgray"); /// An exporter for a whole PDF document. struct PdfExporter<'a> { + writer: PdfWriter, fonts: &'a FontStore, images: &'a ImageStore, - writer: PdfWriter, - alloc: Ref, pages: Vec, - face_map: Remapper, + page_heights: Vec, + alloc: Ref, + page_tree_ref: Ref, face_refs: Vec, - glyph_sets: HashMap>, - image_map: Remapper, image_refs: Vec, page_refs: Vec, + face_map: Remapper, + image_map: Remapper, + glyph_sets: HashMap>, + languages: HashMap, heading_tree: Vec, } impl<'a> PdfExporter<'a> { fn new(ctx: &'a Context) -> Self { + let mut alloc = Ref::new(1); + let page_tree_ref = alloc.bump(); Self { + writer: PdfWriter::new(), fonts: &ctx.fonts, images: &ctx.images, - writer: PdfWriter::new(), - alloc: Ref::new(1), pages: vec![], - face_map: Remapper::new(), - face_refs: vec![], - glyph_sets: HashMap::new(), - image_map: Remapper::new(), - image_refs: vec![], + page_heights: vec![], + alloc, + page_tree_ref, page_refs: vec![], + face_refs: vec![], + image_refs: vec![], + face_map: Remapper::new(), + image_map: Remapper::new(), + glyph_sets: HashMap::new(), + languages: HashMap::new(), heading_tree: vec![], } } @@ -79,7 +87,15 @@ impl<'a> PdfExporter<'a> { self.build_pages(frames); self.write_fonts(); self.write_images(); - self.write_structure(); + + // The root page tree. + for page in std::mem::take(&mut self.pages).into_iter() { + self.write_page(page); + } + + self.write_page_tree(); + self.write_catalog(); + self.writer.finish() } @@ -88,6 +104,7 @@ impl<'a> PdfExporter<'a> { let page_id = self.alloc.bump(); self.page_refs.push(page_id); let page = PageExporter::new(self, page_id).export(frame); + self.page_heights.push(page.size.y.to_f32()); self.pages.push(page); } } @@ -307,45 +324,11 @@ impl<'a> PdfExporter<'a> { } } - fn write_structure(&mut self) { - // The root page tree. - let page_tree_ref = self.alloc.bump(); - - // The page objects (non-root nodes in the page tree). - let mut page_heights = - self.pages.iter().map(|page| page.size.y.to_f32()).collect(); - - let mut languages = HashMap::new(); - - for (page, page_id) in std::mem::take(&mut self.pages) - .into_iter() - .zip(self.page_refs.clone().iter()) - { - self.write_page( - page, - *page_id, - page_tree_ref, - &mut languages, - &mut page_heights, - ); - } - - self.write_page_tree(page_tree_ref); - self.write_catalog(page_tree_ref, &languages); - } - - fn write_page( - &mut self, - page: Page, - page_id: Ref, - page_tree_ref: Ref, - languages: &mut HashMap, - page_heights: &mut Vec, - ) { + fn write_page(&mut self, page: Page) { let content_id = self.alloc.bump(); - let mut page_writer = self.writer.page(page_id); - page_writer.parent(page_tree_ref); + let mut page_writer = self.writer.page(page.id); + page_writer.parent(self.page_tree_ref); let w = page.size.x.to_f32(); let h = page.size.y.to_f32(); @@ -364,7 +347,7 @@ impl<'a> PdfExporter<'a> { } Destination::Internal(loc) => { let index = loc.page - 1; - let height = page_heights[index]; + let height = self.page_heights[index]; link.action() .action_type(ActionType::GoTo) .destination_direct() @@ -377,20 +360,13 @@ impl<'a> PdfExporter<'a> { annotations.finish(); page_writer.finish(); - for (lang, count) in page.languages { - languages - .entry(lang) - .and_modify(|x| *x += count) - .or_insert_with(|| count); - } - self.writer .stream(content_id, &deflate(&page.content.finish())) .filter(Filter::FlateDecode); } - fn write_page_tree(&mut self, page_tree_ref: Ref) { - let mut pages = self.writer.pages(page_tree_ref); + fn write_page_tree(&mut self) { + let mut pages = self.writer.pages(self.page_tree_ref); pages .count(self.page_refs.len() as i32) .kids(self.page_refs.iter().copied()); @@ -420,34 +396,36 @@ impl<'a> PdfExporter<'a> { pages.finish(); } - fn write_catalog(&mut self, page_tree_ref: Ref, languages: &HashMap) { + fn write_catalog(&mut self) { // Build the outline tree. - let outline_root_id = self.alloc.bump(); - + let outline_root_id = (!self.heading_tree.is_empty()).then(|| self.alloc.bump()); let outline_start_ref = self.alloc; + let len = self.heading_tree.len(); + let mut prev_ref = None; for (i, node) in std::mem::take(&mut self.heading_tree).iter().enumerate() { - self.write_outline_item( + prev_ref = Some(self.write_outline_item( node, - i == 0, - i + 1 == self.heading_tree.len(), - outline_root_id, - ); + outline_root_id.unwrap(), + prev_ref, + i + 1 == len, + )); } - if !self.heading_tree.is_empty() { + if let Some(outline_root_id) = outline_root_id { let mut outline_root = self.writer.outline(outline_root_id); outline_root.first(outline_start_ref); outline_root.last(Ref::new(self.alloc.get() - 1)); outline_root.count(self.heading_tree.len() as i32); } - let lang = languages - .into_iter() - .max_by(|(_, v1), (_, v2)| v1.cmp(v2)) - .map(|(k, _)| k); + let lang = self + .languages + .iter() + .max_by_key(|(&lang, &count)| (count, lang)) + .map(|(&k, _)| k); - let dir = if lang.copied().map(Lang::dir) == Some(Dir::RTL) { + let dir = if lang.map(Lang::dir) == Some(Dir::RTL) { Direction::R2L } else { Direction::L2R @@ -456,10 +434,10 @@ impl<'a> PdfExporter<'a> { // Write the document information, catalog and wrap it up! self.writer.document_info(self.alloc.bump()).creator(TextStr("Typst")); let mut catalog = self.writer.catalog(self.alloc.bump()); - catalog.pages(page_tree_ref); + catalog.pages(self.page_tree_ref); catalog.viewer_preferences().direction(dir); - if !self.heading_tree.is_empty() { + if let Some(outline_root_id) = outline_root_id { catalog.outlines(outline_root_id); } @@ -473,29 +451,28 @@ impl<'a> PdfExporter<'a> { fn write_outline_item( &mut self, node: &HeadingNode, - is_first: bool, - is_last: bool, parent_ref: Ref, - ) { + prev_ref: Option, + is_last: bool, + ) -> Ref { let id = self.alloc.bump(); - let next = Ref::new(id.get() + node.len() as i32); + let next_ref = Ref::new(id.get() + node.len() as i32); let mut outline = self.writer.outline_item(id); outline.parent(parent_ref); if !is_last { - outline.next(next); + outline.next(next_ref); } - if !is_first { - outline.prev(Ref::new(id.get() - 1)); + if let Some(prev_rev) = prev_ref { + outline.prev(prev_rev); } if !node.children.is_empty() { let current_child = Ref::new(id.get() + 1); outline.first(current_child); - outline.last(Ref::new(next.get() - 1)); - + outline.last(Ref::new(next_ref.get() - 1)); outline.count(-1 * node.children.len() as i32); } @@ -508,36 +485,37 @@ impl<'a> PdfExporter<'a> { outline.finish(); - if !node.children.is_empty() { - for (i, child) in node.children.iter().enumerate() { - self.write_outline_item(child, i == 0, i + 1 == node.children.len(), id); - } + let mut prev_ref = None; + for (i, child) in node.children.iter().enumerate() { + prev_ref = Some(self.write_outline_item( + child, + id, + prev_ref, + i + 1 == node.children.len(), + )); } + + id } } /// An exporter for the contents of a single PDF page. -struct PageExporter<'a> { - fonts: &'a FontStore, - font_map: &'a mut Remapper, - image_map: &'a mut Remapper, - glyphs: &'a mut HashMap>, - heading_tree: &'a mut Vec, +struct PageExporter<'a, 'b> { + exporter: &'a mut PdfExporter<'b>, page_ref: Ref, - languages: HashMap, - bottom: f32, content: Content, - links: Vec<(Destination, Rect)>, state: State, saves: Vec, + bottom: f32, + links: Vec<(Destination, Rect)>, } /// Data for an exported page. struct Page { + id: Ref, size: Size, content: Content, links: Vec<(Destination, Rect)>, - languages: HashMap, } /// A simulated graphics state used to deduplicate graphics state changes and @@ -573,7 +551,7 @@ impl HeadingNode { } fn len(&self) -> usize { - 1 + self.children.iter().map(|c| c.len()).sum::() + 1 + self.children.iter().map(Self::len).sum::() } fn insert(&mut self, other: Heading, level: usize) -> bool { @@ -581,32 +559,27 @@ impl HeadingNode { return false; } - if !self.children.is_empty() && level + 1 > other.level { - return self.children.last_mut().unwrap().insert(other, level + 1); + if let Some(child) = self.children.last_mut() { + if child.insert(other.clone(), level + 1) { + return true; + } } - self.children - .push(HeadingNode { heading: other, children: Vec::new() }); - + self.children.push(Self::leaf(other)); true } } -impl<'a> PageExporter<'a> { - fn new(exporter: &'a mut PdfExporter, page_ref: Ref) -> Self { +impl<'a, 'b> PageExporter<'a, 'b> { + fn new(exporter: &'a mut PdfExporter<'b>, page_ref: Ref) -> Self { Self { - fonts: exporter.fonts, - font_map: &mut exporter.face_map, - image_map: &mut exporter.image_map, - glyphs: &mut exporter.glyph_sets, - heading_tree: &mut exporter.heading_tree, + exporter, page_ref, - languages: HashMap::new(), - bottom: 0.0, content: Content::new(), - links: vec![], state: State::default(), saves: vec![], + bottom: 0.0, + links: vec![], } } @@ -625,8 +598,8 @@ impl<'a> PageExporter<'a> { Page { size: frame.size, content: self.content, + id: self.page_ref, links: self.links, - languages: self.languages, } } @@ -634,17 +607,17 @@ impl<'a> PageExporter<'a> { if let Some(Role::Heading(level)) = frame.role() { let heading = Heading { position: Point::new(self.state.transform.tx, self.state.transform.ty), - content: frame.inner_text(), + content: frame.text(), page: self.page_ref, level, }; - if let Some(last) = self.heading_tree.last_mut() { + if let Some(last) = self.exporter.heading_tree.last_mut() { if !last.insert(heading.clone(), 1) { - self.heading_tree.push(HeadingNode::leaf(heading)) + self.exporter.heading_tree.push(HeadingNode::leaf(heading)) } } else { - self.heading_tree.push(HeadingNode::leaf(heading)) + self.exporter.heading_tree.push(HeadingNode::leaf(heading)) } } @@ -684,13 +657,14 @@ impl<'a> PageExporter<'a> { } fn write_text(&mut self, x: f32, y: f32, text: &Text) { - *self.languages.entry(text.lang).or_insert(0) += text.glyphs.len(); - self.glyphs + *self.exporter.languages.entry(text.lang).or_insert(0) += text.glyphs.len(); + self.exporter + .glyph_sets .entry(text.face_id) .or_default() .extend(text.glyphs.iter().map(|g| g.id)); - let face = self.fonts.get(text.face_id); + let face = self.exporter.fonts.get(text.face_id); self.set_fill(text.fill); self.set_font(text.face_id, text.size); @@ -804,8 +778,8 @@ impl<'a> PageExporter<'a> { } fn write_image(&mut self, x: f32, y: f32, id: ImageId, size: Size) { - self.image_map.insert(id); - let name = format_eco!("Im{}", self.image_map.map(id)); + self.exporter.image_map.insert(id); + let name = format_eco!("Im{}", self.exporter.image_map.map(id)); let w = size.x.to_f32(); let h = size.y.to_f32(); self.content.save_state(); @@ -868,8 +842,8 @@ impl<'a> PageExporter<'a> { fn set_font(&mut self, face_id: FaceId, size: Length) { if self.state.font != Some((face_id, size)) { - self.font_map.insert(face_id); - let name = format_eco!("F{}", self.font_map.map(face_id)); + self.exporter.face_map.insert(face_id); + let name = format_eco!("F{}", self.exporter.face_map.map(face_id)); self.content.set_font(Name(name.as_bytes()), size.to_f32()); self.state.font = Some((face_id, size)); } diff --git a/src/frame.rs b/src/frame.rs index 0a8a20548..7972b0697 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -71,11 +71,8 @@ impl Frame { /// group based on the number of elements in the frame. pub fn push_frame(&mut self, pos: Point, frame: impl FrameRepr) { if (self.elements.is_empty() || frame.as_ref().is_light()) - && (frame.as_ref().role().is_none() || self.role.is_none()) + && frame.as_ref().role().is_none() { - if self.role.is_none() { - self.role = frame.as_ref().role() - } frame.inline(self, self.layer(), pos); } else { self.elements.push((pos, Element::Group(Group::new(frame.share())))); @@ -98,11 +95,8 @@ impl Frame { /// Add a frame at a position in the background. pub fn prepend_frame(&mut self, pos: Point, frame: impl FrameRepr) { if (self.elements.is_empty() || frame.as_ref().is_light()) - && (frame.as_ref().role().is_none() || self.role.is_none()) + && frame.as_ref().role().is_none() { - if self.role.is_none() { - self.role = frame.as_ref().role() - } frame.inline(self, 0, pos); } else { self.elements @@ -149,10 +143,8 @@ impl Frame { /// Apply the given role to the frame if it doesn't already have one. pub fn apply_role(&mut self, role: Role) { - match self.role { - None => self.role = Some(role), - Some(old) if old.is_weak() => self.role = Some(role), - Some(_) => {} + if self.role.map_or(true, Role::is_weak) { + self.role = Some(role); } } @@ -179,24 +171,29 @@ impl Frame { } /// Recover the text inside of the frame and its children. - pub fn inner_text(&self) -> EcoString { - let mut res = EcoString::new(); + pub fn text(&self) -> EcoString { + let mut text = EcoString::new(); for (_, element) in &self.elements { match element { - Element::Text(text) => res.push_str( - &text.glyphs.iter().map(|glyph| glyph.c).collect::(), - ), - Element::Group(group) => res.push_str(&group.frame.inner_text()), + Element::Text(content) => { + for glyph in &content.glyphs { + text.push(glyph.c); + } + } + Element::Group(group) => text.push_str(&group.frame.text()), _ => {} } } - res + text } } impl Debug for Frame { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.role.fmt(f)?; + if let Some(role) = self.role { + write!(f, "{role:?} ")?; + } + f.debug_list() .entries(self.elements.iter().map(|(_, element)| element)) .finish() @@ -422,7 +419,7 @@ pub enum Role { /// A generic inline subdivision. GenericInline, /// A list. The boolean indicates whether it is ordered. - List(bool), + List { ordered: bool }, /// A list item. Must have a list parent. ListItem, /// The label of a list item. @@ -445,6 +442,8 @@ pub enum Role { Footer, /// A page background. Background, + /// A page foreground. + Foreground, } impl Role { diff --git a/src/library/graphics/shape.rs b/src/library/graphics/shape.rs index e972a3d52..82eb2d9d9 100644 --- a/src/library/graphics/shape.rs +++ b/src/library/graphics/shape.rs @@ -94,9 +94,7 @@ impl Layout for ShapeNode { frames = child.layout(ctx, &pod, styles)?; for frame in frames.iter_mut() { - if frame.role().map_or(true, Role::is_weak) { - Arc::make_mut(frame).apply_role(Role::GenericBlock); - } + Arc::make_mut(frame).apply_role(Role::GenericBlock); } // Relayout with full expansion into square region to make sure diff --git a/src/library/layout/grid.rs b/src/library/layout/grid.rs index 687091fff..2d6eb2596 100644 --- a/src/library/layout/grid.rs +++ b/src/library/layout/grid.rs @@ -65,41 +65,6 @@ pub enum TrackSizing { Fractional(Fraction), } -/// Defines what kind of semantics a grid should represent. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -enum GridSemantics { - /// The grid is transparent to the semantic tree. - None, - /// The grid is a list, its rows are list items. The bool indicates whether - /// the list is ordered. - List, - /// The grid is a table. - Table, -} - -impl GridSemantics { - /// The role of a row in a grid with these semantics. - fn row(self) -> Option { - match self { - Self::None => None, - Self::List => Some(Role::ListItem), - Self::Table => Some(Role::TableRow), - } - } - - /// Returns the semantic role of a grid row given the previous semantics and - /// the cell's role. - fn determine(other: Option, role: Option) -> Self { - match (other, role) { - (None, Some(Role::ListItem | Role::ListLabel)) => Self::List, - (Some(Self::List), Some(Role::ListItem | Role::ListLabel)) => Self::List, - (None, Some(Role::TableCell)) => Self::Table, - (Some(Self::Table), Some(Role::TableCell)) => Self::Table, - _ => Self::None, - } - } -} - castable! { Vec, Expected: "integer, auto, relative length, fraction, or array of the latter three)", @@ -487,7 +452,6 @@ impl<'a> GridLayouter<'a> { let mut output = Frame::new(Size::new(self.used.x, height)); let mut pos = Point::zero(); - let mut semantic = None; for (x, &rcol) in self.rcols.iter().enumerate() { if let Some(node) = self.cell(x, y) { @@ -501,7 +465,13 @@ impl<'a> GridLayouter<'a> { let pod = Regions::one(size, base, Spec::splat(true)); let frame = node.layout(self.ctx, &pod, self.styles)?.remove(0); - semantic = Some(GridSemantics::determine(semantic, frame.role())); + match frame.role() { + Some(Role::ListLabel | Role::ListItemBody) => { + output.apply_role(Role::ListItem) + } + Some(Role::TableCell) => output.apply_role(Role::TableRow), + _ => {} + } output.push_frame(pos, frame); } @@ -509,10 +479,6 @@ impl<'a> GridLayouter<'a> { pos.x += rcol; } - if let Some(role) = semantic.and_then(GridSemantics::row) { - output.apply_role(role); - } - Ok(output) } @@ -535,7 +501,6 @@ impl<'a> GridLayouter<'a> { // Layout the row. let mut pos = Point::zero(); - let mut semantic = None; for (x, &rcol) in self.rcols.iter().enumerate() { if let Some(node) = self.cell(x, y) { pod.first.x = rcol; @@ -549,7 +514,13 @@ impl<'a> GridLayouter<'a> { // Push the layouted frames into the individual output frames. let frames = node.layout(self.ctx, &pod, self.styles)?; for (output, frame) in outputs.iter_mut().zip(frames) { - semantic = Some(GridSemantics::determine(semantic, frame.role())); + match frame.role() { + Some(Role::ListLabel | Role::ListItemBody) => { + output.apply_role(Role::ListItem) + } + Some(Role::TableCell) => output.apply_role(Role::TableRow), + _ => {} + } output.push_frame(pos, frame); } } @@ -557,12 +528,6 @@ impl<'a> GridLayouter<'a> { pos.x += rcol; } - for output in outputs.iter_mut() { - if let Some(role) = semantic.and_then(GridSemantics::row) { - output.apply_role(role); - } - } - Ok(outputs) } diff --git a/src/library/layout/page.rs b/src/library/layout/page.rs index 8bd507c43..115a1923b 100644 --- a/src/library/layout/page.rs +++ b/src/library/layout/page.rs @@ -110,30 +110,28 @@ impl PageNode { let pad = padding.resolve(styles).relative_to(size); let pw = size.x - pad.left - pad.right; let py = size.y - pad.bottom; - for (marginal, pos, area, role) in [ + for (role, marginal, pos, area) in [ ( + Role::Header, header, Point::with_x(pad.left), Size::new(pw, pad.top), - Role::Header, ), ( + Role::Footer, footer, Point::new(pad.left, py), Size::new(pw, pad.bottom), - Role::Footer, ), - (foreground, Point::zero(), size, Role::Background), - (background, Point::zero(), size, Role::Background), + (Role::Foreground, foreground, Point::zero(), size), + (Role::Background, background, Point::zero(), size), ] { if let Some(content) = marginal.resolve(ctx, page)? { let pod = Regions::one(area, area, Spec::splat(true)); - let role_map = StyleMap::with_role(role); - let styles = role_map.chain(&styles); let mut sub = content.layout(ctx, &pod, styles)?.remove(0); Arc::make_mut(&mut sub).apply_role(role); - if std::ptr::eq(marginal, background) { + if role == Role::Background { Arc::make_mut(frame).prepend_frame(pos, sub); } else { Arc::make_mut(frame).push_frame(pos, sub); diff --git a/src/library/structure/heading.rs b/src/library/structure/heading.rs index 242912a3d..af2b36268 100644 --- a/src/library/structure/heading.rs +++ b/src/library/structure/heading.rs @@ -92,8 +92,7 @@ impl Show for HeadingNode { }; } - let mut map = StyleMap::with_role(Role::Heading(self.level.get())); - + let mut map = StyleMap::new(); map.set(TextNode::SIZE, resolve!(Self::SIZE)); if let Smart::Custom(family) = resolve!(Self::FAMILY) { @@ -116,7 +115,7 @@ impl Show for HeadingNode { realized = realized.underlined(); } - realized = realized.styled_with_map(map); + realized = realized.styled_with_map(map).role(Role::Heading(self.level.get())); realized = realized.spaced( resolve!(Self::ABOVE).resolve(styles), resolve!(Self::BELOW).resolve(styles), diff --git a/src/library/structure/list.rs b/src/library/structure/list.rs index 077536d46..015ef5209 100644 --- a/src/library/structure/list.rs +++ b/src/library/structure/list.rs @@ -161,7 +161,9 @@ impl Show for ListNode { } } - Ok(realized.role(Role::List(L == ORDERED)).spaced(above, below)) + Ok(realized + .role(Role::List { ordered: L == ORDERED }) + .spaced(above, below)) } } diff --git a/src/library/text/par.rs b/src/library/text/par.rs index 0fec11ad8..41246b001 100644 --- a/src/library/text/par.rs +++ b/src/library/text/par.rs @@ -557,7 +557,6 @@ fn prepare<'a>( if !shift.is_zero() || frame.role().map_or(true, Role::is_weak) { let frame = Arc::make_mut(&mut frame); - frame.translate(Point::with_y(shift)); frame.apply_role(Role::GenericInline); } diff --git a/src/model/content.rs b/src/model/content.rs index 2b9eb182a..3e27c02fd 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -206,7 +206,7 @@ impl Content { /// Assign a role to this content by adding a style map. pub fn role(self, role: Role) -> Self { - self.styled_with_map(StyleMap::with_role(role)) + self.styled_with_entry(StyleEntry::Role(role)) } /// Reenable the show rule identified by the selector. diff --git a/src/model/styles.rs b/src/model/styles.rs index 7db2df42f..7d16f4ba6 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -37,13 +37,6 @@ impl StyleMap { styles } - /// Create a style map from a single role. - pub fn with_role(role: Role) -> Self { - let mut styles = Self::new(); - styles.push(StyleEntry::Role(role)); - styles - } - /// Set an inner value for a style property. /// /// If the property needs folding and the value is already contained in the