use crate::*;
use std::{
cell::{Cell, OnceCell},
collections::BTreeMap,
mem::ManuallyDrop,
rc::Rc,
str::FromStr,
};
use fonts::{EncodedGlyph, GeneralMetrics};
use pdf_writer::{
Chunk, Filter, Name, Str,
types::{CidFontType, FontFlags, SystemInfo, UnicodeCmap},
writers::{FontDescriptor, WMode},
};
use rustybuzz::{Face, Feature, GlyphBuffer, ShapePlan, UnicodeBuffer, shape_with_plan};
use subsetter::GlyphRemapper;
use ttf_parser::GlyphId;
use super::{Font, ShapedGlyph};
pub struct TruetypeFont {
index: usize,
name: Vec<u8>,
face: Face<'static>,
plan: ShapePlan,
plan_no_ligatures: OnceCell<ShapePlan>,
fallback_fonts: Option<Rc<[TruetypeFont]>>,
}
impl TruetypeFont {
pub fn new(pdf: &mut Pdf, bytes: &'static [u8]) -> Self {
let face = Face::from_slice(bytes, 0).unwrap();
let id = pdf.alloc();
let idx = pdf.fonts.len();
pdf.fonts.push(id);
let resource_name = format!("F{}", idx);
let index = pdf.truetype_fonts.len();
pdf.truetype_fonts.push(TruetypeFontState {
glyph_remapper: GlyphRemapper::new(),
face: face.clone(),
data: bytes,
id,
glyph_set: BTreeMap::new(),
});
let plan = ShapePlan::new(
&face,
rustybuzz::Direction::LeftToRight,
Some(rustybuzz::script::LATIN),
None,
&[],
);
TruetypeFont {
index,
name: resource_name.into_bytes(),
face,
plan,
plan_no_ligatures: OnceCell::new(),
fallback_fonts: None,
}
}
pub fn with_fallback_fonts(self, fallback_fonts: Rc<[TruetypeFont]>) -> Self {
TruetypeFont {
fallback_fonts: Some(fallback_fonts),
..self
}
}
fn plan_no_ligatures(&self) -> &ShapePlan {
self.plan_no_ligatures.get_or_init(|| {
ShapePlan::new(
&self.face,
rustybuzz::Direction::LeftToRight,
Some(rustybuzz::script::LATIN),
None,
&[
Feature::from_str("liga=0").unwrap(),
Feature::from_str("clig=0").unwrap(),
],
)
})
}
}
thread_local! {
static UNICODE_BUFFER: Cell<UnicodeBuffer> = Cell::new(UnicodeBuffer::new());
}
impl Font for TruetypeFont {
type Shaped<'b>
= Shaped<'b>
where
Self: 'b;
fn shape<'b>(
&'b self,
text: &'b str,
character_spacing: f32,
word_spacing: f32,
) -> Self::Shaped<'b> {
let mut buffer = UNICODE_BUFFER.take();
buffer.set_not_found_variation_selector_glyph(0);
buffer.set_cluster_level(rustybuzz::BufferClusterLevel::MonotoneCharacters);
buffer.push_str(text);
buffer.set_script(rustybuzz::script::LATIN);
buffer.set_direction(rustybuzz::Direction::LeftToRight);
let shaped = shape_with_plan(
&self.face,
if character_spacing == 0. {
&self.plan
} else {
self.plan_no_ligatures()
},
buffer,
);
Shaped {
text,
character_spacing,
word_spacing,
face: &self.face,
buffer: Rc::new(Buffer(ManuallyDrop::new(shaped))),
i: 0,
}
}
fn encode(&self, pdf: &mut Pdf, glyph_id: u32, text: &str) -> EncodedGlyph {
let cid = pdf.truetype_fonts[self.index]
.glyph_remapper
.remap(glyph_id as u16);
pdf.truetype_fonts[self.index]
.glyph_set
.entry(glyph_id as u16)
.or_insert_with(|| text.to_string());
EncodedGlyph::TwoBytes(cid.to_be_bytes())
}
fn index(&self) -> usize {
self.index
}
fn resource_name(&self) -> Name<'_> {
Name(&self.name)
}
fn general_metrics(&self) -> GeneralMetrics {
let units_per_em = self.face.units_per_em() as f32;
GeneralMetrics {
height_above_baseline: self.face.ascender() as f32 / units_per_em,
height_below_baseline: (self.face.descender().abs() + self.face.line_gap()) as f32
/ units_per_em,
}
}
fn fallback_fonts(&self) -> &[Self] {
self.fallback_fonts.as_deref().unwrap_or(&[])
}
}
struct Buffer(ManuallyDrop<GlyphBuffer>);
impl Drop for Buffer {
fn drop(&mut self) {
let unicode_buffer = unsafe { ManuallyDrop::take(&mut self.0) }.clear();
UNICODE_BUFFER.set(unicode_buffer);
}
}
#[derive(Clone)]
pub struct Shaped<'a> {
text: &'a str,
face: &'a Face<'static>,
buffer: Rc<Buffer>,
i: usize,
character_spacing: f32,
word_spacing: f32,
}
impl<'a> Iterator for Shaped<'a> {
type Item = ShapedGlyph;
fn next(&mut self) -> Option<Self::Item> {
if self.i >= self.buffer.0.len() {
return None;
}
let infos = self.buffer.0.glyph_infos();
let info = infos[self.i];
let position = self.buffer.0.glyph_positions()[self.i];
let start = info.cluster as usize;
let mut e = self.i.checked_add(1);
loop {
if let Some(index) = e {
if let Some(end_info) = infos.get(index) {
if end_info.cluster == info.cluster {
e = index.checked_add(1);
continue;
}
}
}
break;
}
let end = e
.and_then(|last| infos.get(last))
.map_or(self.text.len(), |info| info.cluster as usize);
self.i += 1;
let text_range = start..(end as usize);
let units_per_em = self.face.units_per_em() as f32;
let mut x_advance = position.x_advance as f32 / units_per_em;
if matches!(&self.text[text_range.clone()], " " | "\u{00A0}" | " ") {
x_advance += self.word_spacing;
}
if self.character_spacing != 0.
&& self
.buffer
.0
.glyph_infos()
.get(self.i + 1)
.map_or(true, |next| next.cluster != info.cluster)
{
x_advance += self.character_spacing;
}
let x_advance_font = self
.face
.glyph_hor_advance(GlyphId(info.glyph_id as u16))
.unwrap() as f32
/ units_per_em;
Some(ShapedGlyph {
unsafe_to_break: info.unsafe_to_break(),
glyph_id: info.glyph_id,
text_range,
x_advance_font,
x_advance,
x_offset: position.x_offset as f32 / units_per_em,
y_offset: position.y_offset as f32 / units_per_em,
y_advance: position.y_advance as f32 / units_per_em,
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.buffer.0.len() - self.i;
(len, Some(len))
}
}
pub(crate) struct TruetypeFontState {
glyph_remapper: GlyphRemapper,
face: Face<'static>,
data: &'static [u8],
id: Ref,
glyph_set: BTreeMap<u16, String>,
}
impl TruetypeFontState {
pub(crate) fn finish(&mut self, pdf: &mut pdf_writer::Pdf, alloc: &mut Ref) {
let type0_ref = self.id;
let cid_ref = alloc.bump();
let descriptor_ref = alloc.bump();
let cmap_ref = alloc.bump();
let data_ref = alloc.bump();
let name = self.face.names().get(1).and_then(|n| n.to_string());
let name = name.as_deref().unwrap_or("Unknown Font");
pdf.type0_font(type0_ref)
.base_font(Name(name.as_bytes()))
.encoding_predefined(Name(b"Identity-H")) .descendant_font(cid_ref)
.to_unicode(cmap_ref);
let mut cid = pdf.cid_font(cid_ref);
cid.cid_to_gid_map_predefined(Name(b"Identity"));
cid.subtype(CidFontType::Type2);
cid.base_font(Name(name.as_bytes()));
cid.system_info(SystemInfo {
registry: Str(b"Adobe"), ordering: Str(b"Identity"),
supplement: 0,
});
cid.font_descriptor(descriptor_ref);
cid.default_width(0.0);
let units_per_em = self.face.units_per_em() as f32;
let widths = self
.glyph_remapper
.remapped_gids()
.map(|gid| {
let width = self.face.glyph_hor_advance(GlyphId(gid)).unwrap_or(0);
(width as f32 / units_per_em * 1000.) as f32
})
.collect::<Vec<_>>();
let mut first = 0;
let mut width_writer = cid.widths();
for group in widths.chunk_by(|&a, &b| a == b) {
let w = group[0];
let end = first + group.len();
if w != 0.0 {
let last = end - 1;
width_writer.same(first as u16, last as u16, w);
}
first = end;
}
drop(width_writer);
drop(cid);
let cmap = create_cmap(&self.glyph_set, &self.glyph_remapper);
pdf.cmap(cmap_ref, &cmap)
.writing_mode(WMode::Horizontal)
.filter(Filter::FlateDecode);
let subset = subset_font(&self.data, &self.glyph_remapper).unwrap();
let mut stream = pdf.stream(data_ref, &subset);
stream.filter(Filter::FlateDecode);
drop(stream);
let mut font_descriptor = write_font_descriptor(pdf, descriptor_ref, &self.face, name);
font_descriptor.font_file2(data_ref);
drop(font_descriptor);
}
}
fn create_cmap(glyph_set: &BTreeMap<u16, String>, glyph_remapper: &GlyphRemapper) -> Vec<u8> {
let mut cmap = UnicodeCmap::new(
Name(b"Custom"),
SystemInfo {
registry: Str(b"Adobe"), ordering: Str(b"Identity"),
supplement: 0,
},
);
for (&g, text) in glyph_set.iter() {
let cid = glyph_remapper.get(g).unwrap();
if !text.is_empty() {
cmap.pair_with_multiple(cid, text.chars());
}
}
deflate(&cmap.finish())
}
fn subset_font(font: &[u8], glyph_remapper: &GlyphRemapper) -> Result<Vec<u8>, subsetter::Error> {
let subset = subsetter::subset(font, 0, glyph_remapper)?;
let data = subset.as_ref();
Ok(deflate(data))
}
fn deflate(data: &[u8]) -> Vec<u8> {
miniz_oxide::deflate::compress_to_vec_zlib(data, 9)
}
pub fn write_font_descriptor<'a>(
pdf: &'a mut Chunk,
descriptor_ref: Ref,
font: &'a Face,
base_font: &str,
) -> FontDescriptor<'a> {
let ttf = font;
let serif = false;
let mut flags = FontFlags::empty();
flags.set(FontFlags::SERIF, serif);
flags.set(FontFlags::FIXED_PITCH, ttf.is_monospaced());
flags.set(FontFlags::ITALIC, ttf.is_italic());
flags.insert(FontFlags::SYMBOLIC);
flags.insert(FontFlags::SMALL_CAP);
let units_per_em = ttf.units_per_em() as f32;
let global_bbox = ttf.global_bounding_box();
let bbox = pdf_writer::Rect::new(
f32::from(global_bbox.x_min) / units_per_em * 1000.,
f32::from(global_bbox.y_min) / units_per_em * 1000.,
f32::from(global_bbox.x_max) / units_per_em * 1000.,
f32::from(global_bbox.y_max) / units_per_em * 1000.,
);
let italic_angle = ttf.italic_angle();
let ascender =
f32::from(ttf.typographic_ascender().unwrap_or(ttf.ascender())) / units_per_em * 1000.;
let descender =
f32::from(ttf.typographic_descender().unwrap_or(ttf.descender())) / units_per_em * 1000.;
let cap_height = ttf
.capital_height()
.filter(|&h| h > 0)
.map_or(ascender, |h| f32::from(h) / units_per_em * 1000.);
let stem_v = 10.0 + 0.244 * (f32::from(ttf.weight().to_number()) - 50.0);
let mut font_descriptor = pdf.font_descriptor(descriptor_ref);
font_descriptor
.name(Name(base_font.as_bytes()))
.flags(flags)
.bbox(bbox)
.italic_angle(italic_angle)
.ascent(ascender)
.descent(descender)
.cap_height(cap_height)
.stem_v(stem_v);
font_descriptor
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
const FONT: &[u8] = include_bytes!("../../assets/fonts/Kenney Bold.ttf");
let mut pdf = Pdf::new(Metadata::fixed());
let font = TruetypeFont::new(&mut pdf, &FONT);
let text = "Rewriting software in\nRust.";
let shaped = font.shape(text, 0., 0.);
let shaped = shaped.clone();
let shaped_vec: Vec<_> = shaped.collect();
insta::assert_debug_snapshot!(shaped_vec);
}
#[test]
fn test_trailing_space() {
const FONT: &[u8] = include_bytes!("../../assets/fonts/Kenney Bold.ttf");
let mut pdf = Pdf::new(Metadata::fixed());
let font = TruetypeFont::new(&mut pdf, &FONT);
let text = "Rewriting ";
let shaped = font.shape(text, 0., 0.);
let shaped = shaped.clone();
let shaped_vec: Vec<_> = shaped.collect();
insta::assert_debug_snapshot!(shaped_vec);
}
}