Class: Fontisan::Type1::CharStringConverter
- Inherits:
-
Object
- Object
- Fontisan::Type1::CharStringConverter
- Defined in:
- lib/fontisan/type1/charstring_converter.rb
Overview
Converter for Type 1 CharStrings to CFF CharStrings
CharStringConverter 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
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
-
#convert(type1_charstring) ⇒ String
Convert Type 1 CharString to CFF CharString.
-
#convert_commands(commands) ⇒ String
Convert parsed commands to CFF CharString.
-
#encode_cff_number(value) ⇒ String
Encode number in CFF format.
-
#encode_cff_operator(op_code) ⇒ String
Encode operator in CFF format.
-
#expand_seac(seac_data) ⇒ String
Expand seac composite glyph.
-
#initialize(charstrings = nil) ⇒ CharStringConverter
constructor
Initialize a new CharStringConverter.
-
#seac?(type1_charstring) ⇒ Boolean
Check if CharString contains seac operator.
Constructor Details
#initialize(charstrings = nil) ⇒ CharStringConverter
Initialize a new CharStringConverter
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
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 (parser.seac_components) end # Convert commands to CFF format convert_commands(commands) end |
#convert_commands(commands) ⇒ String
Convert parsed commands to CFF CharString
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 |
#encode_cff_number(value) ⇒ String
Encode number in CFF format
CFF uses a variable-length encoding for numbers:
- 32-246: 1 byte (value - 139)
- 247-250: 2 bytes (first byte indicates format)
- 251-254: 3 bytes (first byte indicates format)
- 255: 5 bytes (signed 16-bit integer)
- 28: 2 bytes (signed 16.16 fixed point, not used for CharStrings)
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 |
# File 'lib/fontisan/type1/charstring_converter.rb', line 186 def encode_cff_number(value) result = String.new(encoding: Encoding::ASCII_8BIT) if value >= -107 && value <= 107 # 1-byte number: value + 139 result << (value + 139).chr elsif value >= 108 && value <= 1131 # 2-byte positive number value -= 108 result << ((value >> 8) + 247).chr result << (value & 0xFF).chr elsif value >= -1131 && value <= -108 # 2-byte negative number value = -value - 108 result << ((value >> 8) + 251).chr result << (value & 0xFF).chr elsif value >= -32768 && value <= 32767 # 5-byte number (16-bit integer) result << 255.chr result << [(value >> 8) & 0xFF, value & 0xFF].pack("CC") result << [0, 0].pack("CC") # Pad to 5 bytes else raise Fontisan::Error, "Number out of range for CFF encoding: #{value}" end result end |
#encode_cff_operator(op_code) ⇒ String
Encode operator in CFF format
Most operators are single-byte. Some use escape byte (12) followed by a second byte.
222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/fontisan/type1/charstring_converter.rb', line 222 def encode_cff_operator(op_code) result = String.new(encoding: Encoding::ASCII_8BIT) if op_code > 31 && op_code != ESCAPE_BYTE # Two-byte operator (escape + code) result << ESCAPE_BYTE.chr result << (op_code - ESCAPE_BYTE).chr else # Single-byte operator result << op_code.chr 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.
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/fontisan/type1/charstring_converter.rb', line 138 def (seac_data) return encode_cff_operator(TYPE1_TO_CFF[:endchar]) unless @charstrings base_name = @charstrings.encoding[seac_data[:base]] accent_name = @charstrings.encoding[seac_data[:accent]] return encode_cff_operator(TYPE1_TO_CFF[:endchar]) unless base_name && accent_name base_cs = @charstrings[base_name] accent_cs = @charstrings[accent_name] return encode_cff_operator(TYPE1_TO_CFF[:endchar]) unless base_cs && accent_cs base_cff = convert(base_cs) accent_cff = convert(accent_cs) return base_cff unless accent_cff io = String.new(encoding: Encoding::ASCII_8BIT) io << base_cff.delete_suffix("\u000E") io << encode_cff_number(seac_data[:adx].to_i) if seac_data[:adx].to_i != 0 io << encode_cff_number(seac_data[:ady].to_i) if seac_data[:ady].to_i != 0 io << accent_cff.delete_suffix("\u000E") io << encode_cff_operator(TYPE1_TO_CFF[:endchar]) rescue StandardError encode_cff_operator(TYPE1_TO_CFF[:endchar]) end |
#seac?(type1_charstring) ⇒ Boolean
Check if CharString contains seac operator
167 168 169 170 171 |
# File 'lib/fontisan/type1/charstring_converter.rb', line 167 def seac?(type1_charstring) parser = Type1::CharStrings::CharStringParser.new parser.parse(type1_charstring) !parser.seac_components.nil? end |