ttfunk 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/CHANGELOG.md +60 -0
  5. data/README.md +2 -1
  6. data/lib/ttfunk.rb +45 -0
  7. data/lib/ttfunk/aggregate.rb +15 -0
  8. data/lib/ttfunk/bin_utils.rb +47 -0
  9. data/lib/ttfunk/bit_field.rb +31 -0
  10. data/lib/ttfunk/collection.rb +3 -1
  11. data/lib/ttfunk/directory.rb +6 -0
  12. data/lib/ttfunk/encoded_string.rb +97 -0
  13. data/lib/ttfunk/max.rb +25 -0
  14. data/lib/ttfunk/min.rb +25 -0
  15. data/lib/ttfunk/one_based_array.rb +36 -0
  16. data/lib/ttfunk/otf_encoder.rb +61 -0
  17. data/lib/ttfunk/placeholder.rb +13 -0
  18. data/lib/ttfunk/reader.rb +34 -32
  19. data/lib/ttfunk/resource_file.rb +7 -5
  20. data/lib/ttfunk/sci_form.rb +29 -0
  21. data/lib/ttfunk/sub_table.rb +38 -0
  22. data/lib/ttfunk/subset.rb +2 -0
  23. data/lib/ttfunk/subset/base.rb +61 -120
  24. data/lib/ttfunk/subset/code_page.rb +89 -0
  25. data/lib/ttfunk/subset/mac_roman.rb +5 -42
  26. data/lib/ttfunk/subset/unicode.rb +12 -6
  27. data/lib/ttfunk/subset/unicode_8bit.rb +14 -12
  28. data/lib/ttfunk/subset/windows_1252.rb +5 -47
  29. data/lib/ttfunk/subset_collection.rb +4 -0
  30. data/lib/ttfunk/sum.rb +20 -0
  31. data/lib/ttfunk/table.rb +4 -0
  32. data/lib/ttfunk/table/cff.rb +69 -0
  33. data/lib/ttfunk/table/cff/charset.rb +212 -0
  34. data/lib/ttfunk/table/cff/charsets.rb +14 -0
  35. data/lib/ttfunk/table/cff/charsets/expert.rb +189 -0
  36. data/lib/ttfunk/table/cff/charsets/expert_subset.rb +119 -0
  37. data/lib/ttfunk/table/cff/charsets/iso_adobe.rb +241 -0
  38. data/lib/ttfunk/table/cff/charsets/standard_strings.rb +404 -0
  39. data/lib/ttfunk/table/cff/charstring.rb +487 -0
  40. data/lib/ttfunk/table/cff/charstrings_index.rb +39 -0
  41. data/lib/ttfunk/table/cff/dict.rb +266 -0
  42. data/lib/ttfunk/table/cff/encoding.rb +220 -0
  43. data/lib/ttfunk/table/cff/encodings.rb +12 -0
  44. data/lib/ttfunk/table/cff/encodings/expert.rb +206 -0
  45. data/lib/ttfunk/table/cff/encodings/standard.rb +181 -0
  46. data/lib/ttfunk/table/cff/fd_selector.rb +150 -0
  47. data/lib/ttfunk/table/cff/font_dict.rb +79 -0
  48. data/lib/ttfunk/table/cff/font_index.rb +29 -0
  49. data/lib/ttfunk/table/cff/header.rb +33 -0
  50. data/lib/ttfunk/table/cff/index.rb +125 -0
  51. data/lib/ttfunk/table/cff/one_based_index.rb +31 -0
  52. data/lib/ttfunk/table/cff/path.rb +66 -0
  53. data/lib/ttfunk/table/cff/private_dict.rb +84 -0
  54. data/lib/ttfunk/table/cff/subr_index.rb +19 -0
  55. data/lib/ttfunk/table/cff/top_dict.rb +230 -0
  56. data/lib/ttfunk/table/cff/top_index.rb +16 -0
  57. data/lib/ttfunk/table/cmap.rb +4 -4
  58. data/lib/ttfunk/table/cmap/format00.rb +1 -2
  59. data/lib/ttfunk/table/cmap/format04.rb +11 -3
  60. data/lib/ttfunk/table/cmap/format06.rb +2 -0
  61. data/lib/ttfunk/table/cmap/format10.rb +2 -0
  62. data/lib/ttfunk/table/cmap/format12.rb +2 -0
  63. data/lib/ttfunk/table/cmap/subtable.rb +12 -8
  64. data/lib/ttfunk/table/dsig.rb +50 -0
  65. data/lib/ttfunk/table/glyf.rb +11 -9
  66. data/lib/ttfunk/table/glyf/compound.rb +14 -7
  67. data/lib/ttfunk/table/glyf/path_based.rb +47 -0
  68. data/lib/ttfunk/table/glyf/simple.rb +21 -15
  69. data/lib/ttfunk/table/head.rb +43 -5
  70. data/lib/ttfunk/table/hhea.rb +47 -4
  71. data/lib/ttfunk/table/hmtx.rb +11 -4
  72. data/lib/ttfunk/table/kern.rb +3 -0
  73. data/lib/ttfunk/table/kern/format0.rb +3 -0
  74. data/lib/ttfunk/table/loca.rb +2 -0
  75. data/lib/ttfunk/table/maxp.rb +144 -10
  76. data/lib/ttfunk/table/name.rb +75 -37
  77. data/lib/ttfunk/table/os2.rb +327 -4
  78. data/lib/ttfunk/table/post.rb +8 -1
  79. data/lib/ttfunk/table/post/format10.rb +2 -0
  80. data/lib/ttfunk/table/post/format20.rb +5 -1
  81. data/lib/ttfunk/table/post/format30.rb +2 -0
  82. data/lib/ttfunk/table/post/format40.rb +2 -0
  83. data/lib/ttfunk/table/sbix.rb +2 -0
  84. data/lib/ttfunk/table/simple.rb +2 -0
  85. data/lib/ttfunk/table/vorg.rb +54 -0
  86. data/lib/ttfunk/ttf_encoder.rb +220 -0
  87. metadata +88 -20
  88. metadata.gz.sig +0 -0
  89. data/lib/ttfunk/encoding/mac_roman.rb +0 -100
  90. data/lib/ttfunk/encoding/windows_1252.rb +0 -76
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ require_relative 'base'
6
+
7
+ module TTFunk
8
+ module Subset
9
+ class CodePage < Base
10
+ class << self
11
+ def unicode_mapping_for(encoding)
12
+ mapping_cache[encoding] ||= (0..255).each_with_object({}) do |c, ret|
13
+ # rubocop:disable Lint/SuppressedException
14
+ begin
15
+ ret[c] = c.chr(encoding)
16
+ .encode(Encoding::UTF_8)
17
+ .codepoints
18
+ .first
19
+ rescue Encoding::UndefinedConversionError
20
+ # There is not a strict 1:1 mapping between all code page
21
+ # characters and unicode.
22
+ end
23
+ # rubocop:enable Lint/SuppressedException
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def mapping_cache
30
+ @mapping_cache ||= {}
31
+ end
32
+ end
33
+
34
+ attr_reader :code_page, :encoding
35
+
36
+ def initialize(original, code_page, encoding)
37
+ super(original)
38
+ @code_page = code_page
39
+ @encoding = encoding
40
+ @subset = Array.new(256)
41
+ use(space_char_code)
42
+ end
43
+
44
+ def to_unicode_map
45
+ self.class.unicode_mapping_for(encoding)
46
+ end
47
+
48
+ def use(character)
49
+ @subset[from_unicode(character)] = character
50
+ end
51
+
52
+ def covers?(character)
53
+ !from_unicode(character).nil?
54
+ end
55
+
56
+ def includes?(character)
57
+ code = from_unicode(character)
58
+ code && @subset[code]
59
+ end
60
+
61
+ def from_unicode(character)
62
+ [character].pack('U*').encode(encoding).ord
63
+ rescue Encoding::UndefinedConversionError
64
+ nil
65
+ end
66
+
67
+ def new_cmap_table
68
+ @new_cmap_table ||= begin
69
+ mapping = {}
70
+
71
+ @subset.each_with_index do |unicode, roman|
72
+ mapping[roman] = unicode_cmap[unicode]
73
+ end
74
+
75
+ TTFunk::Table::Cmap.encode(mapping, :mac_roman)
76
+ end
77
+ end
78
+
79
+ def original_glyph_ids
80
+ ([0] + @subset.map { |unicode| unicode && unicode_cmap[unicode] })
81
+ .compact.uniq.sort
82
+ end
83
+
84
+ def space_char_code
85
+ @space_char_code ||= from_unicode(Unicode::SPACE_CHAR)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -1,51 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
 
