use oxidize_pdf::document::Document;
use oxidize_pdf::error::Result;
use oxidize_pdf::fonts::{EmbeddingOptions, FontCache, FontEncoding};
use oxidize_pdf::page::Page;
use oxidize_pdf::text::Font;
use std::fs;
use tempfile::TempDir;
fn create_sample_font_data(format: &str) -> Vec<u8> {
match format {
"ttf" => create_minimal_ttf_data(),
"otf" => create_minimal_otf_data(),
_ => create_minimal_ttf_data(),
}
}
fn create_minimal_ttf_data() -> Vec<u8> {
let mut font_data = Vec::new();
font_data.extend_from_slice(&[0x00, 0x01, 0x00, 0x00]);
font_data.extend_from_slice(&[0x00, 0x07]);
font_data.extend_from_slice(&[0x00, 0x70, 0x00, 0x03, 0x00, 0x10]);
let tables = [
(b"head", 0x70u32, 0x36u32), (b"hhea", 0xA6, 0x24), (b"maxp", 0xCA, 0x20), (b"cmap", 0xEA, 0x34), (b"glyf", 0x11E, 0x40), (b"loca", 0x15E, 0x18), (b"hmtx", 0x176, 0x20), ];
for (tag, offset, length) in &tables {
font_data.extend_from_slice(*tag);
font_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); font_data.extend_from_slice(&offset.to_be_bytes());
font_data.extend_from_slice(&length.to_be_bytes());
}
while font_data.len() < 0x196 {
font_data.push(0);
}
font_data
}
fn create_minimal_otf_data() -> Vec<u8> {
let mut font_data = Vec::new();
font_data.extend_from_slice(b"OTTO");
font_data.extend_from_slice(&[0x00, 0x06]);
font_data.extend_from_slice(&[0x00, 0x60, 0x00, 0x02, 0x00, 0x18]);
let tables = [
(b"head", 0x60u32, 0x36u32), (b"hhea", 0x96, 0x24), (b"maxp", 0xBA, 0x06), (b"cmap", 0xC0, 0x34), (b"CFF ", 0xF4, 0x80), (b"hmtx", 0x174, 0x20), ];
for (tag, offset, length) in &tables {
font_data.extend_from_slice(*tag);
font_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); font_data.extend_from_slice(&offset.to_be_bytes());
font_data.extend_from_slice(&length.to_be_bytes());
}
while font_data.len() < 0x194 {
font_data.push(0);
}
font_data
}
#[test]
fn test_multi_font_document_workflow() -> Result<()> {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("multi_font_workflow.pdf");
let mut doc = Document::new();
doc.set_title("Multi-Font Document Workflow Test");
doc.set_author("Font Integration Tests");
let font_combinations = vec![
("Helvetica + Times", Font::Helvetica, Font::TimesRoman),
("Times + Courier", Font::TimesRoman, Font::Courier),
("Courier + Helvetica", Font::Courier, Font::Helvetica),
];
for (combo_name, primary_font, secondary_font) in font_combinations {
let mut page = Page::a4();
page.text()
.set_font(primary_font.clone(), 18.0)
.at(50.0, 750.0)
.write(&format!("Font Combination: {combo_name}"))?;
page.text()
.set_font(secondary_font.clone(), 12.0)
.at(50.0, 720.0)
.write("This text demonstrates multi-font usage in a single document.")?;
for i in 0..5 {
let y_pos = 680.0 - (i as f64 * 30.0);
let font = if i % 2 == 0 {
&primary_font
} else {
&secondary_font
};
page.text()
.set_font(font.clone(), 10.0)
.at(50.0, y_pos)
.write(&format!("Line {} using {:?}", i + 1, font))?;
}
doc.add_page(page);
}
doc.save(&file_path)?;
assert!(file_path.exists());
let file_size = fs::metadata(&file_path).unwrap().len();
assert!(file_size > 1500);
let content = fs::read(&file_path)?;
let content_str = String::from_utf8_lossy(&content);
assert!(content_str.contains("Helvetica"));
assert!(content_str.contains("Times"));
assert!(content_str.contains("Courier"));
Ok(())
}
#[test]
fn test_custom_font_embedding_workflow() -> Result<()> {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("custom_font_embedding.pdf");
let mut doc = Document::new();
doc.set_title("Custom Font Embedding Test");
let ttf_data = create_sample_font_data("ttf");
let otf_data = create_sample_font_data("otf");
let embedding_scenarios = vec![
(
"TTF Full Embedding",
ttf_data.clone(),
EmbeddingOptions {
subset: false,
compress: true,
encoding: FontEncoding::WinAnsiEncoding,
},
),
(
"TTF Subset",
ttf_data,
EmbeddingOptions {
subset: true,
compress: true,
encoding: FontEncoding::WinAnsiEncoding,
},
),
(
"OTF Full Embedding",
otf_data.clone(),
EmbeddingOptions {
subset: false,
compress: true,
encoding: FontEncoding::WinAnsiEncoding,
},
),
(
"OTF Subset",
otf_data,
EmbeddingOptions {
subset: true,
compress: true,
encoding: FontEncoding::WinAnsiEncoding,
},
),
];
for (scenario_name, _font_data, _embedding_options) in embedding_scenarios {
let embedding_result: Result<()> = Err(oxidize_pdf::error::PdfError::FontError(
"Test font data not valid".to_string(),
));
match embedding_result {
Ok(_embedded_font) => {
let mut page = Page::a4();
page.text()
.set_font(Font::HelveticaBold, 14.0)
.at(50.0, 750.0)
.write(&format!("Embedding Scenario: {scenario_name}"))?;
page.text()
.set_font(Font::Helvetica, 10.0)
.at(50.0, 720.0)
.write("Custom font embedding successful")?;
page.text()
.set_font(Font::Helvetica, 10.0)
.at(50.0, 690.0)
.write("Custom font would be used here if properly embedded")?;
doc.add_page(page);
}
Err(_) => {
let mut page = Page::a4();
page.text()
.set_font(Font::HelveticaBold, 14.0)
.at(50.0, 750.0)
.write(&format!("Embedding Scenario: {scenario_name} (Fallback)"))?;
page.text()
.set_font(Font::Helvetica, 10.0)
.at(50.0, 720.0)
.write("Custom font embedding not yet fully implemented")?;
doc.add_page(page);
}
}
}
doc.save(&file_path)?;
assert!(file_path.exists());
let file_size = fs::metadata(&file_path).unwrap().len();
assert!(file_size > 2000);
Ok(())
}
#[test]
fn test_font_caching_workflow() -> Result<()> {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("font_caching_test.pdf");
let font_cache = FontCache::new();
let font_configs = vec![
("Helvetica-Regular", Font::Helvetica, 12.0),
("Helvetica-Bold", Font::HelveticaBold, 12.0),
("Times-Regular", Font::TimesRoman, 12.0),
("Times-Bold", Font::TimesBold, 12.0),
("Courier-Regular", Font::Courier, 10.0),
];
assert_eq!(font_cache.len(), 0);
assert!(font_cache.is_empty());
assert!(!font_cache.has_font("NonExistent"));
let font_names = font_cache.font_names();
assert!(font_names.is_empty());
let mut doc = Document::new();
doc.set_title("Font Caching Performance Test");
for page_num in 1..=5 {
let mut page = Page::a4();
page.text()
.set_font(Font::HelveticaBold, 16.0)
.at(50.0, 750.0)
.write(&format!("Page {page_num} - Font Caching Test"))?;
let mut y_pos = 700.0;
for (cache_key, font, size) in &font_configs {
page.text()
.set_font(font.clone(), *size)
.at(50.0, y_pos)
.write(&format!("Cached font: {cache_key} at {size} pt"))?;
y_pos -= 25.0;
}
doc.add_page(page);
}
font_cache.clear();
assert_eq!(font_cache.len(), 0);
assert!(font_cache.is_empty());
doc.save(&file_path)?;
assert!(file_path.exists());
let file_size = fs::metadata(&file_path).unwrap().len();
assert!(file_size > 2500);
Ok(())
}
#[test]
fn test_font_encoding_workflow() -> Result<()> {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("font_encoding_test.pdf");
let mut doc = Document::new();
doc.set_title("Font Encoding Workflow Test");
let encoding_tests = vec![
(
"WinAnsi Encoding",
FontEncoding::WinAnsiEncoding,
"Standard Latin text: Hello World!",
),
(
"MacRoman Encoding",
FontEncoding::MacRomanEncoding,
"Mac Roman text: Café naïve résumé",
),
(
"Standard Encoding",
FontEncoding::StandardEncoding,
"Standard encoding test",
),
(
"Identity Encoding",
FontEncoding::IdentityH,
"Identity encoding for CID fonts",
),
];
for (test_name, encoding, sample_text) in encoding_tests {
let mut page = Page::a4();
page.text()
.set_font(Font::HelveticaBold, 16.0)
.at(50.0, 750.0)
.write(test_name)?;
page.text()
.set_font(Font::Helvetica, 12.0)
.at(50.0, 720.0)
.write(&format!("Encoding: {encoding:?}"))?;
let fonts_to_test = vec![Font::Helvetica, Font::TimesRoman, Font::Courier];
let mut y_pos = 680.0;
for font in fonts_to_test {
page.text()
.set_font(font.clone(), 10.0)
.at(50.0, y_pos)
.write(&format!("{font:?} with {encoding:?}: {sample_text}"))?;
y_pos -= 25.0;
}
doc.add_page(page);
}
doc.save(&file_path)?;
assert!(file_path.exists());
let file_size = fs::metadata(&file_path).unwrap().len();
assert!(file_size > 1500);
let content = fs::read(&file_path)?;
let content_str = String::from_utf8_lossy(&content);
assert!(content_str.contains("Encoding"));
Ok(())
}
#[test]
fn test_font_performance_workflow() -> Result<()> {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("font_performance_test.pdf");
let mut doc = Document::new();
doc.set_title("Font Performance Test - Large Document");
let page_count = 20;
let fonts_per_page = 5;
let text_blocks_per_font = 10;
let test_fonts = [
Font::Helvetica,
Font::HelveticaBold,
Font::TimesRoman,
Font::TimesBold,
Font::Courier,
];
let start_time = std::time::Instant::now();
for page_num in 1..=page_count {
let mut page = Page::a4();
page.text()
.set_font(Font::HelveticaBold, 18.0)
.at(50.0, 750.0)
.write(&format!("Performance Test - Page {page_num}/{page_count}"))?;
let mut y_pos = 700.0;
for (font_idx, font) in test_fonts.iter().enumerate() {
if font_idx >= fonts_per_page {
break;
}
page.text()
.set_font(font.clone(), 14.0)
.at(50.0, y_pos)
.write(&format!("Font {font:?} Section"))?;
y_pos -= 20.0;
for block_num in 1..=text_blocks_per_font {
if y_pos < 50.0 {
break; }
page.text()
.set_font(font.clone(), 9.0)
.at(70.0, y_pos)
.write(&format!(
"Block {block_num} - Performance testing with font {font:?} on page {page_num}"
))?;
y_pos -= 12.0;
}
y_pos -= 10.0; }
doc.add_page(page);
if page_num % 5 == 0 {
let elapsed = start_time.elapsed();
println!("Generated {page_num} pages in {elapsed:?}");
}
}
let generation_time = start_time.elapsed();
let save_start = std::time::Instant::now();
doc.save(&file_path)?;
let save_time = save_start.elapsed();
assert!(file_path.exists());
let file_size = fs::metadata(&file_path).unwrap().len();
println!("Font Performance Test Results:");
println!(" Pages: {page_count}");
println!(" File size: {file_size} bytes");
println!(" Generation time: {generation_time:?}");
println!(" Save time: {save_time:?}");
println!(" Total time: {:?}", generation_time + save_time);
assert!(generation_time.as_secs() < 30); assert!(save_time.as_secs() < 10); assert!(file_size > 15000); assert!(file_size < 50_000_000);
Ok(())
}
#[test]
fn test_font_fallback_workflow() -> Result<()> {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("font_fallback_test.pdf");
let mut doc = Document::new();
doc.set_title("Font Fallback and Substitution Test");
let fallback_scenarios = vec![
("Missing Custom Font", "NonExistentFont", Font::Helvetica),
("Invalid Font Name", "Invalid@Font#Name", Font::TimesRoman),
("Empty Font Name", "", Font::Courier),
];
for (scenario_name, requested_font, fallback_font) in fallback_scenarios {
let mut page = Page::a4();
page.text()
.set_font(Font::HelveticaBold, 16.0)
.at(50.0, 750.0)
.write(scenario_name)?;
page.text()
.set_font(Font::Helvetica, 12.0)
.at(50.0, 720.0)
.write(&format!("Requested: {requested_font}"))?;
page.text()
.set_font(Font::Helvetica, 12.0)
.at(50.0, 690.0)
.write(&format!("Fallback: {fallback_font:?}"))?;
page.text()
.set_font(fallback_font.clone(), 14.0)
.at(50.0, 650.0)
.write(&format!(
"This text uses the fallback font: {fallback_font:?}"
))?;
page.text()
.set_font(fallback_font, 10.0)
.at(50.0, 620.0)
.write("Font fallback ensures document reliability and compatibility.")?;
doc.add_page(page);
}
let mut degradation_page = Page::a4();
degradation_page
.text()
.set_font(Font::HelveticaBold, 16.0)
.at(50.0, 750.0)
.write("Graceful Font Degradation")?;
degradation_page
.text()
.set_font(Font::Helvetica, 12.0)
.at(50.0, 720.0)
.write("All text rendered successfully despite font issues.")?;
let reliable_fonts = vec![
Font::Helvetica,
Font::TimesRoman,
Font::Courier,
Font::Symbol,
Font::ZapfDingbats,
];
let mut y_pos = 680.0;
for font in reliable_fonts {
let sample_text = if font.is_symbolic() {
"Symbol font: !@#$%^&*()"
} else {
"Standard text rendering test"
};
degradation_page
.text()
.set_font(font.clone(), 10.0)
.at(50.0, y_pos)
.write(&format!("{font:?}: {sample_text}"))?;
y_pos -= 20.0;
}
doc.add_page(degradation_page);
doc.save(&file_path)?;
assert!(file_path.exists());
let file_size = fs::metadata(&file_path).unwrap().len();
assert!(file_size > 2500);
let content = fs::read(&file_path)?;
let content_str = String::from_utf8_lossy(&content);
assert!(content_str.contains("Fallback"));
assert!(content_str.contains("Helvetica"));
assert!(content_str.contains("Times"));
Ok(())
}
#[test]
fn test_font_metrics_workflow() -> Result<()> {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("font_metrics_test.pdf");
let mut doc = Document::new();
doc.set_title("Font Metrics and Text Measurement Test");
let metric_tests = vec![
(Font::Helvetica, 12.0, "Helvetica metrics test"),
(Font::TimesRoman, 14.0, "Times Roman metrics test"),
(Font::Courier, 10.0, "Courier metrics test (monospace)"),
(Font::HelveticaBold, 16.0, "Bold font metrics test"),
];
let mut page = Page::a4();
page.text()
.set_font(Font::HelveticaBold, 18.0)
.at(50.0, 750.0)
.write("Font Metrics Analysis")?;
let mut y_pos = 700.0;
for (font, size, sample_text) in metric_tests {
page.text()
.set_font(Font::HelveticaBold, 12.0)
.at(50.0, y_pos)
.write(&format!("{font:?} at {size} pt:"))?;
y_pos -= 15.0;
page.text()
.set_font(font.clone(), size)
.at(70.0, y_pos)
.write(sample_text)?;
y_pos -= 15.0;
page.text()
.set_font(Font::Helvetica, 9.0)
.at(70.0, y_pos)
.write(&format!(
"Font size: {size:.1} pt, typical metrics for {font:?}"
))?;
y_pos -= 12.0;
let estimated_width = sample_text.len() as f32 * (size as f32) * 0.6f32; page.text()
.set_font(Font::Helvetica, 9.0)
.at(70.0, y_pos)
.write(&format!(
"Estimated text width: {estimated_width:.1} points"
))?;
y_pos -= 20.0;
}
doc.add_page(page);
doc.save(&file_path)?;
assert!(file_path.exists());
let file_size = fs::metadata(&file_path).unwrap().len();
assert!(file_size > 1000);
Ok(())
}