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>
194 lines
6.3 KiB
Zig
194 lines
6.3 KiB
Zig
//! TrueType Font Demo
|
|
//!
|
|
//! Demonstrates loading and using TrueType fonts.
|
|
|
|
const std = @import("std");
|
|
const zpdf = @import("zpdf");
|
|
const Pdf = zpdf.Pdf;
|
|
const Color = zpdf.Color;
|
|
const TrueTypeFont = zpdf.TrueTypeFont;
|
|
|
|
pub fn main() !void {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer _ = gpa.deinit();
|
|
const allocator = gpa.allocator();
|
|
|
|
var pdf = Pdf.init(allocator, .{});
|
|
defer pdf.deinit();
|
|
|
|
pdf.setTitle("TrueType Font Demo");
|
|
pdf.setAuthor("zpdf");
|
|
|
|
// Try to load a system TTF font
|
|
const font_paths = [_][]const u8{
|
|
"/usr/share/fonts/google-carlito-fonts/Carlito-Regular.ttf",
|
|
"/usr/share/fonts/adwaita-sans-fonts/AdwaitaSans-Regular.ttf",
|
|
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
|
"/usr/share/fonts/dejavu/DejaVuSans.ttf",
|
|
};
|
|
|
|
var ttf_font: ?TrueTypeFont = null;
|
|
var font_path: ?[]const u8 = null;
|
|
var font_data: ?[]u8 = null; // Keep font data alive
|
|
|
|
for (font_paths) |path| {
|
|
const file = std.fs.openFileAbsolute(path, .{}) catch continue;
|
|
defer file.close();
|
|
|
|
const data = file.readToEndAlloc(allocator, 10 * 1024 * 1024) catch continue;
|
|
// Don't defer free - font needs to keep data alive
|
|
|
|
ttf_font = TrueTypeFont.parse(allocator, data) catch {
|
|
allocator.free(data);
|
|
continue;
|
|
};
|
|
font_data = data;
|
|
font_path = path;
|
|
break;
|
|
}
|
|
defer if (font_data) |data| allocator.free(data);
|
|
|
|
var page = try pdf.addPage(.{});
|
|
|
|
// Title
|
|
try page.setFont(.helvetica_bold, 28);
|
|
page.setFillColor(Color.hex(0x333333));
|
|
try page.drawText(50, 780, "TrueType Font Demo");
|
|
|
|
try page.setFont(.helvetica, 12);
|
|
page.setFillColor(Color.hex(0x666666));
|
|
try page.drawText(50, 760, "TrueType font parsing and information");
|
|
|
|
if (ttf_font) |*font| {
|
|
defer font.deinit();
|
|
|
|
// Font information section
|
|
try page.setFont(.helvetica_bold, 16);
|
|
page.setFillColor(Color.hex(0x333333));
|
|
try page.drawText(50, 720, "Loaded Font Information");
|
|
|
|
try page.setFont(.courier, 10);
|
|
page.setFillColor(Color.black);
|
|
|
|
var y: f32 = 690;
|
|
|
|
try page.drawText(50, y, "File:");
|
|
try page.drawText(150, y, font_path.?);
|
|
y -= 20;
|
|
|
|
try page.drawText(50, y, "Family:");
|
|
if (font.family_name.len > 0) {
|
|
try page.drawText(150, y, font.family_name);
|
|
} else {
|
|
try page.drawText(150, y, "(not available)");
|
|
}
|
|
y -= 20;
|
|
|
|
try page.drawText(50, y, "Subfamily:");
|
|
if (font.subfamily_name.len > 0) {
|
|
try page.drawText(150, y, font.subfamily_name);
|
|
} else {
|
|
try page.drawText(150, y, "(not available)");
|
|
}
|
|
y -= 20;
|
|
|
|
try page.drawText(50, y, "PostScript:");
|
|
if (font.postscript_name.len > 0) {
|
|
try page.drawText(150, y, font.postscript_name);
|
|
} else {
|
|
try page.drawText(150, y, "(not available)");
|
|
}
|
|
y -= 20;
|
|
|
|
// Font metrics
|
|
var buf: [128]u8 = undefined;
|
|
|
|
const units_str = std.fmt.bufPrint(&buf, "{d}", .{font.units_per_em}) catch "(error)";
|
|
try page.drawText(50, y, "Units/EM:");
|
|
try page.drawText(150, y, units_str);
|
|
y -= 20;
|
|
|
|
const ascender_str = std.fmt.bufPrint(&buf, "{d}", .{font.ascender}) catch "(error)";
|
|
try page.drawText(50, y, "Ascender:");
|
|
try page.drawText(150, y, ascender_str);
|
|
y -= 20;
|
|
|
|
const descender_str = std.fmt.bufPrint(&buf, "{d}", .{font.descender}) catch "(error)";
|
|
try page.drawText(50, y, "Descender:");
|
|
try page.drawText(150, y, descender_str);
|
|
y -= 20;
|
|
|
|
const glyphs_str = std.fmt.bufPrint(&buf, "{d}", .{font.num_glyphs}) catch "(error)";
|
|
try page.drawText(50, y, "Num Glyphs:");
|
|
try page.drawText(150, y, glyphs_str);
|
|
y -= 20;
|
|
|
|
// Bounding box
|
|
const bbox_str = std.fmt.bufPrint(&buf, "[{d}, {d}, {d}, {d}]", .{
|
|
font.bbox[0],
|
|
font.bbox[1],
|
|
font.bbox[2],
|
|
font.bbox[3],
|
|
}) catch "(error)";
|
|
try page.drawText(50, y, "BBox:");
|
|
try page.drawText(150, y, bbox_str);
|
|
y -= 20;
|
|
|
|
const angle_str = std.fmt.bufPrint(&buf, "{d:.2}", .{font.italic_angle}) catch "(error)";
|
|
try page.drawText(50, y, "Italic Angle:");
|
|
try page.drawText(150, y, angle_str);
|
|
y -= 20;
|
|
|
|
// Character width test
|
|
y -= 20;
|
|
try page.setFont(.helvetica_bold, 14);
|
|
try page.drawText(50, y, "Character Widths (in font units):");
|
|
y -= 20;
|
|
|
|
try page.setFont(.courier, 10);
|
|
|
|
const test_chars = "AaBbCcDdEeFf0123456789";
|
|
for (test_chars) |c| {
|
|
const glyph_id = font.getGlyphIndex(c);
|
|
const width = font.getGlyphWidth(glyph_id);
|
|
const char_str = std.fmt.bufPrint(&buf, "'{c}' -> glyph {d}, width {d}", .{ c, glyph_id, width }) catch "(error)";
|
|
try page.drawText(50, y, char_str);
|
|
y -= 15;
|
|
|
|
if (y < 100) break;
|
|
}
|
|
|
|
// String width calculation
|
|
y -= 20;
|
|
try page.setFont(.helvetica_bold, 14);
|
|
try page.drawText(50, y, "String Width Calculation:");
|
|
y -= 20;
|
|
|
|
try page.setFont(.courier, 10);
|
|
const test_string = "Hello, World!";
|
|
const width_12pt = font.stringWidth(test_string, 12.0);
|
|
const width_str = std.fmt.bufPrint(&buf, "\"{s}\" at 12pt = {d:.2} points", .{ test_string, width_12pt }) catch "(error)";
|
|
try page.drawText(50, y, width_str);
|
|
} else {
|
|
try page.setFont(.helvetica, 14);
|
|
page.setFillColor(Color.red);
|
|
try page.drawText(50, 700, "No TrueType font found on this system.");
|
|
try page.drawText(50, 680, "Tried the following paths:");
|
|
|
|
try page.setFont(.courier, 10);
|
|
var y: f32 = 650;
|
|
for (font_paths) |path| {
|
|
try page.drawText(70, y, path);
|
|
y -= 15;
|
|
}
|
|
}
|
|
|
|
// Note about TTF embedding
|
|
try page.setFont(.helvetica_oblique, 10);
|
|
page.setFillColor(Color.hex(0x999999));
|
|
try page.drawText(50, 50, "Note: Full TTF embedding in PDF requires additional implementation for CIDFont Type 2 output.");
|
|
|
|
try pdf.save("ttf_demo.pdf");
|
|
|
|
std.debug.print("Generated ttf_demo.pdf\n", .{});
|
|
}
|