class Prawn::Font::AFM

@private

Constants

BUILT_INS

Attributes

hide_m17n_warning[RW]

Public Class Methods

font_data() click to toggle source

parse each ATM font file once only

# File lib/prawn/font/afm.rb, line 47
def self.font_data
  @font_data ||= SynchronizedCache.new
end
metrics_path() click to toggle source
# File lib/prawn/font/afm.rb, line 31
def self.metrics_path
  @metrics_path ||= if ENV['METRICS']
                      ENV['METRICS'].split(':')
                    else
                      [
                        '.', '/usr/lib/afm',
                        '/usr/local/lib/afm',
                        '/usr/openwin/lib/fonts/afm',
                        Prawn::DATADIR + '/fonts'
                      ]
                    end
end

Public Instance Methods

bbox() click to toggle source

The font bbox, as an array of integers

# File lib/prawn/font/afm.rb, line 77
def bbox
  @bbox ||= @attributes['fontbbox'].split(/\s+/).map { |e| Integer(e) }
end
character_count(str) click to toggle source

Returns the number of characters in str (a WinAnsi-encoded string).

# File lib/prawn/font/afm.rb, line 122
def character_count(str)
  str.length
end
encode_text(text, options = {}) click to toggle source

Perform any changes to the string that need to happen before it is rendered to the canvas. Returns an array of subset “chunks”, where each chunk is an array of two elements. The first element is the font subset number, and the second is either a string or an array (for kerned text).

For Adobe fonts, there is only ever a single subset, so the first element of the array is “0”, and the second is the string itself (or an array, if kerning is performed).

The text parameter must be in WinAnsi encoding (cp1252).

# File lib/prawn/font/afm.rb, line 138
def encode_text(text, options = {})
  [[0, options[:kerning] ? kern(text) : text]]
end
glyph_present?(char) click to toggle source
# File lib/prawn/font/afm.rb, line 142
def glyph_present?(char)
  !normalize_encoding(char).nil?
rescue Prawn::Errors::IncompatibleStringEncoding
  false
end
has_kerning_data?() click to toggle source

Returns true if the font has kerning data, false otherwise

# File lib/prawn/font/afm.rb, line 96
def has_kerning_data?
  @kern_pairs.any?
end
normalize_encoding(text) click to toggle source

built-in fonts only work with winansi encoding, so translate the string. Changes the encoding in-place, so the argument itself is replaced with a string in WinAnsi encoding.

# File lib/prawn/font/afm.rb, line 104
def normalize_encoding(text)
  text.encode('windows-1252')
rescue ::Encoding::InvalidByteSequenceError,
       ::Encoding::UndefinedConversionError

  raise Prawn::Errors::IncompatibleStringEncoding,
    "Your document includes text that's not compatible with the " \
    "Windows-1252 character set.\n" \
    "If you need full UTF-8 support, use TTF fonts instead of PDF's " \
    "built-in fonts.\n"
end
to_utf8(text) click to toggle source
# File lib/prawn/font/afm.rb, line 116
def to_utf8(text)
  text.encode('UTF-8')
end
unicode?() click to toggle source
# File lib/prawn/font/afm.rb, line 27
def unicode?
  false
end

Private Instance Methods

find_font(file) click to toggle source
# File lib/prawn/font/afm.rb, line 167
def find_font(file)
  self.class.metrics_path.find { |f| File.exist? "#{f}/#{file}" } +
    "/#{file}"
rescue NoMethodError
  raise Prawn::Errors::UnknownFont,
    "Couldn't find the font: #{file} in any of:\n" +
    self.class.metrics_path.join("\n")
end
kern(string) click to toggle source

converts a string into an array with spacing offsets bewteen characters that need to be kerned

String must be encoded as WinAnsi

# File lib/prawn/font/afm.rb, line 252
def kern(string)
  kerned = [[]]
  last_byte = nil

  string.each_byte do |byte|
    k = last_byte && @kern_pair_table[[last_byte, byte]]
    if k
      kerned << -k << [byte]
    else
      kerned.last << byte
    end
    last_byte = byte
  end

  kerned.map do |e|
    e = e.is_a?(Array) ? e.pack('C*') : e
    if e.respond_to?(:force_encoding)
      e.force_encoding(::Encoding::Windows_1252)
    else
      e
    end
  end
end
parse_afm(file_name) click to toggle source
# File lib/prawn/font/afm.rb, line 176
def parse_afm(file_name)
  data = {
    glyph_widths: {},
    bounding_boxes: {},
    kern_pairs: {},
    attributes: {}
  }
  section = []

  File.foreach(file_name) do |line|
    case line
    when /^Start(\w+)/
      section.push Regexp.last_match(1)
      next
    when /^End(\w+)/
      section.pop
      next
    end

    case section
    when %w[FontMetrics CharMetrics]
      next unless line =~ /^CH?\s/

      name = line[/\bN\s+(\.?\w+)\s*;/, 1]
      data[:glyph_widths][name] = line[/\bWX\s+(\d+)\s*;/, 1].to_i
      data[:bounding_boxes][name] = line[/\bB\s+([^;]+);/, 1].to_s.rstrip
    when %w[FontMetrics KernData KernPairs]
      next unless line =~ /^KPX\s+(\.?\w+)\s+(\.?\w+)\s+(-?\d+)/
      data[:kern_pairs][[Regexp.last_match(1), Regexp.last_match(2)]] =
        Regexp.last_match(3).to_i
    when %w[FontMetrics KernData TrackKern],
      %w[FontMetrics Composites]
      next
    else
      parse_generic_afm_attribute(line, data)
    end
  end

  # process data parsed from AFM file to build tables which
  #   will be used when measuring and kerning text
  data[:glyph_table] = (0..255).map do |i|
    data[:glyph_widths][Encoding::WinAnsi::CHARACTERS[i]].to_i
  end

  character_hash = Hash[
    Encoding::WinAnsi::CHARACTERS.zip(
      (0..Encoding::WinAnsi::CHARACTERS.size).to_a
    )
  ]
  data[:kern_pair_table] =
    data[:kern_pairs].each_with_object({}) do |p, h|
      h[p[0].map { |n| character_hash[n] }] = p[1]
    end

  data.each_value(&:freeze)
  data.freeze
end
parse_generic_afm_attribute(line, hash) click to toggle source
# File lib/prawn/font/afm.rb, line 234
def parse_generic_afm_attribute(line, hash)
  line =~ /(^\w+)\s+(.*)/
  key = Regexp.last_match(1).to_s.downcase
  value = Regexp.last_match(2)

  hash[:attributes][key] =
    if hash[:attributes][key]
      Array(hash[:attributes][key]) << value
    else
      value
    end
end
register(_subset) click to toggle source
# File lib/prawn/font/afm.rb, line 150
def register(_subset)
  font_dict = {
    Type: :Font,
    Subtype: :Type1,
    BaseFont: name.to_sym
  }

  # Symbolic AFM fonts (Symbol, ZapfDingbats) have their own encodings
  font_dict[:Encoding] = :WinAnsiEncoding unless symbolic?

  @document.ref!(font_dict)
end
symbolic?() click to toggle source
# File lib/prawn/font/afm.rb, line 163
def symbolic?
  attributes['characterset'] == 'Special'
end
unscaled_width_of(string) click to toggle source
# File lib/prawn/font/afm.rb, line 276
def unscaled_width_of(string)
  string.bytes.inject(0) do |s, r|
    s + @glyph_table[r]
  end
end