summaryrefslogtreecommitdiff
path: root/crates/typst-pdf/src/pattern.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-pdf/src/pattern.rs')
-rw-r--r--crates/typst-pdf/src/pattern.rs192
1 files changed, 101 insertions, 91 deletions
diff --git a/crates/typst-pdf/src/pattern.rs b/crates/typst-pdf/src/pattern.rs
index 7fb3d6e8..e06c04f8 100644
--- a/crates/typst-pdf/src/pattern.rs
+++ b/crates/typst-pdf/src/pattern.rs
@@ -1,88 +1,72 @@
+use std::collections::HashMap;
+
use ecow::eco_format;
-use pdf_writer::types::{ColorSpaceOperand, PaintType, TilingType};
-use pdf_writer::{Filter, Finish, Name, Rect};
+use pdf_writer::{
+ types::{ColorSpaceOperand, PaintType, TilingType},
+ Filter, Name, Rect, Ref,
+};
+
use typst::layout::{Abs, Ratio, Transform};
use typst::utils::Numeric;
use typst::visualize::{Pattern, RelativeTo};
-use crate::color::PaintEncode;
-use crate::page::{construct_page, PageContext, PageResource, ResourceKind, Transforms};
-use crate::{transform_to_array, PdfContext};
+use crate::{color::PaintEncode, resources::Remapper, Resources, WithGlobalRefs};
+use crate::{content, resources::ResourcesRefs};
+use crate::{transform_to_array, PdfChunk};
/// Writes the actual patterns (tiling patterns) to the PDF.
/// This is performed once after writing all pages.
-pub(crate) fn write_patterns(ctx: &mut PdfContext) {
- for PdfPattern { transform, pattern, content, resources } in ctx.pattern_map.items() {
- let tiling = ctx.alloc.bump();
- ctx.pattern_refs.push(tiling);
-
- let mut tiling_pattern = ctx.pdf.tiling_pattern(tiling, content);
- tiling_pattern
- .tiling_type(TilingType::ConstantSpacing)
- .paint_type(PaintType::Colored)
- .bbox(Rect::new(
- 0.0,
- 0.0,
- pattern.size().x.to_pt() as _,
- pattern.size().y.to_pt() as _,
- ))
- .x_step((pattern.size().x + pattern.spacing().x).to_pt() as _)
- .y_step((pattern.size().y + pattern.spacing().y).to_pt() as _);
-
- let mut resources_map = tiling_pattern.resources();
-
- resources_map.x_objects().pairs(
- resources
- .iter()
- .filter(|(res, _)| res.is_x_object())
- .map(|(res, ref_)| (res.name(), ctx.image_refs[*ref_])),
- );
-
- resources_map.fonts().pairs(
- resources
- .iter()
- .filter(|(res, _)| res.is_font())
- .map(|(res, ref_)| (res.name(), ctx.font_refs[*ref_])),
- );
-
- ctx.colors
- .write_color_spaces(resources_map.color_spaces(), &mut ctx.alloc);
-
- resources_map
- .patterns()
- .pairs(
- resources
- .iter()
- .filter(|(res, _)| res.is_pattern())
- .map(|(res, ref_)| (res.name(), ctx.pattern_refs[*ref_])),
- )
- .pairs(
- resources
- .iter()
- .filter(|(res, _)| res.is_gradient())
- .map(|(res, ref_)| (res.name(), ctx.gradient_refs[*ref_])),
- );
-
- resources_map.ext_g_states().pairs(
- resources
- .iter()
- .filter(|(res, _)| res.is_ext_g_state())
- .map(|(res, ref_)| (res.name(), ctx.ext_gs_refs[*ref_])),
- );
-
- resources_map.finish();
- tiling_pattern
- .matrix(transform_to_array(
- transform
- .pre_concat(Transform::scale(Ratio::one(), -Ratio::one()))
- .post_concat(Transform::translate(Abs::zero(), pattern.spacing().y)),
- ))
- .filter(Filter::FlateDecode);
- }
+pub fn write_patterns(context: &WithGlobalRefs) -> (PdfChunk, HashMap<PdfPattern, Ref>) {
+ let mut chunk = PdfChunk::new();
+ let mut out = HashMap::new();
+ context.resources.traverse(&mut |resources| {
+ let Some(patterns) = &resources.patterns else {
+ return;
+ };
+
+ for pdf_pattern in patterns.remapper.items() {
+ let PdfPattern { transform, pattern, content, .. } = pdf_pattern;
+ if out.contains_key(pdf_pattern) {
+ continue;
+ }
+
+ let tiling = chunk.alloc();
+ out.insert(pdf_pattern.clone(), tiling);
+
+ let mut tiling_pattern = chunk.tiling_pattern(tiling, content);
+ tiling_pattern
+ .tiling_type(TilingType::ConstantSpacing)
+ .paint_type(PaintType::Colored)
+ .bbox(Rect::new(
+ 0.0,
+ 0.0,
+ pattern.size().x.to_pt() as _,
+ pattern.size().y.to_pt() as _,
+ ))
+ .x_step((pattern.size().x + pattern.spacing().x).to_pt() as _)
+ .y_step((pattern.size().y + pattern.spacing().y).to_pt() as _);
+
+ // The actual resource dict will be written in a later step
+ tiling_pattern.pair(Name(b"Resources"), patterns.resources.reference);
+
+ tiling_pattern
+ .matrix(transform_to_array(
+ transform
+ .pre_concat(Transform::scale(Ratio::one(), -Ratio::one()))
+ .post_concat(Transform::translate(
+ Abs::zero(),
+ pattern.spacing().y,
+ )),
+ ))
+ .filter(Filter::FlateDecode);
+ }
+ });
+
+ (chunk, out)
}
/// A pattern and its transform.
-#[derive(Clone, PartialEq, Eq, Hash)]
+#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct PdfPattern {
/// The transform to apply to the pattern.
pub transform: Transform,
@@ -90,17 +74,20 @@ pub struct PdfPattern {
pub pattern: Pattern,
/// The rendered pattern.
pub content: Vec<u8>,
- /// The resources used by the pattern.
- pub resources: Vec<(PageResource, usize)>,
}
/// Registers a pattern with the PDF.
fn register_pattern(
- ctx: &mut PageContext,
+ ctx: &mut content::Builder,
pattern: &Pattern,
on_text: bool,
- mut transforms: Transforms,
+ mut transforms: content::Transforms,
) -> usize {
+ let patterns = ctx
+ .resources
+ .patterns
+ .get_or_insert_with(|| Box::new(PatternRemapper::new()));
+
// Edge cases for strokes.
if transforms.size.x.is_zero() {
transforms.size.x = Abs::pt(1.0);
@@ -116,22 +103,24 @@ fn register_pattern(
};
// Render the body.
- let content = construct_page(ctx.parent, pattern.frame());
+ let content = content::build(&mut patterns.resources, pattern.frame(), None);
- let mut pdf_pattern = PdfPattern {
+ let pdf_pattern = PdfPattern {
transform,
pattern: pattern.clone(),
content: content.content.wait().clone(),
- resources: content.resources.into_iter().collect(),
};
- pdf_pattern.resources.sort();
-
- ctx.parent.pattern_map.insert(pdf_pattern)
+ patterns.remapper.insert(pdf_pattern)
}
impl PaintEncode for Pattern {
- fn set_as_fill(&self, ctx: &mut PageContext, on_text: bool, transforms: Transforms) {
+ fn set_as_fill(
+ &self,
+ ctx: &mut content::Builder,
+ on_text: bool,
+ transforms: content::Transforms,
+ ) {
ctx.reset_fill_color_space();
let index = register_pattern(ctx, self, on_text, transforms);
@@ -140,15 +129,13 @@ impl PaintEncode for Pattern {
ctx.content.set_fill_color_space(ColorSpaceOperand::Pattern);
ctx.content.set_fill_pattern(None, name);
- ctx.resources
- .insert(PageResource::new(ResourceKind::Pattern, id), index);
}
fn set_as_stroke(
&self,
- ctx: &mut PageContext,
+ ctx: &mut content::Builder,
on_text: bool,
- transforms: Transforms,
+ transforms: content::Transforms,
) {
ctx.reset_stroke_color_space();
@@ -158,7 +145,30 @@ impl PaintEncode for Pattern {
ctx.content.set_stroke_color_space(ColorSpaceOperand::Pattern);
ctx.content.set_stroke_pattern(None, name);
- ctx.resources
- .insert(PageResource::new(ResourceKind::Pattern, id), index);
+ }
+}
+
+/// De-duplicate patterns and the resources they require to be drawn.
+pub struct PatternRemapper<R> {
+ /// Pattern de-duplicator.
+ pub remapper: Remapper<PdfPattern>,
+ /// PDF resources that are used by these patterns.
+ pub resources: Resources<R>,
+}
+
+impl PatternRemapper<()> {
+ pub fn new() -> Self {
+ Self {
+ remapper: Remapper::new("P"),
+ resources: Resources::default(),
+ }
+ }
+
+ /// Allocate a reference to the resource dictionary of these patterns.
+ pub fn with_refs(self, refs: &ResourcesRefs) -> PatternRemapper<Ref> {
+ PatternRemapper {
+ remapper: self.remapper,
+ resources: self.resources.with_refs(refs),
+ }
}
}