Squib Documentation

Welcome to the official Squib documentation!

Contents:

Install & Update

Squib is a Ruby gem, and installation is handled like most gems.

Pre-requisites

Squib works with both x86 and x86_64 versions of Ruby.

On Windows, we recommend using RubyInstaller. Use the version with DevKit.

Typical Install

Regardless of your OS, installation is

$ gem install squib

If you’re using Bundler, add this line to your application’s Gemfile:

gem 'squib'

And then execute:

$ bundle install

Squib has some native dependencies, such as Cairo, Pango, and Nokogiri, which will compile upon installation - this is normal.

Updating Squib

At this time we consider Squib to be still in initial development, so we are not supporting older versions. Please upgrade your Squib as often as possible.

To keep track of when new Squib releases come out, you can watch the BoardGameGeek thread or follow the RSS feed for Squib on its RubyGems page.

In RubyGems, the command looks like this:

$ gem up squib

As a quirk of Ruby/RubyGems, sometimes older versions of gems get caught in caches. You can see which versions of Squib are installed and clean them up, use gem list and gem cleanup:

$ gem list squib

*** LOCAL GEMS ***

squib (0.9.0, 0.8.0)

$ gem cleanup squib
Cleaning up installed gems...
Attempting to uninstall squib-0.8.0
Successfully uninstalled squib-0.8.0
Clean Up Complete

This will remove all prior versions of Squib.

As a sanity check, you can see what version of Squib you’re using by referencing the Squib::VERSION constant:

require 'squib'
puts Squib::VERSION

OS-Specific Quirks

See the wiki for idiosyncracies about specific operating systems, dependency clashes, and other installation issues. If you’ve run into issues and solved them, please post your solutions for others!

Learning Squib

Hello, World! Dissected

After seeing Squib’s landing page, your might find it helpful to dissect what’s really going on in each line of code of a basic Squib snippet.

1
2
3
4
5
6
7
8
require 'squib'

Squib::Deck.new width: 825, height: 1125, cards: 2 do
  background color: 'pink'
  rect
  text str: ['Hello', 'World!']
  save_png prefix: 'hello_'
end

Let’s dissect this:

  • Line 1: this code will bring in the Squib library for us to use. Keep this at the top.
  • Line 2: By convention, we put a blank line between our require statements and the rest of our code
  • Line 3: Define a new deck of cards. Just 2 cards. 825 pixels wide etc. Squib also supports Unit Conversion if you prefer to specify something like '2.75in'.
  • Line 4: Set the background to pink. Colors can be in various notations, and supports linear and radial graidents - see Specifying Colors & Gradients.
  • Line 5: Draw a rectangle around the edge of each card. Note that this has no arguments, because Parameters are Optional. The defaults can be found in the DSL reference for the rect method.
  • Line 6: Put some text in upper-left the corner of the card, using the default font, etc. See the text DSL method for more options. The first card will have 'Hello' and the second card will have 'World' because Squib Thinks in Arrays.
  • Line 7: Save our card out to a png files called hello_00.png and hello_01.png saved in the _output folder.

Dissection of “Even Bigger…”

On Squib’s landing page we end with a pretty complex example. It’s compact and designed to show how much you can get done with a little bit of code. Here’s a dissection of that.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
require 'squib'

Squib::Deck.new(cards: 4, layout: %w(hand.yml even-bigger.yml)) do
  background color: '#230602'
  deck = xlsx file: 'even-bigger.xlsx'
  svg file: deck['Art'], layout: 'Art'

  %w(Title Description Snark).each do |key|
    text str: deck[key], layout: key
  end

  %w(Attack Defend Health).each do |key|
    svg file: "#{key.downcase}.svg", layout: "#{key}Icon"
    text str: deck[key], layout: key
  end

  save_png prefix: 'even_bigger_'
  showcase file: 'showcase.png', fill_color: '#0000'
  hand file: 'hand.png', trim: 37.5, trim_radius: 25, fill_color: '#0000'
end
  • Line 3: Make 4 cards. Use two layouts: the built-in hand.yml (see Layouts are Squib’s Best Feature) and then our own layout. The layouts get merged, with our own even-bigger.yml overriding hand.yml whenever they collide.
  • Line 5: Read some data from an Excel file, which amounts to a column-based hash of arrays, so that each element of an array corresponds to a specific data point to a given card. For example, 3 in the 'Attack' column will be put on the second card.
  • Line 6: Using the Excel data cell for the filename, we can customize a different icon for every card. But, every SVG in this command will be styled according to the Art entry in our layout (i.e. in even-bigger.yml)
  • Line 8: Iterate over an array of strings, namely, 'Title', 'Description', and 'Snark'.
  • Line 9: Draw text for the (Title, Description, or Snark), using their styling rules in our layout.
  • Line 13: Using Ruby String interpolation, lookup the appropriate icon (e.g. 'attack.svg'), converted to lowercase letters, and then using the Icon layout of that for styling (e.g. 'AttackIcon' or 'DefendIcon')
  • Line 17: Render every card to individual PNG files
  • Line 18: Render a “showcase” of cards, using a perspective-reflect effect. See showcase method.
  • Line 19: Render a “hand” of cards (spread over a circle). See hand method.

The Squib Way pt 0: Learning Ruby

This guide is for folks who are new to coding and/or Ruby. Feel free to skip it if you already have some coding experience.

Not a Programmer?

I’m not a programmer, but I want to use Squib. Can you make it easy for non-programmers?

Frequently Asked Question

If you want to use Squib, then you want to automate the graphics generation of a tabletop game in a data-driven way. You want to be able to change your mind about icons, illustrations, stats, and graphic design - then rebuild your game with a just a few keystrokes. Essentially, you want to give a list of instructions to the computer, and have it execute your bidding.

If you want those things, then I have news for you. I think you are a programmer… who just needs to learn some coding. And maybe Squib will finally be your excuse!

Squib is a Ruby library. To learn Squib, you will need to learn Ruby. There is no getting around that fact. Don’t fight it, embrace it.

Fortunately, Squib doesn’t really require tons of Ruby-fu to get going. You can really just start from the examples and go from there. And I’ve done my best to keep to Ruby’s own philosophy that programming in it should be a delight, not a chore.

Doubly fortunately,

  • Ruby is wonderfully rich in features and expressive in its syntax.
  • Ruby has a vibrant, friendly community with people who love to help. I’ve always thought that Ruby people and board game people would be good friends if they spent more time together.
  • Ruby is the language of choice for many new programmers, including many universities.
  • Ruby is also “industrial strength”, so it really can do just about anything you need it to.

Plus, resources for learning how to code are ubiquitous on the Internet.

In this article, we’ll go over some topics that you will undoubtedly want to pick up if you’re new to programming or just new to Ruby.

What You DON’T Need To Know about Ruby for Squib

Let’s get a few things out of the way. When you are out there searching the interwebs for solutions to your problems, you will not need to learn anything about the following things:

  • Rails. Ruby on Rails is a heavyweight framework for web development. It’s awesome in its own way, but it’s not relevant to learning Ruby as a language by itself. Squib is about scripting, and will never (NEVER!) be a web app.
  • Object-Oriented Programming. While OO is very important for developing long-term, scalable applications, some of the philosophy around “Everything in Ruby is an object” can be confusing to newcomers. It’s not super-important to grasp this concept for Squib. This means material about classes, modules, mixins, attributes, etc. are not really necessary for Squib scripts. (Contributing to Squib, that’s another matter - we use OO a lot internally.)
  • Metaprogramming. Such a cool thing in Ruby… don’t worry about it for Squib. Metaprogramming is for people who literally sleep better at night knowing their designs are extensible for years of software development to come. You’re just trying to make a game.

What You Need to Know about Ruby for Squib

I won’t give you an introduction to Ruby - other people do that quite nicely (see Resources at the bottom of this article). Instead, as you go through learning Ruby, you should pay special attention to the following:

  • Comments
  • Variables
  • require
  • What do and end mean
  • Arrays, particularly since most of Squib’s arguments are usually Arrays
  • Strings and symbols
  • String interpolation
  • Hashes are important, especially for Excel or CSV importing
  • Editing Yaml. Yaml is not Ruby per se, but it’s a data format common in the Ruby community and Squib uses it in a couple of places (e.g. layouts and the configuration file)
  • Methods are useful, but not immediately necessary, for most Squib scripts.

If you are looking for some advanced Ruby-fu, these are useful to brush up on:

  • Enumerable - everything you can do with iterating over an Array, for example
  • map - convert one Array to another
  • zip - combine two arrays in parallel
  • inject - process one Enumerable and build up something else

Find a good text editor

The text editor is a programmer’s most sacred tool. It’s where we live, and it’s the tool we’re most passionate (and dogmatic) about. My personal favorite editors are SublimeText and Atom. There are a bajillion others. The main things you’ll need for editing Ruby code are:

  • Line numbers. When you get an error, you’ll need to know where to go.
  • Monospace fonts. Keeping everything lined up is important, especially in keeping indentation.
  • Syntax highlighting. You can catch all kinds of basic syntax mistakes with syntax highlighting. My personal favorite syntax highlighting theme is Monokai.
  • Manage a directory of files. Not all text editors support this, but Sublime and Atom are particularly good for this (e.g. Ctrl+P can open anything!). Squib is more than just the deck.rb - you’ve got layout files, a config file, your instructions, a build file, and a bunch of other stuff. Your editor should be able to pull those up for you in a few keystrokes so you don’t have to go all the way over to your mouse.

There are a ton of other things that these editors will do for you. If you’re just starting out, don’t worry so much about customizing your editor just yet. Work with it for a while and get used to the defaults. After 30+ hours in the editor, only then should you consider installing plugins and customizing options to fit your preferences.

Command line basics

Executing Ruby is usually done through the command line. Depending on your operating system, you’ll have a few options.

  • On Macs, you’ve got the Terminal, which is essentially a Unix shell in Bash (Bourne-Again SHell). This has an amazing amount of customization possible with a long history in the Linux/Unix/BSD world.
  • On Windows, there’s the Command Prompt (Windows Key, cmd). It’s a little janky, but it’ll do. I’ve developed Squib primarily in Windows using the Command Prompt.
  • If you’re on Linux/BSD/etc, you undoubtedly know what the command line is.

For example:

$ cd c:\game-prototypes
$ gem install squib
$ squib new tree-gnome-blasters
$ ruby deck.rb
$ rake
$ bundle install
$ gem up squib

This might seem arcane at first, but the command line is the single most powerful and expressive tool in computing… if you know how to harness it.

Edit-Run-Check.

To me, the most important word in all of software development is incremental. When you’re climbing up a mountain by yourself, do you wait to put in anchors until you reach the summit? No!! You anchor yourself along the way frequently so that when you fall, you don’t fall very far.

In programming, you need to be running your code often. Very often. In an expressive language like Ruby, you should be running your code every 60-90 seconds (seriously). Why? Because if you make a mistake, then you know that you made it in the last 60-90 seconds, and your problem is that much easier to solve. Solving one bug might take two minutes, but solving three bugs at once will take ~20 minutes (empirical studies have actually backed this up exponentiation effect).

How much code can you write in 60-90 seconds? Maybe 1-5 lines, for fast typists. Think of it this way: the longer you go without running your code, the more debt you’re accruing because it will take longer to fix all the bugs you haven’t fixed yet.

That means your code should be stable very often. You’ll pick up little tricks here and there. For example, whenever you type a (, you should immediately type a ) afterward and edit in the middle (some text editors even do this for you). Likewise, after every do you should type end (that’s a Ruby thing). There are many, many more. Tricks like that are all about reducing what you have to remember so that you can keep your code stable.

With Squib, you’ll be doing one other thing: checking your output. Make sure you have some specific cards to check constantly to make sure the card is coming out the way you want. The Squib method save_png (or ones like it) should be one of the first methods you write when you make a new deck.

As a result of all these, you’ll have lots of windows open when working with Squib. You’ll have a text editor to edit your source code, your spreadsheet (if you’re working with one), a command line prompt, and a preview of your image files. It’s a lot of windows, I know. That’s why computer geeks usually have multiple monitors!

So, just to recap: your edit-run-check cycle should be very short. Trust me on this one.

Plan to Fail

If you get to a point where you can’t possibly figure out what’s going on that means one thing.

You’re human.

Everyone runs into bugs they can’t fix. Everyone. Take a break. Put it down. Talk about it out loud. And then, of course, you can always Get Help and Give Help.

Ruby Learning Resources

Here are some of my favorite resources for getting started with Ruby. A lot of them assume you are also new to programming in general. They do cover material that isn’t very relevant to Squib, but that’s okay - learning is never wasted, only squandered.

RubyMonk.com
An interactive explanation through Ruby. Gets a bit philosophical, but hey, what else would you expect from a monk?
Pragmatic Programmer’s Guide to Ruby (The PickAxe Book)
One of the best comprehensive resources out there for Ruby - available for free!
Ruby’s Own Website: Getting Started
This will take you through the basics of programming in Ruby. It works mostly from the Interactive Ruby shell irb, which is pretty helpful for seeing how things work and what Ruby syntax looks like.
Why’s Poignant Guide to Ruby
No list of Ruby resources is complete without a reference to this, well, poignant guide to Ruby. Enjoy.
The Pragmatic Programmer
The best software development book ever written (in my opinion). If you are doing programming and you have one book on your shelf, this is it. Much of what inspired Squib came from this thinking.

The Squib Way pt 1: Zero to Game

I’ve always felt that the Ruby community and the tabletop game design community had a lot in common, and a lot to learn from each other. Both are all about testing. All about iterative development. Both communities are collegial, creative, and fun.

But the Ruby community, and the software development community generally, has a lot to teach us game designers about how to develop something. Ruby has a “way” of doing things that is unique and helpful to game designers.

In this series of guides, I’ll introduce you to Squib’s key features and I’ll walk you through a basic prototype. We’ll also take a more circuitous route than normal so that I can touch upon some key design principles and good software development habits so that you can make your Squib scripts maintainable, understandable, flexible, and changeable.

Prototyping with Squib

Squib is all about being able to change your mind quickly. Change data, change layout, change artwork, change text. But where do we start? What do we work on first?

The key to prototyping tabletop games is playtesting. At the table. With humans. Printed components. That means that we need to get our idea out of our brains and onto pieces of paper as fast as possible.

But! We also want to get the second (and third and fourth and fifth…) version of our game back to the playtesting table quickly, too. If we work with Squib from day one, our ability to react to feedback will be much smoother once we’ve laid the groundwork.

Get Installed and Set Up

The ordinary installation is like most Ruby gems:

$ gem install squib

See Install & Update for more details.

This guide also assumes you’ve got some basic Ruby experience, and you’ve got your tools set up (i.e. text editor, command line, image preview, etc). See The Squib Way pt 0: Learning Ruby to see my recommendations.

Our Idea: Familiar Fights

Let’s start with an idea for a game: Familiar Fights. Let’s say we want to have players fight each other based on collecting cards that represent their familiars, each with different abilities. We’ll have two factions: drones and humans. Each card will have some artwork in it, and some text describing their powers.

First thing: the title. It stinks, I know. It’s gonna change. So instead of naming the directory after our game and getting married to our bad idea, let’s give our game a code name. I like to use animal names, so let’s go with Arctic Lemming:

$ squib new arctic-lemming
$ cd arctic-lemming
$ ls
ABOUT.md      Gemfile         PNP NOTES.md    Rakefile        _output         config.yml      deck.rb         layout.yml

Go ahead and put “Familiar Fights” as an idea for a title in the IDEAS.md file.

If you’re using Git or other version control, now’s a good time to commit. See Squib + Git.

Running Your Squib Build

The simplest way to build with Squib is to run this command line:

$ ruby deck.rb

Squib cares about which directory you are currently in. For example, it will create that _output directory in the current directory, and it will look up files according to your current directory.

An alternative to running the ruby command directly is to use Ruby’s Rake build system. Rakefiles are designed for building projects that have lots of files (that’s us!). The default Rakefile that Squib generates simply runs the deck.rb. To use Rake, you run this from this directory or any subdirectory.

$ rake

We’ll discuss Rake and various other workflow things like auto-building in the The Squib Way pt 3: Workflows.

Data or Layout?

From a prototyping standpoint, we really have two directions we can work from:

  • Laying out an example card
  • Working on the deck data

There’s no wrong direction here - we’ll need to do both to get our idea onto the playtesting table. Go where your inspiration guides you. For this example, let’s say I’ve put together ideas for four cards. Here’s the data:

name faction power
Ninja human Use the power of another player
Pirate human Steal 1 card from another player
Zombie drone Take a card from the discard pile
Robot drone Draw two cards

If you’re a spreadsheet person, go ahead and put this into Excel (in the above format). Or, if you want to be plaintext-friendly, put it into a comma-separated format (CSV). Like this:

Initial Card Layout

Ok let’s get into some code now. Here’s an “Hello, World” code snippet

Let’s dissect this:

  • Line 1: this code will bring in the Squib library for us to use. Keep this at the top.
  • Line 2: By convention, we put a blank line between our require statements and the rest of our code
  • Line 3: Define a new deck of cards. Just 1 card for now
  • Line 4: Set the background to pink. Colors can be in various notations - see Specifying Colors & Gradients.
  • Line 5: Draw a rectangle around the edge of the deck. Note that this has no arguments, because Parameters are Optional.
  • Line 6: Put some text in upper-left the corner of the card.
  • Line 7: Save our card out to a png file called card_00.png. Ordinarily, this will be saved to _output/card_00.png, but in our examples we’ll be saving to the current directory (because this documentation has its examples as GitHub gists and gists don’t have folders - I do not recommend having dir: '.' in your code)

By the way, this is what’s created:

Now let’s incrementally convert the above snippet into just one of our cards. Let’s just focus on one card for now. Later we’ll hook it up to our CSV and apply that to all of our cards.

You may have seen in some examples that we can just put in x-y coordinates into our DSL method calls (e.g. text x: 0, y: 100). That’s great for customizing our work later, but we want to get this to the table quickly. Instead, let’s make use of Squib’s feature (see Layouts are Squib’s Best Feature).

Layouts are a way of specifying some of your arguments in one place - a layout file. The squib new command created our own layout.yml file, but we can also use one of Squib’s built-in layout files. Since we just need a title, artwork, and description, we can just use economy.yml (inspired by a popular deck builder that currently has dominion over the genre). Here’s how that looks:

There are a few key decisions I’ve made here:

  • Black-and-white. We’re now only using black or white so that we can be printer-friendly.
  • Safe and Cut. We added two rectangles for guides based on the poker card template from TheGameCrafter.com. This is important to do now and not later. In most print-on-demand templates, we have a 1/8-inch border that is larger than what is to be used, and will be cut down (called a bleed). Rather than have to change all our coordinates later, let’s build that right into our prototype. Squib can trim around these bleeds for things like showcase, hand, save_sheet, save_png, and save_pdf. See Always Have Bleed.
  • Title. We added a title based on our data.
  • layout: ‘foo’. Each command references a “layout” rule. These can be seen in our layout file, which is a built-in layout called economy.yml (see ours on GitHub ). Later on, we can define our own layout rules in our own file, but for now we just want to get our work done as fast as possible and make use of the stock layout. See Layouts are Squib’s Best Feature.

Multiple Cards

Ok now we’ve got a basic card. But we only have one. The real power of Squib is the ability to customize things per card. So if we, say, want to have two different titles on two different cards, our text call will look like this:

text str: ['Zombie', 'Robot'], layout: 'title'

When Squib gets this, it will:

  • See that the str: option has an array, and put 'Zombie' on the first card and 'Robot' on the second.
  • See that the layout: option is NOT an array - so it will use the same one for every card.

So technically, these two lines are equivalent:

text str: ['Zombie', 'Robot'], layout: 'title'
text str: ['Zombie', 'Robot'], layout: ['title','title']

Ok back to the game. We COULD just put our data into literal arrays. But that’s considered bad programming practice (called hardcoding, where you put data directly into your code). Instead, let’s make use of our CSV data file.

What the csv command does here is read in our file and create a hash of arrays. Each array is a column in the table, and the header to the colum is the key to the hash. To see this in action, check it out on Ruby’s interactive shell (irb):

$ irb
2.1.2 :001 > require 'squib'
 => true
2.1.2 :002 > Squib.csv file: 'data.csv'
 => {"name"=>["Ninja", "Pirate", "Zombie", "Robot"], "class"=>["human", "human", "drone", "drone"], "power"=>["Use the power of another player", "Steal 1 card from another player", "Take a card from the discard pile", "Draw two cards"]}

So, we COULD do this:

require 'squib'

Squib::Deck.new cards: 4, layout: 'economy.yml' do
  data = csv file: 'data.csv'
  #rest of our code
end

BUT! What if we change the number of total cards in the deck? We won’t always have 4 cards (i.e. the number 4 is hardcoded). Instead, let’s read in the data outside of our Squib::Deck.new and then create the deck size based on that:

require 'squib'

data = Squib.csv file: 'data.csv'

Squib::Deck.new cards: data['name'].size, layout: 'economy.yml' do
  #rest of our code
end

So now we’ve got our data, let’s replace all of our other hardcoded data from before with their corresponding arrays:

Awesome! Now we’ve got our all of our cards prototyped out. Let’s add two more calls before we bring this to the table:

  • save_pdf that stitches our images out to pdf
  • A version number, based on today’s date

The file _output/output.pdf gets created now. Note that we don’t want to print out the bleed area, as that is for the printing process, so we add a 1/8-inch trim (Squib defaults to 300ppi, so 300/8=37.5). The save_pdf defaults to 8.5x11 piece of landscape paper, and arranges the cards in rows - ready for you to print out and play!

If you’re working with version control, I recommend committing multiple times throughout this process. At this stage, I recommend creating a tag when you are ready to print something out so you know what version precisely you printed out.

To the table!

Squib’s job is done, for at least this prototype anyway. Now let’s print this sheet out and make some cards!

My recommended approach is to get the following:

  • A pack of standard sized sleeves, 2.5”x3.5”
  • Some cardstock to give the cards some spring
  • A paper trimmer, rotary cutter, knife+steel ruler - some way to cut your cards quickly.

Print your cards out on regular office paper. Cut them along the trim lines. Also, cut your cardstock (maybe a tad smaller than 2.5x3.5) and sleeve them. I will often color-code my cardstock backs in prototypes so I can easily tell them apart. Put the cards into the sleeves. You’ve got your deck!

Now the most important part: play it. When you think of a rule change or card clarification, just pull the paper out of the sleeve and write on the card. These card print-outs are short-lived anyway.

When you playtest, take copious notes. If you want, you can keep those notes in the PLAYTESTING.md file.

Next up…

We’ve got a long way to go on our game. We need artwork, iconography, more data, and more cards. We have a lot of directions we could go from here, so in our next guide we’ll start looking at a variety of strategies. We’ll also look at ways we can keep our code clean and simple so that we’re not afraid to change things later on.

The Squib Way pt 2: Iconography

In the previous guide, we walked you through the basics of going from ideas in your head to a very simple set of cards ready for playtesting at the table. In this guide we take the next step: creating a visual language.

Art: Graphic Design vs. Illustration

A common piece of advice in the prototyping world is “Don’t worry about artwork, just focus on the game and do the artwork later”. That’s good advice, but a bit over-simplified. What folks usually mean by “artwork” is really “illustration”, like the oil painting of a wizard sitting in the middle of the card or the intricate border around the edges.

But games are more than just artwork with text - they’re a system of rules that need to be efficiently conveyed to the players. They’re a visual language. When players are new to your game, the layout of the cards need to facilitate learning. When players are competing in their 30th game, though, they need the cards to maximize usability by reducing their memory load, moving gameplay along efficiently, and provide an overall aesthetic that conveys the game theme. That’s what graphic design is all about, and requires a game designer’s attention much more than commissioning an illustration.

Developing the visual language on your cards is not a trivial task. It’s one that takes a lot of iteration, feedback, testing, improvement, failure, small successes, and reverse-engineering. It’s something you should consider in your prototype early on. It’s also a series of decisions that don’t necessarily require artistic ability - just an intentional effort applied to usability.

Icons and the their placement are, perhaps, the most efficient and powerful tools in your toolbelt for conveying your game’s world. In the prototyping process, you don’t need to be worried about using icons that are your final icons, but you should put some thought into what the visuals will look like because you’ll be iterating on that in the design process.

How Squib Supports Iconography

Squib is good for supporting any kind of layout you can think of, but it’s also good for supporting multiple ways of translating your data into icons on cards. Here are some ways that Squib provides support for your ever-evolving iconography:

  • svg method, and all of its features like scaling, ID-specific rendering, direct XML manipulation, and all that the SVG file format has to offer
  • png method, and all of its features like blending operators, alpha transparency, and masking
  • Layout files allow multiple icons for one data column (see Layouts are Squib’s Best Feature)
  • Layout files also have the extends feature that allows icons to inherit details from each other
  • The range option on text, svg, and png allows you to specify text and icons for any subset of your cards
  • The text method allows for embedded icons.
  • The text method allows for Unicode characters (if the font supports it).
  • Ruby provides neat ways of aggregating data with inject, map, and zip that gives you ultimate flexibility for specifying different icons for different cards.

Back to the Example: Drones vs. Humans

Ok, let’s go back to our running example, project arctic-lemming from Part 1. We created cards for playtesting, but we never put down the faction for each card. That’s a good candidate for an icon.

Let’s get some stock icons for this exercise. For this example, I went to http://game-icons.net. I set my foreground color to black, and background to white. I then downloaded “auto-repair.svg” and “backup.svg”. I’m choosing not to rename the files so that I can find them again on the website if I need to. (If you want to know how to do this process DIRECTLY from Ruby, and not going to the website, check out my other Ruby gem called game_icons - it’s tailor-made for Squib! We’ve got some documentation in Squib + Game-Icons.net

When we were brainstorming our game, we placed one category of icons in a single column (“faction”). Presumably, one would want the faction icon to be in the same place on every card, but a different icon depending on the card’s faction. There are a couple of ways of accomplishing this in Squib. First, here some less-than-clean ways of doing it:

svg range: 0, file: 'auto_repair.svg' # hard-coded range number? not flexible
svg range: 1, file: 'auto_repair.svg' # hard-coded range number? not flexible
svg range: 2, file: 'backup.svg'      # hard-coded range number? not flexible
svg range: 3, file: 'backup.svg'      # hard-coded range number? not flexible
# This gets very hard to maintain over time
svg file: ['auto_repair.svg', 'auto_repair.svg', 'backup.svg', 'backup.svg']
# This is slightly easier to maintain, but is more verbose and still hardcoded
svg range: 0..1, file 'auto_repair.svg'
svg range: 2..3, file 'backup.svg'

That’s too much hardcoding of data into our Ruby code. That’s what layouts are for. Now, we’ve already specified a layout file in our prior example. Fortunately, Squib supports multiple layout files, which get combined into a single set of layout styles. So let’s do that: we create our own layout file that defines what a human is and what a drone is. Then just tell svg to use the layout data. The data column is simply an array of factions, the icon call is just connecting the factions to their styles with:

svg layout: data['faction']

So, putting it all together, our code looks like this.

BUT! There’s a very important software design principle we’re violating here. It’s called DRY: Don’t Repeat Yourself. In making the above layout file, I hit copy and paste. What happens later when we change our mind and want to move the faction icon!?!? We have to change TWO numbers. Blech.

There’s a better way: extends

The layout files in Squib also support a special keyword, extends, that allows us to “copy” (or “inherit”) another style onto our own, and then we can override as we see fit. Thus, the following layout is a bit more DRY:

Much better!

Now if we want to add a new faction, we don’t have to copy-pasta any code! We just extend from faction and call in our new SVG file. Suppose we add a new faction that needs a bigger icon - we can define our own width and height beneath the extends that will override the parent values of 75.

Looks great! Now let’s get these cards out to the playtesting table!

At this point, we’ve got a very scalable design for our future iterations. Let’s take side-trip and discuss why this design works.

Why Ruby+YAML+Spreadsheets Works

In software design, a “good” design is one where the problem is broken down into a set of easier duties that each make sense on their own, where the interaction between duties is easy, and where to place new responsibilities is obvious.

In Squib, we’re using automation to assist the prototyping process. This means that we’re going to have a bunch of decisions and responsibilities, such as:

  • Game data decisions. How many of this card should be in the deck? What should this card be called? What should the cost of this card be?
  • Style Decisions. Where should this icon be? How big should the font be? What color should we use?
  • Logic Decisions. Can we build this to a PDF, too? How do we save this in black-and-white? Can we include a time stamp on each card? Can we just save one card this time so we can test quickly?

