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`](lib/fontisan/type1/charstring_converter.rb) 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
162
163
# File 'lib/fontisan/type1/charstring_converter.rb', line 138

def expand_seac(seac_data)
  # seac format: adx ady bchar achar seac
  # adx, ady: accent offset
  # bchar: base character code
  # achar: accent character code
  # The accent is positioned at (adx, ady) relative to the base

  seac_data[:base]
  seac_data[:accent]
  seac_data[:adx]
  seac_data[:ady]

  # For now, we'll create a simple placeholder that indicates seac expansion
  # In a full implementation, we would:
  # 1. Parse the base glyph's CharString
  # 2. Parse the accent glyph's CharString
  # 3. Merge them with the appropriate offset
  # 4. Convert to CFF format

  # This is a simplified implementation that creates a composite reference
  # CFF doesn't have native seac, so we need to actually merge the outlines

  # For now, return endchar as placeholder
  # TODO: Implement full seac expansion by merging glyph outlines
  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



169
170
171
172
173
# File 'lib/fontisan/type1/charstring_converter.rb', line 169

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