A Rust workspace for converting card images into printable PDFs with cutting guidelines. Available as a web app, GUI application, CLI tool, and library. Written using MIT-licensed printpdf for PDF generation.
- π Web App: Browser-based WASM version (no installation)
- π₯οΈ Desktop GUI: Native cross-platform application
- π» CLI Tool: Command-line interface for automation
- π Library: Embed in your own Rust projects
- π΄ Card layouts: Configurable grid sizes (1Γ1 to 20Γ20)
- βοΈ Cutting guides: Automatic crosshairs and edge lines for precise cutting
- π Double-sided printing: Back cards automatically aligned for flipping
- πΌοΈ Multiple formats: Supports PNG, JPG, and JPEG images
- π Letter size: Generates standard 8.5Γ11 inch PDFs
Try the web version (no installation required): https://chuntttttt.github.io/Cards/
Upload a ZIP file containing your cards and generate PDFs entirely in your browser!
Download the latest release for your platform from the Releases page. No dependencies required!
Requires Rust 1.70+ and Cargo:
# Clone the repository
git clone https://github.com/Chuntttttt/Cards.git
cd Cards
# Build CLI and GUI (native applications)
cargo build --release --workspace
# Build CLI only
cd cards-cli && cargo build --release
# Build GUI only
cd cards-gui && cargo build --release
# Build WASM webapp (requires wasm32 target and trunk)
rustup target add wasm32-unknown-unknown
cargo install trunk
cd cards-wasm && trunk build --release
# Executables will be at:
# - CLI: ./target/release/cards
# - GUI: ./target/release/cards-gui
# - WASM: ./cards-wasm/dist/ (HTML + WASM bundle)Add to your Cargo.toml:
[dependencies]
cards-core = { git = "https://github.com/Chuntttttt/Cards.git" }Or use a local path:
[dependencies]
cards-core = { path = "../Cards/cards-core" }Cards turns a folder structure like this:
cards/
front/
card_01.png
card_02.png
...
card_50.png
back/
back_01.png
back_02.png
...
back_50.png
Into a PDF with cutting guidelines and interleaved front/back cards for double-sided printing.
The card images should be 2.5x3.5 (poker card ratio). You can set the number of rows/columns by
passing the --sides argument (defaults to 3x3 on each page).
If there are more front cards than back cards, the last back card will be duplicated for the remaining unmatched front cards.
cards --cards-path <PATH> [OPTIONS]
Options:
-c, --cards-path <PATH> Path to the folder containing card images (required)
-o, --output <FILE> Output PDF filename [default: cards.pdf]
-s, --sides <N> Grid size (e.g., 3 for 3x3) [default: 3]
-v, --verbose Show progress messages
-h, --help Print help# Generate 3x3 grid PDF
./target/release/cards --cards-path path/to/cards --output cards.pdf
# Generate 5x5 grid PDF with verbose output
./target/release/cards --cards-path path/to/cards --output cards.pdf --sides 5 --verboseuse cards_core::CardWriter;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let writer = CardWriter::new("path/to/cards".to_string(), 3);
writer.create_pdf("output.pdf")?;
println!("PDF created successfully!");
Ok(())
}use cards_core::CardWriter;
fn generate_pdf_response(cards_path: String) -> Result<Vec<u8>, cards_core::CardsError> {
let writer = CardWriter::new(cards_path, 3);
let pdf_bytes = writer.generate_pdf_bytes()?;
Ok(pdf_bytes)
}use cards_core::CardWriter;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Configure logging (optional)
env_logger::init();
let writer = CardWriter::new("path/to/cards".to_string(), 3);
writer.create_pdf("output.pdf")?;
Ok(())
}use cards_core::{CardWriter, CardsError};
fn create_cards() -> Result<(), CardsError> {
let writer = CardWriter::new("path/to/cards".to_string(), 3);
match writer.create_pdf("output.pdf") {
Ok(_) => println!("Success!"),
Err(CardsError::DirectoryNotFound(path)) => {
eprintln!("Directory not found: {}", path);
},
Err(CardsError::ImageDecodeError(msg)) => {
eprintln!("Failed to decode image: {}", msg);
},
Err(e) => eprintln!("Error: {}", e),
}
Ok(())
}