With the Ruby+YAML+Spreadsheets design, we’ve separated these three kinds of questions into three areas:

  • Game data is in a spreadsheet
  • Styles are in YAML layout files
  • Code is in Ruby

When you work with this design, you’ll probably find yourself spending a lot of time working on one of these files for a long time. That means this design is working.

For example, you might be adjusting the exact location of an image by editing your layout file and re-running your code over and over again to make sure you get the exact x-y coordinates right. That’s fine. You’re not making game data decisions in that moment, so you shouldn’t be presented with any of that stuff. This eases the cognitive complexity of what you’re doing.

The best way to preserve this design is to try to keep the Ruby code clean. As wonderful as Ruby is, it’s the hardest of the three to edit. It is code, after all. So don’t clutter it up with game data or style data - let it be the glue between your styles and your game.

Ok, let’s get back to this prototype.

Illustration: One per Card

The cards are starting to come together, but we have another thing to do now. When playtesting, you need a way of visually identifying the card immediately. Reading text takes an extra moment to identify the card - wouldn’t it be nice if we had some sort of artwork, individualized to the card?

Of course, we’re not going to commission an artist or do our own oil paintings just yet. Let’s get some placeholder art in there. Back to GameIcons, we’re going to use “ninja-mask.svg”, “pirate-skull.svg”, “shambling-zombie.svg”, and “robot-golem.svg”.

Method 1: Put the file name in data

The difference between our Faction icon and our Illustration icon is that the Illustration needs to be different for every card. We already have a convenient way to do something different on every card - our CSV file!

Here’s how the CSV would look:

In our layout file we can center it in the middle of the card, nice and big. And then the Ruby & YAML would look like this:

And our output will look like this:

Method 2: Map title to file name

There are some drawbacks to Method 1. First, you’re putting artwork graphics info inside your game data. This can be weird and unexpected for someone new to your code (i.e. that person being you when you put your project down for a few months). Second, when you’re working on artwork you’ll have to look up what the name of every file is in your CSV. (Even writing this tutorial, I forgot that “zombie” is called “shambling-zombie.svg” and had to look it up, distracting me from focusing on writing.)

There’s another way of doing this, and it’s more Ruby-like because it follows the Convention over Configuration philosophy. The idea is to be super consistent with your naming so that you don’t have to configure that, say, “pirate” has an illustration “pirate-skull”. The illustration should be literally the title of the card - converted to lowercase because that’s the convention for files. That means it should just be called “pirate.svg”, and Squib should know to “just put an SVG that is named after the title”. Months later, when you want to edit the illustration for pirate, you will probably just open “pirate.svg”.

To do this, you’ll need to convert an array of Title strings from your CSV (data['title'] to an array of file names. Ruby’s map was born for this.

Note

If you’re new to Ruby, here’s a quick primer. The map method gets run on every element of an array, and it lets you specify a block (either between curly braces for one line or between do and end for multiple lines). It then returns another Array of the same size, but with every value mapped using your block. So:

[1, 2, 3].map { |x| 2 * x }             # returns [2, 4, 6]
[1, 2, 3].map { |x| "$#{x}" }           # returns ["$1", "$2", "$3"]
['NARF', 'ZORT'].map { |x| x.downcase } # returns ['narf', 'zort']

Thus, if we rename our illustration files from “pirate-skull.svg” to “pirate.svg”, we can have CSV data that’s JUST game data:

And our Ruby code will figure out the file name:

And our output images look identical to Method 1.

Learn by Example

In my game, Your Last Heist, I use some similar methods as above:

Are We Done?

At this stage, you’ve got most of what you need to build a game from prototype through completion. Between images and text, you can do pretty much anything. Squib does much more, of course, but these are the basic building blocks.

But, prototyping is all about speed and agility. The faster you can get what you need, the sooner you can playtest, and the sooner you can make it better. Up next, we’ll be looking at workflow: ways to help you get what you need quickly and reliably.

The Squib Way pt 3: Workflows

Warning

Under construction

As we mentioned at the end The Squib Way pt 2: Iconography, we’ve pretty much got most of what we need to prototype a game through completion. From here on out, the DSL Reference will be your best resource for getting things done.

What follows from here out is optional, but useful. As you explore Squib’s features and work away at your games, you’ll pick up a few tricks and conventions to follow that makes your time easier. For me personally, I’ve been using Squib from the beginning side-by-side with developing my own games. After 3+ years of developing games with Squib, here are some helpful ways of improving your workflow.

Improving your workflow comes down to a few principles:

  • Automate the tedious only. There’s a balance here. What do you anticipate will change about your game as you develop it? What do you anticipate will not change? If you automate everything, you will probably spend more time on automating than on game development. If you don’t automate anything, you’ll be re-making every component whenever you make a game design change and Squib will be of no value to you.
  • Focus on one thing only: visuals, game, or build. Cognitively, you’ll have an easier time when you focus on one thing and one thing only. The more loose ends you need to keep in your head, the more mistakes you’ll make.

Additionally, improving your workflow can help you pivot to other tasks you might need for polishing your game later on, such as:

  • Quickly building one card at a time to reduce the time between builds
  • Auto-building your code so you can make minor adjustments and see their results
  • Handling front-to-back printing
  • Maintaining a printer-friendly, black-and-white version of your game in tandem with a color version
  • Building annotated figures for your rulebook
  • Maintaining a changelog for your playtesters who need to update their games remotely
  • Rolling back to older versions of your game

Organizing Your Project

Most games involve building multiple decks. Initially, you might think to put all of your Ruby code inside one file. That can work, but your development time will slow down. You’ll be constantly scrolling around to find what you want instead of jumping to what you need.

Instead, I like to organize my code into separate source code files inside of a src directory. The squib new has an --advanced option that will create a more scalable project layout for you. Take a look at how that works. In practice, the vast majority of my own game designs follow this kind of file structure.

Retro-fitting an existing project into the advanced layout can be a little tedious, and we would like to automate this someday.

Rakefile: Your Project’s Butler

Programming is more than simply writing and executing one a program at a time. It’s about managing lots of files and programs at once so that you can do whatever you want, whenever you want. “Building” is the software development word for this, and every programming language has some version of this tool.

In Ruby, this tool is called rake. (A pun on the popular tool for C, called make.) The way that rake is configured is by executing a special Ruby file called a Rakefile at the root of your repository. Use rake -T or rake -T [pattern] to quickly list available rake tasks.

Consider the following example from our built-in advanced project generator:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
require 'squib'
require 'irb'
require 'rake/clean'

# Add Rake's clean & clobber tasks
CLEAN.include('_output/*').exclude('_output/gitkeep.txt')

desc 'By default, just build the deck without extra options'
task default: [:deck]

desc 'Build everything, with all the options'
task all: [:with_pnp, :with_proofs, :deck]

desc 'Build the deck'
task(:deck)     { load 'src/deck.rb' }

desc 'Enable proof lines'
task(:with_proofs) do
  puts "Enabling proofing lines."
  Squib.enable_build_globally :proofs
end

desc 'Enable print-and-play builds'
task(:with_pnp) do
  puts "Enabling print-and-play builds."
  Squib.enable_build_globally :pnp
end

Ideas to be written up

  • Setting up rake tasks
  • Advanced project layout: splitting out decks into different files
  • Testing individual files (build groups, ranges, id-lookup)
  • Marketing images (using output as images, dependency in Rakefile)
  • Rulebook figures (build groups, annotate after saving)
  • Switch from built-in layouts to your own layout
  • Launch what you need with Launchy
  • Auto-building with Guard
  • Maintaining color and black-and-white builds (build groups, multiple layout files). Changing build sessions within Guard
  • Configuring different things for each build
  • Git (save to json, tagging, rolling back, Gemfile.lock)
  • Buliding on Travis and releasing on GitHub

The Squib Way pt 4: Ruby Power!

Warning

To be written.

This part is about cataloging some powerful things you can do if you’re willing to write some Ruby.

  • Modifying XML at runtime (e.g. convert to black-and-white from color)
  • Using Travis to build and then post to something like Dropbox
  • Scaling the size of text based on its contents
  • Advanced Array techniques: inject, transpose, map, join (use the pre-req example)
  • Building newlines yourself (i.e. with your own placeholder like “%n” in Your Last Heist)
  • Summarization card backs for Your Last Heist as an example
  • “Lacks” string for Your Last Heist
  • Rules doc written in Markdown

Squib in Action

Squib is in use by a lot of people! You can learn a lot from looking at how a whole project is put together.

A good way to peruse Squib code is to search for Ruby files on GitHub that have the phrase require 'squib' in them. And these are the just the people who have decided to release their code open source!

My Projects

Here are some of my own board and card games that use Squib. They are all under “active” development, which means that sometimes I leave them alone for long periods of time ;)

  • Escape from Scrapland is a 9-card nano-game solo roguelike that I started July 2017. I’m also doing some video on it, found here.
  • Your Last Heist is a big-box cooperative game. Lots of really cool Squib things in here, including lots of Rake features, color+bw, and showing how skills can “level up” on their backs by diff’ing the stats in Squib. You can see what the components look like at the game website. Also: the best game I’ve ever made.
  • Victory Point Salad. A card-only game with lots and lots and lots of cards. Pretty straightforward as far as Squib usage goes, but it’s a good peek into how I like to use Squib. Also: the funniest game I’ve made.
  • Junk Land A game I made prior to making starting Squib, but then ported over to Squib while developing Squib. Uses some strange features of SVG, but also a good intro. Also: the scrappiest game I’ve made.

Note

Want to donate back to Squib? Volunteer to playtest these games :)

Open Source Projects using Squib

Poking around GitHub, here are a few sightings of Squib:

  • Ecovalia - a game rapidly prototyped in a hackathon. Squib is featured in their video!
  • Werewolf implemented and even uses GitHub releases!
  • Cult Following is a neat-looking project
  • Mad World uses CircleCI to build, even with some custom fonts.

Other Projects Using Squib

Here are some closed-source projects that use Squib:

Want yours here?

Create an issue on Github and ask for a link her and we’ll add it here!

Squib + Game-Icons.net

I believe that, in prototyping, you want to focus on getting your idea to the table as fast as possible. Artwork is the kind of thing that can wait for later iterations once you know your game is a good one.

But! Playtesting with just text is a real drag.

Fortunately, there’s this amazing project going on over at http://game-icons.net. They are in the process of building a massive library of gaming-related icons.

As a sister project to Squib, I’ve made a Ruby gem that interfaces with the Game Icons library. With this gem, you can access Game Icons files, and even manipulate them as they go into your game.

Here are some instructions for working with the Game Icons gem into Squib.

Install the Gem

To get the gem, do:

$ gem install game_icons

The library update frequently, so it’s a good idea to upgrade whenever you can.

$ gem up game_icons

If you are using Bundler, go ahead and put this in your Gemfile:

gem 'game_icons'

And then run bundle install to install it from there.

The game_icons gem has no required dependencies. However, if you want to manipulate the SVG

To begin using the gem, just require it:

require 'game_icons'

Find Your Icon

Game-Icons.net has a search engine with some great tagging. Find the icon that you need. The gem will need the “name” of your icon. You can get this easily from the URL. For example:

could be called:

'meat'
:meat

Symbols are okay too (really, anything that responds to to_s will suffice). Spaces are replaced with a dash:

'police-badge'
:police_badge

However, some icons have the same name but different authors. To differentiate these, you put the author name before a slash. Like this:

'lorc/meat'
'andymeneely/police-badge'

To get the Icon, you use GameIcons#get:

GameIcons.get(:meat)
GameIcons.get('lorc/meat')
GameIcons.get(:police_badge)
GameIcons.get('police-badge')
GameIcons.get('andymeneely/police-badge')

If you want to know all the icon names, you can always use:

GameIcons.names # returns the list of icon names

If you end up misspelling one, the gem will suggest one:

irb(main):005:0> GameIcons.get(:police_badg)
RuntimeError: game_icons: could not find icon 'police_badg'. Did you mean any of these? police-badge

Use the SVG File

If you just want to use the icon in your game, you can just use the file method:

svg file: GameIcons.get(:police-badge).file

Recolor the SVG file

The gem will also allow you to recolor the icon as you wish, setting foreground and background:

# recolor foreground and background to different shades of gray
svg data: GameIcons.get('glass-heart').
                    recolor(fg: '333', bg: 'ccc').
                    string

# recolor with opacity
svg data: GameIcons.get('glass-heart').
                    recolor(fg: '333', bg: 'ccc',
                            fg_opacity: 0.25, bg_opacity: 0.75).
                    string

Use the SVG XML Data

SVGs are just XML files, and can be manipulated in their own clever ways. GameIcons is super-consistent in the way they format their SVGs - the entire icon is flattened into one path. So you can manipulate how the icon looks in your own way. Here’s an example of using straight string substitution:

svg data: GameIcons.get(:meat).string.gsub('fill="#fff"', 'fill="#abc"')

Here’s a fun one. It replaces all non-white colors in your SVG with black through the SVG:

svg data: GameIcons.get(:meat).string.gsub(':#ffffff', 'snarfblat').
                                      gsub(/:#[0-9a-f]{6}/, ':#000000').
                                      gsub('snarfblat', ':#ffffff')

XML can also be manipulated via CSS or XPATH queries via the nokogiri library, which Squib has as a dependency anyway. Like this:

doc = Nokogiri::XML(GameIcons.get(:meat).string)
doc.css('path')[1]['fill'] = #f00 # set foreground color to red
svg data: doc.to_xml

Path Weirdness

Inkscape and Squib’s libRSVG renderer can lead to unexpected results for some icons. This has to do with a discrepancy in how path data is interpreted according to the specification. (Specifically, negative numbers need to have a space before them in the path data.) The fix for this is quick and easy, and the gem can do this for you:

GameIcons.get(:sheep).correct_pathdata.string # corrects path data

Squib + Git

Warning

To be written

Ideas:
  • .gitignore
  • Workflow
  • Tracking binary data (show json method)
  • Snippet about “what’s changed”
  • Releases via tags and versioning

Autobuild with Guard

Warning

Under construction - going to fold this into the Workflow guide. For now, you can see my samples. This is mostly just a brain dump.

Throughout your prototyping process, you’ll be making small adjustments and then examining the graphical output. Re-running your code can get tedious, because you’re cognitively switching from one context to another, e.g. editing x-y coordinates to running your code.

Ruby has a great tool for this: Guard. With Guard, you specify one configuration file (i.e. a Guardfile), and then Guard will watch a set of files, then execute a task. There are many ways of executing a task, but the cleanest way is via a rake in a Rakefile.

Project layout

Here’s our project layout:

.
├── config.yml
├── Gemfile
├── Guardfile
├── img
│   └── robot-golem.svg
├── layouts
│   ├── characters.yml
│   └── skills.yml
├── Rakefile
└── src
  ├── characters.rb
  └── skills.rb

Using Guard + Rake

Guard is a gem, just like Squib. When using Guard, I recommend also using Bundler. So your Gemfile will look like this.

1
2
3
4
5
6
source 'https://rubygems.org'

gem 'squib'      # the most important part!
gem 'guard'      # guard is what watches
gem 'guard-rake' # this hooks Guard into Rake
gem 'wdm', '>= 0.1.0' if Gem.win_platform? # optional, for watching directories

And then your Rakefile might look something like this

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# This is a sample Rakefile
require 'squib'

desc 'Build all decks black-and-white'
task default: [:characters, :skills]

desc 'Build all decks with color'
task color: [:with_color, :default]

desc 'Enable color build'
task :with_color do
  puts "Enabling color build"
  Squib.configure img_dir: 'color'
end

desc 'Build the character deck'
task :characters do
  puts "Building characters"
  load 'src/characters.rb'
end

desc 'Build the skills deck'
task :skills do
  puts "Building skills"
  load 'src/skills.rb'
end

To get our images directory set, and to turn on proress bars (which I recommend when working under Guard), you’ll need a config.yml file that looks like this.

1
2
img_dir: bw # is overridden by Rakefile, but in case we dont specify
progress_bars: true

Note that we are using load instead of require to run our code. In Ruby, require will only run our code once, because it’s about loading a library. The load will run Ruby code no matter whether or not it’s been loaded before. This doesn’t usually matter, unless you’re running under Guard.

And then our Guardfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
group :characters do
  guard 'rake', task: :characters do # when triggered, do "rake characters"
    watch %r{src/characters.rb}  # a regular expression that matches our file
    watch %r{img/.*}             # watch every file inside of our img directory
    watch %r{.*\.xlsx$}          # Any excel file, anywhere
    watch %r{.*\.yml}            # Any yml file, anywhere (config or layout)
  end
end

group :skills do
  guard 'rake', task: :skills do # when triggered, do "rake skills"
    watch %r{src/skills.rb} # a regular expression that matches our file
    watch %r{img/.*}        # watch every file inside of our img directory
    watch %r{.*\.xlsx$}     # Any excel file, anywhere
    watch %r{.*\.yml}       # Any yml file, anywhere (config or layout)
  end
end

So, let’s say we’re working on our Character deck. To run all this we can kick off our Guard with:

$ bundle exec guard -g characters
14:45:20 - INFO - Run 'gem install win32console' to use color on Windows
14:45:21 - INFO - Starting guard-rake characters
14:45:21 - INFO - running characters
Loading SVG(s) <===========================================> 100% Time: 00:00:00
Saving PNGs to _output/character__* <======================> 100% Time: 00:00:00
]2;[running rake task: characters] watched files: []
[1] Characters guard(main)> ow watching at 'C:/Users/andy/code/squib/samples/project'

Guard will do an initial build, then wait for file changes to be made. From here, once we edit and save anything related to characters - any Excel file, our characters.rb file, any YML file, etc, we’ll rebuild our images.

Guard can do much, much more. It opens up a debugging console based on pry, which means if your code is broken you can test things out right there.

Guard also supports all kinds of notifications too. By default it tends to beep, but you can also have visual bells and other notifications.

To quit guard, type quit on their console. Or, you can do Ctrl+C to quit.

Enjoy!

Parameters are Optional

Squib is all about sane defaults and shorthand specification. Arguments to DSL methods are almost always using hashes, which look a lot like Ruby 2.0’s named parameters. This means you can specify your parameters in any order you please. All parameters are optional.

For example x and y default to 0 (i.e. the upper-left corner of the card). Any parameter that is specified in the command overrides any Squib defaults or layout rules.

You must use named parameters rather than positional parameters. For example:

save(:png) # wrong

will lead to an error like this:

C:/Ruby200/lib/ruby/gems/2.0.0/gems/squib-0.0.3/lib/squib/api/save.rb:12:in `save': wrong number of arguments (2 for 0..1) (ArgumentError)
    from deck.rb:22:in `block in <main>'
    from C:/Ruby200/lib/ruby/gems/2.0.0/gems/squib-0.0.3/lib/squib/deck.rb:60:in `instance_eval'
    from C:/Ruby200/lib/ruby/gems/2.0.0/gems/squib-0.0.3/lib/squib/deck.rb:60:in `initialize'
    from deck.rb:18:in `new'
    from deck.rb:18:in `<main>'

Instead, you must name the parameters:

save(format: :png) # the right way

Warning

If you provide an option to a DSL method that the DSL method does not recognize, Squib ignores the extraenous option without warning. For example, these two calls have identical behavior:

save_png prefix: 'front_'
save_png prefix: 'front_', narf: true # narf has no effect

This can be troublesome when you accidentally misspell an option and don’t realize it.

Squib Thinks in Arrays

When prototyping card games, you usually want some things (e.g. icons, text) to remain the same across every card, but then other things need to change per card. Maybe you want the same background for every card, but a different title.

The vast majority of Squib’s DSL methods can accept two kinds of input: whatever it’s expecting, or an array of whatever it’s expecting. If it’s an array, then Squib expects each element of the array to correspond to a different card.

Think of this like “singleton expansion”, where Squib goes “Is this an array? No? Then just repeat it the same across every card”. Thus, these two DSL calls are logically equivalent:

Squib::Deck.new(cards: 2) do
  text str: 'Hello'
  text str: ['Hello', 'Hello'] # same effect
end

But then to use a different string on each card you can do:

Squib::Deck.new(cards: 2) do
  text str: ['Hello', 'World']
end

Note

Technically, Squib is only looking for something that responds to each (i.e. an Enumerable). So whatever you give it should just respond to each and it will work as expected.

What if you have an array that doesn’t match the size of the deck? No problem - Ruby won’t complain about indexing an array out of bounds - it simply returns nil. So these are equivalent:

Squib::Deck.new(cards: 3) do
  text str: ['Hello', 'Hello']
  text str: ['Hello', 'Hello', nil]  # same effect
end

In the case of the text method, giving it an str: nil will mean the method won’t do anything for that card and move on. Different methods react differently to getting nil as an option, however, so watch out for that.

Using range to specify cards

There’s another way to limit a DSL method to certain cards: the range parameter.

Most of Squib’s DSL methods allow a range to be specified. This can be an actual Ruby Range, or anything that implements each (i.e. an Enumerable) that corresponds to the index of each card.

Integers are also supported for changing one card only. Negatives work from the back of the deck.

Some quick examples:

text range: 0..2  # only cards 0, 1, and 2
text range: [0,2] # only cards 0 and 2
text range: 0     # only card 0

Behold! The Power of Ruby Arrays

One of the more distinctive benefits of Ruby is in its rich set of features for manipulating and accessing arrays. Between range and using arrays, you can specify subsets of cards quite succinctly. The following methods in Ruby are particularly helpful:

  • Array#each - do something on each element of the array (Ruby folks seldom use for-loops!)
  • Array#map - do something on each element of an array and put it into a new array
  • Array#select - select a subset of an array
  • Enumerable#each_with_index - do something to each element, also being aware of the index
  • Enumerable#inject - accumulate elements of an array in a custom way
  • Array#zip - combine two arrays, element by element

Examples

Here are a few recipes for using arrays and ranges in practice:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
require 'squib'

data = { 'name' => ['Thief', 'Grifter', 'Mastermind'],
        'type' => ['Thug', 'Thinker', 'Thinker'],
        'level' => [1, 2, 3] }

Squib::Deck.new(width: 825, height: 1125, cards: 3) do
  # Default range is :all
  background color: :white
  text str: data['name'], x: 250, y: 55, font: 'Arial 18'
  text str: data['level'], x: 65, y: 40, font: 'Arial 24'

  # Could be explicit about using :all, too
  text range: :all,
       str: data['type'], x: 40, y: 128, font: 'Arial 6',
       width: 100, align: :center

  # Ranges are inclusive, zero-based
  text range: 0..1, str: 'Thief and Grifter only!!', x: 25, y:200

  # Integers are also allowed
  text range: 0, str: 'Thief only!', x: 25, y: 250

  # Negatives go from the back of the deck
  text range: -1, str: 'Mastermind only!', x: 25, y: 250
  text range: -2..-1, str: 'Grifter and Mastermind only!', x: 25, y: 650

  # We can use Arrays too!
  text range: [0, 2], str: 'Thief and Mastermind only!!', x: 25, y:300

  # Just about everything in Squib can be given an array that
  # corresponds to the deck's cards. This allows for each card to be styled differently
  # This renders three cards, with three strings that had three different colors at three different locations.
  text str: %w(red green blue),
       color: [:red, :green, :blue],
       x: [40, 80, 120],
       y: [700, 750, 800]

  # Useful idiom: construct a hash from card names back to its index (ID),
  # then use a range. No need to memorize IDs, and you can add cards easily
  id = {} ; data['name'].each_with_index{ |name, i| id[name] = i}
  text range: id['Thief']..id['Grifter'],
       str: 'Thief through Grifter with id lookup!!',
       x:25, y: 400

  # Useful idiom: generate arrays from a column called 'type'
  type = {}; data['type'].each_with_index{ |t, i| (type[t] ||= []) << i}
  text range: type['Thinker'],
       str: 'Only for Thinkers!',
       x:25, y: 500

  # Useful idiom: draw a different number of images for different cards
  hearts = [nil, 1, 2] # i.e. card 0 has no hearts, card 2 has 2 hearts drawn
  1.upto(2).each do |n|
    range = hearts.each_index.select { |i| hearts[i] == n}
    n.times do |i|
      svg file: 'glass-heart.svg', range: range,
          x: 150, y: 55 + i * 42, width: 40, height: 40
    end
  end

  rect stroke_color: 'black' # just a border
  save_sheet prefix: 'ranges_', columns: 3
end

Contribute Recipes!

There are a lot more great recipes we could come up with. Feel free to contribute! You can add them here via pull request or via the wiki

Layouts are Squib’s Best Feature

Working with tons of options to a method can be tiresome. Ideally everything in a game prototype should be data-driven, easily changed, and your Ruby code should readable without being littered with magic numbers.

For this, most Squib methods have a layout option. Layouts are a way of setting default values for any parameter given to the method. They let you group things logically, manipulate options, and use built-in stylings.

Think of layouts and DSL calls like CSS and HTML: you can always specify style in your logic (e.g. directly in an HTML tag), but a cleaner approach is to group your styles together in a separate sheet and work on them separately.

To use a layout, set the layout: option on Deck.new to point to a YAML file. Any command that allows a layout option can be set with a Ruby symbol or string, and the command will then load the specified options. The individual command can also override these options.

For example, instead of this:

# deck.rb
Squib::Deck.new do
  rect x: 75, y: 75, width: 675, height: 975
end

You can put your logic in the layout file and reference them:

# custom-layout.yml
bleed:
  x: 75
  y: 75
  width: 975
  height: 675

Then your script looks like this:

# deck.rb
Squib::Deck.new(layout: 'custom-layout.yml') do
  rect layout: 'bleed'
end

The goal is to make your Ruby code separate the data decisions from logic. For the above example, you are separating the decision to draw rectangle around the “bleed” area, and then your YAML file is defining specifically what “bleed” actually means. (Who is going to remember that x: 75 means “bleed area”??) This process of separating logic from data makes your code more readable, changeable, and maintainable.

Warning

YAML is very finnicky about not allowing tab characters. Use two spaces for indentation instead. If you get a Psych syntax error, this is likely the culprit. Indendation is also strongly enforced in Yaml too. See the Yaml docs for more info.

Order of Precedence for Options

Layouts will override Squib’s system defaults, but are overriden by anything specified in the command itself. Thus, the order of precedence looks like this:

  1. Use what the DSL method specified, e.g. rect x: 25
  2. If anything was not yet specified, use what was given in a layout (if a layout was specified in the command and the file was given to the Deck). e.g. rect layout: :bleed
  3. If still anything was not yet specified, use what was given in Squib’s defaults as defined in the DSL Reference.

For example, back to our example:

# custom-layout.yml
bleed:
  x: 0.25in
  y: 0.25in
  width: 2.5in
  height: 3.5in

(Note that this example makes use of Unit Conversion)

Combined with this script:

# deck.rb
Squib::Deck.new(layout: 'custom-layout.yml') do
  rect layout: 'bleed', x: 50
end

The options that go into rect will be:

  • x will be 50 because it’s specified in the DSL method and overrides the layout
  • y, width, and height were specified in the layout file, so their values are used
  • The rect’s stroke_color (and others options like it) was never specified anywhere, so the default for rect is used - as discussed in Parameters are Optional.

Note

Defaults are not global for the name of the option - they are specific to the method itself. For example, the default fill_color for rect is '#0000' but for showcase it’s :white.

Note

Layouts work with all options (for DSL methods that support layouts), so you can use options like file or font or whatever is needed.

Warning

If you provide an option in the Yaml file that is not supported by the DSL method, the DSL method will simply ignore it. Same behavior as described in Parameters are Optional.

When Layouts Are Similar, Use extends

Using layouts are a great way of keeping your Ruby code clean and concise. But those layout Yaml files can get pretty long. If you have a bunch of icons along the top of a card, for example, you’re specifying the same y option over and over again. This is annoyingly verbose, and what if you want to move all those icons downward at once?

Squib provides a way of reusing layouts with the special extends` key. When defining an `extends key, we can merge in another key and modify its data coming in if we want to. This allows us to do things like place text next to an icon and be able to move them with each other. Like this:

# If we change attack, we move defend too!
attack:
  x: 100
  y: 100
defend:
  extends: attack
  x: 150
  #defend now is {:x => 150, :y => 100}

Over time, using extends saves you a lot of space in your Yaml files while also giving more structure and readability to the file itself.

You can also modify data as they get passed through extends:

# If we change attack, we move defend too!
attack:
  x: 100
defend:
  extends: attack
  x: += 50
  #defend now is {:x => 150, :y => 100}
