summaryrefslogtreecommitdiff
path: root/crates/typst-pdf/src/content.rs
diff options
context:
space:
mode:
authorLaurenz Stampfl <47084093+LaurenzV@users.noreply.github.com>2025-04-01 16:42:52 +0200
committerGitHub <noreply@github.com>2025-04-01 14:42:52 +0000
commit96dd67e011bb317cf78683bcf1edfdfca5e7b6b3 (patch)
tree900a0c4e7723af4289685af35d788041055ad4a2 /crates/typst-pdf/src/content.rs
parent012e14d40cb44997630cf6469a446f217f2e9057 (diff)
Switch PDF backend to `krilla` (#5420)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
Diffstat (limited to 'crates/typst-pdf/src/content.rs')
-rw-r--r--crates/typst-pdf/src/content.rs823
1 files changed, 0 insertions, 823 deletions
diff --git a/crates/typst-pdf/src/content.rs b/crates/typst-pdf/src/content.rs
deleted file mode 100644
index 8b7517f5..00000000
--- a/crates/typst-pdf/src/content.rs
+++ /dev/null
@@ -1,823 +0,0 @@
-//! Generic writer for PDF content.
-//!
-//! It is used to write page contents, color glyph instructions, and tilings.
-//!
-//! See also [`pdf_writer::Content`].
-
-use ecow::eco_format;
-use pdf_writer::types::{
- ColorSpaceOperand, LineCapStyle, LineJoinStyle, TextRenderingMode,
-};
-use pdf_writer::writers::PositionedItems;
-use pdf_writer::{Content, Finish, Name, Rect, Str};
-use typst_library::diag::{bail, error, SourceDiagnostic, SourceResult};
-use typst_library::foundations::Repr;
-use typst_library::layout::{
- Abs, Em, Frame, FrameItem, GroupItem, Point, Ratio, Size, Transform,
-};
-use typst_library::model::Destination;
-use typst_library::text::color::should_outline;
-use typst_library::text::{Font, Glyph, TextItem, TextItemView};
-use typst_library::visualize::{
- Curve, CurveItem, FillRule, FixedStroke, Geometry, Image, LineCap, LineJoin, Paint,
- Shape,
-};
-use typst_syntax::Span;
-use typst_utils::{Deferred, Numeric, SliceExt};
-
-use crate::color::PaintEncode;
-use crate::color_font::ColorFontMap;
-use crate::extg::ExtGState;
-use crate::image::deferred_image;
-use crate::resources::Resources;
-use crate::{deflate_deferred, AbsExt, ContentExt, EmExt, PdfOptions, StrExt};
-
-/// Encode a [`Frame`] into a content stream.
-///
-/// The resources that were used in the stream will be added to `resources`.
-///
-/// `color_glyph_width` should be `None` unless the `Frame` represents a [color
-/// glyph].
-///
-/// [color glyph]: `crate::color_font`
-pub fn build(
- options: &PdfOptions,
- resources: &mut Resources<()>,
- frame: &Frame,
- fill: Option<Paint>,
- color_glyph_width: Option<f32>,
-) -> SourceResult<Encoded> {
- let size = frame.size();
- let mut ctx = Builder::new(options, resources, size);
-
- if let Some(width) = color_glyph_width {
- ctx.content.start_color_glyph(width);
- }
-
- // Make the coordinate system start at the top-left.
- ctx.transform(
- // Make the Y axis go upwards
- Transform::scale(Ratio::one(), -Ratio::one())
- // Also move the origin to the top left corner
- .post_concat(Transform::translate(Abs::zero(), size.y)),
- );
-
- if let Some(fill) = fill {
- let shape = Geometry::Rect(frame.size()).filled(fill);
- write_shape(&mut ctx, Point::zero(), &shape)?;
- }
-
- // Encode the frame into the content stream.
- write_frame(&mut ctx, frame)?;
-
- Ok(Encoded {
- size,
- content: deflate_deferred(ctx.content.finish()),
- uses_opacities: ctx.uses_opacities,
- links: ctx.links,
- })
-}
-
-/// An encoded content stream.
-pub struct Encoded {
- /// The dimensions of the content.
- pub size: Size,
- /// The actual content stream.
- pub content: Deferred<Vec<u8>>,
- /// Whether the content opacities.
- pub uses_opacities: bool,
- /// Links in the PDF coordinate system.
- pub links: Vec<(Destination, Rect)>,
-}
-
-/// An exporter for a single PDF content stream.
-///
-/// Content streams are a series of PDF commands. They can reference external
-/// objects only through resources.
-///
-/// Content streams can be used for page contents, but also to describe color
-/// glyphs and tilings.
-pub struct Builder<'a, R = ()> {
- /// Settings for PDF export.
- pub(crate) options: &'a PdfOptions<'a>,
- /// A list of all resources that are used in the content stream.
- pub(crate) resources: &'a mut Resources<R>,
- /// The PDF content stream that is being built.
- pub content: Content,
- /// Current graphic state.
- state: State,
- /// Stack of saved graphic states.
- saves: Vec<State>,
- /// Whether any stroke or fill was not totally opaque.
- uses_opacities: bool,
- /// All clickable links that are present in this content.
- links: Vec<(Destination, Rect)>,
-}
-
-impl<'a, R> Builder<'a, R> {
- /// Create a new content builder.
- pub fn new(
- options: &'a PdfOptions<'a>,
- resources: &'a mut Resources<R>,
- size: Size,
- ) -> Self {
- Builder {
- options,
- resources,
- uses_opacities: false,
- content: Content::new(),
- state: State::new(size),
- saves: vec![],
- links: vec![],
- }
- }
-}
-
-/// A simulated graphics state used to deduplicate graphics state changes and
-/// keep track of the current transformation matrix for link annotations.
-#[derive(Debug, Clone)]
-struct State {
- /// The transform of the current item.
- transform: Transform,
- /// The transform of first hard frame in the hierarchy.
- container_transform: Transform,
- /// The size of the first hard frame in the hierarchy.
- size: Size,
- /// The current font.
- font: Option<(Font, Abs)>,
- /// The current fill paint.
- fill: Option<Paint>,
- /// The color space of the current fill paint.
- fill_space: Option<Name<'static>>,
- /// The current external graphic state.
- external_graphics_state: ExtGState,
- /// The current stroke paint.
- stroke: Option<FixedStroke>,
- /// The color space of the current stroke paint.
- stroke_space: Option<Name<'static>>,
- /// The current text rendering mode.
- text_rendering_mode: TextRenderingMode,
-}
-
-impl State {
- /// Creates a new, clean state for a given `size`.
- pub fn new(size: Size) -> Self {
- Self {
- transform: Transform::identity(),
- container_transform: Transform::identity(),
- size,
- font: None,
- fill: None,
- fill_space: None,
- external_graphics_state: ExtGState::default(),
- stroke: None,
- stroke_space: None,
- text_rendering_mode: TextRenderingMode::Fill,
- }
- }
-
- /// Creates the [`Transforms`] structure for the current item.
- pub fn transforms(&self, size: Size, pos: Point) -> Transforms {
- Transforms {
- transform: self.transform.pre_concat(Transform::translate(pos.x, pos.y)),
- container_transform: self.container_transform,
- container_size: self.size,
- size,
- }
- }
-}
-
-/// Subset of the state used to calculate the transform of gradients and tilings.
-#[derive(Debug, Clone, Copy)]
-pub(super) struct Transforms {
- /// The transform of the current item.
- pub transform: Transform,
- /// The transform of first hard frame in the hierarchy.
- pub container_transform: Transform,
- /// The size of the first hard frame in the hierarchy.
- pub container_size: Size,
- /// The size of the item.
- pub size: Size,
-}
-
-impl Builder<'_, ()> {
- fn save_state(&mut self) -> SourceResult<()> {
- self.saves.push(self.state.clone());
- self.content.save_state_checked()
- }
-
- fn restore_state(&mut self) {
- self.content.restore_state();
- self.state = self.saves.pop().expect("missing state save");
- }
-
- fn set_external_graphics_state(&mut self, graphics_state: &ExtGState) {
- let current_state = &self.state.external_graphics_state;
- if current_state != graphics_state {
- let index = self.resources.ext_gs.insert(*graphics_state);
- let name = eco_format!("Gs{index}");
- self.content.set_parameters(Name(name.as_bytes()));
-
- self.state.external_graphics_state = *graphics_state;
- if graphics_state.uses_opacities() {
- self.uses_opacities = true;
- }
- }
- }
-
- fn set_opacities(&mut self, stroke: Option<&FixedStroke>, fill: Option<&Paint>) {
- let get_opacity = |paint: &Paint| {
- let color = match paint {
- Paint::Solid(color) => *color,
- Paint::Gradient(_) | Paint::Tiling(_) => return 255,
- };
-
- color.alpha().map_or(255, |v| (v * 255.0).round() as u8)
- };
-
- let stroke_opacity = stroke.map_or(255, |stroke| get_opacity(&stroke.paint));
- let fill_opacity = fill.map_or(255, get_opacity);
- self.set_external_graphics_state(&ExtGState { stroke_opacity, fill_opacity });
- }
-
- fn reset_opacities(&mut self) {
- self.set_external_graphics_state(&ExtGState {
- stroke_opacity: 255,
- fill_opacity: 255,
- });
- }
-
- pub fn transform(&mut self, transform: Transform) {
- let Transform { sx, ky, kx, sy, tx, ty } = transform;
- self.state.transform = self.state.transform.pre_concat(transform);
- if self.state.container_transform.is_identity() {
- self.state.container_transform = self.state.transform;
- }
- self.content.transform([
- sx.get() as _,
- ky.get() as _,
- kx.get() as _,
- sy.get() as _,
- tx.to_f32(),
- ty.to_f32(),
- ]);
- }
-
- fn group_transform(&mut self, transform: Transform) {
- self.state.container_transform =
- self.state.container_transform.pre_concat(transform);
- }
-
- fn set_font(&mut self, font: &Font, size: Abs) {
- if self.state.font.as_ref().map(|(f, s)| (f, *s)) != Some((font, size)) {
- let index = self.resources.fonts.insert(font.clone());
- let name = eco_format!("F{index}");
- self.content.set_font(Name(name.as_bytes()), size.to_f32());
- self.state.font = Some((font.clone(), size));
- }
- }
-
- fn size(&mut self, size: Size) {
- self.state.size = size;
- }
-
- fn set_fill(
- &mut self,
- fill: &Paint,
- on_text: bool,
- transforms: Transforms,
- ) -> SourceResult<()> {
- if self.state.fill.as_ref() != Some(fill)
- || matches!(self.state.fill, Some(Paint::Gradient(_)))
- {
- fill.set_as_fill(self, on_text, transforms)?;
- self.state.fill = Some(fill.clone());
- }
- Ok(())
- }
-
- pub fn set_fill_color_space(&mut self, space: Name<'static>) {
- if self.state.fill_space != Some(space) {
- self.content.set_fill_color_space(ColorSpaceOperand::Named(space));
- self.state.fill_space = Some(space);
- }
- }
-
- pub fn reset_fill_color_space(&mut self) {
- self.state.fill_space = None;
- }
-
- fn set_stroke(
- &mut self,
- stroke: &FixedStroke,
- on_text: bool,
- transforms: Transforms,
- ) -> SourceResult<()> {
- if self.state.stroke.as_ref() != Some(stroke)
- || matches!(
- self.state.stroke.as_ref().map(|s| &s.paint),
- Some(Paint::Gradient(_))
- )
- {
- let FixedStroke { paint, thickness, cap, join, dash, miter_limit } = stroke;
- paint.set_as_stroke(self, on_text, transforms)?;
-
- self.content.set_line_width(thickness.to_f32());
- if self.state.stroke.as_ref().map(|s| &s.cap) != Some(cap) {
- self.content.set_line_cap(to_pdf_line_cap(*cap));
- }
- if self.state.stroke.as_ref().map(|s| &s.join) != Some(join) {
- self.content.set_line_join(to_pdf_line_join(*join));
- }
- if self.state.stroke.as_ref().map(|s| &s.dash) != Some(dash) {
- if let Some(dash) = dash {
- self.content.set_dash_pattern(
- dash.array.iter().map(|l| l.to_f32()),
- dash.phase.to_f32(),
- );
- } else {
- self.content.set_dash_pattern([], 0.0);
- }
- }
- if self.state.stroke.as_ref().map(|s| &s.miter_limit) != Some(miter_limit) {
- self.content.set_miter_limit(miter_limit.get() as f32);
- }
- self.state.stroke = Some(stroke.clone());
- }
-
- Ok(())
- }
-
- pub fn set_stroke_color_space(&mut self, space: Name<'static>) {
- if self.state.stroke_space != Some(space) {
- self.content.set_stroke_color_space(ColorSpaceOperand::Named(space));
- self.state.stroke_space = Some(space);
- }
- }
-
- pub fn reset_stroke_color_space(&mut self) {
- self.state.stroke_space = None;
- }
-
- fn set_text_rendering_mode(&mut self, mode: TextRenderingMode) {
- if self.state.text_rendering_mode != mode {
- self.content.set_text_rendering_mode(mode);
- self.state.text_rendering_mode = mode;
- }
- }
-}
-
-/// Encode a frame into the content stream.
-pub(crate) fn write_frame(ctx: &mut Builder, frame: &Frame) -> SourceResult<()> {
- for &(pos, ref item) in frame.items() {
- let x = pos.x.to_f32();
- let y = pos.y.to_f32();
- match item {
- FrameItem::Group(group) => write_group(ctx, pos, group)?,
- FrameItem::Text(text) => write_text(ctx, pos, text)?,
- FrameItem::Shape(shape, _) => write_shape(ctx, pos, shape)?,
- FrameItem::Image(image, size, span) => {
- write_image(ctx, x, y, image, *size, *span)?
- }
- FrameItem::Link(dest, size) => write_link(ctx, pos, dest, *size),
- FrameItem::Tag(_) => {}
- }
- }
- Ok(())
-}
-
-/// Encode a group into the content stream.
-fn write_group(ctx: &mut Builder, pos: Point, group: &GroupItem) -> SourceResult<()> {
- let translation = Transform::translate(pos.x, pos.y);
-
- ctx.save_state()?;
-
- if group.frame.kind().is_hard() {
- ctx.group_transform(
- ctx.state
- .transform
- .post_concat(ctx.state.container_transform.invert().unwrap())
- .pre_concat(translation)
- .pre_concat(group.transform),
- );
- ctx.size(group.frame.size());
- }
-
- ctx.transform(translation.pre_concat(group.transform));
- if let Some(clip_curve) = &group.clip {
- write_curve(ctx, 0.0, 0.0, clip_curve);
- ctx.content.clip_nonzero();
- ctx.content.end_path();
- }
-
- write_frame(ctx, &group.frame)?;
- ctx.restore_state();
-
- Ok(())
-}
-
-/// Encode a text run into the content stream.
-fn write_text(ctx: &mut Builder, pos: Point, text: &TextItem) -> SourceResult<()> {
- if ctx.options.standards.pdfa && text.font.info().is_last_resort() {
- bail!(
- Span::find(text.glyphs.iter().map(|g| g.span.0)),
- "the text {} could not be displayed with any font",
- &text.text,
- );
- }
-
- let outline_glyphs =
- text.glyphs.iter().filter(|g| should_outline(&text.font, g)).count();
-
- if outline_glyphs == text.glyphs.len() {
- write_normal_text(ctx, pos, TextItemView::full(text))?;
- } else if outline_glyphs == 0 {
- write_complex_glyphs(ctx, pos, TextItemView::full(text))?;
- } else {
- // Otherwise we need to split it into smaller text runs.
- let mut offset = 0;
- let mut position_in_run = Abs::zero();
- for (should_outline, sub_run) in
- text.glyphs.group_by_key(|g| should_outline(&text.font, g))
- {
- let end = offset + sub_run.len();
-
- // Build a sub text-run
- let text_item_view = TextItemView::from_glyph_range(text, offset..end);
-
- // Adjust the position of the run on the line
- let pos = pos + Point::new(position_in_run, Abs::zero());
- position_in_run += text_item_view.width();
- offset = end;
-
- // Actually write the sub text-run.
- if should_outline {
- write_normal_text(ctx, pos, text_item_view)?;
- } else {
- write_complex_glyphs(ctx, pos, text_item_view)?;
- }
- }
- }
-
- Ok(())
-}
-
-/// Encodes a text run (without any color glyph) into the content stream.
-fn write_normal_text(
- ctx: &mut Builder,
- pos: Point,
- text: TextItemView,
-) -> SourceResult<()> {
- let x = pos.x.to_f32();
- let y = pos.y.to_f32();
-
- *ctx.resources.languages.entry(text.item.lang).or_insert(0) += text.glyph_range.len();
-
- let glyph_set = ctx.resources.glyph_sets.entry(text.item.font.clone()).or_default();
- for g in text.glyphs() {
- glyph_set.entry(g.id).or_insert_with(|| text.glyph_text(g));
- }
-
- let fill_transform = ctx.state.transforms(Size::zero(), pos);
- ctx.set_fill(&text.item.fill, true, fill_transform)?;
-
- let stroke = text.item.stroke.as_ref().and_then(|stroke| {
- if stroke.thickness.to_f32() > 0.0 {
- Some(stroke)
- } else {
- None
- }
- });
-
- if let Some(stroke) = stroke {
- ctx.set_stroke(stroke, true, fill_transform)?;
- ctx.set_text_rendering_mode(TextRenderingMode::FillStroke);
- } else {
- ctx.set_text_rendering_mode(TextRenderingMode::Fill);
- }
-
- ctx.set_font(&text.item.font, text.item.size);
- ctx.set_opacities(text.item.stroke.as_ref(), Some(&text.item.fill));
- ctx.content.begin_text();
-
- // Position the text.
- ctx.content.set_text_matrix([1.0, 0.0, 0.0, -1.0, x, y]);
-
- let mut positioned = ctx.content.show_positioned();
- let mut items = positioned.items();
- let mut adjustment = Em::zero();
- let mut encoded = vec![];
-
- let glyph_remapper = ctx
- .resources
- .glyph_remappers
- .entry(text.item.font.clone())
- .or_default();
-
- // Write the glyphs with kerning adjustments.
- for glyph in text.glyphs() {
- if ctx.options.standards.pdfa && glyph.id == 0 {
- bail!(tofu(&text, glyph));
- }
-
- adjustment += glyph.x_offset;
-
- if !adjustment.is_zero() {
- if !encoded.is_empty() {
- show_text(&mut items, &encoded);
- encoded.clear();
- }
-
- items.adjust(-adjustment.to_font_units());
- adjustment = Em::zero();
- }
-
- // In PDF, we use CIDs to index the glyphs in a font, not GIDs. What a
- // CID actually refers to depends on the type of font we are embedding:
- //
- // - For TrueType fonts, the CIDs are defined by an external mapping.
- // - For SID-keyed CFF fonts, the CID is the same as the GID in the font.
- // - For CID-keyed CFF fonts, the CID refers to the CID in the font.
- //
- // (See in the PDF-spec for more details on this.)
- //
- // However, in our case:
- // - We use the identity-mapping for TrueType fonts.
- // - SID-keyed fonts will get converted into CID-keyed fonts by the
- // subsetter.
- // - CID-keyed fonts will be rewritten in a way so that the mapping
- // between CID and GID is always the identity mapping, regardless of
- // the mapping before.
- //
- // Because of this, we can always use the remapped GID as the CID,
- // regardless of which type of font we are actually embedding.
- let cid = glyph_remapper.remap(glyph.id);
- encoded.push((cid >> 8) as u8);
- encoded.push((cid & 0xff) as u8);
-
- if let Some(advance) = text.item.font.advance(glyph.id) {
- adjustment += glyph.x_advance - advance;
- }
-
- adjustment -= glyph.x_offset;
- }
-
- if !encoded.is_empty() {
- show_text(&mut items, &encoded);
- }
-
- items.finish();
- positioned.finish();
- ctx.content.end_text();
-
- Ok(())
-}
-
-/// Shows text, ensuring that each individual string doesn't exceed the
-/// implementation limits.
-fn show_text(items: &mut PositionedItems, encoded: &[u8]) {
- for chunk in encoded.chunks(Str::PDFA_LIMIT) {
- items.show(Str(chunk));
- }
-}
-
-/// Encodes a text run made only of color glyphs into the content stream
-fn write_complex_glyphs(
- ctx: &mut Builder,
- pos: Point,
- text: TextItemView,
-) -> SourceResult<()> {
- let x = pos.x.to_f32();
- let y = pos.y.to_f32();
-
- let mut last_font = None;
-
- ctx.reset_opacities();
-
- ctx.content.begin_text();
- ctx.content.set_text_matrix([1.0, 0.0, 0.0, -1.0, x, y]);
- // So that the next call to ctx.set_font() will change the font to one that
- // displays regular glyphs and not color glyphs.
- ctx.state.font = None;
-
- let glyph_set = ctx
- .resources
- .color_glyph_sets
- .entry(text.item.font.clone())
- .or_default();
-
- for glyph in text.glyphs() {
- if ctx.options.standards.pdfa && glyph.id == 0 {
- bail!(tofu(&text, glyph));
- }
-
- // Retrieve the Type3 font reference and the glyph index in the font.
- let color_fonts = ctx
- .resources
- .color_fonts
- .get_or_insert_with(|| Box::new(ColorFontMap::new()));
-
- let (font, index) = color_fonts.get(ctx.options, &text, glyph)?;
-
- if last_font != Some(font) {
- ctx.content.set_font(
- Name(eco_format!("Cf{}", font).as_bytes()),
- text.item.size.to_f32(),
- );
- last_font = Some(font);
- }
-
- ctx.content.show(Str(&[index]));
-
- glyph_set.entry(glyph.id).or_insert_with(|| text.glyph_text(glyph));
- }
- ctx.content.end_text();
-
- Ok(())
-}
-
-/// Encode a geometrical shape into the content stream.
-fn write_shape(ctx: &mut Builder, pos: Point, shape: &Shape) -> SourceResult<()> {
- let x = pos.x.to_f32();
- let y = pos.y.to_f32();
-
- let stroke = shape.stroke.as_ref().and_then(|stroke| {
- if stroke.thickness.to_f32() > 0.0 {
- Some(stroke)
- } else {
- None
- }
- });
-
- if shape.fill.is_none() && stroke.is_none() {
- return Ok(());
- }
-
- if let Some(fill) = &shape.fill {
- ctx.set_fill(fill, false, ctx.state.transforms(shape.geometry.bbox_size(), pos))?;
- }
-
- if let Some(stroke) = stroke {
- ctx.set_stroke(
- stroke,
- false,
- ctx.state.transforms(shape.geometry.bbox_size(), pos),
- )?;
- }
-
- ctx.set_opacities(stroke, shape.fill.as_ref());
-
- match &shape.geometry {
- Geometry::Line(target) => {
- let dx = target.x.to_f32();
- let dy = target.y.to_f32();
- ctx.content.move_to(x, y);
- ctx.content.line_to(x + dx, y + dy);
- }
- Geometry::Rect(size) => {
- let w = size.x.to_f32();
- let h = size.y.to_f32();
- if w.abs() > f32::EPSILON && h.abs() > f32::EPSILON {
- ctx.content.rect(x, y, w, h);
- }
- }
- Geometry::Curve(curve) => {
- write_curve(ctx, x, y, curve);
- }
- }
-
- match (&shape.fill, &shape.fill_rule, stroke) {
- (None, _, None) => unreachable!(),
- (Some(_), FillRule::NonZero, None) => ctx.content.fill_nonzero(),
- (Some(_), FillRule::EvenOdd, None) => ctx.content.fill_even_odd(),
- (None, _, Some(_)) => ctx.content.stroke(),
- (Some(_), FillRule::NonZero, Some(_)) => ctx.content.fill_nonzero_and_stroke(),
- (Some(_), FillRule::EvenOdd, Some(_)) => ctx.content.fill_even_odd_and_stroke(),
- };
-
- Ok(())
-}
-
-/// Encode a curve into the content stream.
-fn write_curve(ctx: &mut Builder, x: f32, y: f32, curve: &Curve) {
- for elem in &curve.0 {
- match elem {
- CurveItem::Move(p) => ctx.content.move_to(x + p.x.to_f32(), y + p.y.to_f32()),
- CurveItem::Line(p) => ctx.content.line_to(x + p.x.to_f32(), y + p.y.to_f32()),
- CurveItem::Cubic(p1, p2, p3) => ctx.content.cubic_to(
- x + p1.x.to_f32(),
- y + p1.y.to_f32(),
- x + p2.x.to_f32(),
- y + p2.y.to_f32(),
- x + p3.x.to_f32(),
- y + p3.y.to_f32(),
- ),
- CurveItem::Close => ctx.content.close_path(),
- };
- }
-}
-
-/// Encode a vector or raster image into the content stream.
-fn write_image(
- ctx: &mut Builder,
- x: f32,
- y: f32,
- image: &Image,
- size: Size,
- span: Span,
-) -> SourceResult<()> {
- let index = ctx.resources.images.insert(image.clone());
- ctx.resources.deferred_images.entry(index).or_insert_with(|| {
- let (image, color_space) =
- deferred_image(image.clone(), ctx.options.standards.pdfa);
- if let Some(color_space) = color_space {
- ctx.resources.colors.mark_as_used(color_space);
- }
- (image, span)
- });
-
- ctx.reset_opacities();
-
- let name = eco_format!("Im{index}");
- let w = size.x.to_f32();
- let h = size.y.to_f32();
- ctx.content.save_state_checked()?;
- ctx.content.transform([w, 0.0, 0.0, -h, x, y + h]);
-
- if let Some(alt) = image.alt() {
- if ctx.options.standards.pdfa && alt.len() > Str::PDFA_LIMIT {
- bail!(span, "the image's alt text is too long");
- }
-
- let mut image_span =
- ctx.content.begin_marked_content_with_properties(Name(b"Span"));
- let mut image_alt = image_span.properties();
- image_alt.pair(Name(b"Alt"), Str(alt.as_bytes()));
- image_alt.finish();
- image_span.finish();
-
- ctx.content.x_object(Name(name.as_bytes()));
- ctx.content.end_marked_content();
- } else {
- ctx.content.x_object(Name(name.as_bytes()));
- }
-
- ctx.content.restore_state();
- Ok(())
-}
-
-/// Save a link for later writing in the annotations dictionary.
-fn write_link(ctx: &mut Builder, pos: Point, dest: &Destination, size: Size) {
- let mut min_x = Abs::inf();
- let mut min_y = Abs::inf();
- let mut max_x = -Abs::inf();
- let mut max_y = -Abs::inf();
-
- // Compute the bounding box of the transformed link.
- for point in [
- pos,
- pos + Point::with_x(size.x),
- pos + Point::with_y(size.y),
- pos + size.to_point(),
- ] {
- let t = point.transform(ctx.state.transform);
- min_x.set_min(t.x);
- min_y.set_min(t.y);
- max_x.set_max(t.x);
- max_y.set_max(t.y);
- }
-
- let x1 = min_x.to_f32();
- let x2 = max_x.to_f32();
- let y1 = max_y.to_f32();
- let y2 = min_y.to_f32();
- let rect = Rect::new(x1, y1, x2, y2);
-
- ctx.links.push((dest.clone(), rect));
-}
-
-fn to_pdf_line_cap(cap: LineCap) -> LineCapStyle {
- match cap {
- LineCap::Butt => LineCapStyle::ButtCap,
- LineCap::Round => LineCapStyle::RoundCap,
- LineCap::Square => LineCapStyle::ProjectingSquareCap,
- }
-}
-
-fn to_pdf_line_join(join: LineJoin) -> LineJoinStyle {
- match join {
- LineJoin::Miter => LineJoinStyle::MiterJoin,
- LineJoin::Round => LineJoinStyle::RoundJoin,
- LineJoin::Bevel => LineJoinStyle::BevelJoin,
- }
-}
-
-/// The error when there is a tofu glyph.
-#[cold]
-fn tofu(text: &TextItemView, glyph: &Glyph) -> SourceDiagnostic {
- error!(
- glyph.span.0,
- "the text {} could not be displayed with any font",
- text.glyph_text(glyph).repr(),
- )
-}