ttfunk 1.0.1
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.
- data/CHANGELOG +2 -0
- data/README.rdoc +39 -0
- data/data/fonts/DejaVuSans.ttf +0 -0
- data/data/fonts/comicsans.ttf +0 -0
- data/examples/metrics.rb +46 -0
- data/lib/ttfunk/directory.rb +17 -0
- data/lib/ttfunk/encoding/mac_roman.rb +88 -0
- data/lib/ttfunk/encoding/windows_1252.rb +69 -0
- data/lib/ttfunk/reader.rb +44 -0
- data/lib/ttfunk/resource_file.rb +78 -0
- data/lib/ttfunk/subset/base.rb +141 -0
- data/lib/ttfunk/subset/mac_roman.rb +50 -0
- data/lib/ttfunk/subset/unicode.rb +48 -0
- data/lib/ttfunk/subset/unicode_8bit.rb +63 -0
- data/lib/ttfunk/subset/windows_1252.rb +55 -0
- data/lib/ttfunk/subset.rb +18 -0
- data/lib/ttfunk/subset_collection.rb +72 -0
- data/lib/ttfunk/table/cmap/format00.rb +54 -0
- data/lib/ttfunk/table/cmap/format04.rb +126 -0
- data/lib/ttfunk/table/cmap/subtable.rb +79 -0
- data/lib/ttfunk/table/cmap.rb +34 -0
- data/lib/ttfunk/table/glyf/compound.rb +81 -0
- data/lib/ttfunk/table/glyf/simple.rb +37 -0
- data/lib/ttfunk/table/glyf.rb +64 -0
- data/lib/ttfunk/table/head.rb +44 -0
- data/lib/ttfunk/table/hhea.rb +41 -0
- data/lib/ttfunk/table/hmtx.rb +47 -0
- data/lib/ttfunk/table/kern/format0.rb +62 -0
- data/lib/ttfunk/table/kern.rb +79 -0
- data/lib/ttfunk/table/loca.rb +43 -0
- data/lib/ttfunk/table/maxp.rb +40 -0
- data/lib/ttfunk/table/name.rb +125 -0
- data/lib/ttfunk/table/os2.rb +78 -0
- data/lib/ttfunk/table/post/format10.rb +43 -0
- data/lib/ttfunk/table/post/format20.rb +35 -0
- data/lib/ttfunk/table/post/format25.rb +23 -0
- data/lib/ttfunk/table/post/format30.rb +17 -0
- data/lib/ttfunk/table/post/format40.rb +17 -0
- data/lib/ttfunk/table/post.rb +91 -0
- data/lib/ttfunk/table/simple.rb +14 -0
- data/lib/ttfunk/table.rb +46 -0
- data/lib/ttfunk.rb +102 -0
- metadata +121 -0
data/CHANGELOG
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
TTFunk is a TrueType font parser written in pure ruby.
|
2
|
+
|
3
|
+
= Installation
|
4
|
+
|
5
|
+
The recommended installation method is via Rubygems.
|
6
|
+
|
7
|
+
gem install ttfunk
|
8
|
+
|
9
|
+
= Usage
|
10
|
+
|
11
|
+
Basic usage:
|
12
|
+
|
13
|
+
require 'ttfunk'
|
14
|
+
|
15
|
+
file = TTFunk::File.open("some/path/myfont.ttf")
|
16
|
+
puts "name : #{file.name.font_name.join(', ')}"
|
17
|
+
puts "ascent : #{file.ascent}"
|
18
|
+
puts "descent : #{file.descent}"
|
19
|
+
|
20
|
+
For more detailed examples, explore the examples directory.
|
21
|
+
|
22
|
+
= Maintainers
|
23
|
+
|
24
|
+
- Brad Ediger
|
25
|
+
- Daniel Nelson
|
26
|
+
- Jonathan Greenberg
|
27
|
+
- James Healy
|
28
|
+
|
29
|
+
= Licensing
|
30
|
+
|
31
|
+
Matz's terms for Ruby, GPLv2, or GPLv3. See LICENSING for details.
|
32
|
+
|
33
|
+
= Mailing List
|
34
|
+
|
35
|
+
TTFunk is maintained as a dependency of prawn, the ruby PDF generation library.
|
36
|
+
|
37
|
+
Any questions or feedback should be sent to the Prawn google group.
|
38
|
+
|
39
|
+
http://groups.google.com/group/prawn-ruby
|
Binary file
|
Binary file
|
data/examples/metrics.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$LOAD_PATH << "#{File.dirname(__FILE__)}/../lib"
|
3
|
+
require "ttfunk"
|
4
|
+
|
5
|
+
def character_lookup(file, character)
|
6
|
+
puts "character : #{character}"
|
7
|
+
|
8
|
+
character_code = character.unpack("U*").first
|
9
|
+
puts "character code: #{character_code}"
|
10
|
+
|
11
|
+
glyph_id = file.cmap.unicode.first[character_code]
|
12
|
+
puts "glyph id : #{glyph_id}"
|
13
|
+
|
14
|
+
glyph = file.glyph_outlines.for(glyph_id)
|
15
|
+
puts "glyph type : %s" % glyph.class.name.split(/::/).last.downcase
|
16
|
+
puts "glyph size : %db" % glyph.raw.length
|
17
|
+
puts "glyph bbox : (%d,%d)-(%d,%d)" % [glyph.x_min, glyph.y_min, glyph.x_max, glyph.y_max]
|
18
|
+
|
19
|
+
if glyph.compound?
|
20
|
+
puts "components : %d %s" % [glyph.glyph_ids.length, glyph.glyph_ids.inspect]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
file = TTFunk::File.open(ARGV.first || "#{File.dirname(__FILE__)}/../data/fonts/DejaVuSans.ttf")
|
25
|
+
|
26
|
+
puts "-- FONT ------------------------------------"
|
27
|
+
|
28
|
+
puts "revision : %08x" % file.header.font_revision
|
29
|
+
puts "name : #{file.name.font_name.join(', ')}"
|
30
|
+
puts "family : #{file.name.font_family.join(', ')}"
|
31
|
+
puts "subfamily : #{file.name.font_subfamily.join(', ')}"
|
32
|
+
puts "postscript: #{file.name.postscript_name}"
|
33
|
+
|
34
|
+
puts "-- FONT METRICS ----------------------------"
|
35
|
+
|
36
|
+
puts "units/em : #{file.header.units_per_em}"
|
37
|
+
puts "ascent : #{file.ascent}"
|
38
|
+
puts "descent : #{file.descent}"
|
39
|
+
puts "line gap : #{file.line_gap}"
|
40
|
+
puts "bbox : (%d,%d)-(%d,%d)" % file.bbox
|
41
|
+
|
42
|
+
puts "-- SIMPLE CHARACTER -> GLYPH LOOKUP --------"
|
43
|
+
character_lookup(file, "\xE2\x98\x9C")
|
44
|
+
|
45
|
+
puts "-- COMPOUND CHARACTER -> GLYPH LOOKUP ------"
|
46
|
+
character_lookup(file, "ë")
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module TTFunk
|
2
|
+
class Directory
|
3
|
+
attr_reader :tables
|
4
|
+
attr_reader :scaler_type
|
5
|
+
|
6
|
+
def initialize(io)
|
7
|
+
@scaler_type, table_count, search_range,
|
8
|
+
entry_selector, range_shift = io.read(12).unpack("Nn*")
|
9
|
+
|
10
|
+
@tables = {}
|
11
|
+
table_count.times do
|
12
|
+
tag, checksum, offset, length = io.read(16).unpack("a4N*")
|
13
|
+
@tables[tag] = { :tag => tag, :checksum => checksum, :offset => offset, :length => length }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module TTFunk
|
2
|
+
module Encoding
|
3
|
+
class MacRoman
|
4
|
+
TO_UNICODE = Hash[*(0..255).zip(0..255).flatten]
|
5
|
+
TO_UNICODE.update(
|
6
|
+
0x81 => 0x00C5, 0x82 => 0x00C7, 0x83 => 0x00C9, 0x84 => 0x00D1, 0x85 => 0x00D6,
|
7
|
+
0x86 => 0x00DC, 0x87 => 0x00E1, 0x88 => 0x00E0, 0x89 => 0x00E2, 0x8A => 0x00E4,
|
8
|
+
0x8B => 0x00E3, 0x8C => 0x00E5, 0x8D => 0x00E7, 0x8E => 0x00E9, 0x8F => 0x00E8,
|
9
|
+
0x90 => 0x00EA, 0x91 => 0x00EB, 0x92 => 0x00ED, 0x93 => 0x00EC, 0x94 => 0x00EE,
|
10
|
+
0x95 => 0x00EF, 0x96 => 0x00F1, 0x97 => 0x00F3, 0x98 => 0x00F2, 0x99 => 0x00F4,
|
11
|
+
0x9A => 0x00F6, 0x9B => 0x00F5, 0x9C => 0x00FA, 0x9D => 0x00F9, 0x9E => 0x00FB,
|
12
|
+
0x9F => 0x00FC, 0xA0 => 0x2020, 0xA1 => 0x00B0, 0xA4 => 0x00A7, 0xA5 => 0x2022,
|
13
|
+
0xA6 => 0x00B6, 0xA7 => 0x00DF, 0xA8 => 0x00AE, 0xAA => 0x2122, 0xAB => 0x00B4,
|
14
|
+
0xAC => 0x00A8, 0xAD => 0x2260, 0xAE => 0x00C6, 0xAF => 0x00D8, 0xB0 => 0x221E,
|
15
|
+
0xB2 => 0x2264, 0xB3 => 0x2265, 0xB4 => 0x00A5, 0xB6 => 0x2202, 0xB7 => 0x2211,
|
16
|
+
0xB8 => 0x220F, 0xB9 => 0x03C0, 0xBA => 0x222B, 0xBB => 0x00AA, 0xBC => 0x00BA,
|
17
|
+
0xBD => 0x03A9, 0xBE => 0x00E6, 0xBF => 0x00F8, 0xC0 => 0x00BF, 0xC1 => 0x00A1,
|
18
|
+
0xC2 => 0x00AC, 0xC3 => 0x221A, 0xC4 => 0x0192, 0xC5 => 0x2248, 0xC6 => 0x2206,
|
19
|
+
0xC7 => 0x00AB, 0xC8 => 0x00BB, 0xC9 => 0x2026, 0xCA => 0x00A0, 0xCB => 0x00C0,
|
20
|
+
0xCC => 0x00C3, 0xCD => 0x00D5, 0xCE => 0x0152, 0xCF => 0x0153, 0xD0 => 0x2013,
|
21
|
+
0xD1 => 0x2014, 0xD2 => 0x201C, 0xD3 => 0x201D, 0xD4 => 0x2018, 0xD5 => 0x2019,
|
22
|
+
0xD6 => 0x00F7, 0xD7 => 0x25CA, 0xD8 => 0x00FF, 0xD9 => 0x0178, 0xDA => 0x2044,
|
23
|
+
0xDB => 0x20AC, 0xDC => 0x2039, 0xDD => 0x203A, 0xDE => 0xFB01, 0xDF => 0xFB02,
|
24
|
+
0xE0 => 0x2021, 0xE1 => 0x00B7, 0xE2 => 0x201A, 0xE3 => 0x201E, 0xE4 => 0x2030,
|
25
|
+
0xE5 => 0x00C2, 0xE6 => 0x00CA, 0xE7 => 0x00C1, 0xE8 => 0x00CB, 0xE9 => 0x00C8,
|
26
|
+
0xEA => 0x00CD, 0xEB => 0x00CE, 0xEC => 0x00CF, 0xED => 0x00CC, 0xEE => 0x00D3,
|
27
|
+
0xEF => 0x00D4, 0xF0 => 0xF8FF, 0xF1 => 0x00D2, 0xF2 => 0x00DA, 0xF3 => 0x00DB,
|
28
|
+
0xF4 => 0x00D9, 0xF5 => 0x0131, 0xF6 => 0x02C6, 0xF7 => 0x02DC, 0xF8 => 0x00AF,
|
29
|
+
0xF9 => 0x02D8, 0xFA => 0x02D9, 0xFB => 0x02DA, 0xFC => 0x00B8, 0xFD => 0x02DD,
|
30
|
+
0xFE => 0x02DB, 0xFF => 0x02C7
|
31
|
+
)
|
32
|
+
|
33
|
+
FROM_UNICODE = {}
|
34
|
+
(0..255).each { |key| FROM_UNICODE[TO_UNICODE[key]] = key }
|
35
|
+
|
36
|
+
# Maps MacRoman codes to their corresponding index in the Postscript glyph
|
37
|
+
# table (see TTFunk::Table::Post::Format10). If any entry in this array is a string,
|
38
|
+
# it is a postscript glyph that is not in the standard list, and which should be
|
39
|
+
# emitted specially in the TTF postscript table ('post', see format 2).
|
40
|
+
POSTSCRIPT_GLYPH_MAPPING = [
|
41
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 0x0F
|
42
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 0x1F
|
43
|
+
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, # 0x2F
|
44
|
+
19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, # 0x3F
|
45
|
+
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, # 0x4F
|
46
|
+
51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, # 0x5F
|
47
|
+
67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, # 0x6F
|
48
|
+
83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 0, # 0x7F
|
49
|
+
98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, # 0x8F
|
50
|
+
114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, # 0x9F
|
51
|
+
130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, # 0xAF
|
52
|
+
146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, # 0xBF
|
53
|
+
162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, # 0xCF
|
54
|
+
178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, "Euro", 190, 191, 192, 193, # 0xDF
|
55
|
+
194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, # 0xEF
|
56
|
+
210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225 # 0xFF
|
57
|
+
]
|
58
|
+
|
59
|
+
def self.covers?(character)
|
60
|
+
!FROM_UNICODE[character].nil?
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.to_utf8(string)
|
64
|
+
to_unicode_codepoints(string.unpack("C*")).pack("U*")
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.to_unicode(string)
|
68
|
+
to_unicode_codepoints(string.unpack("C*")).pack("n*")
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.from_utf8(string)
|
72
|
+
from_unicode_codepoints(string.unpack("U*")).pack("C*")
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.from_unicode(string)
|
76
|
+
from_unicode_codepoints(string.unpack("n*")).pack("C*")
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.to_unicode_codepoints(array)
|
80
|
+
array.map { |code| TO_UNICODE[code] }
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.from_unicode_codepoints(array)
|
84
|
+
array.map { |code| FROM_UNICODE[code] || 0 }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module TTFunk
|
2
|
+
module Encoding
|
3
|
+
class Windows1252
|
4
|
+
TO_UNICODE = Hash[*(0..255).zip(0..255).flatten]
|
5
|
+
TO_UNICODE.update(
|
6
|
+
0x80 => 0x20AC, 0x82 => 0x201A, 0x83 => 0x0192, 0x84 => 0x201E, 0x85 => 0x2026,
|
7
|
+
0x86 => 0x2020, 0x87 => 0x2021, 0x88 => 0x02C6, 0x89 => 0x2030, 0x8A => 0x0160,
|
8
|
+
0x8B => 0x2039, 0x8C => 0x0152, 0x8E => 0x017D, 0x91 => 0x2018, 0x92 => 0x2019,
|
9
|
+
0x93 => 0x201C, 0x94 => 0x201D, 0x95 => 0x2022, 0x96 => 0x2013, 0x97 => 0x2014,
|
10
|
+
0x98 => 0x02DC, 0x99 => 0x2122, 0x9A => 0x0161, 0x9B => 0x203A, 0x9C => 0x0152,
|
11
|
+
0x9E => 0x017E, 0x9F => 0x0178
|
12
|
+
)
|
13
|
+
|
14
|
+
FROM_UNICODE = {}
|
15
|
+
(0..255).each { |key| FROM_UNICODE[TO_UNICODE[key]] = key }
|
16
|
+
|
17
|
+
# Maps Windows-1252 codes to their corresponding index in the Postscript glyph
|
18
|
+
# table (see TTFunk::Table::Post::Format10). If any entry in this array is a string,
|
19
|
+
# it is a postscript glyph that is not in the standard list, and which should be
|
20
|
+
# emitted specially in the TTF postscript table ('post', see format 2).
|
21
|
+
POSTSCRIPT_GLYPH_MAPPING = [
|
22
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
23
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
24
|
+
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
|
25
|
+
19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
|
26
|
+
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
|
27
|
+
51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
|
28
|
+
67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82,
|
29
|
+
83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 0,
|
30
|
+
"Euro", 0, 196, 166, 197, 171, 130, 194, 216, 198, 228, 190, 176, 0, 230, 0,
|
31
|
+
0, 182, 183, 180, 181, 135, 178, 179, 217, 140, 229, 191, 177, 0, 231, 186,
|
32
|
+
3, 163, 132, 133, 189, 150, 232, 134, 142, 139, 157, 169, 164, 16, 138, 218,
|
33
|
+
131, 147, 242, 243, 141, 151, 136, 195, 222, 241, 158, 170, 245, 244, 246, 162,
|
34
|
+
173, 201, 199, 174, 98, 99, 144, 100, 203, 101, 200, 202, 207, 204, 205, 206,
|
35
|
+
233, 102, 211, 208, 209, 175, 103, 240, 145, 214, 212, 213, 104, 235, 237, 137,
|
36
|
+
106, 105, 107, 109, 108, 110, 160, 111, 113, 112, 114, 115, 117, 116, 118, 119,
|
37
|
+
234, 120, 122, 121, 123, 125, 124, 184, 161, 127, 126, 128, 129, 236, 238, 186
|
38
|
+
]
|
39
|
+
|
40
|
+
def self.covers?(character)
|
41
|
+
!FROM_UNICODE[character].nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.to_utf8(string)
|
45
|
+
to_unicode_codepoints(string.unpack("C*")).pack("U*")
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.to_unicode(string)
|
49
|
+
to_unicode_codepoints(string.unpack("C*")).pack("n*")
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.from_utf8(string)
|
53
|
+
from_unicode_codepoints(string.unpack("U*")).pack("C*")
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.from_unicode(string)
|
57
|
+
from_unicode_codepoints(string.unpack("n*")).pack("C*")
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.to_unicode_codepoints(array)
|
61
|
+
array.map { |code| TO_UNICODE[code] }
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.from_unicode_codepoints(array)
|
65
|
+
array.map { |code| FROM_UNICODE[code] || 0 }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module TTFunk
|
2
|
+
module Reader
|
3
|
+
private
|
4
|
+
|
5
|
+
def io
|
6
|
+
@file.contents
|
7
|
+
end
|
8
|
+
|
9
|
+
def read(bytes, format)
|
10
|
+
io.read(bytes).unpack(format)
|
11
|
+
end
|
12
|
+
|
13
|
+
def read_signed(count)
|
14
|
+
read(count*2, "n*").map { |i| to_signed(i) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_signed(n)
|
18
|
+
(n>=0x8000) ? -((n ^ 0xFFFF) + 1) : n
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_from(position)
|
22
|
+
saved, io.pos = io.pos, position
|
23
|
+
result = yield position
|
24
|
+
io.pos = saved
|
25
|
+
return result
|
26
|
+
end
|
27
|
+
|
28
|
+
# For debugging purposes
|
29
|
+
def hexdump(string)
|
30
|
+
bytes = string.unpack("C*")
|
31
|
+
bytes.each_with_index do |c, i|
|
32
|
+
print "%02X" % c
|
33
|
+
if (i+1) % 16 == 0
|
34
|
+
puts
|
35
|
+
elsif (i+1) % 8 == 0
|
36
|
+
print " "
|
37
|
+
else
|
38
|
+
print " "
|
39
|
+
end
|
40
|
+
end
|
41
|
+
puts unless bytes.length % 16 == 0
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module TTFunk
|
2
|
+
class ResourceFile
|
3
|
+
attr_reader :map
|
4
|
+
|
5
|
+
def self.open(path)
|
6
|
+
::File.open(path, "rb") do |io|
|
7
|
+
file = new(io)
|
8
|
+
yield file
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(io)
|
13
|
+
@io = io
|
14
|
+
|
15
|
+
data_offset, map_offset, data_length, map_length = @io.read(16).unpack("N*")
|
16
|
+
|
17
|
+
@map = {}
|
18
|
+
@io.pos = map_offset + 24 # skip header copy, next map handle, file reference, and attrs
|
19
|
+
type_list_offset, name_list_offset = @io.read(4).unpack("n*")
|
20
|
+
|
21
|
+
type_list_offset += map_offset
|
22
|
+
name_list_offset += map_offset
|
23
|
+
|
24
|
+
@io.pos = type_list_offset
|
25
|
+
max_index = @io.read(2).unpack("n").first
|
26
|
+
0.upto(max_index) do
|
27
|
+
type, max_type_index, ref_list_offset = @io.read(8).unpack("A4nn")
|
28
|
+
@map[type] = { :list => [], :named => {} }
|
29
|
+
|
30
|
+
parse_from(type_list_offset + ref_list_offset) do
|
31
|
+
0.upto(max_type_index) do
|
32
|
+
id, name_ofs, attr = @io.read(5).unpack("nnC")
|
33
|
+
data_ofs = @io.read(3)
|
34
|
+
data_ofs = data_offset + [0, data_ofs].pack("CA*").unpack("N").first
|
35
|
+
handle = @io.read(4).unpack("N").first
|
36
|
+
|
37
|
+
entry = { :id => id, :attributes => attr, :offset => data_ofs, :handle => handle }
|
38
|
+
|
39
|
+
if name_list_offset + name_ofs < map_offset + map_length
|
40
|
+
parse_from(name_ofs + name_list_offset) do
|
41
|
+
len = @io.read(1).unpack("C").first
|
42
|
+
entry[:name] = @io.read(len)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
@map[type][:list] << entry
|
47
|
+
@map[type][:named][entry[:name]] = entry if entry[:name]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def [](type, index=0)
|
54
|
+
if @map[type]
|
55
|
+
collection = index.is_a?(Fixnum) ? :list : :named
|
56
|
+
if @map[type][collection][index]
|
57
|
+
parse_from(@map[type][collection][index][:offset]) do
|
58
|
+
length = @io.read(4).unpack("N").first
|
59
|
+
return @io.read(length)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def resources_for(type)
|
66
|
+
(@map[type] && @map[type][:named] || {}).keys
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def parse_from(offset)
|
72
|
+
saved, @io.pos = @io.pos, offset
|
73
|
+
yield
|
74
|
+
ensure
|
75
|
+
@io.pos = saved
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'ttfunk/table/cmap'
|
2
|
+
require 'ttfunk/table/glyf'
|
3
|
+
require 'ttfunk/table/head'
|
4
|
+
require 'ttfunk/table/hhea'
|
5
|
+
require 'ttfunk/table/hmtx'
|
6
|
+
require 'ttfunk/table/kern'
|
7
|
+
require 'ttfunk/table/loca'
|
8
|
+
require 'ttfunk/table/maxp'
|
9
|
+
require 'ttfunk/table/name'
|
10
|
+
require 'ttfunk/table/post'
|
11
|
+
require 'ttfunk/table/simple'
|
12
|
+
|
13
|
+
module TTFunk
|
14
|
+
module Subset
|
15
|
+
class Base
|
16
|
+
attr_reader :original
|
17
|
+
|
18
|
+
def initialize(original)
|
19
|
+
@original = original
|
20
|
+
end
|
21
|
+
|
22
|
+
def unicode?
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_unicode_map
|
27
|
+
{}
|
28
|
+
end
|
29
|
+
|
30
|
+
def encode(options={})
|
31
|
+
cmap_table = new_cmap_table(options)
|
32
|
+
glyphs = collect_glyphs(original_glyph_ids)
|
33
|
+
|
34
|
+
old2new_glyph = cmap_table[:charmap].inject({ 0 => 0 }) { |map, (code, ids)| map[ids[:old]] = ids[:new]; map }
|
35
|
+
next_glyph_id = cmap_table[:max_glyph_id]
|
36
|
+
|
37
|
+
glyphs.keys.each do |old_id|
|
38
|
+
unless old2new_glyph.key?(old_id)
|
39
|
+
old2new_glyph[old_id] = next_glyph_id
|
40
|
+
next_glyph_id += 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
new2old_glyph = old2new_glyph.invert
|
45
|
+
|
46
|
+
# "mandatory" tables. Every font should ("should") have these, including
|
47
|
+
# the cmap table (encoded above).
|
48
|
+
glyf_table = TTFunk::Table::Glyf.encode(glyphs, new2old_glyph, old2new_glyph)
|
49
|
+
loca_table = TTFunk::Table::Loca.encode(glyf_table[:offsets])
|
50
|
+
hmtx_table = TTFunk::Table::Hmtx.encode(original.horizontal_metrics, new2old_glyph)
|
51
|
+
hhea_table = TTFunk::Table::Hhea.encode(original.horizontal_header, hmtx_table)
|
52
|
+
maxp_table = TTFunk::Table::Maxp.encode(original.maximum_profile, old2new_glyph)
|
53
|
+
post_table = TTFunk::Table::Post.encode(original.postscript, new2old_glyph)
|
54
|
+
name_table = TTFunk::Table::Name.encode(original.name)
|
55
|
+
head_table = TTFunk::Table::Head.encode(original.header, loca_table)
|
56
|
+
|
57
|
+
# "optional" tables. Fonts may omit these if they do not need them. Because they
|
58
|
+
# apply globally, we can simply copy them over, without modification, if they
|
59
|
+
# exist.
|
60
|
+
os2_table = original.os2.raw
|
61
|
+
cvt_table = TTFunk::Table::Simple.new(original, "cvt ").raw
|
62
|
+
fpgm_table = TTFunk::Table::Simple.new(original, "fpgm").raw
|
63
|
+
prep_table = TTFunk::Table::Simple.new(original, "prep").raw
|
64
|
+
|
65
|
+
# for PDF's, the kerning info is all included in the PDF as the text is
|
66
|
+
# drawn. Thus, the PDF readers do not actually use the kerning info in
|
67
|
+
# embedded fonts. If the library is used for something else, the generated
|
68
|
+
# subfont may need a kerning table... in that case, you need to opt into it.
|
69
|
+
if options[:kerning]
|
70
|
+
kern_table = TTFunk::Table::Kern.encode(original.kerning, old2new_glyph)
|
71
|
+
end
|
72
|
+
|
73
|
+
tables = { 'cmap' => cmap_table[:table],
|
74
|
+
'glyf' => glyf_table[:table],
|
75
|
+
'loca' => loca_table[:table],
|
76
|
+
'kern' => kern_table,
|
77
|
+
'hmtx' => hmtx_table[:table],
|
78
|
+
'hhea' => hhea_table,
|
79
|
+
'maxp' => maxp_table,
|
80
|
+
'OS/2' => os2_table,
|
81
|
+
'post' => post_table,
|
82
|
+
'name' => name_table,
|
83
|
+
'head' => head_table,
|
84
|
+
'prep' => prep_table,
|
85
|
+
'fpgm' => fpgm_table,
|
86
|
+
'cvt ' => cvt_table }
|
87
|
+
|
88
|
+
tables.delete_if { |tag, table| table.nil? }
|
89
|
+
|
90
|
+
search_range = (Math.log(tables.length) / Math.log(2)).to_i * 16
|
91
|
+
entry_selector = (Math.log(search_range) / Math.log(2)).to_i
|
92
|
+
range_shift = tables.length * 16 - search_range
|
93
|
+
|
94
|
+
newfont = [original.directory.scaler_type, tables.length, search_range, entry_selector, range_shift].pack("Nn*")
|
95
|
+
|
96
|
+
directory_size = tables.length * 16
|
97
|
+
offset = newfont.length + directory_size
|
98
|
+
|
99
|
+
table_data = ""
|
100
|
+
head_offset = nil
|
101
|
+
tables.each do |tag, data|
|
102
|
+
newfont << [tag, checksum(data), offset, data.length].pack("A4N*")
|
103
|
+
table_data << data
|
104
|
+
head_offset = offset if tag == 'head'
|
105
|
+
offset += data.length
|
106
|
+
while offset % 4 != 0
|
107
|
+
offset += 1
|
108
|
+
table_data << "\0"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
newfont << table_data
|
113
|
+
sum = checksum(newfont)
|
114
|
+
adjustment = 0xB1B0AFBA - sum
|
115
|
+
newfont[head_offset+8,4] = [adjustment].pack("N")
|
116
|
+
|
117
|
+
return newfont
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def unicode_cmap
|
123
|
+
@unicode_cmap ||= @original.cmap.unicode.first
|
124
|
+
end
|
125
|
+
|
126
|
+
def checksum(data)
|
127
|
+
data += "\0" * (4 - data.length % 4) unless data.length % 4 == 0
|
128
|
+
data.unpack("N*").inject(0) { |sum, dword| sum + dword } & 0xFFFF_FFFF
|
129
|
+
end
|
130
|
+
|
131
|
+
def collect_glyphs(glyph_ids)
|
132
|
+
glyphs = glyph_ids.inject({}) { |h, id| h[id] = original.glyph_outlines.for(id); h }
|
133
|
+
additional_ids = glyphs.values.select { |g| g && g.compound? }.map { |g| g.glyph_ids }.flatten
|
134
|
+
|
135
|
+
glyphs.update(collect_glyphs(additional_ids)) if additional_ids.any?
|
136
|
+
|
137
|
+
return glyphs
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'ttfunk/subset/base'
|
3
|
+
require 'ttfunk/encoding/mac_roman'
|
4
|
+
|
5
|
+
module TTFunk
|
6
|
+
module Subset
|
7
|
+
class MacRoman < Base
|
8
|
+
def initialize(original)
|
9
|
+
super
|
10
|
+
@subset = Array.new(256)
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_unicode_map
|
14
|
+
Encoding::MacRoman::TO_UNICODE
|
15
|
+
end
|
16
|
+
|
17
|
+
def use(character)
|
18
|
+
@subset[Encoding::MacRoman::FROM_UNICODE[character]] = character
|
19
|
+
end
|
20
|
+
|
21
|
+
def covers?(character)
|
22
|
+
Encoding::MacRoman.covers?(character)
|
23
|
+
end
|
24
|
+
|
25
|
+
def includes?(character)
|
26
|
+
code = Encoding::MacRoman::FROM_UNICODE[character]
|
27
|
+
code && @subset[code]
|
28
|
+
end
|
29
|
+
|
30
|
+
def from_unicode(character)
|
31
|
+
Encoding::MacRoman::FROM_UNICODE[character]
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def new_cmap_table(options)
|
37
|
+
mapping = {}
|
38
|
+
@subset.each_with_index do |unicode, roman|
|
39
|
+
mapping[roman] = unicode_cmap[unicode] if roman
|
40
|
+
end
|
41
|
+
|
42
|
+
TTFunk::Table::Cmap.encode(mapping, :mac_roman)
|
43
|
+
end
|
44
|
+
|
45
|
+
def original_glyph_ids
|
46
|
+
([0] + @subset.map { |unicode| unicode && unicode_cmap[unicode] }).compact.uniq.sort
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'ttfunk/subset/base'
|
3
|
+
|
4
|
+
module TTFunk
|
5
|
+
module Subset
|
6
|
+
class Unicode < Base
|
7
|
+
def initialize(original)
|
8
|
+
super
|
9
|
+
@subset = Set.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def unicode?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_unicode_map
|
17
|
+
@subset.inject({}) { |map, code| map[code] = code; map }
|
18
|
+
end
|
19
|
+
|
20
|
+
def use(character)
|
21
|
+
@subset << character
|
22
|
+
end
|
23
|
+
|
24
|
+
def covers?(character)
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def includes?(character)
|
29
|
+
@subset.includes(character)
|
30
|
+
end
|
31
|
+
|
32
|
+
def from_unicode(character)
|
33
|
+
character
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def new_cmap_table(options)
|
39
|
+
mapping = @subset.inject({}) { |map, code| map[code] = unicode_cmap[code]; map }
|
40
|
+
TTFunk::Table::Cmap.encode(mapping, :unicode)
|
41
|
+
end
|
42
|
+
|
43
|
+
def original_glyph_ids
|
44
|
+
([0] + @subset.map { |code| unicode_cmap[code] }).uniq.sort
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|