1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 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 129 130 131 132 133 134 135 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
|
# frozen_string_literal: true
module TTFunk
class Table
class Cff < TTFunk::Table
# CFF top dict.
class TopDict < TTFunk::Table::Cff::Dict
# Default charstring type.
DEFAULT_CHARSTRING_TYPE = 2
# Length of placeholders for pointer operators.
POINTER_PLACEHOLDER_LENGTH = 5
# Length of placeholders for other operators.
PLACEHOLDER_LENGTH = 5
# Operators whose values are offsets that point to other parts
# of the file.
POINTER_OPERATORS = {
charset: 15,
encoding: 16,
charstrings_index: 17,
private: 18,
font_index: 1236,
font_dict_selector: 1237,
}.freeze
# All the operators we currently care about.
OPERATORS = {
**POINTER_OPERATORS,
ros: 1230,
charstring_type: 1206,
}.freeze
# Inverse operator mapping.
OPERATOR_CODES = OPERATORS.invert
# Encode dict.
#
# @return [TTFunk::EncodedString]
def encode(*)
EncodedString.new do |result|
each_with_index do |(operator, operands), _idx|
if operator == OPERATORS[:private]
result << encode_private
elsif pointer_operator?(operator)
result << Placeholder.new(
OPERATOR_CODES[operator],
length: POINTER_PLACEHOLDER_LENGTH,
)
else
operands.each { |operand| result << encode_operand(operand) }
end
result << encode_operator(operator)
end
end
end
# Finalize the table.
#
# @param new_cff_data [TTFunk::EncodedString]
# @param charmap [Hash{Integer => Hash}] keys are the charac codes,
# values are hashes:
# * `:old` (<tt>Integer</tt>) - glyph ID in the original font.
# * `:new` (<tt>Integer</tt>) - glyph ID in the subset font.
# @return [void]
def finalize(new_cff_data, charmap)
if charset
finalize_subtable(new_cff_data, :charset, charset.encode(charmap))
end
if encoding
finalize_subtable(new_cff_data, :encoding, encoding.encode(charmap))
end
if charstrings_index
finalize_subtable(new_cff_data, :charstrings_index, charstrings_index.encode(charmap))
end
if font_index
finalize_subtable(new_cff_data, :font_index, font_index.encode)
font_index.finalize(new_cff_data)
end
if font_dict_selector
finalize_subtable(new_cff_data, :font_dict_selector, font_dict_selector.encode(charmap))
end
if private_dict
encoded_private_dict = private_dict.encode
encoded_offset = encode_integer32(new_cff_data.length)
encoded_length = encode_integer32(encoded_private_dict.length)
new_cff_data.resolve_placeholder(:"private_length_#{@table_offset}", encoded_length)
new_cff_data.resolve_placeholder(:"private_offset_#{@table_offset}", encoded_offset)
private_dict.finalize(encoded_private_dict)
new_cff_data << encoded_private_dict
end
end
# Registry Ordering Supplement.
#
# @return [Array(Integer, Integer, Integer), nil]
def ros
self[OPERATORS[:ros]]
end
# Is Registry Ordering Supplement present in this dict?
#
# @return [Boolean]
def ros?
!ros.nil?
end
alias is_cid_font? ros?
# Charset specified in this dict.
#
# @return [TTFunk::Table::Cff::Charset, nil]
def charset
@charset ||=
if (charset_offset_or_id = self[OPERATORS[:charset]])
if charset_offset_or_id.empty?
Charset.new(self, file)
else
Charset.new(self, file, charset_offset_or_id.first)
end
end
end
# Encoding specified in this dict.
#
# @return [TTFunk::Table::Cff::Encoding, nil]
def encoding
# PostScript type 1 fonts, i.e. CID fonts, i.e. some fonts that use
# the CFF table, don't specify an encoding, so this can be nil
@encoding ||=
if (encoding_offset_or_id = self[OPERATORS[:encoding]])
Encoding.new(self, file, encoding_offset_or_id.first)
end
end
# Charstrings index specified in this dict.
#
# > OpenType fonts with TrueType outlines use a glyph index to specify
# and access glyphs within a font; e.g., to index within the `loca`
# table and thereby access glyph data in the `glyf` table. This
# concept is retained in OpenType CFF fonts, except that glyph data is
# accessed through the CharStrings INDEX of the CFF table.
#
# > --- [CFF — Compact Font Format Table](https://www.microsoft.com/typography/otspec/cff.htm)
#
# @return [TTFunk::Table::Cff::CharstringsIndex, nil]
def charstrings_index
@charstrings_index ||=
if (charstrings_offset = self[OPERATORS[:charstrings_index]])
CharstringsIndex.new(self, file, cff_offset + charstrings_offset.first)
end
end
# Charstring type specified in this dict.
#
# @return [Integer]
def charstring_type
@charstring_type =
self[OPERATORS[:charstring_type]] || DEFAULT_CHARSTRING_TYPE
end
# Font index specified in this dict.
#
# @return [TTFunk::Table::Cff::FontIndex, nil]
def font_index
@font_index ||=
if (font_index_offset = self[OPERATORS[:font_index]])
FontIndex.new(self, file, cff_offset + font_index_offset.first)
end
end
# Font dict selector specified in this dict.
#
# @return [TTFunk::Table::Cff::FdSelector, nil]
def font_dict_selector
@font_dict_selector ||=
if (fd_select_offset = self[OPERATORS[:font_dict_selector]])
FdSelector.new(self, file, cff_offset + fd_select_offset.first)
end
end
# Private dict specified in this dict.
#
# @return [TTFunk::Table::Cff::PrivateDict, nil]
def private_dict
@private_dict ||=
if (info = self[OPERATORS[:private]])
private_dict_length, private_dict_offset = info
PrivateDict.new(file, cff_offset + private_dict_offset, private_dict_length)
end
end
# CFF table in this file.
#
# @return [TTFunk::Table::Cff]
def cff
file.cff
end
# Ofsset of CFF table in the file.
#
# @return [Integer]
def cff_offset
cff.offset
end
private
def encode_private
EncodedString.new do |result|
result << Placeholder.new(
:"private_length_#{@table_offset}",
length: PLACEHOLDER_LENGTH,
)
result << Placeholder.new(
:"private_offset_#{@table_offset}",
length: PLACEHOLDER_LENGTH,
)
end
end
def finalize_subtable(new_cff_data, name, table_data)
encoded = encode_integer32(new_cff_data.length)
new_cff_data.resolve_placeholder(name, encoded)
new_cff_data << table_data
end
def pointer_operator?(operator)
POINTER_OPERATORS.include?(OPERATOR_CODES[operator])
end
def encode_charstring_type(charstring_type)
if charstring_type == DEFAULT_CHARSTRING_TYPE
''
else
encode_operand(charstring_type)
end
end
end
end
end
end
|