3
- require_relative 'base'
4
- require_relative '../encoding/mac_roman'
5
+ require_relative 'code_page'
5
6
 
6
7
  module TTFunk
7
8
  module Subset
8
- class MacRoman < Base
9
+ class MacRoman < CodePage
9
10
  def initialize(original)
10
- super
11
- @subset = Array.new(256)
12
- end
13
-
14
- def to_unicode_map
15
- Encoding::MacRoman::TO_UNICODE
16
- end
17
-
18
- def use(character)
19
- @subset[Encoding::MacRoman::FROM_UNICODE[character]] = character
20
- end
21
-
22
- def covers?(character)
23
- Encoding::MacRoman.covers?(character)
24
- end
25
-
26
- def includes?(character)
27
- code = Encoding::MacRoman::FROM_UNICODE[character]
28
- code && @subset[code]
29
- end
30
-
31
- def from_unicode(character)
32
- Encoding::MacRoman::FROM_UNICODE[character]
33
- end
34
-
35
- protected
36
-
37
- def new_cmap_table(_options)
38
- mapping = {}
39
- @subset.each_with_index do |unicode, roman|
40
- mapping[roman] = unicode_cmap[unicode] if roman
41
- end
42
-
43
- TTFunk::Table::Cmap.encode(mapping, :mac_roman)
44
- end
45
-
46
- def original_glyph_ids
47
- ([0] + @subset.map { |unicode| unicode && unicode_cmap[unicode] })
48
- .compact.uniq.sort
11
+ super(original, 10_000, Encoding::MACROMAN)
49
12
  end
