This commit is contained in:
@@ -0,0 +1,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
|
||||
Reference in New Issue
Block a user