zcatpdf/examples/transforms_demo.zig
reugenio 3826cbaed4 Release v1.0 - Feature Complete PDF Generation Library
Major features added since v0.5:
- PNG support with alpha/transparency (soft masks)
- FlateDecode compression via libdeflate-zig
- Bookmarks/Outline for document navigation
- Bezier curves, circles, ellipses, arcs
- Transformations (rotate, scale, translate, skew)
- Transparency/opacity (fill and stroke alpha)
- Linear and radial gradients (Shading Patterns)
- Code128 (1D) and QR Code (2D) barcodes
- TrueType font parsing (metrics, glyph widths)
- RC4 encryption module (40/128-bit)
- AcroForms module (TextField, CheckBox)
- SVG import (basic shapes and paths)
- Template system (reusable layouts)
- Markdown styling (bold, italic, links, headings, lists)

Documentation:
- README.md: Complete API reference with code examples
- FUTURE_IMPROVEMENTS.md: Detailed roadmap for future development
- CLAUDE.md: Updated to v1.0 release status

Stats:
- 125+ unit tests passing
- 16 demo examples
- 46 source files

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 02:01:17 +01:00

335 lines
11 KiB
Zig

//! Transforms Demo - Rotation, Scaling, Skew, and Translation
//!
//! Demonstrates the transformation capabilities of zpdf including:
//! - Rotation around a point
//! - Scaling from a point
//! - Skewing (shearing)
//! - Translation
//! - Combined transformations
const std = @import("std");
const pdf = @import("zpdf");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
std.debug.print("zpdf - Transforms Demo\n", .{});
var doc = pdf.Pdf.init(allocator, .{});
defer doc.deinit();
doc.setTitle("Transforms Demo");
doc.setAuthor("zpdf");
// =========================================================================
// Page 1: Rotation
// =========================================================================
{
const page = try doc.addPage(.{});
// Title
try page.setFont(.helvetica_bold, 24);
page.setFillColor(pdf.Color.rgb(41, 98, 255));
try page.drawText(50, 780, "Rotation Transforms");
try page.setFont(.helvetica, 11);
page.setFillColor(pdf.Color.black);
try page.drawText(50, 755, "Rotate graphics around a specified point");
// Original rectangle (for reference)
try page.setFont(.helvetica, 10);
page.setFillColor(pdf.Color.dark_gray);
try page.drawText(100, 680, "Original:");
page.setFillColor(pdf.Color.rgb(200, 200, 200));
page.setStrokeColor(pdf.Color.black);
try page.setLineWidth(1);
try page.drawFilledRect(100, 600, 80, 60);
// Draw rotation center marker
page.setFillColor(pdf.Color.red);
try page.fillCircle(140, 630, 3);
// Rotated rectangles
const angles = [_]f32{ 15, 30, 45, 60 };
const colors = [_]pdf.Color{
pdf.Color.rgb(255, 100, 100),
pdf.Color.rgb(100, 255, 100),
pdf.Color.rgb(100, 100, 255),
pdf.Color.rgb(255, 200, 100),
};
var x: f32 = 250;
for (angles, colors) |angle, color| {
try page.setFont(.helvetica, 9);
page.setFillColor(pdf.Color.dark_gray);
var angle_buf: [32]u8 = undefined;
const angle_str = std.fmt.bufPrint(&angle_buf, "{d} degrees", .{@as(i32, @intFromFloat(angle))}) catch "angle";
try page.drawText(x, 680, angle_str);
// Save state before transformation
try page.saveState();
// Rotate around center of rectangle
const cx = x + 40;
const cy: f32 = 630;
try page.rotate(angle, cx, cy);
// Draw rectangle
page.setFillColor(color);
page.setStrokeColor(pdf.Color.black);
try page.drawFilledRect(x, 600, 80, 60);
// Restore state
try page.restoreState();
// Draw center point
page.setFillColor(pdf.Color.red);
try page.fillCircle(cx, cy, 3);
x += 100;
}
// Rotated text example
try page.setFont(.helvetica_bold, 14);
page.setFillColor(pdf.Color.black);
try page.drawText(50, 520, "Rotated Text:");
const text_angles = [_]f32{ 0, 15, 30, 45, 90 };
x = 100;
for (text_angles) |angle| {
try page.saveState();
try page.rotate(angle, x, 450);
try page.setFont(.helvetica, 12);
page.setFillColor(pdf.Color.rgb(0, 100, 150));
try page.drawText(x, 450, "Hello!");
try page.restoreState();
x += 80;
}
// Multiple rotations around same point (like clock hands)
try page.setFont(.helvetica_bold, 14);
page.setFillColor(pdf.Color.black);
try page.drawText(50, 350, "Clock-like Rotation:");
const clock_cx: f32 = 200;
const clock_cy: f32 = 250;
// Draw clock circle
page.setStrokeColor(pdf.Color.dark_gray);
try page.setLineWidth(2);
try page.drawCircle(clock_cx, clock_cy, 80);
// Draw hour marks
try page.setLineWidth(1);
for (0..12) |i| {
const fi: f32 = @floatFromInt(i);
try page.saveState();
try page.rotate(fi * 30, clock_cx, clock_cy);
page.setStrokeColor(pdf.Color.dark_gray);
try page.drawLine(clock_cx, clock_cy + 70, clock_cx, clock_cy + 80);
try page.restoreState();
}
// Draw clock hands
try page.setLineWidth(3);
page.setStrokeColor(pdf.Color.rgb(50, 50, 150));
try page.saveState();
try page.rotate(60, clock_cx, clock_cy); // Hour hand at ~2 o'clock
try page.drawLine(clock_cx, clock_cy, clock_cx, clock_cy + 45);
try page.restoreState();
try page.setLineWidth(2);
page.setStrokeColor(pdf.Color.rgb(150, 50, 50));
try page.saveState();
try page.rotate(210, clock_cx, clock_cy); // Minute hand at ~7 o'clock
try page.drawLine(clock_cx, clock_cy, clock_cx, clock_cy + 65);
try page.restoreState();
// Footer
try page.setFont(.helvetica, 9);
page.setFillColor(pdf.Color.medium_gray);
try page.drawText(250, 50, "Page 1 of 2 - zpdf Transforms Demo");
}
// =========================================================================
// Page 2: Scale, Skew, and Combined Transforms
// =========================================================================
{
const page = try doc.addPage(.{});
// Title
try page.setFont(.helvetica_bold, 24);
page.setFillColor(pdf.Color.rgb(41, 98, 255));
try page.drawText(50, 780, "Scale, Skew & Combined");
// Scaling section
try page.setFont(.helvetica_bold, 14);
page.setFillColor(pdf.Color.black);
try page.drawText(50, 720, "Scaling:");
try page.setFont(.helvetica, 10);
page.setFillColor(pdf.Color.dark_gray);
try page.drawText(50, 700, "Scale graphics from a reference point");
// Original
page.setFillColor(pdf.Color.light_gray);
page.setStrokeColor(pdf.Color.black);
try page.setLineWidth(1);
try page.drawFilledRect(80, 620, 40, 40);
try page.setFont(.helvetica, 9);
page.setFillColor(pdf.Color.dark_gray);
try page.drawText(80, 600, "1.0x");
// Scaled versions
const scales = [_]struct { sx: f32, sy: f32, label: []const u8 }{
.{ .sx = 1.5, .sy = 1.5, .label = "1.5x" },
.{ .sx = 0.5, .sy = 0.5, .label = "0.5x" },
.{ .sx = 2.0, .sy = 1.0, .label = "2x,1x" },
.{ .sx = 1.0, .sy = 2.0, .label = "1x,2x" },
};
var x: f32 = 160;
for (scales) |s| {
try page.saveState();
try page.scale(s.sx, s.sy, x + 20, 640);
page.setFillColor(pdf.Color.rgb(100, 200, 150));
page.setStrokeColor(pdf.Color.black);
try page.drawFilledRect(x, 620, 40, 40);
try page.restoreState();
try page.setFont(.helvetica, 9);
page.setFillColor(pdf.Color.dark_gray);
try page.drawText(x, 600, s.label);
x += 100;
}
// Skewing section
try page.setFont(.helvetica_bold, 14);
page.setFillColor(pdf.Color.black);
try page.drawText(50, 540, "Skewing:");
try page.setFont(.helvetica, 10);
page.setFillColor(pdf.Color.dark_gray);
try page.drawText(50, 520, "Skew (shear) graphics along X or Y axis");
// Original
page.setFillColor(pdf.Color.light_gray);
page.setStrokeColor(pdf.Color.black);
try page.drawFilledRect(80, 440, 50, 50);
try page.setFont(.helvetica, 9);
page.setFillColor(pdf.Color.dark_gray);
try page.drawText(80, 420, "Original");
// Skewed versions
const skews = [_]struct { sx: f32, sy: f32, label: []const u8 }{
.{ .sx = 15, .sy = 0, .label = "X: 15 deg" },
.{ .sx = 30, .sy = 0, .label = "X: 30 deg" },
.{ .sx = 0, .sy = 15, .label = "Y: 15 deg" },
.{ .sx = 15, .sy = 15, .label = "XY: 15 deg" },
};
x = 170;
for (skews) |s| {
try page.saveState();
try page.translate(x, 465);
try page.skew(s.sx, s.sy);
try page.translate(-x, -465);
page.setFillColor(pdf.Color.rgb(200, 150, 100));
page.setStrokeColor(pdf.Color.black);
try page.drawFilledRect(x, 440, 50, 50);
try page.restoreState();
try page.setFont(.helvetica, 9);
page.setFillColor(pdf.Color.dark_gray);
try page.drawText(x, 420, s.label);
x += 100;
}
// Combined transforms section
try page.setFont(.helvetica_bold, 14);
page.setFillColor(pdf.Color.black);
try page.drawText(50, 360, "Combined Transforms:");
try page.setFont(.helvetica, 10);
page.setFillColor(pdf.Color.dark_gray);
try page.drawText(50, 340, "Multiple transformations applied in sequence");
// Example 1: Rotate + Scale
try page.saveState();
try page.rotate(30, 150, 260);
try page.scale(1.5, 1.5, 150, 260);
page.setFillColor(pdf.Color.rgb(255, 150, 150));
page.setStrokeColor(pdf.Color.rgb(200, 0, 0));
try page.setLineWidth(2);
try page.drawFilledRect(120, 230, 60, 60);
try page.restoreState();
try page.setFont(.helvetica, 9);
page.setFillColor(pdf.Color.dark_gray);
try page.drawText(100, 170, "Rotate + Scale");
// Example 2: Scale + Skew
try page.saveState();
try page.translate(300, 260);
try page.skew(20, 0);
try page.scaleFromOrigin(1.2, 0.8);
try page.translate(-300, -260);
page.setFillColor(pdf.Color.rgb(150, 255, 150));
page.setStrokeColor(pdf.Color.rgb(0, 150, 0));
try page.drawFilledRect(270, 230, 60, 60);
try page.restoreState();
try page.setFont(.helvetica, 9);
page.setFillColor(pdf.Color.dark_gray);
try page.drawText(250, 170, "Scale + Skew");
// Example 3: Rotate + Skew + Scale
try page.saveState();
try page.rotate(-15, 450, 260);
try page.translate(450, 260);
try page.skew(10, 5);
try page.scaleFromOrigin(1.3, 1.1);
try page.translate(-450, -260);
page.setFillColor(pdf.Color.rgb(150, 150, 255));
page.setStrokeColor(pdf.Color.rgb(0, 0, 200));
try page.drawFilledRect(420, 230, 60, 60);
try page.restoreState();
try page.setFont(.helvetica, 9);
page.setFillColor(pdf.Color.dark_gray);
try page.drawText(400, 170, "Rotate + Skew + Scale");
// Footer
try page.setFont(.helvetica, 9);
page.setFillColor(pdf.Color.medium_gray);
try page.drawText(250, 50, "Page 2 of 2 - zpdf Transforms Demo");
}
// Save
const filename = "transforms_demo.pdf";
try doc.save(filename);
std.debug.print("Created: {s} ({d} pages)\n", .{ filename, doc.pageCount() });
std.debug.print("Done!\n", .{});
}