Class: Fontisan::Type1Font

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

Overview

Adobe Type 1 Font handler

[‘Type1Font`](lib/fontisan/type1_font.rb) 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, format: nil, file_path: nil, mode: :full) ⇒ Type1Font

Initialize a new Type1Font instance

Parameters:

  • data (String)

    Font file data (binary or text)

  • format (Symbol) (defaults to: nil)

    Format type (: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)



70
71
72
73
74
75
76
77
# File 'lib/fontisan/type1_font.rb', line 70

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

  parse_font_data
end

Instance Attribute Details

#decrypted_dataString? (readonly)

Returns Decrypted font data.

Returns:

  • (String, nil)

    Decrypted font data



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

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



35
36
37
# File 'lib/fontisan/type1_font.rb', line 35

def file_path
  @file_path
end

#formatSymbol (readonly)

Returns Format type (:pfb or :pfa).

Returns:

  • (Symbol)

    Format type (:pfb or :pfa)



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

def format
  @format
end

#loading_modeSymbol (readonly)

Returns Loading mode (:metadata or :full).

Returns:

  • (Symbol)

    Loading mode (:metadata or :full)



41
42
43
# File 'lib/fontisan/type1_font.rb', line 41

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



92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/fontisan/type1_font.rb', line 92

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



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

def charstrings
  parse_dictionaries! unless @charstrings
  @charstrings
end

#clear_textString

Get clear text portion (before eexec)

Returns:

  • (String)

    Clear text font dictionary



108
109
110
# File 'lib/fontisan/type1_font.rb', line 108

def clear_text
  @clear_text ||= ""
end

#decrypt!String

Decrypt the font if not already decrypted

Returns:

  • (String)

    Decrypted font data



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/fontisan/type1_font.rb', line 136

def decrypt!
  return @decrypted_data if decrypted?

  if @encrypted_portion.nil? || @encrypted_portion.empty?
    @decrypted_data = @clear_text
  else
    encrypted_binary = if @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



122
123
124
# File 'lib/fontisan/type1_font.rb', line 122

def decrypted?
  !@decrypted_data.nil?
end

#encrypted?Boolean

Check if font is encrypted

Returns:

  • (Boolean)

    True if font has eexec encrypted portion



129
130
131
# File 'lib/fontisan/type1_font.rb', line 129

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

#encrypted_portionString

Get encrypted portion (as hex string for PFA)

Returns:

  • (String)

    Encrypted portion



115
116
117
# File 'lib/fontisan/type1_font.rb', line 115

def encrypted_portion
  @encrypted_portion ||= ""
end

#family_nameString?

Get family name from FontInfo

Returns:

  • (String, nil)

    Family name or nil if not found



196
197
198
199
200
# File 'lib/fontisan/type1_font.rb', line 196

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



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

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



178
179
180
181
182
# File 'lib/fontisan/type1_font.rb', line 178

def font_name
  return @font_dictionary&.font_name if @font_dictionary

  extract_dictionary_value("/FontName")
end

#full_nameString?

Get full name from FontInfo

Returns:

  • (String, nil)

    Full name or nil if not found



187
188
189
190
191
# File 'lib/fontisan/type1_font.rb', line 187

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



214
215
216
217
218
# File 'lib/fontisan/type1_font.rb', line 214

def glyph_names
  return [] unless @charstrings

  @charstrings.glyph_names
end

#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.



162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/fontisan/type1_font.rb', line 162

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



223
224
225
# File 'lib/fontisan/type1_font.rb', line 223

def parsed_dictionaries?
  !@font_dictionary.nil?
end

#private_dictPrivateDict?

Returns Private dictionary.

Returns:

  • (PrivateDict, nil)

    Private dictionary



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

def private_dict
  parse_dictionaries! unless @private_dict
  @private_dict
end

#versionString?

Get version from FontInfo

Returns:

  • (String, nil)

    Version or nil if not found



205
206
207
208
209
# File 'lib/fontisan/type1_font.rb', line 205

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

  extract_fontinfo_value("version")
end