The following operators are supported within evaluating extends
  • += will add the giuven number to the inherited number
  • -= will subtract the given number from the inherited number
  • *= will multiply the inherited number by the given number
  • /= will divide the inherited number by the given number

+= and -= also support Unit Conversion

From a design point of view, you can also extract out a base design and have your other layouts extend from them:

top_icons:
  y: 100
  font: Arial 36
attack:
  extends: top_icon
  x: 25
defend:
  extends: top_icon
  x: 50
health:
  extends: top_icon
  x: 75
# ...and so on

Note

Those fluent in Yaml may notice that extends key is similar to Yaml’s merge keys. Technically, you can use these together - but I just recommend sticking with extends since it does what merge keys do and more. If you do choose to use both extends and Yaml merge keys, the Yaml merge keys are processed first (upon Yaml parsing), then extends (after parsing).

Yes, extends is Multi-Generational

As you might expect, extends can be composed multiple times:

socrates:
  x: 100
plato:
  extends: socrates
  x: += 10    # evaluates to 110
aristotle:
  extends: plato
  x: "*= 2"     # evaluates to 220, note that YAML requires quotes here

Yes, extends has Multiple Inheritance

If you want to extend multiple parents, it looks like this:

socrates:
  x: 100
plato:
  y: 200
aristotle:
  extends:
    - socrates
    - plato
  x: += 50    # evaluates to 150 from socrates
  # y is going to be 200 too from Plato

If multiple keys override the same keys in a parent, the later (“younger”) child in the extends list takes precedent. Like this:

socrates:
  x: 100
plato:
  x: 200
aristotle:
  extends:
    - plato    # note the order here
    - socrates
  x: += 50     # evaluates to 150 from socrates

Multiple Layout Files get Merged

Squib also supports the combination of multiple layout files. If you provide an Array of files then Squib will merge them sequentially. Colliding keys will be completely re-defined by the later file. The extends key is processed after each file, but can be used across files. Here’s an example:

# load order: a.yml, b.yml

##############
# file a.yml #
##############
grandparent:
  x: 100
parent_a:
  extends: grandparent
  x: += 10   # evaluates to 110
parent_b:
  extends: grandparent
  x: += 20   # evaluates to 120

##############
# file b.yml #
##############
child_a:
  extends: parent_a  # i.e. extends a layout in a separate file
  x: += 3    # evaluates to 113 (i.e 110 + 3)
parent_b:    # redefined
  extends: grandparent
  x: += 30   # evaluates to 130 (i.e. 100 + 30)
child_b:
  extends: parent_b
  x: += 3    # evaluates to 133 (i.e. 130 + 3)
This can be helpful for:
  • Creating a base layout for structure, and one for full color for easier color/black-and-white switching
  • Sharing base layouts with other designers

Squib Comes with Built-In Layouts

Why mess with x-y coordinates when you’re first prototyping your game? Just use a built-in layout to get your game to the table as quickly as possible.

If your layout file is not found in the current directory, Squib will search for its own set of layout files. The latest the development version of these can be found on GitHub.

Contributions in this area are particularly welcome!!

The following depictions of the layouts are generated with this script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
require 'squib'

# This sample demonstrates the built-in layouts for Squib.
# Each card demonstrates a different built-in layout.
Squib::Deck.new(layout: 'fantasy.yml') do
  background color: 'white'

  set font: 'Times New Roman,Serif 10.5'
  hint text: '#333' # show extents of text boxes to demo the layout

  text str: 'fantasy.yml', layout: :title
  text str: 'ur',          layout: :upper_right
  text str: 'art',         layout: :art
  text str: 'type',        layout: :type
  text str: 'tr',          layout: :type_right
  text str: 'description', layout: :description
  text str: 'lr',          layout: :lower_right
  text str: 'll',          layout: :lower_left
  text str: 'credits',     layout: :copy

  rect layout: :safe
  rect layout: :cut
  save_png prefix: 'layouts_builtin_fantasy_'
end

Squib::Deck.new(layout: 'economy.yml') do
  background color: 'white'

  set font: 'Times New Roman,Serif 10.5'
  hint text: '#333' # show extents of text boxes to demo the layout

  text str: 'economy.yml', layout: :title
  text str: 'art',         layout: :art
  text str: 'description', layout: :description
  text str: 'type',        layout: :type
  text str: 'lr',          layout: :lower_right
  text str: 'll',          layout: :lower_left
  text str: 'credits',     layout: :copy

  rect layout: :safe
  rect layout: :cut
  save_png prefix: 'layouts_builtin_economy_'
end

Squib::Deck.new(layout: 'hand.yml') do
  background color: 'white'
  %w(title bonus1 bonus2 bonus3 bonus4 bonus5 description
     snark art).each do |icon|
    text str: icon.capitalize, layout: icon,
         hint: :red, valign: 'middle', align: 'center'
  end
  save_png prefix: 'layouts_builtin_hand_'
end

Squib::Deck.new(layout: 'playing-card.yml') do
  background color: 'white'
  text str: "A\u2660", layout: :bonus_ul, font: 'Sans bold 33', hint: :red
  text str: "A\u2660", layout: :bonus_lr, font: 'Sans bold 33', hint: :red
  text str: 'artwork here', layout: :art, hint: :red
  save_png prefix: 'layouts_builtin_playing_card_'
end

Squib::Deck.new(layout: 'tuck_box.yml', width: 2325, height: 1950) do
  background color: 'white'
  rect layout: :top_rect
  rect layout: :bottom_rect
  rect layout: :right_rect
  rect layout: :left_rect
  rect layout: :back_rect
  rect layout: :front_rect
  curve layout: :front_curve

  save_png prefix: 'layouts_builtin_tuck_box_'
end

Squib::Deck.new(layout: 'party.yml') do
  background color: 'white'
  # hint text: :black # uncomment to see the text box boundaries

  rect layout: :title_box,
       fill_color: :deep_sky_blue, stroke_width: 0
  text str: "A SILLY NAME",  layout: :title
  text str: '✔',             layout: :type_icon
  text str: 'TYPE',          layout: :type
  text str: 'A Silly Name',  layout: :title_middle
  rect fill_color: :black,   layout: :middle_rect

  str = 'Rule or story text. Be funny ha ha ha this is a party.'
  text str: str, layout: :rule_top
  text str: str, layout: :rule_bottom

  text str: 'Tiny text', layout: :copyright

  rect layout: :safe
  rect layout: :cut
  save_png prefix: 'layouts_builtin_party_'
end

See Layouts in Action

This sample, which lives here, demonstrates many different ways of using and combining layouts.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# encoding: utf-8
require 'squib'
require 'pp'

Squib::Deck.new(layout: 'custom-layout.yml') do
  background color: :white
  hint text: :cyan

  # Layouts are YAML files that specify any option as a default
  rect layout: :frame

  # You can also override a given layout entry in the command
  circle layout: :frame, x: 50, y: 50, radius: 25

  # Lots of commands have the :layout option
  text str: 'The Title', layout: :title

  # Layouts also support YAML merge keys toreuse settings
  svg file: 'spanner.svg',     layout: :icon_left
  png file: 'shiny-purse.png', layout: :icon_middle
  svg file: 'spanner.svg',     layout: :icon_right

  # Squib has its own, richer merge key: "extends"
  rect fill_color: :black, layout: :bonus
  rect fill_color: :white, layout: :bonus_inner
  text str: 'Extends!',    layout: :bonus_text

  # Strings can also be used to specify a layout (e.g. from a data file)
  text str: 'subtitle', layout: 'subtitle'

  # For debugging purposes, you can always print out the loaded layout
  # require 'pp'
  # pp layout

  save_png prefix: 'layout_'
end

Squib::Deck.new(layout: ['custom-layout.yml', 'custom-layout2.yml']) do
  text str: 'The Title',       layout: :title       # from custom-layout.yml
  text str: 'The Subtitle',    layout: :subtitle    # redefined in custom-layout2.yml
  text str: 'The Description', layout: :description # from custom-layout2.yml
  save_png prefix: 'layout2_'
end

# Built-in layouts are easy to use and extend
Squib::Deck.new(layout: 'playing-card.yml') do
  text str: "A\u2660",      layout: :bonus_ul, font: 'Sans bold 33', hint: :red
  text str: "A\u2660",      layout: :bonus_lr, font: 'Sans bold 33', hint: :red
  text str: 'artwork here', layout: :art, hint: :red
  save_png prefix: 'layout_builtin_playing_card_'
end

# Built-in layouts are easy to use and extend
Squib::Deck.new(layout: 'hand.yml') do
  %w(title bonus1 bonus2 bonus3 bonus4 bonus5
    description snark art).each do |icon|
    text str: icon.capitalize, layout: icon,
         hint: :red, valign: 'middle', align: 'center'
  end
  save_png prefix: 'layout_builtin_hand_'
end

# Layouts can also be specified in their own DSL method call
# Each layout call will progressively be merged with the priors
Squib::Deck.new do
  use_layout file: 'custom-layout.yml'
  use_layout file: 'custom-layout2.yml'
  text str: 'The Title',   layout: :title # from custom-layout.yml
  text str: 'The Subtitle', layout: :subtitle # redefined in custom-layout2.yml
  save_png prefix: 'layout3_'
end

This is custom-layout.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# Used in the layouts.rb sample in conjunction with custom-layout2.yml
frame:
  x: 38
  y: 38
  width: 750
  height: 1050
  radius: 25
title:
  x: 125
  y: 50
  width: 625
  height: 100
  align: center #strings also work for most options
  valign: !ruby/symbol middle #yaml also support symbols, see http://www.yaml.org/YAML_for_ruby.html#symbols
subtitle:
  x: 150
  y: 150
  width: 575
  height: 60
  align: center 
  valign: middle
icon:
  width: 125
  height: 125
  y: 250
icon_left:
  extends: icon
  x: 150
icon_middle:
  extends: icon
  x: 350
  y: 400  #overrides the y inherited from icon
icon_right:
  extends: icon
  x: 550

# Squib also supports its own merging-and-modify feature
# Called "extends"
# Any layout can extend another layout, so long as it's not a circle
# Order doesn't matter since it's done after YAML procesing
# And, if the entry overrides
bonus: #becomes our bonus rectangle
  x: 250
  y: 600
  width:  300
  height: 200
  radius: 32
bonus_inner:
  extends: bonus
  x: += 10 # i.e. 260
  y: += 10 # i.e. 610
  width: -= 20  # i.e. 180
  height: -= 20 # i.e. 180
  radius: -= 8
bonus_text:
  extends: bonus_inner
  x: +=10
  y: +=10
  width: -= 20
  height: -= 20

This is custom-layout2.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Used in the layouts.rb sample in conjunction with custom-layout.yml
#
# When used with custom-layout.yml loaded first, this layout overrides subtitle
subtitle:
  x: 150
  y: 150
  width: 575
  height: 60
  align: left 
  valign: middle
  font_size: 8
# This one is completely new
description: 
  extends: subtitle
  y: += 350

Be Data-Driven with XLSX and CSV

Squib supports importing data from ExcelX (.xlsx) files and Comma-Separated Values (.csv) files. Because Squib Thinks in Arrays, these methods are column-based, which means that they assume you have a header row in your table, and that header row will define the name of the column.

Squib::DataFrame, or a Hash of Arrays

In both DSL methods, Squib will return a “data frame” (literally of type Squib::DataFrame). The best way to think of this is a Hash of Arrays, where each column is a key in the hash, and every element of each Array represents a data point on a card.

The data import methods expect you to structure your Excel sheet or CSV like this:

  • First row should be a header - preferably with concise naming since you’ll reference it in Ruby code
  • Rows should represent cards in the deck
  • Columns represent data about cards (e.g. “Type”, “Cost”, or “Name”)

Of course, you can always import your game data other ways using just Ruby (e.g. from a REST API, a JSON file, or your own custom format). There’s nothing special about Squib’s methods in how they relate to Squib::Deck other than their convenience.

See xlsx and csv for more details and examples on how the data can be imported.

The Squib::DataFrame class provides much more than what a Hash provides, however. The Squib::DataFrame

Quantity Explosion

If you want more than one copy of a card, then have a column in your data file called Qty and fill it with counts for each card. Squib’s xlsx and xlsx methods will automatically expand those rows according to those counts. You can also customize that “Qty” to anything you like by setting the explode option (e.g. explode: 'Quantity'). Again, see the specific methods for examples.

Unit Conversion

By default, Squib thinks in pixels. This decision was made so that we can have pixel-perfect layouts without automatically scaling everything, even though working in units is sometimes easier. We provide some conversion methods, including looking for strings that end in “in”, “cm”, or “mm” and computing based on the current DPI. The dpi is set on Squib::Deck.new (not config.yml).

Cells

A “cell” is a custom unit in Squib that, by default, refers to 37.5 pixels. In a 300 DPI situation (i.e. the default), that refers to a 1/8 inch or 3.175mm. This tends to be a standard unit of measure in a lot of templates. By specifying your units in cells, you can increase your rapid prototyping without having to multiply 37.5.

The cell_px measure is configurable. See Configuration Options.

To use the cell unit, you need to give Squib a string ending in cell, cells, or just c. For example:

  • 2 cells
  • 1cell
  • 0.5c

See more examples below.

Samples

_units.rb

Here are some examples, which lives here

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
require 'squib'

Squib::Deck.new(width: '1.5in', height: '1.5in') do
  background color: :white

  # We can use our DSL-method to use inches
  # Computed using @dpi (set to 300 by default)
  bleed = inches(0.125)
  cut   = inches(1.25)
  rect x: bleed, y: bleed,
       width: cut, height: cut,
       dash: '0.5mm 0.5mm' # yes, units are in dashes too

  # other units too
  cm(2)             # We can also use cm this way
  cm(2) + inches(2) # We can mix units too

  # Or we can use a string ending with cm or in
  safe_margin = '0.25 in' #you can have a space too
  safe_width  = '1 in'
  safe_height = '1.0 in  ' # trailing space is ok too
  rect x: safe_margin, y: safe_margin,
  	   width: safe_width, height: safe_height,
       radius: '2 mm '

  # Angles are also automatically converted to radians if you use deg
  svg file: '../spanner.svg',
      x: 100, y: 100, width: 40, height: 40, angle: '30deg'

  # We can also do stuff in layout. Check out the yml file...
  #  (even cleaner in Yaml since we don't need quotes!)
  use_layout file: 'using_units.yml'
  text str: 'Hello.', layout: :example
  svg file: '../spanner.svg', layout: :angled

  save prefix: 'units_', format: :png

  # But wait... there's more! See _shorthands.rb for more fanciness with units
end

_cells.rb

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
require 'squib'

Squib::Deck.new(width: '1.5in', height: '1.5in') do
  background color: :white

  # Squib has a custom unit, called "cell"
  # A "cell" unit defaults to 37.5px, which at 300dpi is 1/8in or 3.175mm
  # This is a very common multiple for layouts.
  # This helps us lay things out in grids without doing much math in our heads
  # Here's an example... with grid!
  grid width: '1 cell', height: '1 cell'

  # Plurals are fine or just 'c' as a unit is fine
  # Whitespace is pretty lenient too.
  rect fill_color: :blue,
       x: '1 cell', y: '2 cells',
       width: '1c', height: '1cell  '

  # Technically, the "cell" is actually a "unit", so you can even combine
  # with xywh shorhands!!
  rect fill_color: :red,
       x: 'middle + 0.5c', y: 'deck - 1.5c',
       width: '1c', height: '1c'

  # And, unlike xywh shorthands, this applies basically everywhere we support
  # unit conversion.
  circle fill_color: :green,
         x: '3c', y: '2c', radius: '1c'
  # Decimals are fine too
  circle fill_color: :green,
         x: '5c', y: '2c', radius: '0.5c'
  # Even dashes!
  circle fill_color: '#0000', stroke_color: :purple,
         x: '1c', y: '4c', radius: '0.5c', dash: '0.25c 0.25c'

  # We can also do stuff in layout. Check out the yml file...
  #  (even cleaner in Yaml since we don't need quotes!)
  use_layout file: 'cells.yml'
  rect layout: :example
  rect layout: :extends_example

  save_png prefix: 'cells_'
end


# You can customize this with the cell_px configuration option
Squib::Deck.new(width: 100, height: 100, config: 'cell_config.yml') do
  background color: :white
  rect x: '1c', y: '1c', width: '1c', height: '1c', fill_color: :purple
  save_png prefix: 'custom_cell_'
end

XYWH Shorthands

For the arguments x, y, width, and height, a few convenient shorthands are available.

  • middle for x and width refer to the deck’s width / 2
  • middle for y and height refer to the deck’s height / 2
  • The word center behaves the same way
  • deck refers to the deck’s width for x and width
  • deck refers to the deck’s height for y and height
  • You can offset from the middle by using + or - operators, e.g. middle + 1in
  • You can offset from the deck width or height using the + or - operators, e.g. deck - 1in or deck - 2mm
  • You can offset from the deck width or height using, e.g. deck / 3
  • Works with all unit conversion too, e.g. middle + 1 cell. See Unit Conversion.

These are all passed as strings. So you will need to quote them in Ruby, or just plain in your layout YAML.

Note that the following are NOT supported:

  • The += operator when using extends in a layout file
  • Complicated formulas. We’re not evaluating this as code, we’re looking for these specific patterns and applying them. Anything more complicated you’ll have to handle with Ruby code.

Samples

_shorthands.rb

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
require_relative '../../lib/squib'

# Lots of DSL methods have shorthands that are accepted for
# x, y, width, and height parameters.
Squib::Deck.new(width: '0.5in', height: '0.25in') do
  background color: :white

  # middle for x and y will resolve to half the height
  text str: 'xymiddle', font: 'Sans Bold 3', hint: :red,
       x: 'middle', y: :middle

  # 'center' also works
  rect width: 30, height: 30,
       x: :center, y: 'center'

  # Applies to shapes
  triangle x1: 20, y1: 20,
           x2: 60, y2: 20,
           x3: :middle, y3: :middle

  # We can also do width-, height-, width/, height/
  rect x: 20, y: 5, stroke_color: :green,
  width: 'deck - 0.1in', height: 10

  rect x: 10, y: 50, width: 10, height: 'deck / 3',
       stroke_color: :purple

  # And middle+/-
  rect x: 'middle + 0.1in', y: 'center - 0.1in',
       width: '0.1in', height: '0.1in', fill_color: :blue

  # Layouts apply this too.
  use_layout file: 'shorthands.yml'
  rect layout: :example

  # HOWEVER! Shorthands don't combine in an "extends" situation,
  # e.g. this won't work:
  # parent:
  #   x: middle
  # child:
  #   extends: parent
  #   x: += 0.5in

  # These shorthands are not intended for every xywh parameter or
  # length parameter, see docs for when they do or do not apply.

  save_png prefix: 'shorthand_'
end

Specifying Colors & Gradients

Colors

by hex-string

You can specify a color via the standard hexadecimal string for RGB (as in HTML and CSS). You also have a few other options as well. You can use:

  • 12-bit (3 hex numbers), RGB. e.g. '#f08'
  • 24-bit (6 hex numbers), RRGGBB. e.g. '#ff0088'
  • 48-bit (9 hex numbers), RRRGGGBBB. e.g. '#fff000888'

Additionally, you can specify the alpha (i.e. transparency) of the color as RGBA. An alpha of 0 is full transparent, and f is fully opaque. Thus, you can also use:

  • 12-bit (4 hex numbers), RGBA. e.g. '#f085'
  • 24-bit (8 hex numbers), RRGGBBAA. e.g. '#ff008855'
  • 48-bit (12 hex numbers), RRRGGGBBBAAA. e.g. '#fff000888555'

The # at the beginning is optional, but encouraged for readability. In layout files (described in Layouts are Squib’s Best Feature), the # character will initiate a comment in Yaml. So to specify a color in a layout file, just quote it:

# this is a comment in yaml
attack:
  fill_color: '#fff'

by name

Under the hood, Squib uses the rcairo color parser to accept around 300 named colors. The full list can be found here.

Names of colors can be either strings or symbols, and case does not matter. Multiple words are separated by underscores. For example, 'white', :burnt_orange, or 'ALIZARIN_CRIMSON' are all acceptable names.

by custom name

In your config.yml, as described in Configuration Options, you can specify custom names of colors. For example, 'foreground'.

Gradients

In most places where colors are allowed, you may also supply a string that defines a gradient. Squib supports two flavors of gradients: linear and radial. Gradients are specified by supplying some xy coordinates, which are relative to the card (not the command). Each stop must be between 0.0 and 1.0, and you can supply as many as you like. Colors can be specified as above (in any of the hex notations or built-in constant). If you add two or more colors at the same stop, then the gradient keeps the colors in the in order specified and treats it like sharp transition.

The format for linear gradient strings look like this:

'(x1,y1)(x2,y2) color1@stop1 color2@stop2'

The xy coordinates define the angle of the gradient.

The format for radial gradients look like this:

'(x1,y1,radius1)(x2,y2,radius2) color1@stop1 color2@stop2'

The coordinates specify an inner circle first, then an outer circle.

In both of these formats, whitespace is ignored between tokens so as to make complex gradients more readable.

If you need something more powerful than these two types of gradients (e.g. mesh gradients), then we suggest encapsulating your logic within an SVG and using the svg method to render it.

Samples

Code is maintained in the repository here in case you need some of the assets referenced.

Sample: colors and color constants

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
require 'squib'

Squib::Deck.new(width: 825, height: 1125, cards: 1) do
  background color: :white

  y = 0
  text color: '#f00', str: '3-hex', x: 50, y: y += 50
  text color: '#f00', str: '3-hex (alpha)', x: 50, y: y += 50
  text color: '#ff0000', str: '6-hex', x: 50, y: y += 50
  text color: '#ff000099', str: '8-hex(alpha)', x: 50, y: y += 50
  text color: '#ffff00000000', str: '12-hex', x: 50, y: y += 50
  text color: '#ffff000000009999', str: '12-hex (alpha)', x: 50, y: y += 50
  text color: :burnt_orange, str: 'Symbols of constants too', x: 50, y: y += 50
  text color: '(0,0)(400,0) blue@0.0 red@1.0', str: 'Linear gradients!', x: 50, y: y += 50
  text color: '(200,500,10)(200,500,100) blue@0.0 red@1.0', str: 'Radial gradients!', x: 50, y: y += 50
  # see gradients.rb sample for more on gradients

  save_png prefix: 'colors_'
end

# This script generates a table of the built-in constants
colors = (Cairo::Color.constants - %i(HEX_RE Base RGB CMYK HSV X11))
colors.sort_by! do |c|
  hsv = Cairo::Color.parse(c).to_hsv
  [(hsv.hue / 16.0).to_i, hsv.value, hsv.saturation]
end
w, h = 300, 50
deck_height = 4000
deck_width = (colors.size / ((deck_height / h) + 1)) * w
Squib::Deck.new(width: deck_width, height: deck_height) do
  background color: :white
  x, y = 0, 0
  colors.each_with_index do |color, i|
    rect x: x, y: y, width: w, height: h, fill_color: color
    text str: color.to_s, x: x + 5, y: y + 13, font: 'Sans Bold 5',
         color: (Cairo::Color.parse(color).to_hsv.v > 0.9) ? '#000' : '#fff'
    y += h
    if y > deck_height
      x += w
      y = 0
    end
  end
  save_png prefix: 'color_constants_'
end

Sample: gradients

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
require 'squib'

Squib::Deck.new do
  # Just about anywhere Squib takes in a color it can also take in a gradient too
  # The x-y coordinates on the card itself,
  # and then color stops are defined between 0 and 1
  background color: '(0,0)(0,1125) #ccc@0.0 #111@1.0'
  line stroke_color: '(0,0)(825,0) #111@1.0 #ccc@0.0',
      x1: 0, y1: 600, x2: 825, y2: 600,
       stroke_width: 15

  # Radial gradients look like this
  circle fill_color: '(425,400,2)(425,400,120) #ccc@0.0 #111@1.0',
         x: 415, y: 415, radius: 100, stroke_color: '#0000'
  triangle fill_color: '(650,400,2)(650,400,120) #ccc@0.0 #111@1.0',
           stroke_color: '#0000',
           x1: 650, y1: 360,
           x2: 550, y2: 500,
           x3: 750, y3: 500

  # Gradients are also good for beveling effects:
  rect fill_color: '(0,200)(0,600) #111@0.0 #ccc@1.0',
       x: 30, y: 350, width: 150, height: 150,
       radius: 15, stroke_color: '#0000'
  rect fill_color: '(0,200)(0,600) #111@1.0 #ccc@0.0',
       x: 40, y: 360, width: 130, height: 130,
       radius: 15, stroke_color: '#0000'

  # Alpha transparency can be used too
  text str: 'Hello, world!', x: 75, y: 700, font: 'Sans Bold 24',
       color: '(0,0)(825,0) #000f@0.0 #0000@1.0'

  save_png prefix: 'gradient_'
end

Sample: Switch color based on variable

Say you have different card types with different colors or themes but you would like to change the theme quickly without changing all parameters for each method inside Squib::Deck.new().

You could use something like the following snippet to have one place to define the color/theme and use that in all map functions. The example inverts the color from white to black for one card type. Use a switch-statement inside the map function to differentiate multiple types.

It’s possible to use that for e.g. background color, text color or to choose the correct SVG for that color scheme (e.g. use the white or the black version of an image). The sample shows how to define the color at one place, e.g. choose between white and black, to quickly change the color scheme for the cards:

Here’s the data or see the tab-separated file in the sample directory:

Type Text
Snake This is a snake.
Lion This is a lion.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
require_relative '../../lib/squib'

# Choose between black and white color theme for type snake
#   * Allow using white snake cards with black text or
#     black snake cards with white text
color = 'white'

cards = Squib.csv file: '_switch_color_data.csv'

Squib::Deck.new cards: cards['Type'].size do

    background_color = cards['Type'].map do |t|
        if color == 'black' && t == "Snake" then
            "black"
        else
            "white"
        end
    end
    background color: background_color

    text_color = cards['Type'].map do |t|
        if color == 'black' && t == "Snake" then
            "white"
        else
            "black"
        end
    end

    text str: cards['Text'], color: text_color

    save_png prefix: '_switch_color_sample_'

end

The Mighty text Method

The text method is a particularly powerful method with a ton of options. Be sure to check the option-by-option details in the DSL reference, but here are the highlights.

Fonts

To set the font, your text method call will look something like this:

text str: "Hello", font: 'MyFont Bold 32'

The 'MyFont Bold 32' is specified as a “Pango font string”, which can involve a lot of options including backup font families, size, all-caps, stretch, oblique, italic, and degree of boldness. (These options are only available if the underlying font supports them, however.) Here’s are some text calls with different Pango font strings:

text str: "Hello", font: 'Sans 18'
text str: "Hello", font: 'Arial,Verdana weight=900 style=oblique 36'
text str: "Hello", font: 'Times New Roman,Sans 25'

Finally, Squib’s text method has options such as font_size that allow you to override the font string. This means that you can set a blanket font for the whole deck, then adjust sizes from there. This is useful with layouts and extends too (see Layouts are Squib’s Best Feature).

Note

When the font has a space in the name (e.g. Times New Roman), you’ll need to put a backup to get Pango’s parsing to work. In some operating systems, you’ll want to simply end with a comma:

text str: "Hello", font: 'Times New Roman, 25'

Note

Most of the font rendering is done by a combination of your installed fonts, your OS, and your graphics card. Thus, different systems will render text slightly differently.

Width and Height

By default, Pango text boxes will scale the text box to whatever you need, hence the :native default. However, for most of the other customizations to work (e.g. center-aligned) you’ll need to specify the width. If both the width and the height are specified and the text overflows, then the ellipsize option is consulted to figure out what to do with the overflow. Also, the valign will only work if height is also set to something other than :native.

Autoscaling Font Size

See our sample below Sample: _autoscale_font.rb

Hints

Laying out text by typing in numbers can be confusing. What Squib calls “hints” is merely a rectangle around the text box. Hints can be turned on globally in the config file, using the hint method, or in an individual text method. These are there merely for prototyping and are not intended for production. Additionally, these are not to be conflated with “rendering hints” that Pango and Cairo mention in their documentation.

Extents

Sometimes you want size things based on the size of your rendered text. For example, drawing a rectangle around card’s title such that the rectangle perfectly fits. Squib returns the final rendered size of the text so you can work with it afterward. It’s an array of hashes that correspond to each card. The output looks like this:

Squib::Deck.new(cards: 2) do
  extents = text(str: ['Hello', 'World!'])
  puts extents
end

will output:

