class Emoji::Extractor
Constants
- COUPLE
- EMOJI_TTF
- FAMILY
- FAMILY_MAP
- GENDER_MAP
- GlyphIndex
- KISS
Attributes
images_path[R]
size[R]
Public Class Methods
new(size, images_path)
click to toggle source
# File lib/emoji/extractor.rb, line 10 def initialize(size, images_path) @size = size @images_path = images_path end
Public Instance Methods
each(&block)
click to toggle source
# File lib/emoji/extractor.rb, line 15 def each(&block) return to_enum(__method__) unless block_given? File.open(EMOJI_TTF, 'rb') do |file| font_offsets = parse_ttc(file) file.pos = font_offsets[0] tables = parse_tables(file) glyph_index = extract_glyph_index(file, tables) each_glyph_bitmap(file, tables, glyph_index, &block) end end
extract!()
click to toggle source
# File lib/emoji/extractor.rb, line 29 def extract! each do |glyph_name, type, binread| if emoji = glyph_name_to_emoji(glyph_name) image_filename = "#{images_path}/#{emoji.image_filename}" FileUtils.mkdir_p(File.dirname(image_filename)) File.open(image_filename, 'wb') { |f| f.write binread.call } end end end
Private Instance Methods
each_glyph_bitmap(io, tables, glyph_index) { |glyph_name, type, -> { read(next_glyph_offset - glyph_offset - 8)| ... }
click to toggle source
developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6sbix.html
# File lib/emoji/extractor.rb, line 147 def each_glyph_bitmap(io, tables, glyph_index) io.pos = sbix_offset = tables.fetch('sbix')[:offset] strike = extract_sbix_strike(io, glyph_index.length, size) glyph_index.each_with_name do |glyph_id, glyph_name| glyph_offset = strike[:glyph_data_offset][glyph_id] next_glyph_offset = strike[:glyph_data_offset][glyph_id + 1] if glyph_offset && next_glyph_offset && glyph_offset < next_glyph_offset io.pos = sbix_offset + strike[:offset] + glyph_offset x, y, type = io.read(2*2 + 4).unpack('s2A4') yield glyph_name, type, -> { io.read(next_glyph_offset - glyph_offset - 8) } end end end
extract_glyph_index(io, tables)
click to toggle source
# File lib/emoji/extractor.rb, line 128 def extract_glyph_index(io, tables) postscript_table = tables.fetch('post') io.pos = postscript_table[:offset] end_pos = io.pos + postscript_table[:length] parse_version(io.read(32).unpack('l>')[0]) #=> 2.0 num_glyphs = io.read(2).unpack('n')[0] glyph_name_index = io.read(2*num_glyphs).unpack('n*') glyph_names = [] while io.pos < end_pos length = io.read(1).unpack('C')[0] glyph_names << io.read(length) end GlyphIndex.new(num_glyphs, glyph_name_index, glyph_names) end
extract_sbix_strike(io, num_glyphs, image_size)
click to toggle source
# File lib/emoji/extractor.rb, line 163 def extract_sbix_strike(io, num_glyphs, image_size) sbix_offset = io.pos version, flags, num_strikes = io.read(2*2 + 4).unpack('n2N') strike_offsets = num_strikes.times.map { io.read(4).unpack('N')[0] } strike_offsets.each do |strike_offset| io.pos = sbix_offset + strike_offset ppem, resolution = io.read(4*2).unpack('n2') next unless ppem == size data_offsets = io.read(4 * (num_glyphs+1)).unpack('N*') return { ppem: ppem, resolution: resolution, offset: strike_offset, glyph_data_offset: data_offsets, } end return nil end
glyph_name_to_emoji(glyph_name)
click to toggle source
# File lib/emoji/extractor.rb, line 57 def glyph_name_to_emoji(glyph_name) return if glyph_name =~ /\.[1-5]($|\.)/ zwj = Emoji::ZERO_WIDTH_JOINER v16 = Emoji::VARIATION_SELECTOR_16 if glyph_name =~ /^u(#{FAMILY}|#{COUPLE}|#{KISS})\.([#{FAMILY_MAP.keys.join('')}]+)$/ if $1 == FAMILY ? $2 == "MWB" : $2 == "WM" raw = [$1.hex].pack('U') else if $1 == COUPLE middle = "#{zwj}\u{2764}#{v16}#{zwj}" # heavy black heart elsif $1 == KISS middle = "#{zwj}\u{2764}#{v16}#{zwj}\u{1F48B}#{zwj}" # heart + kiss mark else middle = zwj end raw = $2.split('').map { |c| FAMILY_MAP.fetch(c) }.join(middle) end candidates = [raw] else raw = glyph_name.gsub(/(^|_)u([0-9A-F]+)/) { ($1.empty?? $1 : zwj) + [$2.hex].pack('U') } raw.sub!(/\.0\b/, '') raw.sub!(/\.(#{GENDER_MAP.keys.join('|')})$/) { v16 + zwj + GENDER_MAP.fetch($1) } candidates = [raw] candidates << raw.sub(v16, '') if raw.include?(v16) candidates << raw.gsub(zwj, '') if raw.include?(zwj) candidates.dup.each { |c| candidates << (c + v16) } end candidates.map { |c| Emoji.find_by_unicode(c) }.compact.first end
parse_tables(io)
click to toggle source
# File lib/emoji/extractor.rb, line 98 def parse_tables(io) sfnt_version, num_tables = io.read(4 + 2*4).unpack('Nn') # sfnt_version #=> 0x00010000 num_tables.times.each_with_object({}) do |_, tables| tag, checksum, offset, length = io.read(4 + 4*3).unpack('a4N*') tables[tag] = { checksum: checksum, offset: offset, length: length, } end end
parse_ttc(io)
click to toggle source
www.microsoft.com/typography/otspec/otff.htm
# File lib/emoji/extractor.rb, line 90 def parse_ttc(io) header_name = io.read(4).unpack('a*')[0] raise unless "ttcf" == header_name header_version, num_fonts = io.read(4*2).unpack('l>N') # parse_version(header_version) #=> 2.0 io.read(4 * num_fonts).unpack('N*') end
parse_version(num)
click to toggle source
# File lib/emoji/extractor.rb, line 184 def parse_version(num) major = num >> 16 minor = num & 0xFFFF "#{major}.#{minor}" end