Class: Fontisan::Type1::CharStringConverter

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

Overview

Converter for Type 1 CharStrings to CFF CharStrings

CharStringConverter converts Type 1 CharString bytecode to CFF (Compact Font Format) CharString bytecode.

Type 1 and CFF use similar stack-based languages but have different operator codes and some structural differences:

  • Operator codes differ between formats
  • Type 1 has seac operator for composites; CFF doesn't support it
  • Hint operators need to be preserved with code translation

Examples:

Convert a Type 1 CharString to CFF

converter = Fontisan::Type1::CharStringConverter.new
cff_charstring = converter.convert(type1_charstring)

See Also:

Constant Summary collapse

TYPE1_TO_CFF =

Type 1 to CFF operator mapping

Maps Type 1 operator codes to CFF operator codes. Some operators have the same code, others differ.

{
  # Path construction operators
  hmoveto: 22,      # Type 1: 22, CFF: 22
  vmoveto: 4,       # Type 1: 4, CFF: 4
  rlineto: 5,       # Type 1: 5, CFF: 5
  hlineto: 6,       # Type 1: 6, CFF: 6
  vlineto: 7,       # Type 1: 7, CFF: 7
  rrcurveto: 8,     # Type 1: 8, CFF: 8
  hhcurveto: 27,    # Type 1: 27, CFF: 27
  hvcurveto: 31,    # Type 1: 31, CFF: 31
  vhcurveto: 30,    # Type 1: 30, CFF: 30
  rcurveline: 24,   # Type 1: 24, CFF: 24
  rlinecurve: 25,   # Type 1: 25, CFF: 25

  # Hint operators
  hstem: 1,         # Type 1: 1, CFF: 1
  vstem: 3,         # Type 1: 3, CFF: 3
  hstemhm: 18,      # Type 1: 18, CFF: 18
  vstemhm: 23,      # Type 1: 23, CFF: 23

  # Hint substitution (not in Type 1, but we preserve for compatibility)
  hintmask: 19,     # Type 1: N/A, CFF: 19
  cntrmask: 20,     # Type 1: N/A, CFF: 20

  # End char
  endchar: 14, # Type 1: 14 (or 11 in some specs), CFF: 14

  # Miscellaneous
  callsubr: 10,     # Type 1: 10, CFF: 10
  return: 11,       # Type 1: 11, CFF: 11

  # Deprecated operators (preserve for compatibility)
  hstem3: 12,       # Type 1: 12 (escape 0), CFF: 12 (escape 0)
  vstem3: 13,       # Type 1: 13 (escape 1), CFF: 13 (escape 1)
  seac: 12,         # Type 1: 12 (escape 6), CFF: Not supported
}.freeze
ESCAPE_BYTE =

Escape code for two-byte operators

12
SEAC_ESCAPE_CODE =

seac operator escape code (second byte)

6

Instance Method Summary collapse

Constructor Details

#initialize(charstrings = nil) ⇒ CharStringConverter

Initialize a new CharStringConverter

Parameters:

  • charstrings (CharStrings, nil) (defaults to: nil)

    CharStrings dictionary for seac expansion



73
74
75
# File 'lib/fontisan/type1/charstring_converter.rb', line 73

def initialize(charstrings = nil)
  @charstrings = charstrings
end

Instance Method Details

#convert(type1_charstring) ⇒ String

Convert Type 1 CharString to CFF CharString

Examples:

Convert a CharString

converter = Fontisan::Type1::CharStringConverter.new
cff_bytes = converter.convert(type1_bytes)

Parameters:

  • type1_charstring (String)

    Type 1 CharString bytecode

Returns:

  • (String)

    CFF CharString bytecode



85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/fontisan/type1/charstring_converter.rb', line 85

def convert(type1_charstring)
  # Parse Type 1 CharString into commands
  parser = Type1::CharStrings::CharStringParser.new
  commands = parser.parse(type1_charstring)

  # Check for seac operator and expand if needed
  if parser.seac_components
    return expand_seac(parser.seac_components)
  end

  # Convert commands to CFF format
  convert_commands(commands)
end

#convert_commands(commands) ⇒ String

Convert parsed commands to CFF CharString

Parameters:

  • commands (Array<Array>)

    Parsed Type 1 commands

Returns:

  • (String)

    CFF CharString bytecode



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/fontisan/type1/charstring_converter.rb', line 103

def convert_commands(commands)
  result = String.new(encoding: Encoding::ASCII_8BIT)

  commands.each do |command|
    case command[0]
    when :number
      # Encode number in CFF format
      result << encode_cff_number(command[1])
    when :seac
      # seac should be expanded before this point
      raise Fontisan::Error,
            "seac operator not supported in CFF, must be expanded first"
    else
      # Convert operator
      op_code = TYPE1_TO_CFF[command[0]]
      if op_code.nil?
        # Unknown operator, skip or raise error
        next
      end

      result << encode_cff_operator(op_code)
    end
  end

  result
end

#expand_seac(seac_data) ⇒ String

Expand seac composite glyph

The seac operator in Type 1 creates composite glyphs (like À = A + `). CFF doesn't support seac, so we need to expand it into the base glyphs with appropriate positioning.

Parameters:

  • seac_data (Hash)

    seac component data

Returns:

  • (String)

    CFF CharString bytecode with expanded seac



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/fontisan/type1/charstring_converter.rb', line 138

def expand_seac(seac_data)
  return encode_cff_operator(TYPE1_TO_CFF[:endchar]) unless @charstrings

  base_name = @charstrings.encoding[seac_data[:base]]
  accent_name = @charstrings.encoding[seac_data[:accent]]
  return encode_cff_operator(TYPE1_TO_CFF[:endchar]) unless base_name && accent_name

  base_cs = @charstrings[base_name]
  accent_cs = @charstrings[accent_name]
  return encode_cff_operator(TYPE1_TO_CFF[:endchar]) unless base_cs && accent_cs

  base_cff = convert(base_cs)
  accent_cff = convert(accent_cs)
  return base_cff unless accent_cff

  io = String.new(encoding: Encoding::ASCII_8BIT)
  io << base_cff.delete_suffix("\u000E")
  io << encode_cff_number(seac_data[:adx].to_i) if seac_data[:adx].to_i != 0
  io << encode_cff_number(seac_data[:ady].to_i) if seac_data[:ady].to_i != 0
  io << accent_cff.delete_suffix("\u000E")
  io << encode_cff_operator(TYPE1_TO_CFF[:endchar])
rescue StandardError
  encode_cff_operator(TYPE1_TO_CFF[:endchar])
end

#seac?(type1_charstring) ⇒ Boolean

Check if CharString contains seac operator

Parameters:

  • type1_charstring (String)

    Type 1 CharString bytecode

Returns:

  • (Boolean)

    True if CharString contains seac



167
168
169
170
171
# File 'lib/fontisan/type1/charstring_converter.rb', line 167

def seac?(type1_charstring)
  parser = Type1::CharStrings::CharStringParser.new
  parser.parse(type1_charstring)
  !parser.seac_components.nil?
end