class Courseware::Printer

Public Class Methods

new(config, opts) { |self| ... } click to toggle source
# File lib/courseware/printer.rb, line 4
def initialize(config, opts)
  @config  = config
  @course  = opts[:course]  or raise 'Course is a required option'
  @prefix  = opts[:prefix]  or raise 'Prefix is a required option'
  @version = opts[:version] or raise 'Version is a required option'
  raise unless can_print?

  @varfile = config[:presfile] or raise 'Presentation file is not set properly!'
  @variant = File.basename(@varfile, '.json') unless @varfile == 'showoff.json'

  @pdfopts = "--pdf-title '#{@course}' --pdf-author '#{@config[:pdf][:author]}' --pdf-subject '#{@config[:pdf][:subject]}'"
  @pdfopts << " --disallow-modify" if @config[:pdf][:protected]

  if @config[:pdf][:watermark]
    showoff   = Courseware.parse_showoff(@config[:presfile])
    default   = @event_id[/-?(\w*)$/, 1] rescue nil

    @event_id = @config[:event_id] || showoff['event_id'] || Courseware.question('Enter the Event ID:')
    @password = @config[:key]      || showoff['key']      || Courseware.question('Enter desired password:', default)

    if @config[:nocache]
      # Find the '_support' directory, up to three levels up
      # This allows some flexibility in how the courseware repository is laid out
      path = '_support'
      3.times do
        break if File.exist?(path)
        path = File.join('..', path)
      end
      raise "No support files found" unless File.directory?(path)

      @watermark_style = File.join(path, 'watermark.css')
      @watermark_pdf   = File.join(path, 'watermark.pdf')
    else
      @watermark_style = File.join(@config[:cachedir], 'templates', 'watermark.css')
      @watermark_pdf   = File.join(@config[:cachedir], 'templates', 'watermark.pdf')
    end
  end

  FileUtils.mkdir(config[:output]) unless File.directory?(config[:output])

  if block_given?
    yield self
    FileUtils.rm_rf('static')
  end
end

Public Instance Methods

can_print?() click to toggle source

Ensure that the printing toolchain is in place.

# File lib/courseware/printer.rb, line 81
def can_print?
  case @config[:renderer]
  when :wkhtmltopdf
    # Fonts must be installed locally for wkhtmltopdf to find them
    if RUBY_PLATFORM =~ /darwin/
      fontpath = File.expand_path('~/Library/Fonts')
      Dir.glob('_fonts/*').each do |font|
        destination = "#{fontpath}/#{File.basename(font)}"
        FileUtils.cp(font, destination) unless File.exists?(destination)
      end
    end

    unless system 'wkhtmltopdf --version >/dev/null 2>&1' and system 'pdftk --version >/dev/null 2>&1'
      msg = "We use wkhtmltopdf and pdftk to generate courseware PDF files.\n" +
            "\n"                                                               +
            "You should install them using Homebrew or directly from:\n"       +
            "  * http://wkhtmltopdf.org/downloads.htm\n"                       +
            "  * https://www.pdflabs.com/tools/pdftk-server/#download"

      unless RUBY_PLATFORM =~ /darwin/
        msg << "\n\n"
        msg << 'Please install all the fonts in the `_fonts` directory to your system.'
      end

      Courseware.dialog('Printing toolchain unavailable.', msg)
      return false
    end

  when :prince
    unless system 'prince --version >/dev/null 2>&1'
      msg = "This course is configured to use PrinceXMLto generate PDF files.\n" +
            "\n"                                                                 +
            "You should install version 9 from:\n"                               +
            "  * http://www.princexml.com/download/\n"                           +
            "\n"                                                                 +
            "And the license from:\n"                                            +
            "  * https://confluence.puppet.com/display/EDU/Licenses"

      Courseware.dialog('Printing toolchain unavailable.', msg)
      return false
    end
  end

  return true
end
exercises() click to toggle source
# File lib/courseware/printer.rb, line 62
def exercises
  $logger.info "Generating exercise guide pdf for #{@course} #{@version}..."

  generate_pdf(:exercises)
end
generate_html(subject) click to toggle source

clean out the static dir and build the source html

# File lib/courseware/printer.rb, line 128
def generate_html(subject)
  case subject
  when :handouts, :print
    subject = 'print'
  when :exercises, :solutions
    subject = "supplemental #{subject}"
  when :guide
    subject = 'print guide'
  else
    raise "I don't know how to generate HTML of #{subject}."
  end

  begin
    # Until showoff static knows about -f, we have to schlup around files
    if @variant
      FileUtils.mv 'showoff.json', '.showoff.json.tmp'
      FileUtils.cp @varfile, 'showoff.json'
    end

    FileUtils.rm_rf('static')
    system("showoff static #{subject}")
    if File.exists? 'cobrand.png'
      FileUtils.mkdir(File.join('static', 'image'))
      FileUtils.cp('cobrand.png', File.join('static', 'image', 'cobrand.png'))
    end
  ensure
    FileUtils.mv('.showoff.json.tmp', 'showoff.json') if File.exist? '.showoff.json.tmp'
  end