[{:width=>109, :height=>55}, {:width=>142, :height=>55}] # Hello was 109 pixels wide, World 142 pixels

Embedding Images

Squib can embed icons into the flow of text. To do this, you need to define text keys for Squib to look for, and then the corresponding files. The object given to the block is a TextEmbed, which supports PNG and SVG. Here’s a minimal example:

text(str: 'Gain 1 :health:') do |embed|
  embed.svg key: ':health:', file: 'heart.svg'
end

Markup

See Markup in text.

Samples

These samples are maintained in the repository here in case you need some of the assets referenced.

Sample: _text.rb

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
require 'squib'
require 'squib/sample_helpers'

Squib::Deck.new(width: 1000, height: 1450) do
  draw_graph_paper width, height

  sample 'Font strings are quite expressive. Specify family, modifiers, then size. Font names with spaces in them should end with a comma to help with parsing.' do |x, y|
    text font: 'Arial bold italic 11', str: 'Bold and italic!', x: x, y: y - 50
    text font: 'Arial weight=300 11', str: 'Light bold!', x: x, y: y
    text font: 'Times New Roman, 11', str: 'Times New Roman', x: x, y: y + 50
    text font: 'NoSuchFont,Arial 11', str: 'Arial Backup', x: x, y: y + 100
  end

  sample 'Specify width and height to see a text box. Also: set "hint" to see the extents of your text box' do |x, y|
    text str: 'This has fixed width and height.', x: x, y: y,
         hint: :red, width: 300, height: 100, font: 'Serif bold 8'
  end

  sample 'If you specify the width only, the text will ellipsize.' do |x, y|
    text str: 'The meaning of life is 42', x: x - 50, y: y,
         hint: :red, width: 350, font: 'Serif bold 7'
  end

  sample 'If you specify ellipsize: :autosize, the font size will autoscale.' do |x, y|
    text str: 'This text doeas not fit with font size 7. It is autoscaled to the largest size that fits instead.', x: x - 50, y: y,
         hint: :red, width: 350, height: 100, ellipsize: :autoscale, font: 'Serif bold 7'
  end

  sample 'If you specify the width only, and turn off ellipsize, the height will auto-stretch.' do |x, y|
    text str: 'This has fixed width, but not fixed height.', x: x, y: y,
         hint: :red, width: 300, ellipsize: false, font: 'Serif bold 8'
  end

  sample 'The text method returns the ink extents of each card\'s rendered text. So you can custom-fit a shape around it.' do |x, y|
    ['Auto fit!', 'Auto fit!!!!' ].each.with_index do |str, i|
      text_y = y + i * 50
      extents = text str: str, x: x, y: text_y, font: 'Sans Bold 8'

      # Extents come back as an array of hashes, which can get split out like this
      text_width  = extents[0][:width]
      text_height = extents[0][:height]
      rect x: x, y: text_y, width: text_width, height: text_height, radius: 10,
           stroke_color: :purple, stroke_width: 3
    end
  end

  sample 'Text can be rotated about the upper-left corner of the text box. Unit is in radians.' do |x, y|
    text str: 'Rotated', hint: :red, x: x, y: y, angle: Math::PI / 6
  end

  save_png prefix: '_text_'
end

Sample: text_options.rb

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
# encoding: UTF-8
# require 'squib'
require_relative '../../lib/squib'

data = { 'name' => ['Thief', 'Grifter', 'Mastermind'],
        'level' => [1, 2, 3] }
longtext = "This is left-justified text, with newlines.\nWhat do you know about tweetle beetles? well... When tweetle beetles fight, it's called a tweetle beetle battle. And when they battle in a puddle, it's a tweetle beetle puddle battle. AND when tweetle beetles battle with paddles in a puddle, they call it a tweetle beetle puddle paddle battle. AND... When beetles battle beetles in a puddle paddle battle and the beetle battle puddle is a puddle in a bottle... ..they call this a tweetle beetle bottle puddle paddle battle muddle."

Squib::Deck.new(width: 825, height: 1125, cards: 3) do
  background color: :white
  rect x: 15, y: 15, width: 795, height: 1095, x_radius: 50, y_radius: 50
  rect x: 30, y: 30, width: 128, height: 128, x_radius: 25, y_radius: 25

  # Arrays are rendered over each card
  text str: data['name'], x: 250, y: 55, font: 'Arial weight=900 18'
  text str: data['level'], x: 65, y: 40, font: 'Arial 24', color: :burnt_orange

  text str: 'Font strings are expressive!', x:65, y: 200,
       font: 'Impact bold italic 12'

  text str: 'Font strings are expressive!', x:65, y: 300,
       font: 'Arial,Verdana weight=900 style=oblique 12'

  text str: 'Font string sizes can be overridden per card.', x: 65, y: 350,
       font: 'Impact 12', font_size: [5, 7, 8]

  text str: 'This text has fixed width, fixed height, center-aligned, middle-valigned, and has a red hint',
       hint: :red,
       x: 65, y: 400,
       width: 300, height: 125,
       align: :center, valign: 'MIDDLE', # these can be specified with case-insenstive strings too
       font: 'Serif 5'

  extents = text str: 'Ink extent return value',
       x: 65, y: 550,
       font: 'Sans Bold', font_size: [5, 7, 8]
  margin = 10
  # Extents come back as an array of hashes, which can get split out like this
  ws = extents.inject([]) { |arr, ext| arr << ext[:width] + 10; arr }
  hs = extents.inject([]) { |arr, ext| arr << ext[:height] + 10; arr }
  rect x: 65 - margin / 2, y: 550 - margin / 2,
       width: ws, height: hs,
       radius: 10, stroke_color: :black

  # If width & height are defined and the text will overflow the box, we can ellipsize.
  text str: "Ellipsization!\nThe ultimate question of life, the universe, and everything to life and everything is 42",
       hint: :green, font: 'Arial 7',
       x: 450, y: 400,
       width: 280, height: 180,
       ellipsize: true

  # Text hints are guides for showing you how your text boxes are laid out exactly
  hint text: :cyan
  set font: 'Serif 7' # Impacts all future text calls (unless they specify differently)
  text str: 'Text hints & fonts are globally togglable!', x: 65, y: 625
  set font: :default # back to Squib-wide default
  hint text: :off
  text str: 'See? No hint here.',
        x: 565, y: 625,
        font: 'Arial 7'

  # Text can be rotated, in radians, about the upper-left corner of the text box.
  text str: 'Rotated',
        x: 565, y: 675, angle: 0.2,
        font: 'Arial 6', hint: :red

  # Text can be justified, and have newlines
  text str: longtext, font: 'Arial 5',
       x: 65, y: 700,
       width: '1.5in', height: inches(1),
       justify: true, spacing: -6

  # Here's how you embed images into text.
  # Pass a block to the method call and use the given context
  embed_text = 'Embedded icons! Take 1 :tool: and gain 2:health:. If Level 2, take 2 :tool:'
  text(str: embed_text, font: 'Sans 6',
       x: '1.8in', y: '2.5in', width: '0.85in',
       align: :left, ellipsize: false) do |embed|
    embed.svg key: ':tool:',   width: 28, height: 28, file: 'spanner.svg'
    embed.svg key: ':health:', width: 28, height: 28, file: 'glass-heart.svg'
  end

  text str: 'Fill n <span fgcolor="#ff0000">stroke</span>',
       color: :green, stroke_width: 2.0, stroke_color: :blue,
       x: '1.8in', y: '2.9in', width: '0.85in', font: 'Sans Bold 9', markup: true

  text str: 'Stroke n <span fgcolor="#ff0000">fill</span>',
       color: :green, stroke_width: 2.0, stroke_color: :blue, stroke_strategy: :stroke_first,
       x: '1.8in', y: '3.0in', width: '0.85in', font: 'Sans Bold 9', markup: true

  text str: 'Dotted',
       color: :white, stroke_width: 2.0, dash: '4 2', stroke_color: :black,
       x: '1.8in', y: '3.1in', width: '0.85in', font: 'Sans Bold 9', markup: true
  #
  text str: "<b>Markup</b> is <i>quite</i> <s>'easy'</s> <span fgcolor=\"\#ff0000\">awesome</span>. Can't beat those \"smart\" 'quotes', now with 10--20% more en-dashes --- and em-dashes --- with explicit ellipses too...",
       markup: true,
       x: 50, y: 1000,
       width: 750, height: 100,
       valign: :bottom,
       font: 'Serif 6', hint: :cyan

  save prefix: 'text_options_', format: :png
end

Sample: embed_text.rb

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
require 'squib'

Squib::Deck.new do
  background color: :white
  rect x: 0, y: 0, width: 825, height: 1125, stroke_width: 2.0

  embed_text = 'Take 11 :tool: and gain 2 :health:. Take <b>2</b> :tool: <i>and gain 3 :purse: if level 2.</i>'
  text(str: embed_text, font: 'Sans 7',
       x: 0, y: 0, width: 180, hint: :red,
       align: :left, ellipsize: false, justify: false) do |embed|
    embed.svg key: ':tool:',   width: 28, height: 28, file: 'spanner.svg'
    embed.svg key: ':health:', width: 28, height: 28, file: 'glass-heart.svg'
    embed.png key: ':purse:',  width: 28, height: 28, file: 'shiny-purse.png'
  end

  embed_text = 'Middle align: Take 1 :tool: and gain 2 :health:. Take 2 :tool: and gain 3 :purse:'
  text(str: embed_text, font: 'Sans 7',
       x: 200, y: 0, width: 180, height: 300, valign: :middle,
       align: :left, ellipsize: false, justify: false, hint: :cyan) do |embed|
    embed.svg key: ':tool:',   width: 28, height: 28, file: 'spanner.svg'
    embed.svg key: ':health:', width: 28, height: 28, file: 'glass-heart.svg'
    embed.png key: ':purse:',  width: 28, height: 28, file: 'shiny-purse.png'
  end

  embed_text = 'This :tool: aligns on the bottom properly. :purse:'
  text(str: embed_text, font: 'Sans 7',
       x: 400, y: 0, width: 180, height: 300, valign: :bottom,
       align: :left, ellipsize: false, justify: false, hint: :green) do |embed|
    embed.svg key: ':tool:',   width: 28, height: 28, file: 'spanner.svg'
    embed.svg key: ':health:', width: 28, height: 28, file: 'glass-heart.svg'
    embed.png key: ':purse:',  width: 28, height: 28, file: 'shiny-purse.png'
  end

  embed_text = 'Yes, this wraps strangely. We are trying to determine the cause. These are 1 :tool::tool::tool: and these are multiple :tool::tool: :tool::tool:'
  text(str: embed_text, font: 'Sans 6',
       x: 600, y: 0, width: 180, height: 300, wrap: :word_char,
       align: :left, ellipsize: false, justify: false, hint: :cyan) do |embed|
    embed.svg key: ':tool:', width: 28, height: 28, file: 'spanner.svg'
  end

  embed_text = ':tool:Justify will :tool: work too, and :purse: with more words just for fun'
  text(str: embed_text, font: 'Sans 7',
       x: 0, y: 320, width: 180, height: 300, valign: :bottom,
       align: :left, ellipsize: false, justify: true, hint: :magenta) do |embed|
    embed.svg key: ':tool:',   width: 28, height: 28, file: 'spanner.svg'
    embed.svg key: ':health:', width: 28, height: 28, file: 'glass-heart.svg'
    embed.png key: ':purse:',  width: 28, height: 28, file: 'shiny-purse.png'
  end

  embed_text = 'Right-aligned works :tool: with :health: and :purse:'
  text(str: embed_text, font: 'Sans 7',
       x: 200, y: 320, width: 180, height: 300, valign: :bottom,
       align: :right, ellipsize: false, justify: false, hint: :magenta) do |embed|
    embed.svg key: ':tool:',   width: 28, height: 28, file: 'spanner.svg'
    embed.svg key: ':health:', width: 28, height: 28, file: 'glass-heart.svg'
    embed.png key: ':purse:',  width: 28, height: 28, file: 'shiny-purse.png'
  end

  embed_text = ':tool:Center-aligned works :tool: with :health: and :purse:'
  text(str: embed_text, font: 'Sans 7',
       x: 400, y: 320, width: 180, height: 300,
       align: :center, ellipsize: false, justify: false, hint: :magenta) do |embed|
    embed.svg key: ':tool:',   width: 28, height: 28, data: File.read('spanner.svg')
    embed.svg key: ':health:', width: 28, height: 28, file: 'glass-heart.svg'
    embed.png key: ':purse:',  width: 28, height: 28, file: 'shiny-purse.png'
  end

  embed_text = 'Markup --- and typography replacements --- with ":tool:" icons <i>won\'t</i> fail'
  text(str: embed_text, font: 'Serif 6', markup: true,
       x: 600, y: 320, width: 180, height: 300,
       align: :center, hint: :magenta) do |embed|
    embed.svg key: ':tool:',   width: 28, height: 28, file: 'spanner.svg'
  end

  embed_text = ':tool:' # JUST the icon
  text(str: embed_text, x: 0, y: 640, width: 180, height: 50, markup: true,
     font: 'Arial 7', align: :center, valign: :middle, hint: :red) do |embed|
    embed.svg key: ':tool:', width: 28, height: 28, file: 'spanner.svg'
  end

  embed_text = ':purse:' # JUST the icon
  text(str: embed_text, x: 200, y: 640, width: 180, height: 50, markup: true,
     font: 'Arial 7', align: :center, valign: :middle, hint: :red) do |embed|
    embed.png key: ':purse:', width: 28, height: 28, file: 'shiny-purse.png'
  end

  embed_text = ":tool: Death to Nemesis bug 103!! :purse:"
  text(str: embed_text, font: 'Sans Bold 8', stroke_width: 2,
       color: :red, stroke_color: :blue, dash: '3 3', align: :left,
       valign: :middle, x: 0, y: 700, width: 380, height: 150,
       hint: :magenta) do |embed|
    embed.svg key: ':tool:', file: 'spanner.svg', width: 32, height: 32
    embed.png key: ':purse:', file: 'shiny-purse.png', width: 32, height: 32
  end

  embed_text = 'You can adjust the icon with dx and dy. Normal: :tool: Adjusted: :heart:'
  text(str: embed_text, font: 'Sans 6', x: 400, y: 640, width: 180,
       height: 300, hint: :magenta) do |embed|
    embed.svg key: ':tool:', width: 28, height: 28, file: 'spanner.svg'
    embed.svg key: ':heart:', width: 28, height: 28, dx: 10, dy: 10,
              file: 'glass-heart.svg'
  end

  embed_text = "Native sizes work too\n:tool:\n\n\n\n\n\n:shiny-purse:\n\n\n\n\n\n:tool2:"
  text(str: embed_text, font: 'Sans 6', x: 600, y: 640, width: 180,
       height: 475, hint: :magenta) do |embed|
    embed.svg key: ':tool:', width: :native, height: :native,
              file: 'spanner.svg'
    embed.svg key: ':tool2:', width: :native, height: :native,
              data: File.open('spanner.svg','r').read
    embed.png key: ':shiny-purse:', width: :native, height: :native,
              file: 'shiny-purse.png'
  end

  save_png prefix: 'embed_'
end

Squib::Deck.new(cards: 3) do
  background color: :white
  str = 'Take 1 :tool: and gain 2 :health:.'
  text(str: str, font: 'Sans', font_size: [6, 8.5, 11.5],
       x: 0, y: 0, width: 180, height: 300, valign: :bottom,
       align: :left, ellipsize: false, justify: false, hint: :cyan) do |embed|
    embed.svg key: ':tool:',   width: [28, 42, 56], height: [28, 42, 56], file: 'spanner.svg'
    embed.svg key: ':health:', width: [28, 42, 56], height: [28, 42, 56], file: 'glass-heart.svg'
  end
  save_sheet prefix: 'embed_multisheet_', columns: 3
end

Sample: config_text_markup.rb

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
require 'squib'

Squib::Deck.new(config: 'config_text_markup.yml') do
  background color: :white
  text str: %{"'Yaml ain't markup', he says"},
       x: 10, y: 10, width: 300, height: 200, font: 'Serif 7',
       markup: true, hint: :cyan

  text str: 'Notice also the antialiasing method.',
       x: 320, y: 10, width: 300, height: 200, font: 'Arial Bold 7'

  save_png prefix: 'config_text_'
end

Squib::Deck.new(config: 'config_disable_quotes.yml') do
  text str: %{This has typographic sugar --- and ``explicit'' quotes --- but the quotes are "dumb"},
       x: 10, y: 10, width: 300, height: 200, font: 'Serif 7',
       markup: true, hint: :cyan
  save_png prefix: 'config_disable_text_'
end
1
2
3
4
5
6
7
8
9
# We can configure what characters actually get replaced by quoting them with unicode code points.
lsquote: "\u2018" #note that Yaml wants double quotes here to use escape chars
rsquote: "\u2019"
ldquote: "\u201C"
rdquote: "\u201D"
em_dash: "\u2014"
en_dash: "\u2013"
ellipsis: "\u2026"
antialias: gray
1
2
3
# If we want to disable smart quoting and only allow explicit quoting within markup, 
# use this option
smart_quotes: false

Sample: _autoscale_font.rb

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
require 'squib'

# Autoscaling font is handy for a bunch of things:
#  * Picture-perfect text fitting for one-off
#  * Rapid prototyping where you don't have to think about sizes
#
# We've got three options...
#  Option 1. Use your data <--- good for picture-perfect
#  Option 2. Use ellipsize: :autoscale <--- good for rapid prototyping
#  Option 3. Use map ranges in your code <--- good for picture-perfect
#                                             or other weird cases

###########################
# Option 1: Use your data #
###########################
# If you want to tweak the font size per-card, you can always make font_size
# a column and map it from there. This is tedious but leads to perfectly
# customized results
my_data = Squib.csv data: <<~CSV
  "Title","Font Size"
  "Short & Big",10
  "Medium Length & Size", 5
  "Super duper long string here, therefore a smaller font.", 4
CSV

Squib::Deck.new(width: 300, height: 75, cards: 3) do
  background color: :white
  rect stroke_color: :black

  text str: my_data.title, font: 'Arial',
       font_size: my_data.font_size, # <-- key part
       x: 10, y:10, align: :center,
       width: 280, # <-- note how height does NOT need to be set
       ellipsize: false,
       hint: :red
  save_sheet columns: 3, prefix: 'autoscale_w_data_'
end

#######################################
# Option 2: Use ellipsize: :autoscale #
#######################################
# If set the height, you can set "autoscale" and it will incrementally
# downgrade the font size until the text does not ellipsize
#
# Great for rapid prototyping, set-it-and-forget-it
#
# NOTE: You MUST set the height for this to work. Otherwise, the text will
#       never ellipsize and Squib doesn't know when to start autoscaling
Squib::Deck.new(width: 300, height: 75, cards: 3) do
  background color: :white
  rect stroke_color: :black
  title = ['Short & Big',
           'Medium Length & Size',
           'Super duper long string here, therefore a smaller font.']

  # Automatically scale the text down from the specified font_size to the largest size that fits
  text str: title, font: 'Arial',
       font_size: 15, # <-- this is the MAX font size. Scale down from here
       ellipsize: :autoscale, # <-- key part
       height: 50, # <-- need this to be set to something
       width: 280, x: 10, y: 10, align: :center, valign: :middle, hint: :red

  save_sheet columns: 3, prefix: 'autoscale_w_ellipsize_'
end

############################################
# Option 3: Mapping to ranges in your code #
############################################
# Here's an in-between option that allows you to programmatically apply font
# sizes. This allows you a ton of flexibility.Probably more flexibility than
# you need, frankly. But one advantage is that you don't have to set the height
def autoscale(str_array)
  str_array.map do | str |
    case str.length
    when 0..15
      9
    when 16..20
      6
    else
      4
    end
  end
end

Squib::Deck.new(width: 300, height: 75, cards: 3) do
  background color: :white
  rect stroke_color: :black
  title = ['Short & Big',
           'Medium Length & Size',
           'Super duper long string here, therefore a smaller font.']

  # Scale text based on the string length
  text str: title, font: 'Arial',
       font_size: autoscale(title), # <-- key part
       x: 10, y:10, align: :center, width: 280, ellipsize: false, hint: :red

    save_sheet columns: 3, prefix: 'autoscale_w_range_'
end

Always Have Bleed

Summary

  • Always plan to have a printing bleed around the edge of your cards. 1/8 inches is standard.
  • Have a safe zone too
  • Layouts make this easy (see the built-in layouts)
  • Can use png overlays from templates to make sure it fits
  • Trim option is what is used everywhere in Squib
  • Trim_radius also lets you show your cards off like how they’ll really look

An example is also shown in The Squib Way pt 1: Zero to Game.

What is a bleed?

As mentioned in the summary above, we mostly add a cut and a safe rectangle on cards for guides based on the poker card templates like the one from TheGameCrafter.com ( PDF template). See below section templates for more templates as illustration and help. This is important to do as early as possible because this affects the whole layout. In most print-on-demand templates, we have a 1/8-inch border that is larger than what is to be used, and will be cut down (called a bleed). Rather than have to change all our coordinates later, let’s build that right into our initial prototype in the first stage. Squib can trim around these bleeds for things like showcase, hand, save_sheet, save_png, and save_pdf.

See for more details about a discussion whether or not to use a bleed on boardgamegeek.com and specifically one of the comments.

  • Crop - the area which is going to be cut out to meet the final size requirements.
  • Bleed - an extra printed area outside of the crop, to ensure that the printing runs all the way to the edge of the printed, cut piece.

Usage

We can add such cut and safe zones with the available DSL methods cut_zone and safe_zone or by using a layout which specifies the cut and safe zone which are later used with the layout option for the rect method.

Layout:

cut:
  x: 0.125in
  y: 0.125in
  width: 2.5in
  height: 3.5in
  radius: 0

In your Squib script:

rect layout: 'cut', stroke_color: :white

Or by using the DSL methods:

cut_zone radius: 0,  stroke_color: :white
safe_zone radius: 0, stroke_color: :red

Afterwards, you can save the whole card including the bleed or without the bleed by using the mentioned trim method without affecting all other parts of your layout:

save_png ... trim: '0.125in' ...
save_pdf sprue: 'drivethrucards_1up.yml', trim: '0.125in'

Example

In this example we see the cut zone and the safe zone inside the bleed.

Configuration Options

Squib supports various configuration properties that can be specified in an external file. By default, Squib looks for a file called config.yml in the current directory. Or, you can set the config: option in Deck.new to specify the name of the configuration file.

These properties are intended to be immutable for the life of the Deck, and intended to configure how Squib behaves.

The options include:

progress_bars

default: false

When set to true, long-running operations will show a progress bar in the console

hint

default: :off

Text hints are used to show the boundaries of text boxes. Can be enabled/disabled for individual commands, or set globally with the hint method. This setting is overridden by hint (and subsequently individual text).

custom_colors

default: {}

Defines globally-available named colors available to the deck. Must be specified as a hash in yaml. For example:

# config.yml
custom_colors:
  fg: '#abc'
  bg: '#def'
antialias

default: 'best'

Set the algorithm that Cairo will use for anti-aliasing throughout its rendering. Available options are fast, good, best, none, gray, subpixel.

Not every option is available on every platform. Using our benchmarks on large decks, best is only ~10% slower anyway. For more info see the Cairo docs.

backend

default: 'memory'

Defines how Cairo will store the operations. Can be svg or memory. See Vector vs. Raster Backends.

prefix

default: 'card_'

When using an SVG backend, cards are auto-saved with this prefix and '%02d' numbering format.

img_dir

default: '.'

For reading image file command (e.g. png and svg), read from this directory instead

img_missing:

default: :warn

Log a warning if an image file is missing. This option is only consulted if the following are true:

  • If the file specified for an input image (e.g. png or svg) does not exist,
  • AND a placeholder image does not exist
Other options:
  • error - raise a RuntimeError and halt the entire build.
  • silent - do nothing, log nothing, and act as if the file was nil
warn_ellipsize

default: true

Show a warning on the console when text is ellipsized. Warning is issued per card.

warn_png_scale

default: true

Show a warning on the console when a PNG file is upscaled. Warning is issued per card.

lsquote

default: "\u2018"

Smart quoting: change the left single quote when markup: true

rsquote

default: "\u2019"

Smart quoting: change the right single quote when markup: true

ldquote

default: "\u201C"

Smart quoting: change the left double quote when markup: true

rdquote

default: "\u201D"

Smart quoting: change the right double quote when markup: true

em_dash

default: "\u2014"

Convert the -- to this character when markup: true

en_dash

default: "\u2013"

Convert the --- to this character when markup: true

ellipsis

default: "\u2026"

Convert ... to this character when markup: true

smart_quotes

default: true

When markup: true, the text method will convert quotes. With smart_quotes: false, explicit replacements like em-dashes and en-dashes will be replaced but not smart quotes.

cell_px

default: 37.5

The number of pixels that the “cell” custom unit is set to. See Unit Conversion

Options are available as methods

For debugging/sanity purposes, if you want to make sure your configuration options are parsed correctly, the above options are also available as methods within Squib::Deck, for example:

Squib::Deck.new do
  puts backend # prints 'memory' by default
end

These are read-only - you will not be able to change these.

Set options programmatically

You can also use Squib.configure to override anything in the config file. Use it like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# This is a sample Rakefile
require 'squib'

desc 'Build all decks black-and-white'
task default: [:characters, :skills]

desc 'Build all decks with color'
task color: [:with_color, :default]

desc 'Enable color build'
task :with_color do
  puts "Enabling color build"
  Squib.configure img_dir: 'color'
end

desc 'Build the character deck'
task :characters do
  puts "Building characters"
  load 'src/characters.rb'
end

desc 'Build the skills deck'
task :skills do
  puts "Building skills"
  load 'src/skills.rb'
end

See The Squib Way pt 3: Workflows for how we put this to good use.

Making Squib Verbose

By default, Squib’s logger is set to WARN, but more fine-grained logging is embedded in the code. To set the logger, just put this at the top of your script:

Squib::logger.level = Logger::INFO

If you REALLY want to see tons of output, you can also set DEBUG, but that’s not intended for general consumption.

Vector vs. Raster Backends

Squib’s graphics rendering engine, Cairo, has the ability to support a variety of surfaces to draw on, including both raster images stored in memory and vectors stored in SVG files. Thus, Squib supports the ability to handle both. They are options in the configuration file backend: memory or backend: svg described in Configuration Options.

If you use save_pdf then this backend option will determine how your cards are saved too. For memory, the PDF will be filled with compressed raster images and be a larger file (yet it will still print at high quality… see discussion below). For SVG backends, PDFs will be smaller. If you have your deck backed by SVG, then the cards are auto-saved, so there is no save_svg in Squib. (Technically, the operations are stored and then flushed to the SVG file at the very end.)

There are trade-offs to consider here.

  • Print quality is usually higher for raster images. This seems counterintuitive at first, but consider where Squib sits in your workflow. It’s the final assembly line for your cards before they get printed. Cairo puts a ton of work into rendering each pixel perfectly when it works with raster images. Printers, on the other hand, don’t think in vectors and will render your paths in their own memory with their own embedded libraries without putting a lot of work into antialiasing and various other graphical esoterica. You may notice that print-on-demand companies such as The Game Crafter only accept raster file types, because they don’t want their customers complaining about their printers not rendering vectors with enough care.
  • Print quality is sometimes higher for vector images, particularly in laser printers. We have noticed this on a few printers, so it’s worth testing out.
  • PDFs are smaller for SVG back ends. If file size is a limitation for you, and it can be for some printers or internet forums, then an SVG back end for vectorized PDFs is the way to go.
  • Squib is greedy with memory. While I’ve tested Squib with big decks on older computers, the memory backend is quite greedy with RAM. If memory is at a premium for you, switching to SVG might help.
  • Squib does not support every feature with SVG back ends. There are some nasty corner cases here. If it doesn’t, please file an issue so we can look into it. Not every feature in Cairo perfectly translates to SVG.

Note

You can still load PNGs into an SVG-backed deck and load SVGs into a memory-backed deck. To me, the sweet spot is to keep all of my icons, text, and other stuff in vector form for infinite scaling and then render them all to pixels with Squib.

Fortunately, switching backends in Squib is as trivial as changing the setting in the config file (see Configuration Options). So go ahead and experiment with both and see what works for you.

Group Your Builds

Often in the prototyping process you’ll find yourself cycling between running your overall build and building a single card. You’ll probably be commenting out code in the process.

And even after your code is stable, you’ll probably want to build your deck multiple ways: maybe a printer-friendly black-and-white version for print-and-play and then a full color version.

