ttfunk 1.5.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
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