hayro 0.6.0

A rasterizer for PDF files.
Documentation
//! This example shows you how you can render a PDF file to PNG.

use hayro::hayro_interpret::InterpreterSettings;
use hayro::hayro_interpret::font::{FontData, FontQuery, StandardFont};
use hayro::hayro_interpret::hayro_cmap::CidFamily;
use hayro::hayro_syntax::Pdf;
use hayro::{RenderCache, RenderSettings, render};
use std::path::Path;
use std::sync::Arc;
use vello_cpu::color::palette::css::WHITE;

fn load_asset(name: &str) -> Option<(FontData, u32)> {
    let base = Path::new(env!("CARGO_MANIFEST_DIR")).join("../hayro-tests/assets");
    let path = base.join(name);
    let data = std::fs::read(&path).ok()?;
    Some((Arc::new(data), 0))
}

fn main() {
    if let Ok(()) = log::set_logger(&LOGGER) {
        log::set_max_level(log::LevelFilter::Trace);
    }

    let file = std::fs::read(std::env::args().nth(1).unwrap()).unwrap();
    let output_dir = std::env::args().nth(2).unwrap_or_else(|| ".".to_string());
    let scale = std::env::args()
        .nth(3)
        .and_then(|s| s.parse::<f32>().ok())
        .unwrap_or(1.0);

    // Create output directory if it doesn't exist
    std::fs::create_dir_all(&output_dir).unwrap();

    let pdf = Pdf::new(file).unwrap();

    let interpreter_settings = InterpreterSettings {
        font_resolver: Arc::new(move |query| match query {
            FontQuery::Standard(s) => {
                let name = pick_standard_font(s);
                load_asset(name).or_else(|| Some(s.get_font_data()))
            }
            FontQuery::Fallback(f) => {
                if let Some(cc) = &f.character_collection {
                    let name = match cc.family {
                        CidFamily::AdobeGB1 | CidFamily::AdobeCNS1 => {
                            if f.is_bold {
                                "NotoSansCJKsc-Bold.otf"
                            } else {
                                "NotoSansCJKsc-Regular.otf"
                            }
                        }
                        CidFamily::AdobeJapan1 => {
                            if f.is_bold {
                                "NotoSansCJKjp-Bold.otf"
                            } else {
                                "NotoSansCJKjp-Regular.otf"
                            }
                        }
                        CidFamily::AdobeKorea1 => {
                            if f.is_bold {
                                "NotoSansCJKkr-Bold.otf"
                            } else {
                                "NotoSansCJKkr-Regular.otf"
                            }
                        }
                        _ => {
                            let name = pick_standard_font(&f.pick_standard_font());
                            return load_asset(name)
                                .or_else(|| Some(f.pick_standard_font().get_font_data()));
                        }
                    };

                    if let Some(data) = load_asset(name) {
                        return Some(data);
                    }
                }

                let name = pick_standard_font(&f.pick_standard_font());
                load_asset(name).or_else(|| Some(f.pick_standard_font().get_font_data()))
            }
        }),
        ..Default::default()
    };

    let render_settings = RenderSettings {
        x_scale: scale,
        y_scale: scale,
        bg_color: WHITE,
        ..Default::default()
    };
    let cache = RenderCache::new();

    for (idx, page) in pdf.pages().iter().enumerate() {
        let pixmap = render(page, &cache, &interpreter_settings, &render_settings);
        let output_path = format!("{}/rendered_{idx}.png", output_dir);
        std::fs::write(output_path, pixmap.into_png().unwrap()).unwrap();
    }
}

fn pick_standard_font(font: &StandardFont) -> &'static str {
    match font {
        StandardFont::Helvetica => "LiberationSans-Regular.ttf",
        StandardFont::HelveticaBold => "LiberationSans-Bold.ttf",
        StandardFont::HelveticaOblique => "LiberationSans-Italic.ttf",
        StandardFont::HelveticaBoldOblique => "LiberationSans-BoldItalic.ttf",
        StandardFont::Courier => "LiberationMono-Regular.ttf",
        StandardFont::CourierBold => "LiberationMono-Bold.ttf",
        StandardFont::CourierOblique => "LiberationMono-Italic.ttf",
        StandardFont::CourierBoldOblique => "LiberationMono-BoldItalic.ttf",
        StandardFont::TimesRoman => "LiberationSerif-Regular.ttf",
        StandardFont::TimesBold => "LiberationSerif-Bold.ttf",
        StandardFont::TimesItalic => "LiberationSerif-Italic.ttf",
        StandardFont::TimesBoldItalic => "LiberationSerif-BoldItalic.ttf",
        StandardFont::ZapfDingBats => "FoxitDingbats.pfb",
        StandardFont::Symbol => "FoxitSymbol.pfb",
    }
}

/// A simple stderr logger.
static LOGGER: SimpleLogger = SimpleLogger;
struct SimpleLogger;
impl log::Log for SimpleLogger {
    fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
        metadata.level() <= log::LevelFilter::Warn
    }

    fn log(&self, record: &log::Record<'_>) {
        if self.enabled(record.metadata()) {
            let target = if !record.target().is_empty() {
                record.target()
            } else {
                record.module_path().unwrap_or_default()
            };

            let line = record.line().unwrap_or(0);
            let args = record.args();

            match record.level() {
                log::Level::Error => eprintln!("Error (in {target}:{line}): {args}"),
                log::Level::Warn => eprintln!("Warning (in {target}:{line}): {args}"),
                log::Level::Info => eprintln!("Info (in {target}:{line}): {args}"),
                log::Level::Debug => eprintln!("Debug (in {target}:{line}): {args}"),
                log::Level::Trace => eprintln!("Trace (in {target}:{line}): {args}"),
            }
        }
    }

    fn flush(&self) {}
}