File: base.rb

package info (click to toggle)
ruby-ttfunk 1.0.3%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 916 kB
  • sloc: ruby: 1,719; makefile: 16
file content (141 lines) | stat: -rw-r--r-- 5,075 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
require 'ttfunk/table/cmap'
require 'ttfunk/table/glyf'
require 'ttfunk/table/head'
require 'ttfunk/table/hhea'
require 'ttfunk/table/hmtx'
require 'ttfunk/table/kern'
require 'ttfunk/table/loca'
require 'ttfunk/table/maxp'
require 'ttfunk/table/name'
require 'ttfunk/table/post'
require 'ttfunk/table/simple'

module TTFunk
  module Subset
    class Base
      attr_reader :original

      def initialize(original)
        @original = original
      end

      def unicode?
        false
      end

      def to_unicode_map
        {}
      end

      def encode(options={})
        cmap_table = new_cmap_table(options)
        glyphs = collect_glyphs(original_glyph_ids)

        old2new_glyph = cmap_table[:charmap].inject({ 0 => 0 }) { |map, (code, ids)| map[ids[:old]] = ids[:new]; map }
        next_glyph_id = cmap_table[:max_glyph_id]

        glyphs.keys.each do |old_id|
          unless old2new_glyph.key?(old_id) 
            old2new_glyph[old_id] = next_glyph_id
            next_glyph_id += 1
          end
        end

        new2old_glyph = old2new_glyph.invert

        # "mandatory" tables. Every font should ("should") have these, including
        # the cmap table (encoded above).
        glyf_table = TTFunk::Table::Glyf.encode(glyphs, new2old_glyph, old2new_glyph)
        loca_table = TTFunk::Table::Loca.encode(glyf_table[:offsets])
        hmtx_table = TTFunk::Table::Hmtx.encode(original.horizontal_metrics, new2old_glyph)
        hhea_table = TTFunk::Table::Hhea.encode(original.horizontal_header, hmtx_table)
        maxp_table = TTFunk::Table::Maxp.encode(original.maximum_profile, old2new_glyph)
        post_table = TTFunk::Table::Post.encode(original.postscript, new2old_glyph)
        name_table = TTFunk::Table::Name.encode(original.name)
        head_table = TTFunk::Table::Head.encode(original.header, loca_table)

        # "optional" tables. Fonts may omit these if they do not need them. Because they
        # apply globally, we can simply copy them over, without modification, if they
        # exist.
        os2_table  = original.os2.raw
        cvt_table  = TTFunk::Table::Simple.new(original, "cvt ").raw
        fpgm_table = TTFunk::Table::Simple.new(original, "fpgm").raw
        prep_table = TTFunk::Table::Simple.new(original, "prep").raw

        # for PDF's, the kerning info is all included in the PDF as the text is
        # drawn. Thus, the PDF readers do not actually use the kerning info in
        # embedded fonts. If the library is used for something else, the generated
        # subfont may need a kerning table... in that case, you need to opt into it.
        if options[:kerning]
          kern_table = TTFunk::Table::Kern.encode(original.kerning, old2new_glyph)
        end

        tables = { 'cmap' => cmap_table[:table],
                   'glyf' => glyf_table[:table],
                   'loca' => loca_table[:table],
                   'kern' => kern_table,
                   'hmtx' => hmtx_table[:table],
                   'hhea' => hhea_table,
                   'maxp' => maxp_table,
                   'OS/2' => os2_table,
                   'post' => post_table,
                   'name' => name_table,
                   'head' => head_table,
                   'prep' => prep_table,
                   'fpgm' => fpgm_table,
                   'cvt ' => cvt_table }

        tables.delete_if { |tag, table| table.nil? }

        search_range = (Math.log(tables.length) / Math.log(2)).to_i * 16
        entry_selector = (Math.log(search_range) / Math.log(2)).to_i
        range_shift = tables.length * 16 - search_range

        newfont = [original.directory.scaler_type, tables.length, search_range, entry_selector, range_shift].pack("Nn*")

        directory_size = tables.length * 16
        offset = newfont.length + directory_size

        table_data = ""
        head_offset = nil
        tables.each do |tag, data|
          newfont << [tag, checksum(data), offset, data.length].pack("A4N*")
          table_data << data
          head_offset = offset if tag == 'head'
          offset += data.length
          while offset % 4 != 0
            offset += 1
            table_data << "\0"
          end
        end

        newfont << table_data
        sum = checksum(newfont)
        adjustment = 0xB1B0AFBA - sum
        newfont[head_offset+8,4] = [adjustment].pack("N")

        return newfont
      end

      private

        def unicode_cmap
          @unicode_cmap ||= @original.cmap.unicode.first
        end

        def checksum(data)
          data += "\0" * (4 - data.length % 4) unless data.length % 4 == 0
          data.unpack("N*").inject(0) { |sum, dword| sum + dword } & 0xFFFF_FFFF
        end

        def collect_glyphs(glyph_ids)
          glyphs = glyph_ids.inject({}) { |h, id| h[id] = original.glyph_outlines.for(id); h }
          additional_ids = glyphs.values.select { |g| g && g.compound? }.map { |g| g.glyph_ids }.flatten

          glyphs.update(collect_glyphs(additional_ids)) if additional_ids.any?

          return glyphs
        end
    end
  end
end