50
13
  end
51
14
  end
@@ -1,12 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
  require_relative 'base'
3
5
 
4
6
  module TTFunk
5
7
  module Subset
6
8
  class Unicode < Base
9
+ SPACE_CHAR = 0x20
10
+
7
11
  def initialize(original)
8
12
  super
9
13
  @subset = Set.new
14
+ use(SPACE_CHAR)
10
15
  end
11
16
 
12
17
  def unicode?
@@ -26,20 +31,21 @@ module TTFunk
26
31
  end
27
32
 
28
33
  def includes?(character)
29
- @subset.includes(character)
34
+ @subset.include?(character)
30
35
  end
31
36
 
32
37
  def from_unicode(character)
33
38
  character
34
39
  end
35
40
 
36
- protected
41
+ def new_cmap_table
42
+ @new_cmap_table ||= begin
43
+ mapping = @subset.each_with_object({}) do |code, map|
44
+ map[code] = unicode_cmap[code]
45
+ end
37
46
 
38
- def new_cmap_table(_options)
39
- mapping = @subset.each_with_object({}) do |code, map|
40
- map[code] = unicode_cmap[code]
47
+ TTFunk::Table::Cmap.encode(mapping, :unicode)
41
48
  end
42
- TTFunk::Table::Cmap.encode(mapping, :unicode)
43
49
  end
44
50
 
45
51
  def original_glyph_ids
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
  require_relative 'base'
3
5
 
@@ -39,20 +41,20 @@ module TTFunk
39
41
  @unicodes[character]
40
42
  end
41
43
 
42
- protected
44
+ def new_cmap_table
45
+ @new_cmap_table ||= begin
46
+ mapping = @subset.each_with_object({}) do |(code, unicode), map|
47
+ map[code] = unicode_cmap[unicode]
48
+ map
49
+ end
43
50
 
44
- def new_cmap_table(_options)
45
- mapping = @subset.each_with_object({}) do |(code, unicode), map|
46
- map[code] = unicode_cmap[unicode]
47
- map
51
+ # since we're mapping a subset of the unicode glyphs into an
52
+ # arbitrary 256-character space, the actual encoding we're
53
+ # using is irrelevant. We choose MacRoman because it's a 256-character
54
+ # encoding that happens to be well-supported in both TTF and
55
+ # PDF formats.
56
+ TTFunk::Table::Cmap.encode(mapping, :mac_roman)
48
57
  end
49
-
50
- # since we're mapping a subset of the unicode glyphs into an
51
- # arbitrary 256-character space, the actual encoding we're
52
- # using is irrelevant. We choose MacRoman because it's a 256-character
53
- # encoding that happens to be well-supported in both TTF and
54
- # PDF formats.
55
- TTFunk::Table::Cmap.encode(mapping, :mac_roman)
56
58
  end
57
59
 
58
60
  def original_glyph_ids
@@ -1,56 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
 
