fontisan 0.4.6 → 0.4.8
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/BUG-stitcher-drops-isolated-cps.md +58 -0
- data/BUG-stitcher-drops-plane1-codepoints.md +310 -0
- data/BUG-stitcher-gid-cap-65535.md +110 -0
- data/CHANGELOG.md +119 -0
- data/README.adoc +121 -68
- data/benchmark/compile_benchmark.rb +70 -0
- data/docs/.vitepress/config.ts +0 -7
- data/docs/CFF2_SUPPORT.adoc +184 -0
- data/docs/STITCHER_GUIDE.adoc +151 -0
- data/docs/SVG_TO_GLYF.adoc +118 -0
- data/docs/UFO_COMPILATION.adoc +119 -0
- data/docs/cli/index.md +5 -28
- 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/writer.rb +5 -6
- 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 +56 -0
- data/lib/fontisan/models.rb +0 -1
- data/lib/fontisan/stitcher/collection_result.rb +18 -0
- data/lib/fontisan/stitcher/deduplicator.rb +47 -0
- data/lib/fontisan/stitcher/glyph_limit.rb +53 -0
- data/lib/fontisan/stitcher/glyph_signature.rb +51 -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 +223 -168
- data/lib/fontisan/svg_to_glyf/assembler.rb +132 -0
- data/lib/fontisan/svg_to_glyf/document.rb +83 -0
- data/lib/fontisan/svg_to_glyf/geometry/affine_transform.rb +112 -0
- data/lib/fontisan/svg_to_glyf/geometry/normalizer.rb +45 -0
- data/lib/fontisan/svg_to_glyf/geometry/transform_parser.rb +91 -0
- data/lib/fontisan/svg_to_glyf/geometry.rb +13 -0
- data/lib/fontisan/svg_to_glyf/path/command.rb +18 -0
- data/lib/fontisan/svg_to_glyf/path/contour_builder.rb +140 -0
- data/lib/fontisan/svg_to_glyf/path/parser.rb +98 -0
- data/lib/fontisan/svg_to_glyf/path/state.rb +79 -0
- data/lib/fontisan/svg_to_glyf/path.rb +14 -0
- data/lib/fontisan/svg_to_glyf.rb +62 -0
- data/lib/fontisan/tables/cff/cff2_charstring_builder.rb +216 -0
- data/lib/fontisan/tables/cff.rb +1 -0
- data/lib/fontisan/tables/cff2/dict_encoder.rb +94 -0
- data/lib/fontisan/tables/cff2/fd_select.rb +69 -0
- data/lib/fontisan/tables/cff2/header.rb +34 -0
- data/lib/fontisan/tables/cff2/index_builder.rb +79 -0
- data/lib/fontisan/tables/cff2.rb +4 -0
- data/lib/fontisan/ufo/compile/cbdt_cblc.rb +103 -0
- data/lib/fontisan/ufo/compile/cff2.rb +181 -0
- data/lib/fontisan/ufo/compile/cff2_subroutines.rb +39 -0
- data/lib/fontisan/ufo/compile/colr.rb +80 -0
- data/lib/fontisan/ufo/compile/cpal.rb +61 -0
- data/lib/fontisan/ufo/compile/math.rb +143 -0
- data/lib/fontisan/ufo/compile/meta.rb +51 -0
- data/lib/fontisan/ufo/compile/name.rb +2 -2
- data/lib/fontisan/ufo/compile/otf2_compiler.rb +46 -0
- data/lib/fontisan/ufo/compile/sbix.rb +99 -0
- data/lib/fontisan/ufo/compile/svg_table.rb +60 -0
- data/lib/fontisan/ufo/compile/variable_otf.rb +75 -0
- data/lib/fontisan/ufo/compile.rb +11 -0
- 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 +5 -2
- metadata +53 -19
- 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,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
|