lib/pdf/reader/font.rb



################################################################################
#
# Copyright (C) 2008 James Healy (jimmy@deefa.com)
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
################################################################################

class PDF::Reader
  class Font
    attr_accessor :label, :subtype, :encoding, :descendantfonts, :tounicode
    attr_reader :widths, :first_char, :ascent, :descent, :missing_width, :bbox
    attr_reader :basefont

    def initialize(ohash = nil, obj = nil)
      if ohash.nil? || obj.nil?
        $stderr.puts "DEPREACTION WARNING - PDF::Reader::Font.new should be called with 2 args"
        return
      end
      @ohash = ohash

      extract_base_info(obj)
      extract_descriptor(obj)
      extract_descendants(obj)
    end

    def basefont=(font)
      # setup a default encoding for the selected font. It can always be overridden
      # with encoding= if required
      case font
      when "Symbol" then
        @encoding = PDF::Reader::Encoding.new("SymbolEncoding")
      when "ZapfDingbats" then
        @encoding = PDF::Reader::Encoding.new("ZapfDingbatsEncoding")
      else
        @encoding = nil
      end
      @basefont = font
    end

    def to_utf8(params)
      raise UnsupportedFeatureError, "font encoding '#{encoding}' currently unsupported" if encoding.kind_of?(String)

      if params.class == String
        # translate the bytestram into a UTF-8 string.
        # If an encoding hasn't been specified, assume the text using this
        # font is in Adobe Standard Encoding.
        (encoding || PDF::Reader::Encoding.new(:StandardEncoding)).to_utf8(params, tounicode)
      elsif params.class == Array
        params.collect { |param| self.to_utf8(param) }
      else
        params
      end
    end

    def glyph_width(c)
      @missing_width ||= 0
      if @widths.nil?
        0
      else
        @widths.fetch(c.codepoints.first - @first_char, @missing_width)
      end
    end

    private

    def extract_base_info(obj)
      @subtype  = @ohash.object(obj[:Subtype])
      @basefont = @ohash.object(obj[:BaseFont])
      @encoding = PDF::Reader::Encoding.new(@ohash.object(obj[:Encoding]))
      @widths   = @ohash.object(obj[:Widths])
      @first_char = @ohash.object(obj[:FirstChar])
      if obj[:ToUnicode]
        stream = @ohash.object(obj[:ToUnicode])
        @tounicode = PDF::Reader::CMap.new(stream.unfiltered_data)
      end
    end

    def extract_descriptor(obj)
      return unless obj[:FontDescriptor]

      fd       = @ohash.object(obj[:FontDescriptor])
      @ascent  = @ohash.object(fd[:Ascent])
      @descent = @ohash.object(fd[:Descent])
      @missing_width = @ohash.object(fd[:MissingWidth])
      @bbox    = @ohash.object(fd[:FontBBox])
    end

    def extract_descendants(obj)
      return unless obj[:DescendantFonts]

      descendants = @ohash.object(obj[:DescendantFonts])
      @descendantfonts = descendants.map { |desc|
        PDF::Reader::Font.new(@ohash, @ohash.object(desc))
      }
    end

  end
end