3
- require_relative 'base'
4
- require_relative '../encoding/windows_1252'
5
+ require_relative 'code_page'
5
6
 
6
7
  module TTFunk
7
8
  module Subset
8
- class Windows1252 < Base
9
+ class Windows1252 < CodePage
9
10
  def initialize(original)
10
- super
11
- @subset = Array.new(256)
12
- end
13
-
14
- def to_unicode_map
15
- Encoding::Windows1252::TO_UNICODE
16
- end
17
-
18
- def use(character)
19
- @subset[Encoding::Windows1252::FROM_UNICODE[character]] = character
20
- end
21
-
22
- def covers?(character)
23
- Encoding::Windows1252.covers?(character)
24
- end
25
-
26
- def includes?(character)
27
- code = Encoding::Windows1252::FROM_UNICODE[character]
28
- code && @subset[code]
29
- end
30
-
31
- def from_unicode(character)
32
- Encoding::Windows1252::FROM_UNICODE[character]
33
- end
34
-
35
- protected
36
-
37
- def new_cmap_table(_options)
38
- mapping = {}
39
- @subset.each_with_index do |unicode, cp1252|
40
- mapping[cp1252] = unicode_cmap[unicode] if cp1252
41
- end
42
-
43
- # yes, I really mean "mac roman". TTF has no cp1252 encoding, and the
44
- # alternative would be to encode it using a format 4 unicode table,
45
- # which is overkill. for our purposes, mac-roman suffices. (If we were
46
- # building a _real_ font, instead of a PDF-embeddable subset, things
47
- # would probably be different.)
48
- TTFunk::Table::Cmap.encode(mapping, :mac_roman)
49
- end
50
-
51
- def original_glyph_ids
52
- ([0] + @subset.map { |unicode| unicode && unicode_cmap[unicode] })
53
- .compact.uniq.sort
11
+ super(original, 1252, Encoding::CP1252)
54
12
  end
55
13
  end
56
14
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'subset'
2
4
 
3
5
  module TTFunk
@@ -17,6 +19,7 @@ module TTFunk
17
19
  covered = false
18
20
  @subsets.each_with_index do |subset, _i|
19
21
  next unless subset.covers?(char)
22
+
20
23
  subset.use(char)
21
24
  covered = true
22
25
  break
@@ -65,6 +68,7 @@ module TTFunk
65
68
 
66
69
  current_char += 1
67
70
  return parts if current_char >= characters.length
71
+
68
72
  char = characters[current_char]
69
73
  end
70
74
 
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ class Sum < Aggregate
5
+ attr_reader :value
6
+
7
+ def initialize(init_value = 0)
8
+ @value = init_value
9
+ end
10
+
11
+ def <<(operand)
12
+ @value += coerce(operand)
13
+ end
14
+
15
+ def value_or(_default)
16
+ # value should always be non-nil
17
+ value
18
+ end
19
+ end
20
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'reader'
2
4
 
3
5
  module TTFunk
@@ -10,6 +12,8 @@ module TTFunk
10
12
 
11
13
  def initialize(file)
12
14
  @file = file
15
+ @offset = nil
16
+ @length = nil
13
17
 
14
18
  info = file.directory_info(tag)
