Class: Fontisan::Type1Font

Inherits:
Object
  • Object
show all
Defined in:
lib/fontisan/type1_font.rb

Overview

Adobe Type 1 Font handler

Type1Font provides parsing and conversion capabilities for Adobe Type 1 fonts in PFB (Printer Font Binary) and PFA (Printer Font ASCII) formats.

Type 1 fonts were the standard for digital typography in the 1980s-1990s and consist of:

  • Font dictionary with metadata (FontInfo, FontName, Encoding, etc.)
  • Private dictionary with hinting and spacing information
  • CharStrings (glyph outline descriptions)
  • eexec encryption for protection

Examples:

Load a PFB file

font = Fontisan::Type1Font.from_file('font.pfb')
puts font.font_name
puts font.version

Load a PFA file

font = Fontisan::Type1Font.from_file('font.pfa')
puts font.full_name

Access decrypted font data

font = Fontisan::Type1Font.from_file('font.pfb')
puts font.decrypted_data

See Also:

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data, container_format: nil, file_path: nil, mode: :full) ⇒ Type1Font

Initialize a new Type1Font instance

Parameters:

  • data (String)

    Font file data (binary or text)

  • container_format (Symbol) (defaults to: nil)

    Container variant (:pfb or :pfa, auto-detected if nil)

  • file_path (String, nil) (defaults to: nil)

    Optional file path for reference

  • mode (Symbol) (defaults to: :full)

    Loading mode (:metadata or :full, default: :full)



99
100
101
102
103
104
105
106
# File 'lib/fontisan/type1_font.rb', line 99

def initialize(data, container_format: nil, file_path: nil, mode: :full)
  @file_path = file_path
  @container_format = container_format || detect_format(data)
  @data = data
  @loading_mode = mode

  parse_font_data
end

Instance Attribute Details

#container_formatSymbol (readonly)

Returns Container variant (:pfb or :pfa) — the on-disk encoding of a Type 1 font (binary vs ASCII). Distinct from #format, which is the high-level pipeline format.

Returns:

  • (Symbol)

    Container variant (:pfb or :pfa) — the on-disk encoding of a Type 1 font (binary vs ASCII). Distinct from #format, which is the high-level pipeline format.



38
39
40
# File 'lib/fontisan/type1_font.rb', line 38

def container_format
  @container_format
end

#decrypted_dataString? (readonly)

Returns Decrypted font data.

Returns:

  • (String, nil)

    Decrypted font data



72
73
74
# File 'lib/fontisan/type1_font.rb', line 72

def decrypted_data
  @decrypted_data
end

#file_pathString? (readonly)

Returns File path if loaded from file.

Returns:

  • (String, nil)

    File path if loaded from file



33
34
35
# File 'lib/fontisan/type1_font.rb', line 33

def file_path
  @file_path
end

#loading_modeSymbol (readonly)

Returns Loading mode (:metadata or :full).

Returns:

  • (Symbol)

    Loading mode (:metadata or :full)



69
70
71
# File 'lib/fontisan/type1_font.rb', line 69

def loading_mode
  @loading_mode
end

Class Method Details

.from_file(file_path, mode: :full) ⇒ Type1Font

Load Type 1 font from file

Examples:

Load PFB file

font = Fontisan::Type1Font.from_file('font.pfb')

Load PFA file

font = Fontisan::Type1Font.from_file('font.pfa')

Parameters:

  • file_path (String)

    Path to PFB or PFA file

  • mode (Symbol) (defaults to: :full)

    Loading mode (:metadata or :full, default: :full)

Returns:

Raises:

  • (ArgumentError)

    If file_path is nil

  • (Fontisan::Error)

    If file cannot be read or parsed



121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/fontisan/type1_font.rb', line 121

def self.from_file(file_path, mode: :full)
  raise ArgumentError, "File path cannot be nil" if file_path.nil?

  unless File.exist?(file_path)
    raise Fontisan::Error, "File not found: #{file_path}"
  end

  # Read file
  data = File.binread(file_path)

  new(data, file_path: file_path, mode: mode)
end

Instance Method Details

#charstringsCharStrings?

Returns CharStrings dictionary.

Returns:

  • (CharStrings, nil)

    CharStrings dictionary



87
88
89
90
# File 'lib/fontisan/type1_font.rb', line 87

def charstrings
  parse_dictionaries! unless @charstrings
  @charstrings
end

#clear_textString

Get clear text portion (before eexec)

Returns:

  • (String)

    Clear text font dictionary



137
138
139
# File 'lib/fontisan/type1_font.rb', line 137

def clear_text
  @clear_text ||= ""
end

#collection?Boolean

Whether this object represents a font collection rather than a single font. Each font class is the authority on this question.

Returns:

  • (Boolean)


50
# File 'lib/fontisan/type1_font.rb', line 50

def collection? = false

#decrypt!String

Decrypt the font if not already decrypted

Returns:

  • (String)

    Decrypted font data



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/fontisan/type1_font.rb', line 165

