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,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Ufo
|
|
7
|
+
# Minimal XML plist parser/serializer for the files that UFO v3
|
|
8
|
+
# uses. UFO uses XML plists exclusively; binary plists are not
|
|
9
|
+
# used in the spec.
|
|
10
|
+
#
|
|
11
|
+
# The parser is typed at the value level: strings stay as String,
|
|
12
|
+
# integers stay as Integer, booleans become true/false, dicts
|
|
13
|
+
# become Hash<String, Object>, arrays become Array<Object>.
|
|
14
|
+
# Dates, data blobs, and nested structures are supported.
|
|
15
|
+
module Plist
|
|
16
|
+
class ParseError < StandardError; end
|
|
17
|
+
|
|
18
|
+
PLIST_DTD = "-//Apple//DTD PLIST 1.0//EN"
|
|
19
|
+
PLIST_NS = "http://www.apple.com/DTDs/PropertyList-1.0.dtd"
|
|
20
|
+
|
|
21
|
+
# @param source [String, Nokogiri::XML::Document] the XML plist
|
|
22
|
+
# @return [Object] the deserialized value
|
|
23
|
+
def self.parse(source)
|
|
24
|
+
doc = source.is_a?(Nokogiri::XML::Document) ? source : Nokogiri::XML(source)
|
|
25
|
+
root = doc.root
|
|
26
|
+
raise ParseError, "no <plist> root element" unless root&.name == "plist"
|
|
27
|
+
|
|
28
|
+
parse_value(root.children.find { |c| c.element? || c.cdata? || c.text? && !c.text.strip.empty? })
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param value [Object] the value to serialize
|
|
32
|
+
# @return [String] the XML plist text
|
|
33
|
+
def self.emit(value)
|
|
34
|
+
builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
|
|
35
|
+
create_doc_internal_subset(xml)
|
|
36
|
+
xml.plist(version: "1.0") do
|
|
37
|
+
emit_value(xml, value)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
builder.to_xml(
|
|
41
|
+
save_with:
|
|
42
|
+
Nokogiri::XML::Node::SaveOptions::AS_XML |
|
|
43
|
+
Nokogiri::XML::Node::SaveOptions::NO_DECLARATION,
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [Nokogiri::XML::Document] the typed root
|
|
48
|
+
def self.parse_document(source)
|
|
49
|
+
source.is_a?(Nokogiri::XML::Document) ? source : Nokogiri::XML(source)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# ---------- private-ish (called via module_function) ----------
|
|
53
|
+
|
|
54
|
+
def self.parse_value(node)
|
|
55
|
+
return nil if node.nil?
|
|
56
|
+
|
|
57
|
+
case node.name
|
|
58
|
+
when "string" then node.text
|
|
59
|
+
when "integer" then node.text.to_i
|
|
60
|
+
when "real" then node.text.to_f
|
|
61
|
+
when "true" then true
|
|
62
|
+
when "false" then false
|
|
63
|
+
when "dict" then parse_dict(node)
|
|
64
|
+
when "array" then parse_array(node)
|
|
65
|
+
else
|
|
66
|
+
raise ParseError, "unsupported plist element: <#{node.name}>"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.parse_dict(node)
|
|
71
|
+
result = {}
|
|
72
|
+
children = node.element_children.to_a
|
|
73
|
+
# dict children are key/value pairs (key first, then value)
|
|
74
|
+
while children.any?
|
|
75
|
+
key_node = children.shift
|
|
76
|
+
raise ParseError, "dict key is not <key>" unless key_node.name == "key"
|
|
77
|
+
|
|
78
|
+
value_node = children.shift
|
|
79
|
+
result[key_node.text] = parse_value(value_node)
|
|
80
|
+
end
|
|
81
|
+
result
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.parse_array(node)
|
|
85
|
+
node.element_children.map { |child| parse_value(child) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def self.create_doc_internal_subset(xml)
|
|
89
|
+
xml.doc.create_internal_subset("plist", PLIST_DTD, PLIST_NS)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def self.emit_value(xml, value)
|
|
93
|
+
case value
|
|
94
|
+
when nil
|
|
95
|
+
# No <nil/> in Apple's plist DTD; use a sentinel string.
|
|
96
|
+
xml.string("")
|
|
97
|
+
when true then xml.__send__(true)
|
|
98
|
+
when false then xml.__send__(false)
|
|
99
|
+
when Integer then xml.integer(value)
|
|
100
|
+
when Float then xml.real(value)
|
|
101
|
+
when String then xml.string(value)
|
|
102
|
+
when Symbol then xml.string(value.to_s)
|
|
103
|
+
when Array then xml.array { value.each { |v| emit_value(xml, v) } }
|
|
104
|
+
when Hash then xml.dict { emit_dict_body(xml, value) }
|
|
105
|
+
else
|
|
106
|
+
raise ArgumentError, "cannot plist-encode #{value.class}"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def self.emit_dict_body(xml, hash)
|
|
111
|
+
hash.each do |k, v|
|
|
112
|
+
xml.key(k.to_s)
|
|
113
|
+
emit_value(xml, v)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Ufo
|
|
5
|
+
# A single outline point. UFO's `point` element has:
|
|
6
|
+
# - x, y (Float, font units)
|
|
7
|
+
# - type (one of "move", "line", "offcurve", "curve", "qcurve")
|
|
8
|
+
# - smooth (Bool, optional)
|
|
9
|
+
#
|
|
10
|
+
# "offcurve" is the UFO 1/2 name; UFO 3 uses "qcurve". Both are
|
|
11
|
+
# accepted on read.
|
|
12
|
+
class Point
|
|
13
|
+
attr_reader :x, :y, :type, :smooth
|
|
14
|
+
|
|
15
|
+
def initialize(x:, y:, type:, smooth: false)
|
|
16
|
+
@x = x
|
|
17
|
+
@y = y
|
|
18
|
+
@type = type.to_s
|
|
19
|
+
@smooth = smooth
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def on_curve?
|
|
23
|
+
@type == "line" || @type == "move" || @type == "curve"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def off_curve?
|
|
27
|
+
@type == "offcurve" || @type == "qcurve"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @return [Hash] suitable for `to_glif` output
|
|
31
|
+
def to_h
|
|
32
|
+
h = { x: @x, y: @y, type: @type }
|
|
33
|
+
h[:smooth] = true if @smooth
|
|
34
|
+
h
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Ufo
|
|
5
|
+
# Reads a UFO source directory into a typed Fontisan::Ufo::Font.
|
|
6
|
+
#
|
|
7
|
+
# Reads, in order:
|
|
8
|
+
# - `metainfo.plist` (UFO version stamp; informational)
|
|
9
|
+
# - `fontinfo.plist` (Info)
|
|
10
|
+
# - `layercontents.plist` (layer ordering; optional in UFO 3)
|
|
11
|
+
# - `glyphs/contents.plist` (default-layer glyph order)
|
|
12
|
+
# - `glyphs/<layer>/<name>.glif` (per-glyph XML)
|
|
13
|
+
# - `kerning.plist` (Kerning)
|
|
14
|
+
# - `features.fea` (Features)
|
|
15
|
+
# - `lib.plist` (Lib)
|
|
16
|
+
#
|
|
17
|
+
# Phase 1 reads `fontinfo.plist` + `glyphs/contents.plist` +
|
|
18
|
+
# minimal `.glif` (name, advance, unicodes). Full `.glif`
|
|
19
|
+
# decoding (contours, components, anchors) lands when the
|
|
20
|
+
# compiler layer is built.
|
|
21
|
+
class Reader
|
|
22
|
+
attr_reader :font
|
|
23
|
+
|
|
24
|
+
def initialize(font)
|
|
25
|
+
@font = font
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [Fontisan::Ufo::Font]
|
|
29
|
+
def read
|
|
30
|
+
read_metainfo
|
|
31
|
+
read_fontinfo
|
|
32
|
+
read_layercontents
|
|
33
|
+
read_glyphs_contents
|
|
34
|
+
read_kerning
|
|
35
|
+
read_features
|
|
36
|
+
read_lib
|
|
37
|
+
@font
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def read_metainfo
|
|
43
|
+
path = join(@font.path, "metainfo.plist")
|
|
44
|
+
return unless File.exist?(path)
|
|
45
|
+
|
|
46
|
+
data = Plist.parse(File.read(path))
|
|
47
|
+
@font.ufo_version = data["formatVersion"]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def read_fontinfo
|
|
51
|
+
path = join(@font.path, "fontinfo.plist")
|
|
52
|
+
return unless File.exist?(path)
|
|
53
|
+
|
|
54
|
+
data = Plist.parse(File.read(path))
|
|
55
|
+
@font.info = Info.new(data)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def read_layercontents
|
|
59
|
+
# layercontents.plist is optional in UFO 3; default layer is
|
|
60
|
+
# always present via LayerSet#initialize.
|
|
61
|
+
path = join(@font.path, "layercontents.plist")
|
|
62
|
+
return unless File.exist?(path)
|
|
63
|
+
|
|
64
|
+
order = Plist.parse(File.read(path))
|
|
65
|
+
order.each do |layer_name|
|
|
66
|
+
@font.layers.add(layer_name)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def read_glyphs_contents
|
|
71
|
+
# Each layer's glyphs may live under `glyphs/<layer_name>/` (UFO 3)
|
|
72
|
+
# or directly under `glyphs/` (UFO 2, single-layer case).
|
|
73
|
+
# The latter applies when `glyphs/<layer_name>/` does not exist
|
|
74
|
+
# but `glyphs/contents.plist` does.
|
|
75
|
+
@font.layers.each do |layer|
|
|
76
|
+
subdir = join(@font.path, "glyphs", layer.name)
|
|
77
|
+
contents_path = if Dir.exist?(subdir)
|
|
78
|
+
join(subdir, "contents.plist")
|
|
79
|
+
else
|
|
80
|
+
join(@font.path, "glyphs", "contents.plist")
|
|
81
|
+
end
|
|
82
|
+
next unless File.exist?(contents_path)
|
|
83
|
+
|
|
84
|
+
order = Plist.parse(File.read(contents_path))
|
|
85
|
+
|
|
86
|
+
# UFO 2 contents.plist is Hash<glyph_name, glif_filename>.
|
|
87
|
+
# UFO 3 contents.plist can be Array<glif_filename> or
|
|
88
|
+
# Array<Hash<glif_filename, glyph_name>>. Normalize to
|
|
89
|
+
# Hash<glyph_name, glif_filename>.
|
|
90
|
+
entries =
|
|
91
|
+
case order
|
|
92
|
+
when Hash then order
|
|
93
|
+
when Array
|
|
94
|
+
if order.first.is_a?(Hash)
|
|
95
|
+
order.reduce({}) { |h, pair| h.merge(pair) }
|
|
96
|
+
else
|
|
97
|
+
order.to_h { |filename| [File.basename(filename, ".glif"), filename] }
|
|
98
|
+
end
|
|
99
|
+
else
|
|
100
|
+
raise "unsupported contents.plist format: #{order.class}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
entries.each_value do |glif_filename|
|
|
104
|
+
glif_path = if Dir.exist?(subdir)
|
|
105
|
+
join(subdir, glif_filename)
|
|
106
|
+
else
|
|
107
|
+
join(@font.path, "glyphs", glif_filename)
|
|
108
|
+
end
|
|
109
|
+
next unless File.exist?(glif_path)
|
|
110
|
+
|
|
111
|
+
layer.add(Glyph.from_glif(File.read(glif_path)))
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def read_kerning
|
|
117
|
+
path = join(@font.path, "kerning.plist")
|
|
118
|
+
return unless File.exist?(path)
|
|
119
|
+
|
|
120
|
+
data = Plist.parse(File.read(path))
|
|
121
|
+
@font.kerning = Kerning.new(data)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def read_features
|
|
125
|
+
path = join(@font.path, "features.fea")
|
|
126
|
+
return unless File.exist?(path)
|
|
127
|
+
|
|
128
|
+
@font.features = Features.new(text: File.read(path))
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def read_lib
|
|
132
|
+
path = join(@font.path, "lib.plist")
|
|
133
|
+
return unless File.exist?(path)
|
|
134
|
+
|
|
135
|
+
data = Plist.parse(File.read(path))
|
|
136
|
+
@font.lib = Lib.new(data)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def join(*parts)
|
|
140
|
+
File.join(*parts)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Ufo
|
|
5
|
+
# 2×3 transformation matrix, as used in UFO 3 for component and
|
|
6
|
+
# image placement. The matrix is laid out in the standard affine
|
|
7
|
+
# row-major order: `[a b c d e f]` represents
|
|
8
|
+
#
|
|
9
|
+
# | a c e |
|
|
10
|
+
# | b d f |
|
|
11
|
+
# | 0 0 1 |
|
|
12
|
+
#
|
|
13
|
+
# (UFO and OpenType both use the row-major convention despite the
|
|
14
|
+
# geometrically column-major appearance — this is how the
|
|
15
|
+
# `transformation` element reads in .glif XML.)
|
|
16
|
+
class Transformation
|
|
17
|
+
IDENTITY_MATRIX = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0].freeze
|
|
18
|
+
|
|
19
|
+
attr_reader :a, :b, :c, :d, :e, :f
|
|
20
|
+
|
|
21
|
+
def initialize(a: 1.0, b: 0.0, c: 0.0, d: 1.0, e: 0.0, f: 0.0)
|
|
22
|
+
@a = a.to_f
|
|
23
|
+
@b = b.to_f
|
|
24
|
+
@c = c.to_f
|
|
25
|
+
@d = d.to_f
|
|
26
|
+
@e = e.to_f
|
|
27
|
+
@f = f.to_f
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def identity?
|
|
31
|
+
[a, b, c, d, e, f] == IDENTITY_MATRIX
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_a
|
|
35
|
+
[a, b, c, d, e, f]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Ufo
|
|
5
|
+
# Writes a UFO source directory from a typed Fontisan::Ufo::Font.
|
|
6
|
+
#
|
|
7
|
+
# Mirror of Reader. Writes, in order:
|
|
8
|
+
# - `metainfo.plist` (UFO version stamp)
|
|
9
|
+
# - `fontinfo.plist` (Info)
|
|
10
|
+
# - `layercontents.plist` (UFO 3 only; layer ordering)
|
|
11
|
+
# - `glyphs/contents.plist` (default-layer glyph order)
|
|
12
|
+
# - `glyphs/<name>.glif` (per-glyph XML)
|
|
13
|
+
# - `glyphs/<layer>/...` (additional layers)
|
|
14
|
+
# - `kerning.plist`
|
|
15
|
+
# - `features.fea`
|
|
16
|
+
# - `lib.plist`
|
|
17
|
+
class Writer
|
|
18
|
+
UFO_VERSION_DEFAULT = 3
|
|
19
|
+
|
|
20
|
+
attr_reader :font
|
|
21
|
+
|
|
22
|
+
def initialize(font)
|
|
23
|
+
@font = font
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @param path [String] directory to write into; created if missing
|
|
27
|
+
# @param ufo_version [Integer] 2 or 3 (default 3)
|
|
28
|
+
def write(path, ufo_version: nil)
|
|
29
|
+
@ufo_version = ufo_version || @font.ufo_version || UFO_VERSION_DEFAULT
|
|
30
|
+
|
|
31
|
+
FileUtils.mkpath(path)
|
|
32
|
+
write_metainfo(path)
|
|
33
|
+
write_fontinfo(path)
|
|
34
|
+
write_layercontents(path)
|
|
35
|
+
write_glyphs(path)
|
|
36
|
+
write_kerning(path)
|
|
37
|
+
write_features(path)
|
|
38
|
+
write_lib(path)
|
|
39
|
+
path
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def write_metainfo(path)
|
|
45
|
+
data = {
|
|
46
|
+
"creator" => "org.fontisan.ufo",
|
|
47
|
+
"formatVersion" => @ufo_version.to_i,
|
|
48
|
+
}
|
|
49
|
+
File.write(File.join(path, "metainfo.plist"), Plist.emit(data))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def write_fontinfo(path)
|
|
53
|
+
File.write(File.join(path, "fontinfo.plist"), Plist.emit(@font.info.to_plist))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def write_layercontents(path)
|
|
57
|
+
return unless @ufo_version >= 3
|
|
58
|
+
return if @font.layers.size <= 1
|
|
59
|
+
|
|
60
|
+
order = @font.layers.layers.keys
|
|
61
|
+
File.write(File.join(path, "layercontents.plist"), Plist.emit(order))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def write_glyphs(path)
|
|
65
|
+
# Default layer writes glyphs/contents.plist + glyphs/*.glif.
|
|
66
|
+
# Additional layers write glyphs/<layer>/contents.plist + .glif.
|
|
67
|
+
@font.layers.each do |layer|
|
|
68
|
+
layer_dir = if layer.name == Layer::DEFAULT_NAME && @ufo_version < 3
|
|
69
|
+
File.join(path, "glyphs")
|
|
70
|
+
elsif layer.name == Layer::DEFAULT_NAME
|
|
71
|
+
File.join(path, "glyphs")
|
|
72
|
+
else
|
|
73
|
+
File.join(path, "glyphs", layer.name)
|
|
74
|
+
end
|
|
75
|
+
FileUtils.mkpath(layer_dir)
|
|
76
|
+
|
|
77
|
+
contents =
|
|
78
|
+
layer.glyphs.transform_values { |g| "#{safe_filename(g.name)}.glif" }
|
|
79
|
+
File.write(File.join(layer_dir, "contents.plist"), Plist.emit(contents))
|
|
80
|
+
|
|
81
|
+
layer.glyphs.each_value do |glyph|
|
|
82
|
+
File.write(File.join(layer_dir, "#{safe_filename(glyph.name)}.glif"), glyph.to_glif)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Map a glyph name to a filesystem-safe filename. UFO conventions
|
|
88
|
+
# use a base-prefixed name for names starting with non-letter.
|
|
89
|
+
def safe_filename(name)
|
|
90
|
+
return "_#{name}" if name.start_with?(".")
|
|
91
|
+
return "_#{name}" unless name.match?(/\A[A-Za-z_]/)
|
|
92
|
+
|
|
93
|
+
name
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def write_kerning(path)
|
|
97
|
+
return if @font.kerning.empty?
|
|
98
|
+
|
|
99
|
+
File.write(File.join(path, "kerning.plist"), Plist.emit(@font.kerning.to_plist))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def write_features(path)
|
|
103
|
+
return if @font.features.text.nil? || @font.features.text.empty?
|
|
104
|
+
|
|
105
|
+
File.write(File.join(path, "features.fea"), @font.features.text)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def write_lib(path)
|
|
109
|
+
return if @font.lib.data.empty?
|
|
110
|
+
|
|
111
|
+
File.write(File.join(path, "lib.plist"), Plist.emit(@font.lib.data))
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
data/lib/fontisan/ufo.rb
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
# Unified Font Object (UFO) v3 model + compile + convert pipeline.
|
|
5
|
+
#
|
|
6
|
+
# A UFO is a directory-based font source format. Each UFO is a
|
|
7
|
+
# human-readable, human-editable collection of plists + GLIF XML
|
|
8
|
+
# files. fontisan can:
|
|
9
|
+
#
|
|
10
|
+
# - Read any UFO source into a typed model (this namespace).
|
|
11
|
+
# - Edit the model programmatically.
|
|
12
|
+
# - Compile to a binary font (TTF, OTF, …) via
|
|
13
|
+
# Fontisan::Ufo::Compile::*
|
|
14
|
+
# - Convert any binary font fontisan can read INTO a UFO source.
|
|
15
|
+
# - Stitch glyphs from multiple sources into a new font via
|
|
16
|
+
# Fontisan::Stitcher.
|
|
17
|
+
#
|
|
18
|
+
# Reference: https://unifiedfontobject.org
|
|
19
|
+
module Ufo
|
|
20
|
+
autoload :Font, "fontisan/ufo/font"
|
|
21
|
+
autoload :Info, "fontisan/ufo/info"
|
|
22
|
+
autoload :Layer, "fontisan/ufo/layer"
|
|
23
|
+
autoload :LayerSet, "fontisan/ufo/layer_set"
|
|
24
|
+
autoload :Glyph, "fontisan/ufo/glyph"
|
|
25
|
+
autoload :Contour, "fontisan/ufo/contour"
|
|
26
|
+
autoload :Point, "fontisan/ufo/point"
|
|
27
|
+
autoload :Component, "fontisan/ufo/component"
|
|
28
|
+
autoload :Anchor, "fontisan/ufo/anchor"
|
|
29
|
+
autoload :Guideline, "fontisan/ufo/guideline"
|
|
30
|
+
autoload :Image, "fontisan/ufo/image"
|
|
31
|
+
autoload :Transformation, "fontisan/ufo/transformation"
|
|
32
|
+
autoload :Kerning, "fontisan/ufo/kerning"
|
|
33
|
+
autoload :Features, "fontisan/ufo/features"
|
|
34
|
+
autoload :Lib, "fontisan/ufo/lib"
|
|
35
|
+
autoload :DataSet, "fontisan/ufo/data_set"
|
|
36
|
+
autoload :ImageSet, "fontisan/ufo/image_set"
|
|
37
|
+
autoload :Plist, "fontisan/ufo/plist"
|
|
38
|
+
autoload :Reader, "fontisan/ufo/reader"
|
|
39
|
+
autoload :Writer, "fontisan/ufo/writer"
|
|
40
|
+
autoload :Compile, "fontisan/ufo/compile"
|
|
41
|
+
autoload :Convert, "fontisan/ufo/convert"
|
|
42
|
+
autoload :Cli, "fontisan/ufo/cli"
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/fontisan/version.rb
CHANGED
data/lib/fontisan.rb
CHANGED
|
@@ -88,6 +88,7 @@ module Fontisan
|
|
|
88
88
|
autoload :Svg, "fontisan/svg"
|
|
89
89
|
autoload :Tables, "fontisan/tables"
|
|
90
90
|
autoload :Type1, "fontisan/type1"
|
|
91
|
+
autoload :Ufo, "fontisan/ufo"
|
|
91
92
|
autoload :Utilities, "fontisan/utilities"
|
|
92
93
|
autoload :Utils, "fontisan/utils"
|
|
93
94
|
autoload :Validation, "fontisan/validation"
|
|
@@ -110,6 +111,8 @@ module Fontisan
|
|
|
110
111
|
autoload :OutlineExtractor, "fontisan/outline_extractor"
|
|
111
112
|
autoload :SfntFont, "fontisan/sfnt_font"
|
|
112
113
|
autoload :SfntTable, "fontisan/sfnt_table"
|
|
114
|
+
autoload :Stitcher, "fontisan/stitcher"
|
|
115
|
+
autoload :StitcherCli, "fontisan/stitcher_cli"
|
|
113
116
|
autoload :TrueTypeCollection, "fontisan/true_type_collection"
|
|
114
117
|
autoload :TrueTypeFont, "fontisan/true_type_font"
|
|
115
118
|
autoload :TrueTypeFontExtensions, "fontisan/true_type_font_extensions"
|
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
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -435,6 +435,13 @@ files:
|
|
|
435
435
|
- lib/fontisan/pipeline/variation_resolver.rb
|
|
436
436
|
- lib/fontisan/sfnt_font.rb
|
|
437
437
|
- lib/fontisan/sfnt_table.rb
|
|
438
|
+
- lib/fontisan/stitcher.rb
|
|
439
|
+
- lib/fontisan/stitcher/selector.rb
|
|
440
|
+
- lib/fontisan/stitcher/selector/codepoints.rb
|
|
441
|
+
- lib/fontisan/stitcher/selector/gid.rb
|
|
442
|
+
- lib/fontisan/stitcher/selector/range.rb
|
|
443
|
+
- lib/fontisan/stitcher/source.rb
|
|
444
|
+
- lib/fontisan/stitcher_cli.rb
|
|
438
445
|
- lib/fontisan/subset.rb
|
|
439
446
|
- lib/fontisan/subset/builder.rb
|
|
440
447
|
- lib/fontisan/subset/glyph_mapping.rb
|
|
@@ -544,6 +551,49 @@ files:
|
|
|
544
551
|
- lib/fontisan/type1/ttf_to_type1_converter.rb
|
|
545
552
|
- lib/fontisan/type1/upm_scaler.rb
|
|
546
553
|
- lib/fontisan/type1_font.rb
|
|
554
|
+
- lib/fontisan/ufo.rb
|
|
555
|
+
- lib/fontisan/ufo/anchor.rb
|
|
556
|
+
- lib/fontisan/ufo/cli.rb
|
|
557
|
+
- lib/fontisan/ufo/compile.rb
|
|
558
|
+
- lib/fontisan/ufo/compile/base_compiler.rb
|
|
559
|
+
- lib/fontisan/ufo/compile/cff.rb
|
|
560
|
+
- lib/fontisan/ufo/compile/cmap.rb
|
|
561
|
+
- lib/fontisan/ufo/compile/filters.rb
|
|
562
|
+
- lib/fontisan/ufo/compile/filters/cubic_to_quadratic.rb
|
|
563
|
+
- lib/fontisan/ufo/compile/filters/decompose_components.rb
|
|
564
|
+
- lib/fontisan/ufo/compile/filters/flatten_components.rb
|
|
565
|
+
- lib/fontisan/ufo/compile/filters/reverse_contour_direction.rb
|
|
566
|
+
- lib/fontisan/ufo/compile/glyf_loca.rb
|
|
567
|
+
- lib/fontisan/ufo/compile/head.rb
|
|
568
|
+
- lib/fontisan/ufo/compile/hhea.rb
|
|
569
|
+
- lib/fontisan/ufo/compile/hmtx.rb
|
|
570
|
+
- lib/fontisan/ufo/compile/maxp.rb
|
|
571
|
+
- lib/fontisan/ufo/compile/name.rb
|
|
572
|
+
- lib/fontisan/ufo/compile/os2.rb
|
|
573
|
+
- lib/fontisan/ufo/compile/otf_compiler.rb
|
|
574
|
+
- lib/fontisan/ufo/compile/post.rb
|
|
575
|
+
- lib/fontisan/ufo/compile/ttf_compiler.rb
|
|
576
|
+
- lib/fontisan/ufo/component.rb
|
|
577
|
+
- lib/fontisan/ufo/contour.rb
|
|
578
|
+
- lib/fontisan/ufo/convert.rb
|
|
579
|
+
- lib/fontisan/ufo/convert/from_bin_data.rb
|
|
580
|
+
- lib/fontisan/ufo/data_set.rb
|
|
581
|
+
- lib/fontisan/ufo/features.rb
|
|
582
|
+
- lib/fontisan/ufo/font.rb
|
|
583
|
+
- lib/fontisan/ufo/glyph.rb
|
|
584
|
+
- lib/fontisan/ufo/guideline.rb
|
|
585
|
+
- lib/fontisan/ufo/image.rb
|
|
586
|
+
- lib/fontisan/ufo/image_set.rb
|
|
587
|
+
- lib/fontisan/ufo/info.rb
|
|
588
|
+
- lib/fontisan/ufo/kerning.rb
|
|
589
|
+
- lib/fontisan/ufo/layer.rb
|
|
590
|
+
- lib/fontisan/ufo/layer_set.rb
|
|
591
|
+
- lib/fontisan/ufo/lib.rb
|
|
592
|
+
- lib/fontisan/ufo/plist.rb
|
|
593
|
+
- lib/fontisan/ufo/point.rb
|
|
594
|
+
- lib/fontisan/ufo/reader.rb
|
|
595
|
+
- lib/fontisan/ufo/transformation.rb
|
|
596
|
+
- lib/fontisan/ufo/writer.rb
|
|
547
597
|
- lib/fontisan/utilities.rb
|
|
548
598
|
- lib/fontisan/utilities/brotli_wrapper.rb
|
|
549
599
|
- lib/fontisan/utilities/checksum_calculator.rb
|