Class: Fontisan::Tables::Cff::CharStringParser

Inherits:
Object
  • Object
show all
Defined in:
lib/fontisan/tables/cff/charstring_parser.rb

Overview

CharString parser that converts binary CharString data to operation list

Unlike CharString which interprets and executes CharStrings for rendering, CharStringParser parses CharStrings into a list of operations that can be modified and rebuilt. This enables CharString manipulation for hint injection, subroutine optimization, and other transformations.

Operation Format:

{
  type: :operator,
  name: :rmoveto,
  operands: [100, 200]
}

Reference: Adobe Type 2 CharString Format https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf

Examples:

Parse a CharString

parser = CharStringParser.new(charstring_data)
operations = parser.parse
operations.each { |op| puts "#{op[:name]} #{op[:operands]}" }

Constant Summary collapse

OPERATORS =

Type 2 CharString operators (from CharString class)

{
  # Path construction operators
  1 => :hstem,
  3 => :vstem,
  4 => :vmoveto,
  5 => :rlineto,
  6 => :hlineto,
  7 => :vlineto,
  8 => :rrcurveto,
  10 => :callsubr,
  11 => :return,
  14 => :endchar,
  18 => :hstemhm,
  19 => :hintmask,
  20 => :cntrmask,
  21 => :rmoveto,
  22 => :hmoveto,
  23 => :vstemhm,
  24 => :rcurveline,
  25 => :rlinecurve,
  26 => :vvcurveto,
  27 => :hhcurveto,
  28 => :shortint,
  29 => :callgsubr,
  30 => :vhcurveto,
  31 => :hvcurveto,
  # 12 prefix for two-byte operators
  [12, 3] => :and,
  [12, 4] => :or,
  [12, 5] => :not,
  [12, 9] => :abs,
  [12, 10] => :add,
  [12, 11] => :sub,
  [12, 12] => :div,
  [12, 14] => :neg,
  [12, 15] => :eq,
  [12, 18] => :drop,
  [12, 20] => :put,
  [12, 21] => :get,
  [12, 22] => :ifelse,
  [12, 23] => :random,
  [12, 24] => :mul,
  [12, 26] => :sqrt,
  [12, 27] => :dup,
  [12, 28] => :exch,
  [12, 29] => :index,
  [12, 30] => :roll,
  [12, 34] => :hflex,
  [12, 35] => :flex,
  [12, 36] => :hflex1,
  [12, 37] => :flex1,
}.freeze
HINTMASK_OPERATORS =

Operators that require hint mask bytes

%i[hintmask cntrmask].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data, stem_count: 0) ⇒ CharStringParser

Initialize parser with CharString data

Parameters:

  • data (String)

    Binary CharString data

  • stem_count (Integer) (defaults to: 0)

    Number of stem hints (for hintmask)



100
101
102
103
104
# File 'lib/fontisan/tables/cff/charstring_parser.rb', line 100

def initialize(data, stem_count: 0)
  @data = data
  @stem_count = stem_count
  @operations = []
end

Instance Attribute Details

#dataString (readonly)

Returns Binary CharString data.

Returns:

  • (String)

    Binary CharString data



91
92
93
# File 'lib/fontisan/tables/cff/charstring_parser.rb', line 91

def data
  @data
end

#operationsArray<Hash> (readonly)

Returns Parsed operations.

Returns:

  • (Array<Hash>)

    Parsed operations



94
95
96
# File 'lib/fontisan/tables/cff/charstring_parser.rb', line 94

def operations
  @operations
end

Instance Method Details

#parseArray<Hash>

Parse CharString to operation list

Returns:

  • (Array<Hash>)

    Array of operation hashes



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/fontisan/tables/cff/charstring_parser.rb', line 109

def parse
  @operations = []
  io = StringIO.new(@data)
  operand_stack = []

  until io.eof?
    byte = io.getbyte

    if operator_byte?(byte)
      # Operator byte - read operator and create operation
      operator = read_operator(io, byte)

      # Read hint mask data if needed
      hint_data = nil
      if HINTMASK_OPERATORS.include?(operator)
        hint_bytes = (@stem_count + 7) / 8
        hint_data = io.read(hint_bytes) if hint_bytes.positive?
      end

      # Create operation
      @operations << {
        type: :operator,
        name: operator,
        operands: operand_stack.dup,
        hint_data: hint_data,
      }

      # Clear operand stack
      operand_stack.clear
    else
      # Operand byte - read number and push to stack
      io.pos -= 1
      number = read_number(io)
      operand_stack << number
    end
  end

  @operations
rescue StandardError => e
  raise CorruptedTableError,
        "Failed to parse CharString: #{e.message}"
end

#stem_count=(count) ⇒ Object

Update stem count (needed for hintmask operations)

Parameters:

  • count (Integer)

    Number of stem hints



155
156
157
# File 'lib/fontisan/tables/cff/charstring_parser.rb', line 155

def stem_count=(count)
  @stem_count = count
end