Composer - Optional Content

This example shows how to use the optional content feature to create a quiz where the answers can be individually shown and hidden. There is also a link after the questions to toggle all answers.

Note: To provide the “All answers” layer switch functionality we need to make use of optional content membership dictionaries. However, this PDF feature is not supported by all PDF viewers. To enable the “All answers” switch in this example, use a1m, a2m, and a3m instead of a1, a2, and a3 when defining the optional content for a box.

Usage:
ruby composer_optional_content.rb

require ‘hexapdf’

Resulting PDF:
composer_optional_content.pdf
Preview:

Code

HexaPDF::Composer.create('composer_optional_content.pdf') do |composer|
  composer.styles(
    question: {font_size: 16, margin: [0, 0, 16], fill_color: 'hp-blue'},
    answer: {font: 'ZapfDingbats', fill_color: "green"},
  )

  all = composer.document.optional_content.ocg('All answers')
  a1 = composer.document.optional_content.ocg('Answer 1')
  a1m = composer.document.optional_content.create_ocmd([a1, all], policy: :any_on)
  a2 = composer.document.optional_content.ocg('Answer 2')
  a2m = composer.document.optional_content.create_ocmd([a2, all], policy: :any_on)
  a3 = composer.document.optional_content.ocg('Answer 3')
  a3m = composer.document.optional_content.create_ocmd([a3, all], policy: :any_on)

  composer.text('The Great Ruby Quiz', text_align: :center, margin: [0, 0, 24],
                font: 'Helvetica bold', font_size: 24)

  composer.list(marker_type: :decimal, item_spacing: 32, style: :question) do |listing|
    listing.multiple do |item|
      item.text('Who created Ruby?', style: :question)
      item.column(columns: 3, gaps: 5) do |cols|
        cols.list(marker_type: :decimal) do |answers|
          answers.text('Guido van Rossum')
          answers.multiple do |answer|
            answer.text('Yukihiro “Matz” Matsumoto', position: :float)
            answer.text("\u{a0}\u{a0}✔", style: :answer,
                        properties: {'optional_content' => a1})
          end
          answers.text('Rob Pike')
        end
      end
    end

    listing.multiple do |item|
      item.text('When was Ruby created?', style: :question)
      item.column(columns: 3, gaps: 5) do |cols|
        cols.list(marker_type: :decimal) do |answers|
          answers.text('1991')
          answers.text('1992')
          answers.multiple do |answer|
            answer.text('1993', position: :float)
            answer.text("\u{a0}\u{a0}✔", style: :answer,
                        properties: {'optional_content' => a2})
          end
        end
      end
    end

    listing.multiple do |item|
      item.text('What is the best PDF library for Ruby?', style: :question)
      answer = composer.document.layout.text('There are several PDF libraries for ' \
                                             'Ruby but the best is HexaPDF! :)',
                                             width: 400,
                                             properties: {'optional_content' => a3})
      item.formatted_text([{box: answer}], border: {width: [0, 0, 1]})
    end
  end

  action = composer.document.wrap({Type: :Action, S: :SetOCGState})
  action.add_state_change(:toggle, [a1, a2, a3])
  composer.text("Click to toggle answers", border: {width: 1, color: "red"},
                align: :right, padding: 2, overlays: [[:link, action: action]])

  composer.document.optional_content.default_configuration(
    BaseState: :OFF,
    Order: [all, a1, a2, a3],
  )
end