class PDF::Reader::Font
Represents a single font PDF object and provides some useful methods for extracting info. Mainly used for converting text to UTF-8.
Attributes
: Symbol?
: Numeric
: Array
: Array
: Integer?
: PDF::Reader::FontDescriptor?
: Integer?
: Symbol?
: PDF::Reader::CMap | nil
: Array
Public Class Methods
: (PDF::Reader::ObjectHash, Hash[Symbol, untyped]) -> void
# File lib/pdf/reader/font.rb, line 80 def initialize(ohash, obj) @ohash = ohash @tounicode = nil #: PDF::Reader::CMap | nil @descendantfonts = [] #: Array[PDF::Reader::Font] @widths = [] #: Array[Numeric] @first_char = nil #: Integer? @last_char = nil #: Integer? @basefont = nil #: Symbol? @font_descriptor = nil #: PDF::Reader::FontDescriptor? @cid_widths = [] #: Array[Numeric] @cid_default_width = 0 #: Numeric @encoding = PDF::Reader::Encoding.new(:StandardEncoding) #: PDF::Reader::Encoding @cached_widths = {} #: Hash[Integer, Numeric] @font_matrix = nil #: Array[Numeric] | nil extract_base_info(obj) extract_type3_info(obj) extract_descriptor(obj) extract_descendants(obj) @width_calc = build_width_calculator #: widthCalculator end
Public Instance Methods
looks up the specified codepoint and returns a value that is in (pdf) glyph space, which is 1000 glyph units = 1 text space unit
: (Integer | String) -> Numeric
# File lib/pdf/reader/font.rb, line 119 def glyph_width(code_point) if code_point.is_a?(String) code_point = unpack_string_to_array_of_ints(code_point, encoding.unpack).first raise MalformedPDFError, "code point missing" if code_point.nil? end @cached_widths[code_point] ||= @width_calc.glyph_width(code_point) end
In most cases glyph width is converted into text space with a simple divide by 1000. However, Type3 fonts provide their own FontMatrix that's used for the transformation.
: (Integer | String) -> Numeric
# File lib/pdf/reader/font.rb, line 133 def glyph_width_in_text_space(code_point) glyph_width_in_glyph_space = glyph_width(code_point) if @subtype == :Type3 x1, _y1 = font_matrix_transform(0,0) x2, _y2 = font_matrix_transform(glyph_width_in_glyph_space, 0) (x2 - x1).abs.round(2) else glyph_width_in_glyph_space / 1000.0 end end
: (Integer | String | Array[Integer | String]) -> String
# File lib/pdf/reader/font.rb, line 103 def to_utf8(params) if @tounicode to_utf8_via_cmap(params, @tounicode) else to_utf8_via_encoding(params) end end
: (String) -> (Array[Integer | Float | String | nil] | nil)
# File lib/pdf/reader/font.rb, line 112 def unpack(data) data.unpack(encoding.unpack) end
Private Instance Methods
: (Hash[Symbol, untyped]) -> PDF::Reader::Encoding
# File lib/pdf/reader/font.rb, line 208 def build_encoding(obj) if obj[:Encoding].is_a?(Symbol) # one of the standard encodings, referenced by name # TODO pass in a standard shape, always a Hash PDF::Reader::Encoding.new(obj[:Encoding]) elsif obj[:Encoding].is_a?(Hash) || obj[:Encoding].is_a?(PDF::Reader::Stream) PDF::Reader::Encoding.new(obj[:Encoding]) elsif obj[:Encoding].nil? default_encoding(@basefont) else raise MalformedPDFError, "Unexpected type for Encoding (#{obj[:Encoding].class})" end end
: () -> widthCalculator
# File lib/pdf/reader/font.rb, line 181 def build_width_calculator if @subtype == :Type0 PDF::Reader::WidthCalculator::TypeZero.new(self) elsif @subtype == :Type1 if @font_descriptor.nil? PDF::Reader::WidthCalculator::BuiltIn.new(self) else PDF::Reader::WidthCalculator::TypeOneOrThree .new(self) end elsif @subtype == :Type3 PDF::Reader::WidthCalculator::TypeOneOrThree.new(self) elsif @subtype == :TrueType if @font_descriptor PDF::Reader::WidthCalculator::TrueType.new(self) else # A TrueType font that isn't embedded. Most readers look for a version on the # local system and fallback to a substitute. For now, we go straight to a substitute PDF::Reader::WidthCalculator::BuiltIn.new(self) end elsif @subtype == :CIDFontType0 || @subtype == :CIDFontType2 PDF::Reader::WidthCalculator::Composite.new(self) else PDF::Reader::WidthCalculator::TypeOneOrThree.new(self) end end
: (Symbol | String | nil) -> PDF::Reader::Encoding
# File lib/pdf/reader/font.rb, line 169 def default_encoding(font_name) case font_name.to_s when "Symbol" then PDF::Reader::Encoding.new(:SymbolEncoding) when "ZapfDingbats" then PDF::Reader::Encoding.new(:ZapfDingbatsEncoding) else PDF::Reader::Encoding.new(:StandardEncoding) end end
: (Hash[Symbol, untyped]) -> void
# File lib/pdf/reader/font.rb, line 223 def extract_base_info(obj) @subtype = @ohash.deref_name(obj[:Subtype]) @basefont = @ohash.deref_name(obj[:BaseFont]) @encoding = build_encoding(obj) @widths = @ohash.deref_array_of_numbers(obj[:Widths]) || [] @first_char = @ohash.deref_integer(obj[:FirstChar]) @last_char = @ohash.deref_integer(obj[:LastChar]) # CID Fonts are not required to have a W or DW entry, if they don't exist, # the default cid width = 1000, see Section 9.7.4.1 PDF 32000-1:2008 pp 269 @cid_widths = @ohash.deref_array(obj[:W]) || [] @cid_default_width = @ohash.deref_number(obj[:DW]) || 1000 if obj[:ToUnicode] # ToUnicode is optional for Type1 and Type3 stream = @ohash.deref_stream(obj[:ToUnicode]) if stream @tounicode = PDF::Reader::CMap.new(stream.unfiltered_data) end end end
: (Hash[Symbol, untyped]) -> void
# File lib/pdf/reader/font.rb, line 267 def extract_descendants(obj) # per PDF 32000-1:2008 pp. 280 :DescendentFonts is: # A one-element array specifying the CIDFont dictionary that is the # descendant of this Type 0 font. if obj[:DescendantFonts] descendants = @ohash.deref_array(obj[:DescendantFonts]) || [] @descendantfonts = descendants.map { |desc| PDF::Reader::Font.new(@ohash, @ohash.deref_hash(desc) || {}) } else @descendantfonts = [] end end
: (Hash[Symbol, untyped]) -> void
# File lib/pdf/reader/font.rb, line 255 def extract_descriptor(obj) if obj[:FontDescriptor] # create a font descriptor object if we can, in other words, unless this is # a CID Font fd = @ohash.deref_hash(obj[:FontDescriptor]) || {} @font_descriptor = PDF::Reader::FontDescriptor.new(@ohash, fd) else @font_descriptor = nil end end
: (Hash[Symbol, untyped]) -> void
# File lib/pdf/reader/font.rb, line 246 def extract_type3_info(obj) if @subtype == :Type3 @font_matrix = @ohash.deref_array_of_numbers(obj[:FontMatrix]) || [ 0.001, 0, 0, 0.001, 0, 0 ] end end
Only valid for Type3 fonts
: (Numeric, Numeric) -> [Numeric, Numeric]
# File lib/pdf/reader/font.rb, line 149 def font_matrix_transform(x, y) return x, y if @font_matrix.nil? matrix = TransformationMatrix.new( @font_matrix[0] || 0, @font_matrix[1] || 0, @font_matrix[2] || 0, @font_matrix[3] || 0, @font_matrix[4] || 0, @font_matrix[5] || 0, ) if x == 0 && y == 0 [matrix.e, matrix.f] else [ (matrix.a * x) + (matrix.c * y) + (matrix.e), (matrix.b * x) + (matrix.d * y) + (matrix.f) ] end end
: (Integer | String | Array[Integer | String], PDF::Reader::CMap) -> String
# File lib/pdf/reader/font.rb, line 282 def to_utf8_via_cmap(params, cmap) case params when Integer [ cmap.decode(params) ].flatten.pack("U*") when String unpack_string_to_array_of_ints(params, encoding.unpack).map { |code_point| cmap.decode(code_point) }.flatten.pack("U*") when Array params.collect { |param| to_utf8_via_cmap(param, cmap) }.join("") end end
: (Integer | String | Array[Integer | String]) -> String
# File lib/pdf/reader/font.rb, line 298 def to_utf8_via_encoding(params) if encoding.kind_of?(String) raise UnsupportedFeatureError, "font encoding '#{encoding}' currently unsupported" end case params when Integer encoding.int_to_utf8_string(params) when String encoding.to_utf8(params) when Array params.collect { |param| to_utf8_via_encoding(param) }.join("") end end
: (String, String) -> Array
# File lib/pdf/reader/font.rb, line 314 def unpack_string_to_array_of_ints(unpack_me, unpack_arg) unpack_me.unpack(unpack_arg).map { |code_point| code_point = TypeCheck.cast_to_int!(code_point) } end