Squib’s Build Groups help you with these situations. By grouping your Squib code into different groups, you can run parts of it at a time without having to go back and commenting out code.

Here’s a basic example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
require 'squib'

Squib::Deck.new(width: 75, height: 75, cards: 2) do
  # puts "Groups enabled by environment: #{groups.to_a}"

  text str: ['A', 'B']

  build :print_n_play do
    rect
    save_sheet prefix: 'build_groups_bw_'
  end

  build :color do
    rect stroke_color: :red, dash: '5 5'
    save_png prefix: 'build_groups_color_'
  end

  build :test do
    save_png range: 0, prefix: 'build_groups_'
  end

end

# Here's how you can run this on the command line:
#
# --- OSX/Linux (bash or similar shells) ---
# $ ruby build_groups.rb
# $ SQUIB_BUILD=color ruby build_groups.rb
# $ SQUIB_BUILD=print_n_play,test ruby build_groups.rb
#
# --- Windows CMD ---
# $ ruby build_groups.rb
# $ set SQUIB_BUILD=color && ruby build_groups.rb
# $ set SQUIB_BUILD=print_n_play,test && ruby build_groups.rb
#
# Or, better yet... use a Rakefile like the one provided in this gist!

Only one group is enabled by default: :all. All other groups are disabled by default. To see which groups are enabled currently, the build_groups returns the set.

Groups can be enabled and disabled in several ways:

  • The enable_build and disable_build DSL methods within a given Squib::Deck can explicitly enable/disable a group. Again, you’re back to commenting out the enable_group call, but that’s easier than remembering what lines to comment out each time.
  • When a Squib::Deck is initialized, the environment variable SQUIB_BUILD is consulted for a comma-separated string. These are converted to Ruby symbols and the corresponding groups are enabled. This is handy for enabling builds on the command line (e.g. turn on color, work in that for a while, then turn it off)
  • Furthermore, you can use Squib.enable_build_globally and Squib.disable_build_globally to manipulate SQUIB_BUILD environment variable programmatically (e.g. from a Rakefile, inside a Guard session, or other build script).

The The Squib Way pt 3: Workflows tutorial covers how to work these features into your workflow.

Note

There should be no need to set the SQUIB_BUILD variable globally on your system (e.g. at boot). The intent is to set SQUIB_BUILD as part of your session.

One adaptation of this is to do the environment setting in a Rakefile. Rake is the build utility that comes with Ruby, and it allows us to set different tasks exactly in this way. This Rakefile works nicely with our above code example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Example Rakefile that makes use of build groups
require 'squib'

desc 'Build black-and-white by default'
task default: [:pnp]

desc 'Build both bw and color'
task both: [:pnp, :color]

desc 'Build black-and-white only'
task :pnp do
  Squib.enable_build_globally :print_n_play
  load 'build_groups.rb'
end

desc 'Build the color version only'
task :color do
  Squib.enable_build_globally :color
  load 'build_groups.rb'
end

desc 'Build a test card'
task :test do
  Squib.enable_build_globally :test
  load 'build_groups.rb'
end

Thus, you can just run this code on the command line like these:

$ rake
$ rake pnp
$ rake color
$ rake test
$ rake both

Sprue Thy Sheets

A sprue is a Squib feature that customizes how cards get arranged on a sheet as part of save_pdf or save_sheet (PNG). This is particularly useful for:

  • Working with specific dies
  • Cutting out hex chits with fewer cuts
  • Working with quirky duplex printing
  • Printing front-to-back and having a fold in the middle
  • Printing to specific sticker sheets
  • Making an A4 version and a US Letter version for i18n

Without using a sprue, save_pdf and save_sheet will simply lay out your cards one after another with the gap and margins you specify.

Using a Sprue

The easiest way to get started is to use our library of built-in sprues for various default paper sizes and card sizes. See List of Built-in Sprues below.

Sprues are specified in Yaml, just like layouts. To use a built-in sprue, just specify the name and Squib will use that:

save_sheet sprue: 'letter_poker_card_9up.yml'

Here’s a full example from here:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
require 'squib'

Squib::Deck.new(cards: 9) do
  background color: :white
  rect stroke_width: 5, stroke_color: :red
  text str: (0..9).map{ |i| "Card #{i}\n2.5x3.5in" },
       font: 'Sans 32', align: :center, valign: :middle,
       height: :deck, width: :deck
  save_sheet sprue: 'letter_poker_card_9up.yml',
             prefix: "sprue_example_"
end

Make Your Own Sprue

Need a special one? You can make a sprue file yourself following the Sprue Format below, or you can generate one from the command line.

Note

Would someone else find your sprue useful? Contribute one!! This is an easy way to help out the Squib community.

Squib comes with a handy little command-line interface that will generate a sprue file based on your own parameters. Of course, you can edit the sprue file once you’re done to fix any quirks you like.

Here’s an example run:

$ squib make_sprue
1. in
2. cm
3. mm
What measure unit should we use? 1
1. A4, portrait
2. A4, landscape
3. US letter, portrait
4. US letter, landscape
5. Custom size
What paper size you are using? 3
Sheet margins? (in) 0.125
1. left
2. right
3. center
How to align cards on sheet? [left] 3
Card width? (in) 2.5
Card height? (in) 3.5
Gap between cards? (in) 0
1. rows
2. columns
How to layout your cards? [rows]
1
1. true
2. false
Generate crop lines? [true]
1
Output to? |sprue.yml| my_sprue.yml

Of course, a Squib sprue is just a YAML file with a specific structure. Here’s an example (generated from the run above):

---
sheet_width: 8.5in
sheet_height: 11in
card_width: 2.5in
card_height: 3.5in
cards:
- x: 0.5in
  y: 0.125in
- x: 3.0in
  y: 0.125in
- x: 5.5in
  y: 0.125in
- x: 0.5in
  y: 3.625in
- x: 3.0in
  y: 3.625in
- x: 5.5in
  y: 3.625in
- x: 0.5in
  y: 7.125in
- x: 3.0in
  y: 7.125in
- x: 5.5in
  y: 7.125in
crop_line:
  lines:
  - type: :vertical
    position: 0.5in
  - type: :vertical
    position: 3.0in
  - type: :vertical
    position: 5.5in
  - type: :vertical
    position: 8.0in
  - type: :horizontal
    position: 0.125in
  - type: :horizontal
    position: 3.625in
  - type: :horizontal
    position: 7.125in
  - type: :horizontal
    position: 10.625in

Sprue Format

Here are the options for the sprue file. The entire structure of the Yaml file is a Hash

Top-Level parameters

sheet_width
Width of the sheet, supports Unit Conversion.
sheet_height
Width of the sheet, supports Unit Conversion.
card_width
Intended width of the card. Sprues will allow any size of card. Squib will check for potential overlaps, and will warn you if the deck card width is greater than the Sprue’s expected card width (this option). If there is overlap detected Squib will send out a warning to stdout. Supports Unit Conversion.
card_height
Intended height of the card. Sprues will allow any size of card. Squib will check for potential overlaps, and will warn you if the deck card height is greater than the Sprue’s expected card height (this option). If there is overlap detected Squib will send out a warning to stdout. Supports Unit Conversion.
position_reference

Default: :topleft

Can be :topleft or :center. Are the card coordinates refer to the top-left of the card, or the middle of the card? (Don’t forget that colon!)

cards

At the top-level should be a cards key. Within that is an array of hashes that contain the x-y coordinates to indicate the locations of the cards. The order of the coordinates indicates the order of the cards that will be laid out. When there are more cards in the deck than the number of x-y coordinates in the list, a new “page” will be made (i.e. a new page in the PDF or a new sheet for PNG sheets).

x
Horizontal distance card from the left side of the page. Supports Unit Conversion.
y
Vertical distance card from the top side of the page. Supports Unit Conversion.
rotate

Default: 0 (none)

Rotate the card around its position_reference. Allows clockwise, counterclockwise, or turnaround, or numerical angle.

flip_vertical

Default: false

Determine whether or not to flip the card vertically about the vertical line through the card’s its position reference. Works most intuitively with position_reference: :center

flip_horizontal

Default: false

Determine whether or not to flip the card vertically about the horizontal line through the card’s its position reference. Works most intuitively with position_reference: :center

crop_line

Optionally, at the top-level you can specify lines to be drawn.

style
Values: solid,``dotted``,``dashed``
width
The stroke width of the crop line. Supports Unit Conversion.
color
The stroke color of the crop line, using Specifying Colors & Gradients
overlay

Values: on_margin, overlay_on_cards, beneath_cards.

Specifies how the crop line is drawn: before drawing cards, after drawing cards, or only in the margins.

lines
A hash that has the following options below
type
Values: horizontal or vertical
position
The x-position or y-position of the crop line (depending on type)

List of Built-in Sprues

Here’s a list of built-in sprues that come with Squib. You can get the original YAML files on GitHub here.

a4_euro_card.yml

a4_poker_card_8up.yml

a4_usa_card.yml

letter_poker_card_9up.yml

letter_poker_foldable_8up.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
require 'squib'

# Note that this sample has no bleed - you might want to have bleed
# but tighter than usual: 0.1 instead of 0.125in since this sprue is
# crowded horizontally on US letter
Squib::Deck.new(width: '2.5in', height: '3.5in', cards: 8) do
  background color: :white
  rect stroke_width: 5, stroke_color: :red
  # Note that we are interleaving strings
  # This could be used as a secondary Squib script that loads
  # Squib-generated individual images
  strings = [
    "Front 1",
    "Back 1",
    "Front 2",
    "Back 2",
    "Front 3",
    "Back 3",
    "Front 4",
    "Back 4",
  ]

  text str: strings,font: 'Sans 32', align: :center, valign: :middle,
       height: :deck, width: :deck
       
  save_sheet prefix: 'foldable_',
             sprue: 'letter_poker_foldable_8up.yml' # built-in sprue
  save_pdf file: 'foldable.pdf',
           sprue: 'letter_poker_foldable_8up.yml' # built-in sprue
end

printplaygames_18up.yml

drivethrucards_1up.yml

Get Help and Give Help

Show Your Pride

On BoardGameGeek.com? Show your Squib pride by getting the microbadge and joining our guild!

We would also love to hear about the games you make with Squib!

Get Help

Squib is powerful and customizable, which means it can get complicated pretty quickly. Don’t settle for being stuck.

Here’s an ordered list of how to find help:

  1. Go through this documentation
  2. Go through the wiki
  3. Go through the samples
  4. Google it - people have asked lots of questions about Squib already in many forums.
  5. Ask on Stackoverflow using the tags “ruby” and “squib”. You will get answers quickly from Ruby programmers it’s and a great way for us to archive questions for future Squibbers.
  6. Our thread on BoardGameGeek or our guild is quite active and informal (if a bit unstructured).

If you email me directly I’ll probably ask you to post your question publicly so we can document answers for future Googling Squibbers.

Please use GitHub issues for bugs and feature requests.

Help by Troubleshooting

One of the best ways you can help the Squib community is to be active on the above forums. Help people out. Answer questions. Share your code. Most of those forums have a “subscribe” feature.

You can also watch the project on GitHub, which means you get notified when new bugs and features are entered. Try reproducing code on your own machine to confirm a bug. Help write minimal test cases. Suggest workarounds.

Help by Beta Testing

Squib is a small operation. And programming is hard. So we need testers! In particular, I could use help from people to do the following:

  • Test out new features as I write them
  • Watch for regression bugs by running their current projects on new Squib code, checking for compatibility issues.

Want to join the mailing list and get notifications? https://groups.google.com/forum/#!forum/squib-testers

There’s no time commitment expectation associated with signing up. Any help you can give is appreciated!

Beta: Using Pre-Builds

The preferred way of doing beta testing is by to get Squib directly from my GitHub repository. Bundler makes this easy.

If you are just starting out you’ll need to install bundler:

$ gem install bundler

Then, in the root of your Squib project, create a file called Gemfile (capitalization counts). Put this in it:

source 'https://rubygems.org'

gem 'squib', git: 'git://github.com/andymeneely/squib', branch: 'master'

Then run:

$ bundle install

Your output will look something like this:

Fetching git://github.com/andymeneely/squib
Fetching gem metadata from https://rubygems.org/.........
Fetching version metadata from https://rubygems.org/...
Fetching dependency metadata from https://rubygems.org/..
Resolving dependencies...
Using pkg-config 1.1.6
Using cairo 1.14.3
Using glib2 3.0.7
Using gdk_pixbuf2 3.0.7
Using mercenary 0.3.5
Using mini_portile2 2.0.0
Using nokogiri 1.6.7
Using pango 3.0.7
Using rubyzip 1.1.7
Using roo 2.3.0
Using rsvg2 3.0.7
Using ruby-progressbar 1.7.5
Using squib 0.9.0b from git://github.com/andymeneely/squib (at master)
Using bundler 1.10.6
Bundle complete! 1 Gemfile dependency, 14 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.

To double-check that you’re using the test version of Squib, puts this in your code:

require 'squib'
puts Squib::VERSION # prints the Squib version to the console when you run this code

# Rest of your Squib code...

When you run your code, say deck.rb, you’ll need to put bundle exec in front of it. Otherwise Ruby will just go with full releases (e.g. 0.8 instead of pre-releases, e.g. 0.9a). That would look like this:

$ bundle exec ruby deck.rb

If you need to know the exact commit of the build, you can see that commit hash in the generated Gemfile.lock. That revision field will tell you the exact version you’re using, which can be helpful for debugging. That will look something like this:

remote: git://github.com/andymeneely/squib
  revision: 440a8628ed83b24987b9f6af66ad9a6e6032e781
  branch: master

To update to the latest from the repository, run bundle up.

To remove Squib versions, run gem cleanup squib. This will also remove old Squib releases.

Beta: About versions

  • When the version ends in “a” (e.g. v0.9a), then the build is “alpha”. I could be putting in new code all the time without bumping the version. I try to keep things as stable after every commit, but this is considered the least stable code. (Testing still appreciated here, though.) This is also tracked by my dev branch.
  • For versions ending in “b” (e.g. v0.9b), then the build is in “beta”. Features are frozen until release, and we’re just looking for bug fixes. This tends to be tracked by the master branch in my repository.
  • I follow the Semantic Versioning as best I can

Beta: About Bundler+RubyGems

The Gemfile is a configuration file (technically it’s a Ruby DSL) for a widely-used library in the Ruby community called Bundler. Bundler is a way of managing multiple RubyGems at once, and specifying exactly what you want.

Bundler is different from RubyGems. Technically, you CAN use RubyGems without Bundler: just gem install what you need and your require statements will work. BUT Bundler helps you specify versions with the Gemfile, and where to get your gems. If you’re switching between different versions of gems (like with being tester!), then Bundler is the way to go. The Bundler website is here: http://bundler.io/.

By convention, your Gemfile should be in the root directory of your project. If you did squib new, there will be one created by default. Normally, a Squib project Gemfile will look like this. That configuration just pulls the Squib from RubyGems.

But, as a tester, you’ll want to have Bundler install Squib from my repository. That would look like this: https://github.com/andymeneely/project-spider-monkey/blob/master/Gemfile. (Just line 4 - ignore the other stuff.) I tend to work with two main branches - dev and master. Master is more stable, dev is more bleeding edge. Problems in the master branch will be a surprise to me, problems in the dev branch probably won’t surprise me.

After changing your Gemfile, you’ll need to run bundle install. That will generate a Gemfile.lock file - that’s Bundler’s way of saying exactly what it’s planning on using. You don’t modify the Gemfile.lock, but you can look at it to see what version of Squib it’s locked onto.

Make a Sprue!

Do you have a sprue file that lays out cards in sheets in a way others might use? Perhaps for a specific type of sticker sheet, die cutter, duplex, or other situation. If you think there is one other person in the world who would appreciate it, we would love to have it in Squib!

The easiest way to do this to create a new GitHub Issue and just copy the Sprue file in.

Or, better yet, you can do a pull request! Add the sprue file to the /lib/squib/builtin/sprues folder.

Make a Layout!

Do you have any layouts that others might use? Maybe based on print-on-demand templates. Or take a game that you think has a great card layout and reverse-engineer it for others. Don’t be shy!

The easiest way to do this to create a new GitHub Issue and just copy the layout yaml file in.

Or, better yet, you can do a pull request! Add the file to the /lib/squib/layouts folder.

Help by Fixing Bugs

A great way to make yourself known in the community is to go over our backlog and work on fixing bugs. Even suggestions on troubleshooting what’s going on (e.g. trying it out on different OS versions) can be a big help.

Help by Contributing Code