def decrypt!
  return @decrypted_data if decrypted?

  if @encrypted_portion.nil? || @encrypted_portion.empty?
    @decrypted_data = @clear_text
  else
    encrypted_binary = if @container_format == :pfa
                         # Convert hex string to binary
                         [@encrypted_portion.gsub(/\s/, "")].pack("H*")
                       else
                         @encrypted_portion
                       end

    @decrypted_data = @clear_text +
      Type1::Decryptor.eexec_decrypt(encrypted_binary)
  end

  @decrypted_data
end

#decrypted?Boolean

Check if font has been decrypted

Returns:

  • (Boolean)

    True if font data has been decrypted



151
152
153
# File 'lib/fontisan/type1_font.rb', line 151

def decrypted?
  !@decrypted_data.nil?
end

#encrypted?Boolean

Check if font is encrypted

Returns:

  • (Boolean)

    True if font has eexec encrypted portion



158
159
160
# File 'lib/fontisan/type1_font.rb', line 158

def encrypted?
  !@encrypted_portion.nil? && !@encrypted_portion.empty?
end

#encrypted_portionString

Get encrypted portion (as hex string for PFA)

Returns:

  • (String)

    Encrypted portion



144
145
146
# File 'lib/fontisan/type1_font.rb', line 144

def encrypted_portion
  @encrypted_portion ||= ""
end

#family_nameString?

Get family name from FontInfo

Returns:

  • (String, nil)

    Family name or nil if not found



225
226
227
228
229
# File 'lib/fontisan/type1_font.rb', line 225

def family_name
  return @font_dictionary&.font_info&.family_name if @font_dictionary

  extract_fontinfo_value("FamilyName")
end

#font_dictionaryFontDictionary?

Returns Font dictionary.

Returns:

  • (FontDictionary, nil)

    Font dictionary



75
76
77
78
# File 'lib/fontisan/type1_font.rb', line 75

def font_dictionary
  parse_dictionaries! unless @font_dictionary
  @font_dictionary
end

#font_nameString?

Get font name from font dictionary

Returns:

  • (String, nil)

    Font name or nil if not found



207
208
209
210
211
# File 'lib/fontisan/type1_font.rb', line 207

def font_name
  return @font_dictionary&.font_name if @font_dictionary

  extract_dictionary_value("/FontName")
end

#formatSymbol

High-level pipeline format identifier. Owned by the font class so the conversion pipeline can dispatch without case statements (OCP).

Returns:

  • (Symbol)

    :type1



44
# File 'lib/fontisan/type1_font.rb', line 44

def format = :type1

#full_nameString?

Get full name from FontInfo

Returns:

  • (String, nil)

    Full name or nil if not found



216
217
218
219
220
# File 'lib/fontisan/type1_font.rb', line 216

def full_name
  return @font_dictionary&.font_info&.full_name if @font_dictionary

  extract_fontinfo_value("FullName")
end

#glyph_namesArray<String>

Get list of glyph names

Returns:

  • (Array<String>)

    Glyph names



243
244
245
246
247
# File 'lib/fontisan/type1_font.rb', line 243

def glyph_names
  return [] unless @charstrings

  @charstrings.glyph_names
end

#outline_typeSymbol

Outline representation. Type 1 fonts use cubic PostScript CharStrings.

Returns:

  • (Symbol)

    :postscript



60
# File 'lib/fontisan/type1_font.rb', line 60

def outline_type = :postscript

#parse_dictionaries!void

This method returns an undefined value.

Parse font dictionaries from decrypted data

Parses the font dictionary, private dictionary, and CharStrings from the decrypted font data.



191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/fontisan/type1_font.rb', line 191

def parse_dictionaries!
  decrypt! unless decrypted?

  # Parse font dictionary
  @font_dictionary = Type1::FontDictionary.parse(@decrypted_data)

  # Parse private dictionary
  @private_dict = Type1::PrivateDict.parse(@decrypted_data)

  # Parse CharStrings
  @charstrings = Type1::CharStrings.parse(@decrypted_data, @private_dict)
end

#parsed_dictionaries?Boolean

Check if dictionaries have been parsed

Returns:

  • (Boolean)

    True if dictionaries have been parsed



252
253
254
# File 'lib/fontisan/type1_font.rb', line 252

def parsed_dictionaries?
  !@font_dictionary.nil?
end

#private_dictPrivateDict?

Returns Private dictionary.

Returns:

  • (PrivateDict, nil)

    Private dictionary



81
82
83
84
# File 'lib/fontisan/type1_font.rb', line 81

def private_dict
  parse_dictionaries! unless @private_dict
  @private_dict
end

#table_namesArray<String>

Type 1 fonts have no SFNT table directory; the table-oriented pipeline introspection does not apply.

Returns:

  • (Array<String>)

    empty



66
# File 'lib/fontisan/type1_font.rb', line 66

def table_names = []

#variation_typeSymbol

Type 1 fonts are not OpenType variable fonts.

Returns:

  • (Symbol)

    :static



55
# File 'lib/fontisan/type1_font.rb', line 55

def variation_type = :static

#versionString?

Get version from FontInfo

Returns:

  • (String, nil)

    Version or nil if not found



234
235
236
237
238
# File 'lib/fontisan/type1_font.rb', line 234

def version
  return @font_dictionary&.font_info&.version if @font_dictionary

  extract_fontinfo_value("version")
end