mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
161 lines
5.1 KiB
Rust
161 lines
5.1 KiB
Rust
use ecow::eco_format;
|
|
use pdf_writer::types::{ColorSpaceOperand, PaintType, TilingType};
|
|
use pdf_writer::{Filter, Finish, Name, Rect};
|
|
use typst::layout::{Abs, Ratio, Transform};
|
|
use typst::util::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};
|
|
|
|
/// 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())),
|
|
))
|
|
.filter(Filter::FlateDecode);
|
|
}
|
|
}
|
|
|
|
/// A pattern and its transform.
|
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
|
pub struct PdfPattern {
|
|
/// The transform to apply to the gradient.
|
|
pub transform: Transform,
|
|
/// The pattern to paint.
|
|
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,
|
|
pattern: &Pattern,
|
|
on_text: bool,
|
|
mut transforms: Transforms,
|
|
) -> usize {
|
|
// Edge cases for strokes.
|
|
if transforms.size.x.is_zero() {
|
|
transforms.size.x = Abs::pt(1.0);
|
|
}
|
|
|
|
if transforms.size.y.is_zero() {
|
|
transforms.size.y = Abs::pt(1.0);
|
|
}
|
|
|
|
let transform = match pattern.unwrap_relative(on_text) {
|
|
RelativeTo::Self_ => transforms.transform,
|
|
RelativeTo::Parent => transforms.container_transform,
|
|
};
|
|
|
|
// Render the body.
|
|
let (_, content) = construct_page(ctx.parent, pattern.frame());
|
|
|
|
let pdf_pattern = PdfPattern {
|
|
transform,
|
|
pattern: pattern.clone(),
|
|
content: content.content.wait().clone(),
|
|
resources: content.resources.into_iter().collect(),
|
|
};
|
|
|
|
ctx.parent.pattern_map.insert(pdf_pattern)
|
|
}
|
|
|
|
impl PaintEncode for Pattern {
|
|
fn set_as_fill(&self, ctx: &mut PageContext, on_text: bool, transforms: Transforms) {
|
|
ctx.reset_fill_color_space();
|
|
|
|
let index = register_pattern(ctx, self, on_text, transforms);
|
|
let id = eco_format!("P{index}");
|
|
let name = Name(id.as_bytes());
|
|
|
|
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,
|
|
on_text: bool,
|
|
transforms: Transforms,
|
|
) {
|
|
ctx.reset_stroke_color_space();
|
|
|
|
let index = register_pattern(ctx, self, on_text, transforms);
|
|
let id = eco_format!("P{index}");
|
|
let name = Name(id.as_bytes());
|
|
|
|
ctx.content.set_stroke_color_space(ColorSpaceOperand::Pattern);
|
|
ctx.content.set_stroke_pattern(None, name);
|
|
ctx.resources
|
|
.insert(PageResource::new(ResourceKind::Pattern, id), index);
|
|
}
|
|
}
|