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: add some kind of "update" command to support pulling new oracle data
|
||||||
//TODO: implement oracleFileName as a cli arg
|
//TODO: implement oracleFileName as a cli arg
|
||||||
//TODO: add more robust tests
|
//TODO: add more robust tests
|
||||||
//TODO: support other list formats
|
//TODO: support other list formats
|
||||||
//TODO: support multiple copies of cards
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
// https://github.com/Hejsil/zig-clap
|
// https://github.com/Hejsil/zig-clap
|
||||||
|
@ -12,7 +10,6 @@ const clap = @import("clap");
|
||||||
// https://codeberg.org/dude_the_builder/zg
|
// https://codeberg.org/dude_the_builder/zg
|
||||||
const DisplayWidth = @import("DisplayWidth");
|
const DisplayWidth = @import("DisplayWidth");
|
||||||
|
|
||||||
|
|
||||||
// https://github.com/AndreRenaud/PDFGen
|
// https://github.com/AndreRenaud/PDFGen
|
||||||
// including the header file only works for a full build, "zig run" can't find it
|
// including the header file only works for a full build, "zig run" can't find it
|
||||||
const c = @cImport({
|
const c = @cImport({
|
||||||
|
@ -38,20 +35,14 @@ const Card = struct {
|
||||||
toughness: ?[]const u8 = null, //coerced to string
|
toughness: ?[]const u8 = null, //coerced to string
|
||||||
card_faces: ?[]Card = null, //array of cards
|
card_faces: ?[]Card = null, //array of cards
|
||||||
loyalty: ?[]const u8 = null, //coerced to string
|
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 {
|
fn compareCards(ctx: void, lhs: Card, rhs: Card) bool {
|
||||||
lines: [][]const u8 = undefined,
|
_ = ctx;
|
||||||
};
|
return lhs.allText.len < rhs.allText.len;
|
||||||
|
}
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//dimension constants and defaults
|
//dimension constants and defaults
|
||||||
const cardWidth = 30;
|
const cardWidth = 30;
|
||||||
|
@ -104,43 +95,47 @@ pub fn main() !void {
|
||||||
constantHeight = std.mem.eql(u8, choice, "true");
|
constantHeight = std.mem.eql(u8, choice, "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
// var timer = try std.time.Timer.start();
|
|
||||||
const oracleFileSize = (try cwd.statFile(oracleFileName)).size;
|
const oracleFileSize = (try cwd.statFile(oracleFileName)).size;
|
||||||
const oracleFile = try cwd.readFileAlloc(allocator, oracleFileName, oracleFileSize);
|
const oracleFile = try cwd.readFileAlloc(allocator, oracleFileName, oracleFileSize);
|
||||||
const parsedJson = try json.parseFromSlice([]Card, allocator, oracleFile, .{ .ignore_unknown_fields = true });
|
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 listFileSize = (try cwd.statFile(listFileName)).size;
|
||||||
const listText = try cwd.readFileAlloc(allocator, listFileName, listFileSize);
|
const listText = try cwd.readFileAlloc(allocator, listFileName, listFileSize);
|
||||||
var listLines = std.mem.splitAny(u8, listText, "\n");
|
var listLines = std.mem.splitAny(u8, listText, "\n");
|
||||||
while (listLines.next()) |line| {
|
while (listLines.next()) |line| {
|
||||||
if (line.len < 5) break;
|
if (line.len < 5) break;
|
||||||
const cardName = line[indexOf(u8, line, " ").? + 1 .. indexOf(u8, line, "(").? - 1];
|
const firstSpace = indexOf(u8, line, " ").?;
|
||||||
try cardNames.insert(cardName);
|
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| {
|
for (parsedJson.value) |*cardObj| {
|
||||||
if (cardNames.contains(cardObj.name)) {
|
for(cards.items) |*cardInfo| {
|
||||||
cardObj.isFace = false;
|
if (std.mem.eql(u8, cardInfo.name, cardObj.name)) {
|
||||||
const printableCard = try card(cardObj.*, allocator);
|
cardInfo.allText = try card(cardObj.*, allocator);
|
||||||
try cards.put(cardObj.name, printableCard);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cards.sort(CardSortContext{ .list = cards.values() });
|
std.mem.sort(Card, cards.items, {}, compareCards);
|
||||||
|
|
||||||
var allPrinted = std.ArrayList([]const u8).init(allocator);
|
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).* }).?);
|
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);
|
var rowToPrint = std.ArrayList(Card).init(allocator);
|
||||||
for (cards.values()) |cardText| {
|
for (cards.items) |cardText| {
|
||||||
try rowToPrint.append(cardText);
|
try rowToPrint.append(cardText);
|
||||||
if (rowToPrint.items.len >= 3) {
|
if (rowToPrint.items.len >= 3) {
|
||||||
try cardRow.print(allocator, rowToPrint.items, &allPrinted);
|
try printRow(allocator, rowToPrint.items[0..3].*, &allPrinted);
|
||||||
rowToPrint.clearAndFree();
|
rowToPrint.clearAndFree();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try cardRow.print(allocator, rowToPrint.items, &allPrinted);
|
try printRow(allocator, [3]Card{rowToPrint.items[0], Card{}, Card{}}, &allPrinted);
|
||||||
rowToPrint.clearAndFree();
|
rowToPrint.clearAndFree();
|
||||||
}
|
}
|
||||||
var page = c.pdf_append_page(pdf_doc);
|
var page = c.pdf_append_page(pdf_doc);
|
||||||
|
@ -161,7 +156,8 @@ pub fn main() !void {
|
||||||
fn card(
|
fn card(
|
||||||
cardObj: Card,
|
cardObj: Card,
|
||||||
allocator: std.mem.Allocator,
|
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 cardText = std.ArrayList([]const u8).init(allocator);
|
||||||
|
|
||||||
var fullUnformattedText = std.ArrayList(u8).init(allocator);
|
var fullUnformattedText = std.ArrayList(u8).init(allocator);
|
||||||
|
@ -176,10 +172,7 @@ fn card(
|
||||||
var finalOracleText: [30 * 100]u8 = undefined;
|
var finalOracleText: [30 * 100]u8 = undefined;
|
||||||
_ = std.mem.replace(u8, oracleBuf.items, cardObj.name, "~", &finalOracleText);
|
_ = std.mem.replace(u8, oracleBuf.items, cardObj.name, "~", &finalOracleText);
|
||||||
|
|
||||||
// print("{s}\n", .{finalOracleText});
|
if (cardObj.card_faces == null or isFace) {
|
||||||
|
|
||||||
if (cardObj.card_faces == null or cardObj.isFace) {
|
|
||||||
|
|
||||||
try fullUnformattedText.appendSlice(try std.mem.concat(allocator, u8, &[_][]const u8{
|
try fullUnformattedText.appendSlice(try std.mem.concat(allocator, u8, &[_][]const u8{
|
||||||
cardObj.name,
|
cardObj.name,
|
||||||
if (cardObj.mana_cost.len > 0) " " else "",
|
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.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.loyalty) |_| try std.fmt.allocPrint(allocator, "[{s}]", .{cardObj.loyalty.?}) else ""
|
||||||
}));
|
}));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cardObj.card_faces) |faces| {
|
if (cardObj.card_faces) |faces| {
|
||||||
for (faces, 0..) |*face, idx| {
|
for (faces, 0..) |*face, idx| {
|
||||||
face.isFace = true;
|
const faceText = try card(face.*, allocator);
|
||||||
const faceText = (try card(face.*, allocator)).lines;
|
|
||||||
try fullUnformattedText.appendSlice(std.mem.trim(u8, try std.mem.join(allocator, " ", faceText), "\n"));
|
try fullUnformattedText.appendSlice(std.mem.trim(u8, try std.mem.join(allocator, " ", faceText), "\n"));
|
||||||
if (idx == 0) try fullUnformattedText.appendSlice("// ");
|
if (idx == 0) try fullUnformattedText.appendSlice("// ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var line = std.ArrayList(u8).init(allocator);
|
var line = std.ArrayList(u8).init(allocator);
|
||||||
//split on spaces, newlines and null
|
|
||||||
var wordIterator = std.mem.splitAny(u8, fullUnformattedText.items, &.{'\n',' ',170});
|
var wordIterator = std.mem.splitAny(u8, fullUnformattedText.items, &.{'\n',' ',170});
|
||||||
while (wordIterator.next()) |word| {
|
while (wordIterator.next()) |word| {
|
||||||
if (line.items.len + word.len + 1 >= cardWidth) {
|
if (line.items.len + word.len + 1 >= cardWidth) {
|
||||||
|
@ -212,57 +202,49 @@ fn card(
|
||||||
if(word.len > 0) {
|
if(word.len > 0) {
|
||||||
try line.appendSlice(word);
|
try line.appendSlice(word);
|
||||||
try line.append(' ');
|
try line.append(' ');
|
||||||
// print("{any}\n", .{word});
|
|
||||||
// print("{s}\n", .{line.items});
|
|
||||||
assert(line.items.len < 30);
|
assert(line.items.len < 30);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try cardText.append(try line.toOwnedSlice());
|
try cardText.append(try line.toOwnedSlice());
|
||||||
}
|
}
|
||||||
if(!cardObj.isFace and constantHeight) {
|
if(!isFace and constantHeight) {
|
||||||
assert(cardText.items.len <= cardHeight);
|
assert(cardText.items.len <= cardHeight);
|
||||||
try cardText.appendNTimes(" " ** (cardWidth - 2), cardHeight - cardText.items.len);
|
try cardText.appendNTimes(" " ** (cardWidth - 2), cardHeight - cardText.items.len);
|
||||||
} else if(!cardObj.isFace) {
|
} else if(!isFace) {
|
||||||
try cardText.append(" " ** (cardWidth - 2));
|
try cardText.append(" " ** (cardWidth - 2));
|
||||||
}
|
}
|
||||||
assert(cardText.items.len <= cardHeight);
|
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 {
|
fn optionalAccess(slice: anytype, index: usize) []const u8 {
|
||||||
if(slice.len > index) return slice[index];
|
if(slice.len > index) return slice[index];
|
||||||
return "";
|
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, &.{
|
fn printRow(allocator: std.mem.Allocator, cards: [3]Card, allPrinted: *std.ArrayList([]const u8)) !void {
|
||||||
cards[0].lines.len,
|
var lines = std.ArrayList([]const u8).init(allocator);
|
||||||
cards[1].lines.len,
|
|
||||||
cards[2].lines.len
|
|
||||||
}) else cards[0].lines.len - 1;
|
|
||||||
|
|
||||||
for(0..endIndex) |idx| {
|
const endIndex = std.mem.max(u64, &.{
|
||||||
try lines.append(
|
cards[0].allText.len,
|
||||||
if(cards.len > 1)
|
cards[1].allText.len,
|
||||||
try std.fmt.allocPrint(allocator, lineFormatter ** 3 ++ "\n", .{
|
cards[2].allText.len
|
||||||
optionalAccess(cards[0].lines, idx),
|
});
|
||||||
optionalAccess(cards[1].lines, idx),
|
|
||||||
optionalAccess(cards[2].lines, idx)
|
|
||||||
})
|
|
||||||
else cards[0].lines[idx]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rowHeight = lines.items.len;
|
for(0..endIndex) |idx| {
|
||||||
while (pageHeight - (allPrinted.items.len % pageHeight) <= rowHeight) {
|
try lines.append(
|
||||||
assert(rowHeight <= pageHeight);
|
try std.fmt.allocPrint(allocator, lineFormatter ** 3 ++ "\n", .{
|
||||||
try allPrinted.append(fullWidthSpacer);
|
optionalAccess(cards[0].allText, idx),
|
||||||
}
|
optionalAccess(cards[1].allText, idx),
|
||||||
for (lines.items) |line| {
|
optionalAccess(cards[2].allText, idx)
|
||||||
try allPrinted.append(line);
|
})
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
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