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.

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
require 'squib'
require 'squib/sample_helpers'

Squib::Deck.new(width: 1000, height: 1250) 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 32', str: 'Bold and italic!', x: x, y: y - 50
    text font: 'Arial weight=300 32', str: 'Light bold!', x: x, y: y
    text font: 'Times New Roman, 32', str: 'Times New Roman', x: x, y: y + 50
    text font: 'NoSuchFont,Arial 32', 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 24'
  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 22'
  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 24'
  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 24'

      # 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
# encoding: UTF-8
require '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 54'
  text str: data['level'], x: 65, y: 40, font: 'Arial 72', color: :burnt_orange

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

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

  text str: 'Font string sizes can be overridden per card.', x: 65, y: 350,
       font: 'Impact 36', font_size: [16, 20, 24]

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

  extents = text str: 'Ink extent return value',
       x: 65, y: 550,
       font: 'Sans Bold', font_size: [16, 20, 24]
  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 22',
       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 20' # 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 22'

  # 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 18', hint: :red

  # Text can be justified, and have newlines
  text str: longtext, font: 'Arial 16',
       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 18',
       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 26', 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 26', 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 26', 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 18', 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 21',
       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 21',
       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 21',
       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 18',
       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 21',
       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 21',
       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 21',
       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 18', 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 21', 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 21', 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 24', 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 18', 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 18', 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: [18, 26, 35],
       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 20',
       markup: true, hint: :cyan

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

  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 20',
       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