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
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
|