zcatpdf/src/root.zig
reugenio f9189253d7 feat: v0.3 - Image support (JPEG embedding)
Phase 3 - Images:
- JPEG parser with direct DCT passthrough (no re-encoding)
- PNG metadata extraction (full embedding pending)
- Page.image() for drawing images at position
- Page.imageFit() for auto-scaling with aspect ratio
- Pdf.addJpegImage() / addJpegImageFromFile()
- XObject generation in OutputProducer

New modules:
- src/images/mod.zig - Image module exports
- src/images/image_info.zig - ImageInfo struct
- src/images/jpeg.zig - JPEG parser
- src/images/png.zig - PNG metadata parser

New example:
- examples/image_demo.zig - Image embedding demo

Stats:
- 66 unit tests passing
- 4 working examples

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 20:00:56 +01:00

235 lines
6.9 KiB
Zig

//! zpdf - PDF generation library for Zig
//!
//! A pure Zig library for creating PDF documents with zero dependencies.
//! Based on fpdf2 (Python) architecture.
//!
//! ## Quick Start
//!
//! ```zig
//! const zpdf = @import("zpdf");
//!
//! pub fn main() !void {
//! var pdf = zpdf.Pdf.init(allocator, .{});
//! defer pdf.deinit();
//!
//! var page = try pdf.addPage(.{});
//! try page.setFont(.helvetica_bold, 24);
//! try page.drawText(50, 750, "Hello, PDF!");
//!
//! try pdf.save("hello.pdf");
//! }
//! ```
const std = @import("std");
// =============================================================================
// Module Re-exports
// =============================================================================
/// Main PDF document facade
pub const pdf = @import("pdf.zig");
pub const Pdf = pdf.Pdf;
/// Page representation
pub const page = @import("page.zig");
pub const Page = page.Page;
pub const Align = page.Align;
pub const Border = page.Border;
pub const CellPosition = page.Page.CellPosition;
/// Content stream (low-level PDF operators)
pub const content_stream = @import("content_stream.zig");
pub const ContentStream = content_stream.ContentStream;
pub const RenderStyle = content_stream.RenderStyle;
pub const LineCap = content_stream.LineCap;
pub const LineJoin = content_stream.LineJoin;
pub const TextRenderMode = content_stream.TextRenderMode;
/// Graphics (colors, etc.)
pub const graphics = @import("graphics/mod.zig");
pub const Color = graphics.Color;
/// Fonts
pub const fonts = @import("fonts/mod.zig");
pub const Font = fonts.Font;
pub const FontFamily = fonts.FontFamily;
pub const FontState = fonts.FontState;
/// Objects (base types, page sizes, units)
pub const objects = @import("objects/mod.zig");
pub const PageSize = objects.PageSize;
pub const Orientation = objects.Orientation;
pub const Unit = objects.Unit;
/// Output (PDF generation)
pub const output = @import("output/mod.zig");
pub const OutputProducer = output.OutputProducer;
/// Images (JPEG, PNG)
pub const images = @import("images/mod.zig");
pub const ImageInfo = images.ImageInfo;
pub const ImageFormat = images.ImageFormat;
// =============================================================================
// Backwards Compatibility - Old API (Document)
// =============================================================================
/// Legacy Document type (use Pdf instead for new code).
/// Provided for backwards compatibility with existing code.
pub const Document = struct {
inner: Pdf,
const Self = @This();
/// Creates a new empty PDF document.
pub fn init(allocator: std.mem.Allocator) Self {
return .{
.inner = Pdf.init(allocator, .{}),
};
}
/// Frees all resources.
pub fn deinit(self: *Self) void {
self.inner.deinit();
}
/// Adds a new page with a standard size.
pub fn addPage(self: *Self, size: PageSize) !*Page {
return try self.inner.addPage(.{ .size = size });
}
/// Adds a new page with custom dimensions (in points).
pub fn addPageCustom(self: *Self, width: f32, height: f32) !*Page {
return try self.inner.addPageCustom(width, height);
}
/// Renders the document to a byte buffer.
pub fn render(self: *Self, allocator: std.mem.Allocator) ![]u8 {
_ = allocator; // Use inner allocator
return try self.inner.render();
}
/// Saves the document to a file.
pub fn saveToFile(self: *Self, path: []const u8) !void {
return try self.inner.save(path);
}
};
// =============================================================================
// Tests
// =============================================================================
test "zpdf re-exports" {
// Test that all types are accessible
_ = Pdf;
_ = Page;
_ = ContentStream;
_ = Color;
_ = Font;
_ = PageSize;
_ = Document;
}
test "Document backwards compatibility" {
const allocator = std.testing.allocator;
var doc = Document.init(allocator);
defer doc.deinit();
var pg = try doc.addPage(.a4);
try pg.setFont(.helvetica_bold, 24);
try pg.drawText(50, 750, "Hello");
try pg.drawLine(50, 740, 200, 740);
const data = try doc.render(allocator);
defer allocator.free(data);
try std.testing.expect(std.mem.startsWith(u8, data, "%PDF-1.4"));
}
test "new Pdf API" {
const allocator = std.testing.allocator;
var zpdf_doc = Pdf.init(allocator, .{
.page_size = .a4,
.orientation = .portrait,
});
defer zpdf_doc.deinit();
zpdf_doc.setTitle("Test Document");
zpdf_doc.setAuthor("zpdf");
var pg = try zpdf_doc.addPage(.{});
try pg.setFont(.helvetica_bold, 24);
pg.setFillColor(Color.blue);
try pg.drawText(50, 750, "Hello zpdf!");
pg.setStrokeColor(Color.red);
try pg.setLineWidth(2);
try pg.drawLine(50, 740, 200, 740);
pg.setFillColor(Color.light_gray);
try pg.fillRect(50, 600, 150, 100);
const data = try zpdf_doc.output();
defer allocator.free(data);
try std.testing.expect(std.mem.startsWith(u8, data, "%PDF-1.4"));
try std.testing.expect(std.mem.indexOf(u8, data, "/Title (Test Document)") != null);
}
test "Color types" {
const red = Color.rgb(255, 0, 0);
const floats = red.toRgbFloats();
try std.testing.expectApproxEqAbs(@as(f32, 1.0), floats.r, 0.01);
try std.testing.expectApproxEqAbs(@as(f32, 0.0), floats.g, 0.01);
try std.testing.expectApproxEqAbs(@as(f32, 0.0), floats.b, 0.01);
const hex_color = Color.hex(0xFF8000);
try std.testing.expectEqual(@as(u8, 255), hex_color.rgb_val.r);
try std.testing.expectEqual(@as(u8, 128), hex_color.rgb_val.g);
try std.testing.expectEqual(@as(u8, 0), hex_color.rgb_val.b);
}
test "Font metrics" {
const font = Font.helvetica;
try std.testing.expectEqualStrings("Helvetica", font.pdfName());
const width = font.stringWidth("Hello", 12.0);
try std.testing.expect(width > 0);
}
test "ContentStream operators" {
const allocator = std.testing.allocator;
var cs = ContentStream.init(allocator);
defer cs.deinit();
try cs.saveState();
try cs.setLineWidth(2.0);
try cs.setStrokeColorRgb(1.0, 0.0, 0.0);
try cs.moveTo(100, 200);
try cs.lineTo(300, 200);
try cs.stroke();
try cs.restoreState();
const content = cs.getContent();
try std.testing.expect(std.mem.indexOf(u8, content, "q\n") != null);
try std.testing.expect(std.mem.indexOf(u8, content, "Q\n") != null);
try std.testing.expect(std.mem.indexOf(u8, content, "1.000 0.000 0.000 RG") != null);
}
// Import all module tests
comptime {
_ = @import("content_stream.zig");
_ = @import("page.zig");
_ = @import("pdf.zig");
_ = @import("graphics/color.zig");
_ = @import("fonts/type1.zig");
_ = @import("objects/base.zig");
_ = @import("output/producer.zig");
_ = @import("images/mod.zig");
_ = @import("images/image_info.zig");
_ = @import("images/jpeg.zig");
_ = @import("images/png.zig");
}