# 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` (Integer) - glyph ID in the original font. # * `:new` (Integer) - 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