fontisan 0.4.32 → 0.4.34
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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +22 -213
- data/TODO.improvements/README.md +6 -6
- data/lib/fontisan/cli.rb +65 -0
- data/lib/fontisan/commands/audit_command.rb +257 -0
- data/lib/fontisan/commands.rb +1 -0
- data/lib/fontisan/models/audit_report.rb +143 -0
- data/lib/fontisan/models.rb +2 -0
- data/lib/fontisan/subset/table_strategy/cff2.rb +392 -0
- data/lib/fontisan/subset/table_strategy.rb +2 -0
- data/lib/fontisan/tables/cff/cff2_charstring_builder.rb +5 -2
- data/lib/fontisan/tables/cff2/fd_select.rb +74 -1
- data/lib/fontisan/tables/cff2/index.rb +155 -0
- data/lib/fontisan/tables/cff2/table_reader.rb +25 -26
- data/lib/fontisan/tables/cff2.rb +1 -0
- data/lib/fontisan/type1/charstrings.rb +34 -1
- data/lib/fontisan/type1/seac_expander.rb +68 -57
- data/lib/fontisan/ufo/compile/cff2.rb +161 -19
- data/lib/fontisan/ufo/compile/feature_compiler.rb +161 -0
- data/lib/fontisan/ufo/compile/gsub.rb +267 -0
- data/lib/fontisan/ufo/compile/variable_otf.rb +16 -7
- data/lib/fontisan/ufo/compile.rb +3 -1
- data/lib/fontisan/ufo/font.rb +3 -2
- data/lib/fontisan/ufo/image.rb +15 -2
- data/lib/fontisan/ufo/image_set.rb +82 -2
- data/lib/fontisan/ufo/reader.rb +8 -0
- data/lib/fontisan/version.rb +1 -1
- metadata +7 -1
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Ufo
|
|
7
|
+
module Compile
|
|
8
|
+
# Builds the OpenType `GSUB` (Glyph Substitution) table from
|
|
9
|
+
# ligature substitution rules extracted from features.fea via
|
|
10
|
+
# {FeatureCompiler}.
|
|
11
|
+
#
|
|
12
|
+
# Emits a minimal but valid GSUB with:
|
|
13
|
+
#
|
|
14
|
+
# - ScriptList: DFLT script, default language system
|
|
15
|
+
# - FeatureList: one feature record per ligature feature tag
|
|
16
|
+
# (e.g. "liga", "dlig", "clig")
|
|
17
|
+
# - LookupList: one LigatureSubst lookup per feature tag
|
|
18
|
+
#
|
|
19
|
+
# Each `sub A B C by ABC;` rule becomes a LigatureSet on the
|
|
20
|
+
# first glyph (A), with a Ligature record pointing at ABC and
|
|
21
|
+
# component count 2 (B, C).
|
|
22
|
+
#
|
|
23
|
+
# @see https://learn.microsoft.com/en-us/typography/opentype/spec/gsub
|
|
24
|
+
module Gsub
|
|
25
|
+
SCRIPT_DFLT = "DFLT"
|
|
26
|
+
LANGSYS_DEFAULT = 0
|
|
27
|
+
|
|
28
|
+
# Build GSUB table bytes from parsed feature rules.
|
|
29
|
+
#
|
|
30
|
+
# @param parsed [FeatureCompiler::ParsedFeatures]
|
|
31
|
+
# @param name_to_gid [Hash{String=>Integer}]
|
|
32
|
+
# @return [String, nil] GSUB bytes, or nil if no ligature rules
|
|
33
|
+
def self.build(parsed:, name_to_gid:)
|
|
34
|
+
features = ligature_features(parsed, name_to_gid)
|
|
35
|
+
return nil if features.empty?
|
|
36
|
+
|
|
37
|
+
lookups = features.values
|
|
38
|
+
build_gsub_table(features.keys, lookups)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Resolve ligature rules to per-feature lookup data.
|
|
42
|
+
#
|
|
43
|
+
# @param parsed [FeatureCompiler::ParsedFeatures]
|
|
44
|
+
# @param name_to_gid [Hash{String=>Integer}]
|
|
45
|
+
# @return [Hash{String=>Array}] feature_tag → lookup data
|
|
46
|
+
def self.ligature_features(parsed, name_to_gid)
|
|
47
|
+
parsed.ligatures.keys.each_with_object({}) do |tag, h|
|
|
48
|
+
rules = parsed.ligatures_for(tag)
|
|
49
|
+
sets = build_ligature_sets(rules, name_to_gid)
|
|
50
|
+
h[tag] = sets unless sets.empty?
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Build ligature sets keyed by the first glyph in each sequence.
|
|
55
|
+
#
|
|
56
|
+
# @param rules [Array<Hash>] {sequence:, result:}
|
|
57
|
+
# @param name_to_gid [Hash]
|
|
58
|
+
# @return [Hash{Integer=>Array}] gid → [{result_gid, components: [gids]}]
|
|
59
|
+
def self.build_ligature_sets(rules, name_to_gid)
|
|
60
|
+
sets = Hash.new { |h, k| h[k] = [] }
|
|
61
|
+
rules.each do |rule|
|
|
62
|
+
seq_gids = rule[:sequence].map { |n| name_to_gid[n] }
|
|
63
|
+
result_gid = name_to_gid[rule[:result]]
|
|
64
|
+
next unless seq_gids.all? && result_gid
|
|
65
|
+
|
|
66
|
+
first = seq_gids.first
|
|
67
|
+
components = seq_gids[1..]
|
|
68
|
+
sets[first] << { result_gid: result_gid, components: components }
|
|
69
|
+
end
|
|
70
|
+
sets
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# ---------- GSUB binary assembly ----------
|
|
74
|
+
|
|
75
|
+
# Build the complete GSUB table.
|
|
76
|
+
#
|
|
77
|
+
# @param tags [Array<String>] feature tags in lookup order
|
|
78
|
+
# @param lookups [Array<Hash>] one lookup per tag
|
|
79
|
+
# @return [String]
|
|
80
|
+
def self.build_gsub_table(tags, lookups)
|
|
81
|
+
io = StringIO.new(+"")
|
|
82
|
+
# Header: version (uint32 0x00010000), scriptListOffset,
|
|
83
|
+
# featureListOffset, lookupListOffset (each uint16)
|
|
84
|
+
header_size = 2 + 2 + 2 + 4 # version(4) + 3×uint16
|
|
85
|
+
|
|
86
|
+
# Serialize each list independently, then compute offsets.
|
|
87
|
+
script_list = build_script_list(tags.size)
|
|
88
|
+
feature_list = build_feature_list(tags)
|
|
89
|
+
lookup_list = build_lookup_list(lookups)
|
|
90
|
+
|
|
91
|
+
script_list_offset = header_size
|
|
92
|
+
feature_list_offset = script_list_offset + script_list.bytesize
|
|
93
|
+
lookup_list_offset = feature_list_offset + feature_list.bytesize
|
|
94
|
+
|
|
95
|
+
io << [0x00010000].pack("N")
|
|
96
|
+
io << [script_list_offset].pack("n")
|
|
97
|
+
io << [feature_list_offset].pack("n")
|
|
98
|
+
io << [lookup_list_offset].pack("n")
|
|
99
|
+
io << script_list
|
|
100
|
+
io << feature_list
|
|
101
|
+
io << lookup_list
|
|
102
|
+
io.string
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# ScriptList: one script (DFLT) with a default LangSys that
|
|
106
|
+
# references all features (featureIndex 0..count-1).
|
|
107
|
+
def self.build_script_list(feature_count)
|
|
108
|
+
io = +""
|
|
109
|
+
io << [1].pack("n") # scriptCount = 1
|
|
110
|
+
|
|
111
|
+
# ScriptRecord: tag(4) + scriptOffset(2)
|
|
112
|
+
# Script table starts right after the ScriptList header:
|
|
113
|
+
# scriptCount(2) + ScriptRecord(6) = 8
|
|
114
|
+
script_offset = 8
|
|
115
|
+
io << SCRIPT_DFLT
|
|
116
|
+
io << [script_offset].pack("n")
|
|
117
|
+
|
|
118
|
+
# Script table: defaultLangSysOffset(2) + langSysCount(2) = 4
|
|
119
|
+
# LangSys follows immediately
|
|
120
|
+
default_langsys_offset = script_offset + 4
|
|
121
|
+
io << [default_langsys_offset].pack("n")
|
|
122
|
+
io << [0].pack("n") # langSysCount
|
|
123
|
+
|
|
124
|
+
# LangSys: lookupOrder(2)=0, requiredFeatureIndex(2)=0xFFFF,
|
|
125
|
+
# featureIndexCount(2), featureIndices[]
|
|
126
|
+
io << [0].pack("n")
|
|
127
|
+
io << [0xFFFF].pack("n")
|
|
128
|
+
io << [feature_count].pack("n")
|
|
129
|
+
feature_count.times { |i| io << [i].pack("n") }
|
|
130
|
+
|
|
131
|
+
io
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# FeatureList: one FeatureRecord per tag. Each points to a
|
|
135
|
+
# Feature table with one lookup index (the lookup for that tag).
|
|
136
|
+
def self.build_feature_list(tags)
|
|
137
|
+
io = +""
|
|
138
|
+
io << [tags.size].pack("n") # featureCount
|
|
139
|
+
|
|
140
|
+
# FeatureRecords: tag(4) + featureOffset(2)
|
|
141
|
+
records_size = tags.size * 6
|
|
142
|
+
header_size = 2 + records_size
|
|
143
|
+
|
|
144
|
+
feature_records = +""
|
|
145
|
+
feature_tables = +""
|
|
146
|
+
tags.each_with_index do |tag, idx|
|
|
147
|
+
feature_records << tag.ljust(4, " ")[0, 4]
|
|
148
|
+
feature_records << [header_size + (idx * 4)].pack("n")
|
|
149
|
+
|
|
150
|
+
feature_tables << [0].pack("n") # featureParams (null)
|
|
151
|
+
feature_tables << [1].pack("n") # lookupIndexCount
|
|
152
|
+
feature_tables << [idx].pack("n") # lookupIndex (1:1 feature→lookup)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
io << feature_records
|
|
156
|
+
io << feature_tables
|
|
157
|
+
io
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# LookupList: one lookup per feature tag. Each lookup is a
|
|
161
|
+
# LigatureSubst (type 4) lookup.
|
|
162
|
+
def self.build_lookup_list(lookups)
|
|
163
|
+
io = +""
|
|
164
|
+
count = lookups.size
|
|
165
|
+
io << [count].pack("n") # lookupCount
|
|
166
|
+
|
|
167
|
+
# Serialize each lookup first, then compute offsets relative
|
|
168
|
+
# to the start of the LookupList.
|
|
169
|
+
serialized = lookups.map { |sets| build_ligature_lookup(sets) }
|
|
170
|
+
offset_table_size = 2 + (count * 2)
|
|
171
|
+
pos = offset_table_size
|
|
172
|
+
offsets = serialized.map do |s|
|
|
173
|
+
o = pos
|
|
174
|
+
pos += s.bytesize
|
|
175
|
+
o
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
offsets.each { |o| io << [o].pack("n") }
|
|
179
|
+
serialized.each { |s| io << s }
|
|
180
|
+
|
|
181
|
+
io
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Build a single LigatureSubst lookup (type 4, format 1).
|
|
185
|
+
def self.build_ligature_lookup(sets)
|
|
186
|
+
io = +""
|
|
187
|
+
# Lookup table: lookupType(2)=4, lookupFlag(2)=0, subTableCount(2)=1
|
|
188
|
+
io << [4].pack("n")
|
|
189
|
+
io << [0].pack("n")
|
|
190
|
+
io << [1].pack("n")
|
|
191
|
+
|
|
192
|
+
# Subtable offset (relative to start of Lookup table)
|
|
193
|
+
lookup_header_size = 6
|
|
194
|
+
io << [lookup_header_size].pack("n")
|
|
195
|
+
|
|
196
|
+
# LigatureSubstFormat1: format(2)=1, coverageOffset(2),
|
|
197
|
+
# ligatureSetCount(2), ligatureSetOffsets[]
|
|
198
|
+
first_glyphs = sets.keys.sort
|
|
199
|
+
set_count = first_glyphs.size
|
|
200
|
+
subst_header_size = 2 + 2 + 2 + (set_count * 2)
|
|
201
|
+
|
|
202
|
+
# Serialize each LigatureSet to compute offsets
|
|
203
|
+
serialized_sets = first_glyphs.map { |gid| sets[gid] }.map { |s| build_ligature_set(s) }
|
|
204
|
+
coverage = build_coverage(first_glyphs)
|
|
205
|
+
|
|
206
|
+
pos = subst_header_size
|
|
207
|
+
set_offsets = serialized_sets.map do |s|
|
|
208
|
+
o = pos
|
|
209
|
+
pos += s.bytesize
|
|
210
|
+
o
|
|
211
|
+
end
|
|
212
|
+
coverage_offset = pos
|
|
213
|
+
|
|
214
|
+
io << [1].pack("n") # substFormat = 1
|
|
215
|
+
io << [coverage_offset].pack("n")
|
|
216
|
+
io << [set_count].pack("n")
|
|
217
|
+
set_offsets.each { |o| io << [o].pack("n") }
|
|
218
|
+
serialized_sets.each { |s| io << s }
|
|
219
|
+
io << coverage
|
|
220
|
+
|
|
221
|
+
io
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Build a LigatureSet for one first glyph.
|
|
225
|
+
# @param ligatures [Array<Hash>] {result_gid:, components:}
|
|
226
|
+
def self.build_ligature_set(ligatures)
|
|
227
|
+
io = +""
|
|
228
|
+
count = ligatures.size
|
|
229
|
+
io << [count].pack("n")
|
|
230
|
+
|
|
231
|
+
# Serialize each Ligature record
|
|
232
|
+
serialized = ligatures.map { |lig| build_ligature(lig) }
|
|
233
|
+
pos = 2 + (count * 2)
|
|
234
|
+
offsets = serialized.map do |s|
|
|
235
|
+
o = pos
|
|
236
|
+
pos += s.bytesize
|
|
237
|
+
o
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
offsets.each { |o| io << [o].pack("n") }
|
|
241
|
+
serialized.each { |s| io << s }
|
|
242
|
+
io
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Build a single Ligature record.
|
|
246
|
+
# @param lig [Hash] {result_gid:, components: [gids]}
|
|
247
|
+
def self.build_ligature(lig)
|
|
248
|
+
io = +""
|
|
249
|
+
io << [lig[:result_gid]].pack("n")
|
|
250
|
+
io << [lig[:components].size + 1].pack("n") # componentCount includes first
|
|
251
|
+
lig[:components].each { |gid| io << [gid].pack("n") }
|
|
252
|
+
io
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Build a Coverage table (format 1 = glyph list).
|
|
256
|
+
# @param glyphs [Array<Integer>] sorted glyph IDs
|
|
257
|
+
def self.build_coverage(glyphs)
|
|
258
|
+
io = +""
|
|
259
|
+
io << [1].pack("n") # coverageFormat = 1
|
|
260
|
+
io << [glyphs.size].pack("n")
|
|
261
|
+
glyphs.each { |gid| io << [gid].pack("n") }
|
|
262
|
+
io
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
@@ -9,16 +9,13 @@ module Fontisan
|
|
|
9
9
|
#
|
|
10
10
|
# The orchestrator assembles a default-master UFO plus variation
|
|
11
11
|
# masters into a variable OpenType font with:
|
|
12
|
-
# - CFF2 outlines
|
|
12
|
+
# - CFF2 outlines with blend operators for each varying coordinate
|
|
13
|
+
# - ItemVariationStore embedded in the CFF2 table (regions only;
|
|
14
|
+
# deltas live in charstring blend operands)
|
|
13
15
|
# - fvar (axes + instances)
|
|
14
16
|
# - STAT (style attributes)
|
|
15
17
|
# - avar (axis remapping, when maps are provided)
|
|
16
18
|
#
|
|
17
|
-
# NOTE: the CFF2 table currently contains static outlines (no
|
|
18
|
-
# VariationStore, no blend operators). Full variable CFF2 requires
|
|
19
|
-
# TODO 07 (blend/vsindex) and TODO 18 (blend integration) to be
|
|
20
|
-
# wired into the charstring builder.
|
|
21
|
-
#
|
|
22
19
|
# @see https://learn.microsoft.com/en-us/typography/opentype/spec/cff2
|
|
23
20
|
class VariableOtf
|
|
24
21
|
# @param default_font [Fontisan::Ufo::Font] the default master
|
|
@@ -59,7 +56,7 @@ module Fontisan
|
|
|
59
56
|
"post" => Post.build(@default),
|
|
60
57
|
"hmtx" => Hmtx.build(@default, glyphs: glyphs),
|
|
61
58
|
"cmap" => Cmap.build(@default, glyphs: glyphs),
|
|
62
|
-
"CFF2" =>
|
|
59
|
+
"CFF2" => build_cff2(glyphs),
|
|
63
60
|
"fvar" => Fvar.build(@default, axes: @axes, instances: @instances),
|
|
64
61
|
"STAT" => Stat.build(axes: @axes),
|
|
65
62
|
}
|
|
@@ -69,6 +66,18 @@ module Fontisan
|
|
|
69
66
|
|
|
70
67
|
tables
|
|
71
68
|
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
# Build the CFF2 table. When masters are supplied, variable
|
|
73
|
+
# charstrings with blend operators are emitted; otherwise a static
|
|
74
|
+
# CFF2 table is produced.
|
|
75
|
+
def build_cff2(glyphs)
|
|
76
|
+
masters = @masters.map { |mf| { font: mf, axes: @axes } }
|
|
77
|
+
Cff2.build(@default, glyphs: glyphs,
|
|
78
|
+
masters: masters.any? ? masters : nil,
|
|
79
|
+
axis_count: @axes.size)
|
|
80
|
+
end
|
|
72
81
|
end
|
|
73
82
|
end
|
|
74
83
|
end
|
data/lib/fontisan/ufo/compile.rb
CHANGED
|
@@ -34,8 +34,10 @@ module Fontisan
|
|
|
34
34
|
autoload :OtfCompiler, "fontisan/ufo/compile/otf_compiler"
|
|
35
35
|
autoload :Filters, "fontisan/ufo/compile/filters"
|
|
36
36
|
autoload :FeatureWriters, "fontisan/ufo/compile/feature_writers"
|
|
37
|
-
autoload :Fvar,
|
|
37
|
+
autoload :Fvar, "fontisan/ufo/compile/fvar"
|
|
38
|
+
autoload :FeatureCompiler, "fontisan/ufo/compile/feature_compiler"
|
|
38
39
|
autoload :Gpos, "fontisan/ufo/compile/gpos"
|
|
40
|
+
autoload :Gsub, "fontisan/ufo/compile/gsub"
|
|
39
41
|
autoload :Gvar, "fontisan/ufo/compile/gvar"
|
|
40
42
|
autoload :Head, "fontisan/ufo/compile/head"
|
|
41
43
|
autoload :Hhea, "fontisan/ufo/compile/hhea"
|
data/lib/fontisan/ufo/font.rb
CHANGED
|
@@ -11,8 +11,9 @@ module Fontisan
|
|
|
11
11
|
# The class is the read/write API; serialization is handled by
|
|
12
12
|
# Fontisan::Ufo::Reader and Fontisan::Ufo::Writer.
|
|
13
13
|
class Font
|
|
14
|
-
attr_accessor :path, :info, :features, :kerning, :groups, :lib,
|
|
15
|
-
|
|
14
|
+
attr_accessor :path, :info, :features, :kerning, :groups, :lib,
|
|
15
|
+
:ufo_version, :images
|
|
16
|
+
attr_reader :layers, :data
|
|
16
17
|
|
|
17
18
|
def initialize
|
|
18
19
|
@path = nil
|
data/lib/fontisan/ufo/image.rb
CHANGED
|
@@ -3,13 +3,26 @@
|
|
|
3
3
|
module Fontisan
|
|
4
4
|
module Ufo
|
|
5
5
|
# A background image anchored to a glyph (common in color fonts).
|
|
6
|
+
#
|
|
7
|
+
# In UFO 3, images live in `UFO/images/` as binary blobs (PNG in
|
|
8
|
+
# practice). Each image is referenced from a glyph's GLIF via
|
|
9
|
+
# `<image fileName="..."/>`. The image set stores the actual bytes
|
|
10
|
+
# and deduplicates by sha256 content hash.
|
|
6
11
|
class Image
|
|
7
|
-
attr_reader :file_name, :transformation, :color
|
|
12
|
+
attr_reader :file_name, :transformation, :color, :sha, :bytes
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
# @param file_name [String] filename within the UFO images/ directory
|
|
15
|
+
# @param transformation [Hash, nil] UFO image transformation matrix
|
|
16
|
+
# @param color [String, nil] optional color hint
|
|
17
|
+
# @param sha [String, nil] content sha256 hex digest
|
|
18
|
+
# @param bytes [String, nil] raw image bytes (set by ImageSet)
|
|
19
|
+
def initialize(file_name:, transformation: nil, color: nil, sha: nil,
|
|
20
|
+
bytes: nil)
|
|
10
21
|
@file_name = file_name.to_s
|
|
11
22
|
@transformation = transformation
|
|
12
23
|
@color = color
|
|
24
|
+
@sha = sha
|
|
25
|
+
@bytes = bytes
|
|
13
26
|
end
|
|
14
27
|
end
|
|
15
28
|
end
|
|
@@ -1,19 +1,99 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "digest"
|
|
4
|
+
|
|
3
5
|
module Fontisan
|
|
4
6
|
module Ufo
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
+
# UFO 3 background image set. Lives at `UFO/images/` in a UFO
|
|
8
|
+
# source directory. Each image is a PNG file referenced by glyph
|
|
9
|
+
# GLIF `<image fileName="..."/>` elements.
|
|
10
|
+
#
|
|
11
|
+
# Per the UFO 3 spec, image files are arbitrary binary blobs (in
|
|
12
|
+
# practice always PNG). The image set deduplicates by content hash
|
|
13
|
+
# (sha256) so the same image referenced from multiple glyphs takes
|
|
14
|
+
# one slot.
|
|
15
|
+
#
|
|
16
|
+
# @example Read images from a UFO directory
|
|
17
|
+
# imageset = ImageSet.load_from_dir("MyFont.ufo/images")
|
|
18
|
+
# imageset.each { |img| puts img.file_name }
|
|
7
19
|
class ImageSet
|
|
20
|
+
include Enumerable
|
|
21
|
+
|
|
22
|
+
# @return [Hash{String=>Image}] filename → Image
|
|
8
23
|
attr_reader :images
|
|
9
24
|
|
|
10
25
|
def initialize
|
|
11
26
|
@images = {}
|
|
12
27
|
end
|
|
13
28
|
|
|
29
|
+
# Load all PNG files from a UFO `images/` directory.
|
|
30
|
+
#
|
|
31
|
+
# @param dir [String] path to the `images` directory
|
|
32
|
+
# @return [ImageSet]
|
|
33
|
+
def self.load_from_dir(dir)
|
|
34
|
+
is = new
|
|
35
|
+
return is unless File.directory?(dir)
|
|
36
|
+
|
|
37
|
+
Dir.each_child(dir).sort.each do |name|
|
|
38
|
+
path = File.join(dir, name)
|
|
39
|
+
next unless File.file?(path)
|
|
40
|
+
|
|
41
|
+
is.register_file(name, File.binread(path))
|
|
42
|
+
end
|
|
43
|
+
is
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Register an image from its filename and raw bytes. Computes the
|
|
47
|
+
# sha256 content hash automatically.
|
|
48
|
+
#
|
|
49
|
+
# @param file_name [String]
|
|
50
|
+
# @param bytes [String] binary image data
|
|
51
|
+
# @return [Image]
|
|
52
|
+
def register_file(file_name, bytes)
|
|
53
|
+
image = Image.new(
|
|
54
|
+
file_name: file_name,
|
|
55
|
+
sha: Digest::SHA256.hexdigest(bytes),
|
|
56
|
+
bytes: bytes,
|
|
57
|
+
)
|
|
58
|
+
@images[file_name.to_s] = image
|
|
59
|
+
image
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Look up an image by its filename.
|
|
63
|
+
#
|
|
64
|
+
# @param file_name [String]
|
|
65
|
+
# @return [Image, nil]
|
|
66
|
+
def find(file_name)
|
|
67
|
+
@images[file_name.to_s]
|
|
68
|
+
end
|
|
69
|
+
alias [] find
|
|
70
|
+
|
|
71
|
+
# Iterate over images in filename order.
|
|
72
|
+
def each(&)
|
|
73
|
+
return enum_for(:each) unless block_given?
|
|
74
|
+
|
|
75
|
+
@images.each_value(&)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Number of registered images.
|
|
79
|
+
def count
|
|
80
|
+
@images.size
|
|
81
|
+
end
|
|
82
|
+
|
|
14
83
|
def empty?
|
|
15
84
|
@images.empty?
|
|
16
85
|
end
|
|
86
|
+
|
|
87
|
+
# Write all images to a target `images/` directory. Useful for
|
|
88
|
+
# UFO round-trip serialization.
|
|
89
|
+
#
|
|
90
|
+
# @param dir [String] target `images` directory path
|
|
91
|
+
def write_to_dir(dir)
|
|
92
|
+
FileUtils.mkpath(dir) unless File.directory?(dir)
|
|
93
|
+
@images.each do |name, img|
|
|
94
|
+
File.binwrite(File.join(dir, name), img.bytes)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
17
97
|
end
|
|
18
98
|
end
|
|
19
99
|
end
|
data/lib/fontisan/ufo/reader.rb
CHANGED
|
@@ -35,6 +35,7 @@ module Fontisan
|
|
|
35
35
|
read_groups
|
|
36
36
|
read_features
|
|
37
37
|
read_lib
|
|
38
|
+
read_images
|
|
38
39
|
@font
|
|
39
40
|
end
|
|
40
41
|
|
|
@@ -147,6 +148,13 @@ module Fontisan
|
|
|
147
148
|
@font.lib = Lib.new(data)
|
|
148
149
|
end
|
|
149
150
|
|
|
151
|
+
def read_images
|
|
152
|
+
dir = join(@font.path, "images")
|
|
153
|
+
return unless File.directory?(dir)
|
|
154
|
+
|
|
155
|
+
@font.images = ImageSet.load_from_dir(dir)
|
|
156
|
+
end
|
|
157
|
+
|
|
150
158
|
def join(*parts)
|
|
151
159
|
File.join(*parts)
|
|
152
160
|
end
|
data/lib/fontisan/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fontisan
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.34
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -309,6 +309,7 @@ files:
|
|
|
309
309
|
- lib/fontisan/collection/table_deduplicator.rb
|
|
310
310
|
- lib/fontisan/collection/writer.rb
|
|
311
311
|
- lib/fontisan/commands.rb
|
|
312
|
+
- lib/fontisan/commands/audit_command.rb
|
|
312
313
|
- lib/fontisan/commands/base_command.rb
|
|
313
314
|
- lib/fontisan/commands/convert_command.rb
|
|
314
315
|
- lib/fontisan/commands/dump_table_command.rb
|
|
@@ -387,6 +388,7 @@ files:
|
|
|
387
388
|
- lib/fontisan/metrics_calculator.rb
|
|
388
389
|
- lib/fontisan/models.rb
|
|
389
390
|
- lib/fontisan/models/all_scripts_features_info.rb
|
|
391
|
+
- lib/fontisan/models/audit_report.rb
|
|
390
392
|
- lib/fontisan/models/bitmap_glyph.rb
|
|
391
393
|
- lib/fontisan/models/bitmap_strike.rb
|
|
392
394
|
- lib/fontisan/models/collection_brief_info.rb
|
|
@@ -486,6 +488,7 @@ files:
|
|
|
486
488
|
- lib/fontisan/subset/table_strategy/cbdt.rb
|
|
487
489
|
- lib/fontisan/subset/table_strategy/cblc.rb
|
|
488
490
|
- lib/fontisan/subset/table_strategy/cff.rb
|
|
491
|
+
- lib/fontisan/subset/table_strategy/cff2.rb
|
|
489
492
|
- lib/fontisan/subset/table_strategy/cmap.rb
|
|
490
493
|
- lib/fontisan/subset/table_strategy/color_bitmap_placement.rb
|
|
491
494
|
- lib/fontisan/subset/table_strategy/color_bitmap_strike_plan.rb
|
|
@@ -561,6 +564,7 @@ files:
|
|
|
561
564
|
- lib/fontisan/tables/cff2/dict_encoder.rb
|
|
562
565
|
- lib/fontisan/tables/cff2/fd_select.rb
|
|
563
566
|
- lib/fontisan/tables/cff2/header.rb
|
|
567
|
+
- lib/fontisan/tables/cff2/index.rb
|
|
564
568
|
- lib/fontisan/tables/cff2/index_builder.rb
|
|
565
569
|
- lib/fontisan/tables/cff2/operand_stack.rb
|
|
566
570
|
- lib/fontisan/tables/cff2/private_dict_blend_handler.rb
|
|
@@ -647,6 +651,7 @@ files:
|
|
|
647
651
|
- lib/fontisan/ufo/compile/cmap.rb
|
|
648
652
|
- lib/fontisan/ufo/compile/colr.rb
|
|
649
653
|
- lib/fontisan/ufo/compile/cpal.rb
|
|
654
|
+
- lib/fontisan/ufo/compile/feature_compiler.rb
|
|
650
655
|
- lib/fontisan/ufo/compile/feature_writers.rb
|
|
651
656
|
- lib/fontisan/ufo/compile/feature_writers/base.rb
|
|
652
657
|
- lib/fontisan/ufo/compile/feature_writers/curs.rb
|
|
@@ -668,6 +673,7 @@ files:
|
|
|
668
673
|
- lib/fontisan/ufo/compile/fvar.rb
|
|
669
674
|
- lib/fontisan/ufo/compile/glyf_loca.rb
|
|
670
675
|
- lib/fontisan/ufo/compile/gpos.rb
|
|
676
|
+
- lib/fontisan/ufo/compile/gsub.rb
|
|
671
677
|
- lib/fontisan/ufo/compile/gvar.rb
|
|
672
678
|
- lib/fontisan/ufo/compile/head.rb
|
|
673
679
|
- lib/fontisan/ufo/compile/hhea.rb
|