added support for multiple copies of cards, simplified sorting
This commit is contained in:
parent
038dd4d78a
commit
238a4017f4
|
@ -0,0 +1,100 @@
|
|||
1 Ancestral Recall (VMA) 1
|
||||
1 Arid Mesa (MH2) 436 *E*
|
||||
1 Boseiju, Who Endures (NEO) 266
|
||||
1 Botanical Sanctum (KLD) 244
|
||||
1 Brainstorm (MMQ) 61
|
||||
1 Breeding Pool (RNA) 246
|
||||
1 Consider (MID) 44
|
||||
1 Cosmic Rebirth (MAT) 78 *F*
|
||||
1 Counterspell (MMQ) 69
|
||||
1 Cryptic Command (MMA) 38
|
||||
1 Deserted Beach (WHO) 270
|
||||
1 Dovin's Veto (TSR) 375
|
||||
1 Dreamroot Cascade (VOW) 262
|
||||
1 Dress Down (MH2) 39
|
||||
1 Eiganjo, Seat of the Empire (NEO) 268
|
||||
1 Entreat the Angels (AVR) 20
|
||||
1 Field of the Dead (M20) 247
|
||||
1 Flooded Strand (MH3) 220
|
||||
1 Force of Negation (H1R) 9 *F*
|
||||
1 Force of Will (ME1) 33
|
||||
1 Get Lost (LCI) 333
|
||||
1 Gideon Jura (ROE) 21
|
||||
1 Gitaxian Probe (NPH) 35
|
||||
1 Growth Spiral (RNA) 178
|
||||
1 Hallowed Fountain (RTR) 241
|
||||
1 Hedge Maze (MKM) 326 *F*
|
||||
1 Ice-Fang Coatl (H1R) 27 *F*
|
||||
1 Island (MMQ) 335 *F*
|
||||
1 Jace, the Mind Sculptor (EMA) 57
|
||||
1 Karakas (TD0) B32
|
||||
1 Leyline Binding (PRM) 103396
|
||||
1 Library of Alexandria (ARN) 76
|
||||
1 Logic Knot (FUT) 52
|
||||
1 Lose Focus (MH2) 49
|
||||
1 Lush Portico (MKM) 327
|
||||
1 Mana Leak (PLST) DDN-64
|
||||
1 Marsh Flats (MH2) 437 *E*
|
||||
1 Mental Misstep (NPH) 38
|
||||
1 Mental Note (JUD) 46
|
||||
1 Merchant Scroll (8ED) 91
|
||||
1 Meticulous Archive (MKM) 328 *F*
|
||||
1 Misty Rainforest (MH2) 250
|
||||
1 Murktide Regent (MH2) 337 *F*
|
||||
1 Mystic Sanctuary (ELD) 247
|
||||
1 Mystical Tutor (MIR) 80
|
||||
1 No More Lies (MKM) 221
|
||||
1 Oko, Thief of Crowns (ELD) 197
|
||||
1 Opt (STA) 19
|
||||
1 Otawara, Soaring City (NEO) 271
|
||||
1 Path to Exile (PF20) 1 *F*
|
||||
1 Plains (INV) 331 *F*
|
||||
1 Planar Genesis (MH3) 198
|
||||
1 Polluted Delta (MH3) 224
|
||||
1 Ponder (C21) 125
|
||||
1 Portent (ICE) 90
|
||||
1 Preordain (CMR) 84
|
||||
1 Prismatic Ending (MH2) 384
|
||||
1 Prismatic Vista (H1R) 40 *E*
|
||||
1 Remand (RVR) 59
|
||||
1 Reprieve (LTR) 26
|
||||
1 Savannah (LEA) 280
|
||||
1 Scalding Tarn (ZNE) 7
|
||||
1 Scapeshift (M19) 201
|
||||
1 Seachrome Coast (ONE) 258
|
||||
1 Seasoned Dungeoneer (CLB) 610
|
||||
1 Sensei's Divining Top (CHK) 268
|
||||
1 Serum Visions (5DN) 36
|
||||
1 Shark Typhoon (IKO) 67
|
||||
1 Sleight of Hand (P02) 46
|
||||
1 Snapcaster Mage (ISD) 78
|
||||
1 Snow-Covered Forest (KHM) 285
|
||||
1 Snow-Covered Island (ICE) 371
|
||||
1 Snow-Covered Plains (ICE) 367
|
||||
1 Solitude (MH2) 307 *F*
|
||||
1 Spara's Headquarters (SNC) 257
|
||||
1 Spell Pierce (SLD) 41
|
||||
1 Spell Snare (DIS) 33
|
||||
1 Supreme Verdict (RTR) 201
|
||||
1 Swords to Plowshares (ICE) 54
|
||||
1 Tamiyo, Inquisitive Student // Tamiyo, Seasoned Scholar (MH3) 443
|
||||
1 Teferi, Hero of Dominaria (DOM) 207
|
||||
1 Teferi, Time Raveler (WAR) 221
|
||||
1 Temple Garden (RAV) 284
|
||||
1 Terminus (AVR) 38
|
||||
1 The One Ring (LTR) 451
|
||||
1 Thought Scour (2X2) 351
|
||||
1 Treasure Cruise (TSR) 319
|
||||
1 Triumph of Saint Katherine (40K) 17
|
||||
1 Tropical Island (LEA) 283
|
||||
1 Tundra (LEA) 284
|
||||
1 Underground Sea (SUM) 290
|
||||
1 Up the Beanstalk (WOE) 195
|
||||
1 Uro, Titan of Nature's Wrath (PTHB) 229p
|
||||
1 Verdant Catacombs (MH2) 440 *E*
|
||||
1 Wall of Blossoms (STH) 125
|
||||
1 Wall of Omens (2X2) 344
|
||||
1 Wasteland (SLD) 178
|
||||
1 White Plume Adventurer (CLB) 558 *F*
|
||||
1 Windswept Heath (KTK) 248
|
||||
10 Plains (ONS) 330
|
1465
output.pdf
1465
output.pdf
File diff suppressed because it is too large
Load Diff
126
src/print.zig
126
src/print.zig
|
@ -1,9 +1,7 @@
|
|||
//TODO: consider eliminating the TextCard struct
|
||||
//TODO: add some kind of "update" command to support pulling new oracle data
|
||||
//TODO: implement oracleFileName as a cli arg
|
||||
//TODO: add more robust tests
|
||||
//TODO: support other list formats
|
||||
//TODO: support multiple copies of cards
|
||||
const std = @import("std");
|
||||
|
||||
// https://github.com/Hejsil/zig-clap
|
||||
|
@ -12,7 +10,6 @@ const clap = @import("clap");
|
|||
// https://codeberg.org/dude_the_builder/zg
|
||||
const DisplayWidth = @import("DisplayWidth");
|
||||
|
||||
|
||||
// https://github.com/AndreRenaud/PDFGen
|
||||
// including the header file only works for a full build, "zig run" can't find it
|
||||
const c = @cImport({
|
||||
|
@ -38,20 +35,14 @@ const Card = struct {
|
|||
toughness: ?[]const u8 = null, //coerced to string
|
||||
card_faces: ?[]Card = null, //array of cards
|
||||
loyalty: ?[]const u8 = null, //coerced to string
|
||||
isFace: bool = undefined, //cheeky little property that I added
|
||||
object: []const u8 = "", //string
|
||||
allText: [][]const u8 = undefined//
|
||||
};
|
||||
|
||||
const TextCard = struct {
|
||||
lines: [][]const u8 = undefined,
|
||||
};
|
||||
|
||||
const CardSortContext = struct {
|
||||
const Self = @This();
|
||||
list: []TextCard,
|
||||
pub fn lessThan(ctx: Self, a: usize, b: usize) bool {
|
||||
return ctx.list[a].lines.len < ctx.list[b].lines.len;
|
||||
}
|
||||
};
|
||||
fn compareCards(ctx: void, lhs: Card, rhs: Card) bool {
|
||||
_ = ctx;
|
||||
return lhs.allText.len < rhs.allText.len;
|
||||
}
|
||||
|
||||
//dimension constants and defaults
|
||||
const cardWidth = 30;
|
||||
|
@ -104,43 +95,47 @@ pub fn main() !void {
|
|||
constantHeight = std.mem.eql(u8, choice, "true");
|
||||
}
|
||||
|
||||
// var timer = try std.time.Timer.start();
|
||||
const oracleFileSize = (try cwd.statFile(oracleFileName)).size;
|
||||
const oracleFile = try cwd.readFileAlloc(allocator, oracleFileName, oracleFileSize);
|
||||
const parsedJson = try json.parseFromSlice([]Card, allocator, oracleFile, .{ .ignore_unknown_fields = true });
|
||||
|
||||
var cardNames = std.BufSet.init(allocator);
|
||||
var cards = std.ArrayList(Card).init(allocator);
|
||||
const listFileSize = (try cwd.statFile(listFileName)).size;
|
||||
const listText = try cwd.readFileAlloc(allocator, listFileName, listFileSize);
|
||||
var listLines = std.mem.splitAny(u8, listText, "\n");
|
||||
while (listLines.next()) |line| {
|
||||
if (line.len < 5) break;
|
||||
const cardName = line[indexOf(u8, line, " ").? + 1 .. indexOf(u8, line, "(").? - 1];
|
||||
try cardNames.insert(cardName);
|
||||
const firstSpace = indexOf(u8, line, " ").?;
|
||||
const cardName = line[firstSpace + 1 .. indexOf(u8, line, "(").? - 1];
|
||||
const countString = line[0..firstSpace];
|
||||
const printCount = try std.fmt.parseInt(u8, countString, 10);
|
||||
for(0..printCount) |_| {
|
||||
try cards.append(Card{.name = cardName});
|
||||
}
|
||||
}
|
||||
|
||||
var cards = std.StringArrayHashMap(TextCard).init(allocator);
|
||||
for (parsedJson.value) |*cardObj| {
|
||||
if (cardNames.contains(cardObj.name)) {
|
||||
cardObj.isFace = false;
|
||||
const printableCard = try card(cardObj.*, allocator);
|
||||
try cards.put(cardObj.name, printableCard);
|
||||
for(cards.items) |*cardInfo| {
|
||||
if (std.mem.eql(u8, cardInfo.name, cardObj.name)) {
|
||||
cardInfo.allText = try card(cardObj.*, allocator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cards.sort(CardSortContext{ .list = cards.values() });
|
||||
std.mem.sort(Card, cards.items, {}, compareCards);
|
||||
|
||||
var allPrinted = std.ArrayList([]const u8).init(allocator);
|
||||
const pdf_doc: *c.pdf_doc = @ptrCast(c.pdf_create(pdfWidth, pdfHeight, &c.pdf_info{ .creator = ("My Software" ++ " " ** 53).* }).?);
|
||||
|
||||
var rowToPrint = std.ArrayList(TextCard).init(allocator);
|
||||
for (cards.values()) |cardText| {
|
||||
var rowToPrint = std.ArrayList(Card).init(allocator);
|
||||
for (cards.items) |cardText| {
|
||||
try rowToPrint.append(cardText);
|
||||
if (rowToPrint.items.len >= 3) {
|
||||
try cardRow.print(allocator, rowToPrint.items, &allPrinted);
|
||||
try printRow(allocator, rowToPrint.items[0..3].*, &allPrinted);
|
||||
rowToPrint.clearAndFree();
|
||||
}
|
||||
} else {
|
||||
try cardRow.print(allocator, rowToPrint.items, &allPrinted);
|
||||
try printRow(allocator, [3]Card{rowToPrint.items[0], Card{}, Card{}}, &allPrinted);
|
||||
rowToPrint.clearAndFree();
|
||||
}
|
||||
var page = c.pdf_append_page(pdf_doc);
|
||||
|
@ -161,7 +156,8 @@ pub fn main() !void {
|
|||
fn card(
|
||||
cardObj: Card,
|
||||
allocator: std.mem.Allocator,
|
||||
) !TextCard {
|
||||
) ![][]const u8 {
|
||||
const isFace= std.mem.eql(u8, cardObj.object, "card_face");
|
||||
var cardText = std.ArrayList([]const u8).init(allocator);
|
||||
|
||||
var fullUnformattedText = std.ArrayList(u8).init(allocator);
|
||||
|
@ -176,10 +172,7 @@ fn card(
|
|||
var finalOracleText: [30 * 100]u8 = undefined;
|
||||
_ = std.mem.replace(u8, oracleBuf.items, cardObj.name, "~", &finalOracleText);
|
||||
|
||||
// print("{s}\n", .{finalOracleText});
|
||||
|
||||
if (cardObj.card_faces == null or cardObj.isFace) {
|
||||
|
||||
if (cardObj.card_faces == null or isFace) {
|
||||
try fullUnformattedText.appendSlice(try std.mem.concat(allocator, u8, &[_][]const u8{
|
||||
cardObj.name,
|
||||
if (cardObj.mana_cost.len > 0) " " else "",
|
||||
|
@ -189,20 +182,17 @@ fn card(
|
|||
if(cardObj.power) |_| try std.fmt.allocPrint(allocator, "({s}/{s})", .{cardObj.power.?, cardObj.toughness.?}) else "",
|
||||
if(cardObj.loyalty) |_| try std.fmt.allocPrint(allocator, "[{s}]", .{cardObj.loyalty.?}) else ""
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
if (cardObj.card_faces) |faces| {
|
||||
for (faces, 0..) |*face, idx| {
|
||||
face.isFace = true;
|
||||
const faceText = (try card(face.*, allocator)).lines;
|
||||
const faceText = try card(face.*, allocator);
|
||||
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);
|
||||
//split on spaces, newlines and null
|
||||
var wordIterator = std.mem.splitAny(u8, fullUnformattedText.items, &.{'\n',' ',170});
|
||||
while (wordIterator.next()) |word| {
|
||||
if (line.items.len + word.len + 1 >= cardWidth) {
|
||||
|
@ -212,57 +202,49 @@ fn card(
|
|||
if(word.len > 0) {
|
||||
try line.appendSlice(word);
|
||||
try line.append(' ');
|
||||
// print("{any}\n", .{word});
|
||||
// print("{s}\n", .{line.items});
|
||||
assert(line.items.len < 30);
|
||||
}
|
||||
} else {
|
||||
try cardText.append(try line.toOwnedSlice());
|
||||
}
|
||||
if(!cardObj.isFace and constantHeight) {
|
||||
if(!isFace and constantHeight) {
|
||||
assert(cardText.items.len <= cardHeight);
|
||||
try cardText.appendNTimes(" " ** (cardWidth - 2), cardHeight - cardText.items.len);
|
||||
} else if(!cardObj.isFace) {
|
||||
} else if(!isFace) {
|
||||
try cardText.append(" " ** (cardWidth - 2));
|
||||
}
|
||||
assert(cardText.items.len <= cardHeight);
|
||||
return TextCard{ .lines = try cardText.toOwnedSlice() };
|
||||
return try cardText.toOwnedSlice();
|
||||
}
|
||||
|
||||
const linesList = std.MultiArrayList(cardRow);
|
||||
|
||||
fn optionalAccess(slice: anytype, index: usize) []const u8 {
|
||||
if(slice.len > index) return slice[index];
|
||||
return "";
|
||||
}
|
||||
const cardRow = struct {
|
||||
fn print(allocator: std.mem.Allocator, cards: []TextCard, allPrinted: *std.ArrayList([]const u8)) !void {
|
||||
var lines = std.ArrayList([]const u8).init(allocator);
|
||||
|
||||
const endIndex = if(cards.len > 1) std.mem.max(u64, &.{
|
||||
cards[0].lines.len,
|
||||
cards[1].lines.len,
|
||||
cards[2].lines.len
|
||||
}) else cards[0].lines.len - 1;
|
||||
fn printRow(allocator: std.mem.Allocator, cards: [3]Card, allPrinted: *std.ArrayList([]const u8)) !void {
|
||||
var lines = std.ArrayList([]const u8).init(allocator);
|
||||
|
||||
for(0..endIndex) |idx| {
|
||||
try lines.append(
|
||||
if(cards.len > 1)
|
||||
try std.fmt.allocPrint(allocator, lineFormatter ** 3 ++ "\n", .{
|
||||
optionalAccess(cards[0].lines, idx),
|
||||
optionalAccess(cards[1].lines, idx),
|
||||
optionalAccess(cards[2].lines, idx)
|
||||
})
|
||||
else cards[0].lines[idx]);
|
||||
}
|
||||
const endIndex = std.mem.max(u64, &.{
|
||||
cards[0].allText.len,
|
||||
cards[1].allText.len,
|
||||
cards[2].allText.len
|
||||
});
|
||||
|
||||
const rowHeight = lines.items.len;
|
||||
while (pageHeight - (allPrinted.items.len % pageHeight) <= rowHeight) {
|
||||
assert(rowHeight <= pageHeight);
|
||||
try allPrinted.append(fullWidthSpacer);
|
||||
}
|
||||
for (lines.items) |line| {
|
||||
try allPrinted.append(line);
|
||||
}
|
||||
for(0..endIndex) |idx| {
|
||||
try lines.append(
|
||||
try std.fmt.allocPrint(allocator, lineFormatter ** 3 ++ "\n", .{
|
||||
optionalAccess(cards[0].allText, idx),
|
||||
optionalAccess(cards[1].allText, idx),
|
||||
optionalAccess(cards[2].allText, idx)
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const rowHeight = lines.items.len;
|
||||
while (pageHeight - (allPrinted.items.len % pageHeight) <= rowHeight) {
|
||||
assert(rowHeight <= pageHeight);
|
||||
try allPrinted.append(fullWidthSpacer);
|
||||
}
|
||||
try allPrinted.appendSlice(lines.items);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue