use super::Result;
use super::opc::{OpcWriter, PartName};
use std::io::{Seek, Write};
const FONT_CONTENT_TYPE: &str = "application/x-font-ttf";
pub fn sanitize_font_filename(name: &str) -> String {
name.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || c == '-' || c == '_' {
c
} else {
'_'
}
})
.take(40)
.collect()
}
pub fn write_embedded_fonts<W: Write + Seek>(
opc: &mut OpcWriter<W>,
prefix: &str,
fonts: &[(String, Vec<u8>)],
) -> Result<()> {
debug_assert!(prefix.starts_with('/') && prefix.ends_with('/'));
if !fonts.is_empty() {
opc.register_default_content_type("ttf", FONT_CONTENT_TYPE);
}
for (idx, (name, data)) in fonts.iter().enumerate() {
let n = idx + 1;
let safe_name = sanitize_font_filename(name);
let target = format!("{prefix}font_{n}_{safe_name}.ttf");
let part = PartName::new(&target)?;
opc.add_part(&part, FONT_CONTENT_TYPE, data)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanitize_keeps_alphanumeric() {
assert_eq!(sanitize_font_filename("Calibri"), "Calibri");
assert_eq!(sanitize_font_filename("Arial123"), "Arial123");
}
#[test]
fn sanitize_keeps_dash_and_underscore() {
assert_eq!(sanitize_font_filename("Times-Roman"), "Times-Roman");
assert_eq!(sanitize_font_filename("TeXGyreTermesX-Regular"), "TeXGyreTermesX-Regular");
assert_eq!(sanitize_font_filename("my_font"), "my_font");
}
#[test]
fn sanitize_replaces_path_unsafe_chars() {
assert_eq!(sanitize_font_filename("Arial/Bold"), "Arial_Bold");
assert_eq!(sanitize_font_filename("a*b?c"), "a_b_c");
assert_eq!(sanitize_font_filename("Noto Sans"), "Noto_Sans");
assert_eq!(sanitize_font_filename("a.b"), "a_b");
}
#[test]
fn sanitize_replaces_non_ascii() {
assert_eq!(sanitize_font_filename("Café"), "Caf_");
}
#[test]
fn sanitize_clamps_to_40_chars() {
let long = "A".repeat(100);
let s = sanitize_font_filename(&long);
assert_eq!(s.len(), 40);
assert!(s.chars().all(|c| c == 'A'));
}
#[test]
fn sanitize_empty_input() {
assert_eq!(sanitize_font_filename(""), "");
}
}