[
  {
    "path": ".travis.yml",
    "content": "language: rust\n\nscript:\n  - cargo update\n  - cargo build\n  - cargo test\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nmembers = [\n    \"render\",\n    \"view\",\n    \"examples/pdf2image\",\n]\n[patch.crates-io]\npathfinder_gl = { git = \"https://github.com/servo/pathfinder\" }\npathfinder_webgl = { git = \"https://github.com/servo/pathfinder\" }\npathfinder_gpu = { git = \"https://github.com/servo/pathfinder\" }\npathfinder_content = { git = \"https://github.com/servo/pathfinder\" }\npathfinder_color = { git = \"https://github.com/servo/pathfinder\" }\npathfinder_geometry = { git = \"https://github.com/servo/pathfinder\" }\npathfinder_renderer = { git = \"https://github.com/servo/pathfinder\" }\npathfinder_resources = { git = \"https://github.com/servo/pathfinder\" }\npathfinder_export = { git = \"https://github.com/servo/pathfinder\" }\npathfinder_simd = { git = \"https://github.com/servo/pathfinder\" }\n\n\n[patch.\"https://github.com/s3bk/pathfinder_view\"]\npathfinder_view = { path = \"../pathfinder_view\", features=[\"icon\"] }\n\n[patch.\"https://github.com/pdf-rs/pdf\"]\npdf = { path = \"../pdf/pdf\", default-features=false }\n\n#[patch.\"https://github.com/pdf-rs/font\"]\n#font = { path = \"../font\" }\n\n[patch.\"https://github.com/servo/pathfinder\"]\npathfinder_gl = { path = \"../pathfinder/gl\" }\npathfinder_webgl = { path = \"../pathfinder/webgl\" }\npathfinder_gpu = { path = \"../pathfinder/gpu\" }\npathfinder_content = { path = \"../pathfinder/content\" }\npathfinder_color = { path = \"../pathfinder/color\" }\npathfinder_renderer = { path = \"../pathfinder/renderer\" }\npathfinder_resources = { path = \"../pathfinder/resources\" }\npathfinder_export = { path = \"../pathfinder/export\" }\npathfinder_simd = { path = \"../pathfinder/simd\" }\npathfinder_geometry = { path = \"../pathfinder/geometry\" }"
  },
  {
    "path": "LICENSE",
    "content": "Copyright © 2020 The pdf-rs contributers.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# pdf_render [![Build Status](https://travis-ci.com/pdf-rs/pdf.svg?branch=master)](https://travis-ci.com/pdf-rs/pdf_render)\nExperimental PDF viewer building on [pdf](https://github.com/pdf-rs/pdf).\n\nFeel free to contribute with ideas, issues or code! Please join [us on Zulip](https://type.zulipchat.com/#narrow/stream/209232-pdf) if you have any questions or problems.\n\n# Fonts\nGet a copy of https://github.com/s3bk/pdf_fonts and set `STANDARD_FONTS` to the directory of `pdf_fonts`.\n\n# Viewer\nrun it:\n  `cargo run --bin view --release YOUR_FILE.pdf`\nRight now you can change pages with left and right arrow keys and zoom with '+' and '-'. Works for some files.\n\n## [Try it in your browser](https://pdf-rs.github.io/view-wasm/)\n"
  },
  {
    "path": "download_fonts.sh",
    "content": "#!/usr/bin/env bash\nset -e\n\nTMPDIR=`mktemp -d`\nif [[ ! \"$TMPDIR\" || ! -d \"$TMPDIR\" ]]; then\n    echo \"Couldn't create temporary directory\"\n    exit 1\nfi\nfunction cleanup {\n    rm -rf \"$TMPDIR\"\n}\ntrap cleanup EXIT\n\ncurl http://ardownload.adobe.com/pub/adobe/reader/unix/9.x/9.5.5/enu/AdbeRdr9.5.5-1_i386linux_enu.deb > \"$TMPDIR/AdbeRdr9.5.5-1_i386linux_enu.deb\"\n(cd \"$TMPDIR\" && ar x AdbeRdr9.5.5-1_i386linux_enu.deb data.tar.gz)\nmkdir -p fonts/PFM\ntar xzf \"$TMPDIR/data.tar.gz\" --directory=fonts --strip-components=6 ./opt/Adobe/Reader9/Resource/Font/{AdobePiStd.otf,CourierStd-BoldOblique.otf,CourierStd-Bold.otf,CourierStd-Oblique.otf,CourierStd.otf,MinionPro-BoldIt.otf,MinionPro-Bold.otf,MinionPro-It.otf,MinionPro-Regular.otf,MyriadPro-BoldIt.otf,MyriadPro-Bold.otf,MyriadPro-It.otf,MyriadPro-Regular.otf,ZX______.PFB,ZY______.PFB,SY______.PFB} ./opt/Adobe/Reader9/Resource/Font/PFM/{zx______.pfm,zy______.pfm,SY______.PFM}\nexport STANDARD_FONTS=$pwd/fonts\n"
  },
  {
    "path": "examples/pdf2image/Cargo.toml",
    "content": "[package]\nname = \"pdf2image\"\nversion = \"0.1.0\"\nauthors = [\"Sebastian K <s3bk@protonmail.com>\"]\nedition = \"2018\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\npdf = { git = \"https://github.com/pdf-rs/pdf\" }\npdf_render = { path = \"../../render\" }\nargh = \"*\"\npathfinder_rasterize = { path = \"../../../pathfinder_rasterize\" } # git = \"https://github.com/s3bk/pathfinder_rasterizer\" }\npathfinder_geometry = { git = \"https://github.com/servo/pathfinder\" }\nenv_logger = \"*\"\n"
  },
  {
    "path": "examples/pdf2image/src/main.rs",
    "content": "use argh::FromArgs;\nuse pdf::file::{File, FileOptions};\nuse pdf_render::{Cache, SceneBackend, render_page};\nuse pathfinder_rasterize::Rasterizer;\nuse pathfinder_geometry::transform2d::Transform2F;\nuse std::error::Error;\n\nuse std::path::PathBuf;\n\n#[derive(FromArgs)]\n///  PDF rasterizer\nstruct Options {\n    /// DPI\n    #[argh(option, default=\"150.\")]\n    dpi: f32,\n\n    /// page to render (0 based)\n    #[argh(option, default=\"0\")]\n    page: u32,\n\n    /// input PDF file\n    #[argh(positional)]\n    pdf: PathBuf,\n\n    /// output image\n    #[argh(positional)]\n    image: PathBuf,\n}\n\nfn main() -> Result<(), Box<dyn Error>> {\n    env_logger::init();\n    let opt: Options = argh::from_env();\n\n    let file = FileOptions::uncached().open(&opt.pdf)?;\n    let resolver = file.resolver();\n    let page = file.get_page(opt.page)?;\n\n    let mut cache = Cache::new();\n    let mut backend = SceneBackend::new(&mut cache);\n\n    render_page(&mut backend, &resolver, &page, Transform2F::from_scale(opt.dpi / 25.4))?;\n\n    let image = Rasterizer::new().rasterize(backend.finish(), None);\n\n    image.save(opt.image)?;\n\n    Ok(())\n}"
  },
  {
    "path": "render/Cargo.toml",
    "content": "[package]\nname = \"pdf_render\"\nversion = \"0.1.0\"\nauthors = [\"Sebastian Köln <s3bk@protonmail.com>\"]\nedition = \"2021\"\n\n[features]\nunstable = []\nembed = [\"dep:rust-embed\"]\n\n[[bench]]\nname = \"render\"\nharness = false\n\n[dependencies.pdf]\ndefault-features=false \nfeatures = [\"cache\", \"dump\"]\ngit = \"https://github.com/pdf-rs/pdf\"\n\n[dependencies]\npathfinder_renderer = { git = \"https://github.com/servo/pathfinder\" }\npathfinder_color = { git = \"https://github.com/servo/pathfinder\" }\npathfinder_geometry = { git = \"https://github.com/servo/pathfinder\" }\npathfinder_resources = { git = \"https://github.com/servo/pathfinder\" }\npathfinder_content = { git = \"https://github.com/servo/pathfinder\" }\nlog = \"0.4\"\nfont = { git = \"https://github.com/pdf-rs/font\" }\npdf_encoding = \"0.4\"\nitertools = \"*\"\nimage = \"0.25\"\ninstant = \"*\"\ncustom_debug_derive = \"*\"\nglobalcache = { version = \"0.3\", features = [\"sync\"] }\nistring = { git = \"https://github.com/s3bk/istring\" }\nonce_cell = \"*\"\nserde_json = \"*\"\nglyphmatcher = { git = \"https://github.com/s3bk/glyphmatcher\" }\nrust-embed = { version = \"*\", optional = true, features = [\"interpolate-folder-path\"] }\n\n[dev-dependencies]\ncriterion = \"0.3\"\nenv_logger = \"*\"\n"
  },
  {
    "path": "render/benches/render.rs",
    "content": "use criterion::{black_box, criterion_group, criterion_main, Criterion};\n\nuse pdf::file::{FileOptions};\nuse pdf_render::{Cache, render_page, SceneBackend};\nuse std::time::Duration;\n\nfn bench_render_page(c: &mut Criterion) {\n    let file = FileOptions::cached().open(\"/home/sebk/Downloads/10.1016@j.eswa.2020.114101.pdf\").unwrap();\n    let resolver = file.resolver();\n\n    let mut group = c.benchmark_group(\"10.1016@j.eswa.2020.114101.pdf\");\n    group.sample_size(50);\n    group.warm_up_time(Duration::from_secs(1));\n\n    let mut cache = Cache::new();\n    let mut backend = SceneBackend::new(&mut cache);\n    for (i, page) in file.pages().enumerate() {\n        if let Ok(page) = page {\n            group.bench_function(&format!(\"page {}\", i), |b| b.iter(|| render_page(&mut backend, &resolver, &page, Default::default()).unwrap()));\n        }\n    }\n    group.finish();\n}\n\ncriterion_group!(benches, bench_render_page);\ncriterion_main!(benches);\n"
  },
  {
    "path": "render/benches/view.rs",
    "content": "use criterion::{black_box, criterion_group, criterion_main, Criterion};\n\nuse pdf::file::{FileOptions};\nuse pdf::object::*;\nuse std::path::Path;\nuse pdf_render::{Cache, render_page, SceneBackend};\nuse pathfinder_renderer::scene::Scene;\n\nfn render_file(path: &Path) -> Vec<Scene> {\n    let file = FileOptions::cached().open(path).unwrap();\n    let resolver = file.resolver();\n    \n    let mut cache = Cache::new();\n    file.pages().map(|page| {\n        let p: &Page = &*page.unwrap();\n        let mut backend = SceneBackend::new(&mut cache);\n        render_page(&mut backend, &resolver, p, Default::default()).unwrap();\n        backend.finish()\n    }).collect()\n}\n\nfn bench_file(c: &mut Criterion, name: &str) {\n    let path = Path::new(env!(\"CARGO_MANIFEST_DIR\")).parent().unwrap().join(\"files\").join(name);\n    c.bench_function(name, |b| b.iter(|| render_file(&path)));\n}\n\nmacro_rules! bench_files {\n    (@a $($file:expr, $name:ident;)*) => (\n        $(\n            fn $name(c: &mut Criterion) {\n                bench_file(c, $file)\n            }\n        )*\n\n    );\n    (@b $($file:expr, $name:ident;)*) => (\n        criterion_group!(benches $(, $name)*);\n    );\n    ($($file:expr, $name:ident;)*) => (\n        bench_files!(@a $($file, $name;)*);\n        bench_files!(@b $($file, $name;)*);\n    );\n}\n\nbench_files!(\n    \"example.pdf\", example;\n    \"ep.pdf\", ep;\n    \"ep2.pdf\", ep2;\n    \"libreoffice.pdf\", libreoffice;\n    \"pdf-sample.pdf\", pdf_sample;\n    \"xelatex-drawboard.pdf\", xelatex_drawboard;\n    \"xelatex.pdf\", xelatex;\n    \"PDF32000_2008.pdf\", pdf32000;\n);\n\ncriterion_main!(benches);\n"
  },
  {
    "path": "render/examples/trace.rs",
    "content": "use pdf::file::FileOptions;\nuse pdf_render::render_page;\nuse pdf_render::tracer::{TraceCache, Tracer};\n\nfn main() {\n    env_logger::init();\n    let arg = std::env::args().nth(1).unwrap();\n\n    let file = FileOptions::cached().open(&arg).unwrap();\n    let resolver = file.resolver();\n\n    let mut cache = TraceCache::new();\n\n    for page in file.pages() {\n        let p = page.unwrap();\n        let mut clip_paths = vec![];\n        let mut backend = Tracer::new(&mut cache, &mut clip_paths);\n        render_page(&mut backend, &resolver, &p, Default::default()).unwrap();\n        let items = backend.finish();\n        for i in items {\n            println!(\"{:?}\", i);\n        }\n    }\n}\n"
  },
  {
    "path": "render/src/backend.rs",
    "content": "use pathfinder_geometry::{\n    transform2d::Transform2F,\n    rect::RectF,\n};\nuse pathfinder_content::{\n    fill::FillRule,\n    stroke::{StrokeStyle},\n    outline::Outline,\n};\n\nuse pdf::{object::{Ref, XObject, ImageXObject, Resolve, Resources, MaybeRef}, content::Op};\nuse pdf::error::PdfError;\nuse font::Glyph;\nuse super::{FontEntry, TextSpan, Fill};\nuse pdf::font::Font as PdfFont;\nuse std::sync::Arc;\n\n#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]\npub enum BlendMode {\n    Overlay,\n    Darken\n}\n\npub trait Backend {\n    type ClipPathId: Copy;\n\n    fn create_clip_path(&mut self, path: Outline, fill_rule: FillRule, parent: Option<Self::ClipPathId>) -> Self::ClipPathId;\n    fn draw(&mut self, outline: &Outline, mode: &DrawMode, fill_rule: FillRule, transform: Transform2F, clip: Option<Self::ClipPathId>);\n    fn set_view_box(&mut self, r: RectF);\n    fn draw_image(&mut self, xref: Ref<XObject>, im: &ImageXObject, resources: &Resources, transform: Transform2F, mode: BlendMode, clip: Option<Self::ClipPathId>, resolve: &impl Resolve);\n    fn draw_inline_image(&mut self, im: &Arc<ImageXObject>, resources: &Resources, transform: Transform2F, mode: BlendMode, clip: Option<Self::ClipPathId>, resolve: &impl Resolve);\n    fn draw_glyph(&mut self, glyph: &Glyph, mode: &DrawMode, transform: Transform2F, clip: Option<Self::ClipPathId>) {\n        self.draw(&glyph.path, mode, FillRule::Winding, transform, clip);\n    }\n    fn get_font(&mut self, font_ref: &MaybeRef<PdfFont>, resolve: &impl Resolve) -> Result<Option<Arc<FontEntry>>, PdfError>;\n    fn add_text(&mut self, span: TextSpan, clip: Option<Self::ClipPathId>);\n\n    /// The following functions are for debugging PDF files and not relevant for rendering them.\n    fn bug_text_no_font(&mut self, data: &[u8]) {}\n    fn bug_text_invisible(&mut self, text: &str) {}\n    fn bug_postscript(&mut self, data: &[u8]) {}\n    fn bug_op(&mut self, op_nr: usize) {}\n    fn inspect_op(&mut self, op: &Op) {}\n}\n#[derive(Clone, Debug)]\n\npub struct FillMode {\n    pub color: Fill,\n    pub alpha: f32,\n    pub mode: BlendMode,\n}\npub enum DrawMode {\n    Fill { fill: FillMode },\n    Stroke { stroke: FillMode, stroke_mode: Stroke },\n    FillStroke { fill: FillMode, stroke: FillMode, stroke_mode: Stroke },\n}\n#[derive(Clone, Debug)]\npub struct Stroke {\n    pub dash_pattern: Option<(Vec<f32>, f32)>,\n    pub style: StrokeStyle,\n}\n"
  },
  {
    "path": "render/src/cache.rs",
    "content": "use std::path::{PathBuf};\nuse std::sync::Arc;\n\nuse pdf::object::*;\nuse pdf::primitive::Name;\nuse pdf::font::{Font as PdfFont};\nuse pdf::error::{Result};\n\nuse pathfinder_geometry::{\n    vector::{Vector2I},\n};\nuse pathfinder_content::{\n    pattern::{Image},\n};\n\nuse crate::BlendMode;\n\nuse super::{fontentry::FontEntry};\nuse super::image::load_image;\nuse super::font::{load_font, StandardCache};\nuse globalcache::{sync::SyncCache, ValueSize};\n\n#[derive(Clone)]\npub struct ImageResult(pub Arc<Result<Image>>);\nimpl ValueSize for ImageResult {\n    fn size(&self) -> usize {\n        match *self.0 {\n            Ok(ref im) => im.pixels().len() * 4,\n            Err(_) => 1,\n        }\n    }\n}\n\npub struct Cache {\n    // shared mapping of fontname -> font\n    fonts: Arc<SyncCache<usize, Option<Arc<FontEntry>>>>,\n    images: Arc<SyncCache<(Ref<XObject>, BlendMode), ImageResult>>,\n    std: StandardCache,\n    missing_fonts: Vec<Name>,\n}\n\nimpl Cache {\n    pub fn new() -> Cache {\n        Cache {\n            fonts: SyncCache::new(),\n            images: SyncCache::new(),\n            std: StandardCache::new(),\n            missing_fonts: Vec::new(),\n        }\n    }\n    pub fn get_font(&mut self, pdf_font: &MaybeRef<PdfFont>, resolve: &impl Resolve) -> Result<Option<Arc<FontEntry>>, > {\n        let mut error = None;\n        let val = self.fonts.get(&**pdf_font as *const PdfFont as usize, |_| \n            match load_font(pdf_font, resolve, &mut self.std) {\n                Ok(Some(f)) => Some(Arc::new(f)),\n                Ok(None) => {\n                    if let Some(ref name) = pdf_font.name {\n                        self.missing_fonts.push(name.clone());\n                    }\n                    None\n                },\n                Err(e) => {\n                    error = Some(e);\n                    None\n                }\n            }\n        );\n        match error {\n            None => Ok(val),\n            Some(e) => Err(e)\n        }\n    }\n\n    pub fn get_image(&mut self, xobject_ref: Ref<XObject>, im: &ImageXObject, resources: &Resources, resolve: &impl Resolve, mode: BlendMode) -> ImageResult {\n        self.images.get((xobject_ref, mode), |_|\n            ImageResult(Arc::new(load_image(im, resources, resolve, mode).map(|image|\n                Image::new(Vector2I::new(im.width as i32, im.height as i32), Arc::new(image.into_data().into()))\n            )))\n        )\n    }\n}\nimpl Drop for Cache {\n    fn drop(&mut self) {\n        info!(\"missing fonts:\");\n        for name in self.missing_fonts.iter() {\n            info!(\"{}\", name.as_str());\n        }\n    }\n}\n"
  },
  {
    "path": "render/src/font.rs",
    "content": "use std::borrow::Cow;\nuse std::path::{PathBuf};\nuse std::ops::Deref;\nuse std::collections::HashMap;\nuse glyphmatcher::FontDb;\nuse pdf::object::*;\nuse pdf::font::{Font as PdfFont};\nuse pdf::error::{Result, PdfError};\n\nuse font::{self};\nuse std::sync::Arc;\nuse super::FontEntry;\nuse globalcache::{sync::SyncCache, ValueSize};\nuse std::hash::{Hash, Hasher};\n\n#[derive(Clone)]\npub struct FontRc(Arc<dyn font::Font + Send + Sync + 'static>);\nimpl ValueSize for FontRc {\n    #[inline]\n    fn size(&self) -> usize {\n        1 // TODO\n    }\n}\nimpl From<Box<dyn font::Font + Send + Sync + 'static>> for FontRc {\n    #[inline]\n    fn from(f: Box<dyn font::Font + Send + Sync + 'static>) -> Self {\n        FontRc(f.into())\n    }\n}\nimpl Deref for FontRc {\n    type Target = dyn font::Font + Send + Sync + 'static;\n    #[inline]\n    fn deref(&self) -> &Self::Target {\n        &*self.0\n    }\n}\nimpl PartialEq for FontRc {\n    #[inline]\n    fn eq(&self, rhs: &Self) -> bool {\n        Arc::as_ptr(&self.0) == Arc::as_ptr(&rhs.0)\n    }\n}\nimpl Eq for FontRc {}\nimpl Hash for FontRc {\n    #[inline]\n    fn hash<H: Hasher>(&self, state: &mut H) {\n        Arc::as_ptr(&self.0).hash(state)\n    }\n}\npub struct StandardCache {\n    inner: Arc<SyncCache<String, Option<FontRc>>>,\n\n    #[cfg(not(feature=\"embed\"))]\n    dir: PathBuf,\n\n    #[cfg(feature=\"embed\")]\n    dir: EmbeddedStandardFonts,\n\n    fonts: HashMap<String, String>,\n    dump: Dump,\n    require_unique_unicode: bool,\n}\nimpl StandardCache {\n    #[cfg(not(feature=\"embed\"))]\n    pub fn new() -> StandardCache {\n        let standard_fonts = PathBuf::from(std::env::var_os(\"STANDARD_FONTS\").expect(\"STANDARD_FONTS is not set. Please check https://github.com/pdf-rs/pdf_render/#fonts for instructions.\"));\n\n        let data = standard_fonts.read_file(\"fonts.json\").expect(\"can't read fonts.json\");\n        let fonts: HashMap<String, String> = serde_json::from_slice(&data).expect(\"fonts.json is invalid\");\n\n        let dump = match std::env::var(\"DUMP_FONT\").as_deref() {\n            Err(_) => Dump::Never,\n            Ok(\"always\") => Dump::Always,\n            Ok(\"error\") => Dump::OnError,\n            Ok(_) => Dump::Never\n        };\n        StandardCache {\n            inner: SyncCache::new(),\n            dir: standard_fonts,\n            fonts,\n            dump,\n            require_unique_unicode: false,\n        }\n    }\n    #[cfg(feature=\"embed\")]\n    pub fn new() -> StandardCache {\n        let ref data = EmbeddedStandardFonts::get(\"fonts.json\").unwrap().data;\n        let fonts: HashMap<String, String> = serde_json::from_slice(&data).expect(\"fonts.json is invalid\");\n\n        StandardCache {\n            inner: SyncCache::new(),\n            fonts,\n            dir: EmbeddedStandardFonts,\n            dump: Dump::Never,\n            require_unique_unicode: false,\n        }\n    }\n\n    pub fn require_unique_unicode(&mut self, r: bool) {\n        self.require_unique_unicode = r;\n    }\n}\n\npub trait DirRead: Sized {\n    fn read_file(&self, name: &str) -> Result<Cow<'static, [u8]>>;\n    fn sub_dir(&self, name: &str) -> Option<Self>;\n}\n\nimpl DirRead for PathBuf {\n    fn read_file(&self, name: &str) -> Result<Cow<'static, [u8]>> {\n        std::fs::read(self.join(name)).map_err(|e| e.into()).map(|d| d.into())\n    }\n    fn sub_dir(&self, name: &str) -> Option<Self> {\n        let sub = self.join(name);\n        if sub.is_dir() {\n            Some(sub)\n        } else {\n            None\n        }\n    }\n}\n\n#[cfg(feature=\"embed\")]\n#[derive(rust_embed::Embed)]\n#[folder = \"$STANDARD_FONTS\"]\npub struct EmbeddedStandardFonts;\n\n#[cfg(feature=\"embed\")]\nimpl DirRead for EmbeddedStandardFonts {\n    fn read_file(&self, name: &str) -> Result<Cow<'static, [u8]>> {\n        EmbeddedStandardFonts::get(name).map(|f| f.data).ok_or_else(|| PdfError::Other { msg: \"Filed {name:?} not embedded\".into() })\n    }\n    fn sub_dir(&self, name: &str) -> Option<Self> {\n        None\n    }\n}\n\n#[derive(Debug)]\nenum Dump {\n    Never,\n    OnError,\n    Always\n}\n\npub fn load_font(font_ref: &MaybeRef<PdfFont>, resolve: &impl Resolve, cache: &StandardCache) -> Result<Option<FontEntry>> {\n    let pdf_font = font_ref.clone();\n    debug!(\"loading {:?}\", pdf_font);\n    \n    let font: FontRc = match pdf_font.embedded_data(resolve) {\n        Some(Ok(data)) => {\n            debug!(\"loading embedded font\");\n            let font = font::parse(&data).map_err(|e| {\n                PdfError::Other { msg: format!(\"Font Error: {:?}\", e) }\n            });\n            if matches!(cache.dump, Dump::Always) || (matches!(cache.dump, Dump::OnError) && font.is_err()) {\n                let name = format!(\"font_{}\", pdf_font.name.as_ref().map(|s| s.as_str()).unwrap_or(\"unnamed\"));\n                std::fs::write(&name, &data).unwrap();\n                println!(\"font dumped in {}\", name);\n            }\n            FontRc::from(font?)\n        }\n        Some(Err(e)) => return Err(e),\n        None => {\n            debug!(\"no embedded font.\");\n            let name = match pdf_font.name {\n                Some(ref name) => name.as_str(),\n                None => return Ok(None)\n            };\n            debug!(\"loading {name} instead\");\n            match cache.fonts.get(name).or_else(|| cache.fonts.get(\"Arial\")) {\n                Some(file_name) => {\n                    let val = cache.inner.get(file_name.clone(), |_| {\n                        let data = match cache.dir.read_file(file_name) {\n                            Ok(data) => data,\n                            Err(e) => {\n                                warn!(\"can't open {} for {:?} {:?}\", file_name, pdf_font.name, e);\n                                return None;\n                            }\n                        };\n                        match font::parse(&data) {\n                            Ok(f) => Some(f.into()),\n                            Err(e) => {\n                                warn!(\"Font Error: {:?}\", e);\n                                return None;\n                            }\n                        }\n                    });\n                    match val {\n                        Some(f) => f,\n                        None => {\n                            return Ok(None);\n                        }\n                    }\n                }\n                None => {\n                    warn!(\"no font for {:?}\", pdf_font.name);\n                    return Ok(None);\n                }\n            }\n        }\n    };\n\n    Ok(Some(FontEntry::build(font, pdf_font, None, resolve, cache.require_unique_unicode)?))\n}\n"
  },
  {
    "path": "render/src/fontentry.rs",
    "content": "use std::collections::{HashMap, HashSet};\nuse font::{self, GlyphId, TrueTypeFont, CffFont, Type1Font, OpenTypeFont};\nuse glyphmatcher::FontDb;\nuse itertools::Itertools;\nuse pdf::encoding::BaseEncoding;\nuse pdf::font::{Font as PdfFont, Widths, CidToGidMap};\nuse pdf::object::{Resolve, MaybeRef};\nuse pdf::error::PdfError;\nuse pdf_encoding::{Encoding, glyphname_to_unicode};\nuse istring::SmallString;\nuse crate::font::FontRc;\n\npub struct FontEntry {\n    pub font: FontRc,\n    pub pdf_font: MaybeRef<PdfFont>,\n    pub cmap: HashMap<u16, (GlyphId, Option<SmallString>)>,\n    pub widths: Option<Widths>,\n    pub is_cid: bool,\n    pub name: String,\n}\n\n\nimpl FontEntry {\n    pub fn build(font: FontRc, pdf_font: MaybeRef<PdfFont>, font_db: Option<&FontDb>, resolve: &impl Resolve, require_unique_unicode: bool) -> Result<FontEntry, PdfError> {\n        let mut is_cid = pdf_font.is_cid();\n\n        let name = match pdf_font.data {\n            pdf::font::FontData::Type0(ref t0) => t0.descendant_fonts[0].name.as_ref(),\n            _ => pdf_font.name.as_ref()\n        };\n\n        let encoding = pdf_font.encoding().clone();\n        let base_encoding = encoding.as_ref().map(|e| &e.base);\n        \n        let to_unicode = t!(pdf_font.to_unicode(resolve).transpose());\n        let mut font_codepoints = None;\n\n        let font_cmap = font.downcast_ref::<TrueTypeFont>().and_then(|ttf| ttf.cmap.as_ref())\n        .or_else(|| font.downcast_ref::<OpenTypeFont>().and_then(|otf| otf.cmap.as_ref()));\n\n        let glyph_unicode: HashMap<GlyphId, SmallString> = {\n            if let Some(type1) = font.downcast_ref::<Type1Font>() {\n                debug!(\"Font is Type1\");\n                font_codepoints = Some(&type1.codepoints);\n                type1.unicode_names().map(|(gid, s)| (gid, s.into())).collect()\n            } else if let Some(cmap) = font_cmap {\n                cmap.items().filter_map(|(cp, gid)| std::char::from_u32(cp).map(|c| (gid, c.into()))).collect()\n            } else if let Some(cff) = font.downcast_ref::<CffFont>() {\n                cff.unicode_map.iter().map(|(&u, &gid)| (GlyphId(gid as u32), u.into())).collect()\n            } else {\n                (0..font.num_glyphs())\n                    .filter_map(|gid| std::char::from_u32(gid).map(|c| (GlyphId(gid), c.into())))\n                    .collect()\n            }\n        };\n        \n        debug!(\"to_unicode: {:?}\", to_unicode);\n        let build_map = || -> HashMap<u16, (GlyphId, Option<SmallString>)> {\n            if let Some(ref to_unicode) = to_unicode {\n                let mut num1 = 0;\n                // dbg!(font.encoding());\n                let mut map: HashMap<_, _> = to_unicode.iter().map(|(cid, s)| {\n                    let gid = font.gid_for_codepoint(cid as u32);\n                    if gid.is_some() {\n                        num1 += 1;\n                    }\n                    (cid, (gid.unwrap_or(GlyphId(cid as u32)), Some(s.into())))\n                }).collect();\n                if let Some(cff) = font.downcast_ref::<CffFont>() {\n                    let mut num2 = 0;\n                    let map2: HashMap<_, _> = to_unicode.iter().map(|(cid, s)| {\n                        let gid = cff.sid_map.get(&cid).map(|&n| GlyphId(n as u32));\n                        if gid.is_some() {\n                            num2 += 1;\n                        }\n                        (cid, (gid.unwrap_or(GlyphId(cid as u32)), Some(s.into())))\n                    }).collect();\n                    if num2 > num1 {\n                        map = map2;\n                    }\n                }\n                map\n            } else if let Some(cmap) = font_cmap {\n                cmap.items().map(|(cid, gid)| (cid as u16, (gid, None))).collect()\n            } else if let Some(cff) = font.downcast_ref::<CffFont>() {\n                if cff.cid {\n                    cff.sid_map.iter().map(|(&sid, &gid)|  (sid as u16, (GlyphId(gid as u32), None))).collect()\n                } else {\n                    cff.codepoint_map.iter().enumerate().filter(|&(_, &gid)| gid != 0).map(|(cid, &gid)| (cid as u16, (GlyphId(gid as u32), None))).collect()\n                }\n            } else {\n                Default::default()\n            }\n        };\n        \n        let mut cmap = if let Some(map) = pdf_font.cid_to_gid_map() {\n            is_cid = true;\n            debug!(\"gid to cid map: {:?}\", map);\n            match map {\n                CidToGidMap::Identity => {\n                    let mut map: HashMap<_, _> = (0 .. font.num_glyphs()).map(|n| (n as u16, (GlyphId(n as u32), None))).collect();\n                    if let Some(ref to_unicode) = to_unicode {\n                        for (cid, s) in to_unicode.iter() {\n                            if let Some((gid, uni)) = map.get_mut(&cid) {\n                                *uni = Some(s.into());\n                            }\n                        }\n                    }\n                    map\n                }\n                CidToGidMap::Table(ref data) => {\n                    data.iter().enumerate().map(|(cid, &gid)| {\n                        let unicode = match to_unicode {\n                            Some(ref u) => u.get(cid as u16).map(|s| s.into()),\n                            None => glyph_unicode.get(&GlyphId(gid as u32)).cloned()\n                        };\n                        (cid as u16, (GlyphId(gid as u32), unicode))\n                    }).collect()\n                }\n            }\n        } else if base_encoding == Some(&BaseEncoding::IdentityH) {\n            is_cid = true;\n            build_map()\n        } else {\n            let mut cmap = HashMap::<u16, (GlyphId, Option<SmallString>)>::new();\n            \n            let source_encoding = match base_encoding {\n                Some(BaseEncoding::StandardEncoding) => Some(Encoding::AdobeStandard),\n                Some(BaseEncoding::SymbolEncoding) => Some(Encoding::AdobeSymbol),\n                Some(BaseEncoding::WinAnsiEncoding) => Some(Encoding::WinAnsiEncoding),\n                Some(BaseEncoding::MacRomanEncoding) => Some(Encoding::MacRomanEncoding),\n                Some(BaseEncoding::MacExpertEncoding) => Some(Encoding::AdobeExpert),\n                ref e => {\n                    warn!(\"unsupported pdf encoding {:?}\", e);\n                    None\n                }\n            };\n\n            let font_encoding = font.encoding();\n            debug!(\"{:?} -> {:?}\", source_encoding, font_encoding);\n\n            match (source_encoding, font_encoding) {\n                (Some(source), Some(dest)) => {\n                    if let Some(transcoder) = source.to(dest) {\n                        let forward = source.forward_map().unwrap();\n                        for b in 0 .. 256 {\n                            if let Some(gid) = transcoder.translate(b).and_then(|cp| font.gid_for_codepoint(cp)) {\n                                cmap.insert(b as u16, (gid, forward.get(b as u8).map(|c| c.into())));\n                                //debug!(\"{} -> {:?}\", b, gid);\n                            }\n                        }\n                    }\n                },\n                (Some(enc), None) => {\n                    if let Some(encoder) = enc.to(Encoding::Unicode) {\n                        for b in 0 .. 256 {\n                            let unicode = encoder.translate(b as u32);\n                            if let Some(gid) = unicode.and_then(|c| font.gid_for_unicode_codepoint(c)) {\n                                cmap.insert(b, (gid, unicode.and_then(std::char::from_u32).map(|c| c.into())));\n                                debug!(\"{} -> {:?}\", b, gid);\n                            }\n                        }\n                    }\n                }\n                _ => {\n                    if let Some(cff) = font.downcast_ref::<CffFont>() {\n                        for (cp, &gid) in cff.codepoint_map.iter().enumerate() {\n                            let gid = GlyphId(gid as u32);\n                            let unicode = glyph_unicode.get(&gid).cloned();\n                            cmap.insert(cp as u16, (gid, unicode));\n                        }\n                    } else {\n                        warn!(\"can't translate from text encoding {:?} to font encoding {:?}\", base_encoding, font_encoding);\n                    }\n                    // assuming same encoding\n                    \n                    \n                }\n            }\n            if let Some(encoding) = encoding {\n                for (&cp, name) in encoding.differences.iter() {\n                    let uni = glyphname_to_unicode(name);\n                    let gid = font.gid_for_name(&name).or_else(||\n                        uni.and_then(|s| s.chars().next()).and_then(|cp| font.gid_for_unicode_codepoint(cp as u32))\n                    ).or_else(||\n                        font.gid_for_codepoint(cp)\n                    ).unwrap_or(GlyphId(cp));\n                    \n                    let unicode = uni.map(|s| s.into())\n                        .or_else(|| std::char::from_u32(0xf000 + gid.0).map(SmallString::from));\n                    \n                    debug!(\"{} -> gid {:?}, unicode {:?}\", cp, gid, unicode);\n                    cmap.insert(cp as u16, (gid, unicode));\n                }\n            } else {\n                if let Some(ref u) = to_unicode {\n                    debug!(\"using to_unicode to build cmap\");\n                    for (cp, unicode) in u.iter() {\n                        if let Some(gid) = font.gid_for_unicode_codepoint(cp as u32) {\n                            cmap.insert(cp as u16, (gid, Some(unicode.into())));\n                        }\n                    }\n                } else if let Some(codepoints) = font_codepoints {\n                    for (&cp, &gid) in codepoints.iter() {\n                        cmap.insert(cp as u16, (GlyphId(gid), glyph_unicode.get(&GlyphId(gid)).cloned()));\n                    }\n                } else {\n                    debug!(\"assuming text has unicode codepoints\");\n                    for (&gid, unicode) in glyph_unicode.iter() {\n                        if let Some(cp) = unicode.chars().next() {\n                            cmap.insert(cp as u16, (gid, Some(unicode.clone())));\n                        }\n                    }\n                }\n            }\n            \n\n            if cmap.len() == 0 {\n                is_cid = true;\n                build_map()\n            } else {\n                cmap\n            }\n        };\n        \n        if let Some(font_db) = font_db {\n            if let Some(name) = name {\n                let ps_name = name.split(\"+\").nth(1).unwrap_or(name);\n\n                info!(\"request font {ps_name} ({})\", name.as_str());\n                if let Some(map) = font_db.check_font(ps_name, &*font) {\n                    if map.len() > 0 {\n                        info!(\"Got good unicode map for {ps_name}\");\n                    } else {\n                        info!(\"font {ps_name} did not match\");\n                    }\n                    for (cp, (gid, uni)) in cmap.iter_mut() {\n                        let good_uni = map.get(gid);\n                        match (uni.as_mut(), good_uni) {\n                            (Some(uni), Some(good_uni)) if uni != good_uni => {\n                                // println!(\"mismatching unicode for gid {gid:?}: {good_uni:?} != {uni:?}\");\n                                *uni = good_uni.clone();\n                            }\n                            (None, Some(good_uni)) => {\n                                // println!(\"missing unicode for gid {gid:?} added {good_uni:?}\");\n                                *uni = Some(good_uni.clone());\n                            }\n                            (Some(uni), None) => {\n                                // println!(\"glyph {} missing (has uni {:?})\", gid.0, uni);\n                            }\n                            _ => {}\n                        }\n                    }\n                } else {\n                    info!(\"missing {ps_name} font\");\n                }\n            }\n        }\n\n        let widths = pdf_font.widths(resolve)?;\n        let name = pdf_font.name.as_ref().ok_or_else(|| PdfError::Other { msg: \"font has no name\".into() })?.as_str().into();\n\n        if require_unique_unicode {\n            let mut next_code = 0xE000;\n            let mut by_gid: Vec<_> = cmap.values_mut().collect();\n            by_gid.sort_unstable_by_key(|t| t.0.0);\n\n            let reserved_in_used: HashSet<u32> = by_gid.iter().map(|(gid, _)| gid.0).filter(|gid| (0xE000 .. 0xF800).contains(gid)).collect();\n            \n            if reserved_in_used.len() > 0 {\n                info!(\"gid in privated use area: {}\", reserved_in_used.iter().format(\", \"));\n            }\n\n            let mut rev_map = HashMap::new();\n            for (gid, uni_o) in by_gid.iter_mut() {\n                if let Some(uni) = uni_o {\n                    use std::collections::hash_map::Entry;\n\n                    match rev_map.entry(uni.clone()) {\n                        Entry::Vacant(e) => {\n                            e.insert(*gid);\n                        }\n                        Entry::Occupied(e) => {\n                            info!(\"Duplicate unicode {uni:?} for {gid:?} and {:?}\", e.get());\n                            *uni_o = None;\n                        }\n                    }\n                }\n            }\n\n            for (gid, uni) in by_gid.iter_mut() {\n                if uni.is_none() && !font.is_empty_glyph(*gid) {\n                    *uni = Some(std::char::from_u32(next_code).unwrap().into());\n                    \n                    next_code += 1;\n                    while reserved_in_used.contains(&next_code) {\n                        next_code += 1;\n                    }\n\n                    if next_code >= 0xF8000 {\n                        warn!(\"too many unmapped glpyhs in {:?}\", font.name().postscript_name);\n                        break;\n                    }\n                }\n            }\n            if next_code > 0xE000 {\n                info!(\"mapped {} glyphs in private use area\", next_code - 0xE000);\n            }\n\n        }\n        \n        Ok(FontEntry {\n            font,\n            pdf_font,\n            cmap,\n            is_cid,\n            widths,\n            name,\n        })\n    }\n}\n\nimpl globalcache::ValueSize for FontEntry {\n    fn size(&self) -> usize {\n        1 // TODO\n    }\n}\n"
  },
  {
    "path": "render/src/graphicsstate.rs",
    "content": "use pathfinder_content::stroke::StrokeStyle;\nuse pathfinder_renderer::{paint::PaintId, scene::ClipPath};\nuse pdf::object::ColorSpace;\n\nuse pathfinder_geometry::{transform2d::Transform2F, rect::RectF};\nuse crate::{Fill, backend::Stroke, Backend};\n\npub struct GraphicsState<'a, B: Backend> {\n    pub transform: Transform2F,\n    pub stroke_style: StrokeStyle,\n\n    pub fill_color: Fill,\n    pub fill_color_alpha: f32,\n    pub fill_paint: Option<PaintId>,\n    pub stroke_color: Fill,\n    pub stroke_color_alpha: f32,\n    pub stroke_paint: Option<PaintId>,\n    pub clip_path_id: Option<B::ClipPathId>,\n    pub clip_path: Option<ClipPath>,\n    pub clip_path_rect: Option<RectF>,\n    pub fill_color_space: &'a ColorSpace,\n    pub stroke_color_space: &'a ColorSpace,\n    pub dash_pattern: Option<(&'a [f32], f32)>,\n\n    pub stroke_alpha: f32,\n    pub fill_alpha: f32,\n\n    pub overprint_fill: bool,\n    pub overprint_stroke: bool,\n    pub overprint_mode: i32,\n}\n\nimpl<'a, B: Backend> Clone for GraphicsState<'a, B> {\n    fn clone(&self) -> Self {\n        GraphicsState {\n            clip_path: self.clip_path.clone(),\n            .. *self\n        }\n    }\n}\n\n\nimpl<'a, B: Backend> GraphicsState<'a, B> {\n    pub fn set_fill_color(&mut self, fill: Fill) {\n        if fill != self.fill_color {\n            self.fill_color = fill;\n            self.fill_paint = None;\n        }\n    }\n    pub fn set_fill_alpha(&mut self, alpha: f32) {\n        let a = self.fill_alpha * alpha;\n        if a != self.fill_color_alpha {\n            self.fill_color_alpha = a;\n            self.fill_paint = None;\n        }\n    }\n    pub fn set_stroke_color(&mut self, fill: Fill) {\n        if fill != self.stroke_color {\n            self.stroke_color = fill;\n            self.stroke_paint = None;\n        }\n    }\n    pub fn set_stroke_alpha(&mut self, alpha: f32) {\n        let a = self.stroke_alpha * alpha;\n        if a != self.stroke_color_alpha {\n            self.stroke_alpha = a;\n            self.stroke_paint = None;\n        }\n    }\n    pub fn stroke(&self) -> Stroke {\n        Stroke {\n            style: self.stroke_style,\n            dash_pattern: self.dash_pattern.map(|(a, p)| (a.into(), p))\n        }\n    }\n}\n"
  },
  {
    "path": "render/src/image.rs",
    "content": "use image::{RgbaImage, ImageBuffer, Rgba};\nuse pdf::object::*;\nuse pdf::error::PdfError;\nuse pathfinder_color::ColorU;\nuse std::borrow::Cow;\nuse std::path::Path;\nuse std::sync::Arc;\n\nuse crate::BlendMode;\n\n#[derive(Hash, PartialEq, Eq, Clone)]\npub struct ImageData<'a> {\n    data: Cow<'a, [ColorU]>,\n    width: u32,\n    height: u32,\n}\nimpl<'a> ImageData<'a> {\n    pub fn new(data: impl Into<Cow<'a, [ColorU]>>, width: u32, height: u32) -> Option<Self> {\n        let data = data.into();\n        if width as usize * height as usize != data.len() {\n            return None;\n        }\n        Some(ImageData { data, width, height })\n    }\n    pub fn width(&self) -> u32 {\n        self.width\n    }\n    pub fn height(&self) -> u32 {\n        self.height\n    }\n    pub fn data(&self) -> &[ColorU] {\n        &*self.data\n    }\n    pub fn into_data(self) -> Cow<'a, [ColorU]> {\n        self.data\n    }\n    pub fn rgba_data(&self) -> &[u8] {\n        let ptr: *const ColorU = self.data.as_ptr();\n        let len = self.data.len();\n        unsafe {\n            std::slice::from_raw_parts(ptr.cast(), 4 * len)\n        }\n    }\n    /// angle must be in range 0 .. 4\n    pub fn rotate(&self, angle: u8) -> ImageData<'_> {\n        match angle {\n            0 => ImageData {\n                data: Cow::Borrowed(&*self.data),\n                width: self.width,\n                height: self.height\n            },\n            1 => {\n                let mut data = Vec::with_capacity(self.data.len());\n                \n                for y in 0 .. self.width as usize {\n                    for x in (0 .. self.height as usize).rev() {\n                        data.push(self.data[x * self.width as usize + y]);\n                    }\n                }\n                \n                ImageData::new(\n                    data,\n                    self.height,\n                    self.width\n                ).unwrap()\n            }\n            2 => {\n                let data: Vec<ColorU> = self.data.iter().rev().cloned().collect();\n                ImageData::new(\n                    data,\n                    self.width,\n                    self.height\n                ).unwrap()\n            }\n            3 => {\n                let mut data = Vec::with_capacity(self.data.len());\n                \n                for y in (0 .. self.width as usize).rev() {\n                    for x in 0 .. self.height as usize {\n                        data.push(self.data[x * self.width as usize + y]);\n                    }\n                }\n                \n                ImageData::new(\n                    data,\n                    self.height,\n                    self.width\n                ).unwrap()\n            }\n            _ => panic!(\"invalid rotation\")\n        }\n    }\n\n    pub fn safe(&self, path: &Path) {\n        let data = self.rgba_data();\n        ImageBuffer::<Rgba<u8>, &[u8]>::from_raw(self.width, self.height, data).unwrap().save(path).unwrap()\n    }\n}\n\nfn resize_alpha(data: &[u8], src_width: u32, src_height: u32, dest_width: u32, dest_height: u32) -> Option<Vec<u8>> {\n    use image::{ImageBuffer, imageops::{resize, FilterType}, Luma};\n\n    let src: ImageBuffer<Luma<u8>, &[u8]> = ImageBuffer::from_raw(src_width, src_height, data)?;\n    let dest = resize(&src, dest_width, dest_height, FilterType::CatmullRom);\n\n    Some(dest.into_raw())\n}\n\npub fn load_image(image: &ImageXObject, resources: &Resources, resolve: &impl Resolve, mode: BlendMode) -> Result<ImageData<'static>, PdfError> {\n    let raw_data = image.image_data(resolve)?;\n\n    let pixel_count = image.width as usize * image.height as usize;\n\n    if raw_data.len() % pixel_count != 0 {\n        warn!(\"invalid data length {} bytes for {} pixels\", raw_data.len(), pixel_count);\n        info!(\"image: {:?}\", image.inner.info.info);\n        info!(\"filters: {:?}\", image.inner.filters);\n    }\n\n    enum Data<'a> {\n        Arc(Arc<[u8]>),\n        Vec(Vec<u8>),\n        Slice(&'a [u8])\n    }\n    impl<'a> std::ops::Deref for Data<'a> {\n        type Target = [u8];\n        fn deref(&self) -> &[u8] {\n            match self {\n                Data::Arc(ref d) => &**d,\n                Data::Vec(ref d) => &*d,\n                Data::Slice(s) => s\n            }\n        }\n    }\n    impl<'a> From<Vec<u8>> for Data<'a> {\n        fn from(v: Vec<u8>) -> Self {\n            Data::Vec(v)\n        }\n    }\n\n    let mask = t!(image.smask.map(|r| resolve.get(r)).transpose());\n    let alpha = match mask {\n        Some(ref mask) => {\n            let data = Data::Arc(t!((**mask).data(resolve)));\n            let mask_width = mask.width as usize;\n            let mask_height = mask.height as usize;\n            let bits_per_component = mask.bits_per_component.ok_or_else(|| PdfError::Other { msg: format!(\"no bits per component\")})?;\n            let bits = mask_width * mask_height * bits_per_component as usize;\n            assert_eq!(data.len(), (bits + 7) / 8);\n\n            let mut alpha: Data = match bits_per_component {\n                1 => data.iter().flat_map(|&b| (0..8).map(move |i| ex(b >> i, 1))).collect::<Vec<u8>>().into(),\n                2 => data.iter().flat_map(|&b| (0..4).map(move |i| ex(b >> 2*i, 2))).collect::<Vec<u8>>().into(),\n                4 => data.iter().flat_map(|&b| (0..2).map(move |i| ex(b >> 4*i, 4))).collect::<Vec<u8>>().into(),\n                8 => data,\n                12 => data.chunks_exact(3).flat_map(|c| [c[0], c[1] << 4 | c[2] >> 4]).collect::<Vec<u8>>().into(),\n                16 => data.chunks_exact(2).map(|c| c[0]).collect::<Vec<u8>>().into(),\n                n => return Err(PdfError::Other { msg: format!(\"invalid bits per component {}\", n)})\n            };\n            if mask.width != image.width || mask.height != image.height {\n                alpha = resize_alpha(&*alpha, mask.width, mask.height, image.width, image.height).unwrap().into();\n            }\n            alpha\n        }\n        None => Data::Slice(&[][..])\n    };\n    #[inline]\n    fn ex(b: u8, bits: u8) -> u8 {\n        b & ((1 << bits) - 1)\n    }\n    \n    fn resolve_cs<'a>(cs: &'a ColorSpace, resources: &'a Resources) -> Option<&'a ColorSpace> {\n        match cs {\n            ColorSpace::Icc(icc) => {\n                match icc.info.alternate {\n                    Some(ref b) => Some(&**b),\n                    None => match icc.info.components {\n                        1 => Some(&ColorSpace::DeviceGray),\n                        3 => Some(&ColorSpace::DeviceRGB),\n                        4 => Some(&ColorSpace::DeviceCMYK),\n                        _ => None\n                    }\n                }\n            }\n            ColorSpace::Named(ref name) => resources.color_spaces.get(name),\n            _ => Some(cs),\n        }\n    }\n\n    let cs = image.color_space.as_ref().and_then(|cs| resolve_cs(cs, &resources));\n    let alpha = alpha.iter().cloned().chain(std::iter::repeat(255));\n    let data_ratio = (raw_data.len() * 8) / pixel_count;\n    // dbg!(data_ratio);\n\n    debug!(\"CS: {cs:?}\");\n\n    let data = match data_ratio {\n        1 | 2 | 4 | 8 => {\n            let pixel_data: Cow<[u8]> = match data_ratio {\n                1 => raw_data.iter().flat_map(|&b| (0..8).map(move |i| ex(b >> i, 1))).take(pixel_count).collect::<Vec<u8>>().into(),\n                2 => raw_data.iter().flat_map(|&b| (0..4).map(move |i| ex(b >> 2*i, 2))).take(pixel_count).collect::<Vec<u8>>().into(),\n                4 => raw_data.iter().flat_map(|&b| (0..2).map(move |i| ex(b >> 4*i, 4))).take(pixel_count).collect::<Vec<u8>>().into(),\n                8 => Cow::Borrowed(&raw_data[..pixel_count]),\n                n => return Err(PdfError::Other { msg: format!(\"invalid bits per component {}\", n)})\n            };\n            let pixel_data: &[u8] = &*pixel_data;\n            // dbg!(&cs);\n            match cs {\n                Some(&ColorSpace::DeviceGray) => {\n                    assert_eq!(pixel_data.len(), pixel_count);\n                    pixel_data.iter().zip(alpha).map(|(&g, a)| ColorU { r: g, g: g, b: g, a }).collect()\n                }\n                Some(&ColorSpace::Indexed(ref base, hival, ref lookup)) => {\n                    match resolve_cs(&**base, resources) {\n                        Some(ColorSpace::DeviceRGB) => {\n                            let mut data = Vec::with_capacity(pixel_data.len());\n                            for (&b, a) in pixel_data.iter().zip(alpha) {\n                                let off = b as usize * 3;\n                                let c = lookup.get(off .. off + 3).ok_or(PdfError::Bounds { index: off, len: lookup.len() })?;\n                                data.push(rgb2rgba(c, a, mode));\n                            }\n                            data\n                        }\n                        Some(ColorSpace::DeviceCMYK) => {\n                            debug!(\"indexed CMYK {}\", lookup.len());\n                            let mut data = Vec::with_capacity(pixel_data.len());\n                            for (&b, a) in pixel_data.iter().zip(alpha) {\n                                let off = b as usize * 4;\n                                let c = lookup.get(off .. off + 4).ok_or(PdfError::Bounds { index: off, len: lookup.len() })?;\n                                data.push(cmyk2color(c.try_into().unwrap(), a, BlendMode::Darken));\n                            }\n                            data\n                        }\n                        _ => unimplemented!(\"base cs={:?}\", base),\n                    }\n                }\n                Some(&ColorSpace::Separation(_, ref alt, ref func)) => {\n                    let mut lut = [[0u8; 3]; 256];\n\n                    match resolve_cs(alt, resources) {\n                        Some(ColorSpace::DeviceRGB) => {\n                            for (i, rgb) in lut.iter_mut().enumerate() {\n                                let mut c = [0.; 3];\n                                func.apply(&[i as f32 / 255.], &mut c)?;\n                                let [r, g, b] = c;\n                                *rgb = rgb2rgb(r, g, b, mode);\n                            }\n                        }\n                        Some(ColorSpace::DeviceCMYK) => {\n                            for (i, rgb) in lut.iter_mut().enumerate() {\n                                let mut c = [0.; 4];\n                                func.apply(&[i as f32 / 255.], &mut c)?;\n                                let [c, m, y, k] = c;\n                                *rgb = cmyk2rgb([(c * 255.) as u8, (m * 255.) as u8, (y * 255.) as u8, (k * 255.) as u8], mode);\n                            }\n                        }\n                        _ => unimplemented!(\"alt cs={:?}\", alt),\n                    }\n                    pixel_data.iter().zip(alpha).map(|(&b, a)| {\n                        let [r, g, b] = lut[b as usize];\n                        ColorU { r, g, b, a }\n                    }).collect()\n                }\n                None => {\n                    info!(\"image has data/pixel ratio of 1, but no colorspace\");\n                    assert_eq!(pixel_data.len(), pixel_count);\n                    pixel_data.iter().zip(alpha).map(|(&g, a)| ColorU { r: g, g: g, b: g, a }).collect()\n                }\n                _ => unimplemented!(\"cs={:?}\", cs),\n            }\n        }\n        24 => {\n            if !matches!(cs, Some(ColorSpace::DeviceRGB)) {\n                info!(\"image has data/pixel ratio of 3, but colorspace is {:?}\", cs);\n            }\n            raw_data[..pixel_count * 3].chunks_exact(3).zip(alpha).map(|(c, a)| rgb2rgba(c, a, mode)).collect()\n        }\n        32 => {\n            if !matches!(cs, Some(ColorSpace::DeviceCMYK)) {\n                info!(\"image has data/pixel ratio of 4, but colorspace is {:?}\", cs);\n            }\n            cmyk2color_arr(&raw_data[..pixel_count * 4], alpha, mode)\n        }\n        _ => unimplemented!(\"data/pixel ratio {}\", data_ratio),\n    };\n\n    let data_len = data.len();\n    match ImageData::new(data, image.width as u32, image.height as u32) {\n        Some(data) => Ok(data),\n        None => {\n            warn!(\"image width: {}\", image.width);\n            warn!(\"image height: {}\", image.height);\n            warn!(\"data.len(): {}\", data_len);\n            warn!(\"data_ratio: {data_ratio}\");\n            Err(PdfError::Other { msg: \"size mismatch\".into() })\n        }\n    }\n}\n\nfn rgb2rgba(c: &[u8], a: u8, mode: BlendMode) -> ColorU {\n    match mode {\n        BlendMode::Overlay => {\n            ColorU { r: c[0], g: c[1], b: c[2], a }\n        }\n        BlendMode::Darken => {\n            ColorU { r: 255 - c[0], g: 255 - c[1], b: 255 - c[2], a }\n        }\n    }\n    \n}\nfn rgb2rgb(r: f32, g: f32, b: f32, mode: BlendMode) -> [u8; 3] {\n    match mode {\n        BlendMode::Overlay => {\n            [ (255. * r) as u8, (255. * g) as u8, (255. * b) as u8 ]\n        }\n        BlendMode::Darken => {\n            [ 255 - (255. * r) as u8, 255 - (255. * g) as u8, 255 - (255. * b) as u8 ]\n        }\n    }\n    \n}\n/*\nred = 1.0 – min ( 1.0, cyan + black )\ngreen = 1.0 – min ( 1.0, magenta + black )\nblue = 1.0 – min ( 1.0, yellow + black )\n*/\n\n#[inline]\nfn cmyk2rgb([c, m, y, k]: [u8; 4], mode: BlendMode) -> [u8; 3] {\n    match mode {\n        BlendMode::Darken => {\n            let r = 255 - c.saturating_add(k);\n            let g = 255 - m.saturating_add(k);\n            let b = 255 - y.saturating_add(k);\n            [r, g, b]\n        }\n        BlendMode::Overlay => {\n            let (c, m, y, k) = (255 - c, 255 - m, 255 - y, 255 - k);\n            let r = 255 - c.saturating_add(k);\n            let g = 255 - m.saturating_add(k);\n            let b = 255 - y.saturating_add(k);\n            [r, g, b]\n        }\n    }\n}\n\n#[inline]\nfn cmyk2color(cmyk: [u8; 4], a: u8, mode: BlendMode) -> ColorU {\n    let [r, g, b] = cmyk2rgb(cmyk, mode);\n    ColorU::new(r, g, b, a)\n}\n\nfn cmyk2color_arr(data: &[u8], alpha: impl Iterator<Item=u8>, mode: BlendMode) -> Vec<ColorU> {\n    data.chunks_exact(4).zip(alpha).map(|(c, a)| {\n        let mut buf = [0; 4];\n        buf.copy_from_slice(c);\n        cmyk2color(buf, a, mode)\n    }).collect()\n}\n\n"
  },
  {
    "path": "render/src/lib.rs",
    "content": "#[macro_use] extern crate log;\n#[macro_use] extern crate pdf;\n\nmacro_rules! assert_eq {\n    ($a:expr, $b:expr) => {\n        if $a != $b {\n            return Err(pdf::error::PdfError::Other { msg: format!(\"{} ({}) != {} ({})\", stringify!($a), $a, stringify!($b), $b)});\n        }\n        \n    };\n}\n\nmacro_rules! unimplemented {\n    ($msg:tt $(, $arg:expr)*) => {\n        return Err(pdf::error::PdfError::Other { msg: format!(concat!(\"Unimplemented: \", $msg) $(, $arg)*) })\n    };\n}\n\nmod cache;\nmod fontentry;\nmod graphicsstate;\nmod renderstate;\nmod textstate;\nmod backend;\npub mod tracer;\nmod image;\nmod scene;\nmod font;\n\npub use cache::{Cache};\npub use fontentry::{FontEntry};\npub use backend::{DrawMode, Backend, BlendMode, FillMode};\npub use scene::SceneBackend;\npub use crate::image::{load_image, ImageData};\nuse custom_debug_derive::Debug;\n\nuse pdf::{object::*, content::TextMode};\nuse pdf::error::PdfError;\nuse pathfinder_geometry::{\n    vector::{Vector2F},\n    rect::RectF, transform2d::Transform2F,\n};\nuse renderstate::RenderState;\nuse std::sync::Arc;\nuse itertools::Itertools;\nconst SCALE: f32 = 25.4 / 72.;\n\n\n#[derive(Copy, Clone, Default)]\npub struct BBox(Option<RectF>);\nimpl BBox {\n    pub fn empty() -> Self {\n        BBox(None)\n    }\n    pub fn add(&mut self, r2: RectF) {\n        self.0 = Some(match self.0 {\n            Some(r1) => r1.union_rect(r2),\n            None => r2\n        });\n    }\n    pub fn add_bbox(&mut self, bb: Self) {\n        if let Some(r) = bb.0 {\n            self.add(r);\n        }\n    }\n    pub fn rect(self) -> Option<RectF> {\n        self.0\n    }\n}\nimpl From<RectF> for BBox {\n    fn from(r: RectF) -> Self {\n        BBox(Some(r))\n    }\n}\n\n\npub fn page_bounds(page: &Page) -> RectF {\n    let Rect { left, right, top, bottom } = page.media_box().expect(\"no media box\");\n    RectF::from_points(Vector2F::new(left, bottom), Vector2F::new(right, top)) * SCALE\n}\npub fn render_page(backend: &mut impl Backend, resolve: &impl Resolve, page: &Page, transform: Transform2F) -> Result<Transform2F, PdfError> {\n    let bounds = page_bounds(page);\n    let rotate = Transform2F::from_rotation(page.rotate as f32 * std::f32::consts::PI / 180.);\n    let br = rotate * RectF::new(Vector2F::zero(), bounds.size());\n    let translate = Transform2F::from_translation(Vector2F::new(\n        -br.min_x().min(br.max_x()),\n        -br.min_y().min(br.max_y()),\n    ));\n    let view_box = transform * translate * br;\n    backend.set_view_box(view_box);\n    \n    let root_transformation = transform\n        * translate\n        * rotate\n        * Transform2F::row_major(SCALE, 0.0, -bounds.min_x(), 0.0, -SCALE, bounds.max_y());\n    \n    let resources = t!(page.resources());\n\n    let contents = try_opt!(page.contents.as_ref());\n    let ops = contents.operations(resolve)?;\n    let mut renderstate = RenderState::new(backend, resolve, &resources, root_transformation);\n    for (i, op) in ops.iter().enumerate() {\n        debug!(\"op {}: {:?}\", i, op);\n        renderstate.draw_op(op, i)?;\n    }\n\n    Ok(root_transformation)\n}\npub fn render_pattern(backend: &mut impl Backend, pattern: &Pattern, resolve: &impl Resolve) -> Result<(), PdfError> {\n    match pattern {\n        Pattern::Stream(ref dict, ref ops) => {\n            let resources = resolve.get(dict.resources)?;\n            let mut renderstate = RenderState::new(backend, resolve, &*resources, Transform2F::default());\n            for (i, op) in ops.iter().enumerate() {\n                debug!(\"op {}: {:?}\", i, op);\n                renderstate.draw_op(op, i)?;\n            }\n        }\n        Pattern::Dict(_) => {}\n    }\n    Ok(())\n}\n\n\n#[derive(Copy, Clone, PartialEq, Debug)]\npub enum Fill {\n    Solid(f32, f32, f32),\n    Pattern(Ref<Pattern>),\n}\nimpl Fill {\n    pub fn black() -> Self {\n        Fill::Solid(0., 0., 0.)\n    }\n}\n\n#[derive(Debug)]\npub struct TextSpan {\n    // A rect with the origin at the baseline, a height of 1em and width that corresponds to the advance width.\n    pub rect: RectF,\n\n    // width in textspace units (before applying transform)\n    pub width: f32,\n    // Bounding box of the rendered outline\n    pub bbox: Option<RectF>,\n    pub font_size: f32,\n    #[debug(skip)]\n    pub font: Option<Arc<FontEntry>>,\n    pub text: String,\n    pub chars: Vec<TextChar>,\n    pub color: Fill,\n    pub alpha: f32,\n\n    // apply this transform to a text draw in at the origin with the given width and font-size\n    pub transform: Transform2F,\n    pub mode: TextMode,\n    pub op_nr: usize,\n}\nimpl TextSpan {\n    pub fn parts(&self) -> impl Iterator<Item=Part> + '_ {\n        self.chars.iter().cloned()\n            .chain(std::iter::once(TextChar { offset: self.text.len(), pos: self.width, width: 0.0 }))\n            .tuple_windows()\n            .map(|(a, b)| Part {\n                text: &self.text[a.offset..b.offset],\n                pos: a.pos,\n                width: a.width,\n                offset: a.offset\n            })\n    }\n    pub fn rparts(&self) -> impl Iterator<Item=Part> + '_ {\n        self.chars.iter().cloned()\n            .chain(std::iter::once(TextChar { offset: self.text.len(), pos: self.width, width: 0.0 })).rev()\n            .tuple_windows()\n            .map(|(b, a)| Part {\n                text: &self.text[a.offset..b.offset],\n                pos: a.pos,\n                width: a.width,\n                offset: a.offset\n            })\n    }\n}\npub struct Part<'a> {\n    pub text: &'a str,\n    pub pos: f32,\n    pub width: f32,\n    pub offset: usize,\n}\n#[derive(Debug, Clone, Copy)]\npub struct TextChar {\n    pub offset: usize,\n    pub pos: f32,\n    pub width: f32,\n}"
  },
  {
    "path": "render/src/renderstate.rs",
    "content": "use pathfinder_content::outline::ContourIterFlags;\nuse pathfinder_renderer::scene::ClipPath;\nuse pdf::object::*;\nuse pdf::primitive::{Primitive, Dictionary};\nuse pdf::content::{Op, Matrix, Point, Rect, Color, Rgb, Cmyk, Winding, FormXObject};\nuse pdf::error::{PdfError, Result};\nuse pdf::content::TextDrawAdjusted;\nuse crate::backend::{Backend, BlendMode, Stroke, FillMode};\n\nuse pathfinder_geometry::{\n    vector::Vector2F,\n    rect::RectF, transform2d::Transform2F,\n};\nuse pathfinder_content::{\n    fill::FillRule,\n    stroke::{LineCap, LineJoin, StrokeStyle},\n    outline::{Outline, Contour},\n};\nuse super::{\n    graphicsstate::GraphicsState,\n    textstate::{TextState, Span},\n    DrawMode,\n    TextSpan,\n    Fill,\n};\n\ntrait Cvt {\n    type Out;\n    fn cvt(self) -> Self::Out;\n}\nimpl Cvt for Point {\n    type Out = Vector2F;\n    fn cvt(self) -> Self::Out {\n        Vector2F::new(self.x, self.y)\n    }\n}\nimpl Cvt for Matrix {\n    type Out = Transform2F;\n    fn cvt(self) -> Self::Out {\n        let Matrix { a, b, c, d, e, f } = self;\n        Transform2F::row_major(a, c, e, b, d, f)\n    }\n}\nimpl Cvt for Rect {\n    type Out = RectF;\n    fn cvt(self) -> Self::Out {\n        RectF::new(\n            Vector2F::new(self.x, self.y),\n            Vector2F::new(self.width, self.height)\n        )\n    }\n}\nimpl Cvt for Winding {\n    type Out = FillRule;\n    fn cvt(self) -> Self::Out {\n        match self {\n            Winding::NonZero => FillRule::Winding,\n            Winding::EvenOdd => FillRule::EvenOdd\n        }\n    }\n}\nimpl Cvt for Rgb {\n    type Out = (f32, f32, f32);\n    fn cvt(self) -> Self::Out {\n        let Rgb { red, green, blue } = self;\n        (red, green, blue)\n    }\n}\nimpl Cvt for Cmyk {\n    type Out = (f32, f32, f32, f32);\n    fn cvt(self) -> Self::Out {\n        let Cmyk { cyan, magenta, yellow, key } = self;\n        (cyan, magenta, yellow, key)\n    }\n}\n\npub struct RenderState<'a, R: Resolve, B: Backend> {\n    graphics_state: GraphicsState<'a, B>,\n    text_state: TextState,\n    stack: Vec<(GraphicsState<'a, B>, TextState)>,\n    current_outline: Outline,\n    current_contour: Contour,\n    resolve: &'a R,\n    resources: &'a Resources,\n    backend: &'a mut B,\n}\n\nimpl<'a, R: Resolve, B: Backend> RenderState<'a, R, B> {\n    pub fn new(backend: &'a mut B, resolve: &'a R, resources: &'a Resources, root_transformation: Transform2F) -> Self {\n        let graphics_state = GraphicsState {\n            transform: root_transformation,\n            fill_color: Fill::black(),\n            fill_color_alpha: 1.0,\n            fill_paint: None,\n            fill_alpha: 1.0,\n            stroke_color: Fill::black(),\n            stroke_color_alpha: 1.0,\n            stroke_paint: None,\n            stroke_alpha: 1.0,\n            clip_path_id: None,\n            clip_path: None,\n            clip_path_rect: None,\n            fill_color_space: &ColorSpace::DeviceRGB,\n            stroke_color_space: &ColorSpace::DeviceRGB,\n            stroke_style: StrokeStyle {\n                line_cap: LineCap::Butt,\n                line_join: LineJoin::Miter(1.0),\n                line_width: 1.0,\n            },\n            dash_pattern: None,\n            overprint_fill: false,\n            overprint_stroke: false,\n            overprint_mode: 0,\n        };\n        let text_state = TextState::new();\n        let stack = vec![];\n        let current_outline = Outline::new();\n        let current_contour = Contour::new();\n\n        RenderState {\n            graphics_state,\n            text_state,\n            stack,\n            current_outline,\n            current_contour,\n            resources,\n            resolve,\n            backend,\n        }\n    }\n    fn draw(&mut self, mode: &DrawMode, fill_rule: FillRule) {\n        self.flush();\n        self.backend.draw(&self.current_outline, mode, fill_rule, self.graphics_state.transform, self.graphics_state.clip_path_id);\n        self.current_outline.clear();\n    }\n    #[allow(unused_variables)]\n    pub fn draw_op(&mut self, op: &'a Op, op_nr: usize) -> Result<()> {\n        self.backend.inspect_op(op);\n        self.backend.bug_op(op_nr);\n        match *op {\n            Op::BeginMarkedContent { .. } => {}\n            Op::EndMarkedContent { .. } => {}\n            Op::MarkedContentPoint { .. } => {}\n            Op::Close => {\n                self.current_contour.close();\n            }\n            Op::MoveTo { p } => {\n                self.flush();\n                self.current_contour.push_endpoint(p.cvt());\n            },\n            Op::LineTo { p } => {\n                self.current_contour.push_endpoint(p.cvt());\n            },\n            Op::CurveTo { c1, c2, p } => {\n                self.current_contour.push_cubic(c1.cvt(), c2.cvt(), p.cvt());\n            },\n            Op::Rect { rect } => {\n                self.flush();\n                self.current_outline.push_contour(Contour::from_rect(rect.cvt()));\n            },\n            Op::EndPath => {\n                self.current_contour.clear();\n                self.current_outline.clear();\n            }\n            Op::Stroke => {\n                self.draw(&DrawMode::Stroke { \n                    stroke: FillMode {\n                        color: self.graphics_state.stroke_color,\n                        alpha: self.graphics_state.stroke_color_alpha,\n                        mode: self.blend_mode_stroke(),\n                    },\n                    stroke_mode: self.graphics_state.stroke()},\n                    FillRule::Winding\n                );\n            },\n            Op::FillAndStroke { winding } => {\n                self.draw(&DrawMode::FillStroke {\n                    fill: FillMode {\n                        color: self.graphics_state.fill_color,\n                        alpha: self.graphics_state.fill_color_alpha,\n                        mode: self.blend_mode_fill(),\n                    },\n                    stroke: FillMode {\n                        color: self.graphics_state.stroke_color,\n                        alpha: self.graphics_state.stroke_color_alpha,\n                        mode: self.blend_mode_stroke()\n                    },\n                    stroke_mode: self.graphics_state.stroke()\n                }, winding.cvt());\n            }\n            Op::Fill { winding } => {\n                self.draw(&DrawMode::Fill {\n                    fill: FillMode {\n                        color: self.graphics_state.fill_color,\n                        alpha: self.graphics_state.fill_color_alpha,\n                        mode: self.blend_mode_fill(),\n                    },\n            }, winding.cvt());\n            }\n            Op::Shade { ref name } => {},\n            Op::Clip { winding } => {\n                self.flush();\n                let mut path = self.current_outline.clone().transformed(&self.graphics_state.transform);\n                let clip_path_rect = to_rect(&path);\n\n                let (path, r, parent) = match (self.graphics_state.clip_path_rect, clip_path_rect, self.graphics_state.clip_path_id) {\n                    (Some(r1), Some(r2), Some(p)) => {\n                        let r = r1.intersection(r2).unwrap_or_default();\n                        (Outline::from_rect(r), Some(r), None)\n                    }\n                    (Some(r), None, Some(p)) => {\n                        path.clip_against_polygon(&[r.origin(), r.upper_right(), r.lower_right(), r.lower_left()]);\n                        (path, None, None)\n                    }\n                    (None, Some(r), Some(p)) => {\n                        let mut path = self.graphics_state.clip_path.as_ref().unwrap().outline.clone();\n                        path.clip_against_polygon(&[r.origin(), r.upper_right(), r.lower_right(), r.lower_left()]);\n                        (path, None, None)\n                    }\n                    (None, Some(r), None) => {\n                        (path, Some(r), None)\n                    }\n                    (None, None, Some(p)) => (path, None, Some(p)),\n                    (None, None, None) => (path, None, None),\n                    _ => unreachable!()\n                };\n\n                let id = self.backend.create_clip_path(path.clone(), winding.cvt(), parent);\n                self.graphics_state.clip_path_id = Some(id);\n                let mut clip = ClipPath::new(path);\n                clip.set_fill_rule(winding.cvt());\n                self.graphics_state.clip_path = Some(clip);\n                self.graphics_state.clip_path_rect = r;\n            },\n\n            Op::Save => {\n                self.stack.push((self.graphics_state.clone(), self.text_state.clone()));\n            },\n            Op::Restore => {\n                let (g, t) = self.stack.pop().ok_or_else(|| pdf::error::PdfError::Other { msg: \"graphcs stack is empty\".into() })?;\n                self.graphics_state = g;\n                self.text_state = t;\n            },\n\n            Op::Transform { matrix } => {\n                self.graphics_state.transform = self.graphics_state.transform * matrix.cvt();\n            }\n            Op::LineWidth { width } => self.graphics_state.stroke_style.line_width = width,\n            Op::Dash { ref pattern, phase } => self.graphics_state.dash_pattern = Some((&*pattern, phase)),\n            Op::LineJoin { join } => {},\n            Op::LineCap { cap } => {},\n            Op::MiterLimit { limit } => {},\n            Op::Flatness { tolerance } => {},\n            Op::GraphicsState { ref name } => {\n                let gs = try_opt!(self.resources.graphics_states.get(name));\n                debug!(\"GS: {gs:?}\");\n                if let Some(lw) = gs.line_width {\n                    self.graphics_state.stroke_style.line_width = lw;\n                }\n                self.graphics_state.set_fill_alpha(gs.fill_alpha.unwrap_or(1.0));\n                self.graphics_state.set_stroke_alpha(gs.stroke_alpha.unwrap_or(1.0));\n                \n                if let Some((font_ref, size)) = gs.font {\n                    let font = self.resolve.get(font_ref)?;\n                    if let Some(e) = self.backend.get_font(&MaybeRef::Indirect(font), self.resolve)? {\n                        debug!(\"new font: {} at size {}\", e.name, size);\n                        self.text_state.font_entry = Some(e);\n                        self.text_state.font_size = size;\n                    } else {\n                        self.text_state.font_entry = None;\n                    }\n                }\n                if let Some(op) = gs.overprint {\n                    self.graphics_state.overprint_fill = op;\n                    self.graphics_state.overprint_stroke = op;\n                }\n                if let Some(op) = gs.overprint_fill {\n                    self.graphics_state.overprint_fill = op;\n                }\n                if let Some(m) = gs.overprint_mode {\n                    self.graphics_state.overprint_mode = m;\n                }\n            },\n            Op::StrokeColor { ref color } => {\n                let mode = self.blend_mode_stroke();\n                let color = t!(convert_color(&mut self.graphics_state.stroke_color_space, color, &self.resources, self.resolve, mode));\n                self.graphics_state.set_stroke_color(color);\n            },\n            Op::FillColor { ref color } => {\n                let mode = self.blend_mode_fill();\n                let color = t!(convert_color(&mut self.graphics_state.fill_color_space, color, &self.resources, self.resolve, mode));\n                self.graphics_state.set_fill_color(color);\n            },\n            Op::FillColorSpace { ref name } => {\n                self.graphics_state.fill_color_space = self.color_space(name)?;\n                self.graphics_state.set_fill_color(Fill::black());\n            },\n            Op::StrokeColorSpace { ref name } => {\n                self.graphics_state.stroke_color_space = self.color_space(name)?;\n                self.graphics_state.set_stroke_color(Fill::black());\n            },\n            Op::RenderingIntent { intent } => {},\n            Op::BeginText => self.text_state.reset_matrix(),\n            Op::EndText => {},\n            Op::CharSpacing { char_space } => self.text_state.char_space = char_space,\n            Op::WordSpacing { word_space } => self.text_state.word_space = word_space,\n            Op::TextScaling { horiz_scale } => self.text_state.horiz_scale = 0.01 * horiz_scale,\n            Op::Leading { leading } => self.text_state.leading = leading,\n            Op::TextFont { ref name, size } => {\n                let font = match self.resources.fonts.get(name) {\n                    Some(font_ref) => {\n                        let font = font_ref.load(self.resolve)?;\n                        self.backend.get_font(&font, self.resolve)?\n                    },\n                    None => None\n                };\n                if let Some(e) = font {\n                    debug!(\"new font: {} (is_cid={:?})\", e.name, e.is_cid);\n                    self.text_state.font_entry = Some(e);\n                    self.text_state.font_size = size;\n                } else {\n                    info!(\"no font {}\", name);\n                    self.text_state.font_entry = None;\n                }\n            },\n            Op::TextRenderMode { mode } => self.text_state.mode = mode,\n            Op::TextRise { rise } => self.text_state.rise = rise,\n            Op::MoveTextPosition { translation } => self.text_state.translate(translation.cvt()),\n            Op::SetTextMatrix { matrix } => self.text_state.set_matrix(matrix.cvt()),\n            Op::TextNewline => self.text_state.next_line(),\n            Op::TextDraw { ref text } => {\n                let fill_mode = self.blend_mode_fill();\n                let stroke_mode = self.blend_mode_stroke();\n                self.text(|backend, text_state, graphics_state, span| {\n                    text_state.draw_text(backend, graphics_state, &text.data, span, fill_mode, stroke_mode);\n                }, op_nr);\n            },\n            Op::TextDrawAdjusted { ref array } => {\n                let fill_mode = self.blend_mode_fill();\n                let stroke_mode = self.blend_mode_stroke();\n                self.text(|backend, text_state, graphics_state, span| {\n                    for arg in array {\n                        match *arg {\n                            TextDrawAdjusted::Text(ref data) => {\n                                text_state.draw_text(backend, graphics_state, data.as_bytes(), span, fill_mode, stroke_mode);\n                            },\n                            TextDrawAdjusted::Spacing(offset) => {\n                                // because why not PDF…\n                                let advance = text_state.advance(-0.001 * offset);\n                                span.width += advance;\n                            }\n                        }\n                    }\n                }, op_nr);\n            },\n            Op::XObject { ref name } => {\n                let &xobject_ref = self.resources.xobjects.get(name).ok_or(PdfError::NotFound { word: name.as_str().into()})?;\n                let xobject = self.resolve.get(xobject_ref)?;\n                let mode = self.blend_mode_fill();\n                match *xobject {\n                    XObject::Image(ref im) => {\n                        self.backend.draw_image(xobject_ref, im, self.resources, self.graphics_state.transform, mode, self.graphics_state.clip_path_id, self.resolve);\n                    }\n                    XObject::Form(ref content) => {\n                        self.draw_form(content)?;\n                    }\n                    XObject::Postscript(ref ps) => {\n                        let data = ps.data(self.resolve)?;\n                        self.backend.bug_postscript(&data);\n                        warn!(\"Got PostScript?!\");\n                    }\n                }\n            },\n            Op::InlineImage { ref image } => {\n                let mode = self.blend_mode_fill();\n                self.backend.draw_inline_image(image, &self.resources, self.graphics_state.transform, mode, self.graphics_state.clip_path_id, self.resolve);\n            }\n        }\n\n        Ok(())\n    }\n\n    fn blend_mode_fill(&self) -> BlendMode {\n        if self.graphics_state.overprint_fill {\n            BlendMode::Darken\n        } else {\n            BlendMode::Overlay\n        }\n    }\n    fn blend_mode_stroke(&self) -> BlendMode {\n        if self.graphics_state.overprint_stroke {\n            BlendMode::Darken\n        } else {\n            BlendMode::Overlay\n        }\n    }\n\n    fn text(&mut self, inner: impl FnOnce(&mut B, &mut TextState, &mut GraphicsState<B>, &mut Span), op_nr: usize) {\n        let mut span = Span::default();\n        let tm = self.text_state.text_matrix;\n        let origin = tm.translation();\n\n        inner(&mut self.backend, &mut self.text_state, &mut self.graphics_state, &mut span);\n\n        let transform = self.graphics_state.transform * tm * Transform2F::from_scale(Vector2F::new(1.0, -1.0));\n        let p1 = origin;\n        let p2 = (tm * Transform2F::from_translation(Vector2F::new(span.width, self.text_state.font_size))).translation();\n        let clip = self.graphics_state.clip_path_id;\n\n        debug!(\"text {}\", span.text);\n        self.backend.add_text(TextSpan {\n            rect: self.graphics_state.transform * RectF::from_points(p1.min(p2), p1.max(p2)),\n            width: span.width,\n            bbox: span.bbox.rect(),\n            text: span.text,\n            chars: span.chars,\n            font: self.text_state.font_entry.clone(),\n            font_size: self.text_state.font_size,\n            color: self.graphics_state.fill_color,\n            alpha: self.graphics_state.fill_color_alpha,\n            mode: self.text_state.mode,\n            transform,\n            op_nr\n        }, clip);\n    }\n\n    fn color_space(&self, name: &str) -> Result<&'a ColorSpace> {\n        match name {\n            \"DeviceGray\" => return Ok(&ColorSpace::DeviceGray),\n            \"DeviceRGB\" => return Ok(&ColorSpace::DeviceRGB),\n            \"DeviceCMYK\" => return Ok(&ColorSpace::DeviceCMYK),\n            \"Pattern\" => return Ok(&ColorSpace::Pattern),\n            _ => {}\n        }\n        match self.resources.color_spaces.get(name) {\n            Some(cs) => Ok(cs),\n            None => Err(PdfError::Other { msg: format!(\"color space {:?} not present\", name) })\n        }\n    }\n    fn flush(&mut self) {\n        if !self.current_contour.is_empty() {\n            self.current_outline.push_contour(self.current_contour.clone());\n            self.current_contour.clear();\n        }\n    }\n    fn draw_form(&mut self, form: &FormXObject) -> Result<()> {\n        let graphics_state = GraphicsState {\n            stroke_alpha: self.graphics_state.stroke_color_alpha,\n            fill_alpha: self.graphics_state.fill_color_alpha,\n            clip_path_id: self.graphics_state.clip_path_id,\n            clip_path: self.graphics_state.clip_path.clone(),\n            .. self.graphics_state\n        };\n        let resources = match form.dict().resources {\n            Some(ref r) => &*r,\n            None => self.resources\n        };\n\n        let mut inner = RenderState {\n            graphics_state: graphics_state,\n            text_state: self.text_state.clone(),\n            resources,\n            stack: vec![],\n            current_outline: Outline::new(),\n            current_contour: Contour::new(),\n            backend: self.backend,\n            resolve: self.resolve,\n        };\n        \n        let ops = t!(form.operations(self.resolve));\n        for (i, op) in ops.iter().enumerate() {\n            debug!(\" form op {}: {:?}\", i, op);\n            inner.draw_op(op, i)?;\n        }\n\n        Ok(())\n    }\n    #[allow(dead_code)]\n    fn get_properties<'b>(&'b self, p: &'b Primitive) -> Result<&'b Dictionary> {\n        match p {\n            Primitive::Dictionary(ref dict) => Ok(dict),\n            Primitive::Name(ref name) => self.resources.properties.get(name.as_str())\n                .map(|rc| &**rc)\n                .ok_or_else(|| {\n                    PdfError::MissingEntry { typ: \"Properties\", field: name.into() }\n                }),\n            p => Err(PdfError::UnexpectedPrimitive {\n                expected: \"Dictionary or Name\",\n                found: p.get_debug_name()\n            })\n        }\n    }\n}\n\nfn convert_color<'a>(cs: &mut &'a ColorSpace, color: &Color, resources: &Resources, resolve: &impl Resolve, mode: BlendMode) -> Result<Fill> {\n    match convert_color2(cs, color, resources, mode) {\n        Ok(color) => Ok(color),\n        Err(e) if resolve.options().allow_error_in_option => {\n            warn!(\"failed to convert color: {:?}\", e);\n            Ok(Fill::Solid(0.0, 0.0, 0.0))\n        }\n        Err(e) => Err(e)\n    }\n}\n#[allow(unused_variables)]\nfn convert_color2<'a>(cs: &mut &'a ColorSpace, color: &Color, resources: &Resources, mode: BlendMode) -> Result<Fill> {\n    match *color {\n        Color::Gray(g) => {\n            *cs = &ColorSpace::DeviceGray;\n            Ok(gray2rgb(g))\n        }\n        Color::Rgb(rgb) => {\n            *cs = &ColorSpace::DeviceRGB;\n            let (r, g, b) = rgb.cvt();\n            Ok(Fill::Solid(r, g, b))\n        }\n        Color::Cmyk(cmyk) => {\n            *cs = &ColorSpace::DeviceCMYK;\n            Ok(cmyk2rgb(cmyk.cvt(), mode))\n        }\n        Color::Other(ref args) => {\n            let cs = match **cs {\n                ColorSpace::Icc(ref icc) => {\n                    match icc.info.alternate {\n                        Some(ref alt) => alt,\n                        None => {\n                            match args.len() {\n                                3 => &ColorSpace::DeviceRGB,\n                                4 => &ColorSpace::DeviceCMYK,\n                                _ => return Err(PdfError::Other { msg: format!(\"ICC profile without alternate color space\") })\n                            }\n                        }\n                    }\n                }\n                ColorSpace::Named(ref name) => {\n                    resources.color_spaces.get(name).ok_or_else(|| \n                        PdfError::Other { msg: format!(\"named color space {} not found\", name) }\n                    )?\n                }\n                _ => &**cs\n            };\n            \n            match *cs {\n                ColorSpace::Icc(_) => return Err(PdfError::Other { msg: format!(\"nested ICC color space\") }),\n                ColorSpace::DeviceGray | ColorSpace::CalGray(_) => {\n                    if args.len() != 1 {\n                        return Err(PdfError::Other { msg: format!(\"expected 1 color arguments, got {:?}\", args) });\n                    }\n                    let g = args[0].as_number()?;\n                    Ok(gray2rgb(g))\n                }\n                ColorSpace::DeviceRGB | ColorSpace::CalRGB(_) => {\n                    if args.len() != 3 {\n                        return Err(PdfError::Other { msg: format!(\"expected 3 color arguments, got {:?}\", args) });\n                    }\n                    let r = args[0].as_number()?;\n                    let g = args[1].as_number()?;\n                    let b = args[2].as_number()?;\n                    Ok(Fill::Solid(r, g, b))\n                }\n                ColorSpace::DeviceCMYK | ColorSpace::CalCMYK(_) => {\n                    if args.len() != 4 {\n                        return Err(PdfError::Other { msg: format!(\"expected 4 color arguments, got {:?}\", args) });\n                    }\n                    let c = args[0].as_number()?;\n                    let m = args[1].as_number()?;\n                    let y = args[2].as_number()?;\n                    let k = args[3].as_number()?;\n                    Ok(cmyk2rgb((c, m, y, k), mode))\n                }\n                ColorSpace::DeviceN { ref names, ref alt, ref tint, ref attr } => {\n                    assert_eq!(args.len(), tint.input_dim());\n                    let mut input = vec![0.; args.len()];\n                    for (i, a) in input.iter_mut().zip(args.iter()) {\n                        *i = a.as_number()?;\n                    }\n                    let mut out = vec![0.0; tint.output_dim()];\n                    tint.apply(&input, &mut out)?;\n\n                    let alt = match **alt {\n                        ColorSpace::Icc(ref icc) => icc.info.alternate.as_ref().map(|b| &**b),\n                        ref a => Some(a),\n                    };\n                    match alt {\n                        Some(ColorSpace::DeviceGray) => Ok(Fill::Solid(out[0], out[0], out[0])),\n                        Some(ColorSpace::DeviceRGB) => {\n                            Ok(Fill::Solid(out[0], out[1], out[2]))\n                        }\n                        Some(ColorSpace::DeviceCMYK) => {\n                            Ok(cmyk2rgb((out[0], out[1], out[2], out[3]), mode))\n                        }\n                        _ => unimplemented!(\"DeviceN colorspace\")\n                    }\n                }\n                ColorSpace::Separation(ref name, ref alt, ref f) => {\n                    debug!(\"Separation(name={}, alt={:?}, f={:?}\", name, alt, f);\n                    if args.len() != 1 {\n                        return Err(PdfError::Other { msg: format!(\"expected 1 color arguments, got {:?}\", args) });\n                    }\n                    let x = args[0].as_number()?;\n                    let cs = match **alt {\n                        ColorSpace::Icc(ref info) => &**info.alternate.as_ref().ok_or(\n                            PdfError::Other { msg: format!(\"no alternate color space in ICC profile {:?}\", info) }\n                        )?,\n                        _ => alt,\n                    };\n                    match cs {\n                        &ColorSpace::DeviceCMYK => {\n                            let mut cmyk = [0.0; 4];\n                            f.apply(&[x], &mut cmyk)?;\n                            let [c, m, y, k] = cmyk;\n                            //debug!(\"c={c}, m={m}, y={y}, k={k}\");\n                            Ok(cmyk2rgb((c, m, y, k), mode))\n                        },\n                        &ColorSpace::DeviceRGB => {\n                            let mut rgb = [0.0, 0.0, 0.0];\n                            f.apply(&[x], &mut rgb)?;\n                            let [r, g, b] = rgb;\n                            //debug!(\"r={r}, g={g}, b={b}\");\n                            Ok(Fill::Solid(r, g, b))\n                        },\n                        &ColorSpace::DeviceGray => {\n                            let mut gray = [0.0];\n                            f.apply(&[x], &mut gray)?;\n                            let [gray] = gray;\n                            //debug!(\"gray={gray}\");\n                            Ok(Fill::Solid(gray, gray, gray))\n                        }\n                        c => unimplemented!(\"Separation(alt={:?})\", c)\n                    }\n                }\n                ColorSpace::Indexed(ref cs, hival, ref lut) => {\n                    if args.len() != 1 {\n                        return Err(PdfError::Other { msg: format!(\"expected 1 color arguments, got {:?}\", args) });\n                    }\n                    let i = args[0].as_integer()?;\n                    match **cs {\n                        ColorSpace::DeviceRGB => {\n                            let c = &lut[3 * i as usize ..];\n                            let cvt = |b: u8| b as f32;\n                            Ok(Fill::Solid(cvt(c[0]), cvt(c[1]), cvt(c[2])))\n                        }\n                        ColorSpace::DeviceCMYK => {\n                            let c = &lut[4 * i as usize ..];\n                            let cvt = |b: u8| b as f32;\n                            Ok(cmyk2rgb((cvt(c[0]), cvt(c[1]), cvt(c[2]), cvt(c[3])), mode))\n                        }\n                        ref base => unimplemented!(\"Indexed colorspace with base {:?}\", base)\n                    }\n                }\n                ColorSpace::Pattern => {\n                    let name = args[0].as_name()?;\n                    if let Some(&pat) = resources.pattern.get(name) {\n                        Ok(Fill::Pattern(pat))\n                    } else {\n                        unimplemented!(\"Pattern {} not found\", name)\n                    }\n                }\n                ColorSpace::Other(ref p) => unimplemented!(\"Other Color space {:?}\", p),\n                ColorSpace::Named(ref p) => unimplemented!(\"nested Named {:?}\", p),\n            }\n        }\n    }\n}\n\nfn gray2rgb(g: f32) -> Fill {\n    Fill::Solid(g, g, g)\n}\n\nfn cmyk2rgb((c, m, y, k): (f32, f32, f32, f32), mode: BlendMode) -> Fill {\n    let clamp = |f| if f > 1.0 { 1.0 } else { f };\n    Fill::Solid(\n        1.0 - clamp(c + k),\n        1.0 - clamp(m + k),\n        1.0 - clamp(y + k),\n    )\n}\n\n\nfn to_rect(o: &Outline) -> Option<RectF> {\n    if o.contours().len() != 1 {\n        return None;\n    }\n\n    let c = &o.contours()[0];\n    if c.len() != 4 {\n        return None;\n    }\n\n    if !c.iter(ContourIterFlags::IGNORE_CLOSE_SEGMENT).all(|segment| {\n        let line = segment.baseline;\n        segment.is_line() && (line.from_x() == line.to_x()) ^ (line.from_y() == line.to_y())\n    }) {\n        return None;\n    }\n\n    Some(c.bounds())\n}\n"
  },
  {
    "path": "render/src/scene.rs",
    "content": "use pathfinder_color::{ColorF, ColorU};\nuse pathfinder_content::{\n    fill::FillRule,\n    stroke::{OutlineStrokeToFill},\n    outline::Outline,\n    pattern::{Pattern},\n    dash::OutlineDash,\n};\nuse pathfinder_renderer::{\n    scene::{DrawPath, ClipPath, ClipPathId, Scene},\n    paint::{PaintId, Paint},\n};\nuse pathfinder_geometry::{\n    vector::{Vector2F},\n    rect::RectF, transform2d::Transform2F,\n};\nuse pdf::object::{Ref, XObject, ImageXObject, Resolve, Resources, MaybeRef};\nuse crate::backend;\n\nuse super::{FontEntry, TextSpan, DrawMode, Backend, Fill, Cache};\nuse pdf::font::Font as PdfFont;\nuse pdf::error::PdfError;\nuse std::sync::Arc;\n\npub struct SceneBackend<'a> {\n    scene: Scene,\n    cache: &'a mut Cache,\n}\nimpl<'a> SceneBackend<'a> {\n    pub fn new(cache: &'a mut Cache) -> Self {\n        let scene = Scene::new();\n        SceneBackend {\n            scene,\n            cache\n        }\n    }\n    pub fn finish(self) -> Scene {\n        self.scene\n    }\n    fn paint(&mut self, fill: Fill, alpha: f32) -> PaintId {\n        let paint = match fill {\n            Fill::Solid(r, g, b) => Paint::from_color(ColorF::new(r, g, b, alpha).to_u8()),\n            Fill::Pattern(_) => {\n                Paint::black()\n            }\n        };\n        self.scene.push_paint(&paint)\n    }\n}\nimpl<'a> Backend for SceneBackend<'a> {\n    type ClipPathId = ClipPathId;\n    fn create_clip_path(&mut self, path: Outline, fill_rule: FillRule, parent: Option<Self::ClipPathId>) -> Self::ClipPathId {\n        let mut clip = ClipPath::new(path);\n        clip.set_fill_rule(fill_rule);\n        clip.set_clip_path(parent);\n        self.scene.push_clip_path(clip)\n    }\n    fn set_view_box(&mut self, view_box: RectF) {\n        self.scene.set_view_box(view_box);\n\n        let white = self.scene.push_paint(&Paint::from_color(ColorU::white()));\n        self.scene.push_draw_path(DrawPath::new(Outline::from_rect(view_box), white));\n\n    }\n    fn draw(&mut self, outline: &Outline, mode: &DrawMode, fill_rule: FillRule, transform: Transform2F, clip: Option<ClipPathId>) {\n        match mode {\n            DrawMode::Fill { fill } | DrawMode::FillStroke {fill, .. } => {\n                let paint = self.paint(fill.color, fill.alpha);\n                let mut draw_path = DrawPath::new(outline.clone().transformed(&transform), paint);\n                draw_path.set_clip_path(clip);\n                draw_path.set_fill_rule(fill_rule);\n                draw_path.set_blend_mode(blend_mode(fill.mode));\n                self.scene.push_draw_path(draw_path);\n            }\n            _ => {}\n        }\n        match mode {\n            DrawMode::Stroke { stroke, stroke_mode }| DrawMode::FillStroke { stroke, stroke_mode, .. } => {\n                let paint = self.paint(stroke.color, stroke.alpha);\n                let contour = match stroke_mode.dash_pattern {\n                    Some((ref pat, phase)) => {\n                        let dashed = OutlineDash::new(outline, &*pat, phase).into_outline();\n                        let mut stroke = OutlineStrokeToFill::new(&dashed, stroke_mode.style);\n                        stroke.offset();\n                        stroke.into_outline()\n                    }\n                    None => {\n                        let mut stroke = OutlineStrokeToFill::new(outline, stroke_mode.style);\n                        stroke.offset();\n                        stroke.into_outline()\n                    }\n                };\n                let mut draw_path = DrawPath::new(contour.transformed(&transform), paint);\n                draw_path.set_clip_path(clip);\n                draw_path.set_fill_rule(fill_rule);\n\n            draw_path.set_blend_mode(blend_mode(stroke.mode));\n                self.scene.push_draw_path(draw_path);\n            }\n            _ => {}\n        }\n    }\n    fn draw_image(&mut self, xobject_ref: Ref<XObject>, im: &ImageXObject, resources: &Resources, transform: Transform2F, mode: backend::BlendMode, clip: Option<ClipPathId>,  resolve: &impl Resolve) {\n        if let Ok(ref image) = *self.cache.get_image(xobject_ref, im, resources, resolve, mode).0 {\n            let size = image.size();\n            let size_f = size.to_f32();\n            let outline = Outline::from_rect(transform * RectF::new(Vector2F::default(), Vector2F::new(1.0, 1.0)));\n            let im_tr = transform\n                * Transform2F::from_scale(Vector2F::new(1.0 / size_f.x(), -1.0 / size_f.y()))\n                * Transform2F::from_translation(Vector2F::new(0.0, -size_f.y()));\n\n            let mut pattern = Pattern::from_image(image.clone());\n            pattern.apply_transform(im_tr);\n            let paint = Paint::from_pattern(pattern);\n            let paint_id = self.scene.push_paint(&paint);\n            let mut draw_path = DrawPath::new(outline, paint_id);\n            draw_path.set_clip_path(clip);\n            draw_path.set_blend_mode(blend_mode(mode));\n\n            self.scene.push_draw_path(draw_path);\n        }\n    }\n    fn draw_inline_image(&mut self, _im: &Arc<ImageXObject>, _resources: &Resources, _transform: Transform2F, mode: backend::BlendMode, clip: Option<ClipPathId>, _resolve: &impl Resolve) {\n\n    }\n\n    fn get_font(&mut self, font_ref: &MaybeRef<PdfFont>, resolve: &impl Resolve) -> Result<Option<Arc<FontEntry>>, PdfError> {\n        self.cache.get_font(font_ref, resolve)\n    }\n    fn add_text(&mut self, span: TextSpan, clip: Option<Self::ClipPathId>) {}\n}\n\nfn blend_mode(mode: backend::BlendMode) -> pathfinder_content::effects::BlendMode {\n    match mode {\n        crate::BlendMode::Darken => pathfinder_content::effects::BlendMode::Multiply,\n        crate::BlendMode::Overlay => pathfinder_content::effects::BlendMode::Overlay,\n    }\n}"
  },
  {
    "path": "render/src/textstate.rs",
    "content": "use pathfinder_geometry::{\n    vector::Vector2F,\n    transform2d::Transform2F,\n};\nuse font::GlyphId;\nuse crate::{BlendMode, backend::{FillMode, Stroke}};\n\nuse super::{\n    BBox,\n    fontentry::{FontEntry},\n    graphicsstate::{GraphicsState},\n    DrawMode,\n    Backend,\n    TextChar,\n};\nuse std::convert::TryInto;\nuse pdf::content::TextMode;\nuse std::sync::Arc;\nuse itertools::Either;\nuse istring::SmallString;\n\n#[derive(Clone)]\npub struct TextState {\n    pub text_matrix: Transform2F, // tracks current glyph\n    pub line_matrix: Transform2F, // tracks current line\n    pub char_space: f32, // Character spacing\n    pub word_space: f32, // Word spacing\n    pub horiz_scale: f32, // Horizontal scaling\n    pub leading: f32, // Leading\n    pub font_entry: Option<Arc<FontEntry>>, // Text font\n    pub font_size: f32, // Text font size\n    pub mode: TextMode, // Text rendering mode\n    pub rise: f32, // Text rise\n    pub knockout: f32, //Text knockout\n}\nimpl TextState {\n    pub fn new() -> TextState {\n        TextState {\n            text_matrix: Transform2F::default(),\n            line_matrix: Transform2F::default(),\n            char_space: 0.,\n            word_space: 0.,\n            horiz_scale: 1.,\n            leading: 0.,\n            font_entry: None,\n            font_size: 0.,\n            mode: TextMode::Fill,\n            rise: 0.,\n            knockout: 0.\n        }\n    }\n    pub fn reset_matrix(&mut self) {\n        self.set_matrix(Transform2F::default());\n    }\n    pub fn translate(&mut self, v: Vector2F) {\n        let m = self.line_matrix * Transform2F::from_translation(v);\n        self.set_matrix(m);\n    }\n    \n    // move to the next line\n    pub fn next_line(&mut self) {\n        self.translate(Vector2F::new(0., -self.leading));\n    }\n    // set text and line matrix\n    pub fn set_matrix(&mut self, m: Transform2F) {\n        self.text_matrix = m;\n        self.line_matrix = m;\n    }\n    pub fn draw_text<B: Backend>(&mut self, backend: &mut B, gs: &GraphicsState<B>, data: &[u8], span: &mut Span, fill_mode: BlendMode, stroke_mode: BlendMode) {\n        let e = match self.font_entry {\n            Some(ref e) => e,\n            None => {\n                debug!(\"no font set\");\n                return;\n            }\n        };\n\n        let codepoints = if e.is_cid {\n            Either::Left(data.chunks_exact(2).map(|s| u16::from_be_bytes(s.try_into().unwrap())))\n        } else {\n            Either::Right(data.iter().map(|&b| b as u16))\n        };\n\n        let glyphs = codepoints.map(|cid|\n            (cid, e.cmap.get(&cid).map(|&(gid, ref uni)| (gid, uni.clone())))\n        );\n\n        let fill = FillMode { color: gs.fill_color, alpha: gs.fill_color_alpha, mode: fill_mode };\n        let stroke = FillMode { color: gs.stroke_color, alpha: gs.stroke_color_alpha, mode: stroke_mode };\n        let stroke_mode = gs.stroke();\n\n        let draw_mode = match self.mode {\n            TextMode::Fill => Some(DrawMode::Fill { fill }),\n            TextMode::FillAndClip => Some(DrawMode::Fill { fill }),\n            TextMode::FillThenStroke => Some(DrawMode::FillStroke { fill, stroke, stroke_mode }),\n            TextMode::Invisible => None,\n            TextMode::Stroke => Some(DrawMode::Stroke { stroke, stroke_mode }),\n            TextMode::StrokeAndClip => Some(DrawMode::Stroke { stroke, stroke_mode }),\n        };\n        let e = self.font_entry.as_ref().expect(\"no font\");\n\n        let tr = Transform2F::row_major(\n            self.horiz_scale * self.font_size, 0., 0.,\n            0., self.font_size, self.rise\n        ) * e.font.font_matrix();\n        \n        for (cid, t) in glyphs {\n            let (gid, unicode, is_space) = match t {\n                Some((gid, unicode)) => {\n                    let is_space = !e.is_cid && unicode.as_deref() == Some(\" \");\n                    (gid, unicode, is_space)\n                }\n                None => (GlyphId(0), None, cid == 0x20)\n            };\n            //debug!(\"cid {} -> gid {:?} {:?}\", cid, gid, unicode);\n            \n            let glyph = e.font.glyph(gid);\n            let width: f32 = e.widths.as_ref().map(|w| w.get(cid as usize) * 0.001 * self.horiz_scale * self.font_size)\n                .or_else(|| glyph.as_ref().map(|g| tr.m11() * g.metrics.advance))\n                .unwrap_or(0.0);\n            \n            if is_space {\n                let advance = (self.char_space + self.word_space) * self.horiz_scale + width;\n                self.text_matrix = self.text_matrix * Transform2F::from_translation(Vector2F::new(advance, 0.));\n\n                let offset = span.text.len();\n                span.text.push(' ');\n                span.chars.push(TextChar {\n                    offset,\n                    pos: span.width,\n                    width\n                });\n                span.width += advance;\n                continue;\n            }\n            if let Some(glyph) = glyph {\n                let transform = gs.transform * self.text_matrix * tr;\n                if glyph.path.len() != 0 {\n                    span.bbox.add(gs.transform * transform * glyph.path.bounds());\n                    if let Some(ref draw_mode) = draw_mode {\n                        backend.draw_glyph(&glyph, draw_mode, transform, gs.clip_path_id);\n                    }\n                }\n            } else {\n                debug!(\"no glyph for gid {:?}\", gid);\n            }\n            let advance = self.char_space * self.horiz_scale + width;\n            self.text_matrix = self.text_matrix * Transform2F::from_translation(Vector2F::new(advance, 0.));\n            \n            let offset = span.text.len();\n            if let Some(s) = unicode {\n                span.text.push_str(&*s);\n                span.chars.push(TextChar {\n                    offset,\n                    pos: span.width,\n                    width\n                });\n            }\n            span.width += advance;\n        }\n    }\n    pub fn advance(&mut self, delta: f32) -> f32 {\n        //debug!(\"advance by {}\", delta);\n        let advance = delta * self.font_size * self.horiz_scale;\n        self.text_matrix = self.text_matrix * Transform2F::from_translation(Vector2F::new(advance, 0.));\n        advance\n    }\n}\n\n#[derive(Default)]\npub struct Span {\n    pub text: String,\n    pub chars: Vec<TextChar>,\n    pub width: f32,\n    pub bbox: BBox,\n}"
  },
  {
    "path": "render/src/tracer.rs",
    "content": "use crate::{TextSpan, DrawMode, Backend, FontEntry, Fill, backend::{BlendMode, FillMode}, BBox};\nuse pathfinder_content::{\n    outline::Outline,\n    fill::FillRule,\n};\nuse pathfinder_geometry::{\n    rect::RectF,\n    transform2d::Transform2F,\n    vector::Vector2F,\n};\nuse pathfinder_content::{\n    stroke::{StrokeStyle},\n}; \nuse pdf::object::{Ref, XObject, ImageXObject, Resolve, Resources, MaybeRef};\nuse font::Glyph;\nuse pdf::font::Font as PdfFont;\nuse pdf::error::PdfError;\nuse std::sync::Arc;\nuse std::path::PathBuf;\nuse crate::font::{load_font, StandardCache};\nuse globalcache::sync::SyncCache;\nuse crate::backend::Stroke;\n\npub struct ClipPath {\n    pub path: Outline,\n    pub fill_rule: FillRule,\n    pub parent: Option<ClipPathId>,\n}\n\n#[derive(Copy, Clone, Debug)]\npub struct ClipPathId(pub usize);\n\npub struct Tracer<'a> {\n    pub items: Vec<DrawItem>,\n    clip_paths: &'a mut Vec<ClipPath>,\n    pub view_box: RectF,\n    cache: &'a TraceCache,\n    op_nr: usize,\n}\npub struct TraceCache {\n    fonts: Arc<SyncCache<u64, Option<Arc<FontEntry>>>>,\n    std: StandardCache,\n}\nfn font_key(font_ref: &MaybeRef<PdfFont>) -> u64 {\n    match font_ref {\n        MaybeRef::Direct(ref shared) => shared.as_ref() as *const PdfFont as _,\n        MaybeRef::Indirect(re) => re.get_ref().get_inner().id as _\n    }\n}\nimpl TraceCache {\n    pub fn new() -> Self {\n        TraceCache {\n            fonts: SyncCache::new(),\n            std: StandardCache::new(),\n        }\n    }\n    pub fn get_font(&self, font_ref: &MaybeRef<PdfFont>, resolve: &impl Resolve) -> Result<Option<Arc<FontEntry>>, PdfError> {\n        let mut error = None;\n        let val = self.fonts.get(font_key(font_ref), |_| \n            match load_font(font_ref, resolve, &self.std) {\n                Ok(Some(f)) => Some(Arc::new(f)),\n                Ok(None) => None,\n                Err(e) => {\n                    error = Some(e);\n                    None\n                }\n            }\n        );\n        match error {\n            None => Ok(val),\n            Some(e) => Err(e)\n        }\n    }\n    pub fn require_unique_unicode(&mut self, require_unique_unicode: bool) {\n        self.std.require_unique_unicode(require_unique_unicode);\n    }\n}\nimpl<'a> Tracer<'a> {\n    pub fn new(cache: &'a TraceCache, clip_paths: &'a mut Vec<ClipPath>) -> Self {\n        Tracer {\n            items: vec![],\n            view_box: RectF::new(Vector2F::zero(), Vector2F::zero()),\n            cache,\n            op_nr: 0,\n            clip_paths,\n        }\n    }\n    pub fn finish(self) -> Vec<DrawItem> {\n        self.items\n    }\n    pub fn view_box(&self) -> RectF {\n        self.view_box\n    }\n}\nimpl<'a> Backend for Tracer<'a> {\n    type ClipPathId = ClipPathId;\n\n    fn create_clip_path(&mut self, path: Outline, fill_rule: FillRule, parent: Option<ClipPathId>) -> ClipPathId {\n        let id = ClipPathId(self.clip_paths.len());\n        self.clip_paths.push(ClipPath {\n            path,\n            fill_rule,\n            parent,\n        });\n        id\n    }\n    fn draw(&mut self, outline: &Outline, mode: &DrawMode, _fill_rule: FillRule, transform: Transform2F, clip: Option<ClipPathId>) {\n        let stroke = match mode {\n            DrawMode::FillStroke { stroke, stroke_mode, .. } | DrawMode::Stroke { stroke, stroke_mode } => Some((stroke.clone(), stroke_mode.clone())),\n            DrawMode::Fill { .. } => None,\n        };\n        self.items.push(DrawItem::Vector(VectorPath {\n            outline: outline.clone(),\n            fill: match mode {\n                DrawMode::Fill { fill } | DrawMode::FillStroke { fill, .. } => Some(fill.clone()),\n                _ => None\n            },\n            stroke,\n            transform,\n            clip,\n            op_nr: self.op_nr,\n        }));\n    }\n    fn set_view_box(&mut self, r: RectF) {\n        self.view_box = r;\n    }\n    fn draw_image(&mut self, xref: Ref<XObject>, _im: &ImageXObject, _resources: &Resources, transform: Transform2F, mode: BlendMode, clip: Option<ClipPathId>, _resolve: &impl Resolve) {\n        let rect = transform * RectF::new(\n            Vector2F::new(0.0, 0.0), Vector2F::new(1.0, 1.0)\n        );\n        self.items.push(DrawItem::Image(ImageObject {\n            rect, id: xref, transform, op_nr: self.op_nr, mode, clip\n        }));\n    }\n    fn draw_inline_image(&mut self, im: &Arc<ImageXObject>, _resources: &Resources, transform: Transform2F, mode: BlendMode, clip: Option<ClipPathId>, _resolve: &impl Resolve) {\n        let rect = transform * RectF::new(\n            Vector2F::new(0.0, 0.0), Vector2F::new(1.0, 1.0)\n        );\n\n        self.items.push(DrawItem::InlineImage(InlineImageObject {\n            rect, im: im.clone(), transform, op_nr: self.op_nr, mode, clip\n        }));\n    }\n    fn draw_glyph(&mut self, _glyph: &Glyph, _mode: &DrawMode, _transform: Transform2F, clip: Option<ClipPathId>) {}\n    fn get_font(&mut self, font_ref: &MaybeRef<PdfFont>, resolve: &impl Resolve) -> Result<Option<Arc<FontEntry>>, PdfError> {\n        self.cache.get_font(font_ref, resolve)\n    }\n    fn add_text(&mut self, span: TextSpan, clip: Option<Self::ClipPathId>) {\n        self.items.push(DrawItem::Text(span, clip));\n    }\n    fn bug_op(&mut self, op_nr: usize) {\n        self.op_nr = op_nr;\n    }\n}\n\n#[derive(Debug)]\npub struct ImageObject {\n    pub rect: RectF,\n    pub id: Ref<XObject>,\n    pub transform: Transform2F,\n    pub op_nr: usize,\n    pub mode: BlendMode,\n    pub clip: Option<ClipPathId>,\n}\n#[derive(Debug)]\npub struct InlineImageObject {\n    pub rect: RectF,\n    pub im: Arc<ImageXObject>,\n    pub transform: Transform2F,\n    pub op_nr: usize,\n    pub mode: BlendMode,\n    pub clip: Option<ClipPathId>,\n}\n\n#[derive(Debug)]\npub enum DrawItem {\n    Vector(VectorPath),\n    Image(ImageObject),\n    InlineImage(InlineImageObject),\n    Text(TextSpan, Option<ClipPathId>),\n}\n\n#[derive(Debug)]\npub struct VectorPath {\n    pub outline: Outline,\n    pub fill: Option<FillMode>,\n    pub stroke: Option<(FillMode, Stroke)>,\n    pub transform: Transform2F,\n    pub op_nr: usize,\n    pub clip: Option<ClipPathId>,\n}\n"
  },
  {
    "path": "view/Cargo.toml",
    "content": "[package]\nname = \"pdf_view\"\nversion = \"0.1.0\"\nauthors = [\"Sebastian Köln <s3bk@protonmail.com>\"]\nedition = \"2018\"\n\n[features]\nunstable = []\n\n[dependencies.pdf]\ngit = \"https://github.com/pdf-rs/pdf\"\ndefault-features=false \nfeatures = [\"dump\"]\n\n[dependencies.pdf_render]\npath = \"../render\"\n\n[dependencies.pathfinder_view]\ngit = \"https://github.com/s3bk/pathfinder_view\"\nfeatures = [\"icon\"]\n\n[dependencies]\npathfinder_renderer = { git = \"https://github.com/servo/pathfinder\" }\npathfinder_color = { git = \"https://github.com/servo/pathfinder\" }\npathfinder_geometry = { git = \"https://github.com/servo/pathfinder\" }\npathfinder_resources = { git = \"https://github.com/servo/pathfinder\" }\npathfinder_content = { git = \"https://github.com/servo/pathfinder\" }\npathfinder_export = { git = \"https://github.com/servo/pathfinder\" }\nlog = { version = \"0.4\" }\nstructopt = \"0.3\"\nfont = { git = \"https://github.com/pdf-rs/font\" }\npdf_encoding = \"0.1\"\nitertools = \"*\"\nimage = \"0.25\"\n\n[dev-dependencies]\ncriterion = \"0.3\"\n\n[target.wasm32-unknown-unknown.dependencies]\nwasm-bindgen = \"0.2.48\"\njs-sys = \"0.3\"\nweb-sys = { version = \"*\", features = [\"HtmlCanvasElement\", \"WebGl2RenderingContext\"] }\nconsole_log = \"0.2\"\nconsole_error_panic_hook = \"0.1.6\"\ngetrandom = { version = \"0.2.3\", features = [\"js\"]}\n\n[target.'cfg(unix)'.dependencies]\nenv_logger = \"0.8\"\npdf_render = { path = \"../render\" }\n\n[lib]\ncrate-type = [\"cdylib\", \"rlib\"]\n"
  },
  {
    "path": "view/Makefile",
    "content": "DST = /home/sebk/data/view_wasm\n\nbuild:\n\twasm-pack build -t no-modules --release\n\tcp pkg/pdf_view.js pkg/pdf_view_bg.wasm $(DST)/pkg/\n\tcp ../wasm/* $(DST)/\n\npublish:\n\tgit -C $(DST) commit -a -m \"update\"\n\tgit -C $(DST) push\n\n.PHONY:\tall\n\nall:\tbuild\n"
  },
  {
    "path": "view/src/bin/convert.rs",
    "content": "use pdf::file::{File as PdfFile, FileOptions};\nuse pdf::object::*;\nuse pdf::error::PdfError;\nuse std::fs::File;\nuse std::io::BufWriter;\nuse std::path::PathBuf;\nuse pdf_render::{Cache, SceneBackend, render_page};\nuse pathfinder_export::{FileFormat, Export};\nuse pathfinder_geometry::transform2d::Transform2F;\nuse structopt::StructOpt;\n\n#[derive(Debug, StructOpt)]\n#[structopt(name = \"example\", about = \"An example of StructOpt usage.\")]\nstruct Opt {\n    #[structopt(long = \"dpi\", default_value = \"300\")]\n    dpi: f32,\n\n    /// Format to generate. (svg | png | ps | pdf)\n    #[structopt(short = \"f\", long=\"format\")]\n    format: String,\n\n    /// (first) page to generate\n    #[structopt(short = \"p\", long=\"page\", default_value=\"0\")]\n    page: u32,\n\n    /// Number of pages to generate, defaults to 1\n    #[structopt(short = \"n\", long=\"pages\", default_value=\"1\")]\n    pages: u32,\n\n    #[structopt(long = \"placeholder\", default_value=\"\\\"{}\\\"\")]\n    placeholder: String,\n\n    /// Number of digits to zero-pad the page number to\n    #[structopt(long = \"digits\", default_value=\"1\")]\n    digits: usize,\n\n    /// Input file\n    #[structopt(parse(from_os_str))]\n    input: PathBuf,\n\n    /// Output file. use '{}' (can be chaged via --palaceholder) as a replacement for the page\n    output: String,\n}\n\n\nfn main() -> Result<(), PdfError> {\n    env_logger::init();\n    let opt = Opt::from_args();\n\n    let format = match opt.format.as_str() {\n        \"svg\" => FileFormat::SVG,\n        \"pdf\" => FileFormat::PDF,\n       // \"png\" => FileFormat::PNG,\n        \"ps\" => FileFormat::PS,\n        _ => panic!(\"invalid format\")\n    };\n\n    if opt.pages > 1 {\n        assert!(opt.output.contains(&opt.placeholder), \"output name does not contain a placeholder\");\n    }\n\n    let transform = Transform2F::from_scale(opt.dpi / 25.4);\n\n    println!(\"read: {:?}\", opt.input);\n    let file = FileOptions::cached().open(&opt.input)?;\n    let resolver = file.resolver();\n    \n    let mut cache = Cache::new();\n    for (i, page) in file.pages().enumerate().skip(opt.page as usize).take(opt.pages as usize) {\n        println!(\"page {}\", i);\n        let p: &Page = &*page.unwrap();\n        let mut backend = SceneBackend::new(&mut cache);\n        render_page(&mut backend, &resolver, p, transform)?;\n        let output = if opt.pages > 1 {\n            let replacement = format!(\"{page:0digits$}\", page=i, digits=opt.digits);\n            opt.output.replace(opt.placeholder.as_str(), &replacement)\n        } else {\n            opt.output.clone()\n        };\n        let mut writer = BufWriter::new(File::create(&output)?);\n        backend.finish().export(&mut writer, format)?;\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "view/src/bin/view.rs",
    "content": "use pathfinder_renderer::gpu::options::RendererLevel;\nuse pathfinder_view::{show, Config};\nuse pathfinder_resources::embedded::EmbeddedResourceLoader;\nuse pathfinder_color::ColorF;\n\nuse pdf::file::FileOptions;\nuse pdf_view::PdfView;\n\n\nfn main() {\n    env_logger::init();\n    let path = std::env::args().nth(1).unwrap();\n    let file = FileOptions::uncached().open(&path).unwrap();\n    let view = PdfView::new(file);\n    let mut config = Config::new(Box::new(EmbeddedResourceLoader));\n    config.zoom = true;\n    config.pan = true;\n    config.background = ColorF::new(0.9, 0.9, 0.9, 1.0);\n    config.render_level = RendererLevel::D3D9;\n    show(view, config);\n}\n"
  },
  {
    "path": "view/src/lib.rs",
    "content": "#[macro_use] extern crate log;\n\nuse std::sync::Arc;\nuse pathfinder_view::{Config, Interactive, Context, Emitter, view::{ElementState, KeyCode, KeyEvent, ModifiersState}};\nuse pathfinder_renderer::scene::Scene;\nuse pathfinder_geometry::vector::Vector2F;\n\nuse pdf::file::{File as PdfFile, Cache as PdfCache, Log};\nuse pdf::any::AnySync;\nuse pdf::PdfError;\nuse pdf::backend::Backend;\nuse pdf_render::{Cache, SceneBackend, page_bounds, render_page};\n\n#[cfg(target_arch = \"wasm32\")]\nuse pathfinder_view::WasmView;\n\npub struct PdfView<B: Backend, OC, SC, L> {\n    file: PdfFile<B, OC, SC, L>,\n    num_pages: usize,\n    cache: Cache,\n}\nimpl<B, OC, SC, L> PdfView<B, OC, SC, L>\nwhere\n    B: Backend + 'static,\n    OC: PdfCache<Result<AnySync, Arc<PdfError>>> + 'static,\n    SC: PdfCache<Result<Arc<[u8]>, Arc<PdfError>>> + 'static,\n    L: Log\n{\n    pub fn new(file: PdfFile<B, OC, SC, L>) -> Self {\n        PdfView {\n            num_pages: file.num_pages() as usize,\n            file,\n            cache: Cache::new(),\n        }\n    }\n}\nimpl<B, OC, SC, L> Interactive for PdfView<B, OC, SC, L>\nwhere\n    B: Backend + 'static,\n    OC: PdfCache<Result<AnySync, Arc<PdfError>>> + 'static,\n    SC: PdfCache<Result<Arc<[u8]>, Arc<PdfError>>> + 'static,\n    L: Log + 'static\n{\n    type Event = Vec<u8>;\n    fn title(&self) -> String {\n        self.file.trailer.info_dict.as_ref()\n            .and_then(|info| info.title.as_ref())\n            .and_then(|p| p.to_string().ok())\n            .unwrap_or_else(|| \"PDF View\".into())\n    }\n    fn init(&mut self, ctx: &mut Context, sender: Emitter<Self::Event>) {\n        ctx.num_pages = self.num_pages;\n        ctx.set_icon(image::load_from_memory_with_format(include_bytes!(\"../../logo.png\"), image::ImageFormat::Png).unwrap().to_rgba8().into());\n    }\n    fn scene(&mut self, ctx: &mut Context) -> Scene {\n        info!(\"drawing page {}\", ctx.page_nr());\n        let page = self.file.get_page(ctx.page_nr as u32).unwrap();\n\n        ctx.set_bounds(page_bounds(&page));\n\n        let mut backend = SceneBackend::new(&mut self.cache);\n        let resolver = self.file.resolver();\n        render_page(&mut backend, &resolver, &page, ctx.view_transform()).unwrap();\n        backend.finish()\n    }\n    fn mouse_input(&mut self, ctx: &mut Context, page: usize, pos: Vector2F, state: ElementState) {\n        if state != ElementState::Pressed { return; }\n        info!(\"x={}, y={}\", pos.x(), pos.y());\n    }\n    fn keyboard_input(&mut self, ctx: &mut Context, state: ModifiersState, event: KeyEvent) {\n        if event.state == ElementState::Released {\n            return;\n        }\n        if state.shift_key() {\n            let page = ctx.page_nr();\n            match event.physical_key {\n                KeyCode::ArrowRight => ctx.goto_page(page + 10),\n                KeyCode::ArrowLeft =>  ctx.goto_page(page.saturating_sub(10)),\n                _ => return\n            }\n        }\n        match event.physical_key {\n            KeyCode::ArrowRight | KeyCode::PageDown => ctx.next_page(),\n            KeyCode::ArrowLeft | KeyCode::PageUp => ctx.prev_page(),\n            _ => return\n        }\n    }\n}\n\n#[cfg(target_arch = \"wasm32\")]\nuse wasm_bindgen::prelude::*;\n\n#[cfg(target_arch = \"wasm32\")]\nuse js_sys::Uint8Array;\n\n#[cfg(target_arch = \"wasm32\")]\nuse web_sys::{HtmlCanvasElement, WebGl2RenderingContext};\n\n#[cfg(target_arch = \"wasm32\")]\n#[wasm_bindgen(start)]\npub fn run() {\n    std::panic::set_hook(Box::new(console_error_panic_hook::hook));\n    console_log::init_with_level(log::Level::Info);\n    warn!(\"test\");\n}\n\n#[cfg(target_arch = \"wasm32\")]\n#[wasm_bindgen]\npub fn show(canvas: HtmlCanvasElement, context: WebGl2RenderingContext, data: &Uint8Array) -> WasmView {\n    use pathfinder_resources::embedded::EmbeddedResourceLoader;\n\n    let data: Vec<u8> = data.to_vec();\n    info!(\"got {} bytes of data\", data.len());\n    let file = PdfFile::from_data(data).expect(\"failed to parse PDF\");\n    info!(\"got the file\");\n    let view = PdfView::new(file);\n\n    let mut config = Config::new(Box::new(EmbeddedResourceLoader));\n    config.zoom = false;\n    config.pan = false;\n    WasmView::new(\n        canvas,\n        context,\n        config,\n        Box::new(view) as _\n    )\n}\n"
  },
  {
    "path": "wasm/index.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"UTF-8\">\n        <title>PDF View</title>\n        <script type=\"text/javascript\" src=\"pkg/pdf_view.js\"></script>\n        <link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />\n    </head>\n    <body>\n        <canvas id=\"canvas\" tabindex=\"0\"></canvas>\n        <script type=\"text/javascript\" src=\"index.js\"></script>\n        <div id=\"drop\">\n            <input type=\"file\" id=\"file-selector\" onchange=\"show(event.target.files[0])\"></input>\n            <p id=\"msg\">loading …</p>\n            <div id=\"banner\"><a href=\"https://github.com/pdf-rs/pdf_render/\">Code</a></div>\n        </div>\n    </body>\n</html>\n"
  },
  {
    "path": "wasm/index.js",
    "content": "wasm_bindgen(\"pkg/pdf_view_bg.wasm\").catch(console.error)\n.then(show_logo);\n//display(\"Drop a PDF here\");\n\n\nfunction show_logo() {\n    fetch(\"logo.pdf\")\n    .then(r => r.arrayBuffer())\n    .then(buf => show_data(new Uint8Array(buf)));\n}\n\nfunction set_scroll_factors() {}\n\nfunction drop_handler(e) {\n    e.stopPropagation();\n    e.preventDefault();\n    show(e.dataTransfer.files[0]);\n}\nfunction dragover_handler(e) {\n    e.stopPropagation();\n    e.preventDefault();\n}\n\nfunction display(msg) {\n    delete document.getElementById(\"drop\").style.display;\n    document.getElementById(\"msg\").innerText = msg || \"\";\n}\n\nlet view;\nfunction init_view(data, attempt) {\n    let canvas = document.getElementById(\"canvas\");\n    let context = canvas.getContext(\"webgl2\");\n    if (context == null) {\n        if (attempt < 10) {\n            setTimeout(function() { init_view(data, attempt+1) }, 1000);\n            display(`retrying ${attempt}`);\n        }\n        return;\n    }\n\n    view = wasm_bindgen.show(canvas, context, data);\n    display();\n\n    let requested = false;\n    function animation_frame(time) {\n        requested = false;\n        view.animation_frame(time);\n    }\n    function check(request_redraw) {\n        if (request_redraw && !requested) {\n            window.requestAnimationFrame(animation_frame);\n            requested = true;\n        }\n    }\n\n    window.addEventListener(\"keydown\", e => check(view.key_down(e)), {capture: true});\n    window.addEventListener(\"keyup\", e => check(view.key_up(e)), {capture: true});\n    canvas.addEventListener(\"mousemove\", e => check(view.mouse_move(e)));\n    canvas.addEventListener(\"mouseup\", e => check(view.mouse_up(e)));\n    canvas.addEventListener(\"mousedown\", e => check(view.mouse_down(e)));\n    window.addEventListener(\"resize\", e => check(view.resize(e)));\n    view.render();\n}\n\nfunction show_data(data) {\n    try {\n        init_view(data, 0);\n    } catch (e) {\n        display(\"oops. try another one.\");\n    }\n}\n\nfunction show(file) {\n    let reader = new FileReader();\n    reader.onload = function() {\n        let data = new Uint8Array(reader.result);\n        show_data(data);\n    };\n    reader.readAsArrayBuffer(file);\n}\n\nfunction open() {\n    var input = document.createElement('input');\n    input.type = 'file';\n    input.onchange = e => { \n        // getting a hold of the file reference\n        var file = e.target.files[0]; \n        show(file);\n    };\n    input.click();\n}\n\ndocument.addEventListener(\"drop\", drop_handler, false);\ndocument.addEventListener(\"dragover\", dragover_handler, false);\n"
  },
  {
    "path": "wasm/style.css",
    "content": "body {\n    display: flex;\n    flex-direction: row;\n    align-content: center;\n    background-color: rgba(220, 200, 160, 255);\n    padding: 10px;\n}\n#canvas {\n    align-self: center;\n    margin-left: auto;\n    margin-right: auto;\n}\n#drop {\n    display: flex;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    flex-direction: row;\n    align-items: center;\n    position: fixed;\n}\n#drop p {\n    text-align: center;\n    flex: auto;\n    font-size: 10pt;\n    color: black;\n    margin: 0;\n}\n#open {\n}\n#banner {\n    background-color: rgba(0, 0, 0, 0.8);\n    padding: 0.5em;\n    font-family: sans-serif;\n}\n#banner a {\n    color: gold;\n    text-decoration: none;\n    font-weight: bold;\n}\n#banner a:hover {\n    color: white;\n}\n"
  }
]