256 lines
9.7 KiB
Zig
256 lines
9.7 KiB
Zig
//TODO: re-implement sorting
|
|
//c library usage based on https://medium.com/@eddo2626/lets-learn-zig-4-using-c-libraries-in-zig-5fcc3206f0dc
|
|
//
|
|
const std = @import("std");
|
|
const clap = @import("clap");
|
|
const c = @cImport({
|
|
@cInclude("pdfgen.h");
|
|
});
|
|
const print = std.debug.print;
|
|
const io = std.io;
|
|
const fs = std.fs;
|
|
const json = std.json;
|
|
const cwd = fs.cwd();
|
|
const indexOf = std.mem.indexOf;
|
|
const expect = std.testing.expect;
|
|
const assert = std.debug.assert;
|
|
|
|
const Card = struct {
|
|
name: []const u8 = "", //string
|
|
mana_cost: []const u8 = "", //string
|
|
cmc: f32 = 0, //technically a float? but I think we can always cast safely cast. EDIT: NOPE
|
|
type_line: []const u8 = "", //string
|
|
oracle_text: []const u8 = "", //string
|
|
power: []const u8 = "", //coerced to string
|
|
toughness: []const u8 = "", //coerced to string
|
|
card_faces: ?[]Card = null, //array of cards
|
|
};
|
|
|
|
const TextCard = struct {
|
|
lines: [][]const u8 = undefined,
|
|
};
|
|
|
|
|
|
//dimension constants and defaults
|
|
const cardWidth = 30;
|
|
const cardHeight = 32;
|
|
const minCardHeight = 5;
|
|
const pageHeight = 66;
|
|
var heightMayVary = false;
|
|
|
|
const formatString = "{s: <" ++ std.fmt.digits2(cardWidth) ++ "}";
|
|
const lineFormatter = "|" ++ formatString;
|
|
const spacer: []const u8 = "|" ++ (" " ** cardWidth);
|
|
const oracleFileName = "/home/lumenk/Documents/code/zig/proxy-print/src/oracle-cards-20240701090158.json"; //"oracle-cards-20240205220208.js";
|
|
const fullWidthSpacer = (spacer ** 3) ++ "\n";
|
|
const pageWidth = fullWidthSpacer.len;
|
|
|
|
test "Check constants" {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
const paddedLine = try std.fmt.allocPrint(gpa.allocator(), lineFormatter, .{"hello> woooooooooooooooooooorl"});
|
|
try expect(paddedLine.len == spacer.len);
|
|
}
|
|
|
|
pub fn main() !void {
|
|
var args = try std.process.argsWithAllocator(std.heap.page_allocator);
|
|
|
|
//TODO: properly handle program arguments
|
|
_ = args.next(); //handle program name (args[0])
|
|
const listFileName: []const u8 = args.next() orelse {
|
|
return error.ExpectedArgument;
|
|
};
|
|
heightMayVary = stringToBool(args.next());
|
|
|
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
const pdf_doc: *c.pdf_doc = @ptrCast(c.pdf_create(c.PDF_A4_WIDTH, c.PDF_A4_HEIGHT, &c.pdf_info{.creator = ("My Software" ++ " " ** 53).* }).?);
|
|
_ = c.pdf_set_font(pdf_doc, "Times-Roman");
|
|
const page = c.pdf_append_page(pdf_doc);
|
|
_ = c.pdf_add_text(pdf_doc, page, "This is text", 12, 10, 100, c.PDF_BLACK);
|
|
// _ = c.pdf_add_line(pdf_doc, null, 50, 24, 150, 24, 3, c.PDF_BLACK);
|
|
_ = c.pdf_save(pdf_doc, "output.pdf");
|
|
_ = c.pdf_destroy(pdf_doc);
|
|
|
|
const oracleFile = try cwd.openFile(oracleFileName, .{});
|
|
var jsonReader = json.reader(allocator, oracleFile.reader());
|
|
const parsedJson = try json.parseFromTokenSource([]Card, allocator, &jsonReader, .{ .ignore_unknown_fields = true });
|
|
|
|
var cardNames = std.ArrayList([]const u8).init(allocator);
|
|
const listReader = (try cwd.openFile(listFileName, .{})).reader();
|
|
var line = std.ArrayList(u8).init(allocator);
|
|
while (listReader.streamUntilDelimiter(line.writer(), '\n', null)) {
|
|
defer line.clearRetainingCapacity();
|
|
const cardName = line.items[indexOf(u8, line.items, " ").? + 1 .. indexOf(u8, line.items, "(").? - 1];
|
|
try cardNames.append(try allocator.dupe(u8, cardName));
|
|
} else |err| switch (err) {
|
|
error.EndOfStream => {},
|
|
else => return err,
|
|
}
|
|
|
|
var cards = std.StringArrayHashMap(TextCard).init(allocator);
|
|
for (parsedJson.value) |cardObj| {
|
|
for (cardNames.items, 0..) |cardName, i| {
|
|
if (std.mem.eql(u8, cardName, cardObj.name)) {
|
|
const printableCard = try card(cardObj, allocator, false);
|
|
try cards.put(cardObj.name, printableCard);
|
|
_ = cardNames.orderedRemove(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
var allPrinted = std.ArrayList(u8).init(allocator);
|
|
|
|
var rowToPrint = std.ArrayList(TextCard).init(allocator);
|
|
// var cardIterator = cards.valueIterator();
|
|
for (cards.values()) |cardText| {
|
|
try rowToPrint.append(cardText);
|
|
if (rowToPrint.items.len >= 3) {
|
|
try cardRow.print(allocator, rowToPrint.items, &allPrinted);
|
|
rowToPrint.clearAndFree();
|
|
}
|
|
} else {
|
|
try cardRow.print(allocator, rowToPrint.items, &allPrinted);
|
|
std.debug.print("{s}", .{allPrinted.items});
|
|
rowToPrint.clearAndFree();
|
|
}
|
|
}
|
|
|
|
fn card(
|
|
cardObj: Card,
|
|
allocator: std.mem.Allocator,
|
|
isFace: bool,
|
|
) !TextCard {
|
|
var cardText = std.ArrayList([]const u8).init(allocator);
|
|
|
|
var fullUnformattedText = std.ArrayList(u8).init(allocator);
|
|
if (!isFace) {
|
|
try fullUnformattedText.appendSlice(try std.mem.concat(allocator, u8, &[_][]const u8{
|
|
cardObj.name,
|
|
if (cardObj.mana_cost.len > 0) " " else "",
|
|
cardObj.mana_cost,
|
|
" (",
|
|
cardObj.type_line,
|
|
") >> ",
|
|
}));
|
|
}
|
|
try fullUnformattedText.appendSlice(try std.mem.concat(allocator, u8, &[_][]const u8{
|
|
cardObj.oracle_text,
|
|
if (cardObj.power.len > 0) " (" else "",
|
|
cardObj.power,
|
|
if (cardObj.power.len > 0) "/" else "",
|
|
cardObj.toughness,
|
|
if (cardObj.power.len > 0) ") " else "",
|
|
}));
|
|
if (cardObj.card_faces) |faces| {
|
|
for (faces, 0..) |face, idx| {
|
|
const faceText = (try card(face, allocator, true)).lines;
|
|
try fullUnformattedText.appendSlice(std.mem.trim(u8, try std.mem.join(allocator, " ", faceText), "\n"));
|
|
if (idx == 0) try fullUnformattedText.appendSlice(" // ");
|
|
}
|
|
}
|
|
|
|
var line = std.ArrayList(u8).init(allocator);
|
|
var word = std.ArrayList(u8).init(allocator);
|
|
|
|
for (fullUnformattedText.items) |char| {
|
|
try switch (char) {
|
|
'\n', ' ' => addWord(&word, &line, &cardText),
|
|
else => if (std.ascii.isASCII(char)) {
|
|
try word.append(char);
|
|
},
|
|
};
|
|
} else {
|
|
try addWord(&word, &line, &cardText);
|
|
const lineToBeAdded = std.mem.trimRight(u8, try line.toOwnedSlice(), " ");
|
|
// const lineToBeAdded = try line.toOwnedSlice();
|
|
try cardText.append(lineToBeAdded);
|
|
line.clearAndFree();
|
|
}
|
|
while (wrongCardHeight(cardText.items.len)) {
|
|
try cardText.append(" " ** (cardWidth - 2));
|
|
}
|
|
try cardText.append(" " ** (cardWidth - 2));
|
|
// return cardText.items;
|
|
return TextCard{ .lines = try cardText.toOwnedSlice() };
|
|
}
|
|
|
|
fn wrongCardHeight(length: usize) bool {
|
|
return (!heightMayVary and length < cardHeight) or length < minCardHeight;
|
|
}
|
|
|
|
fn addWord(word: *std.ArrayList(u8), line: *std.ArrayList(u8), cardText: *std.ArrayList([]const u8)) !void {
|
|
if (line.items.len + word.items.len >= (cardWidth)) {
|
|
const lineToBeAdded = std.mem.trimRight(u8, try line.toOwnedSlice(), " ");
|
|
assert(lineToBeAdded.len < 30);
|
|
try cardText.append(lineToBeAdded);
|
|
line.clearAndFree();
|
|
}
|
|
try line.appendSlice(word.items);
|
|
try line.append(' ');
|
|
word.clearAndFree();
|
|
}
|
|
const linesList = std.MultiArrayList(cardRow);
|
|
const cardRow = struct {
|
|
first: []const u8 = spacer,
|
|
second: []const u8 = spacer,
|
|
third: []const u8 = spacer,
|
|
last: []const u8 = "\n",
|
|
fn print(allocator: std.mem.Allocator, cards: []TextCard, allPrinted: *std.ArrayList(u8)) !void {
|
|
_ = allocator;
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
|
|
var lines = linesList{};
|
|
defer lines.deinit(gpa.allocator());
|
|
|
|
for (cards, 0..) |cardObj, cardNo| {
|
|
//const cardText = try card(cardObj);
|
|
const cardText = cardObj.lines;
|
|
for (cardText, 0..) |line, idx| {
|
|
//this step is probably unnecessary
|
|
const strippedLine = std.mem.trimRight(u8, line, " ");
|
|
const paddedLine = try std.fmt.allocPrint(gpa.allocator(), lineFormatter, .{strippedLine});
|
|
const theoreticalLength = (paddedLine.len * 3) + 1;
|
|
assert(paddedLine.len == spacer.len);
|
|
assert(theoreticalLength == pageWidth);
|
|
const placeholder = if (idx < lines.items(.first).len) lines.get(idx) else cardRow{};
|
|
const new: cardRow = switch (cardNo) {
|
|
0 => .{ .first = paddedLine },
|
|
1 => .{ .first = placeholder.first, .second = paddedLine },
|
|
2 => .{ .first = placeholder.first, .second = placeholder.second, .third = paddedLine },
|
|
else => unreachable,
|
|
};
|
|
if (idx < lines.items(.first).len) {
|
|
lines.set(idx, new);
|
|
} else {
|
|
try lines.append(gpa.allocator(), new);
|
|
}
|
|
}
|
|
}
|
|
const rowHeight = lines.items(.first).len;
|
|
while (remainingPage(allPrinted.items.len) <= rowHeight) {
|
|
try allPrinted.appendSlice(fullWidthSpacer);
|
|
}
|
|
for (lines.items(.first), 0..) |_, idx| {
|
|
const line = lines.get(idx);
|
|
const printedWidth = line.first.len + line.second.len + line.third.len + line.last.len;
|
|
assert(printedWidth == pageWidth);
|
|
const fullLine = try std.mem.concat(gpa.allocator(), u8, &[_][]const u8{ line.first, line.second, line.third, line.last });
|
|
try allPrinted.appendSlice(fullLine);
|
|
}
|
|
}
|
|
};
|
|
|
|
test "Remaining page length" {
|
|
try expect(remainingPage(67) == 0);
|
|
try expect(remainingPage(1) == 0);
|
|
}
|
|
fn remainingPage(length: usize) usize {
|
|
assert(length % pageWidth == 0);
|
|
return pageHeight - ((length / pageWidth) % pageHeight);
|
|
}
|
|
|
|
fn stringToBool(str: ?[]const u8) bool {
|
|
return std.mem.eql(u8, (str orelse "false"), "true");
|
|
}
|