gemoji 3.0.1 → 4.0.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,190 +0,0 @@
1
- require 'emoji'
2
- require 'fileutils'
3
-
4
- module Emoji
5
- class Extractor
6
- EMOJI_TTF = "/System/Library/Fonts/Apple Color Emoji.ttc"
7
-
8
- attr_reader :size, :images_path
9
-
10
- def initialize(size, images_path)
11
- @size = size
12
- @images_path = images_path
13
- end
14
-
15
- def each(&block)
16
- return to_enum(__method__) unless block_given?
17
-
18
- File.open(EMOJI_TTF, 'rb') do |file|
19
- font_offsets = parse_ttc(file)
20
- file.pos = font_offsets[0]
21
-
22
- tables = parse_tables(file)
23
- glyph_index = extract_glyph_index(file, tables)
24
-
25
- each_glyph_bitmap(file, tables, glyph_index, &block)
26
- end
27
- end
28
-
29
- def extract!
30
- each do |glyph_name, type, binread|
31
- if emoji = glyph_name_to_emoji(glyph_name)
32
- image_filename = "#{images_path}/#{emoji.image_filename}"
33
- FileUtils.mkdir_p(File.dirname(image_filename))
34
- File.open(image_filename, 'wb') { |f| f.write binread.call }
35
- end
36
- end
37
- end
38
-
39
- private
40
-
41
- GENDER_MAP = {
42
- "M" => "\u{2642}",
43
- "W" => "\u{2640}",
44
- }
45
-
46
- FAMILY_MAP = {
47
- "B" => "\u{1f466}",
48
- "G" => "\u{1f467}",
49
- "M" => "\u{1f468}",
50
- "W" => "\u{1f469}",
51
- }.freeze
52
-
53
- FAMILY = "1F46A"
54
- COUPLE = "1F491"
55
- KISS = "1F48F"
56
-
57
- def glyph_name_to_emoji(glyph_name)
58
- return if glyph_name =~ /\.[1-5]($|\.)/
59
- zwj = Emoji::ZERO_WIDTH_JOINER
60
- v16 = Emoji::VARIATION_SELECTOR_16
61
-
62
- if glyph_name =~ /^u(#{FAMILY}|#{COUPLE}|#{KISS})\.([#{FAMILY_MAP.keys.join('')}]+)$/
63
- if $1 == FAMILY ? $2 == "MWB" : $2 == "WM"
64
- raw = [$1.hex].pack('U')
65
- else
66
- if $1 == COUPLE
67
- middle = "#{zwj}\u{2764}#{v16}#{zwj}" # heavy black heart
68
- elsif $1 == KISS
69
- middle = "#{zwj}\u{2764}#{v16}#{zwj}\u{1F48B}#{zwj}" # heart + kiss mark
70
- else
71
- middle = zwj
72
- end
73
- raw = $2.split('').map { |c| FAMILY_MAP.fetch(c) }.join(middle)
74
- end
75
- candidates = [raw]
76
- else
77
- raw = glyph_name.gsub(/(^|_)u([0-9A-F]+)/) { ($1.empty?? $1 : zwj) + [$2.hex].pack('U') }
78
- raw.sub!(/\.0\b/, '')
79
- raw.sub!(/\.(#{GENDER_MAP.keys.join('|')})$/) { v16 + zwj + GENDER_MAP.fetch($1) }
80
- candidates = [raw]
81
- candidates << raw.sub(v16, '') if raw.include?(v16)
82
- candidates << raw.gsub(zwj, '') if raw.include?(zwj)
83
- candidates.dup.each { |c| candidates << (c + v16) }
84
- end
85
-
86
- candidates.map { |c| Emoji.find_by_unicode(c) }.compact.first
87
- end
88
-
89
- # https://www.microsoft.com/typography/otspec/otff.htm
90
- def parse_ttc(io)
91
- header_name = io.read(4).unpack('a*')[0]
92
- raise unless "ttcf" == header_name
93
- header_version, num_fonts = io.read(4*2).unpack('l>N')
94
- # parse_version(header_version) #=> 2.0
95
- io.read(4 * num_fonts).unpack('N*')
96
- end
97
-
98
- def parse_tables(io)
99
- sfnt_version, num_tables = io.read(4 + 2*4).unpack('Nn')
100
- # sfnt_version #=> 0x00010000
101
- num_tables.times.each_with_object({}) do |_, tables|
102
- tag, checksum, offset, length = io.read(4 + 4*3).unpack('a4N*')
103
- tables[tag] = {
104
- checksum: checksum,
105
- offset: offset,
106
- length: length,
107
- }
108
- end
109
- end
110
-
111
- GlyphIndex = Struct.new(:length, :name_index, :names) do
112
- def name_for(glyph_id)
113
- index = name_index[glyph_id]
114
- names[index - 257]
115
- end
116
-
117
- def each(&block)
118
- length.times(&block)
119
- end
120
-
121
- def each_with_name
122
- each do |glyph_id|
123
- yield glyph_id, name_for(glyph_id)
124
- end
125
- end
126
- end
127
-
128
- def extract_glyph_index(io, tables)
129
- postscript_table = tables.fetch('post')
130
- io.pos = postscript_table[:offset]
131
- end_pos = io.pos + postscript_table[:length]
132
-
133
- parse_version(io.read(32).unpack('l>')[0]) #=> 2.0
134
- num_glyphs = io.read(2).unpack('n')[0]
135
- glyph_name_index = io.read(2*num_glyphs).unpack('n*')
136
-
137
- glyph_names = []
138
- while io.pos < end_pos
139
- length = io.read(1).unpack('C')[0]
140
- glyph_names << io.read(length)
141
- end
142
-
143
- GlyphIndex.new(num_glyphs, glyph_name_index, glyph_names)
144
- end
145
-
146
- # https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6sbix.html
147
- def each_glyph_bitmap(io, tables, glyph_index)
148
- io.pos = sbix_offset = tables.fetch('sbix')[:offset]
149
- strike = extract_sbix_strike(io, glyph_index.length, size)
150
-
151
- glyph_index.each_with_name do |glyph_id, glyph_name|
152
- glyph_offset = strike[:glyph_data_offset][glyph_id]
153
- next_glyph_offset = strike[:glyph_data_offset][glyph_id + 1]
154
-
155
- if glyph_offset && next_glyph_offset && glyph_offset < next_glyph_offset
156
- io.pos = sbix_offset + strike[:offset] + glyph_offset
157
- x, y, type = io.read(2*2 + 4).unpack('s2A4')
158
- yield glyph_name, type, -> { io.read(next_glyph_offset - glyph_offset - 8) }
159
- end
160
- end
161
- end
162
-
163
- def extract_sbix_strike(io, num_glyphs, image_size)
164
- sbix_offset = io.pos
165
- version, flags, num_strikes = io.read(2*2 + 4).unpack('n2N')
166
- strike_offsets = num_strikes.times.map { io.read(4).unpack('N')[0] }
167
-
168
- strike_offsets.each do |strike_offset|
169
- io.pos = sbix_offset + strike_offset
170
- ppem, resolution = io.read(4*2).unpack('n2')
171
- next unless ppem == size
172
-
173
- data_offsets = io.read(4 * (num_glyphs+1)).unpack('N*')
174
- return {
175
- ppem: ppem,
176
- resolution: resolution,
177
- offset: strike_offset,
178
- glyph_data_offset: data_offsets,
179
- }
180
- end
181
- return nil
182
- end
183
-
184
- def parse_version(num)
185
- major = num >> 16
186
- minor = num & 0xFFFF
187
- "#{major}.#{minor}"
188
- end
189
- end
190
- end