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.
Files changed (43) hide show
  1. data/CHANGELOG +2 -0
  2. data/README.rdoc +39 -0
  3. data/data/fonts/DejaVuSans.ttf +0 -0
  4. data/data/fonts/comicsans.ttf +0 -0
  5. data/examples/metrics.rb +46 -0
  6. data/lib/ttfunk/directory.rb +17 -0
  7. data/lib/ttfunk/encoding/mac_roman.rb +88 -0
  8. data/lib/ttfunk/encoding/windows_1252.rb +69 -0
  9. data/lib/ttfunk/reader.rb +44 -0
  10. data/lib/ttfunk/resource_file.rb +78 -0
  11. data/lib/ttfunk/subset/base.rb +141 -0
  12. data/lib/ttfunk/subset/mac_roman.rb +50 -0
  13. data/lib/ttfunk/subset/unicode.rb +48 -0
  14. data/lib/ttfunk/subset/unicode_8bit.rb +63 -0
  15. data/lib/ttfunk/subset/windows_1252.rb +55 -0
  16. data/lib/ttfunk/subset.rb +18 -0
  17. data/lib/ttfunk/subset_collection.rb +72 -0
  18. data/lib/ttfunk/table/cmap/format00.rb +54 -0
  19. data/lib/ttfunk/table/cmap/format04.rb +126 -0
  20. data/lib/ttfunk/table/cmap/subtable.rb +79 -0
  21. data/lib/ttfunk/table/cmap.rb +34 -0
  22. data/lib/ttfunk/table/glyf/compound.rb +81 -0
  23. data/lib/ttfunk/table/glyf/simple.rb +37 -0
  24. data/lib/ttfunk/table/glyf.rb +64 -0
  25. data/lib/ttfunk/table/head.rb +44 -0
  26. data/lib/ttfunk/table/hhea.rb +41 -0
  27. data/lib/ttfunk/table/hmtx.rb +47 -0
  28. data/lib/ttfunk/table/kern/format0.rb +62 -0
  29. data/lib/ttfunk/table/kern.rb +79 -0
  30. data/lib/ttfunk/table/loca.rb +43 -0
  31. data/lib/ttfunk/table/maxp.rb +40 -0
  32. data/lib/ttfunk/table/name.rb +125 -0
  33. data/lib/ttfunk/table/os2.rb +78 -0
  34. data/lib/ttfunk/table/post/format10.rb +43 -0
  35. data/lib/ttfunk/table/post/format20.rb +35 -0
  36. data/lib/ttfunk/table/post/format25.rb +23 -0
  37. data/lib/ttfunk/table/post/format30.rb +17 -0
  38. data/lib/ttfunk/table/post/format40.rb +17 -0
  39. data/lib/ttfunk/table/post.rb +91 -0
  40. data/lib/ttfunk/table/simple.rb +14 -0
  41. data/lib/ttfunk/table.rb +46 -0
  42. data/lib/ttfunk.rb +102 -0
  43. metadata +121 -0
data/CHANGELOG ADDED
@@ -0,0 +1,2 @@
1
+ v1.0.0 (XX February 2011)
2
+ * initial release as a standalone gem
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
@@ -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