//! TrueType Font Demo //! //! Demonstrates loading and using TrueType fonts. const std = @import("std"); const zcatpdf = @import("zcatpdf"); const Pdf = zcatpdf.Pdf; const Color = zcatpdf.Color; const TrueTypeFont = zcatpdf.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("zcatpdf"); // 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", .{}); }