ttfunk 1.4.0 → 1.5.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/CHANGELOG.md +114 -0
- data/{README.rdoc → README.md} +12 -8
- data/lib/ttfunk.rb +27 -21
- data/lib/ttfunk/collection.rb +41 -0
- data/lib/ttfunk/directory.rb +10 -4
- data/lib/ttfunk/encoding/mac_roman.rb +50 -40
- data/lib/ttfunk/encoding/windows_1252.rb +28 -22
- data/lib/ttfunk/reader.rb +11 -10
- data/lib/ttfunk/resource_file.rb +28 -21
- data/lib/ttfunk/subset.rb +5 -5
- data/lib/ttfunk/subset/base.rb +66 -38
- data/lib/ttfunk/subset/mac_roman.rb +11 -10
- data/lib/ttfunk/subset/unicode.rb +10 -8
- data/lib/ttfunk/subset/unicode_8bit.rb +16 -16
- data/lib/ttfunk/subset/windows_1252.rb +16 -15
- data/lib/ttfunk/subset_collection.rb +7 -8
- data/lib/ttfunk/table.rb +3 -5
- data/lib/ttfunk/table/cmap.rb +13 -13
- data/lib/ttfunk/table/cmap/format00.rb +8 -10
- data/lib/ttfunk/table/cmap/format04.rb +37 -32
- data/lib/ttfunk/table/cmap/format06.rb +16 -16
- data/lib/ttfunk/table/cmap/format10.rb +22 -17
- data/lib/ttfunk/table/cmap/format12.rb +28 -22
- data/lib/ttfunk/table/cmap/subtable.rb +30 -23
- data/lib/ttfunk/table/glyf.rb +13 -11
- data/lib/ttfunk/table/glyf/compound.rb +13 -10
- data/lib/ttfunk/table/glyf/simple.rb +5 -4
- data/lib/ttfunk/table/head.rb +13 -13
- data/lib/ttfunk/table/hhea.rb +13 -13
- data/lib/ttfunk/table/hmtx.rb +23 -18
- data/lib/ttfunk/table/kern.rb +57 -48
- data/lib/ttfunk/table/kern/format0.rb +18 -10
- data/lib/ttfunk/table/loca.rb +9 -9
- data/lib/ttfunk/table/maxp.rb +9 -9
- data/lib/ttfunk/table/name.rb +65 -56
- data/lib/ttfunk/table/os2.rb +21 -21
- data/lib/ttfunk/table/post.rb +32 -32
- data/lib/ttfunk/table/post/format10.rb +33 -27
- data/lib/ttfunk/table/post/format20.rb +10 -10
- data/lib/ttfunk/table/post/format30.rb +5 -5
- data/lib/ttfunk/table/post/format40.rb +3 -3
- data/lib/ttfunk/table/sbix.rb +19 -10
- metadata +55 -32
- metadata.gz.sig +0 -0
- data/CHANGELOG +0 -5
- data/data/fonts/DejaVuSans.ttf +0 -0
- data/examples/metrics.rb +0 -45
@@ -1,7 +1,6 @@
|
|
1
1
|
module TTFunk
|
2
2
|
class Table
|
3
3
|
class Cmap
|
4
|
-
|
5
4
|
module Format12
|
6
5
|
attr_reader :language
|
7
6
|
attr_reader :code_map
|
@@ -9,14 +8,16 @@ module TTFunk
|
|
9
8
|
def self.encode(charmap)
|
10
9
|
next_id = 0
|
11
10
|
glyph_map = { 0 => 0 }
|
12
|
-
range_firstglyphs
|
11
|
+
range_firstglyphs = []
|
12
|
+
range_firstcodes = []
|
13
|
+
range_lengths = []
|
13
14
|
last_glyph = last_code = -999
|
14
15
|
|
15
|
-
new_map = charmap.keys.sort.
|
16
|
+
new_map = charmap.keys.sort.each_with_object({}) do |code, map|
|
16
17
|
glyph_map[charmap[code]] ||= next_id += 1
|
17
|
-
map[code] = { :
|
18
|
+
map[code] = { old: charmap[code], new: glyph_map[charmap[code]] }
|
18
19
|
|
19
|
-
if code > last_code+1 || glyph_map[charmap[code]] > last_glyph+1
|
20
|
+
if code > last_code + 1 || glyph_map[charmap[code]] > last_glyph + 1
|
20
21
|
range_firstcodes << code
|
21
22
|
range_firstglyphs << glyph_map[charmap[code]]
|
22
23
|
range_lengths << 1
|
@@ -25,17 +26,20 @@ module TTFunk
|
|
25
26
|
end
|
26
27
|
last_code = code
|
27
28
|
last_glyph = glyph_map[charmap[code]]
|
28
|
-
|
29
|
-
map
|
30
29
|
end
|
31
30
|
|
32
|
-
subtable = [
|
31
|
+
subtable = [
|
32
|
+
12, 0, 16 + 12 * range_lengths.size, 0, range_lengths.size
|
33
|
+
].pack('nnNNN')
|
33
34
|
range_lengths.each_with_index do |length, i|
|
34
|
-
firstglyph
|
35
|
-
|
35
|
+
firstglyph = range_firstglyphs[i]
|
36
|
+
firstcode = range_firstcodes[i]
|
37
|
+
subtable << [
|
38
|
+
firstcode, firstcode + length - 1, firstglyph
|
39
|
+
].pack('NNN')
|
36
40
|
end
|
37
41
|
|
38
|
-
{ :
|
42
|
+
{ charmap: new_map, subtable: subtable, max_glyph_id: next_id + 1 }
|
39
43
|
end
|
40
44
|
|
41
45
|
def [](code)
|
@@ -47,20 +51,22 @@ module TTFunk
|
|
47
51
|
end
|
48
52
|
|
49
53
|
private
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
54
|
+
|
55
|
+
def parse_cmap!
|
56
|
+
fractional_version, @language, groupcount = read(14, 'nx4NN')
|
57
|
+
if fractional_version != 0
|
58
|
+
raise NotImplementedError,
|
59
|
+
"cmap version 12.#{fractional_version} is not supported"
|
60
|
+
end
|
61
|
+
@code_map = {}
|
62
|
+
(1..groupcount).each do
|
63
|
+
startchar, endchar, startglyph = read(12, 'NNN')
|
64
|
+
(0..(endchar - startchar)).each do |offset|
|
65
|
+
@code_map[startchar + offset] = startglyph + offset
|
59
66
|
end
|
60
67
|
end
|
61
|
-
|
68
|
+
end
|
62
69
|
end
|
63
|
-
|
64
70
|
end
|
65
71
|
end
|
66
72
|
end
|
@@ -11,11 +11,12 @@ module TTFunk
|
|
11
11
|
attr_reader :format
|
12
12
|
|
13
13
|
ENCODING_MAPPINGS = {
|
14
|
-
:
|
15
|
-
#
|
16
|
-
|
17
|
-
:
|
18
|
-
|
14
|
+
mac_roman: { platform_id: 1, encoding_id: 0 }.freeze,
|
15
|
+
# Use microsoft unicode, instead of generic unicode, for optimal
|
16
|
+
# Windows support
|
17
|
+
unicode: { platform_id: 3, encoding_id: 1 }.freeze,
|
18
|
+
unicode_ucs4: { platform_id: 3, encoding_id: 10 }.freeze
|
19
|
+
}.freeze
|
19
20
|
|
20
21
|
def self.encode(charmap, encoding)
|
21
22
|
case encoding
|
@@ -26,32 +27,37 @@ module TTFunk
|
|
26
27
|
when :unicode_ucs4
|
27
28
|
result = Format12.encode(charmap)
|
28
29
|
else
|
29
|
-
raise NotImplementedError,
|
30
|
+
raise NotImplementedError,
|
31
|
+
"encoding #{encoding.inspect} is not supported"
|
30
32
|
end
|
31
33
|
|
32
34
|
mapping = ENCODING_MAPPINGS[encoding]
|
33
35
|
|
34
36
|
# platform-id, encoding-id, offset
|
35
|
-
result[:subtable] = [
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
result[:subtable] = [
|
38
|
+
mapping[:platform_id],
|
39
|
+
mapping[:encoding_id],
|
40
|
+
12,
|
41
|
+
result[:subtable]
|
42
|
+
].pack('nnNA*')
|
43
|
+
|
44
|
+
result
|
39
45
|
end
|
40
46
|
|
41
47
|
def initialize(file, table_start)
|
42
48
|
@file = file
|
43
|
-
@platform_id, @encoding_id, @offset = read(8,
|
49
|
+
@platform_id, @encoding_id, @offset = read(8, 'nnN')
|
44
50
|
@offset += table_start
|
45
51
|
|
46
52
|
parse_from(@offset) do
|
47
|
-
@format = read(2,
|
53
|
+
@format = read(2, 'n').first
|
48
54
|
|
49
55
|
case @format
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
56
|
+
when 0 then extend(TTFunk::Table::Cmap::Format00)
|
57
|
+
when 4 then extend(TTFunk::Table::Cmap::Format04)
|
58
|
+
when 6 then extend(TTFunk::Table::Cmap::Format06)
|
59
|
+
when 10 then extend(TTFunk::Table::Cmap::Format10)
|
60
|
+
when 12 then extend(TTFunk::Table::Cmap::Format12)
|
55
61
|
end
|
56
62
|
|
57
63
|
parse_cmap!
|
@@ -59,23 +65,24 @@ module TTFunk
|
|
59
65
|
end
|
60
66
|
|
61
67
|
def unicode?
|
62
|
-
platform_id == 3 && (encoding_id == 1 || encoding_id == 10) &&
|
63
|
-
|
68
|
+
platform_id == 3 && (encoding_id == 1 || encoding_id == 10) &&
|
69
|
+
format != 0 ||
|
70
|
+
platform_id == 0 && format != 0
|
64
71
|
end
|
65
72
|
|
66
73
|
def supported?
|
67
74
|
false
|
68
75
|
end
|
69
76
|
|
70
|
-
def [](
|
77
|
+
def [](_code)
|
71
78
|
raise NotImplementedError, "cmap format #{@format} is not supported"
|
72
79
|
end
|
73
80
|
|
74
81
|
private
|
75
82
|
|
76
|
-
|
77
|
-
|
78
|
-
|
83
|
+
def parse_cmap!
|
84
|
+
# do nothing...
|
85
|
+
end
|
79
86
|
end
|
80
87
|
end
|
81
88
|
end
|
data/lib/ttfunk/table/glyf.rb
CHANGED
@@ -7,12 +7,12 @@ module TTFunk
|
|
7
7
|
# mapping old glyph-ids to new glyph-ids.
|
8
8
|
#
|
9
9
|
# Returns a hash containing:
|
10
|
-
#
|
10
|
+
#
|
11
11
|
# * :table - a string representing the encoded 'glyf' table containing
|
12
12
|
# the given glyphs.
|
13
13
|
# * :offsets - an array of offsets for each glyph
|
14
14
|
def self.encode(glyphs, new2old, old2new)
|
15
|
-
result = { :
|
15
|
+
result = { table: '', offsets: [] }
|
16
16
|
|
17
17
|
new2old.keys.sort.each do |new_id|
|
18
18
|
glyph = glyphs[new2old[new_id]]
|
@@ -23,7 +23,7 @@ module TTFunk
|
|
23
23
|
# include an offset at the end of the table, for use in computing the
|
24
24
|
# size of the last glyph
|
25
25
|
result[:offsets] << result[:table].length
|
26
|
-
|
26
|
+
result
|
27
27
|
end
|
28
28
|
|
29
29
|
def for(glyph_id)
|
@@ -32,16 +32,18 @@ module TTFunk
|
|
32
32
|
index = file.glyph_locations.index_of(glyph_id)
|
33
33
|
size = file.glyph_locations.size_of(glyph_id)
|
34
34
|
|
35
|
-
if size
|
35
|
+
if size == 0 # blank glyph, e.g. space character
|
36
36
|
@cache[glyph_id] = nil
|
37
37
|
return nil
|
38
38
|
end
|
39
39
|
|
40
40
|
parse_from(offset + index) do
|
41
41
|
raw = io.read(size)
|
42
|
-
number_of_contours, x_min, y_min, x_max, y_max =
|
42
|
+
number_of_contours, x_min, y_min, x_max, y_max =
|
43
|
+
raw.unpack('n5').map { |i| to_signed(i) }
|
43
44
|
|
44
|
-
@cache[glyph_id] =
|
45
|
+
@cache[glyph_id] =
|
46
|
+
if number_of_contours == -1
|
45
47
|
Compound.new(raw, x_min, y_min, x_max, y_max)
|
46
48
|
else
|
47
49
|
Simple.new(raw, number_of_contours, x_min, y_min, x_max, y_max)
|
@@ -51,11 +53,11 @@ module TTFunk
|
|
51
53
|
|
52
54
|
private
|
53
55
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
56
|
+
def parse!
|
57
|
+
# because the glyf table is rather complex to parse, we defer
|
58
|
+
# the parse until we need a specific glyf, and then cache it.
|
59
|
+
@cache = {}
|
60
|
+
end
|
59
61
|
end
|
60
62
|
end
|
61
63
|
end
|
@@ -21,7 +21,10 @@ module TTFunk
|
|
21
21
|
|
22
22
|
def initialize(raw, x_min, y_min, x_max, y_max)
|
23
23
|
@raw = raw
|
24
|
-
@x_min
|
24
|
+
@x_min = x_min
|
25
|
+
@y_min = y_min
|
26
|
+
@x_max = x_max
|
27
|
+
@y_max = y_max
|
25
28
|
|
26
29
|
# Because TTFunk only cares about glyphs insofar as they (1) provide
|
27
30
|
# a bounding box for each glyph, and (2) can be rewritten into a
|
@@ -37,18 +40,19 @@ module TTFunk
|
|
37
40
|
offset = 10 # 2 bytes for each of num-contours, min x/y, max x/y
|
38
41
|
|
39
42
|
loop do
|
40
|
-
flags, glyph_id = @raw[offset, 4].unpack(
|
43
|
+
flags, glyph_id = @raw[offset, 4].unpack('n*')
|
41
44
|
@glyph_ids << glyph_id
|
42
45
|
@glyph_id_offsets << offset + 2
|
43
46
|
|
44
47
|
break unless flags & MORE_COMPONENTS != 0
|
45
48
|
offset += 4
|
46
49
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
50
|
+
offset +=
|
51
|
+
if flags & ARG_1_AND_2_ARE_WORDS != 0
|
52
|
+
4
|
53
|
+
else
|
54
|
+
2
|
55
|
+
end
|
52
56
|
|
53
57
|
if flags & WE_HAVE_A_TWO_BY_TWO != 0
|
54
58
|
offset += 8
|
@@ -69,13 +73,12 @@ module TTFunk
|
|
69
73
|
new_ids = glyph_ids.map { |id| mapping[id] }
|
70
74
|
|
71
75
|
new_ids.zip(@glyph_id_offsets).each do |new_id, offset|
|
72
|
-
result[offset, 2] = [new_id].pack(
|
76
|
+
result[offset, 2] = [new_id].pack('n')
|
73
77
|
end
|
74
78
|
|
75
|
-
|
79
|
+
result
|
76
80
|
end
|
77
81
|
end
|
78
82
|
end
|
79
83
|
end
|
80
84
|
end
|
81
|
-
|
@@ -11,8 +11,10 @@ module TTFunk
|
|
11
11
|
def initialize(raw, number_of_contours, x_min, y_min, x_max, y_max)
|
12
12
|
@raw = raw
|
13
13
|
@number_of_contours = number_of_contours
|
14
|
-
@x_min
|
15
|
-
@
|
14
|
+
@x_min = x_min
|
15
|
+
@y_min = y_min
|
16
|
+
@x_max = x_max
|
17
|
+
@y_max = y_max
|
16
18
|
|
17
19
|
# Because TTFunk is, at this time, a library for simply pulling
|
18
20
|
# metrics out of font files, or for writing font subsets, we don't
|
@@ -27,11 +29,10 @@ module TTFunk
|
|
27
29
|
false
|
28
30
|
end
|
29
31
|
|
30
|
-
def recode(
|
32
|
+
def recode(_mapping)
|
31
33
|
raw
|
32
34
|
end
|
33
35
|
end
|
34
36
|
end
|
35
37
|
end
|
36
38
|
end
|
37
|
-
|
data/lib/ttfunk/table/head.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require_relative '../table'
|
2
2
|
|
3
|
-
module TTFunk
|
3
|
+
module TTFunk
|
4
4
|
class Table
|
5
5
|
class Head < TTFunk::Table
|
6
6
|
attr_reader :version
|
@@ -23,22 +23,22 @@ module TTFunk
|
|
23
23
|
|
24
24
|
def self.encode(head, loca)
|
25
25
|
table = head.raw
|
26
|
-
table[8,4] = "\0\0\0\0" # set checksum adjustment to 0 initially
|
27
|
-
table[-4,2] = [loca[:type]].pack(
|
28
|
-
|
26
|
+
table[8, 4] = "\0\0\0\0" # set checksum adjustment to 0 initially
|
27
|
+
table[-4, 2] = [loca[:type]].pack('n') # set index_to_loc_format
|
28
|
+
table
|
29
29
|
end
|
30
30
|
|
31
31
|
private
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
33
|
+
def parse!
|
34
|
+
@version, @font_revision, @check_sum_adjustment, @magic_number,
|
35
|
+
@flags, @units_per_em, @created, @modified = read(36, 'N4n2q2')
|
36
|
+
|
37
|
+
@x_min, @y_min, @x_max, @y_max = read_signed(4)
|
38
|
+
|
39
|
+
@mac_style, @lowest_rec_ppem, @font_direction_hint,
|
40
|
+
@index_to_loc_format, @glyph_data_format = read(10, 'n*')
|
41
|
+
end
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
data/lib/ttfunk/table/hhea.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require_relative '../table'
|
2
2
|
|
3
|
-
module TTFunk
|
3
|
+
module TTFunk
|
4
4
|
class Table
|
5
5
|
class Hhea < Table
|
6
6
|
attr_reader :version
|
@@ -18,24 +18,24 @@ module TTFunk
|
|
18
18
|
|
19
19
|
def self.encode(hhea, hmtx)
|
20
20
|
raw = hhea.raw
|
21
|
-
raw[-2,2] = [hmtx[:number_of_metrics]].pack(
|
22
|
-
|
21
|
+
raw[-2, 2] = [hmtx[:number_of_metrics]].pack('n')
|
22
|
+
raw
|
23
23
|
end
|
24
24
|
|
25
25
|
private
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
def parse!
|
28
|
+
@version = read(4, 'N').first
|
29
|
+
@ascent, @descent, @line_gap = read_signed(3)
|
30
|
+
@advance_width_max = read(2, 'n').first
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
@min_left_side_bearing, @min_right_side_bearing, @x_max_extent,
|
33
|
+
@carot_slope_rise, @carot_slope_run, @caret_offset,
|
34
|
+
_reserved, _reserved, _reserved, _reserved,
|
35
|
+
@metric_data_format = read_signed(11)
|
36
36
|
|
37
|
-
|
38
|
-
|
37
|
+
@number_of_metrics = read(2, 'n').first
|
38
|
+
end
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
data/lib/ttfunk/table/hmtx.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require_relative '../table'
|
2
2
|
|
3
|
-
module TTFunk
|
4
|
-
class Table
|
3
|
+
module TTFunk
|
4
|
+
class Table
|
5
5
|
class Hmtx < Table
|
6
6
|
attr_reader :metrics
|
7
7
|
attr_reader :left_side_bearings
|
@@ -13,35 +13,40 @@ module TTFunk
|
|
13
13
|
[metric.advance_width, metric.left_side_bearing]
|
14
14
|
end
|
15
15
|
|
16
|
-
{
|
17
|
-
:
|
16
|
+
{
|
17
|
+
number_of_metrics: metrics.length,
|
18
|
+
table: metrics.flatten.pack('n*')
|
19
|
+
}
|
18
20
|
end
|
19
21
|
|
20
22
|
HorizontalMetric = Struct.new(:advance_width, :left_side_bearing)
|
21
23
|
|
22
24
|
def for(glyph_id)
|
23
25
|
@metrics[glyph_id] ||
|
24
|
-
HorizontalMetric.new(
|
25
|
-
@
|
26
|
+
HorizontalMetric.new(
|
27
|
+
@metrics.last.advance_width,
|
28
|
+
@left_side_bearings[glyph_id - @metrics.length]
|
29
|
+
)
|
26
30
|
end
|
27
31
|
|
28
32
|
private
|
29
33
|
|
30
|
-
|
31
|
-
|
34
|
+
def parse!
|
35
|
+
@metrics = []
|
32
36
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
37
|
+
file.horizontal_header.number_of_metrics.times do
|
38
|
+
advance = read(2, 'n').first
|
39
|
+
lsb = read_signed(1).first
|
40
|
+
@metrics.push HorizontalMetric.new(advance, lsb)
|
41
|
+
end
|
38
42
|
|
39
|
-
|
40
|
-
|
43
|
+
lsb_count = file.maximum_profile.num_glyphs -
|
44
|
+
file.horizontal_header.number_of_metrics
|
45
|
+
@left_side_bearings = read_signed(lsb_count)
|
41
46
|
|
42
|
-
|
43
|
-
|
44
|
-
|
47
|
+
@widths = @metrics.map(&:advance_width)
|
48
|
+
@widths += [@widths.last] * @left_side_bearings.length
|
49
|
+
end
|
45
50
|
end
|
46
51
|
end
|
47
52
|
end
|