15
19
 
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ class Table
5
+ class Cff < TTFunk::Table
6
+ autoload :Charset, 'ttfunk/table/cff/charset'
7
+ autoload :Charsets, 'ttfunk/table/cff/charsets'
8
+ autoload :Charstring, 'ttfunk/table/cff/charstring'
9
+ autoload :CharstringsIndex, 'ttfunk/table/cff/charstrings_index'
10
+ autoload :Dict, 'ttfunk/table/cff/dict'
11
+ autoload :Encoding, 'ttfunk/table/cff/encoding'
12
+ autoload :Encodings, 'ttfunk/table/cff/encodings'
13
+ autoload :FdSelector, 'ttfunk/table/cff/fd_selector'
14
+ autoload :FontDict, 'ttfunk/table/cff/font_dict'
15
+ autoload :FontIndex, 'ttfunk/table/cff/font_index'
16
+ autoload :Header, 'ttfunk/table/cff/header'
17
+ autoload :Index, 'ttfunk/table/cff/index'
18
+ autoload :OneBasedIndex, 'ttfunk/table/cff/one_based_index'
19
+ autoload :Path, 'ttfunk/table/cff/path'
20
+ autoload :PrivateDict, 'ttfunk/table/cff/private_dict'
21
+ autoload :SubrIndex, 'ttfunk/table/cff/subr_index'
22
+ autoload :TopDict, 'ttfunk/table/cff/top_dict'
23
+ autoload :TopIndex, 'ttfunk/table/cff/top_index'
24
+
25
+ TAG = 'CFF ' # the extra space is important
26
+
27
+ attr_reader :header, :name_index, :top_index, :string_index
28
+ attr_reader :global_subr_index
29
+
30
+ def tag
31
+ TAG
32
+ end
33
+
34
+ def encode(new_to_old, old_to_new)
35
+ EncodedString.new do |result|
36
+ sub_tables = [
37
+ header.encode,
38
+ name_index.encode,
39
+ top_index.encode(&:encode),
40
+ string_index.encode,
41
+ global_subr_index.encode
42
+ ]
43
+
44
+ sub_tables.each { |tb| result << tb }
45
+ top_index[0].finalize(result, new_to_old, old_to_new)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def parse!
52
+ @header = Header.new(file, offset)
53
+ @name_index = Index.new(file, @header.table_offset + @header.length)
54
+
55
+ @top_index = TopIndex.new(
56
+ file, @name_index.table_offset + @name_index.length
57
+ )
58
+
59
+ @string_index = OneBasedIndex.new(
60
+ file, @top_index.table_offset + @top_index.length
61
+ )
62
+
63
+ @global_subr_index = SubrIndex.new(
64
+ file, @string_index.table_offset + @string_index.length
65
+ )
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ class Table
5
+ class Cff < TTFunk::Table
6
+ class Charset < TTFunk::SubTable
7
+ include Enumerable
8
+
9
+ FIRST_GLYPH_STRING = '.notdef'
10
+ ARRAY_FORMAT = 0
11
+ RANGE_FORMAT_8 = 1
12
+ RANGE_FORMAT_16 = 2
13
+
14
+ ISO_ADOBE_CHARSET_ID = 0
15
+ EXPERT_CHARSET_ID = 1
16
+ EXPERT_SUBSET_CHARSET_ID = 2
17
+
18
+ DEFAULT_CHARSET_ID = ISO_ADOBE_CHARSET_ID
19
+
20
+ class << self
21
+ def standard_strings
22
+ Charsets::STANDARD_STRINGS
23
+ end
24
+
25
+ def strings_for_charset_id(charset_id)
26
+ case charset_id
27
+ when ISO_ADOBE_CHARSET_ID
28
+ Charsets::ISO_ADOBE
29
+ when EXPERT_CHARSET_ID
30
+ Charsets::EXPERT
31
+ when EXPERT_SUBSET_CHARSET_ID
32
+ Charsets::EXPERT_SUBSET
33
+ end
34
+ end
35
+ end
36
+
37
+ attr_reader :entries, :length
38
+ attr_reader :top_dict, :format, :count, :offset_or_id
39
+
40
+ def initialize(top_dict, file, offset_or_id = nil, length = nil)
41
+ @top_dict = top_dict
42
+ @offset_or_id = offset_or_id || DEFAULT_CHARSET_ID
43
+
44
+ if offset
45
+ super(file, offset, length)
46
+ else
47
+ @count = self.class.strings_for_charset_id(offset_or_id).size
48
+ end
49
+ end
50
+
51
+ def each
52
+ return to_enum(__method__) unless block_given?
53
+
54
+ # +1 adjusts for the implicit .notdef glyph
55
+ (count + 1).times { |i| yield self[i] }
56
+ end
57
+
58
+ def [](glyph_id)
59
+ return FIRST_GLYPH_STRING if glyph_id == 0
60
+
61
+ find_string(sid_for(glyph_id))
62
+ end
63
+
64
+ def offset
65
+ # Numbers from 0..2 mean charset IDs instead of offsets. IDs are
66
+ # basically pre-defined sets of characters.
67
+ #
68
+ # In the case of an offset, add the CFF table's offset since the
69
+ # charset offset is relative to the start of the CFF table. Otherwise
70
+ # return nil (no offset).
71
+ if offset_or_id > 2
72
+ offset_or_id + top_dict.cff_offset
73
+ end
74
+ end
75
+
76
+ # mapping is new -> old glyph ids
77
+ def encode(mapping)
78
+ # no offset means no charset was specified (i.e. we're supposed to
79
+ # use a predefined charset) so there's nothing to encode
80
+ return '' unless offset
81
+
82
+ sids = mapping.keys.sort.map { |new_gid| sid_for(mapping[new_gid]) }
83
+ ranges = TTFunk::BinUtils.rangify(sids)
84
+ range_max = ranges.map(&:last).max
85
+
86
+ range_bytes = if range_max > 0
87
+ (Math.log2(range_max) / 8).floor + 1
88
+ else
89
+ # for cases when there are no sequences at all
90
+ Float::INFINITY
91
+ end
92
+
93
+ # calculate whether storing the charset as a series of ranges is
94
+ # more efficient (i.e. takes up less space) vs storing it as an
95
+ # array of SID values
96
+ total_range_size = (2 * ranges.size) + (range_bytes * ranges.size)
97
+ total_array_size = sids.size * element_width(:array_format)
98
+
99
+ if total_array_size <= total_range_size
100
+ ([format_int(:array_format)] + sids).pack('Cn*')
101
+ else
102
+ fmt = range_bytes == 1 ? :range_format_8 : :range_format_16
103
+ element_fmt = element_format(fmt)
104
+ result = [format_int(fmt)].pack('C')
105
+ ranges.each { |range| result << range.pack(element_fmt) }
106
+ result
107
+ end
108
+ end
109
+
110
+ private
111
+
112
+ def sid_for(glyph_id)
113
+ return 0 if glyph_id == 0
114
+
115
+ # rather than validating the glyph as part of one of the predefined
116
+ # charsets, just pass it through
117
+ return glyph_id unless offset
118
+
119
+ case format_sym
120
+ when :array_format
121
+ entries[glyph_id]
122
+
123
+ when :range_format_8, :range_format_16
124
+ entries.inject(glyph_id) do |remaining, range|
125
+ if range.size >= remaining
126
+ break (range.first + remaining) - 1
127
+ end
128
+
129
+ remaining - range.size
130
+ end
131
+ end
132
+ end
133
+
134
+ def find_string(sid)
135
+ if offset
136
+ return self.class.standard_strings[sid] if sid <= 390
137
+
138
+ idx = sid - 390
139
+
140
+ if idx < file.cff.string_index.count
141
+ file.cff.string_index[idx]
142
+ end
143
+ else
144
+ self.class.strings_for_charset_id(offset_or_id)[sid]
145
+ end
146
+ end
147
+
148
+ def parse!
149
+ return unless offset
150
+
151
+ @format = read(1, 'C').first
152
+
153
+ case format_sym
154
+ when :array_format
155
+ @count = top_dict.charstrings_index.count - 1
156
+ @length = count * element_width
157
+ @entries = OneBasedArray.new(read(length, 'n*'))
158
+
159
+ when :range_format_8, :range_format_16
160
+ # The number of ranges is not explicitly specified in the font.
161
+ # Instead, software utilizing this data simply processes ranges
162
+ # until all glyphs in the font are covered.
163
+ @count = 0
164
+ @entries = []
165
+ @length = 0
166
+
167
+ until count >= top_dict.charstrings_index.count - 1
168
+ @length += 1 + element_width
169
+ sid, num_left = read(element_width, element_format)
170
+ entries << (sid..(sid + num_left))
171
+ @count += num_left + 1
172
+ end
173
+ end
174
+ end
175
+
176
+ def element_width(fmt = format_sym)
177
+ case fmt
178
+ when :array_format then 2 # SID
179
+ when :range_format_8 then 3 # SID + Card8
180
+ when :range_format_16 then 4 # SID + Card16
181
+ end
182
+ end
183
+
184
+ def element_format(fmt = format_sym)
185
+ case fmt
186
+ when :array_format then 'n'
187
+ when :range_format_8 then 'nC'
188
+ when :range_format_16 then 'nn'
189
+ end
190
+ end
191
+
192
+ def format_sym
193
+ case @format
194
+ when ARRAY_FORMAT then :array_format
195
+ when RANGE_FORMAT_8 then :range_format_8
196
+ when RANGE_FORMAT_16 then :range_format_16
197
+ else
198
+ raise "unsupported charset format '#{fmt}'"
199
+ end
200
+ end
201
+
202
+ def format_int(sym = format_sym)
203
+ case sym
204
+ when :array_format then ARRAY_FORMAT
205
+ when :range_format_8 then RANGE_FORMAT_8
206
+ when :range_format_16 then RANGE_FORMAT_16
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end