Our biggest needs are in community support. But, if you happen to have some code to contribute, follow this process:

  1. Fork the git repository ( https://github.com/[my-github-username]/squib/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Be sure to write tests and samples for new features.

Be sure to run the unit tests and packaging with just rake. Also, you can check that the samples render properly with rake sanity.

CLI Reference

Below are the command-line interfaces to Squib.

squib make_sprue

Initiates an interactive command line tool that generates a sprue YAML file based on a few choices. See Sprue Thy Sheets for a full example.

squib new

Generate a basic Squib project layout.

While Squib is just a Ruby library that can be used in all kinds of ways, there a bunch of tried-and-true techniques on how to use it. To make this process easier, we provide some built-in project starter kits.

Basic

The default run will generate a “basic” layout:

$ squib new arctic-lemming

This kit includes the following:

  • deck.rb is your main source code file
  • config.yml is the default config file for all runs of Squib
  • layout.yml is a basic layout file
  • Rakefile has a basic rake task (if you’re familiar with that - it’s not necessary).
  • Git files, such as .gitignore, and gitkeep.txt. These are helpful because every coding project should use version control.
  • Markdown files, e.g. IDEAS.md, RULES.md, PLAYTESTING.md, and ABOUT.md, to write notes in and organize your ideas.

You can see the basic files here on GitHub

--advanced

If you run with the --advanced flag, you get a lot more stuff:

$ squib new --advanced arctic-lemming

In particular, the --advanced run will all run through Ruby’s rake command. You will no longer be able to run your ruby scripts directly with ruby deck.rb, instead you’ll need to do rake. See The Squib Way pt 3: Workflows for a walkthrough.

Addtionally, everything else is broken out into directories.

  • We assume you will have a LOT of images, so there’s a separate image directory configured
  • A default Excel sheet is included, in its own data
  • The Rakefile is much more advanced, taking advantage of build groups and various other things
  • Assume one layout file per deck
  • Separate the game documents (e.g. RULES.md) from the project documents (e.g. IDEAS.md)
  • We also include a Guardfile so you can auto-build your code as you are writing it.
  • We include a version file so you can track the version of your code and print that on all of your cards. It’s a good idea to have this in its own file.
  • The default deck.rb demonstrates some more advanced features of Squib

You can see the advanced project kit files here on GitHub

DSL Reference

Squib::Deck.new

The main interface to Squib. Yields to a block that is used for most of Squib’s operations. The majority of the DSL methods are instance methods of Squib::Deck.

Options

These options set immutable properties for the life of the deck. They are not intended to be changed in the middle of Squib’s operation.

width

default: 825

the width of each card in pixels, including bleed. Supports Unit Conversion (e.g. '2.5in').

height

default: 1125

the height of each card in pixels, including bleed. Supports Unit Conversion (e.g. ‘3.5in’).

cards

default: 1

the number of cards in the deck

dpi

default: 300

the pixels per inch when rendering out to PDF, doing Unit Conversion, or other operations that require measurement.

config

default: 'config.yml'

the file used for global settings of this deck, see Configuration Options. If the file is not found, Squib does not complain.

Note

Since this option has config.yml as a default, then Squib automatically looks up a config.yml in the current working directory.

layout

default: nil

load a YML file of custom layouts. Multiple files in an array are merged sequentially, redefining collisons in the merge process. If no layouts are found relative to the current working directory, then Squib checks for a built-in layout.

Examples

background

Fills the background with the given color

Options

All of these options support arrays and singleton expansion (except for range). See Squib Thinks in Arrays for deeper explanation.

range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

color

default: :black

the color or gradient to fill the background with. See Specifying Colors & Gradients.

Examples

build

Establish a set of commands that can be enabled/disabled together to allow for customized builds. See Group Your Builds for ways to use this effectively.

Required Arguments

Note

This is an argument, not an option like most DSL methods. See example below.

group

default: :all

The name of the build group. Convention is to use a Ruby symbol.

&block
When this group is enabled (and only :all is enabled by default), then this block is executed. Otherwise, the block is ignored.

Examples

Use group to organize your Squib code into build groups:

Squib::Deck.new do
  build :pnp do
    save_pdf
  end
end

build_groups

Returns the set of group names that have been enabled. See Group Your Builds for ways to use this effectively.

Arguments

(none)

Examples

Use group to organize your Squib code into build groups:

Squib::Deck.new do
  enable_build :pnp
  build :pnp do
    save_pdf
  end
  puts build_groups # outputs :all and :pnp
end

circle

Draw a partial or complete circle centered at the given coordinates

Options

All of these options support arrays and singleton expansion (except for range). See Squib Thinks in Arrays for deeper explanation.

x

default: 0

the x-coordinate to place, relative to the upper-left corner of the card and moving right as it increases. Supports Unit Conversion and XYWH Shorthands.

y

default: 0

the y-coordinate to place, relative to the upper-left corner of the card and moving downward as it increases. Supports Unit Conversion and XYWH Shorthands.

radius

default: 100

radius of the circle. Supports Unit Conversion.

arc_start

default: 0

angle on the circle to start an arc

arc_end

default: 2 * Math::PI

angle on the circle to end an arc

arc_direction

default: :clockwise

draw the arc clockwise or counterclockwise from arc_start to arc_end

arc_close

default: false

draw a straight line closing the arc

fill_color

default: '#0000' (fully transparent)

the color or gradient to fill with. See Specifying Colors & Gradients.

stroke_color

default: :black

the color with which to stroke the outside of the shape. See Specifying Colors & Gradients.

stroke_width

default: 2

the width of the outside stroke. Supports Unit Conversion.

stroke_strategy

default: :fill_first

Specify whether the stroke is done before (thinner) or after (thicker) filling the shape.

Must be either :fill_first or :stroke_first (or their string equivalents).

dash

default: '' (no dash pattern set)

Define a dash pattern for the stroke. This is a special string with space-separated numbers that define the pattern of on-and-off alternating strokes, measured in pixels or units. For example, '0.02in 0.02in' will be an equal on-and-off dash pattern. Supports Unit Conversion.

cap

default: :butt

Define how the end of the stroke is drawn. Options are :square, :butt, and :round (or string equivalents of those).

join

default: :mitre

Specifies how to render the junction of two lines when stroking. Options are :mitre, :round, and :bevel.

range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

layout

default: nil

entry in the layout to use as defaults for this command. See Layouts are Squib’s Best Feature.

Examples

This snippet and others like it live here
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
require 'squib'

Squib::Deck.new do
  background color: :white

  grid x: 10, y: 10, width: 50,  height: 50,  stroke_color: '#0066FF', stroke_width: 1.5
  grid x: 10, y: 10, width: 200, height: 200, stroke_color: '#0066FF', stroke_width: 3

  rect x: 305, y: 105, width: 200, height: 50, dash: '4 2'

  rect x: 300, y: 300, width: 400, height: 400,
       fill_color: :blue, stroke_color: :red, stroke_width: 50.0,
       join: 'bevel'

  rect x: 550, y: 105, width: 100, height: 100,
       stroke_width: 5, stroke_color: :orange, angle: -0.2

  ellipse x: 675, y: 105, width: 65, height: 100,
       stroke_width: 5, stroke_color: :orange, angle: -0.2

  text str: 'Shapes!' # regression test for bug 248

  circle x: 450, y: 600, radius: 75,
         fill_color: :gray, stroke_color: :green, stroke_width: 8.0

  circle x: 600, y: 600, radius: 75, # partial circle
         arc_start: 1, arc_end: 4, arc_direction: :counter_clockwise,
         fill_color: :gray, stroke_color: :green, stroke_width: 8.0

  triangle x1: 50, y1: 50,
           x2: 150, y2: 150,
           x3: 75, y3: 250,
           fill_color: :gray, stroke_color: :green, stroke_width: 3.0

  line x1: 50, y1: 550,
       x2: 150, y2: 650,
       stroke_width: 25.0

  curve x1: 50,  y1: 850, cx1: 150, cy1: 700,
        x2: 625, y2: 900, cx2: 150, cy2: 700,
        stroke_width: 12.0, stroke_color: :cyan,
        fill_color: :burgundy, cap: 'round'

  ellipse x: 50, y: 925, width: 200, height: 100,
          stroke_width: 5.0, stroke_color: :cyan,
          fill_color: :burgundy

  star x: 300, y: 1000, n: 5, inner_radius: 15, outer_radius: 40,
       fill_color: :cyan, stroke_color: :burgundy, stroke_width: 5

  # default draw is fill-then-stroke. Can be changed to stroke-then-fill
  star x: 375, y: 1000, n: 5, inner_radius: 15, outer_radius: 40,
       fill_color: :cyan, stroke_color: :burgundy,
       stroke_width: 5, stroke_strategy: :stroke_first

  polygon x: 500, y: 1000, n: 5, radius: 25, angle: Math::PI / 2,
          fill_color: :cyan, stroke_color: :burgundy, stroke_width: 2

  save_png prefix: 'shape_'
end

cm

Given centimeters, returns the number of pixels according to the deck’s DPI.

Parameters

n
the number of centimeters

Examples

cm(1)         # 118.11px (for default Deck::dpi of 300)
cm(2) + cm(1) # 354.33ox (for default Deck::dpi of 300)

Squib.configure

Prior to the construction of a Squib::Deck, set a global default that overrides what is specified config.yml.

This is intended to be done prior to Squib::Deck.new, and is intended to be used inside of a Rakefile

Options

All options that are specified in Configuration Options

Exmaples

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# This is a sample Rakefile
require 'squib'

desc 'Build all decks black-and-white'
task default: [:characters, :skills]

desc 'Build all decks with color'
task color: [:with_color, :default]

desc 'Enable color build'
task :with_color do
  puts "Enabling color build"
  Squib.configure img_dir: 'color'
end

desc 'Build the character deck'
task :characters do
  puts "Building characters"
  load 'src/characters.rb'
end

desc 'Build the skills deck'
task :skills do
  puts "Building skills"
  load 'src/skills.rb'
end

csv

Pulls CSV data from .csv files into a hash of arrays keyed by the headers. First row is assumed to be the header row.

Parsing uses Ruby’s CSV, with options {headers: true, converters: :numeric} http://www.ruby-doc.org/stdlib-2.0/libdoc/csv/rdoc/CSV.html

The csv method is a member of Squib::Deck, but it is also available outside of the Deck DSL with Squib.csv(). This allows a construction like:

data = Squib.csv file: 'data.csv'
Squib::Deck.new(cards: data['name'].size) do
end

If you have multiple source files which you would like to concatenate use the following:

cards1 = Squib.csv file: 'cards1.tsv', col_sep: "\t"
cards2 = Squib.csv file: 'cards2.tsv', col_sep: "\t"

all_cards = cards1
all_cards['column1'] += cards2['column1']
all_cards['column2'] += cards2['column2']

Options

file

default: 'deck.csv'

the CSV-formatted file to open. Opens relative to the current directory. If data is set, this option is overridden.

data

default: nil

when set, CSV will parse this data instead of reading the file.

strip

default: true

When true, strips leading and trailing whitespace on values and headers

explode

default: 'qty'

Quantity explosion will be applied to the column this name. For example, rows in the csv with a 'qty' of 3 will be duplicated 3 times.

col_sep

default: ','

Column separator. One of the CSV custom options in Ruby. See next option below.

quote_char

default: '"'

Character used to quote strings that have a comma in them. One of the CSV custom options in Ruby. See next option below.

CSV custom options in Ruby standard lib.
All of the options in Ruby’s std lib version of CSV are supported except headers is always true and converters is always set to :numeric. See the Ruby Docs for information on the options.

Warning

Data import methods such as xlsx and csv will not consult your layout file or follow the Squib Thinks in Arrays feature.

Individual Pre-processing

The xlsx method also takes in a block that will be executed for each cell in your data. This is useful for processing individual cells, like putting a dollar sign in front of dollars, or converting from a float to an integer. The value of the block will be what is assigned to that cell. For example:

resource_data = Squib.csv(file: 'sample.xlsx') do |header, value|
  case header
  when 'Cost'
    "$#{value}k" # e.g. "3" becomes "$3k"
  else
    value # always return the original value if you didn't do anything to it
  end
end

Examples

To get the sample Excel files, go to its source

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
require 'squib'

Squib::Deck.new(cards: 2) do
  background color: :white

  # Outputs a hash of arrays with the header names as keys
  data = csv file: 'sample.csv'
  text str: data['Type'], x: 250, y: 55, font: 'Arial 18'
  text str: data['Level'], x: 65, y: 65, font: 'Arial 24'

  save format: :png, prefix: 'sample_csv_'

  # You can also specify the sheet, starting at 0
  data = xlsx file: 'sample.xlsx', sheet: 2
end

# CSV is also a Squib-module-level function, so this also works:
data      = Squib.csv file: 'quantity_explosion.csv' # 2 rows...
num_cards = data['Name'].size                        #          ...but 4 cards!

Squib::Deck.new(cards: num_cards) do
  background color: :white
  rect # card border
  text str: data['Name'], font: 'Arial 18'
  save_sheet prefix: 'sample_csv_qty_', columns: 4
end

# Additionally, CSV supports inline data specifically
data = Squib.csv data: <<-EOCSV
Name,Cost
Knight,3
Orc,1
EOCSV

Here’s the sample.csv

1
2
3
Type,"Level"
Thief,1
Mastermind,2

Here’s the quantity_explosion.csv

1
2
3
Name,qty
Basilisk,3
High Templar,1

curve

Draw a bezier curve using the given coordinates, from x1,y1 to x2,y2. The curvature is set by the control points cx1,cy2 and cx2,cy2.

Options

All of these options support arrays and singleton expansion (except for range). See Squib Thinks in Arrays for deeper explanation.

x1

default: 0

the x-coordinate of the first endpoint. Supports Unit Conversion and XYWH Shorthands..

y1

default: 0

the y-coordinate of the first endpoint. Supports Unit Conversion and XYWH Shorthands..

x2

default: 5

the x-coordinate of the second endpoint. Supports Unit Conversion and XYWH Shorthands..

y2

default: 5

the y-coordinate of the second endpoint. Supports Unit Conversion and XYWH Shorthands..

cx1

default: 0

the x-coordinate of the first control point. Supports Unit Conversion and XYWH Shorthands..

cy1

default: 0

the y-coordinate of the first control point. Supports Unit Conversion and XYWH Shorthands..

cx2

default: 5

the x-coordinate of the second control point. Supports Unit Conversion and XYWH Shorthands..

cy2

default: 5

the y-coordinate of the second control point. Supports Unit Conversion and XYWH Shorthands..

fill_color

default: '#0000' (fully transparent)

the color or gradient to fill with. See Specifying Colors & Gradients.

stroke_color

default: :black

the color with which to stroke the outside of the shape. See Specifying Colors & Gradients.

stroke_width

default: 2

the width of the outside stroke. Supports Unit Conversion.

stroke_strategy

default: :fill_first

Specify whether the stroke is done before (thinner) or after (thicker) filling the shape.

Must be either :fill_first or :stroke_first (or their string equivalents).

dash

default: '' (no dash pattern set)

Define a dash pattern for the stroke. This is a special string with space-separated numbers that define the pattern of on-and-off alternating strokes, measured in pixels or units. For example, '0.02in 0.02in' will be an equal on-and-off dash pattern. Supports Unit Conversion.

cap

default: :butt

Define how the end of the stroke is drawn. Options are :square, :butt, and :round (or string equivalents of those).

join

default: :mitre

Specifies how to render the junction of two lines when stroking. Options are :mitre, :round, and :bevel.

range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

layout

default: nil

entry in the layout to use as defaults for this command. See Layouts are Squib’s Best Feature.

Examples

cut_zone

Draw a rounded rectangle set in from the edges of the card to indicate the bleed area.

This method is a wrapper around rect, with its own defaults.

Options

All of these options support arrays and singleton expansion (except for range). See Squib Thinks in Arrays for deeper explanation.

margin

default: ‘0.125in’

The distance from the edge of the card to the cut zone. Supports Unit Conversion.

width

default: width - margin (the width of the deck minus the margin)

the width of the box. Supports Unit Conversion.

height

default: height - margin (the height of the deck minus the margin)

the height of the box. Supports Unit Conversion.

fill_color

default: '#0000' (fully transparent)

the color or gradient to fill with. See Specifying Colors & Gradients.

stroke_color

default: :blue

the color with which to stroke the outside of the shape. See Specifying Colors & Gradients.

stroke_width

default: 1.0

the width of the outside stroke. Supports Unit Conversion.

stroke_strategy

default: :fill_first

Specify whether the stroke is done before (thinner) or after (thicker) filling the shape.

Must be either :fill_first or :stroke_first (or their string equivalents).

join

default: :mitre

Specifies how to render the junction of two lines when stroking. Options are :mitre, :round, and :bevel.

dash

default: '3 3' (no dash pattern set)

Define a dash pattern for the stroke. This is a special string with space-separated numbers that define the pattern of on-and-off alternating strokes, measured in pixels or units. For example, '0.02in 0.02in' will be an equal on-and-off dash pattern. Supports Unit Conversion.

cap

default: :butt

Define how the end of the stroke is drawn. Options are :square, :butt, and :round (or string equivalents of those).

x

default: margin (whatever the margin was set to)

the x-coordinate to place, relative to the upper-left corner of the card and moving right as it increases. Supports /units`and :doc:/shorthands`.

y

default: margin (whatever the margin was set to)

the y-coordinate to place, relative to the upper-left corner of the card and moving downward as it increases. Supports /units`and :doc:/shorthands`.

range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

layout

default: nil

entry in the layout to use as defaults for this command. See Layouts are Squib’s Best Feature.

angle

default: 0

the angle at which to rotate the rectangle about it’s upper-left corner

x_radius

default: 0.125in

The x radius of the rounded corners. Supports Unit Conversion.

y_radius

default: 0.125in

The y radius of the rounded corners. Supports Unit Conversion.

radius

default: nil

The x and y radius of the rounded corners. If specified, overrides x_radius and y_radius. Supports Unit Conversion.

Examples

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
require 'squib'

Squib::Deck.new do
  background color: :white
  safe_zone # defaults TheGameCrafter 0.25in around the edge, rounded corners
  cut_zone  # defaults TheGameCrafter 0.125in around the edge

  text str: 'Poker card with proof lines', x: '0.25in', y: '0.25in'
  save_png prefix: 'proof_poker_'
end


Squib::Deck.new(width:'2in', height: '1in')do
  background color: :white
  safe_zone stroke_color: :purple, margin: '0.1in'
  cut_zone stroke_color: :purple, margin: '0.05in'

  text str: 'Small card with proof lines', x: '0.1in', y: '0.1in',
       font: 'Arial 10'

  save_png prefix: 'proof_tiny_'
end

Squib::DataFrame

As described in Be Data-Driven with XLSX and CSV, the Squib::DataFrame is what is returned by Squib’s data import methods (csv and xlsx).

It behaves like a Hash of Arrays, so acessing an individual column can be done via the square brackets, e.g. data['title'].

Here are some other convenience methods in Squib::DataFrame

columns become methods

Through magic of Ruby metaprogramming, every column also becomes a method on the data frame. So these two are equivalent:

irb(main):002:0> data = Squib.csv file: 'basic.csv'
=> #<Squib::DataFrame:0x00000003764550 @hash={"h1"=>[1, 3], "h2"=>[2, 4]}>
irb(main):003:0> data.h1
=> [1, 3]
irb(main):004:0> data['h1']
=> [1, 3]

#columns

Returns an array of the column names in the data frame

#ncolumns

Returns the number of columns in the data frame

#col?(name)

Returns true if there is column name.

#row(i)

Returns a hash of values across all columns in the i-th row of the dataframe. Represents a single card.

#nrows

Returns the number of rows the data frame has, computed by the maximum length of any column array.

#to_json

Returns a json representation of the entire data frame.

#to_pretty_json

Returns a json representation of the entire data frame, formatted with indentation for human viewing.

#to_pretty_text

Returns a textual representation of the dataframe that emulates what the information looks like on an individual card. Here’s an example:

            ╭------------------------------------╮
       Name | Mage                               |
       Cost | 1                                  |
Description | You may cast 1 spell per turn      |
      Snark | Magic, dude.                       |
            ╰------------------------------------╯
            ╭------------------------------------╮
       Name | Rogue                              |
       Cost | 2                                  |
Description | You always take the first turn.    |
      Snark | I like to be sneaky                |
            ╰------------------------------------╯
            ╭------------------------------------╮
       Name | Warrior                            |
       Cost | 3                                  |
Description |
      Snark | I have a long story to tell to tes |
            | t the word-wrapping ability of pre |
            | tty text formatting.               |
            ╰------------------------------------╯

disable_build

Disable the given build group for the rest of the build. Thus, code within the corresponding build block will not be executed. See Group Your Builds for ways to use this effectively.

Required Arguments

build_group_name
default: :all the name of the group to disable. Convention is to use a Ruby symbol.

Examples

Can be used to disable a group (even if it’s enabled via command line):

Squib::Deck.new do
  disable_build :pnp
  build :pnp do
    save_pdf
  end
end

disable_build_globally

Disable the given build group for all future Squib::Deck runs.

Essentially a convenience method for setting the SQUIB_BUILD environment variable. See Group Your Builds for ways to use this effectively.

This is a member of the Squib module, so you must run it like this:

Squib.disable_build_globally :pdf

The intended purpose of this method is to be able to alter the environment from other build scripts, such as a Rakefile.

Required Arguments

build_group_name

the name of the build group to disable. Convention is to use a Ruby symbol.

Examples

Can be used to disable a group, overriding setting the environment variable at the command line:

Squib.enable_build_globally :pdf
Squib.disable_build_globally :pdf

Squib::Deck.new do
  build :pdf do
    save_pdf #does not get run regardless of incoming environment
  end
end

But gets overridden by an individual Squib::Deck programmatically enabling a build via enable_build:

Squib.enable_build_globally :pdf
Squib.disable_build_globally :pdf

Squib::Deck.new do
  enable_build :pdf
  build :pdf do
    save_pdf # this will be run no matter what
  end
end

ellipse

Draw an ellipse at the given coordinates. An ellipse is an oval that is defined by a bounding rectangle. To draw a circle, see circle.

Options

All of these options support arrays and singleton expansion (except for range). See Squib Thinks in Arrays for deeper explanation.

x

default: 0

the x-coordinate to place, relative to the upper-left corner of the card and moving right as it increases. Supports Unit Conversion and XYWH Shorthands.

y

default: 0

the y-coordinate to place, relative to the upper-left corner of the card and moving downward as it increases. Supports Unit Conversion and XYWH Shorthands.

width

default: :deck (the width of the deck)

the width of the box. Supports Unit Conversion and XYWH Shorthands.

height

default: :deck (the height of the deck)

the height of the box. Supports Unit Conversion. Also can be :center or :middle for half the height of the deck. Supports Unit Conversion and and XYWH Shorthands.

fill_color

default: '#0000' (fully transparent)

the color or gradient to fill with. See Specifying Colors & Gradients.

stroke_color

default: :black

the color with which to stroke the outside of the shape. See Specifying Colors & Gradients.

stroke_width

default: 2

the width of the outside stroke. Supports Unit Conversion.

stroke_strategy

default: :fill_first

Specify whether the stroke is done before (thinner) or after (thicker) filling the shape.

Must be either :fill_first or :stroke_first (or their string equivalents).

dash

default: '' (no dash pattern set)

Define a dash pattern for the stroke. This is a special string with space-separated numbers that define the pattern of on-and-off alternating strokes, measured in pixels or units. For example, '0.02in 0.02in' will be an equal on-and-off dash pattern. Supports Unit Conversion.

cap

default: :butt

Define how the end of the stroke is drawn. Options are :square, :butt, and :round (or string equivalents of those).

join

default: :mitre

Specifies how to render the junction of two lines when stroking. Options are :mitre, :round, and :bevel.

angle

default: 0

the angle at which to rotate the ellipse about it’s upper-left corner

range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

layout

default: nil

entry in the layout to use as defaults for this command. See Layouts are Squib’s Best Feature.

Examples

enable_build

Enable the given build group for the rest of the build. Thus, code within the corresponding build block will be executed. See Group Your Builds for ways to use this effectively.

Required Arguments

build_group_name
the name of the group to enable. Convention is to use a Ruby symbol.

Examples

Can be used to disable a group (even if it’s enabled via command line):

Squib::Deck.new do
  disable_build :pnp
  build :pnp do
    save_pdf
  end
end

disable_build_globally

Enagle the given build group for all future Squib::Deck runs.

Essentially a convenience method for setting the SQUIB_BUILD environment variable. See Group Your Builds for ways to use this effectively.

This is a member of the Squib module, so you must run it like this:

Squib.enable_build_globally :pdf

The intended purpose of this method is to be able to alter the environment from other build scripts, such as a Rakefile.

Required Arguments

build_group_name

the name of the build group to enable. Convention is to use a Ruby symbol.

Examples

Can be used to enable a group, overriding setting the environment variable at the command line:

Squib.enable_build_globally :pdf

Squib::Deck.new do
  build :pdf do
    save_pdf # this runs regardless of incoming environment
  end
end

But gets overridden by an individual Squib::Deck programmatically enabling a build via enable_build:

Squib.enable_build_globally :pdf

Squib::Deck.new do
  disable_build :pdf
  build :pdf do
    save_pdf # this will NOT be run no matter what
  end
end

grid

Draw an unlimited square grid of lines on the deck, starting with x,y and extending off the end of the deck.

Options

All of these options support arrays and singleton expansion (except for range). See Squib Thinks in Arrays for deeper explanation.

x

default: 0

the x-coordinate to place, relative to the upper-left corner of the card and moving right as it increases. Supports Unit Conversion and XYWH Shorthands.

y

default: 0

the y-coordinate to place, relative to the upper-left corner of the card and moving downward as it increases. Supports Unit Conversion and XYWH Shorthands.

width

default: :deck (the width of the deck)

the spacing between vertical gridlines. Supports Unit Conversion and XYWH Shorthands.

height

default: :deck (the height of the deck)

the spacing between horizontal gridlines. Supports Unit Conversion and XYWH Shorthands.

fill_color

default: '#0000' (fully transparent)

the color or gradient to fill with. See Specifying Colors & Gradients.

stroke_color

default: :black

the color with which to stroke the outside of the shape. See Specifying Colors & Gradients.

stroke_width

default: 2

the width of the outside stroke. Supports Unit Conversion.

stroke_strategy

default: :fill_first

Specify whether the stroke is done before (thinner) or after (thicker) filling the shape.

Must be either :fill_first or :stroke_first (or their string equivalents).

dash

default: '' (no dash pattern set)

Define a dash pattern for the stroke. This is a special string with space-separated numbers that define the pattern of on-and-off alternating strokes, measured in pixels or units. For example, '0.02in 0.02in' will be an equal on-and-off dash pattern. Supports Unit Conversion.

cap

default: :butt

Define how the end of the stroke is drawn. Options are :square, :butt, and :round (or string equivalents of those).

join

default: :mitre

Specifies how to render the junction of two lines when stroking. Options are :mitre, :round, and :bevel.

range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

layout

default: nil

entry in the layout to use as defaults for this command. See Layouts are Squib’s Best Feature.

Examples

hand

Renders a range of cards fanned out as if in a hand. Saves as PNG regardless of back end.

Options

radius

default: :auto

The distance from the bottom of each card to the center of the fan. If set to :auto, then it is computed as 30% of the card’s height. Why 30%? Because it looks good that way. Reasons.

angle_range

default: ((Math::PI / -4.0)..(Math::PI / 2))

The overall width of the fan, in radians. Angle of zero is a vertical card. Further negative angles widen the fan counter-clockwise and positive angles widen the fan clockwise.

margin

default: 75

the margin around the entire image. Supports Unit Conversion.

fill_color

default: :white

Backdrop color. See Specifying Colors & Gradients.

trim

default: 0

the margin around the card to trim before putting into the image

trim_radius

default: 0

the rounded rectangle radius around the card to trim before putting into the image

file

default: 'hand.png'

The file to save relative to the current directory. Will overwrite without warning.

dir

default: _output

The directory for the output to be sent to. Will be created if it doesn’t exist. Relative to the current directory.

range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

Examples

hint

Toggle text hints globally. A text hint is a 1-pixel line drawn around the extents of a text box. They are intended to be temporary guides.

Options

text

default: :off

The color of the text hint. See Specifying Colors & Gradients To turn off use :off or nil.

Examples

inches

Given inches, returns the number of pixels according to the deck’s DPI.

Parameters

n
the number of inches

Examples

inches(2.5)                # 750 (for default Deck::dpi of 300)
inches(2.5) + inches(0.5)  # 900 (for default Deck::dpi of 300)

line

Draw a line from x1,y1 to x2,y2.

Options

All of these options support arrays and singleton expansion (except for range). See Squib Thinks in Arrays for deeper explanation.

x1

default: 0

the x-coordinate to place. Supports Unit Conversion and XYWH Shorthands.

y1

default: 0

the y-coordinate to place. Supports Unit Conversion and XYWH Shorthands.

x2

default: 50

the x-coordinate to place. Supports Unit Conversion and XYWH Shorthands.

y2

default: 50

the y-coordinate to place. Supports Unit Conversion and XYWH Shorthands.

fill_color

default: '#0000' (fully transparent)

the color or gradient to fill with. See Specifying Colors & Gradients.

stroke_color

default: :black

the color with which to stroke the outside of the shape. See Specifying Colors & Gradients.

stroke_width

default: 2

the width of the outside stroke. Supports Unit Conversion.

stroke_strategy

default: :fill_first

Specify whether the stroke is done before (thinner) or after (thicker) filling the shape.

Must be either :fill_first or :stroke_first (or their string equivalents).

dash

default: '' (no dash pattern set)

Define a dash pattern for the stroke. This is a special string with space-separated numbers that define the pattern of on-and-off alternating strokes, measured in pixels or units. For example, '0.02in 0.02in' will be an equal on-and-off dash pattern. Supports Unit Conversion.

cap

default: :butt

Define how the end of the stroke is drawn. Options are :square, :butt, and :round (or string equivalents of those).

join

default: :mitre

Specifies how to render the junction of two lines when stroking. Options are :mitre, :round, and :bevel.

range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

layout

default: nil

entry in the layout to use as defaults for this command. See Layouts are Squib’s Best Feature.

Examples

mm

Given millimeters, returns the number of pixels according to the deck’s DPI.

Parameters

n
the number of mm

Examples

mm(1)         # 11.811px (for default Deck::dpi of 300)
mm(2) + mm(1) # 35.433ox (for default Deck::dpi of 300)

png

Renders PNG images.

Options

All of these options support arrays and singleton expansion (except for range). See Squib Thinks in Arrays for deeper explanation.

file

default: '' (empty string)

file(s) to read in. As in Squib Thinks in Arrays, if this a single file, then it’s use for every card in range. If the parameter is an Array of files, then each file is looked up for each card. If any of them are nil or ‘’, nothing is done for that card.

x

default: 0

the x-coordinate to place, relative to the upper-left corner of the card and moving right as it increases. Supports Unit Conversion and XYWH Shorthands.

y

default: 0

the y-coordinate to place, relative to the upper-left corner of the card and moving downward as it increases. Supports Unit Conversion and XYWH Shorthands.

width

default: :native

the pixel width that the image should scale to. Supports Unit Conversion. When set to :native, uses the DPI and units of the loaded SVG document. Using :deck will scale to the deck width. Using :scale will use the height to scale and keep native the aspect ratio. Scaling PNGs is not recommended for professional-looking cards, and up-scaling a PNG will throw a warning in the console (see Configuration Options). Supports Unit Conversion and XYWH Shorthands.

height

default: :native

the pixel height that the image should scale to. Supports Unit Conversion. When set to :native, uses the DPI and units of the loaded SVG document. Using :deck will scale to the deck height. Using :scale will use the width to scale and keep native the aspect ratio. Scaling PNGs is not recommended for professional-looking cards, and up-scaling a PNG will throw a warning in the console (see Configuration Options). Supports Unit Conversion and XYWH Shorthands.

alpha

default: 1.0

the alpha-transparency percentage used to blend this image. Must be between 0.0 and 1.0

blend

default: :none

the composite blend operator used when applying this image. See Blend Modes at http://cairographics.org/operators. The possibilties include: :none, :multiply, :screen, :overlay, :darken, :lighten, :color_dodge, :color_burn, :hard_light, :soft_light, :difference, :exclusion, :hsl_hue, :hsl_saturation, :hsl_color, :hsl_luminosity. String versions of these options are accepted too.

mask

default: nil

Accepts a color (see Specifying Colors & Gradients). If specified, the image will be used as a mask for the given color/gradient. Transparent pixels are ignored, opaque pixels are the given color. Note: the origin for gradient coordinates is at the given x,y, not at 0,0 as it is most other places.

placeholder

default: nil

if file does not exist, but the file pointed to by this string does, then draw this image instead.

No warning is thrown when a placeholder is used.

If this is non-nil, but the placeholder file does not exist, then a warning is thrown and no image is drawn.

Examples of how to use placeholders are below.

angle

default: 0

Rotation of the in radians. Note that this rotates around the upper-left corner, making the placement of x-y coordinates slightly tricky.

crop_x

default: 0

Crop the loaded image at this x coordinate. Supports Unit Conversion.

crop_y

default: 0

Crop the loaded image at this y coordinate. Supports Unit Conversion.

crop_corner_radius

default: 0

Radius for rounded corners, both x and y. When set, overrides crop_corner_x_radius and crop_corner_y_radius. Supports Unit Conversion.

crop_corner_x_radius

default: 0

x radius for rounded corners of cropped image. Supports Unit Conversion.

crop_corner_y_radius

default: 0

y radius for rounded corners of cropped image. Supports Unit Conversion.

crop_width

default: :native

Width of the cropped image. Supports Unit Conversion.

crop_height

default: :native

Height of the cropped image. Supports Unit Conversion.

flip_horizontal

default: false

Flip this image about its center horizontally (i.e. left becomes right and vice versa).

flip_vertical

default: false

Flip this image about its center vertical (i.e. top becomes bottom and vice versa).

range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

layout

default: nil

entry in the layout to use as defaults for this command. See Layouts are Squib’s Best Feature.

Examples

These examples live here: https://github.com/andymeneely/squib/tree/dev/samples/images

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
require 'squib'
require 'squib/sample_helpers'

Squib::Deck.new(width: 1000, height: 3000) do
  draw_graph_paper width, height

  sample "This a PNG.\nNo scaling is done by default." do |x, y|
    png file: 'angler-fish.png', x: x, y: y
  end

  sample 'PNGs can be upscaled, but they will emit an antialias warning (unless you turn it off in the config.yml)' do |x,y|
    png file: 'angler-fish.png', x: x, y: y, width: 150, height: 150
  end

  sample 'SVGs can be loaded from a file (left) or from straight XML (right). They can also be scaled to any size.' do |x,y|
    svg file: 'robot-golem.svg', x: x, y: y, width: 100, height: 100
    svg data: File.read('robot-golem.svg'), width: 100, height: 100,
        x: x + 200, y: y
  end

  sample 'PNG and SVG can be auto-scaled by one side and setting the other to :scale' do |x,y|
    svg file: 'robot-golem.svg', x: x,       y: y, width: 50,     height: :scale
    svg file: 'robot-golem.svg', x: x + 50,  y: y, width: :scale, height: 50

    png file: 'angler-fish.png', x: x + 200, y: y, width: 50,     height: :scale
    png file: 'angler-fish.png', x: x + 250, y: y, width: :scale, height: 50
  end

  sample 'PNGs can be cropped. To work from sprite sheets, you can set crop coordinates to PNG images. Rounded corners supported too.' do |x,y|
    png file: 'sprites.png', x: x - 50, y: y - 50          # entire sprite sheet
    rect x: x - 50, y: y - 50, width: 100, height: 100,    # draw the crop line
         radius: 15, dash: '3 3', stroke_color: 'red', stroke_width: 3
    text str: '➜', font: 'Sans Bold 12', x: x + 150, y: y - 35
    png file: 'sprites.png', x: x + 200, y: y - 50,        # just the robot golem image
        crop_x: 0, crop_y: 0, crop_corner_radius: 15,
        crop_width: 100, crop_height: 100

    png file: 'sprites.png', x: x - 50, y: y + 50     # entire sprite sheet again
    rect x: x + 14, y: y + 50, width: 65, height: 65, # highlight the crop
         radius: 25, dash: '3 3', stroke_color: 'red', stroke_width: 3
    text str: '➜', font: 'Sans Bold 12', x: x + 150, y: y + 50
    png file: 'sprites.png', x: x + 225, y: y + 50,   # just the drakkar ship image, rotated
        crop_x: 64, crop_y: 0, crop_corner_x_radius: 25, crop_corner_y_radius: 25,
        crop_width: 64, crop_height: 64, angle: Math::PI / 6
  end

  sample 'SVGs can be cropped too.' do |x,y|
    svg file: 'robot-golem.svg', x: x, y: y, width: 100, height: 100,
        crop_x: 40, crop_y: 0, crop_width: 50, crop_height: 50
  end

  sample 'Images can be flipped about their center.' do |x,y|
    png file: 'angler-fish.png', x: x, y: y, flip_vertical: true, flip_horizontal: true
    svg file: 'robot-golem.svg', x: x + 200, y: y, width: 100, height: 100,
        flip_horizontal: true
  end

  sample 'SVG can be limited to rendering to a single object if the SVG ID is set. If you look in this SVG file, the black backdrop has ID #backdrop.' do |x,y|
    svg file: 'robot-golem.svg', id: 'backdrop', x: x, y: y, width: 100, height: 100
  end

  sample "The SVG force_id option allows use of an ID only when specified, and render nothing if empty. Useful for multiple icons in one SVG file.\nThis should show nothing." do |x,y|
    svg file: 'robot-golem.svg', x: x, y: y,
        force_id: true, id: '' # <-- the important parts
  end

  sample 'NOTE! If you render a single object in an SVG, its placement is still relative to the SVG document.' do |x,y|
    svg file: 'offset.svg', x: x, y: y
    rect x: x, y: y, width: 100, height: 100, dash: '3 1', stroke_color: 'red', stroke_width: 3

    svg file: 'offset.svg', id: 'thing',  x: x + 200, y: y, width: 100, height: 100
    rect x: x + 200, y: y, width: 100, height: 100, dash: '3 1', stroke_color: 'red', stroke_width: 3
  end

  sample 'PNGs can be blended onto each other with 15 different blending operators. Alpha transparency supported too. See http://cairographics.org/operators' do |x,y|
    png file: 'ball.png', x: x,      y: y
    png file: 'grit.png', x: x + 20, y: y + 20, blend: :color_burn, alpha: 0.75
  end

  sample 'Rotation is around the upper-left corner of the image. Unit is radians.' do |x,y|
    rect x: x, y: y, width: 100, height: 100, stroke_width: 3, dash: '3 3', stroke_color: :red
    png  x: x,  y: y, width: 100, height: 100, angle: Math::PI / 4, file: 'angler-fish.png'

    rect x: x + 250, y: y, width: 100, height: 100, stroke_width: 3, dash: '3 3', stroke_color: :red
    svg  x: x + 250, y: y, width: 100, height: 100, file: 'robot-golem.svg',
        angle: Math::PI / 2 - 0.2
  end

  sample 'SVGs and PNGs can be used as masks for colors instead of being directly rendered.' do |x,y|
    svg mask: '#00ff00', file: 'glass-heart.svg', x: x - 50, y: y - 50, width: 200, height: 200
    svg mask: '(0,0)(500,0) #eee@0.0 #111@1.0', file: 'glass-heart.svg', x: x + 150, y: y - 50, width: 200, height: 200
  end

  sample 'PNG masks are based on the alpha channel. Gradient coordinates are relative to the card.' do |x,y|
    png file: 'with-alpha.png', x: x - 50, y: y
    png file: 'with-alpha.png', mask: :magenta, x: x + 50, y: y

    mask = "(#{x+150+75}, #{y+75}, 0)(#{x+150+75}, #{y+75}, 100) #f00@0.0 #000@1.0"
    png file: 'with-alpha.png', mask: mask, x: x + 150, y: y, width: 150, height: :scale
  end


  save_png prefix: '_images_'
end
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# require 'squib'
require_relative '../../lib/squib'

# By default Squib will simply warn you if an image is missing
# Instead, you can give it a `placeholder`
Squib.configure img_missing: :silent  # no warnings, no errors, no placeholder
# Squib.configure img_missing: :warn  # default
# Squib.configure img_missing: :error # pre Squib v0.18 behavior... blech

Squib::Deck.new(width: 100, height: 100, cards: 3) do
  background color: :white

  files = %w(angler-fish.png does-not-exist.png) # last one is nil
  png file: files, placeholder: 'grit.png'
  save_sheet columns: 1, prefix: 'placeholder_sheet_'
end

# Placeholders can be per-image too.
# What if a placeholder is specified but doesn't exist? It'll always warn.
Squib.configure img_missing: :warn # default
Squib::Deck.new(width: 100, height: 100, cards: 4) do
  background color: :white

  files =        %w(angler-fish.png does-not-exist.png does-not-exist.png does-not-exist.png)
  placeholders = %w(grit.png        does-not-exist.png grit.png                             )
  png file: files, placeholder: placeholders

  # text embeds can have placeholders too
  text(str: 'A', color: :red) do |embed|
    embed.png key: 'A', file: files, placeholder: placeholders, width: 30, height: 30
  end

  save_sheet columns: 1, prefix: 'multi_placeholder_sheet_'
end

# Do errors work?
# If you REALLY want the old, pre-Squib v0.18 functionality
# ...you can still have it
# This is really more of a regression test than example.
Squib.configure img_missing: :error
Squib::Deck.new(width: 100, height: 100, cards: 1) do
  begin
    png file: 'does-not-exist.png' # no placeholder... should error!
    raise Exception.new 'Runtime Error should have been thrown!'
  rescue RuntimeError => e
    # a runtime error should have happened here. So nothing happens. Good.
    Squib.logger.error 'Yay! An error we expected was thrown.'
  end
end

First placeholder expected output.

Second placeholder expected output.

polygon

Draw an n-sided regular polygon, centered at x,y.

Options

All of these options support arrays and singleton expansion (except for range). See Squib Thinks in Arrays for deeper explanation.

n

default: 5

the number of sides on the polygon

x

default: 0

the x-coordinate to place, relative to the upper-left corner of the card and moving right as it increases. Supports Unit Conversion and XYWH Shorthands.

y

default: 0

the y-coordinate to place, relative to the upper-left corner of the card and moving downward as it increases. Supports Unit Conversion and XYWH Shorthands.

radius

default: 0

the distance from the center of the star to the inner circle of its points. Supports Unit Conversion.

angle

default: 0

the angle at which to rotate the star

fill_color

default: '#0000' (fully transparent)

the color or gradient to fill with. See Specifying Colors & Gradients.

stroke_color

default: :black

the color with which to stroke the outside of the shape. See Specifying Colors & Gradients.

stroke_width

default: 2

the width of the outside stroke. Supports Unit Conversion.

stroke_strategy

default: :fill_first

Specify whether the stroke is done before (thinner) or after (thicker) filling the shape.

Must be either :fill_first or :stroke_first (or their string equivalents).

dash

default: '' (no dash pattern set)

Define a dash pattern for the stroke. This is a special string with space-separated numbers that define the pattern of on-and-off alternating strokes, measured in pixels or units. For example, '0.02in 0.02in' will be an equal on-and-off dash pattern. Supports Unit Conversion.

cap

default: :butt

Define how the end of the stroke is drawn. Options are :square, :butt, and :round (or string equivalents of those).

join

default: :mitre

Specifies how to render the junction of two lines when stroking. Options are :mitre, :round, and :bevel.

range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

layout

default: nil

entry in the layout to use as defaults for this command. See Layouts are Squib’s Best Feature.

Examples

rect

Draw a rounded rectangle

Options

All of these options support arrays and singleton expansion (except for range). See Squib Thinks in Arrays for deeper explanation.

x

default: 0

the x-coordinate to place, relative to the upper-left corner of the card and moving right as it increases. Supports Unit Conversion and XYWH Shorthands.

y

default: 0

the y-coordinate to place, relative to the upper-left corner of the card and moving downward as it increases. Supports Unit Conversion and XYWH Shorthands.

width

default: :deck (the width of the deck)

the width of the box. Supports Unit Conversion and XYWH Shorthands.

height

default: :deck (the height of the deck)

the height of the box. Supports Unit Conversion. Also can be :center or :middle for half the height of the deck. Supports Unit Conversion and and XYWH Shorthands.

x_radius

default: 0

The x radius of the rounded corners. Supports Unit Conversion.

y_radius

default: 0

The y radius of the rounded corners. Supports Unit Conversion.

radius

default: nil

The x and y radius of the rounded corners. If specified, overrides x_radius and y_radius. Supports Unit Conversion.

fill_color

default: '#0000' (fully transparent)

the color or gradient to fill with. See Specifying Colors & Gradients.

stroke_color

default: :black

the color with which to stroke the outside of the shape. See Specifying Colors & Gradients.

stroke_width

default: 2

the width of the outside stroke. Supports Unit Conversion.

stroke_strategy

default: :fill_first

Specify whether the stroke is done before (thinner) or after (thicker) filling the shape.

Must be either :fill_first or :stroke_first (or their string equivalents).

dash

default: '' (no dash pattern set)

Define a dash pattern for the stroke. This is a special string with space-separated numbers that define the pattern of on-and-off alternating strokes, measured in pixels or units. For example, '0.02in 0.02in' will be an equal on-and-off dash pattern. Supports Unit Conversion.

cap

default: :butt

Define how the end of the stroke is drawn. Options are :square, :butt, and :round (or string equivalents of those).

join

default: :mitre

Specifies how to render the junction of two lines when stroking. Options are :mitre, :round, and :bevel.

range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

layout

default: nil

entry in the layout to use as defaults for this command. See Layouts are Squib’s Best Feature.

angle

default: 0

the angle at which to rotate the rectangle about it’s upper-left corner

Examples

safe_zone

Draw a rounded rectangle set in from the edges of the card to indicate the bleed area.

This method is a wrapper around rect, with its own defaults.

Options

All of these options support arrays and singleton expansion (except for range). See Squib Thinks in Arrays for deeper explanation.

margin

default: ‘0.25in’

The distance from the edge of the card to the safe zone. Supports Unit Conversion.

width

default: width - margin (the width of the deck minus the margin)

the width of the box. Supports Unit Conversion and XYWH Shorthands.

height

default: height - margin (the height of the deck minus the margin)

the height of the box. Supports Unit Conversion and XYWH Shorthands.

fill_color

default: '#0000' (fully transparent)

the color or gradient to fill with. See Specifying Colors & Gradients.

stroke_color

default: :blue

the color with which to stroke the outside of the shape. See Specifying Colors & Gradients.

stroke_width

default: 1.0

the width of the outside stroke. Supports Unit Conversion.

x_radius

default: 0.125in

The x radius of the rounded corners. Supports Unit Conversion.

y_radius

default: 0.125in

The y radius of the rounded corners. Supports Unit Conversion.

radius

default: nil

The x and y radius of the rounded corners. If specified, overrides x_radius and y_radius. Supports Unit Conversion.

stroke_strategy

default: :fill_first

Specify whether the stroke is done before (thinner) or after (thicker) filling the shape.

Must be either :fill_first or :stroke_first (or their string equivalents).

join

default: :mitre

Specifies how to render the junction of two lines when stroking. Options are :mitre, :round, and :bevel.

dash

default: '3 3' (no dash pattern set)

Define a dash pattern for the stroke. This is a special string with space-separated numbers that define the pattern of on-and-off alternating strokes, measured in pixels or units. For example, '0.02in 0.02in' will be an equal on-and-off dash pattern. Supports Unit Conversion.

cap

default: :butt

Define how the end of the stroke is drawn. Options are :square, :butt, and :round (or string equivalents of those).

x

default: margin (whatever the margin was set to)

the x-coordinate to place, relative to the upper-left corner of the card and moving right as it increases. Supports Unit Conversion and XYWH Shorthands.

y

default: margin (whatever the margin was set to)

the y-coordinate to place, relative to the upper-left corner of the card and moving downward as it increases. Supports Unit Conversion and XYWH Shorthands.

range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

layout

default: nil

entry in the layout to use as defaults for this command. See Layouts are Squib’s Best Feature.

angle

default: 0

the angle at which to rotate the rectangle about it’s upper-left corner

Examples

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
require 'squib'

Squib::Deck.new do
  background color: :white
  safe_zone # defaults TheGameCrafter 0.25in around the edge, rounded corners
  cut_zone  # defaults TheGameCrafter 0.125in around the edge

  text str: 'Poker card with proof lines', x: '0.25in', y: '0.25in'
  save_png prefix: 'proof_poker_'
end


Squib::Deck.new(width:'2in', height: '1in')do
  background color: :white
  safe_zone stroke_color: :purple, margin: '0.1in'
  cut_zone stroke_color: :purple, margin: '0.05in'

  text str: 'Small card with proof lines', x: '0.1in', y: '0.1in',
       font: 'Arial 10'

  save_png prefix: 'proof_tiny_'
end

save

Saves the given range of cards to either PNG or PDF. Wrapper method for other save methods.

Options

This method delegates everything to save_png or save_pdf using the format option. All other options are passed along.

format

default: [] (do nothing)

Use :png to save as a PNG, and :pdf to save as PDF. To save to both at once, use [:png, :pdf]

Examples

save format: :png, prefix: 'front_' # same as: save_png prefix: 'front_'
save format: :pdf, prefix: 'cards_' # same as: save_pdf prefix: 'cards_'
save format: [:png, :pdf]           # same as: save_png; save_pdf

save_pdf

Lays out the cards in a gride and renders a PDF.

Options

file

default: 'output.pdf'

the name of the PDF file to save. Will be overwritten without warning.

dir

default: _output

the directory to save to. Created if it doesn’t exist.

sprue

default: nil

the sprue file to use. If nil, then no sprue is used and the cards are laid out automatically using the parameters below. If non-nil, Squib checks for a built-in sprue file of that name. Otherwise, it attempts to open a file relative to the current directory. For more information on Squib Sprues, see Sprue Thy Sheets. If you use the trim function (see trim option below) the cards are placed considering coordinates reduced by the trim value. A special case is a sprue file which defines one card per sheet: If you use the trim option then the sheet size will be trimmed as well to remove the otherwise added white border in the PDF.

width

default: 3300

the height of the page in pixels. Default is 11in * 300dpi. Supports Unit Conversion.

height

default: 2550

the height of the page in pixels. Default is 8.5in * 300dpi. Supports Unit Conversion.

margin

default: 75

the margin around the outside of the page. Supports Unit Conversion.

gap

default: 0

the space in pixels between the cards. Supports Unit Conversion.

trim

default: 0

the space around the edge of each card to trim (e.g. to cut off the bleed margin for print-and-play). Supports Unit Conversion. Must be the same for all cards.

trim_radius

default: 0

the rounded rectangle radius around the card to trim before saving. Supports Unit Conversion. Must be the same for all cards.

crop_marks

default: false

When true, draws lines in the margins as guides for cutting. Crop marks factor in the trim (if non-zero), and can also be customized via crop_margin_* options (see below). Has no effect if margin is 0.

Warning

Enabling this feature will draw lines to the edge of the page. Most PDF Readers, by default, will recognize this and scale down the entire PDF to fit in those crop marks - throwing off your overall scale. To disable this, you will need to set Print Scaling “Use original” or “None” when you go to print (this looks different for different PDF readers). Be sure to test this out before you do your big print job!!

crop_margin_bottom

default: 0

The space between the bottom edge of the (potentially trimmed) card, and the crop mark. Supports Unit Conversion. Has no effect if crop_marks is false.

crop_margin_left

default: 0

The space between the left edge of the (potentially trimmed) card, and the crop mark. Supports Unit Conversion. Has no effect if crop_marks is false.

crop_margin_right

default: 0

The space between the right edge of the (potentially trimmed) card, and the crop mark. Supports Unit Conversion. Has no effect if crop_marks is false.

crop_margin_top

default: 0

The space between the top edge of the (potentially trimmed) card, and the crop mark. Supports Unit Conversion. Has no effect if crop_marks is false.

crop_stroke_color

default: :black

The color of the crop mark lines. Has no effect if crop_marks is false.

crop_stroke_dash

default: ''

Define a dash pattern for the crop marks. This is a special string with space-separated numbers that define the pattern of on-and-off alternating strokes, measured in pixels or units. For example, '0.02in 0.02in' will be an equal on-and-off dash pattern. Supports Unit Conversion. Has no effect if crop_marks is false.

crop_stroke_width

default: 1.5

Width of the crop mark lines. Has no effect if crop_marks is false.

rtl

default false

whether to render columns right to left, used for duplex printing of card backs
range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

Examples

save_png

Saves the given range of cards to a PNG

Options

dir

default: '_output'

the directory for the output to be sent to. Will be created if it doesn’t exist.

prefix

default 'card_'

the prefix of all the filenames saved

count_format

default: '%02d'

the format string used for formatting the card count (e.g. padding zeros). Uses a Ruby format string (see the Ruby doc for Kernel::sprintf for specifics)

suffix

default: ''

the suffix of all the filenames saved, just before the .png extension.

rotate

default: false

If true, the saved cards will be rotated 90 degrees clockwise. Or, rotate by the number of radians. Intended to rendering landscape instead of portrait. Possible values: true, false, :clockwise, :counterclockwise

trim

default: 0

the space around the edge of each card to trim (e.g. to cut off the bleed margin for print-and-play). Supports Unit Conversion.

trim_radius

default: 0

the rounded rectangle radius around the card to trim before saving.

shadow_radius

default: nil

adds a drop shadow behind the card just before rendering, when non-nil. Does nothing when set to nil.

A larger radius extends the blur’s spread, making it softer. A radius of 0 still enables the shadow, but has no blur.

Recommended range: 3-10 pixels.

See Drop Shadow section below for more details.

shadow_offset_x

default: 3

the horizontal distance that the drop shadow will be shifted beneath the final image. Ignored when shadow_radius is nil.

See Drop Shadow section below for more details.

Supports Unit Conversion.

shadow_offset_y

default: 3

Ignored when shadow_radius is nil. See shadow_radius above for drop shadow details.

See Drop Shadow section below for more details.

Supports Unit Conversion.

shadow_trim

default: 0

the space around the lower right and bottom edge of the output image to be trimmed when a drop shadow is drawn. Can also enlarge the image if it is negative.

Ignored when shadow_radius is nil. See Drop Shadow section below for more details.

Supports Unit Conversion.

shadow_color

default: :black

the color or gradient of the drop shadow. See Specifying Colors & Gradients.

Note about gradients: Squib still does blurring, but gradients give you fine control over the softness of the shadow. See example below of doing a custom gradient for customizing your look.

See Drop Shadow section below for more details.

range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

Drop Shadow

Drop shadows don’t modify the original image. Instead, this will paint your existing card images onto a shadow of themselves. The final image will have the following dimensions:

  • final_width  = card_w + shadow_offset_x + (3 * shadow_radius) - (2 * shadow_trim) - (2 * trim)
  • final_height = card_h + shadow_offset_y + (3 * shadow_radius) - (2 * shadow_trim) - (2 * trim)

A shadow of your card graphic is created using your shadow_color.

See https://github.com/rcairo/rcairo/blob/master/lib/cairo/context/blur.rb for details on blur implementation. Supports Unit Conversion.

Examples

This sample lives here.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
require 'squib'
# The save_png method supports drop shadows on the final save
# This is useful for when you want to generate images for your rulebook

Squib::Deck.new(width: 100, height: 150) do
  background color: '#abc'
  svg file: '../spanner.svg',
      x: 'middle - 25', y: 'middle - 25',
      width: 50, height: 50

  # Shadows off by default, i.e. shadow_radius is nil
  # So our final dimensions are 100 - 2*15 and 150-2*15
  save_png prefix: 'no_shadow_', trim: 15, trim_radius: 15

  # Here's a nice-looking drop shadow
  # Defaults are designed to be generally good, so I recommend just
  # trying out a shadow_radius of 3 to 10 and see how it looks first
  save_png prefix: 'with_shadow_', trim: 15, trim_radius: 15,
           shadow_radius: 8,
           shadow_offset_x: 3, shadow_offset_y: 3,  # about r / 2.5 looks good
           shadow_trim: 2.5, # about r/ 3 looks good
           shadow_color: '#101010aa' #tip: start the shadow color kinda transparent

  # Don't want a blur? Use a radius of 0
  save_png prefix: 'no_blur_', trim: 15, trim_radius: 15,
           shadow_radius: 0

  # Ok this next stop is crazytown, but it does give you ultimate control
  # Remember that all Squib colors can also be gradients.
  # They can be clunky but they do work here.
  #   - x,y's are centered in the card itself
  #   - stops go from fully empty to fully black
  #   - we need to still turn on radius to get the effect
  #   - but, this makes the upper-left corner not have a glowing effect and
  #     have a harder edge, which (to my eye at least) feels more realistic
  #     since the card would obscure the upper-left shadow at that angle
  #   - this also allows you have a larger, softer blur without making it look
  #     like it's glowing
  #
  # Oh just because it's easier to write we can use a ruby heredoc
  save_png prefix: 'gradient_blur_', trim: 15, trim_radius: 15,
           shadow_radius: 10,
           shadow_color: <<~EOS
              (25,25)
              (175,175)
              #0000@0.0
              #000f@1.0
           EOS

  # This one looks weird I know but it's for regression testing
  save_png prefix: 'with_shadow_test_',
           trim: 15, trim_radius: 15, rotate: :clockwise,
           shadow_offset_x: 5, shadow_offset_y: 25, shadow_radius: 10,
           shadow_trim: 10,
           shadow_color: '#123'
end

Squib::Deck.new(width:50, height: 50) do

  # What if we had a transparent card but just some shapes?
  # Like chits or something

  # background defaults to fully transparent here

  png file: 'doodle.png'

  save_png prefix: 'transparent_bg_shadow_',
           shadow_radius: 2,
           shadow_offset_x: 2, shadow_offset_y: 2,
           shadow_color: :black

end
_images/with_shadow_00_expected.png

with_shadow_00.png

_images/no_blur_00_expected.png

no_blur_00.png

_images/gradient_blur_00_expected.png

gradient_blur_00.png

_images/transparent_bg_shadow_00_expected.png

transparent_bg_shadow_00.png

save_sheet

Lays out the cards in range and renders a stitched PNG sheet

Options

range

default: :all

the range of cards over which this will be rendered. See {file:README.md#Specifying_Ranges Specifying Ranges}

sprue

default: nil

the sprue file to use. If nil, then no sprue is used and the cards are laid out automatically using the parameters below. If non-nil, Squib checks for a built-in sprue file of that name. Otherwise, it attempts to open a file relative to the current directory. For more information on Squib Sprues, see Sprue Thy Sheets.

columns

default: 5

the number of columns in the grid. Must be an integer

rows

default: :infinite

the number of rows in the grid. When set to :infinite, the sheet scales to the rows needed. If there are more cards than rows*columns, new sheets are started.

prefix

default: card_

the prefix of the file name(s)

count_format

default: '%02d'

the format string used for formatting the card count (e.g. padding zeros). Uses a Ruby format string (see the Ruby doc for Kernel::sprintf for specifics)

suffix

default: ''

the suffix of all the filenames saved, just before the .png extension.

rotate

default: false

if true, all saved cards will be rotated 90 degrees clockwise. Possible values: true, false, :clockwise, :counterclockwise

Supports arrays so you can rotate individual cards different ways if that’s how you want to roll, e.g. rotate: [:clockwise, :counterclockwise]

dir

default: '_output'

the directory to save to. Created if it doesn’t exist.

margin

default: 0

the margin around the outside of the sheet. Supports Unit Conversion.

gap

default 0

the space in pixels between the cards. Supports Unit Conversion.

trim

default 0

the space around the edge of each card to trim (e.g. to cut off the bleed margin for print-and-play). Supports Unit Conversion. Must be the same for all cards.

trim_radius

default: 0

the rounded rectangle radius around the card to trim before saving. Supports Unit Conversion. Must be the same for all cards.

rtl

default false

whether to render columns right to left, used for duplex printing of card backs

Examples

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# require 'squib'
require_relative '../../lib/squib'

# This sample demonstrates how to use the various save methods
Squib::Deck.new(width: 825, height: 1125, cards: 16) do
  background color: :gray
  rect x: 38, y: 38, width: 750, height: 1050,
       x_radius: 38, y_radius: 38, stroke_width: 2.0, dash: '4 4'

  text str: (1..16).to_a, x: 220, y: 78, font: 'Arial 18'

  # Here's what a regular save_png looks like for just the first card
  save_png range: 0, prefix: 'save_png_'

  # save_png supports trim and trim_radius
  save_png trim: 30, trim_radius: 38,
           range: 0, prefix: 'save_png_trimmed_'

  # Place on multiple pages over the PDF, with bleed beeing trimmed off
  save_pdf file: 'save-pdf.pdf', margin: 75, gap: 5, trim: 37

  # PDFs also support arbitrary paper sizes, in pixels or any other supported units
  save_pdf file: 'save-pdf-small.pdf',
           width: '7in', height: '5in',
           range: 0..1

  # Note that our PNGs still are not trimmed even though the pdf ones were
  save_png range: 1, prefix: 'saves_notrim_'

  # We can also save our PNGs into a single sheet,
  #  rows are calculated based on cols and number of cards
  save_sheet prefix: 'save_single_sheet_',
             columns: 2, margin: 75, gap: 5, trim: 37

  # Or multiple sheets if rows are also specified
  save_sheet prefix: 'save_sheet_',
             columns: 4, rows: 2,
             margin: 75, gap: 5, trim: 37

  # Sheets support ranges too
  save_sheet prefix: 'save_sheet_range_',
             range: 0..5,
             columns: 2, rows: 2,
             margin: 75, gap: 5, trim: 37

  # Sheets can arrange left-to-right and right-to-left
  save_sheet prefix: 'save_sheet_rtl_',
             suffix: '_with_suffix',
             range: 0..1, rtl: true,
             columns: 2, rows: 1,
             margin: 75, gap: 5, trim: 37
end

Squib::Deck.new(width: 100, height: 100, cards: 3) do
  background color: :grey
  text str: 'Hi', font: 'Arial 18'

  # Test bug 332.
  # When we only have 3 cards but want a 2x4 grid with lots of empty spaces.
  # Buggy behavior was to revert to 1 row and not respect the rows arg.
  save_sheet prefix: 'save_sheet_bug332_', rows: 2, columns: 4
end

# Allow rotating
Squib::Deck.new(width: 100, height: 50, cards: 8) do
  background color: :white
  rect x: 10, y: 10, width: 80, height: 30
  rect x: 5, y: 5, width: 90, height: 40, stroke_width: 5, stroke_color: :blue
  text y: 2, str: 0..7, font: 'Open Sans Bold 8', align: :center, width: 100
  save_sheet prefix: 'save_sheet_unrotated_', rows: 2, columns: 3
  save_sheet prefix: 'save_sheet_custom_rotate_', rows: 2, columns: 3, rotate: [:clockwise, :counterclockwise] * 4
  save_sheet prefix: 'save_sheet_rotated_', rows: 2, columns: 3, rotate: true
  save_sheet prefix: 'save_sheet_rotated_trimmed_', rows: 2, columns: 3, rotate: true, trim: 5
  save_sheet prefix: 'save_sheet_rotated_trimmed_rtl_', rows: 2, columns: 3, rotate: true, trim: 5, rtl: true
end

showcase

Renders a range of cards in a showcase as if they are sitting in 3D on a reflective surface.

Options

trim

default: 0

the margin around the card to trim before putting into the image

trim_radius

default: 38

the rounded rectangle radius around the card to trim before putting into the image. Defaults to 1/8in rounded corners (38px).

scale

default: 0.8

Percentage of original width of each (trimmed) card to scale to. Must be between 0.0 and 1.0, but starts looking bad around 0.6.

offset

default: 1.1

Percentage of the scaled width of each card to shift each offset. e.g. 1.1 is a 10% shift, and 0.95 is overlapping by 5%

fill_color

default: :white

Backdrop color. Usually black or white. See Specifying Colors & Gradients.

reflect_offset

default: 15

The number of pixels between the bottom of the card and the reflection. See Unit Conversion

reflect_strength

default: 0.2

The starting alpha transparency of the reflection (at the top of the card). Percentage between 0 and 1. Looks more realistic at low values since even shiny surfaces lose a lot of light.

reflect_percent

default: 0.25

The length of the reflection in percentage of the card. Larger values tend to make the reflection draw just as much attention as the card, which is not good.

face

default: :left

which direction the cards face. Anything but :right will face left

margin

default: 75

the margin around the entire image. Supports Unit Conversion

file

default: 'showcase.png'

The file to save relative to the current directory. Will overwrite without warning.

dir

default: _output

The directory for the output to be sent to. Will be created if it doesn’t exist. Relative to the current directory.

range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

Examples

This sample lives here.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
require 'squib'

# Showcases are a neat way to show off your cards in a modern way, using a
# reflection and a persepctive effect to make them look 3D
Squib::Deck.new(cards: 4) do
  background color: '#CE534D'
  rect fill_color: '#DED4B9', x: 78, y: 78,
       width: '2.25in', height: '3.25in', radius: 32
  text str: %w(Grifter Thief Thug Kingpin),
       font: 'Helvetica,Sans weight=800 40',
       x: 78, y: 78, width: '2.25in', align: :center
  svg file: 'spanner.svg', x: (825 - 500) / 2, y: 500, width: 500, height: 500

  # Defaults are pretty sensible.
  showcase file: 'showcase.png'

  # Here's a more complete example.
  # Tons of ways to tweak it if you like - check the docs.
  showcase trim: 32, trim_radius: 32, margin: 100, face: :right,
           scale: 0.85, offset: 0.95, fill_color: :black,
           reflect_offset: 25, reflect_strength: 0.1, reflect_percent: 0.4,
           file: 'showcase2.png'

  save_png prefix: 'showcase_individual_' # to show that they're not trimmed
end

star

Draw an n-pointed star, centered at x,y.

Options

All of these options support arrays and singleton expansion (except for range). See Squib Thinks in Arrays for deeper explanation.

n

default: 5

the number of points on the star

x

default: 0

the x-coordinate to place, relative to the upper-left corner of the card and moving right as it increases. Supports Unit Conversion and XYWH Shorthands.

y

default: 0

the y-coordinate to place, relative to the upper-left corner of the card and moving downward as it increases. Supports Unit Conversion and XYWH Shorthands.

inner_radius

default: 0

the distance from the center of the star to the inner circle of its points. Supports Unit Conversion.

outer_radius

default: 0

the distance from the center of the star to the outer circle of its points. Supports Unit Conversion.

angle

default: 0

the angle at which to rotate the star

fill_color

default: '#0000' (fully transparent)

the color or gradient to fill with. See Specifying Colors & Gradients.

stroke_color

default: :black

the color with which to stroke the outside of the shape. See Specifying Colors & Gradients.

stroke_width

default: 2

the width of the outside stroke. Supports Unit Conversion.

stroke_strategy

default: :fill_first

Specify whether the stroke is done before (thinner) or after (thicker) filling the shape.

Must be either :fill_first or :stroke_first (or their string equivalents).

dash

default: '' (no dash pattern set)

Define a dash pattern for the stroke. This is a special string with space-separated numbers that define the pattern of on-and-off alternating strokes, measured in pixels or units. For example, '0.02in 0.02in' will be an equal on-and-off dash pattern. Supports Unit Conversion.

cap

default: :butt

Define how the end of the stroke is drawn. Options are :square, :butt, and :round (or string equivalents of those).

join

default: :mitre

Specifies how to render the junction of two lines when stroking. Options are :mitre, :round, and :bevel.

range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

layout

default: nil

entry in the layout to use as defaults for this command. See Layouts are Squib’s Best Feature.

Examples

svg

Renders an entire svg file at the given location. Uses the SVG-specified units and DPI to determine the pixel width and height. If neither data nor file are specified for a given card, this method does nothing.

Note

Note: if alpha transparency is desired, set that in the SVG.

Options

All of these options support arrays and singleton expansion (except for range). See Squib Thinks in Arrays for deeper explanation.

file

default: '' (empty string)

file(s) to read in. As in Squib Thinks in Arrays, if this a single file, then it’s use for every card in range. If the parameter is an Array of files, then each file is looked up for each card. If any of them are nil or ‘’, nothing is done for that card.

By default, if file is not found, a warning is logged. This behavior can be configured in Configuration Options

x

default: 0

the x-coordinate to place, relative to the upper-left corner of the card and moving right as it increases. Supports Unit Conversion and XYWH Shorthands.

y

default: 0

the y-coordinate to place, relative to the upper-left corner of the card and moving downward as it increases. Supports Unit Conversion and XYWH Shorthands.

data

default: nil

render from an SVG XML string. Overrides file if both are specified (a warning is shown).

id

default: nil

if set, then only render the SVG element with the given id. Prefix ‘#’ is optional. Note: the x-y coordinates are still relative to the SVG document’s page.

force_id

default: false

if set to true, then this svg will not be rendered at all if the id is empty or nil. If not set, the entire SVG is rendered. Useful for putting multiple icons in a single SVG file.

width

default: native

the pixel width that the image should scale to. Setting this to :deck will scale to the deck height. :scale will use the width to scale and keep native the aspect ratio. SVG scaling is done with vectors, so the scaling should be smooth. When set to :native, uses the DPI and units of the loaded SVG document. Supports Unit Conversion and XYWH Shorthands.

height

default: :native

the pixel width that the image should scale to. :deck will scale to the deck height. :scale will use the width to scale and keep native the aspect ratio. SVG scaling is done with vectors, so the scaling should be smooth. When set to :native, uses the DPI and units of the loaded SVG document. Supports Unit Conversion and XYWH Shorthands.

blend

default: :none

the composite blend operator used when applying this image. See Blend Modes at http://cairographics.org/operators. The possibilties include :none, :multiply, :screen, :overlay, :darken, :lighten, :color_dodge, :color_burn, :hard_light, :soft_light, :difference, :exclusion, :hsl_hue, :hsl_saturation, :hsl_color, :hsl_luminosity. String versions of these options are accepted too.

angle

default: 0

rotation of the image in radians. Note that this rotates around the upper-left corner, making the placement of x-y coordinates slightly tricky.

mask

default: nil

if specified, the image will be used as a mask for the given color/gradient. Transparent pixels are ignored, opaque pixels are the given color. Note: the origin for gradient coordinates is at the given x,y, not at 0,0 as it is most other places.

Warning

For implementation reasons, your vector image will be rasterized when mask is applied. If you use this with, say, PDF, the images will be embedded as rasters, not vectors.

placeholder

default: nil

if file does not exist, but the file pointed to by this string does, then draw this image instead.

No warning is thrown when a placeholder is used.

If this is non-nil, but the placeholder file does not exist, then a warning is thrown and no image is drawn.

Examples of how to use placeholders are below.

crop_x

default: 0

rop the loaded image at this x coordinate. Supports Unit Conversion

crop_y

default: 0

rop the loaded image at this y coordinate. Supports Unit Conversion

crop_corner_radius

default: 0

Radius for rounded corners, both x and y. When set, overrides crop_corner_x_radius and crop_corner_y_radius. Supports Unit Conversion

crop_corner_x_radius

default: 0

x radius for rounded corners of cropped image. Supports Unit Conversion

crop_corner_y_radius

default: 0

y radius for rounded corners of cropped image. Supports Unit Conversion

crop_width

default: 0

width of the cropped image. Supports Unit Conversion

crop_height

default: 0

ive): Height of the cropped image. Supports Unit Conversion

flip_horizontal

default: false

Flip this image about its center horizontally (i.e. left becomes right and vice versa).

flip_vertical

default: false

Flip this image about its center verticall (i.e. top becomes bottom and vice versa).

range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

layout

default: nil

entry in the layout to use as defaults for this command. See Layouts are Squib’s Best Feature.

Examples

These examples live here: https://github.com/andymeneely/squib/tree/dev/samples/images

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
require 'squib'
require 'squib/sample_helpers'

Squib::Deck.new(width: 1000, height: 3000) do
  draw_graph_paper width, height

  sample "This a PNG.\nNo scaling is done by default." do |x, y|
    png file: 'angler-fish.png', x: x, y: y
  end

  sample 'PNGs can be upscaled, but they will emit an antialias warning (unless you turn it off in the config.yml)' do |x,y|
    png file: 'angler-fish.png', x: x, y: y, width: 150, height: 150
  end

  sample 'SVGs can be loaded from a file (left) or from straight XML (right). They can also be scaled to any size.' do |x,y|
    svg file: 'robot-golem.svg', x: x, y: y, width: 100, height: 100
    svg data: File.read('robot-golem.svg'), width: 100, height: 100,
        x: x + 200, y: y
  end

  sample 'PNG and SVG can be auto-scaled by one side and setting the other to :scale' do |x,y|
    svg file: 'robot-golem.svg', x: x,       y: y, width: 50,     height: :scale
    svg file: 'robot-golem.svg', x: x + 50,  y: y, width: :scale, height: 50

    png file: 'angler-fish.png', x: x + 200, y: y, width: 50,     height: :scale
    png file: 'angler-fish.png', x: x + 250, y: y, width: :scale, height: 50
  end

  sample 'PNGs can be cropped. To work from sprite sheets, you can set crop coordinates to PNG images. Rounded corners supported too.' do |x,y|
    png file: 'sprites.png', x: x - 50, y: y - 50          # entire sprite sheet
    rect x: x - 50, y: y - 50, width: 100, height: 100,    # draw the crop line
         radius: 15, dash: '3 3', stroke_color: 'red', stroke_width: 3
    text str: '➜', font: 'Sans Bold 12', x: x + 150, y: y - 35
    png file: 'sprites.png', x: x + 200, y: y - 50,        # just the robot golem image
        crop_x: 0, crop_y: 0, crop_corner_radius: 15,
        crop_width: 100, crop_height: 100

    png file: 'sprites.png', x: x - 50, y: y + 50     # entire sprite sheet again
    rect x: x + 14, y: y + 50, width: 65, height: 65, # highlight the crop
         radius: 25, dash: '3 3', stroke_color: 'red', stroke_width: 3
    text str: '➜', font: 'Sans Bold 12', x: x + 150, y: y + 50
    png file: 'sprites.png', x: x + 225, y: y + 50,   # just the drakkar ship image, rotated
        crop_x: 64, crop_y: 0, crop_corner_x_radius: 25, crop_corner_y_radius: 25,
        crop_width: 64, crop_height: 64, angle: Math::PI / 6
  end

  sample 'SVGs can be cropped too.' do |x,y|
    svg file: 'robot-golem.svg', x: x, y: y, width: 100, height: 100,
        crop_x: 40, crop_y: 0, crop_width: 50, crop_height: 50
  end

  sample 'Images can be flipped about their center.' do |x,y|
    png file: 'angler-fish.png', x: x, y: y, flip_vertical: true, flip_horizontal: true
    svg file: 'robot-golem.svg', x: x + 200, y: y, width: 100, height: 100,
        flip_horizontal: true
  end

  sample 'SVG can be limited to rendering to a single object if the SVG ID is set. If you look in this SVG file, the black backdrop has ID #backdrop.' do |x,y|
    svg file: 'robot-golem.svg', id: 'backdrop', x: x, y: y, width: 100, height: 100
  end

  sample "The SVG force_id option allows use of an ID only when specified, and render nothing if empty. Useful for multiple icons in one SVG file.\nThis should show nothing." do |x,y|
    svg file: 'robot-golem.svg', x: x, y: y,
        force_id: true, id: '' # <-- the important parts
  end

  sample 'NOTE! If you render a single object in an SVG, its placement is still relative to the SVG document.' do |x,y|
    svg file: 'offset.svg', x: x, y: y
    rect x: x, y: y, width: 100, height: 100, dash: '3 1', stroke_color: 'red', stroke_width: 3

    svg file: 'offset.svg', id: 'thing',  x: x + 200, y: y, width: 100, height: 100
    rect x: x + 200, y: y, width: 100, height: 100, dash: '3 1', stroke_color: 'red', stroke_width: 3
  end

  sample 'PNGs can be blended onto each other with 15 different blending operators. Alpha transparency supported too. See http://cairographics.org/operators' do |x,y|
    png file: 'ball.png', x: x,      y: y
    png file: 'grit.png', x: x + 20, y: y + 20, blend: :color_burn, alpha: 0.75
  end

  sample 'Rotation is around the upper-left corner of the image. Unit is radians.' do |x,y|
    rect x: x, y: y, width: 100, height: 100, stroke_width: 3, dash: '3 3', stroke_color: :red
    png  x: x,  y: y, width: 100, height: 100, angle: Math::PI / 4, file: 'angler-fish.png'

    rect x: x + 250, y: y, width: 100, height: 100, stroke_width: 3, dash: '3 3', stroke_color: :red
    svg  x: x + 250, y: y, width: 100, height: 100, file: 'robot-golem.svg',
        angle: Math::PI / 2 - 0.2
  end

  sample 'SVGs and PNGs can be used as masks for colors instead of being directly rendered.' do |x,y|
    svg mask: '#00ff00', file: 'glass-heart.svg', x: x - 50, y: y - 50, width: 200, height: 200
    svg mask: '(0,0)(500,0) #eee@0.0 #111@1.0', file: 'glass-heart.svg', x: x + 150, y: y - 50, width: 200, height: 200
  end

  sample 'PNG masks are based on the alpha channel. Gradient coordinates are relative to the card.' do |x,y|
    png file: 'with-alpha.png', x: x - 50, y: y
    png file: 'with-alpha.png', mask: :magenta, x: x + 50, y: y

    mask = "(#{x+150+75}, #{y+75}, 0)(#{x+150+75}, #{y+75}, 100) #f00@0.0 #000@1.0"
    png file: 'with-alpha.png', mask: mask, x: x + 150, y: y, width: 150, height: :scale
  end


  save_png prefix: '_images_'
end
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# require 'squib'
require_relative '../../lib/squib'

# By default Squib will simply warn you if an image is missing
# Instead, you can give it a `placeholder`
Squib.configure img_missing: :silent  # no warnings, no errors, no placeholder
# Squib.configure img_missing: :warn  # default
# Squib.configure img_missing: :error # pre Squib v0.18 behavior... blech

Squib::Deck.new(width: 100, height: 100, cards: 3) do
  background color: :white

  files = %w(angler-fish.png does-not-exist.png) # last one is nil
  png file: files, placeholder: 'grit.png'
  save_sheet columns: 1, prefix: 'placeholder_sheet_'
end

# Placeholders can be per-image too.
# What if a placeholder is specified but doesn't exist? It'll always warn.
Squib.configure img_missing: :warn # default
Squib::Deck.new(width: 100, height: 100, cards: 4) do
  background color: :white

  files =        %w(angler-fish.png does-not-exist.png does-not-exist.png does-not-exist.png)
  placeholders = %w(grit.png        does-not-exist.png grit.png                             )
  png file: files, placeholder: placeholders

  # text embeds can have placeholders too
  text(str: 'A', color: :red) do |embed|
    embed.png key: 'A', file: files, placeholder: placeholders, width: 30, height: 30
  end

  save_sheet columns: 1, prefix: 'multi_placeholder_sheet_'
end

# Do errors work?
# If you REALLY want the old, pre-Squib v0.18 functionality
# ...you can still have it
# This is really more of a regression test than example.
Squib.configure img_missing: :error
Squib::Deck.new(width: 100, height: 100, cards: 1) do
  begin
    png file: 'does-not-exist.png' # no placeholder... should error!
    raise Exception.new 'Runtime Error should have been thrown!'
  rescue RuntimeError => e
    # a runtime error should have happened here. So nothing happens. Good.
    Squib.logger.error 'Yay! An error we expected was thrown.'
  end
end

First placeholder expected output.

Second placeholder expected output.

system_fonts

Returns an array of font names that Squib knows about. These are what Squib considers “system” fonts. For debugging purposes.

This is a module function, so it can be called anywhere with Squib.system_fonts

Options

None.

Examples

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# require 'squib'
require_relative '../../lib/squib'

# Per issue #334, sometimes Pango doesn't find the font file you want
# Pango requires fonts to be installed on the system, but sometimes the
# font name is not obvious. e.g. "Foo Regular" might be actually named "Foo"
# Use these methods to debug this problem

# Usually you would just run this method to see what fonts are installed
# This is commented out to make our test cases
# Squib.print_system_fonts

Squib.system_fonts.include? 'Open Sans' # checks if we have Open Sans installed
# Note: does nothing since it's just a check

text

Renders a string at a given location, width, alignment, font, etc.

Unix newlines are interpreted even on Windows (i.e. "\n").

Options

All of these options support arrays and singleton expansion (except for range). See Squib Thinks in Arrays for deeper explanation.

str

default: ''

the string to be rendered. Must support #to_s.

font

default: 'Arial 36'

the Font description string, including family, styles, and size. (e.g. 'Arial bold italic 12'). For the official documentation, see the Pango font string docs. This description is also quite good.

font_size

default: nil

an override of font string description (i.e. font).

x

default: 0

the x-coordinate to place, relative to the upper-left corner of the card and moving right as it increases. Supports Unit Conversion and XYWH Shorthands.

y

default: 0

the y-coordinate to place, relative to the upper-left corner of the card and moving downward as it increases. Supports Unit Conversion and XYWH Shorthands.

markup

default: false

When set to true, various extra styles are allowed. See Markup.

width

default: :auto

the width of the box the string will be placed in. Stretches to the content by default.. Supports Unit Conversion and XYWH Shorthands.

height

default: :auto

the height of the box the string will be placed in. Stretches to the content by default. Supports Unit Conversion and XYWH Shorthands.

wrap

default: :word_char

when width is set, determines the behavior of how the string wraps. The :word_char option will break at words, but then fall back to characters when the word cannot fit. Options are :word, :char, or :word_char.
spacing

default: 0

Adjust the spacing when the text is multiple lines. No effect when the text does not wrap.

align

default: :left

The alignment of the text. [:left, right, :center]

justify

default: false

toggles whether or not the text is justified or not.

valign

default: :top

When width and height are set, align text vertically according to the ink extents of the text. [:top, :middle, :bottom]

ellipsize

default: :end

When width and height are set, determines the behavior of overflowing text. If set to :autoscale, text is automatically scaled down from the set font size to the largest size that does no longer ellipsize. Also: true maps to :end and false maps to :none. Also, as mentioned in Configuration Options, if text is ellipsized a warning is thrown. [:none, :start, :middle, :end, :autoscale, true, false]

angle

default: 0

Rotation of the text in radians. Note that this rotates around the upper-left corner of the text box, making the placement of x-y coordinates slightly tricky.

stroke_width

default: 0.0

the width of the outside stroke. Supports Unit Conversion, see {file:README.md#Units Units}.

stroke_color

default: :black

the color with which to stroke the outside of the rectangle. {file:README.md#Specifying_Colors___Gradients Specifying Colors & Gradients}

stroke_strategy

default: :fill_first

specify whether the stroke is done before (thinner) or after (thicker) filling the shape. [:fill_first, :stroke_first]

dash

default: ''

define a dash pattern for the stroke. Provide a string with space-separated numbers that define the pattern of on-and-off alternating strokes, measured in pixels by defautl. Supports Unit Conversion (e.g. '0.02in 0.02in').

hint

default: :nil (i.e. no hint)

draw a rectangle around the text with the given color. Overrides global hints (see {Deck#hint}).

color
default: [String] (:black) the color the font will render to. Gradients supported. See {file:README.md#Specifying_Colors___Gradients Specifying Colors}
fill_color

default: '#0000' (fully transparent)

the color or gradient to fill with. See Specifying Colors & Gradients.

stroke_color

default: :black

the color with which to stroke the outside of the shape. See Specifying Colors & Gradients.

stroke_width

default: 2

the width of the outside stroke. Supports Unit Conversion.

stroke_strategy

default: :fill_first

Specify whether the stroke is done before (thinner) or after (thicker) filling the shape.

Must be either :fill_first or :stroke_first (or their string equivalents).

dash

default: '' (no dash pattern set)

Define a dash pattern for the stroke. This is a special string with space-separated numbers that define the pattern of on-and-off alternating strokes, measured in pixels or units. For example, '0.02in 0.02in' will be an equal on-and-off dash pattern. Supports Unit Conversion.

cap

default: :butt

Define how the end of the stroke is drawn. Options are :square, :butt, and :round (or string equivalents of those).

join

default: :mitre

Specifies how to render the junction of two lines when stroking. Options are :mitre, :round, and :bevel.

range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

layout

default: nil

entry in the layout to use as defaults for this command. See Layouts are Squib’s Best Feature.

Markup

If you want to do specialized formatting within a given string, Squib has lots of options. By setting markup: true, you enable tons of text processing. This includes:

  • Pango Markup. This is an HTML-like formatting language that specifies formatting inside your string. Pango Markup essentially supports any formatting option, but on a letter-by-letter basis. Such as: font options, letter spacing, gravity, color, etc. See the Pango markup docs for details.
  • Quotes are converted to their curly counterparts where appropriate.
  • Apostraphes are converted to curly as well.
  • LaTeX-style quotes are explicitly converted (``like this'')
  • Em-dash and en-dash are converted with triple and double-dashes respectively (-- is an en-dash, and --- becomes an em-dash.)
  • Ellipses can be specified with ... (three periods). Note that this is entirely different from the ellipsize option (which determines what to do with overflowing text).
A few notes:
  • Smart quoting assumes the UTF-8 character set by default. If you are in a different character set and want to change how it behaves
  • Pango markup uses an XML/HTML-ish processor. Some characters require HTML-entity escaping (e.g. &amp; for &)

You can also disable the auto-quoting mechanism by setting smart_quotes: false in your config. Explicit replacements will still be performed. See Configuration Options

Embedded Icons

The text method will also respond to a block. The object that gets passed to this block allows for embedding images into the flow of your text. The following methods are supported:

text(str: 'Take 1 :tool: and gain 2 :health:') do |embed|
  embed.svg key: ':tool:', file: 'tool.svg'
  embed.png key: ':health:', file: 'health.png'
end
embed.svg

All of these options support arrays and singleton expansion (except for range). See Squib Thinks in Arrays for deeper explanation.

key

default: '*'

the string to replace with the graphic. Can be multiple letters, e.g. ':tool:'

file

default: ''

file(s) to read in, relative to the root directory or img_dir if set in the config.

data

default: nil

render from an SVG XML string. Overrides file if both are specified (a warning is shown).

id

default: nil

if set, then only render the SVG element with the given id. Prefix ‘#’ is optional. Note: the x-y coordinates are still relative to the SVG document’s page.

force_id

default: false

if set, then this svg will not be rendered at all if the id is empty or nil. If not set, the entire SVG is rendered.

layout

default: nil

entry in the layout to use as defaults for this command. See Layouts are Squib’s Best Feature

width

default: :native

the width of the image rendered. Does not support :scale (yet)

height

default: :native

the height the height of the image rendered. Does not support :scale (yet)

dx

default: 0

“delta x”, or adjust the icon horizontally by x pixels

dy

default: 0

“delta y”, or adjust the icon vertically by y pixels

flip_horizontal

default: false

Flip this image about its center horizontally (i.e. left becomes right and vice versa).

flip_vertical

default: false

Flip this image about its center verticall (i.e. top becomes bottom and vice versa).

alpha

default: 1.0

the alpha-transparency percentage used to blend this image.

angle

default: 0

rotation of the in radians. Note that this rotates around the upper-left corner, making the placement of x-y coordinates slightly tricky.

embed.png

All of these options support arrays and singleton expansion (except for range). See Squib Thinks in Arrays for deeper explanation.

key

default: '*'

the string to replace with the graphic. Can be multiple letters, e.g. ':tool:'

file

default: ''

file(s) to read in, relative to the root directory or img_dir if set in the config.

layout

default: nil

entry in the layout to use as defaults for this command. See Layouts are Squib’s Best Feature

width

default: :native

the width of the image rendered.

height

default: :native

the height the height of the image rendered.

dx

default: 0

“delta x”, or adjust the icon horizontally by x pixels

dy

default: 0

“delta y”, or adjust the icon vertically by y pixels

flip_horizontal

default: false

Flip this image about its center horizontally (i.e. left becomes right and vice versa).

flip_vertical

default: false

Flip this image about its center verticall (i.e. top becomes bottom and vice versa).

alpha

default: 1.0

the alpha-transparency percentage used to blend this image.

blend

default: :none

the composite blend operator used when applying this image. See Blend Modes at http://cairographics.org/operators. The possibilties include :none, :multiply, :screen, :overlay, :darken, :lighten, :color_dodge, :color_burn, :hard_light, :soft_light, :difference, :exclusion, :hsl_hue, :hsl_saturation, :hsl_color, :hsl_luminosity. String versions of these options are accepted too.

placeholder

default: nil

if file does not exist, but the file pointed to by this string does, then draw this image instead.

No warning is thrown when a placeholder is used.

If this is non-nil, but the placeholder file does not exist, then a warning is thrown and no image is drawn.

mask

default: nil

Accepts a color (see Specifying Colors & Gradients). If specified, the image will be used as a mask for the given color/gradient. Transparent pixels are ignored, opaque pixels are the given color. Note: the origin for gradient coordinates is at the given x,y, not at 0,0 as it is most other places.

angle

default: 0

rotation of the in radians. Note that this rotates around the upper-left corner, making the placement of x-y coordinates slightly tricky.

Examples

See The Mighty text Method.

triangle

Draw a triangle at the given coordinates.

Options

All of these options support arrays and singleton expansion (except for range). See Squib Thinks in Arrays for deeper explanation.

x1

default: 100

the first x-coordinate to place. Supports Unit Conversion and XYWH Shorthands.

y1

default: 100

the first y-coordinate to place. Supports Unit Conversion and XYWH Shorthands.

x2

default: 150

the second x-coordinate to place. Supports Unit Conversion and XYWH Shorthands.

y2

default: 150

the second y-coordinate to place. Supports Unit Conversion and XYWH Shorthands.

x3

default: 100

the third x-coordinate to place. Supports Unit Conversion and XYWH Shorthands.

y3

default: 150

the third y-coordinate to place. Supports Unit Conversion and XYWH Shorthands.

fill_color

default: '#0000' (fully transparent)

the color or gradient to fill with. See Specifying Colors & Gradients.

stroke_color

default: :black

the color with which to stroke the outside of the shape. See Specifying Colors & Gradients.

stroke_width

default: 2

the width of the outside stroke. Supports Unit Conversion.

stroke_strategy

default: :fill_first

Specify whether the stroke is done before (thinner) or after (thicker) filling the shape.

Must be either :fill_first or :stroke_first (or their string equivalents).

dash

default: '' (no dash pattern set)

Define a dash pattern for the stroke. This is a special string with space-separated numbers that define the pattern of on-and-off alternating strokes, measured in pixels or units. For example, '0.02in 0.02in' will be an equal on-and-off dash pattern. Supports Unit Conversion.

cap

default: :butt

Define how the end of the stroke is drawn. Options are :square, :butt, and :round (or string equivalents of those).

join

default: :mitre

Specifies how to render the junction of two lines when stroking. Options are :mitre, :round, and :bevel.

range

default: :all

the range of cards over which this will be rendered. See Using range to specify cards

layout

default: nil

entry in the layout to use as defaults for this command. See Layouts are Squib’s Best Feature.

Examples

use_layout

Load a layout file and merge into the current set of layouts.

Options

file

default: 'layout.yml'

The file or array of files to load. Treated exactly how Squib::Deck.new parses it.

Examples

xlsx

Pulls ExcelX data from .xlsx files into a hash of arrays keyed by the headers. First row is assumed to be the header row.

The xlsx method is a member of Squib::Deck, but it is also available outside of the Deck DSL with Squib.xlsx(). This allows a construction like:

data = Squib.xlsx file: 'data.xlsx'
Squib::Deck.new(cards: data['name'].size) do
end

Options

file

default: 'deck.xlsx'

the xlsx-formatted file to open. Opens relative to the current directory.

sheet

default: 0

The zero-based index of the sheet from which to read.

strip

default: true

When true, strips leading and trailing whitespace on values and headers

explode

default: 'qty'

Quantity explosion will be applied to the column this name. For example, rows in the csv with a 'qty' of 3 will be duplicated 3 times.

Warning

Data import methods such as xlsx and csv will not consult your layout file or follow the Squib Thinks in Arrays feature.

Individual Pre-processing

The xlsx method also takes in a block that will be executed for each cell in your data. This is useful for processing individual cells, like putting a dollar sign in front of dollars, or converting from a float to an integer. The value of the block will be what is assigned to that cell. For example:

resource_data = Squib.xlsx(file: 'sample.xlsx') do |header, value|
  case header
  when 'Cost'
    "$#{value}k" # e.g. "3" becomes "$3k"
  else
    value # always return the original value if you didn't do anything to it
  end
end

Examples

To get the sample Excel files, go to its source

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
require 'squib'

Squib::Deck.new(cards: 3) do
  background color: :white

  # Reads the first sheet by default (sheet 0)
  # Outputs a hash of arrays with the header names as keys
  data = xlsx file: 'sample.xlsx'

  text str: data['Name'], x: 250, y: 55, font: 'Arial 18'
  text str: data['Level'], x: 65, y: 65, font: 'Arial 24'
  text str: data['Description'], x: 65, y: 600, font: 'Arial 12'

  save format: :png, prefix: 'sample_excel_' # save to individual pngs
end

# xlsx is also a Squib-module-level function, so this also works:
data      = Squib.xlsx file: 'explode_quantities.xlsx' # 2 rows...
num_cards = data['Name'].size                          #          ...but 4 cards!

Squib::Deck.new(cards: num_cards) do
  background color: :white
  rect # card border
  text str: data['Name'], font: 'Arial 18'
  save_sheet prefix: 'sample_xlsx_qty_', columns: 4
end


# Here's another example, a bit more realistic. Here's what's going on:
#   * We call xlsx from Squib directly - BEFORE Squib::Deck creation. This
#     allows us to infer the number of cards based on the size of the "Name"
#     field
#   * We make use of quantity explosion. Fields named "Qty" or "Quantity"
#     (any capitalization), or any other in the "qty_header" get expanded by the
#     number given
#   * We also make sure that trailing and leading whitespace is stripped
#     from each value. This is the default behavior in Squib, but the options
#     are here just to make sure.

resource_data = Squib.xlsx(file: 'sample.xlsx', explode: 'Qty', sheet: 2, strip: true) do |header, value|
  case header
  when 'Cost'
    "$#{value}k" # e.g. "3" becomes "$3k"
  else
    value # always return the original value if you didn't do anything to it
  end
end

Squib::Deck.new(cards: resource_data['Name'].size) do
  background color: :white
  rect width: :deck, height: :deck
  text str: resource_data['Name'], align: :center, width: :deck, hint: 'red'
  text str: resource_data['Cost'], align: :right, width: :deck, hint: 'red'
  save_sheet prefix: 'sample_excel_resources_', columns: 3
end

yaml

Pulls deck data from a YAML files into a Squib::DataFrame (essentially a hash of arrays).

Parsing uses Ruby’s built-in Yaml package.

The yaml method is a member of Squib::Deck, but it is also available outside of the Deck DSL with Squib.yaml(). This allows a construction like:

data = Squib.yaml file: 'data.yml'
Squib::Deck.new(cards: data['name'].size) do
end

The Yaml file format assumes that the entire deck is an array, then each element of the array is a hash. Every key encountered in that hash will translate to a “column” in the data frame. If a key exists in one card and not in another, then it defaults to nil.

Warning

Case matters in your Yaml keys.

Options

file

default: 'deck.yml'

the YAML-formatted file to open. Opens relative to the current directory. If data is set, this option is overridden.

data

default: nil

when set, method will parse this Yaml data instead of reading the file.

explode

default: 'qty'

Quantity explosion will be applied to the column this name. For example, rows in the csv with a 'qty' of 3 will be duplicated 3 times.

Warning

Data import methods such as xlsx and csv will not consult your layout file or follow the Squib Thinks in Arrays feature.

Individual Pre-processing

The yaml method also takes in a block that will be executed for each cell in your data. This is useful for processing individual cells, like putting a dollar sign in front of dollars, or converting from a float to an integer. The value of the block will be what is assigned to that cell. For example:

resource_data = Squib.yaml(file: 'sample.yaml') do |header, value|
  case header
  when 'Cost'
    "$#{value}k" # e.g. "3" becomes "$3k"
  else
    value # always return the original value if you didn't do anything to it
  end
end

Examples

To get the sample Yaml files, go to its source

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
require 'squib'

Squib::Deck.new(cards: 2) do
  background color: :white

  # Outputs a hash of arrays with the header names as keys
  data = yaml file: 'sample.yaml'
  text str: data['Type'], x: 250, y: 55, font: 'Arial 18'
  text str: data['Level'], x: 65, y: 65, font: 'Arial 24'

  save format: :png, prefix: 'sample_yaml_'
end

Here’s the sample.yaml

1
2
3
4
5
- Type: Thief
  Level: 1

- Type: Mastermind
  Level: 2

Indices and tables