use std::{
collections::btree_map::BTreeMap,
vec::Vec,
};
use serde_derive::{Deserialize, Serialize};
use crate::{
FontId,
};
#[cfg(feature = "text_layout")]
pub use azul_layout::{
PdfFontMetrics as FontMetrics, FontParseWarning as PdfFontParseWarning, FontType, OwnedGlyph,
};
#[cfg(feature = "text_layout")]
pub use azul_layout::ParsedFont as AzulParsedFont;
#[cfg(feature = "text_layout")]
pub use self::parsed_font::ParsedFont;
#[cfg(feature = "text_layout")]
mod parsed_font {
use std::{
ops::{Deref, DerefMut},
sync::Arc,
};
use super::{AzulParsedFont, PdfFontParseWarning};
const FONT_B64_START: &str = "data:font/ttf;base64,";
#[derive(Debug, Clone, PartialEq)]
pub struct ParsedFont(AzulParsedFont);
impl ParsedFont {
pub fn from_bytes(
bytes: &[u8],
font_index: usize,
warnings: &mut Vec<PdfFontParseWarning>,
) -> Option<Self> {
let inner = AzulParsedFont::from_bytes(bytes, font_index, warnings)?;
Some(Self::attach_source_bytes(inner, bytes))
}
pub fn from_azul(inner: AzulParsedFont) -> Self {
Self(inner)
}
fn attach_source_bytes(inner: AzulParsedFont, bytes: &[u8]) -> Self {
if inner.source_bytes_for_subset().is_some() {
return Self(inner);
}
Self(inner.with_source_bytes(Arc::new(rust_fontconfig::FontBytes::Owned(
Arc::from(bytes.to_vec()),
))))
}
pub fn source_bytes(&self) -> Option<Arc<rust_fontconfig::FontBytes>> {
self.0.source_bytes_for_subset()
}
pub fn has_source_bytes(&self) -> bool {
self.source_bytes().is_some()
}
pub fn as_azul(&self) -> &AzulParsedFont {
&self.0
}
pub fn into_inner(self) -> AzulParsedFont {
self.0
}
}
impl Deref for ParsedFont {
type Target = AzulParsedFont;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for ParsedFont {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<AzulParsedFont> for ParsedFont {
fn from(inner: AzulParsedFont) -> Self {
Self::from_azul(inner)
}
}
impl serde::Serialize for ParsedFont {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use base64::Engine;
let encoded = match self.source_bytes() {
Some(b) => base64::prelude::BASE64_STANDARD.encode(b.as_slice()),
None => {
let bytes = self.0.to_bytes(None).map_err(|e| {
serde::ser::Error::custom(format!("font has no source bytes: {e}"))
})?;
base64::prelude::BASE64_STANDARD.encode(&bytes)
}
};
let s = format!("{FONT_B64_START}{encoded}");
s.serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for ParsedFont {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use base64::Engine;
let s = String::deserialize(deserializer)?;
let b64 = s.strip_prefix(FONT_B64_START).ok_or_else(|| {
serde::de::Error::custom(format!(
"font must be a {FONT_B64_START}… data URI, got {:.32?}",
s
))
})?;
let bytes = base64::prelude::BASE64_STANDARD
.decode(b64)
.map_err(serde::de::Error::custom)?;
let mut warnings = Vec::new();
ParsedFont::from_bytes(&bytes, 0, &mut warnings).ok_or_else(|| {
serde::de::Error::custom(format!("font deserialization error: {warnings:?}"))
})
}
}
}
#[cfg(not(feature = "text_layout"))]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ParsedFont {
pub original_bytes: Vec<u8>,
pub font_index: u32,
pub font_name: Option<String>,
pub codepoint_to_glyph: BTreeMap<u32, u16>,
pub glyph_widths: BTreeMap<u16, u16>,
pub units_per_em: u16,
pub font_metrics: FontMetrics,
pub font_type: FontType,
pub pdf_font_metrics: PdfFontMetricsStub,
}
#[cfg(not(feature = "text_layout"))]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PdfFontMetricsStub {
pub units_per_em: u16,
pub x_min: i16,
pub y_min: i16,
pub x_max: i16,
pub y_max: i16,
pub caret_slope_rise: i16,
pub caret_slope_run: i16,
pub us_weight_class: u16,
}
#[cfg(not(feature = "text_layout"))]
impl Default for PdfFontMetricsStub {
fn default() -> Self {
Self {
units_per_em: 1000,
x_min: 0,
y_min: -200,
x_max: 1000,
y_max: 800,
caret_slope_rise: 1,
caret_slope_run: 0,
us_weight_class: 0,
}
}
}
#[cfg(not(feature = "text_layout"))]
impl ParsedFont {
pub fn source_bytes(&self) -> Option<Vec<u8>> {
(!self.original_bytes.is_empty()).then(|| self.original_bytes.clone())
}
pub fn has_source_bytes(&self) -> bool {
!self.original_bytes.is_empty()
}
pub fn from_bytes(
bytes: &[u8],
index: usize,
warnings: &mut Vec<PdfFontParseWarning>,
) -> Option<Self> {
use allsorts::{
binary::read::ReadScope,
font_data::FontData,
tables::{cmap::Cmap, FontTableProvider, HeadTable, HheaTable, HmtxTable, MaxpTable},
tag,
};
let mut warn = |msg: &str| {
warnings.push(PdfFontParseWarning {
severity: FontParseSeverity::Warning,
message: msg.to_string(),
})
};
let font_file = ReadScope::new(bytes).read::<FontData<'_>>().ok()?;
let provider = font_file.table_provider(index).ok()?;
let head = provider
.read_table_data(tag::HEAD)
.ok()
.and_then(|d| ReadScope::new(&d).read::<HeadTable>().ok())?;
let maxp = provider
.read_table_data(tag::MAXP)
.ok()
.and_then(|d| ReadScope::new(&d).read::<MaxpTable>().ok())?;
let hhea = provider
.read_table_data(tag::HHEA)
.ok()
.and_then(|d| ReadScope::new(&d).read::<HheaTable>().ok())?;
let mut codepoint_to_glyph = BTreeMap::new();
let cmap_data = provider.read_table_data(tag::CMAP).ok();
match cmap_data
.as_deref()
.and_then(|d| ReadScope::new(d).read::<Cmap<'_>>().ok())
.and_then(|cmap| allsorts::font::read_cmap_subtable(&cmap).ok().flatten())
{
Some((_encoding, subtable)) => {
let _ = subtable.mappings_fn(|cp, gid| {
codepoint_to_glyph.insert(cp, gid);
});
}
None => warn("font has no usable Unicode cmap subtable; text will not map to glyphs"),
}
let mut glyph_widths = BTreeMap::new();
let hmtx_data = provider.read_table_data(tag::HMTX).ok();
match hmtx_data.as_deref().and_then(|d| {
ReadScope::new(d)
.read_dep::<HmtxTable<'_>>((
usize::from(maxp.num_glyphs),
usize::from(hhea.num_h_metrics),
))
.ok()
}) {
Some(hmtx) => {
for gid in 0..maxp.num_glyphs {
if let Ok(advance) = hmtx.horizontal_advance(gid) {
glyph_widths.insert(gid, advance);
}
}
}
None => warn("font has no hmtx table; glyph advances will be zero"),
}
let font_type = if provider.has_table(tag::CFF) {
FontType::OpenTypeCFF(())
} else {
FontType::TrueType
};
let os2_data = provider.read_table_data(tag::OS_2).ok();
let us_weight_class = os2_data
.as_deref()
.and_then(|d| {
ReadScope::new(d)
.read_dep::<allsorts::tables::os2::Os2>(d.len())
.ok()
})
.map(|os2| os2.us_weight_class)
.unwrap_or(0);
Some(ParsedFont {
original_bytes: bytes.to_vec(),
font_index: index as u32,
font_name: None,
codepoint_to_glyph,
glyph_widths,
units_per_em: head.units_per_em,
font_metrics: FontMetrics {
ascent: hhea.ascender,
descent: hhea.descender,
},
font_type,
pdf_font_metrics: PdfFontMetricsStub {
units_per_em: head.units_per_em,
x_min: head.x_min,
y_min: head.y_min,
x_max: head.x_max,
y_max: head.y_max,
caret_slope_rise: hhea.caret_slope_rise,
caret_slope_run: hhea.caret_slope_run,
us_weight_class,
},
})
}
pub fn with_glyph_data(
bytes: Vec<u8>,
index: u32,
font_name: Option<String>,
codepoint_to_glyph: BTreeMap<u32, u16>,
glyph_widths: BTreeMap<u16, u16>,
units_per_em: u16,
font_metrics: FontMetrics,
) -> Self {
ParsedFont {
original_bytes: bytes,
font_index: index,
font_name,
codepoint_to_glyph,
glyph_widths,
units_per_em,
font_metrics,
font_type: FontType::TrueType,
pdf_font_metrics: PdfFontMetricsStub { units_per_em, ..Default::default() },
}
}
pub fn set_codepoint_mapping(&mut self, codepoint: u32, gid: u16) {
self.codepoint_to_glyph.insert(codepoint, gid);
}
pub fn set_glyph_width(&mut self, gid: u16, width: u16) {
self.glyph_widths.insert(gid, width);
}
pub fn get_glyph_width(&self, gid: u16) -> Option<u16> {
self.glyph_widths.get(&gid).copied()
}
pub fn lookup_glyph_index(&self, codepoint: u32) -> Option<u16> {
self.codepoint_to_glyph.get(&codepoint).copied()
}
pub fn get_glyph_primary_char(&self, _gid: u16) -> Option<char> {
None
}
}
#[cfg(not(feature = "text_layout"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FontType {
TrueType,
OpenTypeCFF(()),
}
#[cfg(not(feature = "text_layout"))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum FontParseSeverity {
Info,
Warning,
Error,
}
#[cfg(not(feature = "text_layout"))]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FontParseWarning {
pub severity: FontParseSeverity,
pub message: String,
}
#[cfg(not(feature = "text_layout"))]
pub type PdfFontParseWarning = FontParseWarning;
#[cfg(not(feature = "text_layout"))]
pub type OwnedGlyph = ();
#[cfg(not(feature = "text_layout"))]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FontMetrics {
pub ascent: i16,
pub descent: i16,
}
#[derive(Debug, Clone)]
pub struct SubsetFont {
pub bytes: Vec<u8>,
pub glyph_mapping: BTreeMap<u16, (u16, String)>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PrintpdfFontMeta {
pub original_gid_to_cid: Option<BTreeMap<u16, u16>>,
pub original_to_unicode_map: Option<String>,
pub embedding_mode: FontEmbeddingMode,
pub requires_subsetting: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum FontEmbeddingMode {
Full,
Subset,
Reference,
}
impl Default for PrintpdfFontMeta {
fn default() -> Self {
Self {
original_gid_to_cid: None,
original_to_unicode_map: None,
embedding_mode: FontEmbeddingMode::Subset,
requires_subsetting: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PdfFont {
pub parsed_font: ParsedFont,
pub meta: PrintpdfFontMeta,
}
impl PdfFont {
pub fn new(parsed_font: ParsedFont) -> Self {
Self {
parsed_font,
meta: PrintpdfFontMeta::default(),
}
}
pub fn with_meta(parsed_font: ParsedFont, meta: PrintpdfFontMeta) -> Self {
Self { parsed_font, meta }
}
}
#[derive(Debug, Clone)]
pub enum Font {
BuiltinFont(BuiltinFont),
ExternalFont(ParsedFont, PrintpdfFontMeta),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum BuiltinFont {
TimesRoman,
TimesBold,
TimesItalic,
TimesBoldItalic,
Helvetica,
HelveticaBold,
HelveticaOblique,
HelveticaBoldOblique,
Courier,
CourierOblique,
CourierBold,
CourierBoldOblique,
Symbol,
ZapfDingbats,
}
impl Default for BuiltinFont {
fn default() -> Self {
Self::TimesRoman }
}
include!("../defaultfonts/mapping.rs");
impl BuiltinFont {
pub fn check_if_matches(bytes: &[u8]) -> Option<Self> {
let matching_based_on_len = match_len(bytes)?;
if bytes == matching_based_on_len.get_subset_font().bytes.as_slice() {
Some(matching_based_on_len)
} else {
None
}
}
pub fn get_parsed_font(&self) -> Option<ParsedFont> {
let subset = self.get_subset_font();
ParsedFont::from_bytes(&subset.bytes, 0, &mut Vec::new())
}
pub fn get_svg_font_family(&self) -> &'static str {
match self {
BuiltinFont::TimesRoman => "Times New Roman, Times, serif",
BuiltinFont::TimesBold => "Times New Roman, Times, serif",
BuiltinFont::TimesItalic => "Times New Roman, Times, serif",
BuiltinFont::TimesBoldItalic => "Times New Roman, Times, serif",
BuiltinFont::Helvetica => "Helvetica, Arial, sans-serif",
BuiltinFont::HelveticaBold => "Helvetica, Arial, sans-serif",
BuiltinFont::HelveticaOblique => "Helvetica, Arial, sans-serif",
BuiltinFont::HelveticaBoldOblique => "Helvetica, Arial, sans-serif",
BuiltinFont::Courier => "Courier New, Courier, monospace",
BuiltinFont::CourierOblique => "Courier New, Courier, monospace",
BuiltinFont::CourierBold => "Courier New, Courier, monospace",
BuiltinFont::CourierBoldOblique => "Courier New, Courier, monospace",
BuiltinFont::Symbol => "Symbol",
BuiltinFont::ZapfDingbats => "Zapf Dingbats",
}
}
pub fn get_font_weight(&self) -> &'static str {
match self {
BuiltinFont::TimesRoman
| BuiltinFont::TimesItalic
| BuiltinFont::Helvetica
| BuiltinFont::HelveticaOblique
| BuiltinFont::Courier
| BuiltinFont::CourierOblique
| BuiltinFont::Symbol
| BuiltinFont::ZapfDingbats => "normal",
BuiltinFont::TimesBold
| BuiltinFont::TimesBoldItalic
| BuiltinFont::HelveticaBold
| BuiltinFont::HelveticaBoldOblique
| BuiltinFont::CourierBold
| BuiltinFont::CourierBoldOblique => "bold",
}
}
pub fn get_font_style(&self) -> &'static str {
match self {
BuiltinFont::TimesItalic
| BuiltinFont::TimesBoldItalic
| BuiltinFont::HelveticaOblique
| BuiltinFont::HelveticaBoldOblique
| BuiltinFont::CourierOblique
| BuiltinFont::CourierBoldOblique => "italic",
_ => "normal",
}
}
pub fn get_subset_font(&self) -> SubsetFont {
use self::BuiltinFont::*;
SubsetFont {
bytes: match self {
TimesRoman => crate::utils::uncompress(include_bytes!(
"../defaultfonts/Times-Roman.subset.ttf"
)),
TimesBold => crate::utils::uncompress(include_bytes!(
"../defaultfonts/Times-Bold.subset.ttf"
)),
TimesItalic => crate::utils::uncompress(include_bytes!(
"../defaultfonts/Times-Italic.subset.ttf"
)),
TimesBoldItalic => crate::utils::uncompress(include_bytes!(
"../defaultfonts/Times-BoldItalic.subset.ttf"
)),
Helvetica => {
crate::utils::uncompress(include_bytes!("../defaultfonts/Helvetica.subset.ttf"))
}
HelveticaBold => crate::utils::uncompress(include_bytes!(
"../defaultfonts/Helvetica-Bold.subset.ttf"
)),
HelveticaOblique => crate::utils::uncompress(include_bytes!(
"../defaultfonts/Helvetica-Oblique.subset.ttf"
)),
HelveticaBoldOblique => crate::utils::uncompress(include_bytes!(
"../defaultfonts/Helvetica-BoldOblique.subset.ttf"
)),
Courier => {
crate::utils::uncompress(include_bytes!("../defaultfonts/Courier.subset.ttf"))
}
CourierOblique => crate::utils::uncompress(include_bytes!(
"../defaultfonts/Courier-Oblique.subset.ttf"
)),
CourierBold => crate::utils::uncompress(include_bytes!(
"../defaultfonts/Courier-Bold.subset.ttf"
)),
CourierBoldOblique => crate::utils::uncompress(include_bytes!(
"../defaultfonts/Courier-BoldOblique.subset.ttf"
)),
Symbol => {
crate::utils::uncompress(include_bytes!("../defaultfonts/Symbol.subset.ttf"))
}
ZapfDingbats => crate::utils::uncompress(include_bytes!(
"../defaultfonts/ZapfDingbats.subset.ttf"
)),
},
glyph_mapping: FONTS
.iter()
.filter_map(|(font_id, old_gid, new_gid, char)| {
if *font_id == self.get_num() {
Some((*old_gid, (*new_gid, char.to_string())))
} else {
None
}
})
.collect(),
}
}
pub fn get_pdf_id(&self) -> &'static str {
use self::BuiltinFont::*;
match self {
TimesRoman => "F1",
TimesBold => "F2",
TimesItalic => "F3",
TimesBoldItalic => "F4",
Helvetica => "F5",
HelveticaBold => "F6",
HelveticaOblique => "F7",
HelveticaBoldOblique => "F8",
Courier => "F9",
CourierOblique => "F10",
CourierBold => "F11",
CourierBoldOblique => "F12",
Symbol => "F13",
ZapfDingbats => "F14",
}
}
pub fn get_num(&self) -> usize {
use self::BuiltinFont::*;
match self {
TimesRoman => 0,
TimesBold => 1,
TimesItalic => 2,
TimesBoldItalic => 3,
Helvetica => 4,
HelveticaBold => 5,
HelveticaOblique => 6,
HelveticaBoldOblique => 7,
Courier => 8,
CourierOblique => 9,
CourierBold => 10,
CourierBoldOblique => 11,
Symbol => 12,
ZapfDingbats => 13,
}
}
pub fn from_id(s: &str) -> Option<Self> {
use self::BuiltinFont::*;
match s {
"Times-Roman" | "F1" => Some(TimesRoman),
"Times-Bold" | "F2" => Some(TimesBold),
"Times-Italic" | "F3" => Some(TimesItalic),
"Times-BoldItalic" | "F4" => Some(TimesBoldItalic),
"Helvetica" | "F5" => Some(Helvetica),
"Helvetica-Bold" | "F6" => Some(HelveticaBold),
"Helvetica-Oblique" | "F7" => Some(HelveticaOblique),
"Helvetica-BoldOblique" | "F8" => Some(HelveticaBoldOblique),
"Courier" | "F9" => Some(Courier),
"Courier-Oblique" | "F10" => Some(CourierOblique),
"Courier-Bold" | "F11" => Some(CourierBold),
"Courier-BoldOblique" | "F12" => Some(CourierBoldOblique),
"Symbol" | "F13" => Some(Symbol),
"ZapfDingbats" | "F14" => Some(ZapfDingbats),
_ => None,
}
}
pub fn get_id(&self) -> &'static str {
use self::BuiltinFont::*;
match self {
TimesRoman => "Times-Roman",
TimesBold => "Times-Bold",
TimesItalic => "Times-Italic",
TimesBoldItalic => "Times-BoldItalic",
Helvetica => "Helvetica",
HelveticaBold => "Helvetica-Bold",
HelveticaOblique => "Helvetica-Oblique",
HelveticaBoldOblique => "Helvetica-BoldOblique",
Courier => "Courier",
CourierOblique => "Courier-Oblique",
CourierBold => "Courier-Bold",
CourierBoldOblique => "Courier-BoldOblique",
Symbol => "Symbol",
ZapfDingbats => "ZapfDingbats",
}
}
pub fn all_ids() -> [BuiltinFont; 14] {
use self::BuiltinFont::*;
[
TimesRoman,
TimesBold,
TimesItalic,
TimesBoldItalic,
Helvetica,
HelveticaBold,
HelveticaOblique,
HelveticaBoldOblique,
Courier,
CourierOblique,
CourierBold,
CourierBoldOblique,
Symbol,
ZapfDingbats,
]
}
}
impl Font {
pub fn get_parsed_font(&self) -> Option<&ParsedFont> {
match self {
Font::BuiltinFont(_) => None,
Font::ExternalFont(parsed, _) => Some(parsed),
}
}
pub fn get_parsed_font_mut(&mut self) -> Option<&mut ParsedFont> {
match self {
Font::BuiltinFont(_) => None,
Font::ExternalFont(parsed, _) => Some(parsed),
}
}
pub fn get_font_meta(&self) -> Option<&PrintpdfFontMeta> {
match self {
Font::BuiltinFont(_) => None,
Font::ExternalFont(_, meta) => Some(meta),
}
}
pub fn get_font_meta_mut(&mut self) -> Option<&mut PrintpdfFontMeta> {
match self {
Font::BuiltinFont(_) => None,
Font::ExternalFont(_, meta) => Some(meta),
}
}
}
#[cfg(feature = "text_layout")]
pub fn subset_font(font: &ParsedFont, glyph_ids: &BTreeMap<u16, String>) -> Result<SubsetFont, String> {
use allsorts::{binary::read::ReadScope, font_data::FontData, subset::CmapTarget};
let original_bytes = font
.source_bytes()
.ok_or_else(|| "ParsedFont has no source bytes to subset".to_string())?;
let scope = ReadScope::new(original_bytes.as_slice());
let font_file = scope.read::<FontData<'_>>().map_err(|e| e.to_string())?;
let provider = font_file
.table_provider(font.original_index)
.map_err(|e| e.to_string())?;
let ids: Vec<u16> = std::iter::once(0)
.chain(glyph_ids.keys().copied().filter(|gid| *gid != 0))
.collect();
let bytes = allsorts::subset::subset(
&provider,
&ids,
&allsorts::subset::SubsetProfile::Pdf,
CmapTarget::Unicode,
).map_err(|e| e.to_string())?;
let glyph_mapping: BTreeMap<u16, (u16, String)> = ids
.iter()
.enumerate()
.filter_map(|(idx, &original_gid)| {
glyph_ids.get(&original_gid).map(|ch| {
(original_gid, (idx as u16, ch.clone()))
})
})
.collect();
Ok(SubsetFont {
bytes,
glyph_mapping,
})
}
#[cfg(not(feature = "text_layout"))]
pub fn subset_font(font: &ParsedFont, _glyph_ids: &BTreeMap<u16, String>) -> Result<SubsetFont, String> {
Ok(SubsetFont {
bytes: font.original_bytes.clone(),
glyph_mapping: BTreeMap::new(),
})
}
pub fn generate_cmap_string(_font: &ParsedFont, font_id: &FontId, glyph_ids: &[(u16, String)]) -> String {
let mappings = glyph_ids
.iter()
.map(|(gid, unicode)| {
(*gid as u32, unicode.chars().map(|c| c as u32).collect())
})
.collect();
let cmap = crate::cmap::ToUnicodeCMap { mappings };
cmap.to_cmap_string(&font_id.0)
}
pub fn extract_collection_face(bytes: &[u8], index: usize) -> Option<Vec<u8>> {
use allsorts::{
binary::read::ReadScope, font_data::FontData, subset::whole_font,
tables::FontTableProvider,
};
if bytes.get(..4) != Some(b"ttcf") {
return None;
}
let font_file = ReadScope::new(bytes).read::<FontData<'_>>().ok()?;
let provider = font_file.table_provider(index).ok()?;
let tags = provider.table_tags()?;
whole_font(&provider, &tags).ok()
}
pub fn cff_charset_gid_to_cid_map(font_bytes: &[u8], index: usize) -> Option<BTreeMap<u16, u16>> {
use allsorts::{
binary::read::ReadScope, cff::CFF, font_data::FontData, tables::FontTableProvider, tag,
};
let font_file = ReadScope::new(font_bytes).read::<FontData<'_>>().ok()?;
let provider = font_file.table_provider(index).ok()?;
let cff_data = provider.read_table_data(tag::CFF).ok()?;
let cff = ReadScope::new(&cff_data).read::<CFF<'_>>().ok()?;
let font = cff.fonts.first()?;
if !font.is_cid_keyed() {
return None;
}
let num_glyphs = font.char_strings_index.len() as u16;
Some(
(0..num_glyphs)
.filter_map(|gid| font.charset.id_for_glyph(gid).map(|cid| (gid, cid)))
.collect(),
)
}
#[cfg(feature = "text_layout")]
fn get_glyph_width(font: &ParsedFont, gid: u16) -> Option<u16> {
font.get_or_decode_glyph(gid).map(|g| g.horz_advance)
}
#[cfg(feature = "text_layout")]
pub fn get_normalized_widths_ttf(font: &ParsedFont, glyph_ids: &[(u16, String)]) -> Vec<lopdf::Object> {
let mut widths_list = Vec::new();
let mut current_low_gid = 0;
let mut current_high_gid = 0;
let mut current_width_vec = Vec::new();
let percentage_font_scaling = 1000.0 / (font.pdf_font_metrics.units_per_em as f32);
for (gid, _) in glyph_ids {
let glyph_width = get_glyph_width(font, *gid)
.map(|w| (w as f32 * percentage_font_scaling) as i64)
.unwrap_or(0);
if current_width_vec.is_empty() {
current_low_gid = *gid;
current_high_gid = *gid;
current_width_vec.push(glyph_width);
} else if *gid == current_high_gid + 1 {
current_high_gid = *gid;
current_width_vec.push(glyph_width);
} else {
widths_list.push(lopdf::Object::Integer(current_low_gid as i64));
widths_list.push(lopdf::Object::Array(
current_width_vec.iter().map(|w| lopdf::Object::Integer(*w)).collect(),
));
current_low_gid = *gid;
current_high_gid = *gid;
current_width_vec = vec![glyph_width];
}
}
if !current_width_vec.is_empty() {
widths_list.push(lopdf::Object::Integer(current_low_gid as i64));
widths_list.push(lopdf::Object::Array(
current_width_vec.iter().map(|w| lopdf::Object::Integer(*w)).collect(),
));
}
widths_list
}
#[cfg(feature = "text_layout")]
pub fn get_normalized_widths_codes(
font: &ParsedFont,
entries: &[(u16, u16)],
) -> Vec<lopdf::Object> {
let percentage_font_scaling = 1000.0 / (font.pdf_font_metrics.units_per_em as f32);
let mut widths_list = Vec::new();
let mut current_low_code = 0u16;
let mut current_high_code = 0u16;
let mut current_width_vec: Vec<i64> = Vec::new();
for &(code, gid) in entries {
let glyph_width = get_glyph_width(font, gid)
.map(|w| (w as f32 * percentage_font_scaling) as i64)
.unwrap_or(0);
if current_width_vec.is_empty() {
current_low_code = code;
current_high_code = code;
current_width_vec.push(glyph_width);
} else if code == current_high_code + 1 {
current_high_code = code;
current_width_vec.push(glyph_width);
} else {
widths_list.push(lopdf::Object::Integer(current_low_code as i64));
widths_list.push(lopdf::Object::Array(
current_width_vec.iter().map(|w| lopdf::Object::Integer(*w)).collect(),
));
current_low_code = code;
current_high_code = code;
current_width_vec = vec![glyph_width];
}
}
if !current_width_vec.is_empty() {
widths_list.push(lopdf::Object::Integer(current_low_code as i64));
widths_list.push(lopdf::Object::Array(
current_width_vec.iter().map(|w| lopdf::Object::Integer(*w)).collect(),
));
}
widths_list
}
pub const FONT_B64_START: &str = "data:font/ttf;base64,";
#[cfg(all(test, feature = "text_layout"))]
mod test {
use std::collections::BTreeMap;
use crate::*;
pub const WIN_1252: &[char; 214] = &[
'!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2',
'3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D',
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'{', '|', '}', '~', '€', '‚', 'ƒ', '„', '…', '†', '‡', 'ˆ', '‰', 'Š', '‹', 'Œ', 'Ž', '‘',
'’', '“', '•', '–', '—', '˜', '™', 'š', '›', 'œ', 'ž', 'Ÿ', '¡', '¢', '£', '¤', '¥', '¦',
'§', '¨', '©', 'ª', '«', '¬', '®', '¯', '°', '±', '²', '³', '´', 'µ', '¶', '·', '¸', '¹',
'º', '»', '¼', '½', '¾', '¿', 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë',
'Ì', 'Í', 'Î', 'Ï', 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', '×', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý',
'Þ', 'ß', 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï',
'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', '÷', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ÿ',
];
const FONTS: &[(BuiltinFont, &[u8])] = &[
(
BuiltinFont::Courier,
include_bytes!("../examples/assets/fonts/Courier.ttf"),
),
(
BuiltinFont::CourierOblique,
include_bytes!("../examples/assets/fonts/Courier-Oblique.ttf"),
),
(
BuiltinFont::CourierBold,
include_bytes!("../examples/assets/fonts/Courier-Bold.ttf"),
),
(
BuiltinFont::CourierBoldOblique,
include_bytes!("../examples/assets/fonts/Courier-BoldOblique.ttf"),
),
(
BuiltinFont::Helvetica,
include_bytes!("../examples/assets/fonts/Helvetica.ttf"),
),
(
BuiltinFont::HelveticaBold,
include_bytes!("../examples/assets/fonts/Helvetica-Bold.ttf"),
),
(
BuiltinFont::HelveticaOblique,
include_bytes!("../examples/assets/fonts/Helvetica-Oblique.ttf"),
),
(
BuiltinFont::HelveticaBoldOblique,
include_bytes!("../examples/assets/fonts/Helvetica-BoldOblique.ttf"),
),
(
BuiltinFont::Symbol,
include_bytes!("../examples/assets/fonts/PDFASymbol.woff2"),
),
(
BuiltinFont::TimesRoman,
include_bytes!("../examples/assets/fonts/Times.ttf"),
),
(
BuiltinFont::TimesBold,
include_bytes!("../examples/assets/fonts/Times-Bold.ttf"),
),
(
BuiltinFont::TimesItalic,
include_bytes!("../examples/assets/fonts/Times-Oblique.ttf"),
),
(
BuiltinFont::TimesBoldItalic,
include_bytes!("../examples/assets/fonts/Times-BoldOblique.ttf"),
),
(
BuiltinFont::ZapfDingbats,
include_bytes!("../examples/assets/fonts/ZapfDingbats.ttf"),
),
];
#[test]
#[ignore = "regenerates bundled subset fonts + FONTS table; run manually with --ignored"]
fn subset_test() {
use std::collections::BTreeSet;
let charmap: BTreeSet<char> = WIN_1252.iter().copied().collect();
let mut target_map = vec![];
let mut tm2 = BTreeMap::new();
for (name, bytes) in FONTS {
let mut warnings = Vec::new();
let font = ParsedFont::from_bytes(bytes, 0, &mut warnings).unwrap();
let glyph_ids: Vec<(u16, char)> = charmap.iter()
.filter_map(|&ch| font.lookup_glyph_index(ch as u32).map(|gid| (gid, ch)))
.collect();
let (subset_bytes, glyph_mapping) = font.subset(&glyph_ids, azul_layout::CmapTarget::Unicode).unwrap();
let glyph_mapping = glyph_mapping
.into_iter()
.map(|(k, (g, c))| (k, (g, c.to_string())))
.collect();
let subset = crate::font::SubsetFont { bytes: subset_bytes, glyph_mapping };
tm2.insert(name.clone(), subset.bytes.len());
let _ = std::fs::write(
format!(
"{}/defaultfonts/{}.subset.ttf",
env!("CARGO_MANIFEST_DIR"),
name.get_id()
),
crate::utils::compress(&subset.bytes),
);
for (old_gid, (new_gid, char)) in subset.glyph_mapping.iter() {
let ch = char.chars().next().unwrap_or('\u{FFFD}');
target_map.push(format!(
" ({}, {old_gid}, {new_gid}, '{c}'),",
name.get_num(),
c = if ch == '\'' {
"\\'".to_string()
} else if ch == '\\' {
"\\\\".to_string()
} else {
ch.to_string()
}
));
}
}
let mut tm = vec![format!(
"const FONTS: &[(usize, u16, u16, char);{}] = &[",
target_map.len()
)];
tm.append(&mut target_map);
tm.push("];".to_string());
tm.push("fn match_len(bytes: &[u8]) -> Option<BuiltinFont> {".to_string());
tm.push("match bytes.len() {".to_string());
for (f, b) in tm2.iter() {
tm.push(format!("{b} => Some(BuiltinFont::{f:?}),"));
}
tm.push("_ => None,".to_string());
tm.push("}".to_string());
tm.push("}".to_string());
let _ = std::fs::write(
format!("{}/defaultfonts/mapping.rs", env!("CARGO_MANIFEST_DIR")),
tm.join("\r\n"),
);
}
}