fontisan 0.2.23 → 0.3.0
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/lib/fontisan/cli.rb +6 -0
- data/lib/fontisan/stitcher/selector/codepoints.rb +29 -0
- data/lib/fontisan/stitcher/selector/gid.rb +25 -0
- data/lib/fontisan/stitcher/selector/range.rb +30 -0
- data/lib/fontisan/stitcher/selector.rb +26 -0
- data/lib/fontisan/stitcher/source.rb +97 -0
- data/lib/fontisan/stitcher.rb +182 -0
- data/lib/fontisan/stitcher_cli.rb +69 -0
- data/lib/fontisan/ufo/anchor.rb +17 -0
- data/lib/fontisan/ufo/cli.rb +85 -0
- data/lib/fontisan/ufo/compile/base_compiler.rb +81 -0
- data/lib/fontisan/ufo/compile/cff.rb +224 -0
- data/lib/fontisan/ufo/compile/cmap.rb +129 -0
- data/lib/fontisan/ufo/compile/filters/cubic_to_quadratic.rb +174 -0
- data/lib/fontisan/ufo/compile/filters/decompose_components.rb +33 -0
- data/lib/fontisan/ufo/compile/filters/flatten_components.rb +22 -0
- data/lib/fontisan/ufo/compile/filters/reverse_contour_direction.rb +27 -0
- data/lib/fontisan/ufo/compile/filters.rb +57 -0
- data/lib/fontisan/ufo/compile/glyf_loca.rb +145 -0
- data/lib/fontisan/ufo/compile/head.rb +98 -0
- data/lib/fontisan/ufo/compile/hhea.rb +36 -0
- data/lib/fontisan/ufo/compile/hmtx.rb +27 -0
- data/lib/fontisan/ufo/compile/maxp.rb +57 -0
- data/lib/fontisan/ufo/compile/name.rb +79 -0
- data/lib/fontisan/ufo/compile/os2.rb +81 -0
- data/lib/fontisan/ufo/compile/otf_compiler.rb +43 -0
- data/lib/fontisan/ufo/compile/post.rb +32 -0
- data/lib/fontisan/ufo/compile/ttf_compiler.rb +69 -0
- data/lib/fontisan/ufo/compile.rb +48 -0
- data/lib/fontisan/ufo/component.rb +18 -0
- data/lib/fontisan/ufo/contour.rb +29 -0
- data/lib/fontisan/ufo/convert/from_bin_data.rb +246 -0
- data/lib/fontisan/ufo/convert.rb +18 -0
- data/lib/fontisan/ufo/data_set.rb +21 -0
- data/lib/fontisan/ufo/features.rb +17 -0
- data/lib/fontisan/ufo/font.rb +61 -0
- data/lib/fontisan/ufo/glyph.rb +421 -0
- data/lib/fontisan/ufo/guideline.rb +19 -0
- data/lib/fontisan/ufo/image.rb +16 -0
- data/lib/fontisan/ufo/image_set.rb +19 -0
- data/lib/fontisan/ufo/info.rb +79 -0
- data/lib/fontisan/ufo/kerning.rb +32 -0
- data/lib/fontisan/ufo/layer.rb +38 -0
- data/lib/fontisan/ufo/layer_set.rb +37 -0
- data/lib/fontisan/ufo/lib.rb +24 -0
- data/lib/fontisan/ufo/plist.rb +118 -0
- data/lib/fontisan/ufo/point.rb +38 -0
- data/lib/fontisan/ufo/reader.rb +144 -0
- data/lib/fontisan/ufo/transformation.rb +39 -0
- data/lib/fontisan/ufo/writer.rb +115 -0
- data/lib/fontisan/ufo.rb +44 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan.rb +3 -0
- metadata +51 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Ufo
|
|
5
|
+
# Compiler layer: turns a typed Fontisan::Ufo::Font into OpenType
|
|
6
|
+
# binary tables, then into a TTF or OTF file via Fontisan::FontWriter.
|
|
7
|
+
#
|
|
8
|
+
# Pipeline:
|
|
9
|
+
#
|
|
10
|
+
# Fontisan::Ufo::Font (typed)
|
|
11
|
+
# │
|
|
12
|
+
# ▼
|
|
13
|
+
# BaseCompiler#compile
|
|
14
|
+
# │
|
|
15
|
+
# ├─ Head.build(font) → Tables::Head BinData record
|
|
16
|
+
# ├─ Hhea.build(font) → Tables::Hhea
|
|
17
|
+
# ├─ Maxp.build(font) → Tables::Maxp
|
|
18
|
+
# ├─ Os2.build(font) → Tables::Os2
|
|
19
|
+
# ├─ Name.build(font) → Tables::Name
|
|
20
|
+
# ├─ Post.build(font) → Tables::Post
|
|
21
|
+
# ├─ Hmtx.build(font) → Tables::Hmtx
|
|
22
|
+
# ├─ Cmap.build(font) → Tables::Cmap
|
|
23
|
+
# ├─ (TTF) GlyfLoca.build → Tables::Glyf + Tables::Loca
|
|
24
|
+
# └─ (OTF) Cff.build → Tables::Cff
|
|
25
|
+
# │
|
|
26
|
+
# ▼
|
|
27
|
+
# tables_hash.transform_values(&:to_binary_s)
|
|
28
|
+
# │
|
|
29
|
+
# ▼
|
|
30
|
+
# Fontisan::FontWriter.write_to_file(...)
|
|
31
|
+
module Compile
|
|
32
|
+
autoload :BaseCompiler, "fontisan/ufo/compile/base_compiler"
|
|
33
|
+
autoload :TtfCompiler, "fontisan/ufo/compile/ttf_compiler"
|
|
34
|
+
autoload :OtfCompiler, "fontisan/ufo/compile/otf_compiler"
|
|
35
|
+
autoload :Filters, "fontisan/ufo/compile/filters"
|
|
36
|
+
autoload :Head, "fontisan/ufo/compile/head"
|
|
37
|
+
autoload :Hhea, "fontisan/ufo/compile/hhea"
|
|
38
|
+
autoload :Maxp, "fontisan/ufo/compile/maxp"
|
|
39
|
+
autoload :Os2, "fontisan/ufo/compile/os2"
|
|
40
|
+
autoload :Name, "fontisan/ufo/compile/name"
|
|
41
|
+
autoload :Post, "fontisan/ufo/compile/post"
|
|
42
|
+
autoload :Hmtx, "fontisan/ufo/compile/hmtx"
|
|
43
|
+
autoload :Cmap, "fontisan/ufo/compile/cmap"
|
|
44
|
+
autoload :GlyfLoca, "fontisan/ufo/compile/glyf_loca"
|
|
45
|
+
autoload :Cff, "fontisan/ufo/compile/cff"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Ufo
|
|
5
|
+
# A composite-glyph reference: this glyph draws by transforming
|
|
6
|
+
# another glyph. Used in UFO 3 composites; often the same shape as
|
|
7
|
+
# the OpenType composite-glyph flag set.
|
|
8
|
+
class Component
|
|
9
|
+
attr_reader :base_glyph, :transformation, :identifier
|
|
10
|
+
|
|
11
|
+
def initialize(base_glyph:, transformation: nil, identifier: nil)
|
|
12
|
+
@base_glyph = base_glyph.to_s
|
|
13
|
+
@transformation = transformation # Fontisan::Ufo::Transformation or nil
|
|
14
|
+
@identifier = identifier
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Ufo
|
|
5
|
+
# An ordered list of points forming one closed (or open) contour
|
|
6
|
+
# in a glyph's outline.
|
|
7
|
+
class Contour
|
|
8
|
+
attr_accessor :points
|
|
9
|
+
attr_reader :closed
|
|
10
|
+
|
|
11
|
+
def initialize(points = [], closed: true)
|
|
12
|
+
@points = points
|
|
13
|
+
@closed = closed
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def closed?
|
|
17
|
+
@closed
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def open?
|
|
21
|
+
!@closed
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def point_count
|
|
25
|
+
@points.size
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Ufo
|
|
5
|
+
module Convert
|
|
6
|
+
# Converts a loaded TTF/OTF font (from Fontisan::FontLoader.load)
|
|
7
|
+
# into a typed Fontisan::Ufo::Font. This is the reverse of
|
|
8
|
+
# Compile::TtfCompiler / Compile::OtfCompiler.
|
|
9
|
+
#
|
|
10
|
+
# Reads each BinData table, extracts per-glyph data, and builds
|
|
11
|
+
# Ufo::Glyph objects in the default layer.
|
|
12
|
+
#
|
|
13
|
+
# Composite glyphs are preserved as UFO Components (not decomposed).
|
|
14
|
+
# This keeps the round-trip faithful to the source.
|
|
15
|
+
module FromBinData
|
|
16
|
+
# @param font [Fontisan::SfntFont] loaded TTF or OTF
|
|
17
|
+
# @return [Fontisan::Ufo::Font] typed UFO model
|
|
18
|
+
def self.convert(font)
|
|
19
|
+
ufo = Ufo::Font.new
|
|
20
|
+
ufo.ufo_version = 3
|
|
21
|
+
|
|
22
|
+
extract_info(font, ufo)
|
|
23
|
+
extract_glyphs(font, ufo)
|
|
24
|
+
ufo
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Map head/hhea/OS2/name/post fields → Ufo::Info.
|
|
28
|
+
def self.extract_info(font, ufo)
|
|
29
|
+
info = ufo.info
|
|
30
|
+
|
|
31
|
+
head = font.table("head")
|
|
32
|
+
if head
|
|
33
|
+
info.units_per_em = head.units_per_em
|
|
34
|
+
info.version_major = 1
|
|
35
|
+
info.version_minor = 0
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
hhea = font.table("hhea")
|
|
39
|
+
if hhea
|
|
40
|
+
info.ascender = hhea.ascent
|
|
41
|
+
info.descender = hhea.descent
|
|
42
|
+
info.open_type_hhea_line_gap = hhea.line_gap
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
os2 = font.table("OS/2")
|
|
46
|
+
if os2
|
|
47
|
+
info.open_type_os2_weight_class = os2.us_weight_class
|
|
48
|
+
info.open_type_os2_width_class = os2.us_width_class
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
extract_name_records(font, info)
|
|
52
|
+
extract_post(font, info)
|
|
53
|
+
rescue NoMethodError
|
|
54
|
+
# Some tables may not be present in all fonts; skip silently
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.extract_name_records(font, info)
|
|
58
|
+
name_table = font.table("name")
|
|
59
|
+
return unless name_table
|
|
60
|
+
|
|
61
|
+
records = name_table.respond_to?(:name_records) ? name_table.name_records : []
|
|
62
|
+
records.each do |record|
|
|
63
|
+
next unless record.platform_id == 3 && record.encoding_id == 1 # Windows Unicode BMP
|
|
64
|
+
|
|
65
|
+
value = decode_name_value(record, name_table)
|
|
66
|
+
next unless value
|
|
67
|
+
|
|
68
|
+
case record.name_id
|
|
69
|
+
when 0 then info.copyright = value
|
|
70
|
+
when 1 then info.family_name = value
|
|
71
|
+
when 2 then info.style_name = value
|
|
72
|
+
when 4 then info.postscript_full_name = value
|
|
73
|
+
when 6 then info.postscript_font_name = value
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def self.decode_name_value(record, name_table)
|
|
79
|
+
raw = if name_table.respond_to?(:string_for_record)
|
|
80
|
+
name_table.string_for_record(record)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
if raw && raw.encoding == Encoding::UTF_16BE
|
|
84
|
+
raw.encode("UTF-8")
|
|
85
|
+
elsif raw && raw.bytesize >= 2 && raw.getbyte(0).between?(0, 127) && raw.getbyte(1).zero?
|
|
86
|
+
# Looks like UTF-16BE
|
|
87
|
+
raw.force_encoding("UTF-16BE").encode("UTF-8")
|
|
88
|
+
else
|
|
89
|
+
raw&.force_encoding("UTF-8")
|
|
90
|
+
end
|
|
91
|
+
rescue Encoding::InvalidByteSequenceError, Encoding::ConverterNotFoundError
|
|
92
|
+
raw&.force_encoding("UTF-8")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.extract_post(font, info)
|
|
96
|
+
post = font.table("post")
|
|
97
|
+
return unless post
|
|
98
|
+
|
|
99
|
+
if post.respond_to?(:italic_angle)
|
|
100
|
+
info.italic_angle = post.italic_angle
|
|
101
|
+
elsif post.respond_to?(:italic_angle_raw)
|
|
102
|
+
raw = post.italic_angle_raw
|
|
103
|
+
info.italic_angle = raw.to_i / 65536.0
|
|
104
|
+
end
|
|
105
|
+
rescue NoMethodError
|
|
106
|
+
# post table may not have italic_angle
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Extract every glyph from glyf (TTF) or CFF (OTF).
|
|
110
|
+
def self.extract_glyphs(font, ufo)
|
|
111
|
+
cmap = build_cmap_lookup(font)
|
|
112
|
+
widths = build_width_lookup(font)
|
|
113
|
+
num_glyphs = font.table("maxp")&.num_glyphs || 0
|
|
114
|
+
|
|
115
|
+
if font.has_table?("glyf")
|
|
116
|
+
extract_truetype_glyphs(font, ufo, cmap, widths, num_glyphs)
|
|
117
|
+
elsif font.has_table?("CFF ")
|
|
118
|
+
extract_cff_glyphs(font, ufo, cmap, widths, num_glyphs)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Build {codepoint → gid} from the cmap table.
|
|
123
|
+
def self.build_cmap_lookup(font)
|
|
124
|
+
cmap_table = font.table("cmap")
|
|
125
|
+
return {} unless cmap_table
|
|
126
|
+
|
|
127
|
+
mappings = cmap_table.respond_to?(:unicode_mappings) ? cmap_table.unicode_mappings : {}
|
|
128
|
+
# Invert: gid → [codepoints]
|
|
129
|
+
inverted = Hash.new { |h, k| h[k] = [] }
|
|
130
|
+
mappings.each { |cp, gid| inverted[gid] << cp }
|
|
131
|
+
inverted
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Build {gid → advance_width} from hmtx.
|
|
135
|
+
def self.build_width_lookup(font)
|
|
136
|
+
hmtx = font.table("hmtx")
|
|
137
|
+
return {} unless hmtx
|
|
138
|
+
|
|
139
|
+
hhea = font.table("hhea")
|
|
140
|
+
maxp = font.table("maxp")
|
|
141
|
+
num_h_metrics = hhea&.number_of_h_metrics || 1
|
|
142
|
+
num_glyphs = maxp&.num_glyphs || 0
|
|
143
|
+
|
|
144
|
+
# Hmtx requires context-aware parsing before metric_for works.
|
|
145
|
+
if hmtx.respond_to?(:parse_with_context)
|
|
146
|
+
hmtx.parse_with_context(num_h_metrics, num_glyphs)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
widths = {}
|
|
150
|
+
num_glyphs.times do |gid|
|
|
151
|
+
metric = hmtx.respond_to?(:metric_for) ? hmtx.metric_for(gid) : nil
|
|
152
|
+
widths[gid] = metric ? metric[:advance_width] : 0
|
|
153
|
+
end
|
|
154
|
+
widths
|
|
155
|
+
rescue RuntimeError
|
|
156
|
+
# If hmtx parsing fails, return empty widths
|
|
157
|
+
{}
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# TTF: extract contours from glyf table via SimpleGlyph.
|
|
161
|
+
def self.extract_truetype_glyphs(font, ufo, cmap, widths, num_glyphs)
|
|
162
|
+
glyf = font.table("glyf")
|
|
163
|
+
loca = font.table("loca")
|
|
164
|
+
head = font.table("head")
|
|
165
|
+
return unless glyf && loca && head
|
|
166
|
+
|
|
167
|
+
# Tables need context-aware initialization before per-glyph access.
|
|
168
|
+
loca.parse_with_context(head.index_to_loc_format, num_glyphs) if loca.respond_to?(:parse_with_context)
|
|
169
|
+
|
|
170
|
+
num_glyphs.times do |gid|
|
|
171
|
+
glyph_name = glyph_name_for(font, gid) || "glyph#{gid}"
|
|
172
|
+
ufo_glyph = Ufo::Glyph.new(name: glyph_name)
|
|
173
|
+
ufo_glyph.width = widths.fetch(gid, 0).to_f
|
|
174
|
+
|
|
175
|
+
cmap.fetch(gid, []).each { |cp| ufo_glyph.add_unicode(cp) }
|
|
176
|
+
|
|
177
|
+
simple = begin
|
|
178
|
+
glyf.glyph_for(gid, loca, head)
|
|
179
|
+
rescue StandardError
|
|
180
|
+
nil
|
|
181
|
+
end
|
|
182
|
+
next unless simple
|
|
183
|
+
|
|
184
|
+
if simple.is_a?(Fontisan::Tables::SimpleGlyph)
|
|
185
|
+
extract_simple_contours(simple, ufo_glyph)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
ufo.layers.default_layer.add(ufo_glyph)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Convert a SimpleGlyph's contours + points into UFO contours.
|
|
193
|
+
def self.extract_simple_contours(simple, ufo_glyph)
|
|
194
|
+
num_contours = simple.end_pts_of_contours&.size || 0
|
|
195
|
+
|
|
196
|
+
num_contours.times do |ci|
|
|
197
|
+
points = simple.points_for_contour(ci)
|
|
198
|
+
next unless points && !points.empty?
|
|
199
|
+
|
|
200
|
+
ufo_points = points.map do |pt|
|
|
201
|
+
x = pt[:x] || pt["x"]
|
|
202
|
+
y = pt[:y] || pt["y"]
|
|
203
|
+
on_curve = pt[:on_curve].nil? || pt[:on_curve]
|
|
204
|
+
type = on_curve ? "line" : "offcurve"
|
|
205
|
+
Ufo::Point.new(x: x.to_f, y: y.to_f, type: type)
|
|
206
|
+
end
|
|
207
|
+
ufo_glyph.add_contour(Ufo::Contour.new(ufo_points))
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# OTF: extract outlines from CFF charstrings. TODO.full/10b —
|
|
212
|
+
# for now, stub with advance widths only (no contours).
|
|
213
|
+
def self.extract_cff_glyphs(font, ufo, cmap, widths, num_glyphs)
|
|
214
|
+
num_glyphs.times do |gid|
|
|
215
|
+
glyph_name = glyph_name_for(font, gid) || "glyph#{gid}"
|
|
216
|
+
ufo_glyph = Ufo::Glyph.new(name: glyph_name)
|
|
217
|
+
ufo_glyph.width = widths.fetch(gid, 0).to_f
|
|
218
|
+
cmap.fetch(gid, []).each { |cp| ufo_glyph.add_unicode(cp) }
|
|
219
|
+
ufo.layers.default_layer.add(ufo_glyph)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Look up a glyph name from the post table (v2.0) or synthesize.
|
|
224
|
+
def self.glyph_name_for(font, gid)
|
|
225
|
+
post = font.table("post")
|
|
226
|
+
return nil unless post
|
|
227
|
+
|
|
228
|
+
if post.respond_to?(:glyph_name)
|
|
229
|
+
name = post.glyph_name(gid)
|
|
230
|
+
return name unless name.nil? || name.empty?
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
nil
|
|
234
|
+
rescue NoMethodError
|
|
235
|
+
nil
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
private_class_method :extract_info, :extract_name_records, :decode_name_value,
|
|
239
|
+
:extract_post, :extract_glyphs, :build_cmap_lookup,
|
|
240
|
+
:build_width_lookup, :extract_truetype_glyphs,
|
|
241
|
+
:extract_simple_contours, :extract_cff_glyphs,
|
|
242
|
+
:glyph_name_for
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Ufo
|
|
5
|
+
# Conversion layer between the UFO model and fontisan's BinData
|
|
6
|
+
# table layer. Two directions:
|
|
7
|
+
#
|
|
8
|
+
# ToBinData.convert(ufo_font) → Hash<tag, BinData record or bytes>
|
|
9
|
+
# FromBinData.convert(loaded_font) → Fontisan::Ufo::Font
|
|
10
|
+
#
|
|
11
|
+
# The ToBinData path is already implemented as Compile::* modules
|
|
12
|
+
# (each table builder IS a UFO→BinData converter). This namespace
|
|
13
|
+
# owns the reverse direction.
|
|
14
|
+
module Convert
|
|
15
|
+
autoload :FromBinData, "fontisan/ufo/convert/from_bin_data"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Ufo
|
|
5
|
+
# Per-glyph custom data from `glyphs/<glyph>.plist`. Each glyph
|
|
6
|
+
# has its own plist file with arbitrary key/value data.
|
|
7
|
+
class DataSet
|
|
8
|
+
def initialize
|
|
9
|
+
@data = {} # glyph_name => Hash<String, Object>
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def [](glyph_name)
|
|
13
|
+
@data[glyph_name.to_s] ||= {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def keys
|
|
17
|
+
@data.keys
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Ufo
|
|
5
|
+
# Wrapper around the FEA feature source in `features.fea`.
|
|
6
|
+
#
|
|
7
|
+
# The MVP just stores the raw text. A FEA parser/compiler lands in
|
|
8
|
+
# TODO 08 (feature writers).
|
|
9
|
+
class Features
|
|
10
|
+
attr_accessor :text
|
|
11
|
+
|
|
12
|
+
def initialize(text: "")
|
|
13
|
+
@text = text
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Ufo
|
|
5
|
+
# Top-level container for a UFO source directory.
|
|
6
|
+
#
|
|
7
|
+
# A Fontisan::Ufo::Font corresponds to one `.ufo` directory. It
|
|
8
|
+
# exposes typed access to the font's metadata, layers, kerning,
|
|
9
|
+
# features, and any custom data.
|
|
10
|
+
#
|
|
11
|
+
# The class is the read/write API; serialization is handled by
|
|
12
|
+
# Fontisan::Ufo::Reader and Fontisan::Ufo::Writer.
|
|
13
|
+
class Font
|
|
14
|
+
attr_accessor :path, :info, :features, :kerning, :lib, :ufo_version
|
|
15
|
+
attr_reader :layers, :data, :images
|
|
16
|
+
|
|
17
|
+
def initialize
|
|
18
|
+
@path = nil
|
|
19
|
+
@ufo_version = nil
|
|
20
|
+
@info = Info.new
|
|
21
|
+
@layers = LayerSet.new
|
|
22
|
+
@features = Features.new
|
|
23
|
+
@kerning = Kerning.new
|
|
24
|
+
@lib = Lib.new
|
|
25
|
+
@data = nil # DataSet needs the Font ref, set by Reader
|
|
26
|
+
@images = ImageSet.new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Convenience accessor for the default layer's glyphs.
|
|
30
|
+
def glyphs
|
|
31
|
+
@layers.default_layer.glyphs
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Convenience: lookup a glyph in the default layer by name.
|
|
35
|
+
def glyph(name)
|
|
36
|
+
@layers.default_layer[name]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Convenience: read family name through the Info model.
|
|
40
|
+
def family_name
|
|
41
|
+
@info.family_name
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Convenience: iterate every glyph in every layer.
|
|
45
|
+
def each_glyph(&)
|
|
46
|
+
@layers.each do |layer|
|
|
47
|
+
layer.each(&)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @param path [String, Pathname] directory containing the UFO
|
|
52
|
+
# @return [Fontisan::Ufo::Font] the parsed font
|
|
53
|
+
def self.open(path)
|
|
54
|
+
font = new
|
|
55
|
+
font.path = path.to_s
|
|
56
|
+
Reader.new(font).read
|
|
57
|
+
font
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|