This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
# CFF Charset
|
||||
class Charset < TTFunk::SubTable
|
||||
include Enumerable
|
||||
|
||||
# First glyph string. This is an implicit glyph present in all charsets.
|
||||
FIRST_GLYPH_STRING = '.notdef'
|
||||
|
||||
# Format 0.
|
||||
ARRAY_FORMAT = 0
|
||||
|
||||
# Format 1.
|
||||
RANGE_FORMAT_8 = 1
|
||||
|
||||
# Format 2.
|
||||
RANGE_FORMAT_16 = 2
|
||||
|
||||
# Predefined ISOAdobe charset ID.
|
||||
ISO_ADOBE_CHARSET_ID = 0
|
||||
|
||||
# Predefined Expert charset ID.
|
||||
EXPERT_CHARSET_ID = 1
|
||||
|
||||
# Predefined Expert Subset charset ID.
|
||||
EXPERT_SUBSET_CHARSET_ID = 2
|
||||
|
||||
# Default charset ID.
|
||||
DEFAULT_CHARSET_ID = ISO_ADOBE_CHARSET_ID
|
||||
|
||||
class << self
|
||||
# Standard strings defined in the spec that do not need to be defined
|
||||
# in the CFF.
|
||||
#
|
||||
# @return [TTFunk::OneBasedArray<String>]
|
||||
def standard_strings
|
||||
Charsets::STANDARD_STRINGS
|
||||
end
|
||||
|
||||
# Strings for charset ID.
|
||||
#
|
||||
# @param charset_id [Integer]
|
||||
# @return [TTFunk::OneBasedArray<String>]
|
||||
def strings_for_charset_id(charset_id)
|
||||
case charset_id
|
||||
when ISO_ADOBE_CHARSET_ID
|
||||
Charsets::ISO_ADOBE
|
||||
when EXPERT_CHARSET_ID
|
||||
Charsets::EXPERT
|
||||
when EXPERT_SUBSET_CHARSET_ID
|
||||
Charsets::EXPERT_SUBSET
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Encoded entries.
|
||||
# @return [TTFunk::OneBasedArray<Integer>, Array<Range<Integer>>]
|
||||
attr_reader :entries
|
||||
|
||||
# Length of encoded charset subtable.
|
||||
# @return [Integer]
|
||||
attr_reader :length
|
||||
|
||||
# Top dict.
|
||||
# @return [TTFunk::Table::Cff::TopDict]
|
||||
attr_reader :top_dict
|
||||
|
||||
# Encodign format.
|
||||
# @return [Integer]
|
||||
attr_reader :format
|
||||
|
||||
# Number of encoded items.
|
||||
# @return [Integer]
|
||||
attr_reader :items_count
|
||||
|
||||
# Offset or charset ID.
|
||||
# @return [Integer]
|
||||
attr_reader :offset_or_id
|
||||
|
||||
# @overload initialize(top_dict, file, offset = nil, length = nil)
|
||||
# @param top_dict [TTFunk::Table:Cff::TopDict]
|
||||
# @param file [TTFunk::File]
|
||||
# @param offset [Integer]
|
||||
# @param length [Integer]
|
||||
# @overload initialize(top_dict, file, charset_id)
|
||||
# @param top_dict [TTFunk::Table:Cff::TopDict]
|
||||
# @param file [TTFunk::File]
|
||||
# @param charset_id [Integer] 0, 1, or 2
|
||||
def initialize(top_dict, file, offset_or_id = nil, length = nil)
|
||||
@top_dict = top_dict
|
||||
@offset_or_id = offset_or_id || DEFAULT_CHARSET_ID
|
||||
|
||||
if offset
|
||||
super(file, offset, length)
|
||||
else
|
||||
@items_count = self.class.strings_for_charset_id(offset_or_id).size
|
||||
end
|
||||
end
|
||||
|
||||
# Iterate over character names.
|
||||
#
|
||||
# @overload each()
|
||||
# @yieldparam name [String]
|
||||
# @return [void]
|
||||
# @overload each()
|
||||
# @return [Enumerator]
|
||||
def each
|
||||
return to_enum(__method__) unless block_given?
|
||||
|
||||
# +1 adjusts for the implicit .notdef glyph
|
||||
(items_count + 1).times { |i| yield(self[i]) }
|
||||
end
|
||||
|
||||
# Get character name for glyph index.
|
||||
#
|
||||
# @param glyph_id [Integer]
|
||||
# @return [String, nil]
|
||||
def [](glyph_id)
|
||||
return FIRST_GLYPH_STRING if glyph_id.zero?
|
||||
|
||||
find_string(sid_for(glyph_id))
|
||||
end
|
||||
|
||||
# Charset offset in the file.
|
||||
#
|
||||
# @return [Integer, nil]
|
||||
def offset
|
||||
# Numbers from 0..2 mean charset IDs instead of offsets. IDs are
|
||||
# basically pre-defined sets of characters.
|
||||
#
|
||||
# In the case of an offset, add the CFF table's offset since the
|
||||
# charset offset is relative to the start of the CFF table. Otherwise
|
||||
# return nil (no offset).
|
||||
if offset_or_id > 2
|
||||
offset_or_id + top_dict.cff_offset
|
||||
end
|
||||
end
|
||||
|
||||
# Encode charset.
|
||||
#
|
||||
# @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 [String]
|
||||
def encode(charmap)
|
||||
# no offset means no charset was specified (i.e. we're supposed to
|
||||
# use a predefined charset) so there's nothing to encode
|
||||
return '' unless offset
|
||||
|
||||
sids =
|
||||
charmap
|
||||
.values
|
||||
.reject { |mapping| mapping[:new].zero? }
|
||||
.sort_by { |mapping| mapping[:new] }
|
||||
.map { |mapping| sid_for(mapping[:old]) }
|
||||
|
||||
ranges = TTFunk::BinUtils.rangify(sids)
|
||||
range_max = ranges.map(&:last).max
|
||||
|
||||
range_bytes =
|
||||
if range_max.positive?
|
||||
(Math.log2(range_max) / 8).floor + 1
|
||||
else
|
||||
# for cases when there are no sequences at all
|
||||
Float::INFINITY
|
||||
end
|
||||
|
||||
# calculate whether storing the charset as a series of ranges is
|
||||
# more efficient (i.e. takes up less space) vs storing it as an
|
||||
# array of SID values
|
||||
total_range_size = (2 * ranges.size) + (range_bytes * ranges.size)
|
||||
total_array_size = sids.size * element_width(:array_format)
|
||||
|
||||
if total_array_size <= total_range_size
|
||||
([format_int(:array_format)] + sids).pack('Cn*')
|
||||
else
|
||||
fmt = range_bytes == 1 ? :range_format8 : :range_format16
|
||||
element_fmt = element_format(fmt)
|
||||
result = [format_int(fmt)].pack('C')
|
||||
ranges.each { |range| result << range.pack(element_fmt) }
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sid_for(glyph_id)
|
||||
return 0 if glyph_id.zero?
|
||||
|
||||
# rather than validating the glyph as part of one of the predefined
|
||||
# charsets, just pass it through
|
||||
return glyph_id unless offset
|
||||
|
||||
case format_sym
|
||||
when :array_format
|
||||
entries[glyph_id]
|
||||
|
||||
when :range_format8, :range_format16
|
||||
entries.reduce(glyph_id) do |remaining, range|
|
||||
if range.size >= remaining
|
||||
break (range.first + remaining) - 1
|
||||
end
|
||||
|
||||
remaining - range.size
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def find_string(sid)
|
||||
if offset
|
||||
return self.class.standard_strings[sid] if sid <= 390
|
||||
|
||||
idx = sid - 390
|
||||
|
||||
if idx < file.cff.string_index.items_count
|
||||
file.cff.string_index[idx]
|
||||
end
|
||||
else
|
||||
self.class.strings_for_charset_id(offset_or_id)[sid]
|
||||
end
|
||||
end
|
||||
|
||||
def parse!
|
||||
return unless offset
|
||||
|
||||
@format = read(1, 'C').first
|
||||
|
||||
case format_sym
|
||||
when :array_format
|
||||
@items_count = top_dict.charstrings_index.items_count - 1
|
||||
@length = @items_count * element_width
|
||||
@entries = OneBasedArray.new(read(length, 'n*'))
|
||||
|
||||
when :range_format8, :range_format16
|
||||
# The number of ranges is not explicitly specified in the font.
|
||||
# Instead, software utilizing this data simply processes ranges
|
||||
# until all glyphs in the font are covered.
|
||||
@items_count = 0
|
||||
@entries = []
|
||||
@length = 0
|
||||
|
||||
until @items_count >= top_dict.charstrings_index.items_count - 1
|
||||
@length += 1 + element_width
|
||||
sid, num_left = read(element_width, element_format)
|
||||
@entries << (sid..(sid + num_left))
|
||||
@items_count += num_left + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def element_width(fmt = format_sym)
|
||||
{
|
||||
array_format: 2, # SID
|
||||
range_format8: 3, # SID + Card8
|
||||
range_format16: 4, # SID + Card16
|
||||
}[fmt]
|
||||
end
|
||||
|
||||
def element_format(fmt = format_sym)
|
||||
{
|
||||
array_format: 'n',
|
||||
range_format8: 'nC',
|
||||
range_format16: 'nn',
|
||||
}[fmt]
|
||||
end
|
||||
|
||||
def format_sym
|
||||
case @format
|
||||
when ARRAY_FORMAT then :array_format
|
||||
when RANGE_FORMAT_8 then :range_format8
|
||||
when RANGE_FORMAT_16 then :range_format16
|
||||
else
|
||||
raise Error, "unsupported charset format '#{fmt}'"
|
||||
end
|
||||
end
|
||||
|
||||
def format_int(sym = format_sym)
|
||||
{
|
||||
array_format: ARRAY_FORMAT,
|
||||
range_format8: RANGE_FORMAT_8,
|
||||
range_format16: RANGE_FORMAT_16,
|
||||
}[sym]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
# Predefined CFF data
|
||||
module Charsets
|
||||
autoload :EXPERT, 'ttfunk/table/cff/charsets/expert'
|
||||
autoload :EXPERT_SUBSET, 'ttfunk/table/cff/charsets/expert_subset'
|
||||
autoload :ISO_ADOBE, 'ttfunk/table/cff/charsets/iso_adobe'
|
||||
autoload :STANDARD_STRINGS, 'ttfunk/table/cff/charsets/standard_strings'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,190 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
module Charsets
|
||||
# Predefinde CFF Expert charset
|
||||
EXPERT = OneBasedArray.new(
|
||||
[
|
||||
'space',
|
||||
*[nil] * 11,
|
||||
'comma',
|
||||
'hyphen',
|
||||
'period',
|
||||
*[nil] * 11,
|
||||
'colon',
|
||||
'semicolon',
|
||||
*[nil] * 70,
|
||||
'fraction',
|
||||
*[nil] * 9,
|
||||
'fi',
|
||||
'fl',
|
||||
*[nil] * 39,
|
||||
'onesuperior',
|
||||
*[nil] * 4,
|
||||
'onehalf',
|
||||
nil,
|
||||
nil,
|
||||
'onequarter',
|
||||
*[nil] * 4,
|
||||
'threequarters',
|
||||
'twosuperior',
|
||||
*[nil] * 4,
|
||||
'threesuperior',
|
||||
*[nil] * 59,
|
||||
'exclamsmall',
|
||||
'Hungarumlautsmall',
|
||||
'dollaroldstyle',
|
||||
'dollarsuperior',
|
||||
'ampersandsmall',
|
||||
'Acutesmall',
|
||||
'parenleftsuperior',
|
||||
'parenrightsuperior',
|
||||
'twodotenleader',
|
||||
'onedotenleader',
|
||||
'zerooldstyle',
|
||||
'oneoldstyle',
|
||||
'twooldstyle',
|
||||
'threeoldstyle',
|
||||
'fouroldstyle',
|
||||
'fiveoldstyle',
|
||||
'sixoldstyle',
|
||||
'sevenoldstyle',
|
||||
'eightoldstyle',
|
||||
'nineoldstyle',
|
||||
'commasuperior',
|
||||
'threequartersemdash',
|
||||
'periodsuperior',
|
||||
'questionsmall',
|
||||
'asuperior',
|
||||
'bsuperior',
|
||||
'centsuperior',
|
||||
'dsuperior',
|
||||
'esuperior',
|
||||
'isuperior',
|
||||
'lsuperior',
|
||||
'msuperior',
|
||||
'nsuperior',
|
||||
'osuperior',
|
||||
'rsuperior',
|
||||
'ssuperior',
|
||||
'tsuperior',
|
||||
'ff',
|
||||
'ffi',
|
||||
'ffl',
|
||||
'parenleftinferior',
|
||||
'parenrightinferior',
|
||||
'Circumflexsmall',
|
||||
'hyphensuperior',
|
||||
'Gravesmall',
|
||||
'Asmall',
|
||||
'Bsmall',
|
||||
'Csmall',
|
||||
'Dsmall',
|
||||
'Esmall',
|
||||
'Fsmall',
|
||||
'Gsmall',
|
||||
'Hsmall',
|
||||
'Ismall',
|
||||
'Jsmall',
|
||||
'Ksmall',
|
||||
'Lsmall',
|
||||
'Msmall',
|
||||
'Nsmall',
|
||||
'Osmall',
|
||||
'Psmall',
|
||||
'Qsmall',
|
||||
'Rsmall',
|
||||
'Ssmall',
|
||||
'Tsmall',
|
||||
'Usmall',
|
||||
'Vsmall',
|
||||
'Wsmall',
|
||||
'Xsmall',
|
||||
'Ysmall',
|
||||
'Zsmall',
|
||||
'colonmonetary',
|
||||
'onefitted',
|
||||
'rupiah',
|
||||
'Tildesmall',
|
||||
'exclamdownsmall',
|
||||
'centoldstyle',
|
||||
'Lslashsmall',
|
||||
'Scaronsmall',
|
||||
'Zcaronsmall',
|
||||
'Dieresissmall',
|
||||
'Brevesmall',
|
||||
'Caronsmall',
|
||||
'Dotaccentsmall',
|
||||
'Macronsmall',
|
||||
'figuredash',
|
||||
'hypheninferior',
|
||||
'Ogoneksmall',
|
||||
'Ringsmall',
|
||||
'Cedillasmall',
|
||||
'questiondownsmall',
|
||||
'oneeighth',
|
||||
'threeeighths',
|
||||
'fiveeighths',
|
||||
'seveneighths',
|
||||
'onethird',
|
||||
'twothirds',
|
||||
'zerosuperior',
|
||||
'foursuperior',
|
||||
'fivesuperior',
|
||||
'sixsuperior',
|
||||
'sevensuperior',
|
||||
'eightsuperior',
|
||||
'ninesuperior',
|
||||
'zeroinferior',
|
||||
'oneinferior',
|
||||
'twoinferior',
|
||||
'threeinferior',
|
||||
'fourinferior',
|
||||
'fiveinferior',
|
||||
'sixinferior',
|
||||
'seveninferior',
|
||||
'eightinferior',
|
||||
'nineinferior',
|
||||
'centinferior',
|
||||
'dollarinferior',
|
||||
'periodinferior',
|
||||
'commainferior',
|
||||
'Agravesmall',
|
||||
'Aacutesmall',
|
||||
'Acircumflexsmall',
|
||||
'Atildesmall',
|
||||
'Adieresissmall',
|
||||
'Aringsmall',
|
||||
'AEsmall',
|
||||
'Ccedillasmall',
|
||||
'Egravesmall',
|
||||
'Eacutesmall',
|
||||
'Ecircumflexsmall',
|
||||
'Edieresissmall',
|
||||
'Igravesmall',
|
||||
'Iacutesmall',
|
||||
'Icircumflexsmall',
|
||||
'Idieresissmall',
|
||||
'Ethsmall',
|
||||
'Ntildesmall',
|
||||
'Ogravesmall',
|
||||
'Oacutesmall',
|
||||
'Ocircumflexsmall',
|
||||
'Otildesmall',
|
||||
'Odieresissmall',
|
||||
'OEsmall',
|
||||
'Oslashsmall',
|
||||
'Ugravesmall',
|
||||
'Uacutesmall',
|
||||
'Ucircumflexsmall',
|
||||
'Udieresissmall',
|
||||
'Yacutesmall',
|
||||
'Thornsmall',
|
||||
],
|
||||
).freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,120 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
module Charsets
|
||||
# Predefined CFF Expert Subset charset
|
||||
EXPERT_SUBSET = OneBasedArray.new(
|
||||
[
|
||||
'space',
|
||||
*[nil] * 11,
|
||||
'comma',
|
||||
'hyphen',
|
||||
'period',
|
||||
*[nil] * 11,
|
||||
'colon',
|
||||
'semicolon',
|
||||
*[nil] * 70,
|
||||
'fraction',
|
||||
*[nil] * 9,
|
||||
'fi',
|
||||
'fl',
|
||||
*[nil] * 39,
|
||||
'onesuperior',
|
||||
*[nil] * 4,
|
||||
'onehalf',
|
||||
nil,
|
||||
nil,
|
||||
'onequarter',
|
||||
*[nil] * 4,
|
||||
'threequarters',
|
||||
'twosuperior',
|
||||
*[nil] * 4,
|
||||
'threesuperior',
|
||||
*[nil] * 61,
|
||||
'dollaroldstyle',
|
||||
'dollarsuperior',
|
||||
nil,
|
||||
nil,
|
||||
'parenleftsuperior',
|
||||
'parenrightsuperior',
|
||||
'twodotenleader',
|
||||
'onedotenleader',
|
||||
'zerooldstyle',
|
||||
'oneoldstyle',
|
||||
'twooldstyle',
|
||||
'threeoldstyle',
|
||||
'fouroldstyle',
|
||||
'fiveoldstyle',
|
||||
'sixoldstyle',
|
||||
'sevenoldstyle',
|
||||
'eightoldstyle',
|
||||
'nineoldstyle',
|
||||
'commasuperior',
|
||||
'threequartersemdash',
|
||||
'periodsuperior',
|
||||
nil,
|
||||
'asuperior',
|
||||
'bsuperior',
|
||||
'centsuperior',
|
||||
'dsuperior',
|
||||
'esuperior',
|
||||
'isuperior',
|
||||
'lsuperior',
|
||||
'msuperior',
|
||||
'nsuperior',
|
||||
'osuperior',
|
||||
'rsuperior',
|
||||
'ssuperior',
|
||||
'tsuperior',
|
||||
'ff',
|
||||
'ffi',
|
||||
'ffl',
|
||||
'parenleftinferior',
|
||||
'parenrightinferior',
|
||||
nil,
|
||||
'hyphensuperior',
|
||||
*[nil] * 27,
|
||||
'colonmonetary',
|
||||
'onefitted',
|
||||
'rupiah',
|
||||
nil,
|
||||
nil,
|
||||
'centoldstyle',
|
||||
*[nil] * 8,
|
||||
'figuredash',
|
||||
'hypheninferior',
|
||||
*[nil] * 4,
|
||||
'oneeighth',
|
||||
'threeeighths',
|
||||
'fiveeighths',
|
||||
'seveneighths',
|
||||
'onethird',
|
||||
'twothirds',
|
||||
'zerosuperior',
|
||||
'foursuperior',
|
||||
'fivesuperior',
|
||||
'sixsuperior',
|
||||
'sevensuperior',
|
||||
'eightsuperior',
|
||||
'ninesuperior',
|
||||
'zeroinferior',
|
||||
'oneinferior',
|
||||
'twoinferior',
|
||||
'threeinferior',
|
||||
'fourinferior',
|
||||
'fiveinferior',
|
||||
'sixinferior',
|
||||
'seveninferior',
|
||||
'eightinferior',
|
||||
'nineinferior',
|
||||
'centinferior',
|
||||
'dollarinferior',
|
||||
'periodinferior',
|
||||
],
|
||||
).freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,242 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
module Charsets
|
||||
# Predefined CFF ISOAdobe charset
|
||||
ISO_ADOBE = OneBasedArray.new(
|
||||
[
|
||||
'space',
|
||||
'exclam',
|
||||
'quotedbl',
|
||||
'numbersign',
|
||||
'dollar',
|
||||
'percent',
|
||||
'ampersand',
|
||||
'quoteright',
|
||||
'parenleft',
|
||||
'parenright',
|
||||
'asterisk',
|
||||
'plus',
|
||||
'comma',
|
||||
'hyphen',
|
||||
'period',
|
||||
'slash',
|
||||
'zero',
|
||||
'one',
|
||||
'two',
|
||||
'three',
|
||||
'four',
|
||||
'five',
|
||||
'six',
|
||||
'seven',
|
||||
'eight',
|
||||
'nine',
|
||||
'colon',
|
||||
'semicolon',
|
||||
'less',
|
||||
'equal',
|
||||
'greater',
|
||||
'question',
|
||||
'at',
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
'D',
|
||||
'E',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'I',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'M',
|
||||
'N',
|
||||
'O',
|
||||
'P',
|
||||
'Q',
|
||||
'R',
|
||||
'S',
|
||||
'T',
|
||||
'U',
|
||||
'V',
|
||||
'W',
|
||||
'X',
|
||||
'Y',
|
||||
'Z',
|
||||
'bracketleft',
|
||||
'backslash',
|
||||
'bracketright',
|
||||
'asciicircum',
|
||||
'underscore',
|
||||
'quoteleft',
|
||||
'a',
|
||||
'b',
|
||||
'c',
|
||||
'd',
|
||||
'e',
|
||||
'f',
|
||||
'g',
|
||||
'h',
|
||||
'i',
|
||||
'j',
|
||||
'k',
|
||||
'l',
|
||||
'm',
|
||||
'n',
|
||||
'o',
|
||||
'p',
|
||||
'q',
|
||||
'r',
|
||||
's',
|
||||
't',
|
||||
'u',
|
||||
'v',
|
||||
'w',
|
||||
'x',
|
||||
'y',
|
||||
'z',
|
||||
'braceleft',
|
||||
'bar',
|
||||
'braceright',
|
||||
'asciitilde',
|
||||
'exclamdown 97 cent',
|
||||
'sterling',
|
||||
'fraction',
|
||||
'yen',
|
||||
'florin',
|
||||
'section',
|
||||
'currency',
|
||||
'quotesingle',
|
||||
'quotedblleft',
|
||||
'guillemotleft',
|
||||
'guilsinglleft',
|
||||
'guilsinglright',
|
||||
'fi',
|
||||
'fl',
|
||||
'endash',
|
||||
'dagger',
|
||||
'daggerdbl',
|
||||
'periodcentered',
|
||||
'paragraph',
|
||||
'bullet',
|
||||
'quotesinglbase',
|
||||
'quotedblbase',
|
||||
'quotedblright',
|
||||
'guillemotright',
|
||||
'ellipsis',
|
||||
'perthousand',
|
||||
'questiondown',
|
||||
'grave',
|
||||
'acute',
|
||||
'circumflex',
|
||||
'tilde',
|
||||
'macron',
|
||||
'breve',
|
||||
'dotaccent',
|
||||
'dieresis',
|
||||
'ring',
|
||||
'cedilla',
|
||||
'hungarumlaut',
|
||||
'ogonek',
|
||||
'caron',
|
||||
'emdash',
|
||||
'AE',
|
||||
'ordfeminine',
|
||||
'Lslash',
|
||||
'Oslash',
|
||||
'OE',
|
||||
'ordmasculine',
|
||||
'ae',
|
||||
'dotlessi',
|
||||
'lslash',
|
||||
'oslash',
|
||||
'oe',
|
||||
'germandbls',
|
||||
'onesuperior',
|
||||
'logicalnot',
|
||||
'mu',
|
||||
'trademark',
|
||||
'Eth',
|
||||
'onehalf',
|
||||
'plusminus',
|
||||
'Thorn',
|
||||
'onequarter',
|
||||
'divide',
|
||||
'brokenbar',
|
||||
'degree',
|
||||
'thorn',
|
||||
'threequarters',
|
||||
'twosuperior',
|
||||
'registered',
|
||||
'minus',
|
||||
'eth',
|
||||
'multiply',
|
||||
'threesuperior',
|
||||
'copyright',
|
||||
'Aacute',
|
||||
'Acircumflex',
|
||||
'Adieresis',
|
||||
'Agrave',
|
||||
'Aring',
|
||||
'Atilde',
|
||||
'Ccedilla',
|
||||
'Eacute',
|
||||
'Ecircumflex',
|
||||
'Edieresis',
|
||||
'Egrave',
|
||||
'Iacute',
|
||||
'Icircumflex',
|
||||
'Idieresis',
|
||||
'Igrave',
|
||||
'Ntilde',
|
||||
'Oacute',
|
||||
'Ocircumflex',
|
||||
'Odieresis',
|
||||
'Ograve',
|
||||
'Otilde',
|
||||
'Scaron',
|
||||
'Uacute',
|
||||
'Ucircumflex',
|
||||
'Udieresis',
|
||||
'Ugrave',
|
||||
'Yacute',
|
||||
'Ydieresis',
|
||||
'Zcaron',
|
||||
'aacute',
|
||||
'acircumflex',
|
||||
'adieresis',
|
||||
'agrave',
|
||||
'aring',
|
||||
'atilde',
|
||||
'ccedilla',
|
||||
'eacute',
|
||||
'ecircumflex',
|
||||
'edieresis',
|
||||
'egrave',
|
||||
'iacute',
|
||||
'icircumflex',
|
||||
'idieresis',
|
||||
'igrave',
|
||||
'ntilde',
|
||||
'oacute',
|
||||
'ocircumflex',
|
||||
'odieresis',
|
||||
'ograve',
|
||||
'otilde',
|
||||
'scaron',
|
||||
'uacute',
|
||||
'ucircumflex',
|
||||
'udieresis',
|
||||
'ugrave',
|
||||
'yacute',
|
||||
'ydieresis',
|
||||
'zcaron',
|
||||
],
|
||||
).freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,405 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
module Charsets
|
||||
# Standard CFF strings
|
||||
STANDARD_STRINGS = OneBasedArray.new(
|
||||
[
|
||||
'space',
|
||||
'exclam',
|
||||
'quotedbl',
|
||||
'numbersign',
|
||||
'dollar',
|
||||
'percent',
|
||||
'ampersand',
|
||||
'quoteright',
|
||||
'parenleft',
|
||||
'parenright',
|
||||
'asterisk',
|
||||
'plus',
|
||||
'comma',
|
||||
'hyphen',
|
||||
'period',
|
||||
'slash',
|
||||
'zero',
|
||||
'one',
|
||||
'two',
|
||||
'three',
|
||||
'four',
|
||||
'five',
|
||||
'six',
|
||||
'seven',
|
||||
'eight',
|
||||
'nine',
|
||||
'colon',
|
||||
'semicolon',
|
||||
'less',
|
||||
'equal',
|
||||
'greater',
|
||||
'question',
|
||||
'at',
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
'D',
|
||||
'E',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'I',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'M',
|
||||
'N',
|
||||
'O',
|
||||
'P',
|
||||
'Q',
|
||||
'R',
|
||||
'S',
|
||||
'T',
|
||||
'U',
|
||||
'V',
|
||||
'W',
|
||||
'X',
|
||||
'Y',
|
||||
'Z',
|
||||
'bracketleft',
|
||||
'backslash',
|
||||
'bracketright',
|
||||
'asciicircum',
|
||||
'underscore',
|
||||
'quoteleft',
|
||||
'a',
|
||||
'b',
|
||||
'c',
|
||||
'd',
|
||||
'e',
|
||||
'f',
|
||||
'g',
|
||||
'h',
|
||||
'i',
|
||||
'j',
|
||||
'k',
|
||||
'l',
|
||||
'm',
|
||||
'n',
|
||||
'o',
|
||||
'p',
|
||||
'q',
|
||||
'r',
|
||||
's',
|
||||
't',
|
||||
'u',
|
||||
'v',
|
||||
'w',
|
||||
'x',
|
||||
'y',
|
||||
'z',
|
||||
'braceleft',
|
||||
'bar',
|
||||
'braceright',
|
||||
'asciitilde',
|
||||
'exclamdown',
|
||||
'cent',
|
||||
'sterling',
|
||||
'fraction',
|
||||
'yen',
|
||||
'florin',
|
||||
'section',
|
||||
'currency',
|
||||
'quotesingle',
|
||||
'quotedblleft',
|
||||
'guillemotleft',
|
||||
'guilsinglleft',
|
||||
'guilsinglright',
|
||||
'fi',
|
||||
'fl',
|
||||
'endash',
|
||||
'dagger',
|
||||
'daggerdbl',
|
||||
'periodcentered',
|
||||
'paragraph',
|
||||
'bullet',
|
||||
'quotesinglbase',
|
||||
'quotedblbase',
|
||||
'quotedblright',
|
||||
'guillemotright',
|
||||
'ellipsis',
|
||||
'perthousand',
|
||||
'questiondown',
|
||||
'grave',
|
||||
'acute',
|
||||
'circumflex',
|
||||
'tilde',
|
||||
'macron',
|
||||
'breve',
|
||||
'dotaccent',
|
||||
'dieresis',
|
||||
'ring',
|
||||
'cedilla',
|
||||
'hungarumlaut',
|
||||
'ogonek',
|
||||
'caron',
|
||||
'emdash',
|
||||
'AE',
|
||||
'ordfeminine',
|
||||
'Lslash',
|
||||
'Oslash',
|
||||
'OE',
|
||||
'ordmasculine',
|
||||
'ae',
|
||||
'dotlessi',
|
||||
'lslash',
|
||||
'oslash',
|
||||
'oe',
|
||||
'germandbls',
|
||||
'onesuperior',
|
||||
'logicalnot',
|
||||
'mu',
|
||||
'trademark',
|
||||
'Eth',
|
||||
'onehalf',
|
||||
'plusminus',
|
||||
'Thorn',
|
||||
'onequarter',
|
||||
'divide',
|
||||
'brokenbar',
|
||||
'degree',
|
||||
'thorn',
|
||||
'threequarters',
|
||||
'twosuperior',
|
||||
'registered',
|
||||
'minus',
|
||||
'eth',
|
||||
'multiply',
|
||||
'threesuperior',
|
||||
'copyright',
|
||||
'Aacute',
|
||||
'Acircumflex',
|
||||
'Adieresis',
|
||||
'Agrave',
|
||||
'Aring',
|
||||
'Atilde',
|
||||
'Ccedilla',
|
||||
'Eacute',
|
||||
'Ecircumflex',
|
||||
'Edieresis',
|
||||
'Egrave',
|
||||
'Iacute',
|
||||
'Icircumflex',
|
||||
'Idieresis',
|
||||
'Igrave',
|
||||
'Ntilde',
|
||||
'Oacute',
|
||||
'Ocircumflex',
|
||||
'Odieresis',
|
||||
'Ograve',
|
||||
'Otilde',
|
||||
'Scaron',
|
||||
'Uacute',
|
||||
'Ucircumflex',
|
||||
'Udieresis',
|
||||
'Ugrave',
|
||||
'Yacute',
|
||||
'Ydieresis',
|
||||
'Zcaron',
|
||||
'aacute',
|
||||
'acircumflex',
|
||||
'adieresis',
|
||||
'agrave',
|
||||
'aring',
|
||||
'atilde',
|
||||
'ccedilla',
|
||||
'eacute',
|
||||
'ecircumflex',
|
||||
'edieresis',
|
||||
'egrave',
|
||||
'iacute',
|
||||
'icircumflex',
|
||||
'idieresis',
|
||||
'igrave',
|
||||
'ntilde',
|
||||
'oacute',
|
||||
'ocircumflex',
|
||||
'odieresis',
|
||||
'ograve',
|
||||
'otilde',
|
||||
'scaron',
|
||||
'uacute',
|
||||
'ucircumflex',
|
||||
'udieresis',
|
||||
'ugrave',
|
||||
'yacute',
|
||||
'ydieresis',
|
||||
'zcaron',
|
||||
'exclamsmall',
|
||||
'Hungarumlautsmall',
|
||||
'dollaroldstyle',
|
||||
'dollarsuperior',
|
||||
'ampersandsmall',
|
||||
'Acutesmall',
|
||||
'parenleftsuperior',
|
||||
'parenrightsuperior',
|
||||
'266 ff',
|
||||
'onedotenleader',
|
||||
'zerooldstyle',
|
||||
'oneoldstyle',
|
||||
'twooldstyle',
|
||||
'threeoldstyle',
|
||||
'fouroldstyle',
|
||||
'fiveoldstyle',
|
||||
'sixoldstyle',
|
||||
'sevenoldstyle',
|
||||
'eightoldstyle',
|
||||
'nineoldstyle',
|
||||
'commasuperior',
|
||||
'threequartersemdash',
|
||||
'periodsuperior',
|
||||
'questionsmall',
|
||||
'asuperior',
|
||||
'bsuperior',
|
||||
'centsuperior',
|
||||
'dsuperior',
|
||||
'esuperior',
|
||||
'isuperior',
|
||||
'lsuperior',
|
||||
'msuperior',
|
||||
'nsuperior',
|
||||
'osuperior',
|
||||
'rsuperior',
|
||||
'ssuperior',
|
||||
'tsuperior',
|
||||
'ff',
|
||||
'ffi',
|
||||
'ffl',
|
||||
'parenleftinferior',
|
||||
'parenrightinferior',
|
||||
'Circumflexsmall',
|
||||
'hyphensuperior',
|
||||
'Gravesmall',
|
||||
'Asmall',
|
||||
'Bsmall',
|
||||
'Csmall',
|
||||
'Dsmall',
|
||||
'Esmall',
|
||||
'Fsmall',
|
||||
'Gsmall',
|
||||
'Hsmall',
|
||||
'Ismall',
|
||||
'Jsmall',
|
||||
'Ksmall',
|
||||
'Lsmall',
|
||||
'Msmall',
|
||||
'Nsmall',
|
||||
'Osmall',
|
||||
'Psmall',
|
||||
'Qsmall',
|
||||
'Rsmall',
|
||||
'Ssmall',
|
||||
'Tsmall',
|
||||
'Usmall',
|
||||
'Vsmall',
|
||||
'Wsmall',
|
||||
'Xsmall',
|
||||
'Ysmall',
|
||||
'Zsmall',
|
||||
'colonmonetary',
|
||||
'onefitted',
|
||||
'rupiah',
|
||||
'Tildesmall',
|
||||
'exclamdownsmall',
|
||||
'centoldstyle',
|
||||
'Lslashsmall',
|
||||
'Scaronsmall',
|
||||
'Zcaronsmall',
|
||||
'Dieresissmall',
|
||||
'Brevesmall',
|
||||
'Caronsmall',
|
||||
'Dotaccentsmall',
|
||||
'Macronsmall',
|
||||
'figuredash',
|
||||
'hypheninferior',
|
||||
'Ogoneksmall',
|
||||
'Ringsmall',
|
||||
'Cedillasmall',
|
||||
'questiondownsmall',
|
||||
'oneeighth',
|
||||
'threeeighths',
|
||||
'fiveeighths',
|
||||
'seveneighths',
|
||||
'onethird',
|
||||
'twothirds',
|
||||
'zerosuperior',
|
||||
'foursuperior',
|
||||
'fivesuperior',
|
||||
'sixsuperior',
|
||||
'sevensuperior',
|
||||
'eightsuperior',
|
||||
'ninesuperior',
|
||||
'zeroinferior',
|
||||
'oneinferior',
|
||||
'twoinferior',
|
||||
'threeinferior',
|
||||
'fourinferior',
|
||||
'fiveinferior',
|
||||
'sixinferior',
|
||||
'seveninferior',
|
||||
'eightinferior',
|
||||
'nineinferior',
|
||||
'centinferior',
|
||||
'dollarinferior',
|
||||
'periodinferior',
|
||||
'commainferior',
|
||||
'Agravesmall',
|
||||
'Aacutesmall',
|
||||
'Acircumflexsmall',
|
||||
'Atildesmall',
|
||||
'Adieresissmall',
|
||||
'Aringsmall',
|
||||
'AEsmall',
|
||||
'Ccedillasmall',
|
||||
'Egravesmall',
|
||||
'Eacutesmall',
|
||||
'Ecircumflexsmall',
|
||||
'Edieresissmall',
|
||||
'Igravesmall',
|
||||
'Iacutesmall',
|
||||
'Icircumflexsmall',
|
||||
'Idieresissmall',
|
||||
'Ethsmall',
|
||||
'Ntildesmall',
|
||||
'Ogravesmall',
|
||||
'Oacutesmall',
|
||||
'Ocircumflexsmall',
|
||||
'Otildesmall',
|
||||
'Odieresissmall',
|
||||
'OEsmall',
|
||||
'Oslashsmall',
|
||||
'Ugravesmall',
|
||||
'Uacutesmall',
|
||||
'Ucircumflexsmall',
|
||||
'Udieresissmall',
|
||||
'Yacutesmall',
|
||||
'Thornsmall',
|
||||
'Ydieresissmall',
|
||||
'001.000',
|
||||
'001.001',
|
||||
'001.002',
|
||||
'001.003',
|
||||
'Black',
|
||||
'Bold',
|
||||
'Book',
|
||||
'Light',
|
||||
'Medium',
|
||||
'Regular',
|
||||
'Roman',
|
||||
'Semibold',
|
||||
],
|
||||
).freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,507 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
# CFF Charstring.
|
||||
class Charstring
|
||||
# Type 2 charstring operators
|
||||
CODE_MAP = {
|
||||
1 => :hstem,
|
||||
3 => :vstem,
|
||||
4 => :vmoveto,
|
||||
5 => :rlineto,
|
||||
6 => :hlineto,
|
||||
7 => :vlineto,
|
||||
8 => :rrcurveto,
|
||||
10 => :callsubr,
|
||||
12 => :flex_select,
|
||||
14 => :endchar,
|
||||
18 => :hstemhm,
|
||||
19 => :hintmask,
|
||||
20 => :cntrmask,
|
||||
21 => :rmoveto,
|
||||
22 => :hmoveto,
|
||||
23 => :vstemhm,
|
||||
24 => :rcurveline,
|
||||
25 => :rlinecurve,
|
||||
26 => :vvcurveto,
|
||||
27 => :hhcurveto,
|
||||
28 => :shortint,
|
||||
29 => :callgsubr,
|
||||
30 => :vhcurveto,
|
||||
31 => :hvcurveto,
|
||||
}.freeze
|
||||
|
||||
# Type 2 Flex operators.
|
||||
FLEX_CODE_MAP = {
|
||||
35 => :flex,
|
||||
34 => :hflex,
|
||||
36 => :hflex1,
|
||||
37 => :flex1,
|
||||
}.freeze
|
||||
|
||||
# Glyph ID.
|
||||
# @return [Integer]
|
||||
attr_reader :glyph_id
|
||||
|
||||
# Encoded charstring.
|
||||
# @return [String]
|
||||
attr_reader :raw
|
||||
|
||||
# @param glyph_id [Integer]
|
||||
# @param top_dict [TTFunk::Table:Cff::TopDict]
|
||||
# @param font_dict [TTFunk::Table:Cff::FontDict]
|
||||
# @param raw [String]
|
||||
def initialize(glyph_id, top_dict, font_dict, raw)
|
||||
@glyph_id = glyph_id
|
||||
@top_dict = top_dict
|
||||
@font_dict = font_dict
|
||||
@raw = raw
|
||||
|
||||
default_width_x = (@font_dict || @top_dict).private_dict.default_width_x
|
||||
|
||||
@nominal_width_x = (@font_dict || @top_dict).private_dict.nominal_width_x
|
||||
|
||||
@subrs = (@font_dict || @top_dict).private_dict.subr_index
|
||||
@gsubrs = @top_dict.cff.global_subr_index
|
||||
@subrs_bias = @subrs.bias if @subrs
|
||||
@gsubrs_bias = @gsubrs.bias if @gsubrs
|
||||
|
||||
@stack = []
|
||||
@data = raw.bytes
|
||||
@index = 0
|
||||
@n_stems = 0
|
||||
@have_width = false
|
||||
@open = false
|
||||
@width = default_width_x
|
||||
@x = 0
|
||||
@y = 0
|
||||
end
|
||||
|
||||
# Get path representation of this charstring.
|
||||
#
|
||||
# @return [TTFunk::Table::Cff::Path]
|
||||
def path
|
||||
@path || begin
|
||||
@path = Path.new
|
||||
parse!
|
||||
@path
|
||||
end
|
||||
end
|
||||
|
||||
# Get a TrueType-compatible glyph representation of this charstring.
|
||||
#
|
||||
# @return [TTFunk::Table::Glyf::PathBased]
|
||||
def glyph
|
||||
@glyph ||=
|
||||
begin
|
||||
horizontal_metrics = @top_dict.file.horizontal_metrics.for(glyph_id)
|
||||
Glyf::PathBased.new(path, horizontal_metrics)
|
||||
end
|
||||
end
|
||||
|
||||
# Get path representation of this charstring at the specified font size.
|
||||
#
|
||||
# @param x [Integer, Float] new horizontal position.
|
||||
# @param y [Integer, Float] new vertical position.
|
||||
# @param font_size [Integer, Float] font size.
|
||||
# @return [TTFunk::Table::Cff::Path]
|
||||
def render(x: 0, y: 0, font_size: 72)
|
||||
path.render(
|
||||
x: x,
|
||||
y: y,
|
||||
font_size: font_size,
|
||||
units_per_em: @top_dict.file.header.units_per_em,
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse!
|
||||
until @index >= @data.size
|
||||
code = @data[@index]
|
||||
@index += 1
|
||||
|
||||
# return from callgsubr - do nothing since we inline subrs
|
||||
next if code == 11
|
||||
|
||||
if code >= 32 && code <= 246
|
||||
@stack << (code - 139)
|
||||
elsif (m = CODE_MAP[code])
|
||||
__send__(m)
|
||||
elsif code >= 247 && code <= 250
|
||||
b0 = code
|
||||
b1 = @data[@index]
|
||||
@index += 1
|
||||
@stack << (((b0 - 247) * 256) + b1 + 108)
|
||||
elsif code >= 251 && code <= 254
|
||||
b0 = code
|
||||
b1 = @data[@index]
|
||||
@index += 1
|
||||
@stack << ((-(b0 - 251) * 256) - b1 - 108)
|
||||
else
|
||||
b1, b2, b3, b4 = read_bytes(4)
|
||||
@stack << (((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65_536)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def read_bytes(length)
|
||||
bytes = @data[@index, length]
|
||||
@index += length
|
||||
bytes
|
||||
end
|
||||
|
||||
def hstem
|
||||
stem
|
||||
end
|
||||
|
||||
def vstem
|
||||
stem
|
||||
end
|
||||
|
||||
# rubocop:disable Style/EvenOdd
|
||||
def stem
|
||||
# The number of stem operators on the stack is always even.
|
||||
# If the value is uneven, that means a width is specified.
|
||||
has_width_arg = @stack.size % 2 == 1
|
||||
|
||||
if has_width_arg && !@have_width
|
||||
@width = @stack.shift + @nominal_width_x
|
||||
end
|
||||
|
||||
@n_stems += @stack.length >> 1
|
||||
@stack.clear
|
||||
@have_width = true
|
||||
end
|
||||
# rubocop:enable Style/EvenOdd
|
||||
|
||||
def vmoveto
|
||||
if @stack.size > 1 && !@have_width
|
||||
@width = @stack.shift + @nominal_width_x
|
||||
@have_width = true
|
||||
end
|
||||
|
||||
@y += @stack.pop
|
||||
add_contour(@x, @y)
|
||||
end
|
||||
|
||||
def add_contour(x, y)
|
||||
if @open
|
||||
@path.close_path
|
||||
end
|
||||
|
||||
@path.move_to(x, y)
|
||||
@open = true
|
||||
end
|
||||
|
||||
def rlineto
|
||||
until @stack.empty?
|
||||
@x += @stack.shift
|
||||
@y += @stack.shift
|
||||
@path.line_to(@x, @y)
|
||||
end
|
||||
end
|
||||
|
||||
def hlineto
|
||||
until @stack.empty?
|
||||
@x += @stack.shift
|
||||
@path.line_to(@x, @y)
|
||||
|
||||
break if @stack.empty?
|
||||
|
||||
@y += @stack.shift
|
||||
@path.line_to(@x, @y)
|
||||
end
|
||||
end
|
||||
|
||||
def vlineto
|
||||
until @stack.empty?
|
||||
@y += @stack.shift
|
||||
@path.line_to(@x, @y)
|
||||
|
||||
break if @stack.empty?
|
||||
|
||||
@x += @stack.shift
|
||||
@path.line_to(@x, @y)
|
||||
end
|
||||
end
|
||||
|
||||
def rrcurveto
|
||||
until @stack.empty?
|
||||
c1x = @x + @stack.shift
|
||||
c1y = @y + @stack.shift
|
||||
c2x = c1x + @stack.shift
|
||||
c2y = c1y + @stack.shift
|
||||
@x = c2x + @stack.shift
|
||||
@y = c2y + @stack.shift
|
||||
@path.curve_to(c1x, c1y, c2x, c2y, @x, @y)
|
||||
end
|
||||
end
|
||||
|
||||
def callsubr
|
||||
code_index = @stack.pop + @subrs_bias
|
||||
subr_code = @subrs[code_index].bytes
|
||||
@data[@index, 0] = subr_code
|
||||
end
|
||||
|
||||
def flex_select
|
||||
flex_code = @data[@index]
|
||||
@index += 1
|
||||
__send__(FLEX_CODE_MAP[flex_code])
|
||||
end
|
||||
|
||||
def flex
|
||||
c1x = @x + @stack.shift # dx1
|
||||
c1y = @y + @stack.shift # dy1
|
||||
c2x = c1x + @stack.shift # dx2
|
||||
c2y = c1y + @stack.shift # dy2
|
||||
jpx = c2x + @stack.shift # dx3
|
||||
jpy = c2y + @stack.shift # dy3
|
||||
c3x = jpx + @stack.shift # dx4
|
||||
c3y = jpy + @stack.shift # dy4
|
||||
c4x = c3x + @stack.shift # dx5
|
||||
c4y = c3y + @stack.shift # dy5
|
||||
@x = c4x + @stack.shift # dx6
|
||||
@y = c4y + @stack.shift # dy6
|
||||
@stack.shift # flex depth
|
||||
|
||||
@path.curve_to(c1x, c1y, c2x, c2y, jpx, jpy)
|
||||
@path.curve_to(c3x, c3y, c4x, c4y, @x, @y)
|
||||
end
|
||||
|
||||
def hflex
|
||||
c1x = @x + @stack.shift # dx1
|
||||
c1y = @y # dy1
|
||||
c2x = c1x + @stack.shift # dx2
|
||||
c2y = c1y + @stack.shift # dy2
|
||||
jpx = c2x + @stack.shift # dx3
|
||||
jpy = c2y # dy3
|
||||
c3x = jpx + stack.shift # dx4
|
||||
c3y = c2y # dy4
|
||||
c4x = c3x + stack.shift # dx5
|
||||
c4y = @y # dy5
|
||||
@x = c4x + stack.shift # dx6
|
||||
|
||||
@path.curve_to(c1x, c1y, c2x, c2y, jpx, jpy)
|
||||
@path.curve_to(c3x, c3y, c4x, c4y, @x, @y)
|
||||
end
|
||||
|
||||
def hflex1
|
||||
c1x = @x + @stack.shift # dx1
|
||||
c1y = @y + @stack.shift # dy1
|
||||
c2x = c1x + @stack.shift # dx2
|
||||
c2y = c1y + @stack.shift # dy2
|
||||
jpx = c2x + @stack.shift # dx3
|
||||
jpy = c2y # dy3
|
||||
c3x = jpx + @stack.shift # dx4
|
||||
c3y = c2y # dy4
|
||||
c4x = c3x + @stack.shift # dx5
|
||||
c4y = c3y + @stack.shift # dy5
|
||||
@x = c4x + @stack.shift # dx6
|
||||
|
||||
@path.curve_to(c1x, c1y, c2x, c2y, jpx, jpy)
|
||||
@path.curve_to(c3x, c3y, c4x, c4y, @x, @y)
|
||||
end
|
||||
|
||||
def flex1
|
||||
c1x = @x + @stack.shift # dx1
|
||||
c1y = @y + @stack.shift # dy1
|
||||
c2x = c1x + @stack.shift # dx2
|
||||
c2y = c1y + @stack.shift # dy2
|
||||
jpx = c2x + @stack.shift # dx3
|
||||
jpy = c2y + @stack.shift # dy3
|
||||
c3x = jpx + @stack.shift # dx4
|
||||
c3y = jpy + @stack.shift # dy4
|
||||
c4x = c3x + @stack.shift # dx5
|
||||
c4y = c3y + @stack.shift # dy5
|
||||
|
||||
if (c4x - @x).abs > (c4y - @y).abs
|
||||
@x = c4x + @stack.shift
|
||||
else
|
||||
@y = c4y + @stack.shift
|
||||
end
|
||||
|
||||
@path.curve_to(c1x, c1y, c2x, c2y, jpx, jpy)
|
||||
@path.curve_to(c3x, c3y, c4x, c4y, @x, @y)
|
||||
end
|
||||
|
||||
def endchar
|
||||
if !@stack.empty? && !@have_width
|
||||
@width = @stack.shift + @nominal_width_x
|
||||
@have_width = true
|
||||
end
|
||||
|
||||
if @open
|
||||
@path.close_path
|
||||
@open = false
|
||||
end
|
||||
end
|
||||
|
||||
def hstemhm
|
||||
stem
|
||||
end
|
||||
|
||||
def hintmask
|
||||
cntrmask
|
||||
end
|
||||
|
||||
def cntrmask
|
||||
stem
|
||||
|
||||
# skip past hinting data, no need to worry about it since we're not
|
||||
# very concerned about rendering glyphs
|
||||
read_bytes((@n_stems + 7) >> 3)
|
||||
end
|
||||
|
||||
def rmoveto
|
||||
if @stack.size > 2 && !@have_width
|
||||
@width = @stack.shift + @nominal_width_x
|
||||
@have_width = true
|
||||
end
|
||||
|
||||
@y += @stack.pop
|
||||
@x += @stack.pop
|
||||
add_contour(@x, @y)
|
||||
end
|
||||
|
||||
def hmoveto
|
||||
if @stack.size > 1 && !@have_width
|
||||
@width = @stack.shift + @nominal_width_x
|
||||
@have_width = true
|
||||
end
|
||||
|
||||
@x += @stack.pop
|
||||
add_contour(@x, @y)
|
||||
end
|
||||
|
||||
def vstemhm
|
||||
stem
|
||||
end
|
||||
|
||||
def rcurveline
|
||||
while @stack.size > 2
|
||||
c1x = @x + @stack.shift
|
||||
c1y = @y + @stack.shift
|
||||
c2x = c1x + @stack.shift
|
||||
c2y = c1y + @stack.shift
|
||||
@x = c2x + @stack.shift
|
||||
@y = c2y + @stack.shift
|
||||
@path.curve_to(c1x, c1y, c2x, c2y, @x, @y)
|
||||
end
|
||||
|
||||
@x += @stack.shift
|
||||
@y += @stack.shift
|
||||
@path.line_to(@x, @y)
|
||||
end
|
||||
|
||||
def rlinecurve
|
||||
while @stack.size > 6
|
||||
@x += @stack.shift
|
||||
@y += @stack.shift
|
||||
@path.line_to(@x, @y)
|
||||
end
|
||||
|
||||
c1x = @x + @stack.shift
|
||||
c1y = @y + @stack.shift
|
||||
c2x = c1x + @stack.shift
|
||||
c2y = c1y + @stack.shift
|
||||
@x = c2x + @stack.shift
|
||||
@y = c2y + @stack.shift
|
||||
|
||||
@path.curve_to(c1x, c1y, c2x, c2y, @x, @y)
|
||||
end
|
||||
|
||||
# rubocop:disable Style/EvenOdd
|
||||
def vvcurveto
|
||||
if @stack.size % 2 == 1
|
||||
@x += @stack.shift
|
||||
end
|
||||
|
||||
until @stack.empty?
|
||||
c1x = @x
|
||||
c1y = @y + @stack.shift
|
||||
c2x = c1x + @stack.shift
|
||||
c2y = c1y + @stack.shift
|
||||
@x = c2x
|
||||
@y = c2y + @stack.shift
|
||||
@path.curve_to(c1x, c1y, c2x, c2y, @x, @y)
|
||||
end
|
||||
end
|
||||
|
||||
def hhcurveto
|
||||
if @stack.size % 2 == 1
|
||||
@y += @stack.shift
|
||||
end
|
||||
|
||||
until @stack.empty?
|
||||
c1x = @x + @stack.shift
|
||||
c1y = @y
|
||||
c2x = c1x + @stack.shift
|
||||
c2y = c1y + @stack.shift
|
||||
@x = c2x + @stack.shift
|
||||
@y = c2y
|
||||
@path.curve_to(c1x, c1y, c2x, c2y, @x, @y)
|
||||
end
|
||||
end
|
||||
# rubocop:enable Style/EvenOdd
|
||||
|
||||
def shortint
|
||||
b1, b2 = read_bytes(2)
|
||||
@stack << (((b1 << 24) | (b2 << 16)) >> 16)
|
||||
end
|
||||
|
||||
def callgsubr
|
||||
code_index = @stack.pop + @gsubrs_bias
|
||||
subr_code = @gsubrs[code_index].bytes
|
||||
@data[@index, 0] = subr_code
|
||||
end
|
||||
|
||||
def vhcurveto
|
||||
until @stack.empty?
|
||||
c1x = @x
|
||||
c1y = @y + @stack.shift
|
||||
c2x = c1x + @stack.shift
|
||||
c2y = c1y + @stack.shift
|
||||
@x = c2x + @stack.shift
|
||||
@y = c2y + (@stack.size == 1 ? @stack.shift : 0)
|
||||
@path.curve_to(c1x, c1y, c2x, c2y, @x, @y)
|
||||
|
||||
break if @stack.empty?
|
||||
|
||||
c1x = @x + @stack.shift
|
||||
c1y = @y
|
||||
c2x = c1x + @stack.shift
|
||||
c2y = c1y + @stack.shift
|
||||
@y = c2y + @stack.shift
|
||||
@x = c2x + (@stack.size == 1 ? @stack.shift : 0)
|
||||
@path.curve_to(c1x, c1y, c2x, c2y, @x, @y)
|
||||
end
|
||||
end
|
||||
|
||||
def hvcurveto
|
||||
until @stack.empty?
|
||||
c1x = @x + @stack.shift
|
||||
c1y = @y
|
||||
c2x = c1x + @stack.shift
|
||||
c2y = c1y + @stack.shift
|
||||
@y = c2y + @stack.shift
|
||||
@x = c2x + (@stack.size == 1 ? @stack.shift : 0)
|
||||
@path.curve_to(c1x, c1y, c2x, c2y, @x, @y)
|
||||
|
||||
break if @stack.empty?
|
||||
|
||||
c1x = @x
|
||||
c1y = @y + @stack.shift
|
||||
c2x = c1x + @stack.shift
|
||||
c2y = c1y + @stack.shift
|
||||
@x = c2x + @stack.shift
|
||||
@y = c2y + (@stack.size == 1 ? @stack.shift : 0)
|
||||
@path.curve_to(c1x, c1y, c2x, c2y, @x, @y)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
# CFF Charstrings Index.
|
||||
class CharstringsIndex < TTFunk::Table::Cff::Index
|
||||
# Top dict.
|
||||
# @return [TTFunk::Table::Cff::TopDict]
|
||||
attr_reader :top_dict
|
||||
|
||||
# @overload initialize(top_dict, file, offset, length = nil)
|
||||
# @param top_dict [TTFunk::Table:Cff::TopDict]
|
||||
# @param file [TTFunk::File]
|
||||
# @param offset [Integer]
|
||||
# @param length [Integer]
|
||||
def initialize(top_dict, *remaining_args)
|
||||
super(*remaining_args)
|
||||
@top_dict = top_dict
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def decode_item(index, _offset, _length)
|
||||
TTFunk::Table::Cff::Charstring.new(index, top_dict, font_dict_for(index), super)
|
||||
end
|
||||
|
||||
def encode_items(charmap)
|
||||
charmap
|
||||
.reject { |code, mapping| mapping[:new].zero? && !code.zero? }
|
||||
.sort_by { |_code, mapping| mapping[:new] }
|
||||
.map { |(_code, mapping)| items[mapping[:old]] }
|
||||
end
|
||||
|
||||
def font_dict_for(index)
|
||||
# only CID-keyed fonts contain an FD selector and font dicts
|
||||
if top_dict.is_cid_font?
|
||||
fd_index = top_dict.font_dict_selector[index]
|
||||
top_dict.font_index[fd_index]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,298 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'bigdecimal'
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
# CFF Dict.
|
||||
class Dict < TTFunk::SubTable
|
||||
# Indicates malformed operand.
|
||||
class InvalidOperandError < StandardError; end
|
||||
|
||||
# Indicates too many operands.
|
||||
class TooManyOperandsError < StandardError; end
|
||||
|
||||
# Single-byte operators.
|
||||
OPERATOR_BZERO = (0..21).freeze
|
||||
# Bytes indicating an operand.
|
||||
OPERAND_BZERO = [28..30, 32..254].freeze
|
||||
|
||||
# Two-byte operator
|
||||
WIDE_OPERATOR_BZERO = 12
|
||||
# Two-byte operator adjustment. Used for encoding and decoding of wide
|
||||
# operators.
|
||||
WIDE_OPERATOR_ADJUSTMENT = 1200
|
||||
|
||||
# Maximum number of operands allowed per operator.
|
||||
MAX_OPERANDS = 48
|
||||
|
||||
# Scientific notation operand significand validation regular
|
||||
# experession.
|
||||
VALID_SCI_SIGNIFICAND_RE = /\A-?(\.\d+|\d+|\d+\.\d+)\z/.freeze
|
||||
|
||||
# Scientific notation operand exponent validation regular experession.
|
||||
VALID_SCI_EXPONENT_RE = /\A-?\d+\z/.freeze
|
||||
|
||||
include Enumerable
|
||||
|
||||
# Get dict value by operator.
|
||||
#
|
||||
# @param operator [Integer]
|
||||
# @return [Array<Integer, TTFunk::SciForm>]
|
||||
def [](operator)
|
||||
@dict[operator]
|
||||
end
|
||||
|
||||
# Add dict entry.
|
||||
#
|
||||
# @param operator [Integer] Entry operator. Must be in range 0..255. Wide operators must be in range 1200..1455.
|
||||
# @param operands [Array<Integer, TTFunk::SciForm>]
|
||||
def []=(operator, *operands)
|
||||
@dict[operator] = Array(*operands)
|
||||
end
|
||||
|
||||
# Iterate over dict entries.
|
||||
#
|
||||
# @yieldparam key [Integer]
|
||||
# @yieldparam value [Array<Integer, TTFunk::SciForm>]
|
||||
# @return [void]
|
||||
def each(&block)
|
||||
@dict.each(&block)
|
||||
end
|
||||
|
||||
alias each_pair each
|
||||
|
||||
# Encode dict.
|
||||
#
|
||||
# @return [String]
|
||||
def encode
|
||||
sort_by(&:first)
|
||||
.map { |(operator, operands)|
|
||||
operands.map { |operand| encode_operand(operand) }.join +
|
||||
encode_operator(operator)
|
||||
}
|
||||
.join
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def encode_operator(operator)
|
||||
if operator >= WIDE_OPERATOR_ADJUSTMENT
|
||||
[
|
||||
WIDE_OPERATOR_BZERO,
|
||||
operator - WIDE_OPERATOR_ADJUSTMENT,
|
||||
].pack('C*')
|
||||
else
|
||||
[operator].pack('C')
|
||||
end
|
||||
end
|
||||
|
||||
def encode_operand(operand)
|
||||
case operand
|
||||
when Integer
|
||||
encode_integer(operand)
|
||||
when Float, BigDecimal
|
||||
encode_float(operand)
|
||||
when SciForm
|
||||
encode_sci(operand)
|
||||
end
|
||||
end
|
||||
|
||||
def encode_integer(int)
|
||||
case int
|
||||
when -107..107
|
||||
[int + 139].pack('C')
|
||||
|
||||
when 108..1131
|
||||
int -= 108
|
||||
[(int >> 8) + 247, int & 0xFF].pack('C*')
|
||||
|
||||
when -1131..-108
|
||||
int = -int - 108
|
||||
[(int >> 8) + 251, int & 0xFF].pack('C*')
|
||||
|
||||
when -32_768..32_767
|
||||
[28, (int >> 8) & 0xFF, int & 0xFF].pack('C*')
|
||||
|
||||
else
|
||||
encode_integer32(int)
|
||||
end
|
||||
end
|
||||
|
||||
def encode_integer32(int)
|
||||
[29, int].pack('CN')
|
||||
end
|
||||
|
||||
def encode_float(float)
|
||||
pack_decimal_nibbles(encode_significand(float))
|
||||
end
|
||||
|
||||
def encode_sci(sci)
|
||||
sig_bytes = encode_significand(sci.significand)
|
||||
exp_bytes = encode_exponent(sci.exponent)
|
||||
pack_decimal_nibbles(sig_bytes + exp_bytes)
|
||||
end
|
||||
|
||||
def encode_exponent(exp)
|
||||
return [] if exp.zero?
|
||||
|
||||
[exp.positive? ? 0xB : 0xC, *encode_significand(exp.abs)]
|
||||
end
|
||||
|
||||
def encode_significand(sig)
|
||||
sig.to_s.each_char.with_object([]) do |char, ret|
|
||||
case char
|
||||
when '0'..'9'
|
||||
ret << Integer(char)
|
||||
when '.'
|
||||
ret << 0xA
|
||||
when '-'
|
||||
ret << 0xE
|
||||
else
|
||||
break ret
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def pack_decimal_nibbles(nibbles)
|
||||
bytes = [30]
|
||||
|
||||
nibbles.each_slice(2).each do |(high_nb, low_nb)|
|
||||
# low_nb can be nil if nibbles contains an odd number of elements
|
||||
low_nb ||= 0xF
|
||||
bytes << ((high_nb << 4) | low_nb)
|
||||
end
|
||||
|
||||
bytes << 0xFF if nibbles.size.even?
|
||||
bytes.pack('C*')
|
||||
end
|
||||
|
||||
def parse!
|
||||
@dict = {}
|
||||
operands = []
|
||||
|
||||
# @length must be set via the constructor
|
||||
while io.pos < table_offset + length
|
||||
case b_zero = read(1, 'C').first
|
||||
when WIDE_OPERATOR_BZERO
|
||||
operator = decode_wide_operator
|
||||
@dict[operator] = operands
|
||||
operands = []
|
||||
when OPERATOR_BZERO
|
||||
@dict[b_zero] = operands unless operands.empty?
|
||||
operands = []
|
||||
when *OPERAND_BZERO
|
||||
operands << decode_operand(b_zero)
|
||||
|
||||
if operands.size > MAX_OPERANDS
|
||||
raise TooManyOperandsError,
|
||||
"found one too many operands at position #{io.pos} in dict at position #{table_offset}"
|
||||
end
|
||||
else
|
||||
raise Error, "dict byte value #{b_zero} is reserved"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def decode_wide_operator
|
||||
WIDE_OPERATOR_ADJUSTMENT + read(1, 'C').first
|
||||
end
|
||||
|
||||
def decode_operand(b_zero)
|
||||
case b_zero
|
||||
when 30
|
||||
decode_sci
|
||||
else
|
||||
decode_integer(b_zero)
|
||||
end
|
||||
end
|
||||
|
||||
def decode_sci
|
||||
significand = ''.b
|
||||
exponent = ''.b
|
||||
|
||||
loop do
|
||||
current = read(1, 'C').first
|
||||
break if current == 0xFF
|
||||
|
||||
high_nibble = current >> 4
|
||||
low_nibble = current & 0x0F # 0b00001111
|
||||
|
||||
[high_nibble, low_nibble].each do |nibble|
|
||||
case nibble
|
||||
when 0..9
|
||||
(exponent.empty? ? significand : exponent) << nibble.to_s
|
||||
when 0xA
|
||||
significand << '.'
|
||||
when 0xB
|
||||
# take advantage of Integer#to_i not caring about whitespace
|
||||
exponent << ' '
|
||||
when 0xC
|
||||
exponent << '-'
|
||||
when 0xE
|
||||
significand << '-'
|
||||
end
|
||||
end
|
||||
|
||||
break if low_nibble == 0xF
|
||||
end
|
||||
|
||||
validate_sci!(significand, exponent)
|
||||
|
||||
exponent = 0 if exponent.empty?
|
||||
|
||||
SciForm.new(Float(significand), Integer(exponent))
|
||||
end
|
||||
|
||||
def validate_sci!(significand, exponent)
|
||||
unless valid_significand?(significand) && valid_exponent?(exponent)
|
||||
raise InvalidOperandError,
|
||||
'invalid scientific notation operand with significand ' \
|
||||
"'#{significand}' and exponent '#{exponent}' ending at " \
|
||||
"position #{io.pos} in dict at position #{table_offset}"
|
||||
end
|
||||
end
|
||||
|
||||
def valid_significand?(significand)
|
||||
!(significand.strip =~ VALID_SCI_SIGNIFICAND_RE).nil?
|
||||
end
|
||||
|
||||
def valid_exponent?(exponent)
|
||||
exponent = exponent.strip
|
||||
return true if exponent.empty?
|
||||
|
||||
!(exponent.strip =~ VALID_SCI_EXPONENT_RE).nil?
|
||||
end
|
||||
|
||||
def decode_integer(b_zero)
|
||||
case b_zero
|
||||
when 32..246
|
||||
# 1 byte
|
||||
b_zero - 139
|
||||
|
||||
when 247..250
|
||||
# 2 bytes
|
||||
b_one = read(1, 'C').first
|
||||
((b_zero - 247) * 256) + b_one + 108
|
||||
|
||||
when 251..254
|
||||
# 2 bytes
|
||||
b_one = read(1, 'C').first
|
||||
(-(b_zero - 251) * 256) - b_one - 108
|
||||
|
||||
when 28
|
||||
# 2 bytes in number (3 total)
|
||||
b_one, b_two = read(2, 'C*')
|
||||
BinUtils.twos_comp_to_int((b_one << 8) | b_two, bit_width: 16)
|
||||
|
||||
when 29
|
||||
# 4 bytes in number (5 total)
|
||||
b_one, b_two, b_three, b_four = read(4, 'C*')
|
||||
BinUtils.twos_comp_to_int((b_one << 24) | (b_two << 16) | (b_three << 8) | b_four, bit_width: 32)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,277 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
# CFF Encoding.
|
||||
class Encoding < TTFunk::SubTable
|
||||
include Enumerable
|
||||
|
||||
# Predefined Standard Encoding ID.
|
||||
STANDARD_ENCODING_ID = 0
|
||||
|
||||
# Predefined Expert Encoding ID.
|
||||
EXPERT_ENCODING_ID = 1
|
||||
|
||||
# Default encoding ID.
|
||||
DEFAULT_ENCODING_ID = STANDARD_ENCODING_ID
|
||||
|
||||
class << self
|
||||
# Get predefined encoding by ID.
|
||||
#
|
||||
# @param encoding_id [Integer]
|
||||
# @return [TTFunk::OneBasedArray<Integer>]
|
||||
def codes_for_encoding_id(encoding_id)
|
||||
case encoding_id
|
||||
when STANDARD_ENCODING_ID
|
||||
Encodings::STANDARD
|
||||
when EXPERT_ENCODING_ID
|
||||
Encodings::EXPERT
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Top dict.
|
||||
# @return [TTFunk::Table::Cff::TopDict]
|
||||
attr_reader :top_dict
|
||||
|
||||
# Encodign format.
|
||||
# @return [Integer]
|
||||
attr_reader :format
|
||||
|
||||
# Number of encoded items.
|
||||
# @return [Integer]
|
||||
attr_reader :items_count
|
||||
|
||||
# Offset or encoding ID.
|
||||
# @return [Integer]
|
||||
attr_reader :offset_or_id
|
||||
|
||||
# @overload initialize(top_dict, file, offset = nil, length = nil)
|
||||
# @param top_dict [TTFunk::Table:Cff::TopDict]
|
||||
# @param file [TTFunk::File]
|
||||
# @param offset [Integer]
|
||||
# @param length [Integer]
|
||||
# @overload initialize(top_dict, file, charset_id)
|
||||
# @param top_dict [TTFunk::Table:Cff::TopDict]
|
||||
# @param file [TTFunk::File]
|
||||
# @param encoding_id [Integer] 0, 1, or 2
|
||||
def initialize(top_dict, file, offset_or_id = nil, length = nil)
|
||||
@top_dict = top_dict
|
||||
@offset_or_id = offset_or_id || DEFAULT_ENCODING_ID
|
||||
|
||||
if offset
|
||||
super(file, offset, length)
|
||||
@supplemental = format >> 7 == 1
|
||||
else
|
||||
@items_count = self.class.codes_for_encoding_id(offset_or_id).size
|
||||
@supplemental = false
|
||||
end
|
||||
end
|
||||
|
||||
# Iterate over character codes.
|
||||
#
|
||||
# @overload each()
|
||||
# @yieldparam code [Integer]
|
||||
# @return [void]
|
||||
# @overload each()
|
||||
# @return [Enumerator]
|
||||
def each
|
||||
return to_enum(__method__) unless block_given?
|
||||
|
||||
# +1 adjusts for the implicit .notdef glyph
|
||||
(items_count + 1).times { |i| yield(self[i]) }
|
||||
end
|
||||
|
||||
# Get character code for glyph index.
|
||||
#
|
||||
# @param glyph_id [Integer]
|
||||
# @return [Integer, nil]
|
||||
def [](glyph_id)
|
||||
return 0 if glyph_id.zero?
|
||||
return code_for(glyph_id) if offset
|
||||
|
||||
self.class.codes_for_encoding_id(offset_or_id)[glyph_id]
|
||||
end
|
||||
|
||||
# Encoding offset in the file.
|
||||
#
|
||||
# @return [Integer, nil]
|
||||
def offset
|
||||
# Numbers from 0..1 mean encoding IDs instead of offsets. IDs are
|
||||
# pre-defined, generic encodings that define the characters present
|
||||
# in the font.
|
||||
#
|
||||
# In the case of an offset, add the CFF table's offset since the
|
||||
# charset offset is relative to the start of the CFF table. Otherwise
|
||||
# return nil (no offset).
|
||||
if offset_or_id > 1
|
||||
offset_or_id + top_dict.cff_offset
|
||||
end
|
||||
end
|
||||
|
||||
# Encode encoding.
|
||||
#
|
||||
# @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 [String]
|
||||
def encode(charmap)
|
||||
# Any subset encoding is all but guaranteed to be different from the
|
||||
# standard encoding so we don't even attempt to see if it matches. We
|
||||
# assume it's different and just encode it anew.
|
||||
|
||||
return encode_supplemental(charmap) if supplemental?
|
||||
|
||||
codes =
|
||||
charmap
|
||||
.reject { |_code, mapping| mapping[:new].zero? }
|
||||
.sort_by { |_code, mapping| mapping[:new] }
|
||||
.map { |(code, _m)| code }
|
||||
|
||||
ranges = TTFunk::BinUtils.rangify(codes)
|
||||
|
||||
# calculate whether storing the charset as a series of ranges is
|
||||
# more efficient (i.e. takes up less space) vs storing it as an
|
||||
# array of SID values
|
||||
total_range_size = (2 * ranges.size) +
|
||||
(element_width(:range_format) * ranges.size)
|
||||
|
||||
total_array_size = codes.size * element_width(:array_format)
|
||||
|
||||
if total_array_size <= total_range_size
|
||||
([format_int(:array_format), codes.size] + codes).pack('C*')
|
||||
else
|
||||
element_fmt = element_format(:range_format)
|
||||
result = [format_int(:range_format), ranges.size].pack('CC')
|
||||
ranges.each { |range| result << range.pack(element_fmt) }
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
# Is this a supplemental encoding?
|
||||
#
|
||||
# @return [Boolean]
|
||||
def supplemental?
|
||||
# high-order bit set to 1 indicates supplemental encoding
|
||||
@supplemental
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def encode_supplemental(charmap)
|
||||
new_entries =
|
||||
charmap
|
||||
.reject { |_code, mapping| mapping[:new].zero? }
|
||||
.transform_values { |mapping| mapping[:new] }
|
||||
|
||||
result = [format_int(:supplemental), new_entries.size].pack('CC')
|
||||
fmt = element_format(:supplemental)
|
||||
|
||||
new_entries.each do |code, new_gid|
|
||||
result << [code, new_gid].pack(fmt)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def code_for(glyph_id)
|
||||
return 0 if glyph_id.zero?
|
||||
|
||||
# rather than validating the glyph as part of one of the predefined
|
||||
# encodings, just pass it through
|
||||
return glyph_id unless offset
|
||||
|
||||
case format_sym
|
||||
when :array_format, :supplemental
|
||||
@entries[glyph_id]
|
||||
|
||||
when :range_format
|
||||
remaining = glyph_id
|
||||
|
||||
@entries.each do |range|
|
||||
if range.size >= remaining
|
||||
return (range.first + remaining) - 1
|
||||
end
|
||||
|
||||
remaining -= range.size
|
||||
end
|
||||
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def parse!
|
||||
@format, entry_count = read(2, 'C*')
|
||||
@length = entry_count * element_width
|
||||
|
||||
case format_sym
|
||||
when :array_format
|
||||
@items_count = entry_count
|
||||
@entries = OneBasedArray.new(read(length, 'C*'))
|
||||
|
||||
when :range_format
|
||||
@entries = []
|
||||
@items_count = 0
|
||||
|
||||
entry_count.times do
|
||||
code, num_left = read(element_width, element_format)
|
||||
@entries << (code..(code + num_left))
|
||||
@items_count += num_left + 1
|
||||
end
|
||||
|
||||
when :supplemental
|
||||
@entries = {}
|
||||
@items_count = entry_count
|
||||
|
||||
entry_count.times do
|
||||
code, glyph = read(element_width, element_format)
|
||||
@entries[code] = glyph
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def element_format(fmt = format_sym)
|
||||
{
|
||||
array_format: 'C',
|
||||
range_format: 'CC',
|
||||
supplemental: 'Cn',
|
||||
}[fmt]
|
||||
end
|
||||
|
||||
# @TODO: handle supplemental encoding (necessary?)
|
||||
def element_width(fmt = format_sym)
|
||||
case fmt
|
||||
when :array_format then 1
|
||||
when :range_format then 2
|
||||
when :supplemental then 3
|
||||
else
|
||||
raise Error, "'#{fmt}' is an unsupported encoding format"
|
||||
end
|
||||
end
|
||||
|
||||
def format_sym
|
||||
return :supplemental if supplemental?
|
||||
|
||||
case @format
|
||||
when 0 then :array_format
|
||||
when 1 then :range_format
|
||||
else
|
||||
raise Error, "unsupported charset format '#{fmt}'"
|
||||
end
|
||||
end
|
||||
|
||||
def format_int(sym = format_sym)
|
||||
case sym
|
||||
when :array_format then 0
|
||||
when :range_format then 1
|
||||
when :supplemental then 129
|
||||
else
|
||||
raise Error, "unsupported charset format '#{sym}'"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
# Predefined encodings.
|
||||
module Encodings
|
||||
autoload :EXPERT, 'ttfunk/table/cff/encodings/expert'
|
||||
autoload :STANDARD, 'ttfunk/table/cff/encodings/standard'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,207 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
module Encodings
|
||||
# CFF predefined Expert encoding.
|
||||
EXPERT = OneBasedArray.new(
|
||||
[
|
||||
*[0] * 31,
|
||||
1,
|
||||
229,
|
||||
230,
|
||||
0,
|
||||
231,
|
||||
232,
|
||||
233,
|
||||
234,
|
||||
235,
|
||||
236,
|
||||
237,
|
||||
238,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
99,
|
||||
239,
|
||||
240,
|
||||
241,
|
||||
242,
|
||||
243,
|
||||
244,
|
||||
245,
|
||||
246,
|
||||
247,
|
||||
248,
|
||||
27,
|
||||
28,
|
||||
249,
|
||||
250,
|
||||
251,
|
||||
252,
|
||||
0,
|
||||
253,
|
||||
254,
|
||||
255,
|
||||
256,
|
||||
257,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
258,
|
||||
0,
|
||||
0,
|
||||
259,
|
||||
260,
|
||||
261,
|
||||
262,
|
||||
0,
|
||||
0,
|
||||
263,
|
||||
264,
|
||||
265,
|
||||
0,
|
||||
266,
|
||||
109,
|
||||
110,
|
||||
267,
|
||||
268,
|
||||
269,
|
||||
0,
|
||||
270,
|
||||
271,
|
||||
272,
|
||||
273,
|
||||
274,
|
||||
275,
|
||||
276,
|
||||
277,
|
||||
278,
|
||||
279,
|
||||
280,
|
||||
281,
|
||||
282,
|
||||
283,
|
||||
284,
|
||||
285,
|
||||
286,
|
||||
287,
|
||||
288,
|
||||
289,
|
||||
290,
|
||||
291,
|
||||
292,
|
||||
293,
|
||||
294,
|
||||
295,
|
||||
296,
|
||||
297,
|
||||
298,
|
||||
299,
|
||||
300,
|
||||
301,
|
||||
302,
|
||||
303,
|
||||
*[0] * 34,
|
||||
304,
|
||||
305,
|
||||
306,
|
||||
0,
|
||||
0,
|
||||
307,
|
||||
308,
|
||||
309,
|
||||
310,
|
||||
311,
|
||||
0,
|
||||
312,
|
||||
0,
|
||||
0,
|
||||
313,
|
||||
0,
|
||||
0,
|
||||
314,
|
||||
315,
|
||||
0,
|
||||
0,
|
||||
316,
|
||||
317,
|
||||
318,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
158,
|
||||
155,
|
||||
163,
|
||||
319,
|
||||
320,
|
||||
321,
|
||||
322,
|
||||
323,
|
||||
324,
|
||||
325,
|
||||
0,
|
||||
0,
|
||||
326,
|
||||
150,
|
||||
164,
|
||||
169,
|
||||
327,
|
||||
328,
|
||||
329,
|
||||
330,
|
||||
331,
|
||||
332,
|
||||
333,
|
||||
334,
|
||||
335,
|
||||
336,
|
||||
337,
|
||||
338,
|
||||
339,
|
||||
340,
|
||||
341,
|
||||
342,
|
||||
343,
|
||||
344,
|
||||
345,
|
||||
346,
|
||||
347,
|
||||
348,
|
||||
349,
|
||||
350,
|
||||
351,
|
||||
352,
|
||||
353,
|
||||
354,
|
||||
355,
|
||||
356,
|
||||
357,
|
||||
358,
|
||||
359,
|
||||
360,
|
||||
361,
|
||||
362,
|
||||
363,
|
||||
364,
|
||||
365,
|
||||
366,
|
||||
367,
|
||||
368,
|
||||
369,
|
||||
370,
|
||||
371,
|
||||
372,
|
||||
373,
|
||||
374,
|
||||
375,
|
||||
376,
|
||||
377,
|
||||
378,
|
||||
],
|
||||
).freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,182 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
module Encodings
|
||||
# CFF predefined Standard encoding.
|
||||
STANDARD = OneBasedArray.new(
|
||||
[
|
||||
*[0] * 31,
|
||||
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,
|
||||
*[0] * 34,
|
||||
96,
|
||||
97,
|
||||
98,
|
||||
99,
|
||||
100,
|
||||
101,
|
||||
102,
|
||||
103,
|
||||
104,
|
||||
105,
|
||||
106,
|
||||
107,
|
||||
108,
|
||||
109,
|
||||
110,
|
||||
0,
|
||||
111,
|
||||
112,
|
||||
113,
|
||||
114,
|
||||
0,
|
||||
115,
|
||||
116,
|
||||
117,
|
||||
118,
|
||||
119,
|
||||
120,
|
||||
121,
|
||||
122,
|
||||
0,
|
||||
123,
|
||||
0,
|
||||
124,
|
||||
125,
|
||||
126,
|
||||
127,
|
||||
128,
|
||||
129,
|
||||
130,
|
||||
131,
|
||||
0,
|
||||
132,
|
||||
133,
|
||||
0,
|
||||
134,
|
||||
135,
|
||||
136,
|
||||
137,
|
||||
*[0] * 16,
|
||||
138,
|
||||
0,
|
||||
139,
|
||||
*[0] * 4,
|
||||
140,
|
||||
141,
|
||||
142,
|
||||
143,
|
||||
*[0] * 5,
|
||||
144,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
145,
|
||||
0,
|
||||
0,
|
||||
146,
|
||||
147,
|
||||
148,
|
||||
149,
|
||||
*[0] * 4,
|
||||
],
|
||||
).freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,193 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
# CFF FDSelect.
|
||||
class FdSelector < TTFunk::SubTable
|
||||
include Enumerable
|
||||
|
||||
# Array format.
|
||||
ARRAY_FORMAT = 0
|
||||
|
||||
# Range format.
|
||||
RANGE_FORMAT = 3
|
||||
|
||||
# Range entry size.
|
||||
RANGE_ENTRY_SIZE = 3
|
||||
|
||||
# Array entry size.
|
||||
ARRAY_ENTRY_SIZE = 1
|
||||
|
||||
# Top dict.
|
||||
# @return [TTFunk::Table::Cff::TopDict]
|
||||
attr_reader :top_dict
|
||||
|
||||
# Number of encoded items.
|
||||
# @return [Integer]
|
||||
attr_reader :items_count
|
||||
|
||||
# Number of entries.
|
||||
# @return [Array<Integer>] if format is array.
|
||||
# @return [Array<Array(Range, Integer)>] if format is range.
|
||||
attr_reader :entries
|
||||
|
||||
# Number of glyphs.
|
||||
# @return [Integer]
|
||||
attr_reader :n_glyphs
|
||||
|
||||
# @param top_dict [TTFunk::Table:Cff::TopDict]
|
||||
# @param file [TTFunk::File]
|
||||
# @param offset [Integer]
|
||||
# @param length [Integer]
|
||||
def initialize(top_dict, file, offset, length = nil)
|
||||
@top_dict = top_dict
|
||||
super(file, offset, length)
|
||||
end
|
||||
|
||||
# Get font dict index for glyph ID.
|
||||
#
|
||||
# @return [Integer]
|
||||
def [](glyph_id)
|
||||
case format_sym
|
||||
when :array_format
|
||||
entries[glyph_id]
|
||||
|
||||
when :range_format
|
||||
if (entry = range_cache[glyph_id])
|
||||
return entry
|
||||
end
|
||||
|
||||
range, entry =
|
||||
entries.bsearch { |rng, _|
|
||||
if rng.cover?(glyph_id)
|
||||
0
|
||||
elsif glyph_id < rng.first
|
||||
-1
|
||||
else
|
||||
1
|
||||
end
|
||||
}
|
||||
|
||||
range.each { |i| range_cache[i] = entry }
|
||||
entry
|
||||
end
|
||||
end
|
||||
|
||||
# Iterate over font dicts for each glyph ID.
|
||||
#
|
||||
# @yieldparam [Integer] font dict index.
|
||||
# @return [void]
|
||||
def each
|
||||
return to_enum(__method__) unless block_given?
|
||||
|
||||
items_count.times { |i| yield(self[i]) }
|
||||
end
|
||||
|
||||
# Encode Font dict selector.
|
||||
#
|
||||
# @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 [String]
|
||||
def encode(charmap)
|
||||
# get list of [new_gid, fd_index] pairs
|
||||
new_indices =
|
||||
charmap
|
||||
.reject { |code, mapping| mapping[:new].zero? && !code.zero? }
|
||||
.sort_by { |_code, mapping| mapping[:new] }
|
||||
.map { |(_code, mapping)| [mapping[:new], self[mapping[:old]]] }
|
||||
|
||||
ranges = rangify_gids(new_indices)
|
||||
total_range_size = ranges.size * RANGE_ENTRY_SIZE
|
||||
total_array_size = new_indices.size * ARRAY_ENTRY_SIZE
|
||||
|
||||
''.b.tap do |result|
|
||||
if total_array_size <= total_range_size
|
||||
result << [ARRAY_FORMAT].pack('C')
|
||||
result << new_indices.map(&:last).pack('C*')
|
||||
else
|
||||
result << [RANGE_FORMAT, ranges.size].pack('Cn')
|
||||
ranges.each { |range| result << range.pack('nC') }
|
||||
|
||||
# "A sentinel GID follows the last range element and serves to
|
||||
# delimit the last range in the array. (The sentinel GID is set
|
||||
# equal to the number of glyphs in the font. That is, its value
|
||||
# is 1 greater than the last GID in the font)."
|
||||
result << [new_indices.size].pack('n')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def range_cache
|
||||
@range_cache ||= {}
|
||||
end
|
||||
|
||||
# values is an array of [new_gid, fd_index] pairs
|
||||
def rangify_gids(values)
|
||||
start_gid = 0
|
||||
|
||||
[].tap do |ranges|
|
||||
values.each_cons(2) do |(_, first_idx), (sec_gid, sec_idx)|
|
||||
if first_idx != sec_idx
|
||||
ranges << [start_gid, first_idx]
|
||||
start_gid = sec_gid
|
||||
end
|
||||
end
|
||||
|
||||
ranges << [start_gid, values.last[1]]
|
||||
end
|
||||
end
|
||||
|
||||
def parse!
|
||||
@format = read(1, 'C').first
|
||||
@length = 1
|
||||
|
||||
case format_sym
|
||||
when :array_format
|
||||
@n_glyphs = top_dict.charstrings_index.items_count
|
||||
data = io.read(n_glyphs)
|
||||
@length += data.bytesize
|
||||
@items_count = data.bytesize
|
||||
@entries = data.bytes
|
||||
|
||||
when :range_format
|
||||
# +2 for sentinel GID, +2 for num_ranges
|
||||
num_ranges = read(2, 'n').first
|
||||
@length += (num_ranges * RANGE_ENTRY_SIZE) + 4
|
||||
|
||||
ranges = Array.new(num_ranges) { read(RANGE_ENTRY_SIZE, 'nC') }
|
||||
|
||||
@entries =
|
||||
ranges.each_cons(2).map { |first, second|
|
||||
first_gid, fd_index = first
|
||||
second_gid, = second
|
||||
[(first_gid...second_gid), fd_index]
|
||||
}
|
||||
|
||||
# read the sentinel GID, otherwise known as the number of glyphs
|
||||
# in the font
|
||||
@n_glyphs = read(2, 'n').first
|
||||
|
||||
last_start_gid, last_fd_index = ranges.last
|
||||
@entries << [(last_start_gid...(n_glyphs + 1)), last_fd_index]
|
||||
|
||||
@items_count = entries.reduce(0) { |sum, entry| sum + entry.first.size }
|
||||
end
|
||||
end
|
||||
|
||||
def format_sym
|
||||
case @format
|
||||
when ARRAY_FORMAT then :array_format
|
||||
when RANGE_FORMAT then :range_format
|
||||
else
|
||||
raise Error, "unsupported fd select format '#{@format}'"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,91 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
# CFF Font dict.
|
||||
class FontDict < TTFunk::Table::Cff::Dict
|
||||
# Length of placeholders.
|
||||
PLACEHOLDER_LENGTH = 5
|
||||
|
||||
# Operators we care about in this dict.
|
||||
OPERATORS = { private: 18 }.freeze
|
||||
|
||||
# Inverse operator mapping.
|
||||
OPERATOR_CODES = OPERATORS.invert
|
||||
|
||||
# Top dict.
|
||||
# @return [TTFunk::Table::Cff::TopDict]
|
||||
attr_reader :top_dict
|
||||
|
||||
# @param top_dict [TTFunk::Table:Cff::TopDict]
|
||||
# @param file [TTFunk::File]
|
||||
# @param offset [Integer]
|
||||
# @param length [Integer]
|
||||
def initialize(top_dict, file, offset, length = nil)
|
||||
@top_dict = top_dict
|
||||
super(file, offset, length)
|
||||
end
|
||||
|
||||
# Encode dict.
|
||||
#
|
||||
# @return [TTFunk::EncodedString]
|
||||
def encode
|
||||
EncodedString.new do |result|
|
||||
each do |operator, operands|
|
||||
case OPERATOR_CODES[operator]
|
||||
when :private
|
||||
result << encode_private
|
||||
else
|
||||
operands.each { |operand| result << encode_operand(operand) }
|
||||
end
|
||||
|
||||
result << encode_operator(operator)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Finalize dict.
|
||||
#
|
||||
# @param new_cff_data [TTFunk::EncodedString]
|
||||
# @return [void]
|
||||
def finalize(new_cff_data)
|
||||
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
|
||||
|
||||
# Private 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,
|
||||
top_dict.cff_offset + private_dict_offset,
|
||||
private_dict_length,
|
||||
)
|
||||
end
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,42 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
# CFF Font Dict Index.
|
||||
class FontIndex < TTFunk::Table::Cff::Index
|
||||
# Top dict.
|
||||
# @return [TTFunk::Table::Cff::TopDict]
|
||||
attr_reader :top_dict
|
||||
|
||||
# @param top_dict [TTFunk::Table:Cff::TopDict]
|
||||
# @param file [TTFunk::File]
|
||||
# @param offset [Integer]
|
||||
# @param length [Integer]
|
||||
def initialize(top_dict, file, offset, length = nil)
|
||||
super(file, offset, length)
|
||||
@top_dict = top_dict
|
||||
end
|
||||
|
||||
# Finalize index.
|
||||
#
|
||||
# @param new_cff_data [TTFunk::EncodedString]
|
||||
# @return [void]
|
||||
def finalize(new_cff_data)
|
||||
each { |font_dict| font_dict.finalize(new_cff_data) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def decode_item(_index, offset, length)
|
||||
TTFunk::Table::Cff::FontDict.new(top_dict, file, offset, length)
|
||||
end
|
||||
|
||||
def encode_items(*)
|
||||
# Re-encode font dicts
|
||||
map(&:encode)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
# CFF Header.
|
||||
class Header < TTFunk::SubTable
|
||||
# CFF table major version.
|
||||
# @return [Integer]
|
||||
attr_reader :major
|
||||
|
||||
# CFF table minor version.
|
||||
# @return [Integer]
|
||||
attr_reader :minor
|
||||
|
||||
# Size of the header itself.
|
||||
# @return [Integer]
|
||||
attr_reader :header_size
|
||||
|
||||
# Size of all offsets from beginning of table.
|
||||
# @return [Integer]
|
||||
attr_reader :absolute_offset_size
|
||||
|
||||
# Length of header.
|
||||
#
|
||||
# @return [Integer]
|
||||
def length
|
||||
4
|
||||
end
|
||||
|
||||
# Encode header.
|
||||
#
|
||||
# @return [String]
|
||||
def encode
|
||||
[major, minor, header_size, absolute_offset_size].pack('C*')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse!
|
||||
@major, @minor, @header_size, @absolute_offset_size = read(4, 'C*')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,158 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
# CFF Index.
|
||||
class Index < TTFunk::SubTable
|
||||
include Enumerable
|
||||
|
||||
# Get value by index.
|
||||
#
|
||||
# @param index [Integer]
|
||||
# @return [any]
|
||||
def [](index)
|
||||
return if index >= items_count
|
||||
|
||||
entry_cache[index] ||=
|
||||
decode_item(
|
||||
index,
|
||||
data_reference_offset + offsets[index],
|
||||
offsets[index + 1] - offsets[index],
|
||||
)
|
||||
end
|
||||
|
||||
# Iterate over index items.
|
||||
#
|
||||
# @overload each()
|
||||
# @yieldparam item [any]
|
||||
# @return [void]
|
||||
# @overload each()
|
||||
# @return [Enumerator]
|
||||
def each(&block)
|
||||
return to_enum(__method__) unless block
|
||||
|
||||
items_count.times do |i|
|
||||
yield(self[i])
|
||||
end
|
||||
end
|
||||
|
||||
# Numer of items in this index.
|
||||
#
|
||||
# @return [Integer]
|
||||
def items_count
|
||||
items.length
|
||||
end
|
||||
|
||||
# Encode index.
|
||||
#
|
||||
# @param args all arguments are passed to `encode_item` method.
|
||||
# @return [TTFunk::EncodedString]
|
||||
def encode(*args)
|
||||
new_items = encode_items(*args)
|
||||
|
||||
if new_items.empty?
|
||||
return [0].pack('n')
|
||||
end
|
||||
|
||||
if new_items.length > 0xffff
|
||||
raise Error, 'Too many items in a CFF index'
|
||||
end
|
||||
|
||||
offsets_array =
|
||||
new_items
|
||||
.each_with_object([1]) { |item, offsets|
|
||||
offsets << (offsets.last + item.length)
|
||||
}
|
||||
|
||||
offset_size = (offsets_array.last.bit_length / 8.0).ceil
|
||||
|
||||
offsets_array.map! { |offset| encode_offset(offset, offset_size) }
|
||||
|
||||
EncodedString.new.concat(
|
||||
[new_items.length, offset_size].pack('nC'),
|
||||
*offsets_array,
|
||||
*new_items,
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :items
|
||||
attr_reader :offsets
|
||||
attr_reader :data_reference_offset
|
||||
|
||||
def entry_cache
|
||||
@entry_cache ||= {}
|
||||
end
|
||||
|
||||
# Returns an array of EncodedString elements (plain strings,
|
||||
# placeholders, or EncodedString instances). Each element is supposed to
|
||||
# represent an encoded item.
|
||||
#
|
||||
# This is the place to do all the filtering, reordering, or individual
|
||||
# item encoding.
|
||||
#
|
||||
# It gets all the arguments `encode` gets.
|
||||
def encode_items(*)
|
||||
items
|
||||
end
|
||||
|
||||
# By default do nothing
|
||||
def decode_item(index, _offset, _length)
|
||||
items[index]
|
||||
end
|
||||
|
||||
def encode_offset(offset, offset_size)
|
||||
case offset_size
|
||||
when 1
|
||||
[offset].pack('C')
|
||||
when 2
|
||||
[offset].pack('n')
|
||||
when 3
|
||||
[offset].pack('N')[1..]
|
||||
when 4
|
||||
[offset].pack('N')
|
||||
end
|
||||
end
|
||||
|
||||
def parse!
|
||||
@entry_cache = {}
|
||||
|
||||
num_entries = read(2, 'n').first
|
||||
|
||||
if num_entries.zero?
|
||||
@length = 2
|
||||
@items = []
|
||||
return
|
||||
end
|
||||
|
||||
offset_size = read(1, 'C').first
|
||||
|
||||
@offsets =
|
||||
Array.new(num_entries + 1) {
|
||||
unpack_offset(io.read(offset_size), offset_size)
|
||||
}
|
||||
|
||||
@data_reference_offset = table_offset + 3 + (offsets.length * offset_size) - 1
|
||||
|
||||
@length =
|
||||
2 + # num entries
|
||||
1 + # offset size
|
||||
(offsets.length * offset_size) + # offsets
|
||||
offsets.last - 1 # items
|
||||
|
||||
@items =
|
||||
offsets.each_cons(2).map { |offset, next_offset|
|
||||
io.read(next_offset - offset)
|
||||
}
|
||||
end
|
||||
|
||||
def unpack_offset(offset_data, offset_size)
|
||||
padding = "\x00" * (4 - offset_size)
|
||||
(padding + offset_data).unpack1('N')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'forwardable'
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
# CFF Index with indexing starting at 1.
|
||||
class OneBasedIndex
|
||||
extend Forwardable
|
||||
|
||||
def_delegators :base_index,
|
||||
:each,
|
||||
:table_offset,
|
||||
:items_count,
|
||||
:length,
|
||||
:encode
|
||||
|
||||
# Underlaying Index.
|
||||
# @return [TTFunk::Table::Cff::Index]
|
||||
attr_reader :base_index
|
||||
|
||||
# @param args [Array] all params are passed to the base index.
|
||||
# @see Index
|
||||
def initialize(*args)
|
||||
@base_index = Index.new(*args)
|
||||
end
|
||||
|
||||
# Get item by index.
|
||||
#
|
||||
# @param idx [Integer]
|
||||
# @return [any]
|
||||
# @raise [IndexError] when requested index is 0.
|
||||
def [](idx)
|
||||
if idx.zero?
|
||||
raise IndexError,
|
||||
"index #{idx} was outside the bounds of the index"
|
||||
end
|
||||
|
||||
base_index[idx - 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,107 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
# Path. Mostly used for CFF glyph outlines.
|
||||
class Path
|
||||
# Close path command.
|
||||
CLOSE_PATH_CMD = [:close].freeze
|
||||
|
||||
# Commands in this path.
|
||||
# @return [Array]
|
||||
attr_reader :commands
|
||||
|
||||
# Number of contours in this path.
|
||||
# @return [Integer]
|
||||
attr_reader :number_of_contours
|
||||
|
||||
def initialize
|
||||
@commands = []
|
||||
@number_of_contours = 0
|
||||
end
|
||||
|
||||
# Move implicit cursor to coordinates.
|
||||
#
|
||||
# @param x [Integer, Float]
|
||||
# @param y [Integer, Float]
|
||||
# @return [void]
|
||||
def move_to(x, y)
|
||||
@commands << [:move, x, y]
|
||||
end
|
||||
|
||||
# Add a line to coordinates.
|
||||
#
|
||||
# @param x [Integer, Float]
|
||||
# @param y [Integer, Float]
|
||||
# @return [void]
|
||||
def line_to(x, y)
|
||||
@commands << [:line, x, y]
|
||||
end
|
||||
|
||||
# Add a Bézier curve. Current position is the first control point, (`x1`,
|
||||
# `y1`) is the second, (`x2`, `y2`) is the third, and (`x`, `y`) is the
|
||||
# last control point.
|
||||
#
|
||||
# @param x1 [Integer, Float]
|
||||
# @param y1 [Integer, Float]
|
||||
# @param x2 [Integer, Float]
|
||||
# @param y2 [Integer, Float]
|
||||
# @param x [Integer, Float]
|
||||
# @param y [Integer, Float]
|
||||
# @return [void]
|
||||
def curve_to(x1, y1, x2, y2, x, y) # rubocop: disable Metrics/ParameterLists
|
||||
@commands << [:curve, x1, y1, x2, y2, x, y]
|
||||
end
|
||||
|
||||
# Close current contour.
|
||||
#
|
||||
# @return [void]
|
||||
def close_path
|
||||
@commands << CLOSE_PATH_CMD
|
||||
@number_of_contours += 1
|
||||
end
|
||||
|
||||
# Reposition and scale path.
|
||||
#
|
||||
# @param x [Integer, Float] new horizontal position.
|
||||
# @param y [Integer, Float] new vertical position.
|
||||
# @param font_size [Integer, Float] font size.
|
||||
# @param units_per_em [Integer] units per Em as defined in the font.
|
||||
# @return [TTFunk::Table::Cff::Path]
|
||||
def render(x: 0, y: 0, font_size: 72, units_per_em: 1000)
|
||||
new_path = self.class.new
|
||||
scale = 1.0 / units_per_em * font_size
|
||||
|
||||
commands.each do |cmd|
|
||||
case cmd[:type]
|
||||
when :move
|
||||
new_path.move_to(x + (cmd[1] * scale), y + (-cmd[2] * scale))
|
||||
when :line
|
||||
new_path.line_to(x + (cmd[1] * scale), y + (-cmd[2] * scale))
|
||||
when :curve
|
||||
new_path.curve_to(
|
||||
x + (cmd[1] * scale),
|
||||
y + (-cmd[2] * scale),
|
||||
x + (cmd[3] * scale),
|
||||
y + (-cmd[4] * scale),
|
||||
x + (cmd[5] * scale),
|
||||
y + (-cmd[6] * scale),
|
||||
)
|
||||
when :close
|
||||
new_path.close_path
|
||||
end
|
||||
end
|
||||
|
||||
new_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def format_values(command)
|
||||
command[1..].map { |k| format('%.2f', k) }.join(' ')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,104 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
# CFF Private dict.
|
||||
class PrivateDict < TTFunk::Table::Cff::Dict
|
||||
# Default value of Default Width X.
|
||||
DEFAULT_WIDTH_X_DEFAULT = 0
|
||||
|
||||
# Default value of Nominal Width X.
|
||||
DEFAULT_WIDTH_X_NOMINAL = 0
|
||||
|
||||
# Length of placeholders.
|
||||
PLACEHOLDER_LENGTH = 5
|
||||
|
||||
# Operators we care about in this dict.
|
||||
OPERATORS = {
|
||||
subrs: 19,
|
||||
default_width_x: 20,
|
||||
nominal_width_x: 21,
|
||||
}.freeze
|
||||
|
||||
# Inverse operator mapping.
|
||||
OPERATOR_CODES = OPERATORS.invert
|
||||
|
||||
# Encode dict.
|
||||
#
|
||||
# @return [TTFunk::EncodedString]
|
||||
def encode
|
||||
# TODO: use mapping to determine which subroutines are still used.
|
||||
# For now, just encode them all.
|
||||
EncodedString.new do |result|
|
||||
each do |operator, operands|
|
||||
case OPERATOR_CODES[operator]
|
||||
when :subrs
|
||||
result << encode_subrs
|
||||
else
|
||||
operands.each { |operand| result << encode_operand(operand) }
|
||||
end
|
||||
|
||||
result << encode_operator(operator)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Finalize dict.
|
||||
#
|
||||
# @param private_dict_data [TTFunk::EncodedString]
|
||||
# @return [void]
|
||||
def finalize(private_dict_data)
|
||||
return unless subr_index
|
||||
|
||||
encoded_subr_index = subr_index.encode
|
||||
encoded_offset = encode_integer32(private_dict_data.length)
|
||||
|
||||
private_dict_data.resolve_placeholder(:"subrs_#{@table_offset}", encoded_offset)
|
||||
|
||||
private_dict_data << encoded_subr_index
|
||||
end
|
||||
|
||||
# Subroutine index.
|
||||
#
|
||||
# @return [TTFunk::Table::Cff::SubrIndex, nil]
|
||||
def subr_index
|
||||
@subr_index ||=
|
||||
if (subr_offset = self[OPERATORS[:subrs]])
|
||||
SubrIndex.new(file, table_offset + subr_offset.first)
|
||||
end
|
||||
end
|
||||
|
||||
# Default Width X.
|
||||
#
|
||||
# @return [Integer]
|
||||
def default_width_x
|
||||
if (width = self[OPERATORS[:default_width_x]])
|
||||
width.first
|
||||
else
|
||||
DEFAULT_WIDTH_X_DEFAULT
|
||||
end
|
||||
end
|
||||
|
||||
# Nominal Width X.
|
||||
#
|
||||
# @return [Integer]
|
||||
def nominal_width_x
|
||||
if (width = self[OPERATORS[:nominal_width_x]])
|
||||
width.first
|
||||
else
|
||||
DEFAULT_WIDTH_X_NOMINAL
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def encode_subrs
|
||||
EncodedString.new do |result|
|
||||
result << Placeholder.new(:"subrs_#{@table_offset}", length: PLACEHOLDER_LENGTH)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
# CFF Subroutine index.
|
||||
class SubrIndex < TTFunk::Table::Cff::Index
|
||||
# Subroutine index biase. For correct subroutine selection the
|
||||
# calculated bias must be added to the subroutine number operand before
|
||||
# accessing the index.
|
||||
# @return [Integer]
|
||||
def bias
|
||||
if items.length < 1240
|
||||
107
|
||||
elsif items.length < 33_900
|
||||
1131
|
||||
else
|
||||
32_768
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TTFunk
|
||||
class Table
|
||||
class Cff < TTFunk::Table
|
||||
# CFF Index conatining Top dict.
|
||||
class TopIndex < TTFunk::Table::Cff::Index
|
||||
private
|
||||
|
||||
def decode_item(_index, offset, length)
|
||||
TTFunk::Table::Cff::TopDict.new(file, offset, length)
|
||||
end
|
||||
|
||||
def encode_items(*)
|
||||
# Re-encode the top dict
|
||||
map(&:encode)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user