diff options
| author | Ana Gelez <ana@gelez.xyz> | 2024-05-29 15:01:11 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-05-29 13:01:11 +0000 |
| commit | 2946cde6fa1b9039fe5de1cded2e0c697575b81d (patch) | |
| tree | 52338c72388a4bb3371edd18dc16ad6d9a5af12e /crates/typst-pdf/src/gradient.rs | |
| parent | 6d07f702e1d662f28463f4c9e4346b197da4cb63 (diff) | |
Refactor PDF export (#4154)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
Diffstat (limited to 'crates/typst-pdf/src/gradient.rs')
| -rw-r--r-- | crates/typst-pdf/src/gradient.rs | 296 |
1 files changed, 166 insertions, 130 deletions
diff --git a/crates/typst-pdf/src/gradient.rs b/crates/typst-pdf/src/gradient.rs index 576c254e..de77df33 100644 --- a/crates/typst-pdf/src/gradient.rs +++ b/crates/typst-pdf/src/gradient.rs @@ -1,19 +1,23 @@ +use std::collections::HashMap; use std::f32::consts::{PI, TAU}; use std::sync::Arc; use ecow::eco_format; -use pdf_writer::types::{ColorSpaceOperand, FunctionShadingType}; -use pdf_writer::writers::StreamShadingType; -use pdf_writer::{Filter, Finish, Name, Ref}; +use pdf_writer::{ + types::{ColorSpaceOperand, FunctionShadingType}, + writers::StreamShadingType, + Filter, Finish, Name, Ref, +}; + use typst::layout::{Abs, Angle, Point, Quadrant, Ratio, Transform}; use typst::utils::Numeric; use typst::visualize::{ Color, ColorSpace, Gradient, RatioOrAngle, RelativeTo, WeightedColor, }; -use crate::color::{ColorSpaceExt, PaintEncode, QuantizedColor}; -use crate::page::{PageContext, PageResource, ResourceKind, Transforms}; -use crate::{deflate, transform_to_array, AbsExt, PdfContext}; +use crate::color::{self, ColorSpaceExt, PaintEncode, QuantizedColor}; +use crate::{content, WithGlobalRefs}; +use crate::{deflate, transform_to_array, AbsExt, PdfChunk}; /// A unique-transform-aspect-ratio combination that will be encoded into the /// PDF. @@ -32,122 +36,144 @@ pub struct PdfGradient { /// Writes the actual gradients (shading patterns) to the PDF. /// This is performed once after writing all pages. -pub(crate) fn write_gradients(ctx: &mut PdfContext) { - for PdfGradient { transform, aspect_ratio, gradient, angle } in - ctx.gradient_map.items().cloned().collect::<Vec<_>>() - { - let shading = ctx.alloc.bump(); - ctx.gradient_refs.push(shading); - - let color_space = if gradient.space().hue_index().is_some() { - ColorSpace::Oklab - } else { - gradient.space() - }; - - let mut shading_pattern = match &gradient { - Gradient::Linear(_) => { - let shading_function = shading_function(ctx, &gradient, color_space); - let mut shading_pattern = ctx.pdf.shading_pattern(shading); - let mut shading = shading_pattern.function_shading(); - shading.shading_type(FunctionShadingType::Axial); - - ctx.colors.write(color_space, shading.color_space(), &mut ctx.alloc); - - let (mut sin, mut cos) = (angle.sin(), angle.cos()); - - // Scale to edges of unit square. - let factor = cos.abs() + sin.abs(); - sin *= factor; - cos *= factor; - - let (x1, y1, x2, y2): (f64, f64, f64, f64) = match angle.quadrant() { - Quadrant::First => (0.0, 0.0, cos, sin), - Quadrant::Second => (1.0, 0.0, cos + 1.0, sin), - Quadrant::Third => (1.0, 1.0, cos + 1.0, sin + 1.0), - Quadrant::Fourth => (0.0, 1.0, cos, sin + 1.0), - }; - - shading - .anti_alias(gradient.anti_alias()) - .function(shading_function) - .coords([x1 as f32, y1 as f32, x2 as f32, y2 as f32]) - .extend([true; 2]); - - shading.finish(); - - shading_pattern - } - Gradient::Radial(radial) => { - let shading_function = shading_function(ctx, &gradient, color_space); - let mut shading_pattern = ctx.pdf.shading_pattern(shading); - let mut shading = shading_pattern.function_shading(); - shading.shading_type(FunctionShadingType::Radial); - - ctx.colors.write(color_space, shading.color_space(), &mut ctx.alloc); - - shading - .anti_alias(gradient.anti_alias()) - .function(shading_function) - .coords([ - radial.focal_center.x.get() as f32, - radial.focal_center.y.get() as f32, - radial.focal_radius.get() as f32, - radial.center.x.get() as f32, - radial.center.y.get() as f32, - radial.radius.get() as f32, - ]) - .extend([true; 2]); - - shading.finish(); - - shading_pattern +pub fn write_gradients( + context: &WithGlobalRefs, +) -> (PdfChunk, HashMap<PdfGradient, Ref>) { + let mut chunk = PdfChunk::new(); + let mut out = HashMap::new(); + context.resources.traverse(&mut |resources| { + for pdf_gradient in resources.gradients.items() { + if out.contains_key(pdf_gradient) { + continue; } - Gradient::Conic(_) => { - let vertices = compute_vertex_stream(&gradient, aspect_ratio); - - let stream_shading_id = ctx.alloc.bump(); - let mut stream_shading = - ctx.pdf.stream_shading(stream_shading_id, &vertices); - - ctx.colors.write( - color_space, - stream_shading.color_space(), - &mut ctx.alloc, - ); - - let range = color_space.range(); - stream_shading - .bits_per_coordinate(16) - .bits_per_component(16) - .bits_per_flag(8) - .shading_type(StreamShadingType::CoonsPatch) - .decode([ - 0.0, 1.0, 0.0, 1.0, range[0], range[1], range[2], range[3], - range[4], range[5], - ]) - .anti_alias(gradient.anti_alias()) - .filter(Filter::FlateDecode); - - stream_shading.finish(); - - let mut shading_pattern = ctx.pdf.shading_pattern(shading); - shading_pattern.shading_ref(stream_shading_id); - shading_pattern - } - }; - shading_pattern.matrix(transform_to_array(transform)); - } + let shading = chunk.alloc(); + out.insert(pdf_gradient.clone(), shading); + + let PdfGradient { transform, aspect_ratio, gradient, angle } = pdf_gradient; + + let color_space = if gradient.space().hue_index().is_some() { + ColorSpace::Oklab + } else { + gradient.space() + }; + + let mut shading_pattern = match &gradient { + Gradient::Linear(_) => { + let shading_function = + shading_function(gradient, &mut chunk, color_space); + let mut shading_pattern = chunk.chunk.shading_pattern(shading); + let mut shading = shading_pattern.function_shading(); + shading.shading_type(FunctionShadingType::Axial); + + color::write( + color_space, + shading.color_space(), + &context.globals.color_functions, + ); + + let (mut sin, mut cos) = (angle.sin(), angle.cos()); + + // Scale to edges of unit square. + let factor = cos.abs() + sin.abs(); + sin *= factor; + cos *= factor; + + let (x1, y1, x2, y2): (f64, f64, f64, f64) = match angle.quadrant() { + Quadrant::First => (0.0, 0.0, cos, sin), + Quadrant::Second => (1.0, 0.0, cos + 1.0, sin), + Quadrant::Third => (1.0, 1.0, cos + 1.0, sin + 1.0), + Quadrant::Fourth => (0.0, 1.0, cos, sin + 1.0), + }; + + shading + .anti_alias(gradient.anti_alias()) + .function(shading_function) + .coords([x1 as f32, y1 as f32, x2 as f32, y2 as f32]) + .extend([true; 2]); + + shading.finish(); + + shading_pattern + } + Gradient::Radial(radial) => { + let shading_function = + shading_function(gradient, &mut chunk, color_space_of(gradient)); + let mut shading_pattern = chunk.chunk.shading_pattern(shading); + let mut shading = shading_pattern.function_shading(); + shading.shading_type(FunctionShadingType::Radial); + + color::write( + color_space, + shading.color_space(), + &context.globals.color_functions, + ); + + shading + .anti_alias(gradient.anti_alias()) + .function(shading_function) + .coords([ + radial.focal_center.x.get() as f32, + radial.focal_center.y.get() as f32, + radial.focal_radius.get() as f32, + radial.center.x.get() as f32, + radial.center.y.get() as f32, + radial.radius.get() as f32, + ]) + .extend([true; 2]); + + shading.finish(); + + shading_pattern + } + Gradient::Conic(_) => { + let vertices = compute_vertex_stream(gradient, *aspect_ratio); + + let stream_shading_id = chunk.alloc(); + let mut stream_shading = + chunk.chunk.stream_shading(stream_shading_id, &vertices); + + color::write( + color_space, + stream_shading.color_space(), + &context.globals.color_functions, + ); + + let range = color_space.range(); + stream_shading + .bits_per_coordinate(16) + .bits_per_component(16) + .bits_per_flag(8) + .shading_type(StreamShadingType::CoonsPatch) + .decode([ + 0.0, 1.0, 0.0, 1.0, range[0], range[1], range[2], range[3], + range[4], range[5], + ]) + .anti_alias(gradient.anti_alias()) + .filter(Filter::FlateDecode); + + stream_shading.finish(); + + let mut shading_pattern = chunk.shading_pattern(shading); + shading_pattern.shading_ref(stream_shading_id); + shading_pattern + } + }; + + shading_pattern.matrix(transform_to_array(*transform)); + } + }); + + (chunk, out) } /// Writes an expotential or stitched function that expresses the gradient. fn shading_function( - ctx: &mut PdfContext, gradient: &Gradient, + chunk: &mut PdfChunk, color_space: ColorSpace, ) -> Ref { - let function = ctx.alloc.bump(); + let function = chunk.alloc(); let mut functions = vec![]; let mut bounds = vec![]; let mut encode = vec![]; @@ -166,7 +192,7 @@ fn shading_function( let real_t = first.1.get() * (1.0 - t) + second.1.get() * t; let c = gradient.sample(RatioOrAngle::Ratio(Ratio::new(real_t))); - functions.push(single_gradient(ctx, last_c, c, color_space)); + functions.push(single_gradient(chunk, last_c, c, color_space)); bounds.push(real_t as f32); encode.extend([0.0, 1.0]); last_c = c; @@ -174,7 +200,7 @@ fn shading_function( } bounds.push(second.1.get() as f32); - functions.push(single_gradient(ctx, first.0, second.0, color_space)); + functions.push(single_gradient(chunk, first.0, second.0, color_space)); encode.extend([0.0, 1.0]); } @@ -187,7 +213,7 @@ fn shading_function( bounds.pop(); // Create the stitching function. - ctx.pdf + chunk .stitching_function(function) .domain([0.0, 1.0]) .range(color_space.range()) @@ -201,14 +227,13 @@ fn shading_function( /// Writes an expontential function that expresses a single segment (between two /// stops) of a gradient. fn single_gradient( - ctx: &mut PdfContext, + chunk: &mut PdfChunk, first_color: Color, second_color: Color, color_space: ColorSpace, ) -> Ref { - let reference = ctx.alloc.bump(); - - ctx.pdf + let reference = chunk.alloc(); + chunk .exponential_function(reference) .range(color_space.range()) .c0(color_space.convert(first_color)) @@ -220,7 +245,12 @@ fn single_gradient( } impl PaintEncode for Gradient { - 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_gradient(ctx, self, on_text, transforms); @@ -229,15 +259,13 @@ impl PaintEncode for Gradient { ctx.content.set_fill_color_space(ColorSpaceOperand::Pattern); ctx.content.set_fill_pattern(None, name); - ctx.resources - .insert(PageResource::new(ResourceKind::Gradient, 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(); @@ -247,17 +275,15 @@ impl PaintEncode for Gradient { ctx.content.set_stroke_color_space(ColorSpaceOperand::Pattern); ctx.content.set_stroke_pattern(None, name); - ctx.resources - .insert(PageResource::new(ResourceKind::Gradient, id), index); } } /// Deduplicates a gradient to a named PDF resource. fn register_gradient( - ctx: &mut PageContext, + ctx: &mut content::Builder, gradient: &Gradient, on_text: bool, - mut transforms: Transforms, + mut transforms: content::Transforms, ) -> usize { // Edge cases for strokes. if transforms.size.x.is_zero() { @@ -307,7 +333,9 @@ fn register_gradient( angle: Gradient::correct_aspect_ratio(rotation, size.aspect_ratio()), }; - ctx.parent.gradient_map.insert(pdf_gradient) + ctx.resources.colors.mark_as_used(color_space_of(gradient)); + + ctx.resources.gradients.insert(pdf_gradient) } /// Writes a single Coons Patch as defined in the PDF specification @@ -466,3 +494,11 @@ fn compute_vertex_stream(gradient: &Gradient, aspect_ratio: Ratio) -> Arc<Vec<u8 Arc::new(deflate(&vertices)) } + +fn color_space_of(gradient: &Gradient) -> ColorSpace { + if gradient.space().hue_index().is_some() { + ColorSpace::Oklab + } else { + gradient.space() + } +} |
