Class: Fontisan::Type1::CffToType1Converter
- Inherits:
-
Object
- Object
- Fontisan::Type1::CffToType1Converter
- Defined in:
- lib/fontisan/type1/cff_to_type1_converter.rb
Overview
Converter for CFF CharStrings to Type 1 CharStrings
CffToType1Converter converts
CFF (Compact Font Format) Type 2 CharStrings to Type 1 CharStrings.
CFF and Type 1 use similar stack-based languages but have different operator codes and some structural differences:
- Operator codes differ between formats
- Type 1 uses hsbw/sbw for width selection; CFF uses initial operand
- Hint operators need to be preserved with code translation
Constant Summary collapse
- CFF_TO_TYPE1 =
CFF to Type 1 operator mapping
Maps CFF operator codes to Type 1 operator codes. Most operators are the same, but some differ.
{ # Path construction operators hmoveto: 22, # CFF: 22, Type 1: 22 vmoveto: 4, # CFF: 4, Type 1: 4 rlineto: 5, # CFF: 5, Type 1: 5 hlineto: 6, # CFF: 6, Type 1: 6 vlineto: 7, # CFF: 7, Type 1: 7 rrcurveto: 8, # CFF: 8, Type 1: 8 hhcurveto: 27, # CFF: 27, Type 1: 27 hvcurveto: 31, # CFF: 31, Type 1: 31 vhcurveto: 30, # CFF: 30, Type 1: 30 rcurveline: 24, # CFF: 24, Type 1: 24 rlinecurve: 25, # CFF: 25, Type 1: 25 # Hint operators hstem: 1, # CFF: 1, Type 1: 1 vstem: 3, # CFF: 3, Type 1: 3 hstemhm: 18, # CFF: 18, Type 1: 18 vstemhm: 23, # CFF: 23, Type 1: 23 # Hint substitution (preserve for compatibility) hintmask: 19, # CFF: 19, Type 1: Not supported (skip) cntrmask: 20, # CFF: 20, Type 1: Not supported (skip) # End char endchar: 14, # CFF: 14, Type 1: 14 # Miscellaneous callsubr: 10, # CFF: 10, Type 1: 10 return: 11, # CFF: 11, Type 1: 11 rmoveto: 21, # CFF: 21, Type 1: 21 }.freeze
- ESCAPE_BYTE =
Escape code for two-byte operators
12
Instance Method Summary collapse
-
#convert(cff_charstring, private_dict: {}) ⇒ String
Convert CFF CharString to Type 1 CharString.
-
#convert_operations(operations, width = nil) ⇒ String
Convert parsed CFF operations to Type 1 CharString.
-
#extract_width(operations) ⇒ Integer?
Extract width from CFF operations.
-
#initialize(nominal_width: 0, default_width: 0) ⇒ CffToType1Converter
constructor
Initialize a new CffToType1Converter.
Constructor Details
#initialize(nominal_width: 0, default_width: 0) ⇒ CffToType1Converter
Initialize a new CffToType1Converter
67 68 69 70 |
# File 'lib/fontisan/type1/cff_to_type1_converter.rb', line 67 def initialize(nominal_width: 0, default_width: 0) @nominal_width = nominal_width @default_width = default_width end |
Instance Method Details
#convert(cff_charstring, private_dict: {}) ⇒ String
Convert CFF CharString to Type 1 CharString
Takes binary CFF CharString data and converts it to Type 1 format.
83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/fontisan/type1/cff_to_type1_converter.rb', line 83 def convert(cff_charstring, private_dict: {}) # Parse CFF CharString into operations parser = Tables::Cff::CharStringParser.new(cff_charstring, stem_count: private_dict[:stem_count].to_i) operations = parser.parse # Extract width from operations (CFF spec: odd stack before first move = width) width = extract_width(operations) # Convert operations to Type 1 format convert_operations(operations, width) end |
#convert_operations(operations, width = nil) ⇒ String
Convert parsed CFF operations to Type 1 CharString
136 137 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 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
# File 'lib/fontisan/type1/cff_to_type1_converter.rb', line 136 def convert_operations(operations, width = nil) result = String.new(encoding: Encoding::ASCII_8BIT) # Determine width: use provided width or default/nominal glyph_width = width || @default_width # Add hsbw (horizontal sidebearing and width) at start # This is the standard width operator for horizontal fonts result << encode_number(0) # left sidebearing (usually 0 for CFF) result << encode_number(glyph_width) result << ESCAPE_BYTE result << 34 # hsbw operator (two-byte: 12 34) x = 0 y = 0 first_move = true skip_first_operand = false # Check if width was extracted (odd stack before first move) if width && operations.any? # Count operands before first move to determine if width was in stack first_move_idx = operations.index do |op| %i[rmoveto hmoveto vmoveto rcurveline rrcurveline vvcurveto hhcurveto].include?(op[:name]) end if first_move_idx operand_count = operations[0...first_move_idx].sum do |op| op[:operands]&.length || 0 end skip_first_operand = operand_count.odd? end end operations.each do |op| case op[:name] when :hstem, :vstem, :hstemhm, :vstemhm # Hint operators - preserve op[:operands].each { |val| result << encode_number(val) } result << CFF_TO_TYPE1[op[:name]] when :rmoveto # rmoveto dx dy (or width dx dy if first move with odd stack) operands = op[:operands] if first_move && skip_first_operand && !operands.empty? # Skip first operand (it was the width) operands = operands[1..] skip_first_operand = false end if operands.length >= 2 dx = operands[0] dy = operands[1] x += dx y += dy result << encode_number(dx) result << encode_number(dy) result << 21 # rmoveto elsif operands.length == 1 # Only dy (hmoveto/vmoveto style) result << encode_number(operands.first) result << 4 # vmoveto (closest approximation) end first_move = false when :hmoveto # hmoveto dx (or width dx if first move) operands = op[:operands] if first_move && skip_first_operand && !operands.empty? operands = [operands[0]] if operands.length > 1 skip_first_operand = false end dx = operands.first x += dx if dx result << encode_number(dx) result << 22 # hmoveto first_move = false when :vmoveto # vmoveto dy dy = op[:operands].first y += dy if dy result << encode_number(dy) result << 4 # vmoveto first_move = false when :rlineto # rlineto dx dy dx, dy = op[:operands] x += dx y += dy result << encode_number(dx) result << encode_number(dy) result << 5 # rlineto first_move = false when :hlineto # hlineto dx dx = op[:operands].first x += dx result << encode_number(dx) result << 6 # hlineto first_move = false when :vlineto # vlineto dy dy = op[:operands].first y += dy result << encode_number(dy) result << 7 # vlineto first_move = false when :rrcurveto # rrcurveto dx1 dy1 dx2 dy2 dx3 dy3 dx1, dy1, dx2, dy2, dx3, dy3 = op[:operands] x += dx1 + dx2 + dx3 y += dy1 + dy2 + dy3 [dx1, dy1, dx2, dy2, dx3, dy3].each do |val| result << encode_number(val) end result << 8 # rrcurveto first_move = false when :hhcurveto, :hvcurveto, :vhcurveto # Flexible curve operators op[:operands].each { |val| result << encode_number(val) } result << CFF_TO_TYPE1[op[:name]] first_move = false when :rcurveline, :rlinecurve # Flexible curve operators op[:operands].each { |val| result << encode_number(val) } result << CFF_TO_TYPE1[op[:name]] first_move = false when :callsubr, :return, :endchar # Control operators result << CFF_TO_TYPE1[op[:name]] first_move = false when :hintmask, :cntrmask # Hint mask operators - Type 1 doesn't support these # Skip them first_move = false when :shortint # Short integer push - handled by operand encoding first_move = false else # Unknown operator - skip first_move = false end end # Add endchar result << 14 result end |
#extract_width(operations) ⇒ Integer?
Extract width from CFF operations
In CFF, if there's an odd number of arguments before the first move operator (rmoveto, hmoveto, vmoveto, rcurveline, rrcurveline, vvcurveto, hhcurveto), the first argument is the width.
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 129 |
# File 'lib/fontisan/type1/cff_to_type1_converter.rb', line 104 def extract_width(operations) return @default_width if operations.empty? # Find first move operator first_move_idx = operations.index do |op| %i[rmoveto hmoveto vmoveto rcurveline rrcurveline vvcurveto hhcurveto].include?(op[:name]) end return @default_width unless first_move_idx # Count operands before first move operand_count = operations[0...first_move_idx].sum do |op| op[:operands]&.length || 0 end # If odd, first operand of first move is width if operand_count.odd? first_move = operations[first_move_idx] if first_move[:operands] && !first_move[:operands].empty? return first_move[:operands].first end end @default_width end |