- Renamed all references from zpdf to zcatpdf
- Module import: @import("zcatpdf")
- Consistent with zcatui, zcatgui naming convention
- All lowercase per Zig standards
Note: Directory rename (zpdf -> zcatpdf) and Forgejo repo rename
should be done manually after this commit.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
234 lines
9.2 KiB
Zig
234 lines
9.2 KiB
Zig
//! Image Demo - Demonstrates JPEG and PNG image embedding
|
|
//!
|
|
//! Usage: ./image_demo [path_to_image]
|
|
//! Supports: JPEG (.jpg, .jpeg) and PNG (.png) images
|
|
//! If no path is provided, creates a simple PDF with text explaining the features.
|
|
|
|
const std = @import("std");
|
|
const pdf = @import("zcatpdf");
|
|
|
|
pub fn main() !void {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer _ = gpa.deinit();
|
|
const allocator = gpa.allocator();
|
|
|
|
std.debug.print("zcatpdf - Image Demo\n", .{});
|
|
|
|
// Get command line args
|
|
const args = try std.process.argsAlloc(allocator);
|
|
defer std.process.argsFree(allocator, args);
|
|
|
|
var doc = pdf.Pdf.init(allocator, .{});
|
|
defer doc.deinit();
|
|
|
|
doc.setTitle("Image Demo");
|
|
doc.setAuthor("zcatpdf");
|
|
|
|
var page = try doc.addPage(.{});
|
|
page.setMargins(50, 50, 50);
|
|
page.setXY(50, 800);
|
|
|
|
// Title
|
|
try page.setFont(.helvetica_bold, 24);
|
|
page.setFillColor(pdf.Color.rgb(41, 98, 255));
|
|
try page.cell(0, 30, "Image Demo - JPEG & PNG Support", pdf.Border.none, .center, false);
|
|
page.ln(40);
|
|
|
|
// Check if an image path was provided
|
|
if (args.len > 1) {
|
|
const image_path = args[1];
|
|
std.debug.print("Loading image: {s}\n", .{image_path});
|
|
|
|
// Try to load the image (auto-detect format)
|
|
const image_index = doc.addImageFromFile(image_path) catch |err| {
|
|
std.debug.print("Error loading image: {any}\n", .{err});
|
|
|
|
try page.setFont(.helvetica, 12);
|
|
page.setFillColor(pdf.Color.red);
|
|
try page.cell(0, 20, "Error: Could not load image file", pdf.Border.none, .left, false);
|
|
page.ln(25);
|
|
|
|
try page.setFont(.helvetica, 10);
|
|
page.setFillColor(pdf.Color.black);
|
|
const long_text =
|
|
\\Make sure the file exists and is a valid JPEG or PNG image.
|
|
\\
|
|
\\Supported formats:
|
|
\\- JPEG/JPG images (RGB, Grayscale, CMYK)
|
|
\\- PNG images (RGB, RGBA, Grayscale, Indexed)
|
|
;
|
|
try page.multiCell(450, null, long_text, pdf.Border.none, .left, false);
|
|
|
|
const filename = "image_demo.pdf";
|
|
try doc.save(filename);
|
|
std.debug.print("Created: {s}\n", .{filename});
|
|
return;
|
|
};
|
|
|
|
// Get image info
|
|
const img_info = doc.getImage(image_index).?;
|
|
|
|
// Show image info
|
|
try page.setFont(.helvetica_bold, 14);
|
|
page.setFillColor(pdf.Color.black);
|
|
try page.cell(0, 20, "Image Information:", pdf.Border.none, .left, false);
|
|
page.ln(25);
|
|
|
|
try page.setFont(.helvetica, 11);
|
|
|
|
// Display image properties
|
|
var buf: [256]u8 = undefined;
|
|
const size_text = std.fmt.bufPrint(&buf, "Dimensions: {d} x {d} pixels", .{ img_info.width, img_info.height }) catch "Error";
|
|
try page.cell(0, 16, size_text, pdf.Border.none, .left, false);
|
|
page.ln(18);
|
|
|
|
const format_text = std.fmt.bufPrint(&buf, "Format: {s}", .{if (img_info.format == .jpeg) "JPEG" else "PNG"}) catch "Error";
|
|
try page.cell(0, 16, format_text, pdf.Border.none, .left, false);
|
|
page.ln(18);
|
|
|
|
const color_text = std.fmt.bufPrint(&buf, "Color Space: {s}", .{img_info.color_space.pdfName()}) catch "Error";
|
|
try page.cell(0, 16, color_text, pdf.Border.none, .left, false);
|
|
page.ln(18);
|
|
|
|
const bpc_text = std.fmt.bufPrint(&buf, "Bits per Component: {d}", .{img_info.bits_per_component}) catch "Error";
|
|
try page.cell(0, 16, bpc_text, pdf.Border.none, .left, false);
|
|
page.ln(18);
|
|
|
|
const alpha_text = std.fmt.bufPrint(&buf, "Has Alpha Channel: {s}", .{if (img_info.hasAlpha()) "Yes" else "No"}) catch "Error";
|
|
try page.cell(0, 16, alpha_text, pdf.Border.none, .left, false);
|
|
page.ln(30);
|
|
|
|
// Draw the image
|
|
try page.setFont(.helvetica_bold, 14);
|
|
try page.cell(0, 20, "Image Preview:", pdf.Border.none, .left, false);
|
|
page.ln(25);
|
|
|
|
// Calculate size to fit in available space (max 400x400)
|
|
const max_w: f32 = 400;
|
|
const max_h: f32 = 400;
|
|
const img_w: f32 = @floatFromInt(img_info.width);
|
|
const img_h: f32 = @floatFromInt(img_info.height);
|
|
const scale = @min(max_w / img_w, max_h / img_h, 1.0);
|
|
const display_w = img_w * scale;
|
|
const display_h = img_h * scale;
|
|
|
|
// Draw border around image area
|
|
const img_x = page.getX();
|
|
const img_y = page.getY() - display_h;
|
|
|
|
page.setStrokeColor(pdf.Color.light_gray);
|
|
try page.drawRect(img_x - 2, img_y - 2, display_w + 4, display_h + 4);
|
|
|
|
// Draw the image
|
|
try page.image(image_index, img_info, img_x, img_y, display_w, display_h);
|
|
|
|
std.debug.print("Image embedded: {d}x{d} pixels, displayed at {d:.0}x{d:.0} points\n", .{ img_info.width, img_info.height, display_w, display_h });
|
|
if (img_info.hasAlpha()) {
|
|
std.debug.print("Alpha channel: embedded as soft mask\n", .{});
|
|
}
|
|
} else {
|
|
// No image provided - show instructions
|
|
try page.setFont(.helvetica_bold, 14);
|
|
page.setFillColor(pdf.Color.black);
|
|
try page.cell(0, 20, "How to Use Images:", pdf.Border.none, .left, false);
|
|
page.ln(25);
|
|
|
|
try page.setFont(.helvetica, 11);
|
|
const instructions =
|
|
\\To include an image in your PDF:
|
|
\\
|
|
\\1. Load the image file (auto-detects format):
|
|
\\ const img_idx = try doc.addImageFromFile("photo.jpg");
|
|
\\ // Or specifically:
|
|
\\ const jpg_idx = try doc.addJpegImageFromFile("photo.jpg");
|
|
\\ const png_idx = try doc.addPngImageFromFile("image.png");
|
|
\\
|
|
\\2. Get the image info:
|
|
\\ const info = doc.getImage(img_idx).?;
|
|
\\
|
|
\\3. Draw on a page:
|
|
\\ try page.image(img_idx, info, x, y, width, height);
|
|
\\
|
|
\\Or use imageFit to auto-scale:
|
|
\\ try page.imageFit(img_idx, info, x, y, max_w, max_h);
|
|
\\
|
|
\\Run this example with an image file path:
|
|
\\ ./image_demo photo.jpg
|
|
\\ ./image_demo logo.png
|
|
;
|
|
try page.multiCell(450, null, instructions, pdf.Border.all, .left, false);
|
|
|
|
page.ln(30);
|
|
|
|
// Supported features
|
|
try page.setFont(.helvetica_bold, 14);
|
|
try page.cell(0, 20, "Supported Features:", pdf.Border.none, .left, false);
|
|
page.ln(25);
|
|
|
|
try page.setFont(.helvetica, 11);
|
|
|
|
// JPEG Features
|
|
page.setFillColor(pdf.Color.rgb(0, 128, 0));
|
|
try page.cell(20, 16, "[OK]", pdf.Border.none, .left, false);
|
|
page.setFillColor(pdf.Color.black);
|
|
try page.cell(0, 16, "JPEG images (RGB, Grayscale, CMYK)", pdf.Border.none, .left, false);
|
|
page.ln(18);
|
|
|
|
page.setFillColor(pdf.Color.rgb(0, 128, 0));
|
|
try page.cell(20, 16, "[OK]", pdf.Border.none, .left, false);
|
|
page.setFillColor(pdf.Color.black);
|
|
try page.cell(0, 16, "JPEG direct passthrough (no re-encoding)", pdf.Border.none, .left, false);
|
|
page.ln(18);
|
|
|
|
// PNG Features
|
|
page.setFillColor(pdf.Color.rgb(0, 128, 0));
|
|
try page.cell(20, 16, "[OK]", pdf.Border.none, .left, false);
|
|
page.setFillColor(pdf.Color.black);
|
|
try page.cell(0, 16, "PNG images (RGB, RGBA, Grayscale, Indexed)", pdf.Border.none, .left, false);
|
|
page.ln(18);
|
|
|
|
page.setFillColor(pdf.Color.rgb(0, 128, 0));
|
|
try page.cell(20, 16, "[OK]", pdf.Border.none, .left, false);
|
|
page.setFillColor(pdf.Color.black);
|
|
try page.cell(0, 16, "PNG transparency (alpha channel as soft mask)", pdf.Border.none, .left, false);
|
|
page.ln(18);
|
|
|
|
page.setFillColor(pdf.Color.rgb(0, 128, 0));
|
|
try page.cell(20, 16, "[OK]", pdf.Border.none, .left, false);
|
|
page.setFillColor(pdf.Color.black);
|
|
try page.cell(0, 16, "PNG filters (None, Sub, Up, Average, Paeth)", pdf.Border.none, .left, false);
|
|
page.ln(18);
|
|
|
|
// General features
|
|
page.setFillColor(pdf.Color.rgb(0, 128, 0));
|
|
try page.cell(20, 16, "[OK]", pdf.Border.none, .left, false);
|
|
page.setFillColor(pdf.Color.black);
|
|
try page.cell(0, 16, "Automatic format detection", pdf.Border.none, .left, false);
|
|
page.ln(18);
|
|
|
|
page.setFillColor(pdf.Color.rgb(0, 128, 0));
|
|
try page.cell(20, 16, "[OK]", pdf.Border.none, .left, false);
|
|
page.setFillColor(pdf.Color.black);
|
|
try page.cell(0, 16, "Aspect ratio preservation", pdf.Border.none, .left, false);
|
|
page.ln(18);
|
|
|
|
// Not yet supported
|
|
page.setFillColor(pdf.Color.rgb(255, 165, 0));
|
|
try page.cell(20, 16, "[--]", pdf.Border.none, .left, false);
|
|
page.setFillColor(pdf.Color.black);
|
|
try page.cell(0, 16, "PNG interlaced images (not supported)", pdf.Border.none, .left, false);
|
|
}
|
|
|
|
// Footer
|
|
page.setXY(50, 50);
|
|
try page.setFont(.helvetica, 9);
|
|
page.setFillColor(pdf.Color.medium_gray);
|
|
try page.cell(0, 15, "Generated with zcatpdf - Pure Zig PDF Library", pdf.Border.none, .center, false);
|
|
|
|
// Save
|
|
const filename = "image_demo.pdf";
|
|
try doc.save(filename);
|
|
|
|
std.debug.print("Created: {s}\n", .{filename});
|
|
std.debug.print("Done!\n", .{});
|
|
}
|