end
generate_pdf(subject) click to toggle source
# File lib/courseware/printer.rb, line 158
def generate_pdf(subject)
  # TODO screen printing
  # @pdfopts << " #{SCREENHACK}"
  # @suffix = "-screen"

  # Build the filename for the output PDF. This is ugly.
  output = File.join(@config[:output], "#{@prefix}-")
  output << "#{@variant}-" if @variant
  output << 'w-' if @config[:pdf][:watermark]
  case subject
  when :print
    output << "#{@version}.pdf"
  when :exercises, :solutions, :guide
    output << "#{@version}-#{subject}.pdf"
  else
    raise "I don't know how to generate a PDF of #{subject}."
  end

  generate_html(subject)
  FileUtils.mkdir(@config[:output]) unless File.directory?(@config[:output])

  case @config[:renderer]
  when :wkhtmltopdf
    infofile    = File.join(@config[:output], 'info.txt')
    scratchfile = File.join(@config[:output], 'scratch.pdf')

    command = ['wkhtmltopdf', '-s', 'Letter', '--print-media-type', '--quiet']
    command << ['--footer-left', "#{@course} #{@version}", '--footer-center', '[page]']
    command << ['--footer-right', "©#{Time.now.year} Puppet", '--header-center', '[section]']
    command << ['--title', @course, File.join('static', 'index.html'), output]
    system(*command.flatten)
    raise 'Error generating PDF files' unless $?.success?

    pagecount = `pdftk #{output} dump_data`.each_line.select {|l| l.match /NumberOfPages/ }.first.split.last.to_i rescue nil
    if [1, 2].include? pagecount
      puts "#{output} is empty; aborting and cleaning up."
      FileUtils.rm(output)
      return
    end

    # We can't add metadata in the same run. It requires dumping, modifying, and updating
    if @event_id
      command = ['pdftk', output, 'dump_data', 'output', infofile]
      system(*command.flatten)
      raise 'Error retrieving PDF info' unless $?.success?
      info = File.read(infofile)
      File.open(infofile, 'w+') do |file|
        file.write("InfoBegin\n")
        file.write("InfoKey: Subject\n")
        file.write("InfoValue: #{@event_id}\n")
        file.write(info)
      end
      command = ['pdftk', output, 'update_info', infofile, 'output', scratchfile]
      system(*command.flatten)
      raise 'Error updating PDF info' unless $?.success?
    else
      FileUtils.mv(output, scratchfile)
    end

    command = ['pdftk', scratchfile, 'output', output]
    command << ['owner_pw', @config[:pdf][:password], 'allow', 'printing', 'CopyContents']
    command << ['background', @watermark_pdf] if @config[:pdf][:watermark]
    command << ['user_pw', @password] if @password
    system(*command.flatten)
    raise 'Error watermarking PDF files' unless $?.success?

    FileUtils.rm(infofile)    if File.exists? infofile
    FileUtils.rm(scratchfile) if File.exists? scratchfile

  when :prince
    command = ['prince', File.join('static', 'index.html')]
    command << ['--pdf-title', @course, '--pdf-author', @config[:pdf][:author], '--disallow-modify']
    command << ['--pdf-subject', @event_id] if @event_id
    command << ['--style', @watermark_style] if @config[:pdf][:watermark]
    command << ['--encrypt', '--user-password', @password] if @password
    command << ['--license-file', @config[:pdf][:license]] if (@config[:pdf][:license] and File.exists? @config[:pdf][:license])
    command << ['-o', output]
    system(*command.flatten)
    raise 'Error generating PDF files' unless $?.success?
  end

  FileUtils.rm_rf('static')
end
guide() click to toggle source
# File lib/courseware/printer.rb, line 74
def guide
  $logger.info "Generating instructor guide pdf for #{@course} #{@version}..."

  generate_pdf(:guide)
end
handouts() click to toggle source
# File lib/courseware/printer.rb, line 56
def handouts
  $logger.info "Generating handouts pdf for #{@course} #{@version}..."

  generate_pdf(:print)
end
print() click to toggle source
solutions() click to toggle source
# File lib/courseware/printer.rb, line 68
def solutions
  $logger.info "Generating solutions guide pdf for #{@course} #{@version}..."

  generate_pdf(:solutions)
end