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>
335 lines
11 KiB
Zig
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", .{});
|
|
}
|