write_tiff.rs

Le fichier Rust de l'exemple « Toutes les images qu'un scanner met dans un seul fichier ». Un fichier de trois feuilles et un fichier d'une seule photographie, chaque image sur une page taillée à sa mesure ; la photographie entre telle que l'appareil l'a écrite, puisqu'un logiciel de lecture sait déjà lire une photographie.

Rust 145 lignes

À quoi sert cet exemple

Un scanner ne vous rend pas un fichier par feuille. Il en rend un seul, avec toute la pile dedans, et un télécopieur fait pareil : toutes les pages de l'appel sont là, l'une après l'autre. Un logiciel qui ne regarde que la première perd les autres sans rien dire, et personne ne s'en aperçoit avant le jour où il faut la page quatre.

Ce que montre cet exemple

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//! Places every picture a TIFF holds on a page of its own, and writes the
//! result to disk.
//!
//! A TIFF holds any number of pictures, one directory apiece: a scanner puts
//! every sheet of a batch in one file, and a fax puts every page of a call
//! there. Each of them gets a page exactly its own size — at 72 dots to the
//! inch one pixel is one point — so the page that comes back out of a renderer
//! is the picture that went in.
//!
//! Named nothing, the example reads the two committed fixtures: one holding a
//! photograph in a single strip, whose bytes go into the document undecoded
//! because PDF reads a photograph itself, and one holding three pictures whose
//! samples are read and written again. It opens on a sheet laying all of them
//! out the way a document would.
//!
//! Usage: `cargo run --example write_tiff -- tmp/tiff.pdf [file.tif...]`

use std::env;
use std::fs;
use std::path::{Path, PathBuf};

use hqf_pdf::content::Content;
use hqf_pdf::cos::Name;
use hqf_pdf::{Document, Image, ImageHandle, Page, Rgb};

#[path = "shared/out.rs"]
mod out;

#[path = "shared/licence.rs"]
mod licence;

#[path = "shared/failure.rs"]
mod failure;

/// How wide the sheet the showcase is laid out on is.
const A4_WIDTH: f64 = 595.276;

/// The side of the box each picture of the showcase is fitted into, and how far
/// apart the boxes stand.
const BOX_SIDE: f64 = 120.0;

/// See [`BOX_SIDE`].
const BOX_STEP: f64 = 130.0;

/// The files the example reads when the caller names none: the ones committed
/// for the tests, so that it runs on any machine.
fn default_files() -> Vec<PathBuf> {
    let images = Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("tests")
        .join("images");
    vec![images.join("photograph.tif"), images.join("pages.tif")]
}

fn main() -> std::process::ExitCode {
    failure::reported(run())
}

fn run() -> Result<(), Box<dyn std::error::Error>> {
    let mut args = env::args().skip(1);
    let out = args.next().unwrap_or_else(|| out::default_path("tiff"));

    let paths: Vec<PathBuf> = args.map(PathBuf::from).collect();
    let showcase = paths.is_empty();
    let paths = if showcase { default_files() } else { paths };

    let mut doc = Document::new();
    doc.set_license(licence::licensed());
    doc.set_info(Name::new("Title"), "hqf-pdf TIFF sample");

    let mut handles = Vec::new();
    for path in &paths {
        let bytes = fs::read(path)?;
        let held = Image::tiff_pages(&bytes)?;
        println!("{}: {held} picture(s)", path.display());

        for page in 0..held {
            let image = Image::from_tiff_page(&bytes, page)?;
            println!(
                "  {}: {}x{} {:?}, {} bits a component, {}",
                page + 1,
                image.width(),
                image.height(),
                image.color_space(),
                image.bits_per_component(),
                image.resolution().map_or_else(
                    || "stating no resolution of its own".to_owned(),
                    |resolution| {
                        format!("{} by {} dots to the inch", resolution.x(), resolution.y())
                    },
                )
            );
            handles.push(doc.add_image(image));
        }
    }

    if showcase {
        doc.add_page(sheet_of_all(&handles)?)?;
    }
    for handle in &handles {
        // A page the size of the picture, with the picture filling it. The size
        // is the one the file itself asks for; a file asking for none is laid
        // down one pixel to the point.
        let (width, height) = handle.size();
        let mut c = Content::new();
        c.draw_image(handle, 0.0, 0.0, width, height)?;

        let mut page = Page::new(width, height);
        page.content = c.into_bytes();
        doc.add_page(page)?;
    }

    let bytes = doc.to_bytes()?;
    if let Some(parent) = Path::new(&out).parent() {
        fs::create_dir_all(parent)?;
    }
    fs::write(&out, &bytes)?;

    println!(
        "wrote {out}: {} bytes, {} page(s)",
        bytes.len(),
        doc.page_count()
    );
    Ok(())
}

/// One sheet holding every picture the files gave, each fitted whole into a box
/// of one size and standing on the same line as the rest.
fn sheet_of_all(handles: &[ImageHandle]) -> Result<Page, Box<dyn std::error::Error>> {
    let mut c = Content::new();

    c.set_fill(Rgb::new(0.93, 0.94, 0.96))?;
    c.rect(0.0, 700.0, A4_WIDTH, 142.0)?;
    c.fill();

    let mut left = 60.0;
    for handle in handles {
        let (width, height) = handle.fit_within(BOX_SIDE, BOX_SIDE)?;
        c.draw_image(handle, left, 600.0, width, height)?;
        left += BOX_STEP;
    }

    let mut page = Page::a4();
    page.content = c.into_bytes();
    Ok(page)
}