use std::num::NonZeroU64;
use crate::color::rgb;
use crate::content::ContentBuilder;
use crate::geom::Path;
#[cfg(any(feature = "raster-images", feature = "pdf"))]
use crate::geom::Size;
use crate::geom::{Point, Transform};
use crate::graphic::Graphic;
use crate::graphics::blend::BlendMode;
use crate::graphics::graphics_state::ExtGState;
#[cfg(feature = "raster-images")]
use crate::graphics::image::Image;
use crate::graphics::mask::Mask;
use crate::graphics::paint::{Fill, FillRule, Stroke};
use crate::graphics::shading_function::ShadingFunction;
use crate::interchange::tagging::{ContentTag, Identifier, PageTagIdentifier};
use crate::num::NormalizedF32;
use crate::paint::{InnerPaint, Paint};
#[cfg(feature = "pdf")]
use crate::pdf::PdfDocument;
use crate::serialize::SerializeContext;
use crate::stream::{Stream, StreamBuilder};
use crate::tagging::SpanTag;
use crate::text::Font;
use crate::text::{draw_glyph, Glyph};
#[cfg(feature = "simple-text")]
use crate::text::{shape::naive_shape, TextDirection};
pub type Location = NonZeroU64;
pub struct Surface<'a> {
pub(crate) sc: &'a mut SerializeContext,
fill: Option<Fill>,
stroke: Option<Stroke>,
bd: Builders,
push_instructions: Vec<PushInstruction>,
page_identifier: Option<PageTagIdentifier>,
finish_fn: Box<dyn FnMut(Stream, i32) + 'a>,
}
impl<'a> Surface<'a> {
pub(crate) fn new(
sc: &'a mut SerializeContext,
root_builder: ContentBuilder,
page_identifier: Option<PageTagIdentifier>,
finish_fn: Box<dyn FnMut(Stream, i32) + 'a>,
) -> Surface<'a> {
Self {
sc,
bd: Builders::new(root_builder),
page_identifier,
fill: None,
stroke: None,
push_instructions: vec![],
finish_fn,
}
}
pub fn stream_builder(&mut self) -> StreamBuilder<'_> {
StreamBuilder::new(self.sc)
}
pub fn set_fill(&mut self, fill: Option<Fill>) {
self.fill = fill;
}
pub fn get_fill(&mut self) -> Option<&Fill> {
self.fill.as_ref()
}
pub fn set_stroke(&mut self, stroke: Option<Stroke>) {
self.stroke = stroke;
}
pub fn get_stroke(&mut self) -> Option<&Stroke> {
self.stroke.as_ref()
}
pub fn draw_path(&mut self, path: &Path) {
if self.fill.is_some() || self.stroke.is_some() {
if self.has_complex_fill_or_stroke() {
self.bd
.get_mut()
.draw_path(&path.0, self.fill.as_ref(), None, self.sc);
self.bd
.get_mut()
.draw_path(&path.0, None, self.stroke.as_ref(), self.sc);
} else {
self.bd.get_mut().draw_path(
&path.0,
self.fill.as_ref(),
self.stroke.as_ref(),
self.sc,
);
}
} else {
self.bd
.get_mut()
.draw_path(&path.0, Some(&Fill::default()), None, self.sc);
}
}
pub fn start_tagged(&mut self, tag: ContentTag) -> Identifier {
if let Some(id) = &mut self.page_identifier {
match tag {
ContentTag::Artifact(at) => {
if at.requires_properties() {
self.bd
.get_mut()
.start_marked_content_with_properties(self.sc, None, tag);
} else {
self.bd.get_mut().start_marked_content(tag.name());
}
Identifier::dummy()
}
ContentTag::Span(_) | ContentTag::Other => {
self.bd.get_mut().start_marked_content_with_properties(
self.sc,
Some(id.mcid),
tag,
);
id.bump().into()
}
}
} else {
Identifier::dummy()
}
}
#[doc(hidden)]
pub fn start_alt_text(&mut self, text: &str) {
let tag = ContentTag::Span(SpanTag {
lang: None,
alt_text: Some(text),
expanded: None,
actual_text: None,
});
self.bd
.get_mut()
.start_marked_content_with_properties(self.sc, None, tag);
}
#[doc(hidden)]
pub fn end_alt_text(&mut self) {
self.bd.get_mut().end_marked_content();
}
pub fn end_tagged(&mut self) {
if self.page_identifier.is_some() {
self.bd.get_mut().end_marked_content();
}
}
fn outline_glyphs(
&mut self,
glyphs: &[impl Glyph],
context_color: rgb::Color,
start: Point,
font: Font,
font_size: f32,
) {
let (mut cur_x, y) = (start.x, start.y);
for glyph in glyphs {
let mut base_transform = tiny_skia_path::Transform::from_translate(
cur_x + glyph.x_offset(font_size),
y - glyph.y_offset(font_size),
);
base_transform = base_transform.pre_concat(tiny_skia_path::Transform::from_scale(
font_size / font.units_per_em(),
-font_size / font.units_per_em(),
));
draw_glyph(
font.clone(),
context_color,
glyph.glyph_id(),
Transform::from_tsp(base_transform),
self,
);
cur_x += glyph.x_advance(font_size);
}
}
pub fn draw_glyphs(
&mut self,
start: Point,
glyphs: &[impl Glyph],
font: Font,
text: &str,
font_size: f32,
outlined: bool,
) {
let context_color = self.context_color();
if outlined {
self.outline_glyphs(glyphs, context_color, start, font, font_size);
} else {
match (self.fill.as_ref(), self.stroke.as_ref()) {
(Some(f), Some(s)) => {
if self.has_complex_fill_or_stroke() {
self.bd.get_mut().draw_glyphs(
start,
self.sc,
Some(f),
None,
context_color,
glyphs,
font.clone(),
text,
font_size,
);
self.outline_glyphs(glyphs, context_color, start, font, font_size);
} else {
self.bd.get_mut().draw_glyphs(
start,
self.sc,
Some(f),
Some(s),
context_color,
glyphs,
font,
text,
font_size,
);
}
}
(None, Some(s)) => {
self.bd.get_mut().draw_glyphs(
start,
self.sc,
None,
Some(s),
context_color,
glyphs,
font,
text,
font_size,
);
}
(Some(f), None) => {
self.bd.get_mut().draw_glyphs(
start,
self.sc,
Some(f),
None,
context_color,
glyphs,
font,
text,
font_size,
);
}
(None, None) => {
self.bd.get_mut().draw_glyphs(
start,
self.sc,
Some(&Fill::default()),
None,
context_color,
glyphs,
font,
text,
font_size,
);
}
}
}
}
#[cfg(feature = "simple-text")]
pub fn draw_text(
&mut self,
start: Point,
font: Font,
font_size: f32,
text: &str,
outlined: bool,
direction: TextDirection,
) {
let glyphs = naive_shape(text, font.clone(), direction);
self.draw_glyphs(start, &glyphs, font, text, font_size, outlined);
}
pub fn set_location(&mut self, location: Location) {
self.sc.set_location(location);
}
pub fn reset_location(&mut self) {
self.sc.reset_location();
}
pub fn ctm(&self) -> Transform {
self.bd.get().cur_transform()
}
pub fn push_transform(&mut self, transform: &Transform) {
self.push_instructions.push(PushInstruction::Transform);
self.bd.get_mut().save_graphics_state();
self.bd.get_mut().concat_transform(transform);
}
pub fn push_blend_mode(&mut self, blend_mode: BlendMode) {
self.push_instructions.push(PushInstruction::BlendMode);
self.bd.get_mut().save_graphics_state();
self.bd.get_mut().set_blend_mode(blend_mode.to_pdf());
}
pub fn push_clip_path(&mut self, path: &Path, clip_rule: &FillRule) {
self.push_instructions.push(PushInstruction::ClipPath);
self.bd.get_mut().push_clip_path(&path.0, clip_rule);
}
pub fn push_mask(&mut self, mask: Mask) {
self.push_instructions
.push(PushInstruction::Mask(Box::new(mask)));
self.bd
.sub_builders
.push(ContentBuilder::new(Transform::identity(), true));
}
#[cfg(feature = "pdf")]
pub fn draw_pdf_page(&mut self, pdf: &PdfDocument, size: Size, page_idx: usize) {
use crate::geom::Rect;
let obj_ref = self.sc.embed_pdf_page_as_xobject(pdf, page_idx);
let (page_width, page_height) = pdf
.pages()
.get(page_idx)
.map(|p| p.render_dimensions())
.unwrap_or((1.0, 1.0));
let transform =
Transform::from_scale(size.width() / page_width, size.height() / page_height);
self.push_transform(&transform);
self.push_transform(&Transform::from_row(1.0, 0.0, 0.0, -1.0, 0.0, page_height));
self.bd.get_mut().draw_xobject_by_reference(
self.sc,
Rect::from_xywh(0.0, 0.0, page_width, page_height).unwrap(),
obj_ref,
);
self.pop();
self.pop();
}
pub fn push_opacity(&mut self, opacity: NormalizedF32) {
self.push_instructions
.push(PushInstruction::Opacity(opacity));
if opacity != NormalizedF32::ONE {
self.bd
.sub_builders
.push(ContentBuilder::new(Transform::identity(), true));
}
}
pub fn push_isolated(&mut self) {
self.push_instructions.push(PushInstruction::Isolated);
self.bd
.sub_builders
.push(ContentBuilder::new(Transform::identity(), true));
}
pub fn pop(&mut self) {
match self.push_instructions.pop().unwrap() {
PushInstruction::Transform => self.bd.get_mut().restore_graphics_state(),
PushInstruction::Opacity(o) => {
if o != NormalizedF32::ONE {
let stream = self.bd.sub_builders.pop().unwrap().finish(self.sc);
self.bd.get_mut().draw_opacified(self.sc, o, stream);
}
}
PushInstruction::ClipPath => self.bd.get_mut().pop_clip_path(),
PushInstruction::BlendMode => self.bd.get_mut().restore_graphics_state(),
PushInstruction::Mask(mask) => {
let stream = self.bd.sub_builders.pop().unwrap().finish(self.sc);
self.bd.get_mut().draw_masked(self.sc, *mask, stream)
}
PushInstruction::Isolated => {
let stream = self.bd.sub_builders.pop().unwrap().finish(self.sc);
self.bd.get_mut().draw_isolated(self.sc, stream);
}
}
}
#[cfg(feature = "raster-images")]
pub fn draw_image(&mut self, image: Image, size: Size) {
self.bd.get_mut().draw_image(image, size, self.sc);
}
pub fn draw_graphic(&mut self, graphic: Graphic) {
self.bd
.get_mut()
.draw_xobject(self.sc, graphic.x_object, &ExtGState::new())
}
pub(crate) fn draw_shading(&mut self, shading: &ShadingFunction) {
self.bd.get_mut().draw_shading(shading, self.sc);
}
pub fn finish(self) {}
pub(crate) fn draw_opacified_stream(&mut self, opacity: NormalizedF32, stream: Stream) {
self.bd.get_mut().draw_opacified(self.sc, opacity, stream)
}
pub fn cur_transform(&self) -> Transform {
self.bd.get().cur_transform()
}
fn context_color(&self) -> rgb::Color {
self.fill
.as_ref()
.and_then(|f| f.paint.as_rgb())
.or_else(|| self.stroke.as_ref().and_then(|s| s.paint.as_rgb()))
.unwrap_or_default()
}
fn has_complex_fill_or_stroke(&self) -> bool {
let check_paint = |paint: &Paint| match &paint.0 {
InnerPaint::Color(_) => false,
InnerPaint::LinearGradient(l) => {
l.stops.iter().any(|s| s.opacity != NormalizedF32::ONE)
}
InnerPaint::RadialGradient(r) => {
r.stops.iter().any(|s| s.opacity != NormalizedF32::ONE)
}
InnerPaint::SweepGradient(r) => r.stops.iter().any(|s| s.opacity != NormalizedF32::ONE),
InnerPaint::Pattern(_) => false,
};
let complex_stroke = self
.stroke
.as_ref()
.is_some_and(|s| s.opacity != NormalizedF32::ONE || check_paint(&s.paint));
let complex_fill = self.fill.as_ref().is_some_and(|f| check_paint(&f.paint));
complex_fill || complex_stroke
}
}
impl Drop for Surface<'_> {
fn drop(&mut self) {
let root_builder = std::mem::replace(
&mut self.bd.root_builder,
ContentBuilder::new(Transform::identity(), false),
);
let num_mcids = match self.page_identifier {
Some(pi) => pi.mcid,
None => 0,
};
assert!(self.bd.sub_builders.is_empty());
assert!(self.push_instructions.is_empty());
assert!(!root_builder.active_marked_content);
(self.finish_fn)(root_builder.finish(self.sc), num_mcids)
}
}
struct Builders {
pub(crate) root_builder: ContentBuilder,
pub(crate) sub_builders: Vec<ContentBuilder>,
}
impl Builders {
fn new(root_builder: ContentBuilder) -> Self {
Self {
root_builder,
sub_builders: vec![],
}
}
fn get_mut(&mut self) -> &mut ContentBuilder {
self.sub_builders
.last_mut()
.unwrap_or(&mut self.root_builder)
}
fn get(&self) -> &ContentBuilder {
self.sub_builders.last().unwrap_or(&self.root_builder)
}
}
pub(crate) enum PushInstruction {
Transform,
Opacity(NormalizedF32),
ClipPath,
BlendMode,
Mask(Box<Mask>),
Isolated,
}