Class: Fontisan::Tables::Cff::CharString

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

Overview

Type 2 CharString interpreter

CharStrings are stack-based programs that draw glyph outlines using a series of operators. They are stored in the CharStrings INDEX and contain path construction, hinting, and arithmetic operations.

Type 2 CharString Format:

  • Numbers are pushed onto an operand stack
  • Operators pop operands and execute commands
  • Path operators build the glyph outline
  • Hint operators define stem hints (can be ignored for rendering)
  • Subroutine operators allow code reuse
  • Arithmetic operators perform calculations on the stack

Path Construction Flow:

  1. Optional width value (first operand if odd number before first move)
  2. Initial moveto operator to start a path
  3. Line/curve operators to construct the path
  4. Optional closepath (implicit at endchar)
  5. endchar to finish the glyph

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

Examples:

Interpreting a CharString

charstring = CharString.new(data, private_dict, global_subrs,
                            local_subrs)
puts charstring.width  # => glyph width
puts charstring.path   # => array of path commands
bbox = charstring.bounding_box  # => [xMin, yMin, xMax, yMax]

Constant Summary collapse

OPERATORS =

Type 2 CharString operators

These operators define the behavior of the CharString interpreter

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data, private_dict, global_subrs, local_subrs = nil) ⇒ CharString

Initialize and interpret a CharString

Parameters:

  • data (String)

    Binary CharString data

  • private_dict (PrivateDict)

    Private DICT for width defaults

  • global_subrs (Index)

    Global subroutines INDEX

  • local_subrs (Index, nil) (defaults to: nil)

    Local subroutines INDEX



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/fontisan/tables/cff/charstring.rb', line 113

def initialize(data, private_dict, global_subrs, local_subrs = nil)
  @data = data
  @private_dict = private_dict
  @global_subrs = global_subrs
  @local_subrs = local_subrs

  @stack = []
  @path = []
  @x = 0.0
  @y = 0.0
  @width = nil
  @stems = 0
  @transient_array = []
  @subroutine_bias = calculate_bias(local_subrs)
  @global_subroutine_bias = calculate_bias(global_subrs)

  parse!
end

Instance Attribute Details

#pathArray<Hash> (readonly)

Returns Path commands array.

Returns:

  • (Array<Hash>)

    Path commands array



43
44
45
# File 'lib/fontisan/tables/cff/charstring.rb', line 43

def path
  @path
end

#widthInteger? (readonly)

Returns Glyph width (nil if using default width).

Returns:

  • (Integer, nil)

    Glyph width (nil if using default width)



40
41
42
# File 'lib/fontisan/tables/cff/charstring.rb', line 40

def width
  @width
end

#xFloat (readonly)

Returns Current X coordinate.

Returns:

  • (Float)

    Current X coordinate



46
47
48
# File 'lib/fontisan/tables/cff/charstring.rb', line 46

def x
  @x
end

#yFloat (readonly)

Returns Current Y coordinate.

Returns:

  • (Float)

    Current Y coordinate



49
50
51
# File 'lib/fontisan/tables/cff/charstring.rb', line 49

def y
  @y
end

Instance Method Details

#bounding_boxArray<Float>

Calculate the bounding box of the glyph

Returns:

  • (Array<Float>)

    [xMin, yMin, xMax, yMax] or nil if no path



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

def bounding_box
  return nil if @path.empty?

  x_coords = []
  y_coords = []

  @path.each do |cmd|
    case cmd[:type]
    when :move_to, :line_to
      x_coords << cmd[:x]
      y_coords << cmd[:y]
    when :curve_to
      x_coords << cmd[:x1] << cmd[:x2] << cmd[:x]
      y_coords << cmd[:y1] << cmd[:y2] << cmd[:y]
    end
  end

  return nil if x_coords.empty?

  [x_coords.min, y_coords.min, x_coords.max, y_coords.max]
end

#to_commandsArray<Array>

Convert path to drawing commands

Returns:

  • (Array<Array>)

    Array of command arrays: [:move_to, x, y], [:line_to, x, y], [:curve_to, x1, y1, x2, y2, x, y]



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

def to_commands
  @path.map do |cmd|
    case cmd[:type]
    when :move_to
      [:move_to, cmd[:x], cmd[:y]]
    when :line_to
      [:line_to, cmd[:x], cmd[:y]]
    when :curve_to
      [:curve_to, cmd[:x1], cmd[:y1], cmd[:x2], cmd[:y2],
       cmd[:x], cmd[:y]]
    end
  end
end