hayro-interpret 0.8.0

A crate for interpreting PDF files.
Documentation
//! A small example that shows how you can extract unicode characters from
//! glyphs. Please note that Unicode extraction is still experimental, so it
//! might not work fully correctly in certain cases. Note also that generating
//! div elements for every single character is clearly not desirable and there
//! should be some word/sentence merging algorithm in-place, but this is
//! out-of-scope for this example.

use hayro_interpret::font::GlyphRun;
use hayro_interpret::{
    BlendMode, ClipPath, Context, Device, DrawMode, DrawProps, Image, ImageDrawProps,
    InterpreterCache, InterpreterSettings, SoftMask, interpret_page,
};
use hayro_syntax::Pdf;

use std::fmt::Write;

use hayro_cmap::BfString;
use kurbo::{Affine, BezPath, Point, Rect};
use std::path::PathBuf;

fn main() {
    let args: Vec<String> = std::env::args().collect();
    let relative_path = args
        .get(1)
        .expect("Please provide a relative path to the PDF file as the first argument");
    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(relative_path);
    let data = std::fs::read(path).unwrap();

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

    let settings = InterpreterSettings::default();
    let cache = InterpreterCache::new();
    // Pass dummy values for bbox and initial transform, since we don't care about those.
    let mut context = Context::new(
        Affine::IDENTITY,
        Rect::new(0.0, 0.0, 1.0, 1.0),
        &cache,
        pdf.xref(),
        settings,
    );

    // Run everything!
    let page = &pdf.pages()[0];

    let mut extractor = TextExtractor {
        dimensions: page.render_dimensions(),
        ..Default::default()
    };

    writeln!(extractor.text, "<meta charset='utf-8' /> ").unwrap();
    writeln!(extractor.text, "<!-- page {} -->", 0).unwrap();
    writeln!(
        extractor.text,
        "<div id='page{}' style='position: relative; width: {}px; height: {}px; border: 1px black solid'>",
        0,
        extractor.dimensions.0,
        extractor.dimensions.1
    ).unwrap();

    interpret_page(page, &mut context, &mut extractor);

    writeln!(extractor.text, "</div>").unwrap();

    print!("{}", extractor.text);
}

#[derive(Default)]
struct TextExtractor {
    text: String,
    dimensions: (f32, f32),
}

/// Implement `Device` for `TextExtractor`. We extract Unicode text from glyphs.
impl Device<'_> for TextExtractor {
    fn draw_path(&mut self, _: &BezPath, _: DrawProps<'_>, _: &DrawMode) {}

    fn push_clip_path(&mut self, _: &ClipPath) {}

    fn push_transparency_group(&mut self, _: f32, _: Option<SoftMask<'_>>, _: BlendMode) {}

    fn draw_glyph_run(&mut self, glyph_run: &GlyphRun<'_, '_>, props: DrawProps<'_>, _: &DrawMode) {
        for glyph in glyph_run.glyphs() {
            if let Some(unicode_char) = glyph.as_unicode() {
                // Apply vertical flip transformation to combined transform
                // to place origin at top-left corner.
                let flip_transform = Affine::translate((0.0, self.dimensions.1 as f64))
                    * Affine::scale_non_uniform(1.0, -1.0);
                let transform = flip_transform * props.transform * glyph.transform();
                let position = transform * Point::ZERO;

                writeln!(
                    self.text,
                    "<div style='position: absolute; color: black; left: {}px; top: {}px; font-size: {}pt'>{}</div>",
                    position.x, position.y, 6, match unicode_char {
                        BfString::Char(c) => c.to_string(),
                        BfString::String(s) => s
                    }
                ).unwrap();
            } else {
                // Fallback for glyphs without Unicode mapping.
                self.text.push('�');
            }
        }
    }

    fn pop_clip(&mut self) {}

    fn pop_transparency_group(&mut self) {}

    fn draw_image(&mut self, _: Image<'_, '_>, _: ImageDrawProps<'_>) {}
}