summaryrefslogtreecommitdiff
path: root/crates/typst-pdf/src/text.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-pdf/src/text.rs')
-rw-r--r--crates/typst-pdf/src/text.rs135
1 files changed, 135 insertions, 0 deletions
diff --git a/crates/typst-pdf/src/text.rs b/crates/typst-pdf/src/text.rs
new file mode 100644
index 00000000..8d532e08
--- /dev/null
+++ b/crates/typst-pdf/src/text.rs
@@ -0,0 +1,135 @@
+use std::ops::Range;
+use std::sync::Arc;
+
+use bytemuck::TransparentWrapper;
+use krilla::surface::{Location, Surface};
+use krilla::text::GlyphId;
+use typst_library::diag::{bail, SourceResult};
+use typst_library::layout::Size;
+use typst_library::text::{Font, Glyph, TextItem};
+use typst_library::visualize::FillRule;
+use typst_syntax::Span;
+
+use crate::convert::{FrameContext, GlobalContext};
+use crate::paint;
+use crate::util::{display_font, AbsExt, TransformExt};
+
+#[typst_macros::time(name = "handle text")]
+pub(crate) fn handle_text(
+ fc: &mut FrameContext,
+ t: &TextItem,
+ surface: &mut Surface,
+ gc: &mut GlobalContext,
+) -> SourceResult<()> {
+ *gc.languages.entry(t.lang).or_insert(0) += t.glyphs.len();
+
+ let font = convert_font(gc, t.font.clone())?;
+ let fill = paint::convert_fill(
+ gc,
+ &t.fill,
+ FillRule::NonZero,
+ true,
+ surface,
+ fc.state(),
+ Size::zero(),
+ )?;
+ let stroke =
+ if let Some(stroke) = t.stroke.as_ref().map(|s| {
+ paint::convert_stroke(gc, s, true, surface, fc.state(), Size::zero())
+ }) {
+ Some(stroke?)
+ } else {
+ None
+ };
+ let text = t.text.as_str();
+ let size = t.size;
+ let glyphs: &[PdfGlyph] = TransparentWrapper::wrap_slice(t.glyphs.as_slice());
+
+ surface.push_transform(&fc.state().transform().to_krilla());
+ surface.set_fill(Some(fill));
+ surface.set_stroke(stroke);
+ surface.draw_glyphs(
+ krilla::geom::Point::from_xy(0.0, 0.0),
+ glyphs,
+ font.clone(),
+ text,
+ size.to_f32(),
+ false,
+ );
+
+ surface.pop();
+
+ Ok(())
+}
+
+fn convert_font(
+ gc: &mut GlobalContext,
+ typst_font: Font,
+) -> SourceResult<krilla::text::Font> {
+ if let Some(font) = gc.fonts_forward.get(&typst_font) {
+ Ok(font.clone())
+ } else {
+ let font = build_font(typst_font.clone())?;
+
+ gc.fonts_forward.insert(typst_font.clone(), font.clone());
+ gc.fonts_backward.insert(font.clone(), typst_font.clone());
+
+ Ok(font)
+ }
+}
+
+#[comemo::memoize]
+fn build_font(typst_font: Font) -> SourceResult<krilla::text::Font> {
+ let font_data: Arc<dyn AsRef<[u8]> + Send + Sync> =
+ Arc::new(typst_font.data().clone());
+
+ match krilla::text::Font::new(font_data.into(), typst_font.index()) {
+ None => {
+ let font_str = display_font(&typst_font);
+ bail!(Span::detached(), "failed to process font {font_str}");
+ }
+ Some(f) => Ok(f),
+ }
+}
+
+#[derive(TransparentWrapper, Debug)]
+#[repr(transparent)]
+struct PdfGlyph(Glyph);
+
+impl krilla::text::Glyph for PdfGlyph {
+ #[inline(always)]
+ fn glyph_id(&self) -> GlyphId {
+ GlyphId::new(self.0.id as u32)
+ }
+
+ #[inline(always)]
+ fn text_range(&self) -> Range<usize> {
+ self.0.range.start as usize..self.0.range.end as usize
+ }
+
+ #[inline(always)]
+ fn x_advance(&self, size: f32) -> f32 {
+ // Don't use `Em::at`, because it contains an expensive check whether the result is finite.
+ self.0.x_advance.get() as f32 * size
+ }
+
+ #[inline(always)]
+ fn x_offset(&self, size: f32) -> f32 {
+ // Don't use `Em::at`, because it contains an expensive check whether the result is finite.
+ self.0.x_offset.get() as f32 * size
+ }
+
+ #[inline(always)]
+ fn y_offset(&self, _: f32) -> f32 {
+ 0.0
+ }
+
+ #[inline(always)]
+ fn y_advance(&self, _: f32) -> f32 {
+ 0.0
+ }
+
+ fn location(&self) -> Option<Location> {
+ Some(self.0.span.0.into_raw().get())
+ }
+}