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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/CHANGELOG.md +60 -0
- data/README.md +2 -1
- data/lib/ttfunk.rb +45 -0
- data/lib/ttfunk/aggregate.rb +15 -0
- data/lib/ttfunk/bin_utils.rb +47 -0
- data/lib/ttfunk/bit_field.rb +31 -0
- data/lib/ttfunk/collection.rb +3 -1
- data/lib/ttfunk/directory.rb +6 -0
- data/lib/ttfunk/encoded_string.rb +97 -0
- data/lib/ttfunk/max.rb +25 -0
- data/lib/ttfunk/min.rb +25 -0
- data/lib/ttfunk/one_based_array.rb +36 -0
- data/lib/ttfunk/otf_encoder.rb +61 -0
- data/lib/ttfunk/placeholder.rb +13 -0
- data/lib/ttfunk/reader.rb +34 -32
- data/lib/ttfunk/resource_file.rb +7 -5
- data/lib/ttfunk/sci_form.rb +29 -0
- data/lib/ttfunk/sub_table.rb +38 -0
- data/lib/ttfunk/subset.rb +2 -0
- data/lib/ttfunk/subset/base.rb +61 -120
- data/lib/ttfunk/subset/code_page.rb +89 -0
- data/lib/ttfunk/subset/mac_roman.rb +5 -42
- data/lib/ttfunk/subset/unicode.rb +12 -6
- data/lib/ttfunk/subset/unicode_8bit.rb +14 -12
- data/lib/ttfunk/subset/windows_1252.rb +5 -47
- data/lib/ttfunk/subset_collection.rb +4 -0
- data/lib/ttfunk/sum.rb +20 -0
- data/lib/ttfunk/table.rb +4 -0
- data/lib/ttfunk/table/cff.rb +69 -0
- data/lib/ttfunk/table/cff/charset.rb +212 -0
- data/lib/ttfunk/table/cff/charsets.rb +14 -0
- data/lib/ttfunk/table/cff/charsets/expert.rb +189 -0
- data/lib/ttfunk/table/cff/charsets/expert_subset.rb +119 -0
- data/lib/ttfunk/table/cff/charsets/iso_adobe.rb +241 -0
- data/lib/ttfunk/table/cff/charsets/standard_strings.rb +404 -0
- data/lib/ttfunk/table/cff/charstring.rb +487 -0
- data/lib/ttfunk/table/cff/charstrings_index.rb +39 -0
- data/lib/ttfunk/table/cff/dict.rb +266 -0
- data/lib/ttfunk/table/cff/encoding.rb +220 -0
- data/lib/ttfunk/table/cff/encodings.rb +12 -0
- data/lib/ttfunk/table/cff/encodings/expert.rb +206 -0
- data/lib/ttfunk/table/cff/encodings/standard.rb +181 -0
- data/lib/ttfunk/table/cff/fd_selector.rb +150 -0
- data/lib/ttfunk/table/cff/font_dict.rb +79 -0
- data/lib/ttfunk/table/cff/font_index.rb +29 -0
- data/lib/ttfunk/table/cff/header.rb +33 -0
- data/lib/ttfunk/table/cff/index.rb +125 -0
- data/lib/ttfunk/table/cff/one_based_index.rb +31 -0
- data/lib/ttfunk/table/cff/path.rb +66 -0
- data/lib/ttfunk/table/cff/private_dict.rb +84 -0
- data/lib/ttfunk/table/cff/subr_index.rb +19 -0
- data/lib/ttfunk/table/cff/top_dict.rb +230 -0
- data/lib/ttfunk/table/cff/top_index.rb +16 -0
- data/lib/ttfunk/table/cmap.rb +4 -4
- data/lib/ttfunk/table/cmap/format00.rb +1 -2
- data/lib/ttfunk/table/cmap/format04.rb +11 -3
- data/lib/ttfunk/table/cmap/format06.rb +2 -0
- data/lib/ttfunk/table/cmap/format10.rb +2 -0
- data/lib/ttfunk/table/cmap/format12.rb +2 -0
- data/lib/ttfunk/table/cmap/subtable.rb +12 -8
- data/lib/ttfunk/table/dsig.rb +50 -0
- data/lib/ttfunk/table/glyf.rb +11 -9
- data/lib/ttfunk/table/glyf/compound.rb +14 -7
- data/lib/ttfunk/table/glyf/path_based.rb +47 -0
- data/lib/ttfunk/table/glyf/simple.rb +21 -15
- data/lib/ttfunk/table/head.rb +43 -5
- data/lib/ttfunk/table/hhea.rb +47 -4
- data/lib/ttfunk/table/hmtx.rb +11 -4
- data/lib/ttfunk/table/kern.rb +3 -0
- data/lib/ttfunk/table/kern/format0.rb +3 -0
- data/lib/ttfunk/table/loca.rb +2 -0
- data/lib/ttfunk/table/maxp.rb +144 -10
- data/lib/ttfunk/table/name.rb +75 -37
- data/lib/ttfunk/table/os2.rb +327 -4
- data/lib/ttfunk/table/post.rb +8 -1
- data/lib/ttfunk/table/post/format10.rb +2 -0
- data/lib/ttfunk/table/post/format20.rb +5 -1
- data/lib/ttfunk/table/post/format30.rb +2 -0
- data/lib/ttfunk/table/post/format40.rb +2 -0
- data/lib/ttfunk/table/sbix.rb +2 -0
- data/lib/ttfunk/table/simple.rb +2 -0
- data/lib/ttfunk/table/vorg.rb +54 -0
- data/lib/ttfunk/ttf_encoder.rb +220 -0
- metadata +88 -20
- metadata.gz.sig +0 -0
- data/lib/ttfunk/encoding/mac_roman.rb +0 -100
- 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 '
|
4
|
-
require_relative '../encoding/mac_roman'
|
5
|
+
require_relative 'code_page'
|
5
6
|
|
6
7
|
module TTFunk
|
7
8
|
module Subset
|
8
|
-
class MacRoman <
|
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.
|
34
|
+
@subset.include?(character)
|
30
35
|
end
|
31
36
|
|
32
37
|
def from_unicode(character)
|
33
38
|
character
|
34
39
|
end
|
35
40
|
|
36
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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 '
|
4
|
-
require_relative '../encoding/windows_1252'
|
5
|
+
require_relative 'code_page'
|
5
6
|
|
6
7
|
module TTFunk
|
7
8
|
module Subset
|
8
|
-
class Windows1252 <
|
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
|
|
data/lib/ttfunk/sum.rb
ADDED
@@ -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
|
data/lib/ttfunk/table.rb
CHANGED
@@ -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
|