soli-pdf

Quote with options

Base scope and optional work priced separately, with tick boxes and two totals side by side.

The tick box is an empty cell with its four borders on: no glyph involved, so it cannot render as tofu in a font that lacks a box character.

Tick boxes Side-by-side totals 1 page
Page 1
Page 1 of 1
render_quote_options.rb ruby
require 'json'
require 'soli/pdf'

template = JSON.parse(File.read('quote_options.template.json'))
data     = JSON.parse(File.read('quote_options.data.json'))

pdf = Soli::PDF.render(
  template: template,
  data: data,
  title: 'Quote FK-2025-031'
)
File.binwrite('quote_options.pdf', pdf)

Run it yourself

  1. Add the gem to your Gemfile — see Installation.
  2. Download quote_options.template.json and quote_options.data.json.
  3. Run ruby render_quote_options.rb.
quote_options.template.json json
{
  "fonts": ["titillium"],
  "options": { "header_height": 0 },
  "footer": [
    { "type": "paragraph", "value": "${studio.name} · ${studio.email} · Quote ${quote.number} · Page #PAGE# of #TOTAL_PAGE#", "options": { "alignment": "center", "fontSize": 7.5, "color": "7A7290" } }
  ],
  "content": [
    {
      "type": "table",
      "header_columns": [
        {
          "content": [
            { "type": "text", "value": "${studio.name}", "fontSize": 18, "fontWeight": "bold" },
            { "type": "text", "value": "${studio.tagline}", "fontSize": 9 }
          ],
          "width": 300, "alignment": "left",
          "borderSides": { "top": "false", "bottom": "false", "left": "false", "right": "false" }
        },
        {
          "content": [
            { "type": "text", "value": "QUOTE", "fontSize": 24, "fontWeight": "bold" },
            { "type": "text", "value": "${quote.number}", "fontSize": 10 }
          ],
          "width": 181, "alignment": "right",
          "borderSides": { "top": "false", "bottom": "false", "left": "false", "right": "false" }
        }
      ],
      "rows": [],
      "options": { "header": { "fillColor": "4C3A8A", "textColor": "FFFFFF" }, "padding_x": 18, "padding_y": 16 }
    },
    {
      "type": "table",
      "header_columns": [
        {
          "text": "${quote.project}   ·   For ${customer.name}   ·   Valid until ${quote.valid_until}",
          "width": 481, "fontSize": 9, "fontWeight": "bold", "alignment": "center",
          "borderSides": { "top": "false", "bottom": "false", "left": "false", "right": "false" }
        }
      ],
      "rows": [],
      "options": { "header": { "fillColor": "F1EEF9", "textColor": "4C3A8A" }, "padding_x": 12, "padding_y": 8 }
    },
    { "type": "move", "y": 22 },

    { "type": "paragraph", "value": "BASE SCOPE", "options": { "fontSize": 9, "fontWeight": "bold", "color": "4C3A8A" } },
    { "type": "move", "y": 2 },
    { "type": "paragraph", "value": "Included in the quoted price. No action needed.", "options": { "fontSize": 8.5, "color": "6E6880" } },
    { "type": "move", "y": 8 },
    {
      "type": "table",
      "data": "base_items",
      "header_columns": [
        { "text": "DELIVERABLE", "width": 281, "fontSize": 8, "fontWeight": "bold", "alignment": "left",   "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "4C3A8A" },
        { "text": "QTY",         "width": 50,  "fontSize": 8, "fontWeight": "bold", "alignment": "center", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "4C3A8A" },
        { "text": "UNIT",        "width": 70,  "fontSize": 8, "fontWeight": "bold", "alignment": "right",  "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "4C3A8A" },
        { "text": "AMOUNT",      "width": 80,  "fontSize": 8, "fontWeight": "bold", "alignment": "right",  "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "4C3A8A" }
      ],
      "rows": [
        [
          { "text": "${name}",   "width": 281, "fontSize": 9.5, "alignment": "left",   "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "E7E3F0" },
          { "text": "${qty}",    "width": 50,  "fontSize": 9.5, "alignment": "center", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "E7E3F0" },
          { "text": "${unit}",   "width": 70,  "fontSize": 9.5, "alignment": "right",  "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "E7E3F0" },
          { "text": "${amount}", "width": 80,  "fontSize": 9.5, "alignment": "right",  "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "E7E3F0" }
        ]
      ],
      "options": { "padding_x": 6, "padding_y": 6 }
    },
    { "type": "move", "y": 6 },
    {
      "type": "table",
      "header_columns": [],
      "rows": [
        [
          { "text": "Base scope, net", "width": 401, "fontSize": 9.5, "fontWeight": "bold", "alignment": "right", "borderSides": { "top": "false", "bottom": "false", "left": "false", "right": "false" } },
          { "text": "${totals.base_net}", "width": 80, "fontSize": 9.5, "fontWeight": "bold", "alignment": "right", "borderSides": { "top": "false", "bottom": "false", "left": "false", "right": "false" } }
        ]
      ],
      "options": { "padding_x": 6, "padding_y": 4 }
    },
    { "type": "move", "y": 24 },

    { "type": "paragraph", "value": "OPTIONS", "options": { "fontSize": 9, "fontWeight": "bold", "color": "4C3A8A" } },
    { "type": "move", "y": 2 },
    { "type": "paragraph", "value": "Tick the options you want and return this page. Each is priced independently and can be added later.", "options": { "fontSize": 8.5, "color": "6E6880" } },
    { "type": "move", "y": 8 },
    {
      "type": "table",
      "data": "options",
      "header_columns": [
        { "text": "",           "width": 22,  "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "4C3A8A" },
        { "text": "OPTION",     "width": 259, "fontSize": 8, "fontWeight": "bold", "alignment": "left",   "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "4C3A8A" },
        { "text": "QTY",        "width": 50,  "fontSize": 8, "fontWeight": "bold", "alignment": "center", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "4C3A8A" },
        { "text": "UNIT",       "width": 70,  "fontSize": 8, "fontWeight": "bold", "alignment": "right",  "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "4C3A8A" },
        { "text": "AMOUNT",     "width": 80,  "fontSize": 8, "fontWeight": "bold", "alignment": "right",  "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "true" }, "borderColor": "4C3A8A" }
      ],
      "rows": [
        [
          { "text": "",         "width": 22,  "borderSides": { "top": "true", "left": "true", "right": "true", "bottom": "true" }, "borderColor": "4C3A8A", "valign": "middle" },
          { "text": "${name}",  "width": 259, "fontSize": 9.5, "alignment": "left",   "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "false" } },
          { "text": "${qty}",   "width": 50,  "fontSize": 9.5, "alignment": "center", "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "false" } },
          { "text": "${unit}",  "width": 70,  "fontSize": 9.5, "alignment": "right",  "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "false" } },
          { "text": "${amount}","width": 80,  "fontSize": 9.5, "alignment": "right",  "borderSides": { "top": "false", "left": "false", "right": "false", "bottom": "false" } }
        ]
      ],
      "options": { "header": { "fillColor": "F1EEF9", "textColor": "4C3A8A" }, "padding_x": 6, "padding_y": 7 }
    },
    { "type": "move", "y": 6 },
    {
      "type": "table",
      "header_columns": [],
      "rows": [
        [
          { "text": "All options, net", "width": 401, "fontSize": 9.5, "fontWeight": "bold", "alignment": "right", "borderSides": { "top": "false", "bottom": "false", "left": "false", "right": "false" } },
          { "text": "${totals.options_net}", "width": 80, "fontSize": 9.5, "fontWeight": "bold", "alignment": "right", "borderSides": { "top": "false", "bottom": "false", "left": "false", "right": "false" } }
        ]
      ],
      "options": { "padding_x": 6, "padding_y": 4 }
    },
    { "type": "move", "y": 26 },

    { "type": "rect", "width": 235, "height": 74, "fill": "F4F3F7" },
    { "type": "move", "x": 246, "y": 0 },
    { "type": "rect", "width": 235, "height": 74, "fill": "4C3A8A" },
    { "type": "move", "x": -232, "y": 14 },
    { "type": "paragraph", "spans": [ { "text": "BASE SCOPE ONLY", "fontWeight": "bold", "color": "6E6880" } ], "options": { "fontSize": 8 } },
    { "type": "paragraph", "spans": [ { "text": "${totals.base_gross}", "fontWeight": "bold", "color": "2A2438" } ], "options": { "fontSize": 20 } },
    { "type": "paragraph", "spans": [ { "text": "incl. VAT ${quote.vat_rate} · ${totals.base_vat}", "color": "6E6880" } ], "options": { "fontSize": 8 } },
    { "type": "move", "x": 246, "y": -44 },
    { "type": "paragraph", "spans": [ { "text": "WITH ALL OPTIONS", "fontWeight": "bold", "color": "FFFFFF" } ], "options": { "fontSize": 8 } },
    { "type": "paragraph", "spans": [ { "text": "${totals.all_gross}", "fontWeight": "bold", "color": "FFFFFF" } ], "options": { "fontSize": 20 } },
    { "type": "paragraph", "spans": [ { "text": "incl. VAT ${quote.vat_rate} · ${totals.all_vat}", "color": "D9D0F0" } ], "options": { "fontSize": 8 } },
    { "type": "move", "x": -260, "y": 36 },
    { "type": "paragraph", "value": "${quote.note}", "options": { "fontSize": 8, "color": "7A7290" } }
  ]
}
quote_options.data.json json
{
  "data": {
    "studio": {
      "name": "Fjord & Kessler",
      "tagline": "Brand and packaging design",
      "email": "studio@fjordkessler.at"
    },
    "customer": {
      "name": "Ovenhaus Bakery, Vienna"
    },
    "quote": {
      "number": "FK-2025-031",
      "project": "Brand relaunch",
      "valid_until": "15 Jan 2026",
      "vat_rate": "20%",
      "note": "Options may be added at any point during the project at the prices shown. Prices hold for 60 days from the issue date; after that they are re-quoted at the then-current rates."
    },
    "base_items": [
      { "name": "Brand identity system", "qty": "1", "unit": "3,200.00", "amount": "3,200.00" },
      { "name": "Packaging design, three SKUs", "qty": "3", "unit": "480.00", "amount": "1,440.00" },
      { "name": "Brand guidelines document", "qty": "1", "unit": "900.00", "amount": "900.00" }
    ],
    "options": [
      { "name": "Motion identity package", "qty": "1", "unit": "1,200.00", "amount": "1,200.00" },
      { "name": "Photography art direction", "qty": "1 day", "unit": "750.00", "amount": "750.00" },
      { "name": "Storefront signage design", "qty": "1", "unit": "640.00", "amount": "640.00" }
    ],
    "totals": {
      "base_net": "€5,540.00",
      "options_net": "€2,590.00",
      "base_vat": "€1,108.00",
      "base_gross": "€6,648.00",
      "all_vat": "€1,626.00",
      "all_gross": "€9,756.00"
    }
  }
}