stuff is mostly working again, trying to switch to using a C PDF library

This commit is contained in:
Lumen Keyes 2024-07-06 18:11:00 -06:00
parent b9945e8097
commit adec84668e
11 changed files with 5593 additions and 482 deletions

View File

@ -0,0 +1,2 @@
pub const packages = struct {};
pub const root_deps: []const struct { []const u8, []const u8 } = &.{};

1
PDFGen Submodule

@ -0,0 +1 @@
Subproject commit 53ea781d446d51ce70492a796c2523286490633f

View File

@ -3,6 +3,8 @@ const std = @import("std");
// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
//
const CFlags = &.{};
pub fn build(b: *std.Build) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
@ -36,6 +38,17 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
});
exe.addCSourceFile(.{
.file = .{
.path = "src/pdfgen.c",
},
.flags = CFlags,
});
exe.addIncludePath(.{
.path = "include/pdfgen.h",
});
// This declares intent for the executable to be installed into the
// standard location when the user invokes the "install" step (the default
// step when running `zig build`).
@ -46,9 +59,9 @@ pub fn build(b: *std.Build) void {
// such a dependency.
const run_cmd = b.addRunArtifact(exe);
const msgpack = b.dependency("zig-msgpack", .{
.target = target,
.optimize = optimize,
});
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("msgpack", msgpack.module("msgpack"));

View File

@ -15,10 +15,10 @@
// Once all dependencies are fetched, `zig build` no longer requires
// internet connectivity.
.dependencies = .{
.@"zig-msgpack" = .{
.url = "https://github.com/zigcc/zig-msgpack/archive/main.tar.gz",
.hash = "1220e4669d29190ac809cd3a7726c20b6b49ea7425b7b89cab16d4dc3172016982bc",
},
//.@"zig-msgpack" = .{
// .url = "https://github.com/zigcc/zig-msgpack/archive/main.tar.gz",
// .hash = "1220e4669d29190ac809cd3a7726c20b6b49ea7425b7b89cab16d4dc3172016982bc",
//},
//.clap = .{
// .url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.8.0.tar.gz",
// .hash = "1220949d4e88864579067b6d4cdad6476c6176f27e782782c2c39b7f2c4817a10efb",

800
include/pdfgen.h Normal file
View File

@ -0,0 +1,800 @@
/**
* Simple engine for creating PDF files.
* It supports text, shapes, images etc...
* Capable of handling millions of objects without too much performance
* penalty.
* Public domain license - no warrenty implied; use at your own risk.
* @file pdfgen.h
*/
#ifndef PDFGEN_H
#define PDFGEN_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdio.h>
/**
* @defgroup subsystem Simple PDF Generation
* Allows for quick generation of simple PDF documents.
* This is useful for producing easily printed output from C code, where
* advanced formatting is not required
*
* Note: All coordinates/sizes are in points (1/72 of an inch).
* All coordinates are based on 0,0 being the bottom left of the page.
* All colours are specified as a packed 32-bit value - see @ref PDF_RGB.
* Text strings are interpreted as UTF-8 encoded, but only a small subset of
* characters beyond 7-bit ascii are supported (see @ref pdf_add_text for
* details).
*
* @par PDF library example:
* @code
#include "pdfgen.h"
...
struct pdf_info info = {
.creator = "My software",
.producer = "My software",
.title = "My document",
.author = "My name",
.subject = "My subject",
.date = "Today"
};
struct pdf_doc *pdf = pdf_create(PDF_A4_WIDTH, PDF_A4_HEIGHT, &info);
pdf_set_font(pdf, "Times-Roman");
pdf_append_page(pdf);
pdf_add_text(pdf, NULL, "This is text", 12, 50, 20, PDF_BLACK);
pdf_add_line(pdf, NULL, 50, 24, 150, 24);
pdf_save(pdf, "output.pdf");
pdf_destroy(pdf);
* @endcode
*/
struct pdf_doc;
struct pdf_object;
/**
* pdf_info describes the metadata to be inserted into the
* header of the output PDF
*/
struct pdf_info {
char creator[64]; //!< Software used to create the PDF
char producer[64]; //!< Software used to create the PDF
char title[64]; //!< The title of the PDF (typically displayed in the
//!< window bar when viewing)
char author[64]; //!< Who created the PDF
char subject[64]; //!< What is the PDF about
char date[64]; //!< The date the PDF was created
};
/**
* Enum that declares the different image file formats we currently support.
* Each value has a corresponding header struct used within
* the format_specific_img_info union.
*/
enum {
IMAGE_PNG,
IMAGE_JPG,
IMAGE_PPM,
IMAGE_BMP,
IMAGE_UNKNOWN
};
/**
* Since we're casting random areas of memory to these, make sure
* they're packed properly to match the image format requirements
*/
#pragma pack(push, 1)
/**
* Information about color type of PNG format
* As defined by https://www.w3.org/TR/2003/REC-PNG-20031110/#6Colour-values
*/
enum /* png colortype */ {
// Greyscale
PNG_COLOR_GREYSCALE = 0,
// Truecolour
PNG_COLOR_RGB = 2,
// Indexed-colour
PNG_COLOR_INDEXED = 3,
// Greyscale with alpha
PNG_COLOR_GREYSCALE_A = 4,
// Truecolour with alpha
PNG_COLOR_RGBA = 6,
PNG_COLOR_INVALID = 255
};
/**
* png_header describes the header information extracted from .PNG files
*/
struct png_header {
uint32_t width; //!< Width in pixels
uint32_t height; //!< Height in pixels
uint8_t bitDepth; //!< Bit Depth
uint8_t colorType; //!< Color type - see PNG_COLOR_xx
uint8_t deflate; //!< Deflate setting
uint8_t filtering; //!< Filtering
uint8_t interlace; //!< Interlacing
};
/**
* bmp_header describes the header information extracted from .BMP files
*/
struct bmp_header {
uint32_t bfSize; //!< size of BMP in bytes
uint16_t bfReserved1; //!< ignore!
uint16_t bfReserved2; //!< ignore!
uint32_t bfOffBits; //!< Offset to BMP data
uint32_t biSize; //!< Size of this header (40)
int32_t biWidth; //!< Width in pixels
int32_t biHeight; //!< Height in pixels
uint16_t biPlanes; //!< Number of colour planes - must be 1
uint16_t biBitCount; //!< Bits Per Pixel
uint32_t biCompression; //!< Compression Method
};
#pragma pack(pop)
/**
* jpeg_header describes the header information extracted from .JPG files
*/
struct jpeg_header {
int ncolours; //!< Number of colours
};
/**
* PPM color spaces
*/
enum {
PPM_BINARY_COLOR_RGB, //!< binary ppm with RGB colors (magic number P5)
PPM_BINARY_COLOR_GRAY, //!< binary ppm with grayscale colors (magic number
//!< P6)
};
/**
* ppm_header describes the header information extracted from .PPM files
*/
struct ppm_header {
size_t size; //!< Indicate the size of the image data
size_t data_begin_pos; //!< position in the data where the image starts
int color_space; //!< PPM color space
};
/**
* pdf_img_info describes the metadata for an arbitrary image
*/
struct pdf_img_info {
int image_format; //!< Indicates the image format (IMAGE_PNG, ...)
uint32_t width; //!< Width in pixels
uint32_t height; //!< Height in pixels
#ifndef DOXYGEN_SHOULD_SKIP_THIS
// Doxygen doesn't like anonymous unions
//!< Image specific details
union {
struct bmp_header bmp; //!< BMP header info
struct jpeg_header jpeg; //!< JPEG header info
struct png_header png; //!< PNG header info
struct ppm_header ppm; //!< PPM header info
};
#endif
};
/**
* pdf_path_operation holds information about a path
* drawing operation.
* See PDF reference for detailed usage.
*/
struct pdf_path_operation {
char op; /*!< Operation command. Possible operators are: m = move to, l =
line to, c = cubic bezier curve with two control points, v =
cubic bezier curve with one control point fixed at first
point, y = cubic bezier curve with one control point fixed
at second point, h = close path */
float x1; /*!< X offset of the first point. Used with: m, l, c, v, y */
float y1; /*!< Y offset of the first point. Used with: m, l, c, v, y */
float x2; /*!< X offset of the second point. Used with: c, v, y */
float y2; /*!< Y offset of the second point. Used with: c, v, y */
float x3; /*!< X offset of the third point. Used with: c */
float y3; /*!< Y offset of the third point. Used with: c */
};
/**
* Convert a value in inches into a number of points.
* @param inch inches value to convert to points
*/
#define PDF_INCH_TO_POINT(inch) ((float)((inch)*72.0f))
/**
* Convert a value in milli-meters into a number of points.
* @param mm millimeter value to convert to points
*/
#define PDF_MM_TO_POINT(mm) ((float)((mm)*72.0f / 25.4f))
/*! Point width of a standard US-Letter page */
#define PDF_LETTER_WIDTH PDF_INCH_TO_POINT(8.5f)
/*! Point height of a standard US-Letter page */
#define PDF_LETTER_HEIGHT PDF_INCH_TO_POINT(11.0f)
/*! Point width of a standard A4 page */
#define PDF_A4_WIDTH PDF_MM_TO_POINT(210.0f)
/*! Point height of a standard A4 page */
#define PDF_A4_HEIGHT PDF_MM_TO_POINT(297.0f)
/*! Point width of a standard A3 page */
#define PDF_A3_WIDTH PDF_MM_TO_POINT(297.0f)
/*! Point height of a standard A3 page */
#define PDF_A3_HEIGHT PDF_MM_TO_POINT(420.0f)
/**
* Convert three 8-bit RGB values into a single packed 32-bit
* colour. These 32-bit colours are used by various functions
* in PDFGen
*/
#define PDF_RGB(r, g, b) \
(uint32_t)((((r)&0xff) << 16) | (((g)&0xff) << 8) | (((b)&0xff)))
/**
* Convert four 8-bit ARGB values into a single packed 32-bit
* colour. These 32-bit colours are used by various functions
* in PDFGen. Alpha values range from 0 (opaque) to 0xff
* (transparent)
*/
#define PDF_ARGB(a, r, g, b) \
(uint32_t)(((uint32_t)((a)&0xff) << 24) | (((r)&0xff) << 16) | \
(((g)&0xff) << 8) | (((b)&0xff)))
/*! Utility macro to provide bright red */
#define PDF_RED PDF_RGB(0xff, 0, 0)
/*! Utility macro to provide bright green */
#define PDF_GREEN PDF_RGB(0, 0xff, 0)
/*! Utility macro to provide bright blue */
#define PDF_BLUE PDF_RGB(0, 0, 0xff)
/*! Utility macro to provide black */
#define PDF_BLACK PDF_RGB(0, 0, 0)
/*! Utility macro to provide white */
#define PDF_WHITE PDF_RGB(0xff, 0xff, 0xff)
/*!
* Utility macro to provide a transparent colour
* This is used in some places for 'fill' colours, where no fill is required
*/
#define PDF_TRANSPARENT (uint32_t)(0xffu << 24)
/**
* Different alignment options for rendering text
*/
enum {
PDF_ALIGN_LEFT, //!< Align text to the left
PDF_ALIGN_RIGHT, //!< Align text to the right
PDF_ALIGN_CENTER, //!< Align text in the center
PDF_ALIGN_JUSTIFY, //!< Align text in the center, with padding to fill the
//!< available space
PDF_ALIGN_JUSTIFY_ALL, //!< Like PDF_ALIGN_JUSTIFY, except even short
//!< lines will be fully justified
PDF_ALIGN_NO_WRITE, //!< Fake alignment for only checking wrap height with
//!< no writes
};
/**
* Create a new PDF object, with the given page
* width/height
* @param width Width of the page
* @param height Height of the page
* @param info Optional information to be put into the PDF header
* @return PDF document object, or NULL on failure
*/
struct pdf_doc *pdf_create(float width, float height,
const struct pdf_info *info);
/**
* Destroy the pdf object, and all of its associated memory
* @param pdf PDF document to clean up
*/
void pdf_destroy(struct pdf_doc *pdf);
/**
* Retrieve the error message if any operation fails
* @param pdf pdf document to retrieve error message from
* @param errval optional pointer to an integer to be set to the error code
* @return NULL if no error message, string description of error otherwise
*/
const char *pdf_get_err(const struct pdf_doc *pdf, int *errval);
/**
* Acknowledge an outstanding pdf error
* @param pdf pdf document to clear the error message from
*/
void pdf_clear_err(struct pdf_doc *pdf);
/**
* Sets the font to use for text objects. Default value is Times-Roman if
* this function is not called.
* Note: The font selection should be done before text is output,
* and will remain until pdf_set_font is called again.
* @param pdf PDF document to update font on
* @param font New font to use. This must be one of the standard PDF fonts:
* Courier, Courier-Bold, Courier-BoldOblique, Courier-Oblique,
* Helvetica, Helvetica-Bold, Helvetica-BoldOblique, Helvetica-Oblique,
* Times-Roman, Times-Bold, Times-Italic, Times-BoldItalic,
* Symbol or ZapfDingbats
* @return < 0 on failure, 0 on success
*/
int pdf_set_font(struct pdf_doc *pdf, const char *font);
/**
* Calculate the width of a given string in the current font
* @param pdf PDF document
* @param font_name Name of the font to get the width of.
* This must be one of the standard PDF fonts:
* Courier, Courier-Bold, Courier-BoldOblique, Courier-Oblique,
* Helvetica, Helvetica-Bold, Helvetica-BoldOblique, Helvetica-Oblique,
* Times-Roman, Times-Bold, Times-Italic, Times-BoldItalic,
* Symbol or ZapfDingbats
* @param text Text to determine width of
* @param size Size of the text, in points
* @param text_width area to store calculated width in
* @return < 0 on failure, 0 on success
*/
int pdf_get_font_text_width(struct pdf_doc *pdf, const char *font_name,
const char *text, float size, float *text_width);
/**
* Retrieves a PDF document height
* @param pdf PDF document to get height of
* @return height of PDF document (in points)
*/
float pdf_height(const struct pdf_doc *pdf);
/**
* Retrieves a PDF document width
* @param pdf PDF document to get width of
* @return width of PDF document (in points)
*/
float pdf_width(const struct pdf_doc *pdf);
/**
* Retrieves page height
* @param page Page object to get height of
* @return height of page (in points)
*/
float pdf_page_height(const struct pdf_object *page);
/**
* Retrieves page width
* @param page Page object to get width of
* @return width of page (in points)
*/
float pdf_page_width(const struct pdf_object *page);
/**
* Add a new page to the given pdf
* @param pdf PDF document to append page to
* @return new page object
*/
struct pdf_object *pdf_append_page(struct pdf_doc *pdf);
/**
* Retrieve a page by its number.
*
* Note: The page must have already been created via \ref pdf_append_page
*
* @param pdf PDF document to get page from
* @param page_number Page number to retrieve, starting from 1.
* @return Page object if the given page is found, NULL otherwise
*/
struct pdf_object *pdf_get_page(struct pdf_doc *pdf, int page_number);
/**
* Adjust the width/height of a specific page
* @param pdf PDF document that the page belongs to
* @param page object returned from @ref pdf_append_page
* @param width Width of the page in points
* @param height Height of the page in points
* @return < 0 on failure, 0 on success
*/
int pdf_page_set_size(struct pdf_doc *pdf, struct pdf_object *page,
float width, float height);
/**
* Save the given pdf document to the supplied filename.
* @param pdf PDF document to save
* @param filename Name of the file to store the PDF into (NULL for stdout)
* @return < 0 on failure, >= 0 on success
*/
int pdf_save(struct pdf_doc *pdf, const char *filename);
/**
* Save the given pdf document to the given FILE output
* @param pdf PDF document to save
* @param fp FILE pointer to store the data into (must be writable)
* @return < 0 on failure, >= 0 on success
*/
int pdf_save_file(struct pdf_doc *pdf, FILE *fp);
/**
* Add a text string to the document
* @param pdf PDF document to add to
* @param page Page to add object to (NULL => most recently added page)
* @param text String to display
* @param size Point size of the font
* @param xoff X location to put it in
* @param yoff Y location to put it in
* @param colour Colour to draw the text
* @return 0 on success, < 0 on failure
*/
int pdf_add_text(struct pdf_doc *pdf, struct pdf_object *page,
const char *text, float size, float xoff, float yoff,
uint32_t colour);
/**
* Add a text string to the document at a rotated angle
* @param pdf PDF document to add to
* @param page Page to add object to (NULL => most recently added page)
* @param text String to display
* @param size Point size of the font
* @param xoff X location to put it in
* @param yoff Y location to put it in
* @param angle Rotation angle of text (in radians)
* @param colour Colour to draw the text
* @return 0 on success, < 0 on failure
*/
int pdf_add_text_rotate(struct pdf_doc *pdf, struct pdf_object *page,
const char *text, float size, float xoff, float yoff,
float angle, uint32_t colour);
/**
* Add a text string to the document, making it wrap if it is too
* long
* @param pdf PDF document to add to
* @param page Page to add object to (NULL => most recently added page)
* @param text String to display
* @param size Point size of the font
* @param xoff X location to put it in
* @param yoff Y location to put it in
* @param angle Rotation angle of text (in radians)
* @param colour Colour to draw the text
* @param wrap_width Width at which to wrap the text
* @param align Text alignment (see PDF_ALIGN_xxx)
* @param height Store the final height of the wrapped text here (optional)
* @return < 0 on failure, >= 0 on success
*/
int pdf_add_text_wrap(struct pdf_doc *pdf, struct pdf_object *page,
const char *text, float size, float xoff, float yoff,
float angle, uint32_t colour, float wrap_width,
int align, float *height);
/**
* Add a line to the document
* @param pdf PDF document to add to
* @param page Page to add object to (NULL => most recently added page)
* @param x1 X offset of start of line
* @param y1 Y offset of start of line
* @param x2 X offset of end of line
* @param y2 Y offset of end of line
* @param width Width of the line
* @param colour Colour to draw the line
* @return 0 on success, < 0 on failure
*/
int pdf_add_line(struct pdf_doc *pdf, struct pdf_object *page, float x1,
float y1, float x2, float y2, float width, uint32_t colour);
/**
* Add a cubic bezier curve to the document
* @param pdf PDF document to add to
* @param page Page to add object to (NULL => most recently added page)
* @param x1 X offset of the initial point of the curve
* @param y1 Y offset of the initial point of the curve
* @param x2 X offset of the final point of the curve
* @param y2 Y offset of the final point of the curve
* @param xq1 X offset of the first control point of the curve
* @param yq1 Y offset of the first control point of the curve
* @param xq2 X offset of the second control of the curve
* @param yq2 Y offset of the second control of the curve
* @param width Width of the curve
* @param colour Colour to draw the curve
* @return 0 on success, < 0 on failure
*/
int pdf_add_cubic_bezier(struct pdf_doc *pdf, struct pdf_object *page,
float x1, float y1, float x2, float y2, float xq1,
float yq1, float xq2, float yq2, float width,
uint32_t colour);
/**
* Add a quadratic bezier curve to the document
* @param pdf PDF document to add to
* @param page Page to add object to (NULL => most recently added page)
* @param x1 X offset of the initial point of the curve
* @param y1 Y offset of the initial point of the curve
* @param x2 X offset of the final point of the curve
* @param y2 Y offset of the final point of the curve
* @param xq1 X offset of the control point of the curve
* @param yq1 Y offset of the control point of the curve
* @param width Width of the curve
* @param colour Colour to draw the curve
* @return 0 on success, < 0 on failure
*/
int pdf_add_quadratic_bezier(struct pdf_doc *pdf, struct pdf_object *page,
float x1, float y1, float x2, float y2,
float xq1, float yq1, float width,
uint32_t colour);
/**
* Add a custom path to the document
* @param pdf PDF document to add to
* @param page Page to add object to (NULL => most recently added page)
* @param operations Array of drawing operations
* @param operation_count The number of operations
* @param stroke_width Width of the stroke
* @param stroke_colour Colour to stroke the curve
* @param fill_colour Colour to fill the path
* @return 0 on success, < 0 on failure
*/
int pdf_add_custom_path(struct pdf_doc *pdf, struct pdf_object *page,
const struct pdf_path_operation *operations,
int operation_count, float stroke_width,
uint32_t stroke_colour, uint32_t fill_colour);
/**
* Add an ellipse to the document
* @param pdf PDF document to add to
* @param page Page to add object to (NULL => most recently added page)
* @param x X offset of the center of the ellipse
* @param y Y offset of the center of the ellipse
* @param xradius Radius of the ellipse in the X axis
* @param yradius Radius of the ellipse in the Y axis
* @param width Width of the ellipse outline stroke
* @param colour Colour to draw the ellipse outline stroke
* @param fill_colour Colour to fill the ellipse
* @return 0 on success, < 0 on failure
*/
int pdf_add_ellipse(struct pdf_doc *pdf, struct pdf_object *page, float x,
float y, float xradius, float yradius, float width,
uint32_t colour, uint32_t fill_colour);
/**
* Add a circle to the document
* @param pdf PDF document to add to
* @param page Page to add object to (NULL => most recently added page)
* @param x X offset of the center of the circle
* @param y Y offset of the center of the circle
* @param radius Radius of the circle
* @param width Width of the circle outline stroke
* @param colour Colour to draw the circle outline stroke
* @param fill_colour Colour to fill the circle
* @return 0 on success, < 0 on failure
*/
int pdf_add_circle(struct pdf_doc *pdf, struct pdf_object *page, float x,
float y, float radius, float width, uint32_t colour,
uint32_t fill_colour);
/**
* Add an outline rectangle to the document
* @param pdf PDF document to add to
* @param page Page to add object to (NULL => most recently added page)
* @param x X offset to start rectangle at
* @param y Y offset to start rectangle at
* @param width Width of rectangle
* @param height Height of rectangle
* @param border_width Width of rectangle border
* @param colour Colour to draw the rectangle
* @return 0 on success, < 0 on failure
*/
int pdf_add_rectangle(struct pdf_doc *pdf, struct pdf_object *page, float x,
float y, float width, float height, float border_width,
uint32_t colour);
/**
* Add a filled rectangle to the document
* @param pdf PDF document to add to
* @param page Page to add object to (NULL => most recently added page)
* @param x X offset to start rectangle at
* @param y Y offset to start rectangle at
* @param width Width of rectangle
* @param height Height of rectangle
* @param border_width Width of rectangle border
* @param colour_fill Colour to fill the rectangle
* @param colour_border Colour to draw the rectangle
* @return 0 on success, < 0 on failure
*/
int pdf_add_filled_rectangle(struct pdf_doc *pdf, struct pdf_object *page,
float x, float y, float width, float height,
float border_width, uint32_t colour_fill,
uint32_t colour_border);
/**
* Add an outline polygon to the document
* @param pdf PDF document to add to
* @param page Page to add object to (NULL => most recently added page)
* @param x array of X offsets for points comprising the polygon
* @param y array of Y offsets for points comprising the polygon
* @param count Number of points comprising the polygon
* @param border_width Width of polygon border
* @param colour Colour to draw the polygon
* @return 0 on success, < 0 on failure
*/
int pdf_add_polygon(struct pdf_doc *pdf, struct pdf_object *page, float x[],
float y[], int count, float border_width,
uint32_t colour);
/**
* Add a filled polygon to the document
* @param pdf PDF document to add to
* @param page Page to add object to (NULL => most recently added page)
* @param x array of X offsets of points comprising the polygon
* @param y array of Y offsets of points comprising the polygon
* @param count Number of points comprising the polygon
* @param border_width Width of polygon border
* @param colour Colour to draw the polygon
* @return 0 on success, < 0 on failure
*/
int pdf_add_filled_polygon(struct pdf_doc *pdf, struct pdf_object *page,
float x[], float y[], int count,
float border_width, uint32_t colour);
/**
* Add a bookmark to the document
* @param pdf PDF document to add bookmark to
* @param page Page to jump to for bookmark
(or NULL for the most recently added page)
* @param parent ID of a previously created bookmark that is the parent
of this one. -1 if this should be a top-level bookmark.
* @param name String to associate with the bookmark
* @return < 0 on failure, new bookmark id on success
*/
int pdf_add_bookmark(struct pdf_doc *pdf, struct pdf_object *page, int parent,
const char *name);
/**
* Add a link annotation to the document
* @param pdf PDF document to add link to
* @param page Page that holds the clickable rectangle
(or NULL for the most recently added page)
* @param x X coordinate of bottom LHS corner of clickable rectangle
* @param y Y coordinate of bottom LHS corner of clickable rectangle
* @param width width of clickable rectangle
* @param height height of clickable rectangle
* @param target_page Page to jump to for link
* @param target_x X coordinate to position at the left of the view
* @param target_y Y coordinate to position at the top of the view
* @return < 0 on failure, new bookmark id on success
*/
int pdf_add_link(struct pdf_doc *pdf, struct pdf_object *page, float x,
float y, float width, float height,
struct pdf_object *target_page, float target_x,
float target_y);
/**
* List of different barcode encodings that are supported
*/
enum {
PDF_BARCODE_128A, //!< Produce code-128A style barcodes
PDF_BARCODE_39, //!< Produce code-39 style barcodes
PDF_BARCODE_EAN13, //!< Produce EAN-13 style barcodes
PDF_BARCODE_UPCA, //!< Produce UPC-A style barcodes
PDF_BARCODE_EAN8, //!< Produce EAN-8 style barcodes
PDF_BARCODE_UPCE, //!< Produce UPC-E style barcodes
};
/**
* Add a barcode to the document
* @param pdf PDF document to add barcode to
* @param page Page to add barcode to (NULL => most recently added page)
* @param code Type of barcode to add (PDF_BARCODE_xxx)
* @param x X offset to put barcode at
* @param y Y offset to put barcode at
* @param width Width of barcode
* @param height Height of barcode
* @param string Barcode contents
* @param colour Colour to draw barcode
* @return < 0 on failure, >= 0 on success
*/
int pdf_add_barcode(struct pdf_doc *pdf, struct pdf_object *page, int code,
float x, float y, float width, float height,
const char *string, uint32_t colour);
/**
* Add image data as an image to the document.
* Image data must be one of: JPEG, PNG, PPM, PGM or BMP formats
* Passing 0 for either the display width or height will
* include the image but not render it visible.
* Passing a negative number either the display height or width will
* have the image be resized while keeping the original aspect ratio.
* @param pdf PDF document to add image to
* @param page Page to add image to (NULL => most recently added page)
* @param x X offset to put image at
* @param y Y offset to put image at
* @param display_width Displayed width of image
* @param display_height Displayed height of image
* @param data Image data bytes
* @param len Length of data
* @return < 0 on failure, >= 0 on success
*/
int pdf_add_image_data(struct pdf_doc *pdf, struct pdf_object *page, float x,
float y, float display_width, float display_height,
const uint8_t *data, size_t len);
/**
* Add a raw 24 bit per pixel RGB buffer as an image to the document
* Passing 0 for either the display width or height will
* include the image but not render it visible.
* Passing a negative number either the display height or width will
* have the image be resized while keeping the original aspect ratio.
* @param pdf PDF document to add image to
* @param page Page to add image to (NULL => most recently added page)
* @param x X offset to put image at
* @param y Y offset to put image at
* @param display_width Displayed width of image
* @param display_height Displayed height of image
* @param data RGB data to add
* @param width width of image in pixels
* @param height height of image in pixels
* @return < 0 on failure, >= 0 on success
*/
int pdf_add_rgb24(struct pdf_doc *pdf, struct pdf_object *page, float x,
float y, float display_width, float display_height,
const uint8_t *data, uint32_t width, uint32_t height);
/**
* Add a raw 8 bit per pixel grayscale buffer as an image to the document
* @param pdf PDF document to add image to
* @param page Page to add image to (NULL => most recently added page)
* @param x X offset to put image at
* @param y Y offset to put image at
* @param display_width Displayed width of image
* @param display_height Displayed height of image
* @param data grayscale pixel data to add
* @param width width of image in pixels
* @param height height of image in pixels
* @return < 0 on failure, >= 0 on success
*/
int pdf_add_grayscale8(struct pdf_doc *pdf, struct pdf_object *page, float x,
float y, float display_width, float display_height,
const uint8_t *data, uint32_t width, uint32_t height);
/**
* Add an image file as an image to the document.
* Passing 0 for either the display width or height will
* include the image but not render it visible.
* Passing a negative number either the display height or width will
* have the image be resized while keeping the original aspect ratio.
* Supports image formats: JPEG, PNG, PPM, PGM & BMP
* @param pdf PDF document to add bookmark to
* @param page Page to add image to (NULL => most recently added page)
* @param x X offset to put image at
* @param y Y offset to put image at
* @param display_width Displayed width of image
* @param display_height Displayed height of image
* @param image_filename Filename of image file to display
* @return < 0 on failure, >= 0 on success
*/
int pdf_add_image_file(struct pdf_doc *pdf, struct pdf_object *page, float x,
float y, float display_width, float display_height,
const char *image_filename);
/**
* Parse image data to determine the image type & metadata
* @param info structure to hold the parsed metadata
* @param data image data to parse
* @param length number of bytes in data
* @param err_msg area to put any failure details
* @param err_msg_length maximum number of bytes to store in err_msg
* @return < 0 on failure, >= 0 on success
*/
int pdf_parse_image_header(struct pdf_img_info *info, const uint8_t *data,
size_t length, char *err_msg,
size_t err_msg_length);
#ifdef __cplusplus
}
#endif
#endif // PDFGEN_H

View File

@ -1,371 +1,161 @@
```
|Abrupt Decay {B}{G} (Instant) |Badlands (Land Swamp |Bayou (Land Swamp Forest) >>
|>> This spell can't be |Mountain) >> ({T}: Add {B} or |({T}: Add {B} or {G}.)
|countered. Destroy target |{R}.) |
|nonland permanent with mana | |
|value 3 or less. | |
| | |
|Birds of Paradise {G} |City of Brass (Land) >> |Eladamri's Call {G}{W}
|(Creature Bird) >> Flying |Whenever City of Brass |(Instant) >> Search your
|{T}: Add one mana of any |becomes tapped, it deals 1 |library for a creature card,
|color. >> 0/1 |damage to you. {T}: Add one |reveal that card, put it into
| |mana of any color. |your hand, then shuffle.
| | |
|Elvish Spirit Guide {2}{G} |Forest (Basic Land Forest) |Gitaxian Probe {U/P}
|(Creature Elf Spirit) >> |>> ({T}: Add {G}.) |(Sorcery) >> ({U/P} can be
|Exile Elvish Spirit Guide | |paid with either {U} or 2
|from your hand: Add {G}. >> | |life.) Look at target
|2/2 | |player's hand. Draw a card.
| | |
|Karakas (Legendary Land) >> |Lotus Petal {0} (Artifact) >> |Mana Confluence (Land) >>
|{T}: Add {W}. {T}: Return |{T}, Sacrifice Lotus Petal: |{T}, Pay 1 life: Add one mana
|target legendary creature to |Add one mana of any color. |of any color.
|its owner's hand. | |
| | |
| | |
|Mental Misstep {U/P} |Mox Emerald {0} (Artifact) >> |Mox Jet {0} (Artifact) >>
|(Instant) >> ({U/P} can be |{T}: Add {G}. |{T}: Add {B}.
|paid with either {U} or 2 | |
|life.) Counter target spell | |
|with mana value 1. | |
| | |
|Mox Pearl {0} (Artifact) >> |Plains (Basic Land Plains) |Plateau (Land Mountain
|{T}: Add {W}. |>> ({T}: Add {W}.) |Plains) >> ({T}: Add {R} or
| | |{W}.)
| | |
| | |
| | |
|Razorverge Thicket (Land) >> |Savannah (Land Forest |Scrubland (Land Plains
|Razorverge Thicket enters the |Plains) >> ({T}: Add {G} or |Swamp) >> ({T}: Add {W} or
|battlefield tapped unless you |{W}.) |{B}.)
|control two or fewer other | |
|lands. {T}: Add {G} or {W}. | |
| | |
|Simian Spirit Guide {2}{R} |Swamp (Basic Land Swamp) >> |Swords to Plowshares {W}
|(Creature Ape Spirit) >> |({T}: Add {B}.) |(Instant) >> Exile target
|Exile Simian Spirit Guide | |creature. Its controller
|from your hand: Add {R}. >> | |gains life equal to its
|2/2 | |power.
| | |
|Taiga (Land Mountain Forest) |Thalia, Guardian of Thraben |Wasteland (Land) >> {T}: Add
|>> ({T}: Add {R} or {G}.) |{1}{W} (Legendary Creature |{C}. {T}, Sacrifice
| |Human Soldier) >> First |Wasteland: Destroy target
| |strike Noncreature spells |nonbasic land.
| |cost {1} more to cast. >> 2/1 |
| | |
|Arid Mesa (Land) >> {T}, Pay |Duress {B} (Sorcery) >> |Flooded Strand (Land) >> {T},
|1 life, Sacrifice Arid Mesa: |Target opponent reveals their |Pay 1 life, Sacrifice Flooded
|Search your library for a |hand. You choose a |Strand: Search your library
|Mountain or Plains card, put |noncreature, nonland card |for a Plains or Island card,
|it onto the battlefield, then |from it. That player discards |put it onto the battlefield,
|shuffle. |that card. |then shuffle.
| | |
| | |
| | |
| | |
| | |
| | |
|Luminarch Aspirant {1}{W} |Marsh Flats (Land) >> {T}, |Misty Rainforest (Land) >>
|(Creature Human Cleric) >> |Pay 1 life, Sacrifice Marsh |{T}, Pay 1 life, Sacrifice
|At the beginning of combat on |Flats: Search your library |Misty Rainforest: Search your
|your turn, put a +1/+1 |for a Plains or Swamp card, |library for a Forest or
|counter on target creature |put it onto the battlefield, |Island card, put it onto the
|you control. >> 1/1 |then shuffle. |battlefield, then shuffle.
| | |
|Mother of Runes {W} (Creature |Polluted Delta (Land) >> {T}, |Prismatic Vista (Land) >>
| Human Cleric) >> {T}: Target |Pay 1 life, Sacrifice |{T}, Pay 1 life, Sacrifice
|creature you control gains |Polluted Delta: Search your |Prismatic Vista: Search your
|protection from the color of |library for an Island or |library for a basic land
|your choice until end of |Swamp card, put it onto the |card, put it onto the
|turn. >> 1/1 |battlefield, then shuffle. |battlefield, then shuffle.
| | |
|Reanimate {B} (Sorcery) >> |Thoughtseize {B} (Sorcery) >> |Verdant Catacombs (Land) >>
|Put target creature card from |Target player reveals their |{T}, Pay 1 life, Sacrifice
|a graveyard onto the |hand. You choose a nonland |Verdant Catacombs: Search
|battlefield under your |card from it. That player |your library for a Swamp or
|control. You lose life equal |discards that card. You lose |Forest card, put it onto the
|to its mana value. |2 life. |battlefield, then shuffle.
| | |
|Windswept Heath (Land) >> |Wooded Foothills (Land) >> |Archon of Emeria {2}{W}
|{T}, Pay 1 life, Sacrifice |{T}, Pay 1 life, Sacrifice |(Creature Archon) >> Flying
|Windswept Heath: Search your |Wooded Foothills: Search your |Each player can't cast more
|library for a Forest or |library for a Mountain or |than one spell each turn.
|Plains card, put it onto the |Forest card, put it onto the |Nonbasic lands your opponents
|battlefield, then shuffle. |battlefield, then shuffle. |control enter the battlefield
| | |tapped. >> 2/3
| | |
|Bloodstained Mire (Land) >> |Chrome Mox {0} (Artifact) >> |Godless Shrine (Land Plains
|{T}, Pay 1 life, Sacrifice |Imprint When Chrome Mox |Swamp) >> ({T}: Add {W} or
|Bloodstained Mire: Search |enters the battlefield, you |{B}.) As Godless Shrine
|your library for a Swamp or |may exile a nonartifact, |enters the battlefield, you
|Mountain card, put it onto |nonland card from your hand. |may pay 2 life. If you don't,
|the battlefield, then |{T}: Add one mana of any of |it enters the battlefield
|shuffle. |the exiled card's colors. |tapped.
| | |
|Ignoble Hierarch {G} |Inquisition of Kozilek {B} |Noble Hierarch {G} (Creature
|(Creature Goblin Shaman) >> |(Sorcery) >> Target player |Human Druid) >> Exalted
|Exalted (Whenever a creature |reveals their hand. You |(Whenever a creature you
|you control attacks alone, |choose a nonland card from it |control attacks alone, that
|that creature gets +1/+1 |with mana value 3 or less. |creature gets +1/+1 until end
|until end of turn.) {T}: Add |That player discards that |of turn.) {T}: Add {G}, {W},
|{B}, {R}, or {G}. >> 0/1 |card. |or {U}. >> 0/1
| | |
|Overgrown Tomb (Land Swamp |Path to Exile {W} (Instant) |Prismatic Ending {X}{W}
|Forest) >> ({T}: Add {B} or |>> Exile target creature. Its |(Sorcery) >> Converge Exile
|{G}.) As Overgrown Tomb |controller may search their |target nonland permanent if
|enters the battlefield, you |library for a basic land |its mana value is less than
|may pay 2 life. If you don't, |card, put that card onto the |or equal to the number of
|it enters the battlefield |battlefield tapped, then |colors of mana spent to cast
|tapped. |shuffle. |this spell.
| | |
|Scalding Tarn (Land) >> {T}, |Scavenging Ooze {1}{G} |Stomping Ground (Land
|Pay 1 life, Sacrifice |(Creature Ooze) >> {G}: |Mountain Forest) >> ({T}: Add
|Scalding Tarn: Search your |Exile target card from a |{R} or {G}.) As Stomping
|library for an Island or |graveyard. If it was a |Ground enters the
|Mountain card, put it onto |creature card, put a +1/+1 |battlefield, you may pay 2
|the battlefield, then |counter on Scavenging Ooze |life. If you don't, it enters
|shuffle. |and you gain 1 life. >> 2/2 |the battlefield tapped.
| | |
| | |
| | |
| | |
| | |
| | |
|Tarmogoyf {1}{G} (Creature |Temple Garden (Land Forest |Thalia, Heretic Cathar {2}{W}
|Lhurgoyf) >> Tarmogoyf's |Plains) >> ({T}: Add {G} or |(Legendary Creature Human
|power is equal to the number |{W}.) As Temple Garden enters |Soldier) >> First strike
|of card types among cards in |the battlefield, you may pay |Creatures and nonbasic lands
|all graveyards and its |2 life. If you don't, it |your opponents control enter
|toughness is equal to that |enters the battlefield |the battlefield tapped. >>
|number plus 1. >> */1+* |tapped. |3/2
| | |
|Unearth {B} (Sorcery) >> |Dark Confidant {1}{B} |Fatal Push {B} (Instant) >>
|Return target creature card |(Creature Human Wizard) >> |Destroy target creature if it
|with mana value 3 or less |At the beginning of your |has mana value 2 or less.
|from your graveyard to the |upkeep, reveal the top card |Revolt Destroy that creature
|battlefield. Cycling {2} |of your library and put that |if it has mana value 4 or
|({2}, Discard this card: Draw |card into your hand. You lose |less instead if a permanent
|a card.) |life equal to its mana value. |you controlled left the
| |>> 2/1 |battlefield this turn.
| | |
|Green Sun's Zenith {X}{G} |Hexdrinker {G} (Creature |Shadowspear {1} (Legendary
|(Sorcery) >> Search your |Snake) >> Level up {1} ({1}: |Artifact Equipment) >>
|library for a green creature |Put a level counter on this. |Equipped creature gets +1/+1
|card with mana value X or |Level up only as a sorcery.) |and has trample and lifelink.
|less, put it onto the |LEVEL 3-7 4/4 Protection from |{1}: Permanents your
|battlefield, then shuffle. |instants LEVEL 8+ 6/6 |opponents control lose
|Shuffle Green Sun's Zenith |Protection from everything >> |hexproof and indestructible
|into its owner's library. |2/1 |until end of turn. Equip {2}
| | |
|Forth Eorlingas! {X}{R}{W} |Mox Diamond {0} (Artifact) >> |Undermountain Adventurer
|(Sorcery) >> Create X 2/2 red |If Mox Diamond would enter |{3}{G} (Creature Giant
|Human Knight creature tokens |the battlefield, you may |Warrior) >> Vigilance When
|with trample and haste. |discard a land card instead. |Undermountain Adventurer
|Whenever one or more |If you do, put Mox Diamond |enters the battlefield, you
|creatures you control deal |onto the battlefield. If you |take the initiative. {T}: Add
|combat damage to one or more |don't, put it into its |{G}{G}. If you've completed a
|players this turn, you become |owner's graveyard. {T}: Add |dungeon, add six {G} instead.
|the monarch. |one mana of any color. |>> 3/4
| | |
|Endurance {1}{G}{G} (Creature |Orcish Bowmasters {1}{B} |Troll of Khazad-dm {5}{B}
| Elemental Incarnation) >> |(Creature Orc Archer) >> |(Creature Troll) >> Troll of
|Flash Reach When Endurance |Flash When Orcish Bowmasters |Khazad-dm can't be blocked
|enters the battlefield, up to |enters the battlefield and |except by three or more
|one target player puts all |whenever an opponent draws a |creatures. Swampcycling {1}
|the cards from their |card except the first one |({1}, Discard this card:
|graveyard on the bottom of |they draw in each of their |Search your library for a
|their library in a random |draw steps, Orcish Bowmasters |Swamp card, reveal it, put it
|order. EvokeExile a green |deals 1 damage to any target. |into your hand, then
|card from your hand. >> 3/4 |Then amass Orcs 1. >> 1/1 |shuffle.) >> 6/5
| | |
|Underground Mortuary (Land |Cankerbloom {1}{G} (Creature |Leyline Binding {5}{W}
|Swamp Forest) >> ({T}: Add |Phyrexian Fungus) >> {1}, |(Enchantment) >> Flash Domain
|{B} or {G}.) Underground |Sacrifice Cankerbloom: Choose | This spell costs {1} less to
|Mortuary enters the |one Destroy target |cast for each basic land type
|battlefield tapped. When |artifact. Destroy target |among lands you control. When
|Underground Mortuary enters |enchantment. Proliferate. |Leyline Binding enters the
|the battlefield, surveil 1. |(Choose any number of |battlefield, exile target
|(Look at the top card of your |permanents and/or players, |nonland permanent an opponent
|library. You may put it into |then give each another |controls until Leyline
|your graveyard.) |counter of each kind already |Binding leaves the
| |there.) >> 3/2 |battlefield.
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
|Tidehollow Sculler {W}{B} |White Plume Adventurer {2}{W} |Ayara's Oathsworn {1}{B}
|(Artifact Creature Zombie) |(Creature Orc Cleric) >> |(Creature Human Knight) >>
|>> When Tidehollow Sculler |When White Plume Adventurer |Menace Whenever Ayara's
|enters the battlefield, |enters the battlefield, you |Oathsworn deals combat damage
|target opponent reveals their |take the initiative. At the |to a player, if it has fewer
|hand and you choose a nonland |beginning of each opponent's |than four +1/+1 counters on
|card from it. Exile that |upkeep, untap a creature you |it, put a +1/+1 counter on
|card. When Tidehollow Sculler |control. If you've completed |it. Then if it has exactly
|leaves the battlefield, |a dungeon, untap all |four +1/+1 counters on it,
|return the exiled card to its |creatures you control |search your library for a
|owner's hand. >> 2/2 |instead. >> 3/3 |card, put it into your hand,
| | |then shuffle. >> 2/2
| | |
|Deathrite Shaman {B/G} |Laelia, the Blade Reforged |Lord Skitter, Sewer King
|(Creature Elf Shaman) >> |{2}{R} (Legendary Creature |{2}{B} (Legendary Creature
|{T}: Exile target land card |Spirit Warrior) >> Haste |Rat Noble) >> Whenever
|from a graveyard. Add one |Whenever Laelia, the Blade |another Rat enters the
|mana of any color. {B}, {T}: |Reforged attacks, exile the |battlefield under your
|Exile target instant or |top card of your library. You |control, exile up to one
|sorcery card from a |may play that card this turn. |target card from an
|graveyard. Each opponent |Whenever one or more cards |opponent's graveyard. At the
|loses 2 life. {G}, {T}: Exile |are put into exile from your |beginning of combat on your
|target creature card from a |library and/or your |turn, create a 1/1 black Rat
|graveyard. You gain 2 life. |graveyard, put a +1/+1 |creature token with "This
|>> 1/2 |counter on Laelia. >> 2/2 |creature can't block." >> 3/3
| | |
|Once Upon a Time {1}{G} |Sentinel of the Nameless City |Witherbloom Command {B}{G}
|(Instant) >> If this spell is |{2}{G} (Creature Merfolk |(Sorcery) >> Choose two
|the first spell you've cast |Warrior Scout) >> Vigilance |Target player mills three
|this game, you may cast it |Whenever Sentinel of the |cards, then you return a land
|without paying its mana cost. |Nameless City enters the |card from your graveyard to
|Look at the top five cards of |battlefield or attacks, |your hand. Destroy target
|your library. You may reveal |create a Map token. (It's an |noncreature, nonland
|a creature or land card from |artifact with "{1}, {T}, |permanent with mana value 2
|among them and put it into |Sacrifice this artifact: |or less. Target creature
|your hand. Put the rest on |Target creature you control |gets -3/-1 until end of turn.
|the bottom of your library in |explores. Activate only as a | Target opponent loses 2 life
|a random order. |sorcery.") >> 3/4 |and you gain 2 life.
| | |
|Broadside Bombardiers {2}{R} |Generous Ent {5}{G} (Creature |Inti, Seneschal of the Sun
|(Creature Goblin Pirate) >> | Treefolk) >> Reach When |{1}{R} (Legendary Creature
|Menace, haste Boast |Generous Ent enters the |Human Knight) >> Whenever you
|Sacrifice another creature or |battlefield, create a Food |attack, you may discard a
|artifact: Broadside |token. (It's an artifact with |card. When you do, put a
|Bombardiers deals damage |"{2}, {T}, Sacrifice this |+1/+1 counter on target
|equal to 2 plus the |artifact: You gain 3 life.") |attacking creature. It gains
|sacrificed permanent's mana |Forestcycling {1} ({1}, |trample until end of turn.
|value to any target. |Discard this card: Search |Whenever you discard one or
|(Activate only if this |your library for a Forest |more cards, exile the top
|creature attacked this turn |card, reveal it, put it into |card of your library. You may
|and only once each turn.) >> |your hand, then shuffle.) >> |play that card until your
|2/2 |5/7 |next end step. >> 2/2
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
|Legolas's Quick Reflexes {G} |Mawloc {X}{R}{G} (Creature |Mosswood Dreadknight // Dread
|(Instant) >> Split second (As |Tyranid) >> Ravenous (This |Whispers {1}{G} // {1}{B}
|long as this spell is on the |creature enters the |(Creature Human Knight //
|stack, players can't cast |battlefield with X +1/+1 |Sorcery Adventure) >> >>
|spells or activate abilities |counters on it. If X is 5 or |3/2Trample When Mosswood
|that aren't mana abilities.) |more, draw a card when it |Dreadknight dies, you may
|Untap target creature. Until |enters.) Terror from the Deep |cast it from your graveyard
|end of turn, it gains | When Mawloc enters the |as an Adventure until the end
|hexproof, reach, and |battlefield, it fights up to |of your next turn. 3/2 // You
|"Whenever this creature |one target creature an |draw a card and you lose 1
|becomes tapped, it deals |opponent controls. If that |life. (Then exile this card.
|damage equal to its power to |creature would die this turn, |You may cast the creature
|up to one target creature." |exile it instead. >> 2/2 |later from exile.)
| | |
|Anointed Peacekeeper {2}{W} |Caves of Chaos Adventurer |Opposition Agent {2}{B}
|(Creature Human Cleric) >> |{3}{R} (Creature Human |(Creature Human Rogue) >>
|Vigilance As Anointed |Barbarian) >> Trample When |Flash You control your
|Peacekeeper enters the |Caves of Chaos Adventurer |opponents while they're
|battlefield, look at an |enters the battlefield, you |searching their libraries.
|opponent's hand, then choose |take the initiative. Whenever |While an opponent is
|any card name. Spells your |Caves of Chaos Adventurer |searching their library, they
|opponents cast with the |attacks, exile the top card |exile each card they find.
|chosen name cost {2} more to |of your library. If you've |You may play those cards for
|cast. Activated abilities of |completed a dungeon, you may |as long as they remain
|sources with the chosen name |play that card this turn |exiled, and you may spend
|cost {2} more to activate |without paying its mana cost. |mana as though it were mana
|unless they're mana |Otherwise, you may play that |of any color to cast them. >>
|abilities. >> 3/3 |card this turn. >> 5/3 |3/2
| | |
|Questing Beast {2}{G}{G} |Boseiju, Who Endures |Sungold Sentinel {1}{W}
|(Legendary Creature Beast) |(Legendary Land) >> {T}: Add |(Creature Human Soldier) >>
|>> Vigilance, deathtouch, |{G}. Channel {1}{G}, Discard |Whenever Sungold Sentinel
|haste Questing Beast can't be |Boseiju, Who Endures: Destroy |enters the battlefield or
|blocked by creatures with |target artifact, enchantment, |attacks, exile up to one
|power 2 or less. Combat |or nonbasic land an opponent |target card from a graveyard.
|damage that would be dealt by |controls. That player may |Coven {1}{W}: Choose a
|creatures you control can't |search their library for a |color. Sungold Sentinel gains
|be prevented. Whenever |land card with a basic land |hexproof from that color
|Questing Beast deals combat |type, put it onto the |until end of turn and can't
|damage to an opponent, it |battlefield, then shuffle. |be blocked by creatures of
|deals that much damage to |This ability costs {1} less |that color this turn.
|target planeswalker that |to activate for each |Activate only if you control
|player controls. >> 4/4 |legendary creature you |three or more creatures with
| |control. |different powers. >> 3/2
| | |
|Tenth District Hero {1}{W} |Wrenn and Six {R}{G} |Seasoned Dungeoneer {3}{W}
|(Creature Human) >> {1}{W}, |(Legendary Planeswalker |(Creature Human Warrior) >>
|Collect evidence 2: Tenth |Wrenn) >> +1: Return up to |When Seasoned Dungeoneer
|District Hero becomes a Human |one target land card from |enters the battlefield, you
|Detective with base power and |your graveyard to your hand. |take the initiative. Whenever
|toughness 4/4 and gains |1: Wrenn and Six deals 1 |you attack, target attacking
|vigilance. {2}{W}, Collect |damage to any target. 7: You |Cleric, Rogue, Warrior, or
|evidence 4: If Tenth District |get an emblem with "Instant |Wizard gains protection from
|Hero is a Detective, it |and sorcery cards in your |creatures until end of turn.
|becomes a legendary creature |graveyard have retrace." (You |It explores. (Reveal the top
|named Mileva, the Stalwart, |may cast instant and sorcery |card of your library. Put
|it has base power and |cards from your graveyard by |that card into your hand if
|toughness 5/5, and it gains |discarding a land card in |it's a land. Otherwise, put a
|"Other creatures you control |addition to paying their |+1/+1 counter on the
|have indestructible." >> 2/3 |other costs.) |creature, then put the card
| | |back or put it into your
| | |graveyard.) >> 3/4
| | |
| | |
| | |
| | |
|Comet, Stellar Pup {2}{R}{W} |Kellan, Daring Traveler // |Grist, the Hunger Tide
|(Legendary Planeswalker |Journey On {1}{W} // {G} |{1}{B}{G} (Legendary
|Comet) >> 0: Roll a six-sided |(Legendary Creature Human |Planeswalker Grist) >> As
|die. 1 or 2 [+2], then |Faerie Scout // Sorcery |long as Grist, the Hunger
|create two 1/1 green Squirrel |Adventure) >> >> 2/3Whenever |Tide isn't on the
|creature tokens. They gain |Kellan, Daring Traveler |battlefield, it's a 1/1
|haste until end of turn. 3 |attacks, reveal the top card |Insect creature in addition
|[1], then return a card with |of your library. If it's a |to its other types. +1:
|mana value 2 or less from |creature card with mana value |Create a 1/1 black and green
|your graveyard to your hand. |3 or less, put it into your |Insect creature token, then
|4 or 5 Comet, Stellar Pup |hand. Otherwise, you may put |mill a card. If an Insect
|deals damage equal to the |it into your graveyard. 2/3 |card was milled this way, put
|number of loyalty counters on |// Create X Map tokens, where |a loyalty counter on Grist
|him to a creature or player, |X is one plus the number of |and repeat this process. 2:
|then [2]. 6 [+1], and you |opponents who control an |You may sacrifice a creature.
|may activate Comet, Stellar |artifact. (Then exile this |When you do, destroy target
|Pup's loyalty ability two |card. You may cast the |creature or planeswalker. 5:
|more times this turn. |creature later from exile.) |Each opponent loses life
| | |equal to the number of
| | |creature cards in your
| | |graveyard.
| | |
|Minsc & Boo, Timeless Heroes | |
|{2}{R}{G} (Legendary | |
|Planeswalker Minsc) >> When | |
|Minsc & Boo, Timeless Heroes | |
|enters the battlefield and at | |
|the beginning of your upkeep, | |
|you may create Boo, a | |
|legendary 1/1 red Hamster | |
|creature token with trample | |
|and haste. +1: Put three | |
|+1/+1 counters on up to one | |
|target creature with trample | |
|or haste. 2: Sacrifice a | |
|creature. When you do, Minsc | |
|& Boo, Timeless Heroes deals | |
|Prismatic Vista (Land) >> |Mox Jet {0} (Artifact) >> |Tenth District Hero {1}{W}
|{T}, Pay 1 life, Sacrifice |{T}: Add {B}. |(Creature Human) >> {1}{W},
|Prismatic Vista: Search your | |Collect evidence 2: Tenth
|library for a basic land | |District Hero becomes a Human
|card, put it onto the | |Detective with base power and
|battlefield, then shuffle. | |toughness 4/4 and gains
| | |vigilance. {2}{W}, Collect
| | |evidence 4: If Tenth District
| | |Hero is a Detective, it
| | |becomes a legendary creature
| | |named Mileva, the Stalwart,
| | |it has base power and
| | |toughness 5/5, and it gains
| | |"Other creatures you control
| | |have indestructible." (2/3)
| | |
|Wasteland (Land) >> {T}: Add |Misty Rainforest (Land) >> |Green Sun's Zenith {X}{G}
|{C}. {T}, Sacrifice |{T}, Pay 1 life, Sacrifice |(Sorcery) >> Search your
|Wasteland: Destroy target |Misty Rainforest: Search your |library for a green creature
|nonbasic land. |library for a Forest or |card with mana value X or
| |Island card, put it onto the |less, put it onto the
| |battlefield, then shuffle. |battlefield, then shuffle.
| | |Shuffle Green Sun's Zenith
| | |into its owner's library.
| | |
|Grist, the Hunger Tide |Wrenn and Six {R}{G} |Thalia, Heretic Cathar {2}{W}
|{1}{B}{G} (Legendary |(Legendary Planeswalker |(Legendary Creature Human
|Planeswalker Grist) >> As |Wrenn) >> +1: Return up to |Soldier) >> First strike
|long as Grist, the Hunger |one target land card from |Creatures and nonbasic lands
|Tide isn't on the |your graveyard to your hand. |your opponents control enter
|battlefield, it's a 1/1 |1: Wrenn and Six deals 1 |the battlefield tapped. (3/2)
|Insect creature in addition |damage to any target. 7: You |
|to its other types. +1: |get an emblem with "Instant |
|Create a 1/1 black and green |and sorcery cards in your |
|Insect creature token, then |graveyard have retrace." (You |
|mill a card. If an Insect |may cast instant and sorcery |
|card was milled this way, put |cards from your graveyard by |
|a loyalty counter on Grist |discarding a land card in |
|and repeat this process. 2: |addition to paying their |
|You may sacrifice a creature. |other costs.) |
|When you do, destroy target | |
|creature or planeswalker. 5: | |
|Each opponent loses life | |
|equal to the number of | |
|creature cards in your | |
|graveyard. | |
| | |
|Badlands (Land Swamp |Stomping Ground (Land |Fatal Push {B} (Instant) >>
|Mountain) >> ({T}: Add {B} or |Mountain Forest) >> ({T}: Add |Destroy target creature if it
|{R}.) |{R} or {G}.) As Stomping |has mana value 2 or less.
| |Ground enters the |Revolt Destroy that creature
| |battlefield, you may pay 2 |if it has mana value 4 or
| |life. If you don't, it enters |less instead if a permanent
| |the battlefield tapped. |you controlled left the
| | |battlefield this turn.
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
|Inti, Seneschal of the Sun |Mental Misstep {U/P} |Abrupt Decay {B}{G} (Instant)
|{1}{R} (Legendary Creature |(Instant) >> ({U/P} can be |>> This spell can't be
|Human Knight) >> Whenever you |paid with either {U} or 2 |countered. Destroy target
|attack, you may discard a |life.) Counter target spell |nonland permanent with mana
|card. When you do, put a |with mana value 1. |value 3 or less.
|+1/+1 counter on target | |
|attacking creature. It gains | |
|trample until end of turn. | |
|Whenever you discard one or | |
|more cards, exile the top | |
|card of your library. You may | |
|play that card until your | |
|next end step. (2/2) | |
| | |
|Gitaxian Probe {U/P} |Opposition Agent {2}{B} |Scavenging Ooze {1}{G}
|(Sorcery) >> ({U/P} can be |(Creature Human Rogue) >> |(Creature Ooze) >> {G}:
|paid with either {U} or 2 |Flash You control your |Exile target card from a
|life.) Look at target |opponents while they're |graveyard. If it was a
|player's hand. Draw a card. |searching their libraries. |creature card, put a +1/+1
| |While an opponent is |counter on Scavenging Ooze
| |searching their library, they |and you gain 1 life. (2/2)
| |exile each card they find. |
| |You may play those cards for |
| |as long as they remain |
| |exiled, and you may spend |
| |mana as though it were mana |
| |of any color to cast them. |
| |(3/2) |
| | |
|Dark Confidant {1}{B} |Troll of Khazad-dm {5}{B} |Taiga (Land Mountain Forest)
|(Creature Human Wizard) >> |(Creature Troll) >> Troll of |>> ({T}: Add {R} or {G}.)
|At the beginning of your |Khazad-dm can't be blocked |
|upkeep, reveal the top card |except by three or more |
|of your library and put that |creatures. Swampcycling {1} |
|card into your hand. You lose |({1}, Discard this card: |
|life equal to its mana value. |Search your library for a |
|(2/1) |Swamp card, reveal it, put it |
| |into your hand, then |
| |shuffle.) (6/5) |
| | |
|Deathrite Shaman {B/G} |Windswept Heath (Land) >> |Prismatic Ending {X}{W}
|(Creature Elf Shaman) >> |{T}, Pay 1 life, Sacrifice |(Sorcery) >> Converge Exile
|{T}: Exile target land card |Windswept Heath: Search your |target nonland permanent if
|from a graveyard. Add one |library for a Forest or |its mana value is less than
|mana of any color. {B}, {T}: |Plains card, put it onto the |or equal to the number of
|Exile target instant or |battlefield, then shuffle. |colors of mana spent to cast
|sorcery card from a | |this spell.
|graveyard. Each opponent | |
|loses 2 life. {G}, {T}: Exile | |
|target creature card from a | |
|graveyard. You gain 2 life. | |
|(1/2) | |
| | |
|Undermountain Adventurer |Lotus Petal {0} (Artifact) >> |Duress {B} (Sorcery) >>
|{3}{G} (Creature Giant |{T}, Sacrifice Lotus Petal: |Target opponent reveals their
|Warrior) >> Vigilance When |Add one mana of any color. |hand. You choose a
|Undermountain Adventurer | |noncreature, nonland card
|enters the battlefield, you | |from it. That player discards
|take the initiative. {T}: Add | |that card.
|{G}{G}. If you've completed a | |
|dungeon, add six {G} instead. | |
|(3/4) | |
| | |
| | |
| | |
| | |
|Forth Eorlingas! {X}{R}{W} |Mox Emerald {0} (Artifact) >> |Inquisition of Kozilek {B}
|(Sorcery) >> Create X 2/2 red |{T}: Add {G}. |(Sorcery) >> Target player
|Human Knight creature tokens | |reveals their hand. You
|with trample and haste. | |choose a nonland card from it
|Whenever one or more | |with mana value 3 or less.
|creatures you control deal | |That player discards that
|combat damage to one or more | |card.
|players this turn, you become | |
|the monarch. | |
| | |
|Minsc & Boo, Timeless Heroes |Tarmogoyf (Token Creature |Sungold Sentinel {1}{W}
|{2}{R}{G} (Legendary |Lhurgoyf) >> Tarmogoyf's |(Creature Human Soldier) >>
|Planeswalker Minsc) >> When |power is equal to the number |Whenever Sungold Sentinel
|Minsc & Boo, Timeless Heroes |of card types among cards in |enters the battlefield or
|enters the battlefield and at |all graveyards and its |attacks, exile up to one
|the beginning of your upkeep, |toughness is equal to that |target card from a graveyard.
|you may create Boo, a |number plus 1. (This token's |Coven {1}{W}: Choose a
|legendary 1/1 red Hamster |mana cost is {1}{G}.) (*/1+*) |color. Sungold Sentinel gains
|creature token with trample | |hexproof from that color
|and haste. +1: Put three | |until end of turn and can't
|+1/+1 counters on up to one | |be blocked by creatures of
|target creature with trample | |that color this turn.
|or haste. 2: Sacrifice a | |Activate only if you control
|creature. When you do, Minsc | |three or more creatures with
|& Boo, Timeless Heroes deals | |different powers. (3/2)
|X damage to any target, where | |
|X is that creature's power. | |
|If the sacrificed creature | |
@ -373,4 +163,328 @@
|Minsc & Boo, Timeless Heroes | |
|can be your commander. | |
| | |
|Simian Spirit Guide {2}{R} |Eladamri's Call {G}{W} |Unearth {B} (Sorcery) >>
|(Creature Ape Spirit) >> |(Instant) >> Search your |Return target creature card
|Exile Simian Spirit Guide |library for a creature card, |with mana value 3 or less
|from your hand: Add {R}. |reveal that card, put it into |from your graveyard to the
|(2/2) |your hand, then shuffle. |battlefield. Cycling {2}
| | |({2}, Discard this card: Draw
| | |a card.)
| | |
|Tidehollow Sculler {W}{B} |White Plume Adventurer {2}{W} |Swamp (Basic Land Swamp) >>
|(Artifact Creature Zombie) |(Creature Orc Cleric) >> |({T}: Add {B}.)
|>> When Tidehollow Sculler |When White Plume Adventurer |
|enters the battlefield, |enters the battlefield, you |
|target opponent reveals their |take the initiative. At the |
|hand and you choose a nonland |beginning of each opponent's |
|card from it. Exile that |upkeep, untap a creature you |
|card. When Tidehollow Sculler |control. If you've completed |
|leaves the battlefield, |a dungeon, untap all |
|return the exiled card to its |creatures you control |
|owner's hand. (2/2) |instead. (3/3) |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
|Mosswood Dreadknight // Dread |Karakas (Legendary Land) >> |Legolas's Quick Reflexes {G}
|Whispers {1}{G} // {1}{B} |{T}: Add {W}. {T}: Return |(Instant) >> Split second (As
|(Creature Human Knight // |target legendary creature to |long as this spell is on the
|Sorcery Adventure) >> (3/2) |its owner's hand. |stack, players can't cast
|Trample When Mosswood | |spells or activate abilities
|Dreadknight dies, you may | |that aren't mana abilities.)
|cast it from your graveyard | |Untap target creature. Until
|as an Adventure until the end | |end of turn, it gains
|of your next turn. (3/2) | |hexproof, reach, and
| // | |"Whenever this creature
|You draw a card and you lose | |becomes tapped, it deals
|1 life. (Then exile this | |damage equal to its power to
|card. You may cast the | |up to one target creature."
|creature later from exile.) | |
| | |
| | |
| | |
|Seasoned Dungeoneer {3}{W} |Mother of Runes {W} (Creature |Wooded Foothills (Land) >>
|(Creature Human Warrior) >> | Human Cleric) >> {T}: Target |{T}, Pay 1 life, Sacrifice
|When Seasoned Dungeoneer |creature you control gains |Wooded Foothills: Search your
|enters the battlefield, you |protection from the color of |library for a Mountain or
|take the initiative. Whenever |your choice until end of |Forest card, put it onto the
|you attack, target attacking |turn. (1/1) |battlefield, then shuffle.
|Cleric, Rogue, Warrior, or | |
|Wizard gains protection from | |
|creatures until end of turn. | |
|It explores. (Reveal the top | |
|card of your library. Put | |
|that card into your hand if | |
|it's a land. Otherwise, put a | |
|+1/+1 counter on the | |
|creature, then put the card | |
|back or put it into your | |
|graveyard.) (3/4) | |
| | |
|Verdant Catacombs (Land) >> |Hexdrinker {G} (Creature |Elvish Spirit Guide {2}{G}
|{T}, Pay 1 life, Sacrifice |Snake) >> Level up {1} ({1}: |(Creature Elf Spirit) >>
|Verdant Catacombs: Search |Put a level counter on this. |Exile Elvish Spirit Guide
|your library for a Swamp or |Level up only as a sorcery.) |from your hand: Add {G}.
|Forest card, put it onto the |LEVEL 3-7 4/4 Protection from |(2/2)
|battlefield, then shuffle. |instants LEVEL 8+ 6/6 |
| |Protection from everything |
| |(2/1) |
| | |
|Savannah (Land Forest |Godless Shrine (Land Plains |Kellan, Daring Traveler //
|Plains) >> ({T}: Add {G} or |Swamp) >> ({T}: Add {W} or |Journey On {1}{W} // {G}
|{W}.) |{B}.) As Godless Shrine |(Legendary Creature Human
| |enters the battlefield, you |Faerie Scout // Sorcery
| |may pay 2 life. If you don't, |Adventure) >> (2/3) Whenever
| |it enters the battlefield |Kellan, Daring Traveler
| |tapped. |attacks, reveal the top card
| | |of your library. If it's a
| | |creature card with mana value
| | |3 or less, put it into your
| | |hand. Otherwise, you may put
| | |it into your graveyard. (2/3)
| | |
| | |// Create X Map tokens, where
| | |X is one plus the number of
| | |opponents who control an
| | |artifact. (Then exile this
| | |card. You may cast the
| | |creature later from exile.)
| | |
| | |
| | |
|Witherbloom Command {B}{G} |Mox Pearl {0} (Artifact) >> |Underground Mortuary (Land
|(Sorcery) >> Choose two |{T}: Add {W}. |Swamp Forest) >> ({T}: Add
|Target player mills three | |{B} or {G}.) Underground
|cards, then you return a land | |Mortuary enters the
|card from your graveyard to | |battlefield tapped. When
|your hand. Destroy target | |Underground Mortuary enters
|noncreature, nonland | |the battlefield, surveil 1.
|permanent with mana value 2 | |(Look at the top card of your
|or less. Target creature | |library. You may put it into
|gets -3/-1 until end of turn. | |your graveyard.)
| Target opponent loses 2 life | |
|and you gain 2 life. | |
| | |
|Shadowspear {1} (Legendary |Razorverge Thicket (Land) >> |Overgrown Tomb (Land Swamp
|Artifact Equipment) >> |Razorverge Thicket enters the |Forest) >> ({T}: Add {B} or
|Equipped creature gets +1/+1 |battlefield tapped unless you |{G}.) As Overgrown Tomb
|and has trample and lifelink. |control two or fewer other |enters the battlefield, you
|{1}: Permanents your |lands. {T}: Add {G} or {W}. |may pay 2 life. If you don't,
|opponents control lose | |it enters the battlefield
|hexproof and indestructible | |tapped.
|until end of turn. Equip {2} | |
| | |
|Noble Hierarch {G} (Creature |Thalia, Guardian of Thraben |Reanimate {B} (Sorcery) >>
|Human Druid) >> Exalted |{1}{W} (Legendary Creature |Put target creature card from
|(Whenever a creature you |Human Soldier) >> First |a graveyard onto the
|control attacks alone, that |strike Noncreature spells |battlefield under your
|creature gets +1/+1 until end |cost {1} more to cast. (2/1) |control. You lose life equal
|of turn.) {T}: Add {G}, {W}, | |to its mana value.
|or {U}. (0/1) | |
| | |
|Laelia, the Blade Reforged |Sentinel of the Nameless City |Anointed Peacekeeper {2}{W}
|{2}{R} (Legendary Creature |{2}{G} (Creature Merfolk |(Creature Human Cleric) >>
|Spirit Warrior) >> Haste |Warrior Scout) >> Vigilance |Vigilance As Anointed
|Whenever Laelia, the Blade |Whenever Sentinel of the |Peacekeeper enters the
|Reforged attacks, exile the |Nameless City enters the |battlefield, look at an
|top card of your library. You |battlefield or attacks, |opponent's hand, then choose
|may play that card this turn. |create a Map token. (It's an |any card name. Spells your
|Whenever one or more cards |artifact with "{1}, {T}, |opponents cast with the
|are put into exile from your |Sacrifice this artifact: |chosen name cost {2} more to
|library and/or your |Target creature you control |cast. Activated abilities of
|graveyard, put a +1/+1 |explores. Activate only as a |sources with the chosen name
|counter on Laelia. (2/2) |sorcery.") (3/4) |cost {2} more to activate
| | |unless they're mana
| | |abilities. (3/3)
| | |
|Swords to Plowshares {W} |Forest (Basic Land Forest) |Leyline Binding {5}{W}
|(Instant) >> Exile target |>> ({T}: Add {G}.) |(Enchantment) >> Flash Domain
|creature. Its controller | | This spell costs {1} less to
|gains life equal to its | |cast for each basic land type
|power. | |among lands you control. When
| | |Leyline Binding enters the
| | |battlefield, exile target
| | |nonland permanent an opponent
| | |controls until Leyline
| | |Binding leaves the
| | |battlefield.
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
|Questing Beast {2}{G}{G} |Generous Ent {5}{G} (Creature |Bayou (Land Swamp Forest) >>
|(Legendary Creature Beast) | Treefolk) >> Reach When |({T}: Add {B} or {G}.)
|>> Vigilance, deathtouch, |Generous Ent enters the |
|haste Questing Beast can't be |battlefield, create a Food |
|blocked by creatures with |token. (It's an artifact with |
|power 2 or less. Combat |"{2}, {T}, Sacrifice this |
|damage that would be dealt by |artifact: You gain 3 life.") |
|creatures you control can't |Forestcycling {1} ({1}, |
|be prevented. Whenever |Discard this card: Search |
|Questing Beast deals combat |your library for a Forest |
|damage to an opponent, it |card, reveal it, put it into |
|deals that much damage to |your hand, then shuffle.) |
|target planeswalker that |(5/7) |
|player controls. (4/4) | |
| | |
|Plains (Basic Land Plains) |Boseiju, Who Endures |Arid Mesa (Land) >> {T}, Pay
|>> ({T}: Add {W}.) |(Legendary Land) >> {T}: Add |1 life, Sacrifice Arid Mesa:
| |{G}. Channel {1}{G}, Discard |Search your library for a
| |Boseiju, Who Endures: Destroy |Mountain or Plains card, put
| |target artifact, enchantment, |it onto the battlefield, then
| |or nonbasic land an opponent |shuffle.
| |controls. That player may |
| |search their library for a |
| |land card with a basic land |
| |type, put it onto the |
| |battlefield, then shuffle. |
| |This ability costs {1} less |
| |to activate for each |
| |legendary creature you |
| |control. |
| | |
|Plateau (Land Mountain |Endurance {1}{G}{G} (Creature |Scrubland (Land Plains
|Plains) >> ({T}: Add {R} or | Elemental Incarnation) >> |Swamp) >> ({T}: Add {W} or
|{W}.) |Flash Reach When Endurance |{B}.)
| |enters the battlefield, up to |
| |one target player puts all |
| |the cards from their |
| |graveyard on the bottom of |
| |their library in a random |
| |order. EvokeExile a green |
| |card from your hand. (3/4) |
| | |
|Ignoble Hierarch {G} |Scalding Tarn (Land) >> {T}, |Luminarch Aspirant {1}{W}
|(Creature Goblin Shaman) >> |Pay 1 life, Sacrifice |(Creature Human Cleric) >>
|Exalted (Whenever a creature |Scalding Tarn: Search your |At the beginning of combat on
|you control attacks alone, |library for an Island or |your turn, put a +1/+1
|that creature gets +1/+1 |Mountain card, put it onto |counter on target creature
|until end of turn.) {T}: Add |the battlefield, then |you control. (1/1)
|{B}, {R}, or {G}. (0/1) |shuffle. |
| | |
|Archon of Emeria {2}{W} |Once Upon a Time {1}{G} |Mana Confluence (Land) >>
|(Creature Archon) >> Flying |(Instant) >> If this spell is |{T}, Pay 1 life: Add one mana
|Each player can't cast more |the first spell you've cast |of any color.
|than one spell each turn. |this game, you may cast it |
|Nonbasic lands your opponents |without paying its mana cost. |
|control enter the battlefield |Look at the top five cards of |
|tapped. (2/3) |your library. You may reveal |
| |a creature or land card from |
| |among them and put it into |
| |your hand. Put the rest on |
| |the bottom of your library in |
| |a random order. |
| | |
| | |
| | |
| | |
|Birds of Paradise {G} |Cankerbloom {1}{G} (Creature |Path to Exile {W} (Instant)
|(Creature Bird) >> Flying |Phyrexian Fungus) >> {1}, |>> Exile target creature. Its
|{T}: Add one mana of any |Sacrifice Cankerbloom: Choose |controller may search their
|color. (0/1) |one Destroy target |library for a basic land
| |artifact. Destroy target |card, put that card onto the
| |enchantment. Proliferate. |battlefield tapped, then
| |(Choose any number of |shuffle.
| |permanents and/or players, |
| |then give each another |
| |counter of each kind already |
| |there.) (3/2) |
| | |
|Marsh Flats (Land) >> {T}, |Broadside Bombardiers {2}{R} |Caves of Chaos Adventurer
|Pay 1 life, Sacrifice Marsh |(Creature Goblin Pirate) >> |{3}{R} (Creature Human
|Flats: Search your library |Menace, haste Boast |Barbarian) >> Trample When
|for a Plains or Swamp card, |Sacrifice another creature or |Caves of Chaos Adventurer
|put it onto the battlefield, |artifact: Broadside |enters the battlefield, you
|then shuffle. |Bombardiers deals damage |take the initiative. Whenever
| |equal to 2 plus the |Caves of Chaos Adventurer
| |sacrificed permanent's mana |attacks, exile the top card
| |value to any target. |of your library. If you've
| |(Activate only if this |completed a dungeon, you may
| |creature attacked this turn |play that card this turn
| |and only once each turn.) |without paying its mana cost.
| |(2/2) |Otherwise, you may play that
| | |card this turn. (5/3)
| | |
|Mawloc {X}{R}{G} (Creature |Comet, Stellar Pup {2}{R}{W} |Orcish Bowmasters {1}{B}
|Tyranid) >> Ravenous (This |(Legendary Planeswalker |(Creature Orc Archer) >>
|creature enters the |Comet) >> 0: Roll a six-sided |Flash When Orcish Bowmasters
|battlefield with X +1/+1 |die. 1 or 2 [+2], then |enters the battlefield and
|counters on it. If X is 5 or |create two 1/1 green Squirrel |whenever an opponent draws a
|more, draw a card when it |creature tokens. They gain |card except the first one
|enters.) Terror from the Deep |haste until end of turn. 3 |they draw in each of their
| When Mawloc enters the |[1], then return a card with |draw steps, Orcish Bowmasters
|battlefield, it fights up to |mana value 2 or less from |deals 1 damage to any target.
|one target creature an |your graveyard to your hand. |Then amass Orcs 1. (1/1)
|opponent controls. If that |4 or 5 Comet, Stellar Pup |
|creature would die this turn, |deals damage equal to the |
|exile it instead. (2/2) |number of loyalty counters on |
| |him to a creature or player, |
| |then [2]. 6 [+1], and you |
| |may activate Comet, Stellar |
| |Pup's loyalty ability two |
| |more times this turn. |
| | |
|Chrome Mox {0} (Artifact) >> |Thoughtseize {B} (Sorcery) >> |Polluted Delta (Land) >> {T},
|Imprint When Chrome Mox |Target player reveals their |Pay 1 life, Sacrifice
|enters the battlefield, you |hand. You choose a nonland |Polluted Delta: Search your
|may exile a nonartifact, |card from it. That player |library for an Island or
|nonland card from your hand. |discards that card. You lose |Swamp card, put it onto the
|{T}: Add one mana of any of |2 life. |battlefield, then shuffle.
|the exiled card's colors. | |
| | |
|City of Brass (Land) >> |Mox Diamond {0} (Artifact) >> |Flooded Strand (Land) >> {T},
|Whenever City of Brass |If Mox Diamond would enter |Pay 1 life, Sacrifice Flooded
|becomes tapped, it deals 1 |the battlefield, you may |Strand: Search your library
|damage to you. {T}: Add one |discard a land card instead. |for a Plains or Island card,
|mana of any color. |If you do, put Mox Diamond |put it onto the battlefield,
| |onto the battlefield. If you |then shuffle.
| |don't, put it into its |
| |owner's graveyard. {T}: Add |
| |one mana of any color. |
| | |
| | |
| | |
|Temple Garden (Land Forest |Lord Skitter, Sewer King |Bloodstained Mire (Land) >>
|Plains) >> ({T}: Add {G} or |{2}{B} (Legendary Creature |{T}, Pay 1 life, Sacrifice
|{W}.) As Temple Garden enters |Rat Noble) >> Whenever |Bloodstained Mire: Search
|the battlefield, you may pay |another Rat enters the |your library for a Swamp or
|2 life. If you don't, it |battlefield under your |Mountain card, put it onto
|enters the battlefield |control, exile up to one |the battlefield, then
|tapped. |target card from an |shuffle.
| |opponent's graveyard. At the |
| |beginning of combat on your |
| |turn, create a 1/1 black Rat |
| |creature token with "This |
| |creature can't block." (3/3) |
| | |
|Ayara's Oathsworn {1}{B} | |
|(Creature Human Knight) >> | |
|Menace Whenever Ayara's | |
|Oathsworn deals combat damage | |
|to a player, if it has fewer | |
|than four +1/+1 counters on | |
|it, put a +1/+1 counter on | |
|it. Then if it has exactly | |
|four +1/+1 counters on it, | |
|search your library for a | |
|card, put it into your hand, | |
|then shuffle. (2/2) | |
| | |
```

4167
src/pdfgen.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,11 @@
//TODO:
//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;
@ -21,7 +26,9 @@ const Card = struct {
card_faces: ?[]Card = null, //array of cards
};
const TextCard = struct { lines: [][]const u8 = undefined };
const TextCard = struct {
lines: [][]const u8 = undefined,
};
const PandocOptions = &[_][]const u8{ "pandoc", "out.md", "-o", "out.pdf", "--pdf-engine", "xelatex", "-V", "mainfont:Liberation Mono", "-V", "geometry:margin=0cm" };
@ -47,7 +54,17 @@ test "Check constants" {
try expect(pageWidth == markdownFormatString.len);
}
const pdf_info = struct { .creator = "My software", .producer = "My software", .title = "My document", .author = "My name", .subject = "My subject", .date = "Today" };
pub fn main() !void {
const pdf_doc: *c.pdf = c.pdf_create(c.PDF_A4_WIDTH, c.PDF_A4_HEIGHT, &pdf_info);
c.pdf_set_font(pdf_doc, "Times-Roman");
c.pdf_append_page(pdf_doc);
c.pdf_add_text(pdf_doc, c.NULL, "This is text", 12, 50, 20, c.PDF_BLACK);
c.pdf_add_line(pdf_doc, c.NULL, 50, 24, 150, 24, 3, c.PDF_BLACK);
c.pdf_save(pdf_doc, "output.pdf");
c.pdf_destroy(pdf_doc);
var args = try std.process.argsWithAllocator(std.heap.page_allocator);
//TODO: properly handle program arguments
@ -62,8 +79,8 @@ pub fn main() !void {
const allocator = arena.allocator();
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 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();
@ -77,133 +94,76 @@ pub fn main() !void {
else => return err,
}
var depth: u32 = 0;
var cardString = std.ArrayList(u8).init(allocator);
var cards = std.StringHashMap(Card).init(allocator);
while (oracleFile.reader().readByte()) |char| {
if (char == '{') depth += 1;
if (depth != 0) try cardString.append(char);
if (char == '}') {
depth -= 1;
if (depth == 0) {
// print("{s}\n", .{cardString.items});
const parsedCard = try std.json.parseFromSlice(Card, allocator, cardString.items, .{ .ignore_unknown_fields = true });
var lookupTimer = try std.time.Timer.start();
for (cardNames.items, 0..) |cardName, i| {
if (std.mem.eql(u8, parsedCard.value.name, cardName)) {
// print("{s}\n", .{parsedCard.value.name});
try cards.putNoClobber(try allocator.dupe(u8, parsedCard.value.name), (try allocator.dupe(Card, &[_]Card{parsedCard.value}))[0]);
_ = cardNames.orderedRemove(i);
}
}
cardString.clearAndFree();
print("took {d:.10}\n", .{lookupTimer.read() / (1000 * 1000 * 1000)});
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);
}
}
} else |e| {
switch (e) {
error.EndOfStream => {},
else => {
return e;
},
}
}
var cardIterator = cards.valueIterator();
while (cardIterator.next()) |cardObj| {
print("{any}\n", .{cardObj});
}
// var allCards = std.StringHashMap(Card).init(allocator);
// for(parsedJson.value) |cardObj| {
// try allCards.put(cardObj.name, cardObj);
// }
// parsedJson.deinit();
//
// var allPrinted = std.ArrayList(u8).init(allocator);
// var cards = std.ArrayList(TextCard).init(allocator);
// // var pages = std.ArrayList(page).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];
// assert(cardName.len > 0);
// const cardText = try card(allCards.get(cardName).?);
// try cards.append(cardText);
// } else |err| switch(err) {
// error.EndOfStream => {},
// else => return err,
// }
//
// for(cards.items) |cardText| {
// std.debug.print("{any}\n", .{cardText});
// }
//sort the cards
// std.mem.sort(TextCard, cards.items, {}, compareTwo);
var allPrinted = std.ArrayList(u8).init(allocator);
//TODO (fixme): absolutely GARBAGE hack to get pandoc to preserve whitespace
// try allPrinted.appendSlice(markdownFormatString);
// var rowToPrint = std.ArrayList(TextCard).init(allocator);
// for(cards.items) |cardObj| {
// try rowToPrint.append(cardObj);
// std.debug.print("{any}\n", .{cardObj});
// if(rowToPrint.items.len >= 3) {
// try cardRow.print(allocator, rowToPrint.items, &allPrinted);
// rowToPrint.clearAndFree();
// }
// // } else {
// try cardRow.print(allocator, rowToPrint.items, &allPrinted);
// try allPrinted.appendSlice(markdownFormatString);
// std.debug.print("{s}", .{allPrinted.items});
// try cwd.writeFile(.{.sub_path = "out.md", .data = allPrinted.items});
// rowToPrint.clearAndFree();
// var pandocProcess = std.process.Child.init(PandocOptions, allocator);
// _ = try pandocProcess.spawnAndWait();
// }
}
fn compareTwo(_: void, a: TextCard, b: TextCard) bool {
return a.lines.len < b.lines.len;
try allPrinted.appendSlice(markdownFormatString);
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);
try allPrinted.appendSlice(markdownFormatString);
std.debug.print("{s}", .{allPrinted.items});
try cwd.writeFile(.{ .sub_path = "out.md", .data = allPrinted.items });
rowToPrint.clearAndFree();
var pandocProcess = std.process.Child.init(PandocOptions, allocator);
_ = try pandocProcess.spawnAndWait();
}
}
fn card(
cardObj: Card,
allocator: std.mem.Allocator,
isFace: bool,
) !TextCard {
var buffer: [cardHeight * 1024]u8 = undefined;
var cardTextAllocator = std.heap.FixedBufferAllocator.init(&buffer);
defer cardTextAllocator.reset();
const cta = cardTextAllocator.allocator();
var cardText = std.ArrayList([]const u8).init(cta);
var cardText = std.ArrayList([]const u8).init(allocator);
var fullUnformattedText = std.ArrayList(u8).init(cta);
try fullUnformattedText.appendSlice(cardObj.name);
try fullUnformattedText.appendSlice(try std.mem.concat(cta, u8, &[_][]const u8{
if (cardObj.mana_cost.len > 0) " " else "",
cardObj.mana_cost,
" (",
cardObj.type_line,
") >> ",
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 "",
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| {
try fullUnformattedText.appendSlice(face.oracle_text);
if (face.power.len > 0) try fullUnformattedText.appendSlice(" ");
try fullUnformattedText.appendSlice(face.power);
if (face.power.len > 0) try fullUnformattedText.appendSlice("/");
try fullUnformattedText.appendSlice(face.toughness);
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(cta);
var word = std.ArrayList(u8).init(cta);
var line = std.ArrayList(u8).init(allocator);
var word = std.ArrayList(u8).init(allocator);
for (fullUnformattedText.items) |char| {
try switch (char) {

54
src/selectiveParse-v2.zig Normal file
View File

@ -0,0 +1,54 @@
const std = @import("std");
const cwd = std.fs.cwd;
const json = std.json;
const print = std.debug.print;
const oracleFileName = "oracle-cards-20240701090158.json";
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 testCardName = "Raffine, Scheming Seer";
const cardList = std.MultiArrayList(Card);
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
const oracleFile = try cwd().openFile(oracleFileName, .{});
const fileReader = oracleFile.reader();
var bufferedReader = std.io.bufferedReader(fileReader);
const readerReader = bufferedReader.reader();
// var jsonReader = json.reader(allocator, oracleFile.reader());
// var fields = std.ArrayList(Field).init(allocator);
// var isVal = false;
// var cards = std.StringHashMap(*StringHashMap);
// var currentCard = std.ArrayList(std.json.Token).init(allocator);
var depth: u32 = 0;
var cardString = std.ArrayList(u8).init(allocator);
var idx: u32 = 0;
while (readerReader.readByte()) |char| {
if (char == '{') depth += 1;
if (depth != 0) try cardString.append(char);
if (char == '}') {
depth -= 1;
if (depth == 0) {
// print("{s}\n", .{cardString.items});
const parsedCard = try std.json.parseFromSlice(Card, allocator, cardString.items, .{ .ignore_unknown_fields = true });
print("{s}\n", .{parsedCard.value.name});
cardString.clearAndFree();
}
}
idx += 1;
} else |e| {
return e;
}
}