fontisan 0.4.7 → 0.4.9
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/CHANGELOG.md +13 -0
- data/README.adoc +116 -2
- data/docs/.vitepress/config.ts +1 -7
- data/docs/COLLECTION_VALIDATION.adoc +61 -0
- data/docs/STITCHER_GUIDE.adoc +138 -1
- data/docs/cli/convert.md +44 -2
- data/docs/cli/index.md +21 -28
- data/docs/cli/validate-collection.md +95 -0
- data/docs/cli/validate.md +8 -0
- data/docs/index.md +0 -2
- data/lib/fontisan/cli.rb +29 -8
- data/lib/fontisan/collection/reader/stats.rb +23 -0
- data/lib/fontisan/collection/reader.rb +90 -0
- data/lib/fontisan/collection.rb +1 -0
- data/lib/fontisan/commands/convert_command.rb +96 -18
- data/lib/fontisan/commands/multi_format_output.rb +59 -0
- data/lib/fontisan/commands/validate_collection_command.rb +121 -0
- data/lib/fontisan/commands.rb +2 -0
- data/lib/fontisan/error.rb +25 -0
- data/lib/fontisan/models.rb +0 -1
- data/lib/fontisan/stitcher/collection_result.rb +18 -0
- data/lib/fontisan/stitcher/partition_strategy/base.rb +23 -0
- data/lib/fontisan/stitcher/partition_strategy/blueprint.rb +24 -0
- data/lib/fontisan/stitcher/partition_strategy/by_plane.rb +131 -0
- data/lib/fontisan/stitcher/partition_strategy/partition.rb +24 -0
- data/lib/fontisan/stitcher/partition_strategy.rb +22 -0
- data/lib/fontisan/stitcher.rb +44 -10
- data/lib/fontisan/ufo/compile/name.rb +2 -2
- data/lib/fontisan/ufo/info.rb +48 -0
- data/lib/fontisan/unicode/plane.rb +56 -0
- data/lib/fontisan/unicode.rb +17 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan.rb +2 -2
- metadata +14 -18
- data/docs/cli/audit.md +0 -337
- data/lib/fontisan/cldr/aggregator.rb +0 -33
- data/lib/fontisan/cldr/cache_manager.rb +0 -110
- data/lib/fontisan/cldr/config.rb +0 -59
- data/lib/fontisan/cldr/download_error.rb +0 -9
- data/lib/fontisan/cldr/downloader.rb +0 -79
- data/lib/fontisan/cldr/error.rb +0 -8
- data/lib/fontisan/cldr/index.rb +0 -64
- data/lib/fontisan/cldr/index_builder.rb +0 -72
- data/lib/fontisan/cldr/unicode_set_parser.rb +0 -189
- data/lib/fontisan/cldr/unknown_version_error.rb +0 -9
- data/lib/fontisan/cldr/version_resolver.rb +0 -91
- data/lib/fontisan/cldr.rb +0 -23
- data/lib/fontisan/cli/cldr_cli.rb +0 -85
- data/lib/fontisan/config/cldr.yml +0 -22
- data/lib/fontisan/models/cldr/language_coverage.rb +0 -31
- data/lib/fontisan/models/cldr.rb +0 -12
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
class Stitcher
|
|
5
|
+
# Per-subfont stats computed from the loaded, on-disk subfont (not the
|
|
6
|
+
# in-memory UFO target — the compiler may add glyphs, e.g. .notdef).
|
|
7
|
+
SubfontStats = Struct.new(:name, :glyph_count, :codepoint_count,
|
|
8
|
+
keyword_init: true)
|
|
9
|
+
|
|
10
|
+
# Return value of {Stitcher#write_collection}. Carries the output path,
|
|
11
|
+
# total bytes, and one {SubfontStats} per declared subfont.
|
|
12
|
+
CollectionResult = Struct.new(:path, :bytes, :subfonts, keyword_init: true) do
|
|
13
|
+
def face_count
|
|
14
|
+
subfonts.size
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
class Stitcher
|
|
5
|
+
module PartitionStrategy
|
|
6
|
+
# Abstract base. Concrete partitioners (ByPlane, ByBlock, …)
|
|
7
|
+
# implement {#call} and return a {Blueprint}.
|
|
8
|
+
class Base
|
|
9
|
+
# Default cap: 65,535 − .notdef − safety margin. Matches the
|
|
10
|
+
# Stitcher's own +GlyphLimit+ cap for TTF.
|
|
11
|
+
DEFAULT_CAP = 65_484
|
|
12
|
+
|
|
13
|
+
# @param cp_map [Hash{Integer=>Object}] codepoint → donor label
|
|
14
|
+
# @param cap [Integer] max codepoints per partition
|
|
15
|
+
# @return [Blueprint]
|
|
16
|
+
def call(cp_map, cap: DEFAULT_CAP)
|
|
17
|
+
raise NotImplementedError,
|
|
18
|
+
"#{self.class} must implement #call"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
class Stitcher
|
|
5
|
+
module PartitionStrategy
|
|
6
|
+
# The result of partitioning a codepoint set: an ordered list of
|
|
7
|
+
# {Partition}s. Applying a blueprint to a Stitcher declares one
|
|
8
|
+
# subfont per partition.
|
|
9
|
+
Blueprint = Struct.new(:partitions, keyword_init: true) do
|
|
10
|
+
# @param stitcher [Fontisan::Stitcher]
|
|
11
|
+
# @return [Array<Symbol>] names of subfonts declared
|
|
12
|
+
def apply_to(stitcher)
|
|
13
|
+
partitions.each { |p| p.apply_to(stitcher) }
|
|
14
|
+
partitions.map(&:name)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @return [Array<Symbol>]
|
|
18
|
+
def names
|
|
19
|
+
partitions.map(&:name)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
class Stitcher
|
|
5
|
+
module PartitionStrategy
|
|
6
|
+
# Partition codepoints by Unicode plane (BMP, SMP, SIP, …).
|
|
7
|
+
#
|
|
8
|
+
# For each plane with codepoints:
|
|
9
|
+
# - if the count fits under +cap+, emit one partition named
|
|
10
|
+
# +:plane_<n>+ (e.g. +:plane_0+, +:plane_2+);
|
|
11
|
+
# - otherwise sub-split using the large CJK extension block
|
|
12
|
+
# boundaries, naming partitions +:plane_<n>_a+, +:plane_<n>_b+,
|
|
13
|
+
# and so on.
|
|
14
|
+
#
|
|
15
|
+
# If a single CJK extension block alone exceeds +cap+, raises
|
|
16
|
+
# {Fontisan::GlyphLimitExceededError} — the partitioner cannot
|
|
17
|
+
# satisfy the cap and the caller must use a smaller cap, split
|
|
18
|
+
# manually, or switch to a format with a higher glyph limit.
|
|
19
|
+
class ByPlane < Base
|
|
20
|
+
# @param cp_map [Hash{Integer=>Object}] codepoint → donor label
|
|
21
|
+
# @param cap [Integer] max codepoints per partition
|
|
22
|
+
# @return [Blueprint]
|
|
23
|
+
def call(cp_map, cap: DEFAULT_CAP)
|
|
24
|
+
grouped = group_by_plane(cp_map)
|
|
25
|
+
partitions = grouped.flat_map do |plane_num, entries|
|
|
26
|
+
build_partitions_for_plane(plane_num, entries, cap)
|
|
27
|
+
end
|
|
28
|
+
Blueprint.new(partitions: partitions)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def group_by_plane(cp_map)
|
|
34
|
+
cp_map.group_by { |cp, _label| Fontisan::Unicode::Plane.of(cp) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def build_partitions_for_plane(plane_num, entries, cap)
|
|
38
|
+
if entries.size <= cap
|
|
39
|
+
return [single_partition(plane_num, entries)]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
sub_split_by_block(plane_num, entries, cap)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def single_partition(plane_num, entries)
|
|
46
|
+
Partition.new(
|
|
47
|
+
name: :"plane_#{plane_num}",
|
|
48
|
+
cps: entries.map(&:first),
|
|
49
|
+
donor_map: entries.to_h,
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# When a plane overflows +cap+, carve it along the large CJK
|
|
54
|
+
# extension block boundaries. The +:other+ bucket (everything
|
|
55
|
+
# outside the known mega-blocks) is split into chunks of +cap+,
|
|
56
|
+
# while each known large block becomes one partition atomically —
|
|
57
|
+
# if a single block alone exceeds +cap+, we cannot sub-split it
|
|
58
|
+
# (its codepoints are contiguous and we don't have finer-grained
|
|
59
|
+
# boundaries to use), so raise.
|
|
60
|
+
def sub_split_by_block(plane_num, entries, cap)
|
|
61
|
+
buckets = bucket_by_large_block(entries)
|
|
62
|
+
partitions = []
|
|
63
|
+
suffix = "a"
|
|
64
|
+
|
|
65
|
+
buckets.each do |label, bucket_entries|
|
|
66
|
+
if large_block?(label) && bucket_entries.size > cap
|
|
67
|
+
raise PartitionCapExceededError.new(
|
|
68
|
+
block_label: label,
|
|
69
|
+
actual: bucket_entries.size,
|
|
70
|
+
cap: cap,
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
chunks(bucket_entries, cap).each do |chunk|
|
|
75
|
+
partitions << Partition.new(
|
|
76
|
+
name: :"plane_#{plane_num}_#{suffix}",
|
|
77
|
+
cps: chunk.map(&:first),
|
|
78
|
+
donor_map: chunk.to_h,
|
|
79
|
+
)
|
|
80
|
+
suffix = suffix.succ
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
partitions
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def large_block?(label)
|
|
87
|
+
Fontisan::Unicode::Plane::LARGE_CJK_BLOCKS.key?(label)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Split +entries+ into sub-arrays of at most +cap+ each.
|
|
91
|
+
def chunks(entries, cap)
|
|
92
|
+
entries.each_slice(cap).to_a
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Bucket entries by which large CJK block (or :other) they fall
|
|
96
|
+
# into. Entries outside any known large block go into :other,
|
|
97
|
+
# which is then packed into its own partition(s).
|
|
98
|
+
def bucket_by_large_block(entries)
|
|
99
|
+
buckets = { other: [] }
|
|
100
|
+
Fontisan::Unicode::Plane::LARGE_CJK_BLOCKS.each_key do |label|
|
|
101
|
+
buckets[label] = []
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
entries.each do |cp, label|
|
|
105
|
+
block = find_large_block(cp)
|
|
106
|
+
if block
|
|
107
|
+
buckets[block] << [cp, label]
|
|
108
|
+
else
|
|
109
|
+
buckets[:other] << [cp, label]
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Drop empty buckets; preserve order (other first if non-empty,
|
|
114
|
+
# then CJK blocks in declaration order).
|
|
115
|
+
ordered = []
|
|
116
|
+
ordered << [:other, buckets[:other]] unless buckets[:other].empty?
|
|
117
|
+
Fontisan::Unicode::Plane::LARGE_CJK_BLOCKS.each_key do |label|
|
|
118
|
+
ordered << [label, buckets[label]] unless buckets[label].empty?
|
|
119
|
+
end
|
|
120
|
+
ordered
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def find_large_block(codepoint)
|
|
124
|
+
Fontisan::Unicode::Plane::LARGE_CJK_BLOCKS.find do |_label, range|
|
|
125
|
+
range.include?(codepoint)
|
|
126
|
+
end&.first
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
class Stitcher
|
|
5
|
+
module PartitionStrategy
|
|
6
|
+
# One slice of a partitioned codepoint set. Each partition names
|
|
7
|
+
# a target subfont and the codepoints + donor mapping that should
|
|
8
|
+
# land in it.
|
|
9
|
+
#
|
|
10
|
+
# {#apply_to} pushes the partition's bindings into a Stitcher via
|
|
11
|
+
# +include_codepoints_map+ (TODO 68). If that API is unavailable
|
|
12
|
+
# (older Stitcher), it falls back to one +include_codepoints+
|
|
13
|
+
# call per donor — but the preferred path is the map API.
|
|
14
|
+
Partition = Struct.new(:name, :cps, :donor_map, keyword_init: true) do
|
|
15
|
+
# @param stitcher [Fontisan::Stitcher]
|
|
16
|
+
# @return [void]
|
|
17
|
+
def apply_to(stitcher)
|
|
18
|
+
slice = cps.to_h { |cp| [cp, donor_map[cp]] }
|
|
19
|
+
stitcher.include_codepoints_map(slice, into: name)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
class Stitcher
|
|
5
|
+
# Namespace for codepoint partitioners that split a codepoint set
|
|
6
|
+
# across named subfonts while respecting the format's glyph cap.
|
|
7
|
+
#
|
|
8
|
+
# A partitioner is a strategy object with a single entry point
|
|
9
|
+
# (`.partition`) that takes a +{codepoint => donor}+ map and returns
|
|
10
|
+
# a {Blueprint}. The blueprint is then applied to a Stitcher via
|
|
11
|
+
# {Blueprint#apply_to}.
|
|
12
|
+
#
|
|
13
|
+
# Adding a new partitioner = adding a new file + a new entry here.
|
|
14
|
+
# No edits to existing partitioners required (open/closed).
|
|
15
|
+
module PartitionStrategy
|
|
16
|
+
autoload :Base, "fontisan/stitcher/partition_strategy/base"
|
|
17
|
+
autoload :Blueprint, "fontisan/stitcher/partition_strategy/blueprint"
|
|
18
|
+
autoload :Partition, "fontisan/stitcher/partition_strategy/partition"
|
|
19
|
+
autoload :ByPlane, "fontisan/stitcher/partition_strategy/by_plane"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/fontisan/stitcher.rb
CHANGED
|
@@ -25,11 +25,20 @@ module Fontisan
|
|
|
25
25
|
# stitcher.include_range(0x4E00..0x9FFF, from: :noto_cjk, into: :cjk)
|
|
26
26
|
# stitcher.write_collection("out.otc", format: :otf2)
|
|
27
27
|
class Stitcher
|
|
28
|
-
autoload :Source,
|
|
29
|
-
autoload :Selector,
|
|
30
|
-
autoload :GlyphSignature,
|
|
31
|
-
autoload :Deduplicator,
|
|
32
|
-
autoload :GlyphLimit,
|
|
28
|
+
autoload :Source, "fontisan/stitcher/source"
|
|
29
|
+
autoload :Selector, "fontisan/stitcher/selector"
|
|
30
|
+
autoload :GlyphSignature, "fontisan/stitcher/glyph_signature"
|
|
31
|
+
autoload :Deduplicator, "fontisan/stitcher/deduplicator"
|
|
32
|
+
autoload :GlyphLimit, "fontisan/stitcher/glyph_limit"
|
|
33
|
+
autoload :CollectionResult, "fontisan/stitcher/collection_result"
|
|
34
|
+
autoload :SubfontStats, "fontisan/stitcher/collection_result"
|
|
35
|
+
autoload :PartitionStrategy, "fontisan/stitcher/partition_strategy"
|
|
36
|
+
|
|
37
|
+
# Internal: pairs a compiled loaded font with its stats so
|
|
38
|
+
# +write_collection+ can build the collection and the result from a
|
|
39
|
+
# single compilation pass.
|
|
40
|
+
CompiledSubfont = Struct.new(:name, :font, :stats, keyword_init: true)
|
|
41
|
+
private_constant :CompiledSubfont
|
|
33
42
|
|
|
34
43
|
DEFAULT_DEDUPLICATE = true
|
|
35
44
|
|
|
@@ -54,6 +63,13 @@ module Fontisan
|
|
|
54
63
|
Selector::Codepoints.new(codepoints).apply(source(from), @subfonts[into])
|
|
55
64
|
end
|
|
56
65
|
|
|
66
|
+
def include_codepoints_map(cp_map, into:)
|
|
67
|
+
cp_map.to_h
|
|
68
|
+
.group_by { |_cp, label| label }
|
|
69
|
+
.transform_values { |pairs| pairs.map(&:first).sort }
|
|
70
|
+
.each { |label, cps| include_codepoints(cps, from: label, into: into) }
|
|
71
|
+
end
|
|
72
|
+
|
|
57
73
|
def include_gid(donor_gid, from:, into:)
|
|
58
74
|
Selector::Gid.new(donor_gid).apply(source(from), @subfonts[into])
|
|
59
75
|
end
|
|
@@ -89,13 +105,19 @@ module Fontisan
|
|
|
89
105
|
raise ArgumentError, "no subfonts declared" if @subfonts.empty?
|
|
90
106
|
|
|
91
107
|
compiled = @subfonts.keys.map do |name|
|
|
92
|
-
|
|
108
|
+
compile_subfont_with_stats(name, format: format)
|
|
93
109
|
end
|
|
110
|
+
fonts = compiled.map(&:font)
|
|
94
111
|
|
|
95
112
|
collection_format = collection_format_for(format)
|
|
96
|
-
Collection::Builder.new(
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
Collection::Builder.new(fonts, format: collection_format,
|
|
114
|
+
optimize: true).build_to_file(path)
|
|
115
|
+
|
|
116
|
+
CollectionResult.new(
|
|
117
|
+
path: path,
|
|
118
|
+
bytes: File.size(path),
|
|
119
|
+
subfonts: compiled.map(&:stats),
|
|
120
|
+
)
|
|
99
121
|
end
|
|
100
122
|
|
|
101
123
|
private
|
|
@@ -140,6 +162,10 @@ module Fontisan
|
|
|
140
162
|
end
|
|
141
163
|
|
|
142
164
|
def compile_subfont_to_loaded_font(subfont_name, format:)
|
|
165
|
+
compile_subfont_with_stats(subfont_name, format: format).font
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def compile_subfont_with_stats(subfont_name, format:)
|
|
143
169
|
target = build_target_for(subfont_name)
|
|
144
170
|
GlyphLimit.check!(target.glyphs.size, format: format)
|
|
145
171
|
|
|
@@ -148,7 +174,15 @@ module Fontisan
|
|
|
148
174
|
sub_path = File.join(dir, "sub#{subfont_name}#{ext}")
|
|
149
175
|
compiler = compiler_for(format)
|
|
150
176
|
compiler.new(target).compile(output_path: sub_path)
|
|
151
|
-
|
|
177
|
+
propagate_cbdt_tables(sub_path) if cbdt_source
|
|
178
|
+
|
|
179
|
+
loaded = Fontisan::FontLoader.load(sub_path)
|
|
180
|
+
stats = SubfontStats.new(
|
|
181
|
+
name: subfont_name,
|
|
182
|
+
glyph_count: loaded.table("maxp")&.num_glyphs || 0,
|
|
183
|
+
codepoint_count: (loaded.table("cmap")&.unicode_mappings || {}).size,
|
|
184
|
+
)
|
|
185
|
+
CompiledSubfont.new(name: subfont_name, font: loaded, stats: stats)
|
|
152
186
|
end
|
|
153
187
|
end
|
|
154
188
|
|
|
@@ -31,7 +31,7 @@ module Fontisan
|
|
|
31
31
|
family = font.info.family_name || "Untitled"
|
|
32
32
|
subfamily = font.info.style_name || "Regular"
|
|
33
33
|
ps_name = font.info.postscript_font_name || "#{family}-#{subfamily}"
|
|
34
|
-
full_name = "#{family} #{subfamily}".strip
|
|
34
|
+
full_name = font.info.postscript_full_name || "#{family} #{subfamily}".strip
|
|
35
35
|
major = font.info.version_major || 0
|
|
36
36
|
minor = font.info.version_minor || 0
|
|
37
37
|
version_str = "Version #{major}.#{minor}"
|
|
@@ -72,7 +72,7 @@ module Fontisan
|
|
|
72
72
|
|
|
73
73
|
header + body + storage
|
|
74
74
|
end
|
|
75
|
-
private_class_method :format0_bytes
|
|
75
|
+
private_class_method :format0_bytes
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
78
|
end
|
data/lib/fontisan/ufo/info.rb
CHANGED
|
@@ -41,6 +41,54 @@ module Fontisan
|
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
# Build a +Ufo::Info+ for one subfont of a collection. The family
|
|
45
|
+
# name embeds the subfont name (e.g. +"MyFont CJK"+), and the
|
|
46
|
+
# PostScript name uses the hyphenated form (e.g. +"MyFont-CJK"+).
|
|
47
|
+
#
|
|
48
|
+
# +version+ is parsed into +version_major+ / +version_minor+ per
|
|
49
|
+
# the UFO major.minor shape (patch is dropped).
|
|
50
|
+
#
|
|
51
|
+
# +trademark+ is stored under +extras["openTypeNameTrademark"]+
|
|
52
|
+
# because it is not in +STANDARD_FIELDS+ yet — see TODO 71
|
|
53
|
+
# out-of-scope follow-up.
|
|
54
|
+
#
|
|
55
|
+
# @param family [String] collection-wide family name
|
|
56
|
+
# @param subfont [String, Symbol] subfont identifier (appended to family)
|
|
57
|
+
# @param version [String, Integer] e.g. "0.1", "1", "1.2.3"
|
|
58
|
+
# @param subfamily [String] OpenType subfamily (default "Regular")
|
|
59
|
+
# @param copyright [String, nil]
|
|
60
|
+
# @param trademark [String, nil]
|
|
61
|
+
# @return [Info]
|
|
62
|
+
def self.for_subfont(family:, subfont:, version:,
|
|
63
|
+
subfamily: "Regular",
|
|
64
|
+
copyright: nil, trademark: nil)
|
|
65
|
+
major, minor = parse_version(version)
|
|
66
|
+
subfont_str = subfont.to_s
|
|
67
|
+
values = {
|
|
68
|
+
family_name: "#{family} #{subfont_str}",
|
|
69
|
+
style_name: subfamily,
|
|
70
|
+
version_major: major,
|
|
71
|
+
version_minor: minor,
|
|
72
|
+
postscript_font_name: "#{family}-#{subfont_str}",
|
|
73
|
+
postscript_full_name: "#{family} #{subfont_str}",
|
|
74
|
+
}
|
|
75
|
+
values[:copyright] = copyright if copyright
|
|
76
|
+
values["openTypeNameTrademark"] = trademark if trademark
|
|
77
|
+
new(values)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @param version [String, Integer]
|
|
81
|
+
# @return [Array(Integer, Integer)] [major, minor]
|
|
82
|
+
def self.parse_version(version)
|
|
83
|
+
case version.to_s
|
|
84
|
+
when /\A(\d+)\.(\d+)\.\d+\z/ then [Regexp.last_match(1).to_i, Regexp.last_match(2).to_i]
|
|
85
|
+
when /\A(\d+)\.(\d+)\z/ then [Regexp.last_match(1).to_i, Regexp.last_match(2).to_i]
|
|
86
|
+
when /\A(\d+)\z/ then [Regexp.last_match(1).to_i, 0]
|
|
87
|
+
else [0, 0]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
private_class_method :parse_version
|
|
91
|
+
|
|
44
92
|
# @return [Hash] a Hash<String, Object> suitable for emit() to
|
|
45
93
|
# serialize back to plist. Keys are in camelCase per UFO 3.
|
|
46
94
|
def to_plist
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Unicode
|
|
5
|
+
# Unicode plane metadata. A plane is a contiguous range of 65,536
|
|
6
|
+
# codepoints; +cp >> 16+ is the plane number.
|
|
7
|
+
#
|
|
8
|
+
# Plane labels follow the Unicode standard names. Unassigned planes
|
|
9
|
+
# fall back to +"Plane_N"+. This is a pure-data module — no I/O, no
|
|
10
|
+
# state, no dependency on tables or stitcher.
|
|
11
|
+
module Plane
|
|
12
|
+
BMP = 0
|
|
13
|
+
SMP = 1
|
|
14
|
+
SIP = 2
|
|
15
|
+
TIP = 3
|
|
16
|
+
SSP = 14
|
|
17
|
+
|
|
18
|
+
# The handful of mega-blocks large enough to overflow a single
|
|
19
|
+
# plane's 65,535-glyph cap when partitioning by plane. Range +
|
|
20
|
+
# label only — full Unicode Blocks.txt data lives in +Block+
|
|
21
|
+
# (follow-up).
|
|
22
|
+
LARGE_CJK_BLOCKS = {
|
|
23
|
+
"CJK_Ext_B" => 0x2A700..0x2B73F,
|
|
24
|
+
"CJK_Ext_C" => 0x2B740..0x2B81F,
|
|
25
|
+
"CJK_Ext_D" => 0x2B820..0x2CEAF,
|
|
26
|
+
"CJK_Ext_E" => 0x2CEB0..0x2EBEF,
|
|
27
|
+
"CJK_Ext_F" => 0x2EBF0..0x2EE5F,
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
# @param codepoint [Integer]
|
|
31
|
+
# @return [Integer] plane number (0..16)
|
|
32
|
+
def self.of(codepoint)
|
|
33
|
+
codepoint >> 16
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @param plane [Integer]
|
|
37
|
+
# @return [String] human-readable plane label
|
|
38
|
+
def self.label(plane)
|
|
39
|
+
case plane
|
|
40
|
+
when BMP then "BMP"
|
|
41
|
+
when SMP then "SMP"
|
|
42
|
+
when SIP then "SIP"
|
|
43
|
+
when TIP then "TIP"
|
|
44
|
+
when SSP then "SSP"
|
|
45
|
+
else "Plane_#{plane}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @param codepoint [Integer]
|
|
50
|
+
# @return [String] label of the plane containing +codepoint+
|
|
51
|
+
def self.label_of(codepoint)
|
|
52
|
+
label(of(codepoint))
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Namespace hub for Unicode metadata used by font tooling.
|
|
4
|
+
#
|
|
5
|
+
# Plane/Block/Script metadata is a separate concern from font format
|
|
6
|
+
# parsing — it is pure Unicode knowledge. Keeping it under its own
|
|
7
|
+
# namespace (rather than scattering it across +Tables+ or +Stitcher+)
|
|
8
|
+
# keeps the data MECE: one concept, one home.
|
|
9
|
+
#
|
|
10
|
+
# Each metadata concept (Plane, Block, Script) lives in its own file so
|
|
11
|
+
# that adding a new concept is additive (open/closed).
|
|
12
|
+
|
|
13
|
+
module Fontisan
|
|
14
|
+
module Unicode
|
|
15
|
+
autoload :Plane, "fontisan/unicode/plane"
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/fontisan/version.rb
CHANGED
data/lib/fontisan.rb
CHANGED
|
@@ -72,10 +72,10 @@ module Fontisan
|
|
|
72
72
|
autoload :VariationDataCorruptedError, "fontisan/error"
|
|
73
73
|
autoload :MultipleCbdtSourcesError, "fontisan/error"
|
|
74
74
|
autoload :GlyphLimitExceededError, "fontisan/error"
|
|
75
|
+
autoload :PartitionCapExceededError, "fontisan/error"
|
|
75
76
|
|
|
76
77
|
# Namespace hubs (each hub declares its own child autoloads)
|
|
77
78
|
autoload :Binary, "fontisan/binary"
|
|
78
|
-
autoload :Cldr, "fontisan/cldr"
|
|
79
79
|
autoload :Collection, "fontisan/collection"
|
|
80
80
|
autoload :Commands, "fontisan/commands"
|
|
81
81
|
autoload :Converters, "fontisan/converters"
|
|
@@ -92,6 +92,7 @@ module Fontisan
|
|
|
92
92
|
autoload :Tables, "fontisan/tables"
|
|
93
93
|
autoload :Type1, "fontisan/type1"
|
|
94
94
|
autoload :Ufo, "fontisan/ufo"
|
|
95
|
+
autoload :Unicode, "fontisan/unicode"
|
|
95
96
|
autoload :Utilities, "fontisan/utilities"
|
|
96
97
|
autoload :Utils, "fontisan/utils"
|
|
97
98
|
autoload :Validation, "fontisan/validation"
|
|
@@ -120,7 +121,6 @@ module Fontisan
|
|
|
120
121
|
autoload :TrueTypeFont, "fontisan/true_type_font"
|
|
121
122
|
autoload :TrueTypeFontExtensions, "fontisan/true_type_font_extensions"
|
|
122
123
|
autoload :Type1Font, "fontisan/type1_font"
|
|
123
|
-
autoload :CldrCli, "fontisan/cli/cldr_cli"
|
|
124
124
|
autoload :Woff2Font, "fontisan/woff2_font"
|
|
125
125
|
autoload :WoffFont, "fontisan/woff_font"
|
|
126
126
|
|
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.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -173,7 +173,6 @@ files:
|
|
|
173
173
|
- docs/api/validators/font-validator.md
|
|
174
174
|
- docs/api/validators/helper.md
|
|
175
175
|
- docs/api/validators/profile.md
|
|
176
|
-
- docs/cli/audit.md
|
|
177
176
|
- docs/cli/convert.md
|
|
178
177
|
- docs/cli/dump-table.md
|
|
179
178
|
- docs/cli/export.md
|
|
@@ -189,6 +188,7 @@ files:
|
|
|
189
188
|
- docs/cli/subset.md
|
|
190
189
|
- docs/cli/tables.md
|
|
191
190
|
- docs/cli/unicode.md
|
|
191
|
+
- docs/cli/validate-collection.md
|
|
192
192
|
- docs/cli/validate.md
|
|
193
193
|
- docs/cli/variable.md
|
|
194
194
|
- docs/cli/version.md
|
|
@@ -277,24 +277,13 @@ files:
|
|
|
277
277
|
- lib/fontisan/binary.rb
|
|
278
278
|
- lib/fontisan/binary/base_record.rb
|
|
279
279
|
- lib/fontisan/binary/structures.rb
|
|
280
|
-
- lib/fontisan/cldr.rb
|
|
281
|
-
- lib/fontisan/cldr/aggregator.rb
|
|
282
|
-
- lib/fontisan/cldr/cache_manager.rb
|
|
283
|
-
- lib/fontisan/cldr/config.rb
|
|
284
|
-
- lib/fontisan/cldr/download_error.rb
|
|
285
|
-
- lib/fontisan/cldr/downloader.rb
|
|
286
|
-
- lib/fontisan/cldr/error.rb
|
|
287
|
-
- lib/fontisan/cldr/index.rb
|
|
288
|
-
- lib/fontisan/cldr/index_builder.rb
|
|
289
|
-
- lib/fontisan/cldr/unicode_set_parser.rb
|
|
290
|
-
- lib/fontisan/cldr/unknown_version_error.rb
|
|
291
|
-
- lib/fontisan/cldr/version_resolver.rb
|
|
292
280
|
- lib/fontisan/cli.rb
|
|
293
|
-
- lib/fontisan/cli/cldr_cli.rb
|
|
294
281
|
- lib/fontisan/collection.rb
|
|
295
282
|
- lib/fontisan/collection/builder.rb
|
|
296
283
|
- lib/fontisan/collection/dfont_builder.rb
|
|
297
284
|
- lib/fontisan/collection/offset_calculator.rb
|
|
285
|
+
- lib/fontisan/collection/reader.rb
|
|
286
|
+
- lib/fontisan/collection/reader/stats.rb
|
|
298
287
|
- lib/fontisan/collection/shared_logic.rb
|
|
299
288
|
- lib/fontisan/collection/table_analyzer.rb
|
|
300
289
|
- lib/fontisan/collection/table_deduplicator.rb
|
|
@@ -309,6 +298,7 @@ files:
|
|
|
309
298
|
- lib/fontisan/commands/info_command.rb
|
|
310
299
|
- lib/fontisan/commands/instance_command.rb
|
|
311
300
|
- lib/fontisan/commands/ls_command.rb
|
|
301
|
+
- lib/fontisan/commands/multi_format_output.rb
|
|
312
302
|
- lib/fontisan/commands/optical_size_command.rb
|
|
313
303
|
- lib/fontisan/commands/pack_command.rb
|
|
314
304
|
- lib/fontisan/commands/scripts_command.rb
|
|
@@ -316,9 +306,9 @@ files:
|
|
|
316
306
|
- lib/fontisan/commands/tables_command.rb
|
|
317
307
|
- lib/fontisan/commands/unicode_command.rb
|
|
318
308
|
- lib/fontisan/commands/unpack_command.rb
|
|
309
|
+
- lib/fontisan/commands/validate_collection_command.rb
|
|
319
310
|
- lib/fontisan/commands/validate_command.rb
|
|
320
311
|
- lib/fontisan/commands/variable_command.rb
|
|
321
|
-
- lib/fontisan/config/cldr.yml
|
|
322
312
|
- lib/fontisan/config/collection_settings.yml
|
|
323
313
|
- lib/fontisan/config/conversion_matrix.yml
|
|
324
314
|
- lib/fontisan/config/export_settings.yml
|
|
@@ -379,8 +369,6 @@ files:
|
|
|
379
369
|
- lib/fontisan/models/all_scripts_features_info.rb
|
|
380
370
|
- lib/fontisan/models/bitmap_glyph.rb
|
|
381
371
|
- lib/fontisan/models/bitmap_strike.rb
|
|
382
|
-
- lib/fontisan/models/cldr.rb
|
|
383
|
-
- lib/fontisan/models/cldr/language_coverage.rb
|
|
384
372
|
- lib/fontisan/models/collection_brief_info.rb
|
|
385
373
|
- lib/fontisan/models/collection_font_summary.rb
|
|
386
374
|
- lib/fontisan/models/collection_info.rb
|
|
@@ -444,9 +432,15 @@ files:
|
|
|
444
432
|
- lib/fontisan/sfnt_font.rb
|
|
445
433
|
- lib/fontisan/sfnt_table.rb
|
|
446
434
|
- lib/fontisan/stitcher.rb
|
|
435
|
+
- lib/fontisan/stitcher/collection_result.rb
|
|
447
436
|
- lib/fontisan/stitcher/deduplicator.rb
|
|
448
437
|
- lib/fontisan/stitcher/glyph_limit.rb
|
|
449
438
|
- lib/fontisan/stitcher/glyph_signature.rb
|
|
439
|
+
- lib/fontisan/stitcher/partition_strategy.rb
|
|
440
|
+
- lib/fontisan/stitcher/partition_strategy/base.rb
|
|
441
|
+
- lib/fontisan/stitcher/partition_strategy/blueprint.rb
|
|
442
|
+
- lib/fontisan/stitcher/partition_strategy/by_plane.rb
|
|
443
|
+
- lib/fontisan/stitcher/partition_strategy/partition.rb
|
|
450
444
|
- lib/fontisan/stitcher/selector.rb
|
|
451
445
|
- lib/fontisan/stitcher/selector/codepoints.rb
|
|
452
446
|
- lib/fontisan/stitcher/selector/gid.rb
|
|
@@ -642,6 +636,8 @@ files:
|
|
|
642
636
|
- lib/fontisan/ufo/reader.rb
|
|
643
637
|
- lib/fontisan/ufo/transformation.rb
|
|
644
638
|
- lib/fontisan/ufo/writer.rb
|
|
639
|
+
- lib/fontisan/unicode.rb
|
|
640
|
+
- lib/fontisan/unicode/plane.rb
|
|
645
641
|
- lib/fontisan/utilities.rb
|
|
646
642
|
- lib/fontisan/utilities/brotli_wrapper.rb
|
|
647
643
|
- lib/fontisan/utilities/checksum_calculator.rb
|