Class: Fontisan::Tables::Cff::Cff2CharStringBuilder
- Inherits:
-
CharStringBuilder
- Object
- CharStringBuilder
- Fontisan::Tables::Cff::Cff2CharStringBuilder
- Defined in:
- lib/fontisan/tables/cff/cff2_charstring_builder.rb
Overview
CFF2 CharStringBuilder — extends the CFF1 charstring with variable-font operators (vsindex, blend).
In CFF2, the hmoveto operator (22) is repurposed as vsindex (selects a VariationStore item), and a new blend operator (23) applies variation deltas to the current operands.
The blend protocol for a single coordinate:
push base_value, push delta_r0, ..., push delta_r(n-1), push 1, blend
After blend, the stack has one blended value.
For n coordinates batched together:
push base_0 + deltas_0, push base_1 + deltas_1, ..., push n, blend
After blend, the stack has n blended values.
Defined Under Namespace
Classes: MasterState
Constant Summary collapse
- OPERATORS_CFF2 =
CFF2 replaces hmoveto(22) with vsindex(22). The :hmoveto key is omitted entirely so attempts to emit it raise naturally.
OPERATORS.except(:hmoveto).merge( vsindex: 22, blend: 23, )
Constants inherited from CharStringBuilder
Fontisan::Tables::Cff::CharStringBuilder::OPERATORS, Fontisan::Tables::Cff::CharStringBuilder::TWO_BYTE_OPERATORS
Class Method Summary collapse
-
.build_variable(outline, master_outlines:, num_regions:, width: nil) ⇒ String
Build a complete variable CFF2 charstring from a default outline plus variation master outlines.
Instance Method Summary collapse
-
#build_variable_outline(outline, master_outlines, num_regions, width) ⇒ String
Build a variable charstring from a default outline + masters.
- #encode_variable_command(cmd, master_cmds) ⇒ Object
-
#encode_variable_curveto(cmd, master_cmds) ⇒ Object
---------- variable curveto ----------.
-
#encode_variable_lineto(cmd, master_cmds) ⇒ Object
---------- variable lineto ----------.
-
#encode_variable_moveto(cmd, master_cmds) ⇒ Object
---------- variable moveto ----------.
-
#encode_variable_outline(outline, master_outlines, num_regions) ⇒ Object
Process an outline with blend operators for each varying coordinate.
- #write_operator(operator) ⇒ Object
-
#write_variable_number(base_value, deltas) ⇒ Object
Emit a variable coordinate: base value + per-region deltas + blend.
- #write_vsindex(index) ⇒ Object
Methods inherited from CharStringBuilder
#build, #build_empty, build_from_operations, #build_from_operations
Class Method Details
.build_variable(outline, master_outlines:, num_regions:, width: nil) ⇒ String
Build a complete variable CFF2 charstring from a default outline plus variation master outlines.
43 44 45 |
# File 'lib/fontisan/tables/cff/cff2_charstring_builder.rb', line 43 def self.build_variable(outline, master_outlines:, num_regions:, width: nil) new.build_variable_outline(outline, master_outlines, num_regions, width) end |
Instance Method Details
#build_variable_outline(outline, master_outlines, num_regions, width) ⇒ String
Build a variable charstring from a default outline + masters. Creates the output buffer internally (same pattern as the parent's build method).
53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/fontisan/tables/cff/cff2_charstring_builder.rb', line 53 def build_variable_outline(outline, master_outlines, num_regions, width) @output = StringIO.new("".b) @first_move = true @current_x = 0.0 @current_y = 0.0 encode_width(width) if width encode_variable_outline(outline, master_outlines, num_regions) write_operator(:endchar) @output.string end |
#encode_variable_command(cmd, master_cmds) ⇒ Object
82 83 84 85 86 87 88 |
# File 'lib/fontisan/tables/cff/cff2_charstring_builder.rb', line 82 def encode_variable_command(cmd, master_cmds) case cmd[:type] when :move_to then encode_variable_moveto(cmd, master_cmds) when :line_to then encode_variable_lineto(cmd, master_cmds) when :curve_to then encode_variable_curveto(cmd, master_cmds) end end |
#encode_variable_curveto(cmd, master_cmds) ⇒ Object
---------- variable curveto ----------
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 |
# File 'lib/fontisan/tables/cff/cff2_charstring_builder.rb', line 116 def encode_variable_curveto(cmd, master_cmds) # Compute relative values for the default master dx1 = (cmd[:x1] - @current_x).round dy1 = (cmd[:y1] - @current_y).round dx2 = (cmd[:x2] - @current_x).round dy2 = (cmd[:y2] - @current_y).round dx = (cmd[:x] - @current_x).round dy = (cmd[:y] - @current_y).round base_values = [dx1, dy1, dx2, dy2, dx, dy] base_keys = %i[x1 y1 x2 y2 x y] # Compute deltas for each coordinate against each master all_deltas = base_keys.each_with_index.map do |key, vi| master_cmds.each_with_index.map do |mc, i| ms = @master_states[i] master_relative = if key.to_s.start_with?("x") (mc[key] - ms.current_x).round else (mc[key] - ms.current_y).round end master_relative - base_values[vi] end end base_values.each_with_index do |val, vi| write_variable_number(val, all_deltas[vi]) end write_operator(:rrcurveto) advance_state(cmd, master_cmds) end |
#encode_variable_lineto(cmd, master_cmds) ⇒ Object
---------- variable lineto ----------
104 105 106 107 108 109 110 111 112 |
# File 'lib/fontisan/tables/cff/cff2_charstring_builder.rb', line 104 def encode_variable_lineto(cmd, master_cmds) dx, dy, dx_deltas, dy_deltas = compute_variable_deltas(cmd, master_cmds) write_variable_number(dx, dx_deltas) write_variable_number(dy, dy_deltas) write_operator(:rlineto) advance_state(cmd, master_cmds) end |
#encode_variable_moveto(cmd, master_cmds) ⇒ Object
---------- variable moveto ----------
92 93 94 95 96 97 98 99 100 |
# File 'lib/fontisan/tables/cff/cff2_charstring_builder.rb', line 92 def encode_variable_moveto(cmd, master_cmds) dx, dy, dx_deltas, dy_deltas = compute_variable_deltas(cmd, master_cmds) write_variable_number(dx, dx_deltas) write_variable_number(dy, dy_deltas) write_operator(:rmoveto) advance_state(cmd, master_cmds) end |
#encode_variable_outline(outline, master_outlines, num_regions) ⇒ Object
Process an outline with blend operators for each varying coordinate.
67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/fontisan/tables/cff/cff2_charstring_builder.rb', line 67 def encode_variable_outline(outline, master_outlines, num_regions) @master_states = master_outlines.map { MasterState.new(current_x: 0, current_y: 0) } @num_regions = num_regions @has_blend = false outline.commands.each_with_index do |cmd, i| master_cmds = master_outlines.map { |mo| mo.commands[i] } encode_variable_command(cmd, master_cmds) end # Emit vsindex(0) at the start if any blend operators were used. # In a full implementation, vsindex selects the ItemVariationData # subtable; for single-subtable fonts, vsindex 0 is the default. end |
#write_operator(operator) ⇒ Object
171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/fontisan/tables/cff/cff2_charstring_builder.rb', line 171 def write_operator(operator) if OPERATORS_CFF2.key?(operator) @output.putc(OPERATORS_CFF2[operator]) elsif TWO_BYTE_OPERATORS.key?(operator) bytes = TWO_BYTE_OPERATORS[operator] @output.putc(bytes[0]) @output.putc(bytes[1]) else raise ArgumentError, "Unknown CFF2 operator: #{operator}" end end |
#write_variable_number(base_value, deltas) ⇒ Object
Emit a variable coordinate: base value + per-region deltas + blend. If all deltas are zero, emits just the base value (no blend needed).
155 156 157 158 159 160 161 162 163 164 |
# File 'lib/fontisan/tables/cff/cff2_charstring_builder.rb', line 155 def write_variable_number(base_value, deltas) return write_number(base_value) if deltas.nil? || deltas.empty? return write_number(base_value) if deltas.all?(&:zero?) @has_blend = true write_number(base_value) deltas.each { |d| write_number(d) } write_number(1) # n = 1 value to blend write_operator(:blend) end |
#write_vsindex(index) ⇒ Object
166 167 168 169 |
# File 'lib/fontisan/tables/cff/cff2_charstring_builder.rb', line 166 def write_vsindex(index) write_number(index) write_operator(:vsindex) end |