- 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>
329 lines
10 KiB
Zig
329 lines
10 KiB
Zig
//! zcatpdf - PDF generation library for Zig
|
|
//!
|
|
//! A pure Zig library for creating PDF documents with minimal dependencies.
|
|
//! Based on fpdf2 (Python) architecture.
|
|
//!
|
|
//! ## Quick Start
|
|
//!
|
|
//! ```zig
|
|
//! const zcatpdf = @import("zcatpdf");
|
|
//!
|
|
//! pub fn main() !void {
|
|
//! var pdf = zcatpdf.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;
|
|
pub const Config = pdf.Config;
|
|
|
|
/// 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, transparency, gradients)
|
|
pub const graphics = @import("graphics/mod.zig");
|
|
pub const Color = graphics.Color;
|
|
pub const ExtGState = graphics.ExtGState;
|
|
pub const Gradient = graphics.Gradient;
|
|
pub const LinearGradient = graphics.LinearGradient;
|
|
pub const RadialGradient = graphics.RadialGradient;
|
|
pub const ColorStop = graphics.ColorStop;
|
|
pub const GradientDirection = page.Page.GradientDirection;
|
|
|
|
/// Fonts
|
|
pub const fonts = @import("fonts/mod.zig");
|
|
pub const Font = fonts.Font;
|
|
pub const FontFamily = fonts.FontFamily;
|
|
pub const FontState = fonts.FontState;
|
|
pub const TrueTypeFont = fonts.TrueTypeFont;
|
|
|
|
/// 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;
|
|
pub const CompressionOptions = output.CompressionOptions;
|
|
|
|
/// Images (JPEG, PNG)
|
|
pub const images = @import("images/mod.zig");
|
|
pub const ImageInfo = images.ImageInfo;
|
|
pub const ImageFormat = images.ImageFormat;
|
|
|
|
/// Compression (zlib/deflate)
|
|
pub const compression = @import("compression/mod.zig");
|
|
|
|
/// Table helper
|
|
pub const table = @import("table.zig");
|
|
pub const Table = table.Table;
|
|
pub const TableOptions = table.TableOptions;
|
|
|
|
/// Pagination (page numbers, headers, footers)
|
|
pub const pagination = @import("pagination.zig");
|
|
pub const Pagination = pagination.Pagination;
|
|
pub const PageNumberOptions = pagination.PageNumberOptions;
|
|
pub const FooterOptions = pagination.FooterOptions;
|
|
pub const HeaderOptions = pagination.HeaderOptions;
|
|
pub const addHeader = pagination.addHeader;
|
|
pub const addFooterWithLine = pagination.addFooterWithLine;
|
|
|
|
/// Links (URL annotations)
|
|
pub const links = @import("links.zig");
|
|
pub const Link = links.Link;
|
|
pub const PageLinks = links.PageLinks;
|
|
|
|
/// Outline/Bookmarks
|
|
pub const outline_mod = @import("outline.zig");
|
|
pub const Outline = outline_mod.Outline;
|
|
pub const OutlineItem = outline_mod.OutlineItem;
|
|
|
|
/// Barcodes (Code128, QR)
|
|
pub const barcodes = @import("barcodes/mod.zig");
|
|
pub const Code128 = barcodes.Code128;
|
|
pub const QRCode = barcodes.QRCode;
|
|
|
|
/// Security (Encryption)
|
|
pub const security = @import("security/mod.zig");
|
|
pub const Encryption = security.Encryption;
|
|
pub const EncryptionOptions = security.EncryptionOptions;
|
|
pub const Permissions = security.Permissions;
|
|
|
|
/// Forms (AcroForms)
|
|
pub const forms = @import("forms/mod.zig");
|
|
pub const TextField = forms.TextField;
|
|
pub const CheckBox = forms.CheckBox;
|
|
pub const FieldFlags = forms.FieldFlags;
|
|
pub const FormField = forms.FormField;
|
|
|
|
/// SVG Import
|
|
pub const svg = @import("svg/mod.zig");
|
|
pub const SvgParser = svg.SvgParser;
|
|
pub const SvgElement = svg.SvgElement;
|
|
|
|
/// Templates (reusable layouts)
|
|
pub const template = @import("template/mod.zig");
|
|
pub const Template = template.Template;
|
|
pub const TemplateRegion = template.TemplateRegion;
|
|
pub const RegionType = template.RegionType;
|
|
|
|
/// Markdown styling
|
|
pub const markdown = @import("markdown/mod.zig");
|
|
pub const MarkdownRenderer = markdown.MarkdownRenderer;
|
|
pub const TextSpan = markdown.TextSpan;
|
|
pub const SpanStyle = markdown.SpanStyle;
|
|
|
|
// =============================================================================
|
|
// 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 "zcatpdf 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 pdf_doc = Pdf.init(allocator, .{
|
|
.page_size = .a4,
|
|
.orientation = .portrait,
|
|
});
|
|
defer pdf_doc.deinit();
|
|
|
|
pdf_doc.setTitle("Test Document");
|
|
pdf_doc.setAuthor("zcatpdf");
|
|
|
|
var pg = try pdf_doc.addPage(.{});
|
|
try pg.setFont(.helvetica_bold, 24);
|
|
pg.setFillColor(Color.blue);
|
|
try pg.drawText(50, 750, "Hello zcatpdf!");
|
|
|
|
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 pdf_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("graphics/extgstate.zig");
|
|
_ = @import("graphics/gradient.zig");
|
|
_ = @import("barcodes/mod.zig");
|
|
_ = @import("barcodes/code128.zig");
|
|
_ = @import("barcodes/qr.zig");
|
|
_ = @import("fonts/type1.zig");
|
|
_ = @import("fonts/ttf.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");
|
|
_ = @import("compression/mod.zig");
|
|
_ = @import("compression/zlib.zig");
|
|
_ = @import("table.zig");
|
|
_ = @import("pagination.zig");
|
|
_ = @import("links.zig");
|
|
_ = @import("outline.zig");
|
|
_ = @import("security/mod.zig");
|
|
_ = @import("security/rc4.zig");
|
|
_ = @import("security/encryption.zig");
|
|
_ = @import("forms/mod.zig");
|
|
_ = @import("forms/field.zig");
|
|
_ = @import("svg/mod.zig");
|
|
_ = @import("svg/parser.zig");
|
|
_ = @import("template/mod.zig");
|
|
_ = @import("template/template.zig");
|
|
_ = @import("markdown/mod.zig");
|
|
_ = @import("markdown/markdown.zig");
|
|
}
|