use pdf_writer::types::ColorSpaceOperand;
use pdf_writer::types::ColorSpaceOperand::Pattern;
use pdf_writer::{Chunk, Content, Finish};
use usvg::tiny_skia_path::PathSegment;
use usvg::Path;
use usvg::{Fill, FillRule, Opacity, Paint, PaintOrder, Rect};
use usvg::{Stroke, Transform};
use super::{gradient, pattern};
use crate::util::context::Context;
use crate::util::helper::{ColorExt, ContentExt, LineCapExt, LineJoinExt, NameExt};
use crate::util::resources::ResourceContainer;
use crate::Result;
pub fn render(
path: &Path,
chunk: &mut Chunk,
content: &mut Content,
ctx: &mut Context,
rc: &mut ResourceContainer,
accumulated_transform: Transform,
) -> Result<()> {
if !path.is_visible() {
return Ok(());
}
match path.paint_order() {
PaintOrder::FillAndStroke => {
fill_path(path, chunk, content, ctx, rc, accumulated_transform)?;
stroke_path(path, chunk, content, ctx, rc, accumulated_transform)?;
}
PaintOrder::StrokeAndFill => {
stroke_path(path, chunk, content, ctx, rc, accumulated_transform)?;
fill_path(path, chunk, content, ctx, rc, accumulated_transform)?;
}
}
Ok(())
}
pub fn draw_path(path_data: impl Iterator<Item = PathSegment>, content: &mut Content) {
fn calc(n1: f32, n2: f32) -> f32 {
(n1 + n2 * 2.0) / 3.0
}
let mut p_prev = None;
for operation in path_data {
match operation {
PathSegment::MoveTo(p) => {
content.move_to(p.x, p.y);
p_prev = Some(p);
}
PathSegment::LineTo(p) => {
content.line_to(p.x, p.y);
p_prev = Some(p);
}
PathSegment::QuadTo(p1, p2) => {
let prev = p_prev.unwrap();
content.cubic_to(
calc(prev.x, p1.x),
calc(prev.y, p1.y),
calc(p2.x, p1.x),
calc(p2.y, p1.y),
p2.x,
p2.y,
);
p_prev = Some(p2);
}
PathSegment::CubicTo(p1, p2, p3) => {
content.cubic_to(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
p_prev = Some(p3);
}
PathSegment::Close => {
content.close_path();
}
};
}
}
pub(crate) fn stroke_path(
path: &Path,
chunk: &mut Chunk,
content: &mut Content,
ctx: &mut Context,
rc: &mut ResourceContainer,
accumulated_transform: Transform,
) -> Result<()> {
if path.data().bounds().width() == 0.0 && path.data().bounds().height() == 0.0 {
return Ok(());
}
let operation = |content: &mut Content, stroke: &Stroke| {
draw_path(path.data().segments(), content);
finish_path(Some(stroke), None, content);
Ok(())
};
if let Some(path_stroke) = path.stroke() {
stroke(
path_stroke,
chunk,
content,
ctx,
rc,
operation,
accumulated_transform,
path.stroke_bounding_box(),
)?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn stroke(
stroke: &Stroke,
chunk: &mut Chunk,
content: &mut Content,
ctx: &mut Context,
rc: &mut ResourceContainer,
operation: impl Fn(&mut Content, &Stroke) -> Result<()>,
accumulated_transform: Transform,
bbox: Rect,
) -> Result<()> {
let paint = &stroke.paint();
content.save_state_checked()?;
match paint {
Paint::Color(c) => {
set_opacity_gs(chunk, content, ctx, Some(stroke.opacity()), None, rc);
let srgb_name = rc.add_color_space(ctx.srgb_ref());
let srgb_name = ColorSpaceOperand::Named(srgb_name.to_pdf_name());
content.set_stroke_color_space(srgb_name);
content.set_stroke_color(c.to_pdf_color());
}
Paint::Pattern(p) => {
let pattern_ref = pattern::create(
p.clone(),
chunk,
ctx,
accumulated_transform,
Some(stroke.opacity()),
)?;
let pattern_name = rc.add_pattern(pattern_ref);
content.set_stroke_color_space(Pattern);
content.set_stroke_pattern(None, pattern_name.to_pdf_name());
}
Paint::LinearGradient(_) | Paint::RadialGradient(_) => {
set_opacity_gs(
chunk,
content,
ctx,
Some(stroke.opacity()),
Some(stroke.opacity()),
rc,
);
if let Some(soft_mask) =
gradient::create_shading_soft_mask(paint, chunk, ctx, bbox)
{
let soft_mask_name = rc.add_graphics_state(soft_mask);
content.set_parameters(soft_mask_name.to_pdf_name());
}
let pattern_ref = gradient::create_shading_pattern(
paint,
chunk,
ctx,
&accumulated_transform,
);
let pattern_name = rc.add_pattern(pattern_ref);
content.set_stroke_color_space(Pattern);
content.set_stroke_pattern(None, pattern_name.to_pdf_name());
}
}
content.set_line_width(stroke.width().get());
content.set_miter_limit(stroke.miterlimit().get());
content.set_line_cap(stroke.linecap().to_pdf_line_cap());
content.set_line_join(stroke.linejoin().to_pdf_line_join());
if let Some(dasharray) = &stroke.dasharray() {
content.set_dash_pattern(dasharray.iter().cloned(), stroke.dashoffset());
} else {
content.set_dash_pattern(vec![], 0.0);
}
operation(content, stroke)?;
content.restore_state();
Ok(())
}
pub(crate) fn fill_path(
path: &Path,
chunk: &mut Chunk,
content: &mut Content,
ctx: &mut Context,
rc: &mut ResourceContainer,
accumulated_transform: Transform,
) -> Result<()> {
if path.data().bounds().width() == 0.0 || path.data().bounds().height() == 0.0 {
return Ok(());
}
let operation = |content: &mut Content, fill: &Fill| {
draw_path(path.data().segments(), content);
finish_path(None, Some(fill), content);
Ok(())
};
if let Some(path_fill) = path.fill() {
fill(
path_fill,
chunk,
content,
ctx,
rc,
operation,
accumulated_transform,
path.bounding_box(),
)?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn fill(
fill: &Fill,
chunk: &mut Chunk,
content: &mut Content,
ctx: &mut Context,
rc: &mut ResourceContainer,
operation: impl Fn(&mut Content, &Fill) -> Result<()>,
accumulated_transform: Transform,
bbox: Rect,
) -> Result<()> {
let paint = &fill.paint();
content.save_state_checked()?;
match paint {
Paint::Color(c) => {
set_opacity_gs(chunk, content, ctx, None, Some(fill.opacity()), rc);
let srgb_name = rc.add_color_space(ctx.srgb_ref());
let srgb_name = ColorSpaceOperand::Named(srgb_name.to_pdf_name());
content.set_fill_color_space(srgb_name);
content.set_fill_color(c.to_pdf_color());
}
Paint::Pattern(p) => {
let pattern_ref = pattern::create(
p.clone(),
chunk,
ctx,
accumulated_transform,
Some(fill.opacity()),
)?;
let pattern_name = rc.add_pattern(pattern_ref);
content.set_fill_color_space(Pattern);
content.set_fill_pattern(None, pattern_name.to_pdf_name());
}
Paint::LinearGradient(_) | Paint::RadialGradient(_) => {
set_opacity_gs(chunk, content, ctx, None, Some(fill.opacity()), rc);
if let Some(soft_mask) =
gradient::create_shading_soft_mask(paint, chunk, ctx, bbox)
{
let soft_mask_name = rc.add_graphics_state(soft_mask);
content.set_parameters(soft_mask_name.to_pdf_name());
}
let pattern_ref = gradient::create_shading_pattern(
paint,
chunk,
ctx,
&accumulated_transform,
);
let pattern_name = rc.add_pattern(pattern_ref);
content.set_fill_color_space(Pattern);
content.set_fill_pattern(None, pattern_name.to_pdf_name());
}
}
operation(content, fill)?;
content.restore_state();
Ok(())
}
fn finish_path(stroke: Option<&Stroke>, fill: Option<&Fill>, content: &mut Content) {
match (stroke, fill.map(|f| f.rule())) {
(Some(_), Some(FillRule::NonZero)) => content.fill_nonzero_and_stroke(),
(Some(_), Some(FillRule::EvenOdd)) => content.fill_even_odd_and_stroke(),
(None, Some(FillRule::NonZero)) => content.fill_nonzero(),
(None, Some(FillRule::EvenOdd)) => content.fill_even_odd(),
(Some(_), None) => content.stroke(),
(None, None) => content.end_path(),
};
}
fn set_opacity_gs(
chunk: &mut Chunk,
content: &mut Content,
ctx: &mut Context,
stroke_opacity: Option<Opacity>,
fill_opacity: Option<Opacity>,
rc: &mut ResourceContainer,
) {
let fill_opacity = fill_opacity.unwrap_or(Opacity::ONE).get();
let stroke_opacity = stroke_opacity.unwrap_or(Opacity::ONE).get();
if fill_opacity == 1.0 && stroke_opacity == 1.0 {
return;
}
let gs_ref = ctx.alloc_ref();
let mut gs = chunk.ext_graphics(gs_ref);
gs.non_stroking_alpha(fill_opacity)
.stroking_alpha(stroke_opacity)
.finish();
content.set_parameters(rc.add_graphics_state(gs_ref).to_pdf_name());
}