write_image.rs

Die Rust-Datei des Beispiels „Bilder auf einer Seite“. Ein Foto, das genau so hineingeht, wie es aus der Kamera kam, und eines mit durchsichtigen Teilen, das sie behält, jedes auf einer Seite in seiner eigenen Größe.

Rust 133 Zeilen

Wofür dieses Beispiel da ist

Ein Foto, das einmal komprimiert und dann von dem, was es ins Dokument gesetzt hat, ein zweites Mal komprimiert wurde, kommt weich heraus, fleckig an den Kanten und schwerer als das Original. Das ist die häufigste Art, wie ein gutes Bild zwischen Kamera und Drucker verdorben wird, und nichts in der fertigen Datei verrät, dass es geschehen ist.

Was dieses Beispiel zeigt

  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
//! Places images on a page, and writes the result to disk.
//!
//! Each image named on the command line gets a page of its own, exactly as big
//! as the image is: at 72 dots to the inch one pixel is one point, so the page
//! that comes back out of a renderer is the image that went in. That is what
//! makes the output checkable against its own source, pixel for pixel.
//!
//! Named nothing, the example places the two committed fixtures — a JPEG, whose
//! bytes go into the file undecoded, and a PNG whose transparency becomes a
//! soft mask — and adds a page that lays them out the way a document would.
//!
//! Usage: `cargo run --example write_image -- tmp/image.pdf [image...]`

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, Page, Rgb};

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

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

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

/// The images the example places when the caller names none: the ones committed
/// for the tests, so that it runs on any machine.
fn default_images() -> Vec<PathBuf> {
    let images = Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("tests")
        .join("images");
    vec![images.join("photo.jpg"), images.join("logo.png")]
}

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("image"));

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

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

    let mut handles = Vec::new();
    for path in &paths {
        let image = Image::parse(fs::read(path)?)?;
        println!(
            "{}: {}x{} {:?}{}, {}, {}",
            path.display(),
            image.width(),
            image.height(),
            image.color_space(),
            if image.has_alpha() {
                ", with a soft mask"
            } else {
                ""
            },
            image.resolution().map_or_else(
                || "stating no resolution of its own".to_owned(),
                |resolution| {
                    format!("{} by {} dots to the inch", resolution.x(), resolution.y())
                },
            ),
            image.orientation().map_or_else(
                || "and no way up".to_owned(),
                |orientation| format!("seen {orientation:?}"),
            )
        );

        let handle = doc.add_image(image);

        // A page the size of the image, with the image 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)?;

        handles.push(handle);
    }

    if showcase {
        // One more page, laid out the way a document would lay it out: the
        // photo fitted into a box without being stretched, and the logo, whose
        // transparency lets what is under it show through, over a rule.
        let mut c = Content::new();

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

        let photo = &handles[0];
        let (width, height) = photo.fit_within(240.0, 160.0)?;
        c.draw_image(photo, 60.0, 500.0, width, height)?;

        let logo = &handles[1];
        let (width, height) = logo.fit_within(90.0, 90.0)?;
        c.draw_image(logo, 380.0, 730.0, width, height)?;

        let mut page = Page::a4();
        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(())
}