use super::geometry::BoxPaintGeometry;
use super::*;
pub(super) fn paint_box_gradient_backgrounds(
content: &mut String,
paint: &crate::layout::elements::BoxPaint,
geometry: BoxPaintGeometry,
ctx: &mut PageRenderContext<'_>,
) {
let layers = &paint.background.layers;
let geometry = geometry.background(layers.origin, layers.clip, paint.border_radii);
let positioning_box = geometry.positioning_area.generated_image_box();
let painted_box = geometry.painting_box;
if geometry.image_destination_box.is_empty() {
return;
}
let paint_area = LayerPaintArea::new(positioning_box, geometry.image_destination_box);
let layer_box = background_layer_box(layers.size, layers.position, layers.repeat);
if let Some(gradient) = &layers.gradient {
let gradient = linear_with_background_layer(gradient, layer_box);
let clipped = painted_box.push_rounded_clip(content);
render_linear_gradient(
content,
&gradient,
GradientBackdrop::isolated_linear_layer(
paint.background.color,
layers.radial_gradient.is_some()
|| layers.conic_gradient.is_some()
|| layers.svg.is_some(),
crate::style::computed::BlendMode::Normal,
),
paint_area,
ctx.shadings,
ctx.shading_counter,
ctx.text.pdf_writer,
ctx.text.page_images,
);
if clipped {
content.push_str("Q\n");
}
}
if let Some(gradient) = &layers.radial_gradient {
let gradient = radial_with_background_layer(gradient, layer_box);
let clipped = painted_box.push_rounded_clip(content);
render_radial_gradient(
content,
&gradient,
paint_area,
ctx.shadings,
ctx.shading_counter,
ctx.text.pdf_writer,
ctx.text.page_images,
);
if clipped {
content.push_str("Q\n");
}
}
if let Some(gradient) = &layers.conic_gradient {
let gradient = conic_with_background_layer(gradient, layer_box);
let clipped = painted_box.push_rounded_clip(content);
render_conic_gradient(
content,
&gradient,
paint_area,
ctx.text.pdf_writer,
ctx.text.page_images,
);
if clipped {
content.push_str("Q\n");
}
}
}
#[allow(clippy::too_many_arguments)]
pub(super) fn render_linear_gradient(
content: &mut String,
gradient: &impl GradientView<LinearGradient>,
backdrop: GradientBackdrop,
area: LayerPaintArea,
shadings: &mut Vec<ShadingEntry>,
shading_counter: &mut usize,
pdf_writer: &mut PdfWriter,
page_images: &mut Vec<ImageRef>,
) {
let source = gradient.source();
let Some(pattern) = gradient_layer_pattern(&gradient.layer_box(), area) else {
return;
};
let Some(first_tile) = pattern.first_tile() else {
return;
};
if pattern.is_single() {
let content_transform = pdf_writer.page_content_transform;
render_linear_gradient_layer_tile(
content,
source,
backdrop,
first_tile,
content_transform,
shadings,
shading_counter,
pdf_writer,
page_images,
);
return;
}
let content_transform = pdf_writer.page_content_transform;
if pattern.has_distributed_repeat()
&& render_distributed_linear_gradient_raster(
content,
source,
pattern,
pdf_writer,
page_images,
)
{
return;
}
if paint_distributed_tiles(content, pattern, |content, tile| {
render_linear_gradient_layer_tile(
content,
source,
backdrop,
tile,
content_transform,
shadings,
shading_counter,
pdf_writer,
page_images,
);
}) {
return;
}
let tile_size = pattern.tile_size();
let mut stream = String::new();
render_linear_gradient_layer_tile(
&mut stream,
source,
backdrop,
PdfRect::new(0.0, 0.0, tile_size.x, tile_size.y),
PageContentTransform::default(),
shadings,
shading_counter,
pdf_writer,
page_images,
);
let Some(form) = pattern
.pdf_pattern(PdfRect::new(0.0, 0.0, tile_size.x, tile_size.y))
.and_then(|spec| pdf_writer.add_tiling_pattern(stream, spec))
else {
return;
};
paint_tiling_pattern(content, &form, pattern.paint_box());
page_images.push(form);
}
#[allow(clippy::too_many_arguments)]
pub(super) fn render_linear_gradient_layer_tile(
content: &mut String,
gradient: &LinearGradient,
backdrop: GradientBackdrop,
tile: PdfRect,
content_transform: PageContentTransform,
shadings: &mut Vec<ShadingEntry>,
shading_counter: &mut usize,
pdf_writer: &mut PdfWriter,
page_images: &mut Vec<ImageRef>,
) {
let basis = linear_gradient_line_length(gradient.angle, tile.width, tile.height);
let Some(native) = native_pdf_linear_gradient(&gradient.ramp, basis)
.or_else(|| native_pdf_linear_gradient_over_solid(&gradient.ramp, basis, backdrop))
else {
if render_linear_function_gradient(content, gradient, tile, content_transform, pdf_writer) {
return;
}
let page = pdf_writer.page_content_transform.page_bounds();
if gradient.angle.rem_euclid(360.0) == 90.0
&& page.is_some_and(|page| tile.left == page.left && tile.width == page.width)
&& let Some(color) = premultiplied_solid_gradient_color(&gradient.ramp, basis)
&& let Some(mask) = pdf_writer.try_linear_gradient_alpha_mask(gradient, tile)
&& let Some(pattern) = pdf_writer.add_masked_solid_page_pattern(tile, &mask, color)
&& paint_css_box_pattern(content, content_transform, &pattern, tile).is_some()
{
return;
}
render_linear_gradient_tile_raster(content, gradient, tile, pdf_writer, page_images);
return;
};
if !content_transform.is_identity() {
let (width, height) = (
tile.width / crate::fonts::PT_PER_CSS_PX,
tile.height / crate::fonts::PT_PER_CSS_PX,
);
let (sin, cos) = sin_cos_degrees(gradient.angle);
let half = (width * sin.abs() + height * cos.abs()) / 2.0;
let (cx, cy) = (width / 2.0, height / 2.0);
let (dx, dy) = (sin * half, cos * half);
let nominal_start = PdfPoint::new(cx - dx, cy + dy);
let nominal_end = PdfPoint::new(cx + dx, cy - dy);
let axis = nominal_end - nominal_start;
let pattern_matrix = pdf_writer.paint_matrix(PdfMatrix::new(
PdfVector::new(crate::fonts::PT_PER_CSS_PX, 0.0),
PdfVector::new(0.0, -crate::fonts::PT_PER_CSS_PX),
PdfPoint::new(tile.left, tile.top()),
));
let name = pdf_writer.add_shading_pattern(PdfShadingPattern::axial(
nominal_start + axis * native.span.start,
nominal_start + axis * native.span.end,
pattern_matrix,
native.stops,
PdfPatternGeometryFormat::SixDecimals,
));
paint_shading_pattern(content, &name, tile);
return;
}
render_linear_gradient_tile(
content,
gradient.angle,
tile.left,
tile.bottom,
tile.width,
tile.height,
native,
content_transform,
shadings,
shading_counter,
);
}
pub(super) fn linear_gradient_line_length(angle: f32, width: f32, height: f32) -> f32 {
let (sin_a, cos_a) = sin_cos_degrees(angle);
width * sin_a.abs() + height * cos_a.abs()
}
#[allow(clippy::too_many_arguments)]
pub(super) fn render_linear_gradient_tile(
content: &mut String,
angle: f32,
x: f32,
y: f32,
width: f32,
height: f32,
native: NativePdfGradient,
content_transform: PageContentTransform,
shadings: &mut Vec<ShadingEntry>,
shading_counter: &mut usize,
) {
let tile = PdfRect::new(x, y, width, height);
render_linear_gradient_tile_clipped(
content,
LinearGradientTile {
angle,
bounds: tile,
clip: tile,
},
native,
content_transform,
shadings,
shading_counter,
);
}
#[derive(Clone, Copy)]
pub(super) struct LinearGradientTile {
pub(super) angle: f32,
pub(super) bounds: PdfRect,
pub(super) clip: PdfRect,
}
pub(super) fn render_linear_gradient_tile_clipped(
content: &mut String,
tile: LinearGradientTile,
native: NativePdfGradient,
content_transform: PageContentTransform,
shadings: &mut Vec<ShadingEntry>,
shading_counter: &mut usize,
) {
let (sin_a, cos_a) = sin_cos_degrees(tile.angle);
let cx = tile.bounds.left + tile.bounds.width / 2.0;
let cy = tile.bounds.bottom + tile.bounds.height / 2.0;
let half_len = (tile.bounds.width * sin_a.abs() + tile.bounds.height * cos_a.abs()) / 2.0;
let dx = sin_a * half_len;
let dy = cos_a * half_len;
let nominal_start = PdfPoint::new(cx - dx, cy - dy);
let axis = PdfVector::new(dx * 2.0, dy * 2.0);
let start = nominal_start + axis * native.span.start;
let end = nominal_start + axis * native.span.end;
let name = push_axial_shading(
shadings,
shading_counter,
[start.x, start.y, end.x, end.y],
native.stops,
);
content.push_str("q\n");
content.push_str(&tile.clip.rect_path());
content.push_str("W n\n");
content.push_str(&content_transform.inverse_operator());
content.push_str(&format!("/{name} sh\n"));
content.push_str("Q\n");
}