use crate::CMapResolverFn;
use crate::context::Context;
use crate::device::Device;
use crate::font::glyph_simulator::GlyphSimulator;
use crate::font::true_type::{Width, read_encoding, read_widths};
use crate::font::{
Encoding, Glyph, Type3Glyph, UNITS_PER_EM, glyph_name_to_unicode, normalized_glyph_name,
read_to_unicode,
};
use crate::interpret::state::TextState;
use crate::soft_mask::SoftMask;
use crate::util::RectExt;
use crate::{BlendMode, interpret};
use crate::{CacheKey, ClipPath, GlyphDrawMode, PathDrawMode};
use crate::{Image, Paint};
use kurbo::{Affine, BezPath, Rect};
use pdf_font::cmap::{BfString, CMap};
use pdf_syntax::content::TypedIter;
use pdf_syntax::content::ops::TypedInstruction;
use pdf_syntax::object::Dict;
use pdf_syntax::object::Stream;
use pdf_syntax::object::dict::keys::{CHAR_PROCS, FONT_BBOX, FONT_MATRIX, RESOURCES};
use pdf_syntax::page::Resources;
use skrifa::GlyphId;
use std::collections::HashMap;
#[derive(Debug)]
pub(crate) struct Type3<'a> {
widths: Vec<Width>,
missing_width: f32,
encoding: Encoding,
encodings: HashMap<u8, String>,
dict: Dict<'a>,
char_procs: HashMap<String, Stream<'a>>,
glyph_simulator: GlyphSimulator,
font_bbox: Rect,
matrix: Affine,
to_unicode: Option<CMap>,
}
impl<'a> Type3<'a> {
pub(crate) fn new(dict: &Dict<'a>, cmap_resolver: &CMapResolverFn) -> Option<Self> {
let (encoding, encodings) = read_encoding(dict);
let (widths, missing_width) = read_widths(dict, dict)?;
let font_bbox = dict
.get::<pdf_syntax::object::Rect>(FONT_BBOX)
.unwrap_or(pdf_syntax::object::Rect::ZERO)
.to_kurbo();
let matrix = Affine::new(
dict.get::<[f64; 6]>(FONT_MATRIX)
.unwrap_or([0.001, 0.0, 0.0, 0.001, 0.0, 0.0]),
);
let char_procs = {
let mut procs = HashMap::new();
let dict = dict.get::<Dict<'_>>(CHAR_PROCS).unwrap_or_default();
for name in dict.keys() {
if let Some(prog) = dict.get::<Stream<'_>>(name.clone()) {
procs.insert(name.as_str().to_string(), prog.clone());
}
}
procs
};
let to_unicode = read_to_unicode(dict, cmap_resolver);
Some(Self {
glyph_simulator: GlyphSimulator::new(),
encoding,
font_bbox,
char_procs,
widths,
missing_width,
encodings,
matrix,
dict: dict.clone(),
to_unicode,
})
}
pub(crate) fn map_code(&self, code: u8) -> GlyphId {
self.encodings
.get(&code)
.map(|s| s.as_str())
.or_else(|| self.encoding.map_code(code))
.map(|g| self.glyph_simulator.string_to_glyph(g))
.unwrap_or(GlyphId::NOTDEF)
}
pub(crate) fn glyph_width(&self, code: u8) -> f32 {
let w = match self.widths.get(code as usize).copied() {
Some(Width::Value(w)) => w,
_ => self.missing_width,
};
(w * self.matrix.as_coeffs()[0] as f32) * UNITS_PER_EM
}
pub(crate) fn char_code_to_unicode(&self, char_code: u32) -> Option<BfString> {
if let Some(c) = self
.to_unicode
.as_ref()
.and_then(|t| t.lookup_bf_string(char_code))
&& c != BfString::Char('\0')
{
return Some(c);
}
let code = char_code as u8;
if let Some(name) = self.encodings.get(&code) {
if let Some(ch) = glyph_name_to_unicode(name)
.or_else(|| glyph_name_to_unicode(normalized_glyph_name(name)))
{
return Some(BfString::Char(ch));
}
}
if let Some(name) = self.encoding.map_code(code) {
if let Some(ch) = glyph_name_to_unicode(name)
.or_else(|| glyph_name_to_unicode(normalized_glyph_name(name)))
{
return Some(BfString::Char(ch));
}
}
if let Some(name) = Encoding::Standard.map_code(code)
&& let Some(ch) = glyph_name_to_unicode(name)
{
return Some(BfString::Char(ch));
}
if (0x20..=0x7E).contains(&code) {
return Some(BfString::Char(code as char));
}
None
}
pub(crate) fn render_glyph(
&self,
glyph: &Type3Glyph<'a>,
transform: Affine,
glyph_transform: Affine,
paint: &Paint<'a>,
device: &mut impl Device<'a>,
) -> Option<()> {
let mut state = glyph.state.clone();
let root_transform =
transform * glyph_transform * self.matrix * Affine::scale(UNITS_PER_EM as f64);
state.ctm = root_transform;
state.text_state = TextState::default();
let name = self.glyph_simulator.glyph_to_string(glyph.glyph_id)?;
let program = self.char_procs.get(&name)?;
let decoded = program.decoded().ok()?;
let iter = TypedIter::new(decoded.as_ref());
let is_shape_glyph = {
let iter = iter.clone();
let mut is_shape_glyph = false;
for op in iter {
match op {
TypedInstruction::ShapeGlyph(_) => {
is_shape_glyph = true;
break;
}
TypedInstruction::ColorGlyph(_) => {
break;
}
_ => {}
}
}
is_shape_glyph
};
let mut context = Context::new_with(
state.ctm,
self.font_bbox,
glyph.cache.clone(),
glyph.xref,
glyph.settings.clone(),
state,
);
let mut resources = Resources::from_parent(
self.dict.get(RESOURCES).unwrap_or_default(),
glyph.parent_resources.clone(),
);
if let Some(procs_resources) = program.dict().get::<Dict<'_>>(RESOURCES) {
resources = Resources::from_parent(procs_resources, resources);
}
if is_shape_glyph {
let mut device = Type3ShapeGlyphDevice::new(device, paint.clone());
interpret(iter, &resources, &mut context, &mut device);
} else {
interpret(iter, &resources, &mut context, device);
}
Some(())
}
}
impl CacheKey for Type3<'_> {
fn cache_key(&self) -> u128 {
self.dict.cache_key()
}
}
struct Type3ShapeGlyphDevice<'a, 'b, T: Device<'a>> {
inner: &'b mut T,
paint: Paint<'a>,
}
impl<'a, 'b, T: Device<'a>> Type3ShapeGlyphDevice<'a, 'b, T> {
pub(crate) fn new(device: &'b mut T, paint: Paint<'a>) -> Self {
Self {
inner: device,
paint,
}
}
}
impl<'a, T: Device<'a>> Device<'a> for Type3ShapeGlyphDevice<'a, '_, T> {
fn set_soft_mask(&mut self, _: Option<SoftMask<'_>>) {}
fn draw_path(
&mut self,
path: &BezPath,
transform: Affine,
_: &Paint<'_>,
draw_mode: &PathDrawMode,
) {
self.inner
.draw_path(path, transform, &self.paint, draw_mode);
}
fn push_clip_path(&mut self, clip_path: &ClipPath) {
self.inner.push_clip_path(clip_path);
}
fn push_transparency_group(&mut self, _: f32, _: Option<SoftMask<'_>>, _: BlendMode) {}
fn draw_glyph(
&mut self,
g: &Glyph<'a>,
transform: Affine,
glyph_transform: Affine,
p: &Paint<'a>,
draw_mode: &GlyphDrawMode,
) {
self.inner
.draw_glyph(g, transform, glyph_transform, p, draw_mode);
}
fn pop_clip_path(&mut self) {
self.inner.pop_clip_path();
}
fn pop_transparency_group(&mut self) {}
fn draw_image(&mut self, image: Image<'a, '_>, transform: Affine) {
if let Image::Stencil(mut s) = image {
s.paint = self.paint.clone();
self.inner.draw_image(Image::Stencil(s), transform);
}
}
fn set_blend_mode(&mut self, _: BlendMode) {}
}