fontisan 0.4.9 → 0.4.11
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/Rakefile +53 -39
- data/docs/STITCHER_GUIDE.adoc +12 -2
- data/lib/fontisan/converters/svg_generator.rb +30 -82
- data/lib/fontisan/sfnt_font.rb +12 -2
- data/lib/fontisan/stitcher/format_metadata.rb +51 -0
- data/lib/fontisan/stitcher/partition_strategy/base.rb +32 -3
- data/lib/fontisan/stitcher/partition_strategy/by_block.rb +429 -0
- data/lib/fontisan/stitcher/partition_strategy/by_plane.rb +59 -29
- data/lib/fontisan/stitcher/partition_strategy/by_script.rb +305 -0
- data/lib/fontisan/stitcher/partition_strategy.rb +2 -0
- data/lib/fontisan/stitcher/source.rb +89 -8
- data/lib/fontisan/stitcher.rb +9 -27
- data/lib/fontisan/subset/table_subsetter.rb +123 -5
- data/lib/fontisan/svg/font_generator.rb +2 -2
- data/lib/fontisan/svg/glyph_generator.rb +44 -9
- data/lib/fontisan/tasks/fixture_downloader.rb +162 -0
- data/lib/fontisan/tasks.rb +11 -0
- data/lib/fontisan/unicode/plane.rb +0 -12
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan.rb +2 -0
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1ed3f50509fb7f223b2cdcfdf7e59345e83b5ca2d11b2a0806574651687c4582
|
|
4
|
+
data.tar.gz: ccb6cabe8c99dfcc060acc7f46d26efd9961b5384365ab404552dad539dc4555
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b977d4f9ac972020514daca43a14917784c713258f73223597c8a48dd2b32fcf3ccb9adbe2bd343db7ac43e63eaba8943a883580ec3c7bb7dec23f6a0f1e1fe9
|
|
7
|
+
data.tar.gz: 5c17de9dc3c60f44e472e94c19a1ca33435805ed81f261cc357dfcfd6d197f3109531ae8ad4b3a842e52f4f04de19501c0e860793212b87e874b63f01d99d8f3
|
data/Rakefile
CHANGED
|
@@ -12,24 +12,25 @@ RuboCop::RakeTask.new
|
|
|
12
12
|
namespace :fixtures do
|
|
13
13
|
# Load centralized fixture configuration
|
|
14
14
|
require_relative "spec/support/fixture_fonts"
|
|
15
|
+
require "fontisan/tasks"
|
|
15
16
|
|
|
16
17
|
# Helper method to download a single file
|
|
17
18
|
def download_single_file(name, url, target_path)
|
|
18
|
-
require "open-uri"
|
|
19
|
-
|
|
20
19
|
puts "[fixtures:download] Downloading #{name}..."
|
|
21
|
-
FileUtils.mkdir_p(File.dirname(target_path))
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
Fontisan::Tasks::FixtureDownloader.new(
|
|
22
|
+
url: url,
|
|
23
|
+
destination: target_path,
|
|
24
|
+
).call
|
|
26
25
|
|
|
27
26
|
puts "[fixtures:download] #{name} downloaded successfully"
|
|
27
|
+
rescue Fontisan::Tasks::FixtureDownloader::Error => e
|
|
28
|
+
warn "[fixtures:download] #{name} failed after retries: #{e.message}"
|
|
29
|
+
raise
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
# Helper method to download and extract a font archive
|
|
31
33
|
def download_font(name, url, target_dir)
|
|
32
|
-
require "open-uri"
|
|
33
34
|
require "zip"
|
|
34
35
|
|
|
35
36
|
puts "[fixtures:download] Downloading #{name}..."
|
|
@@ -39,45 +40,58 @@ namespace :fixtures do
|
|
|
39
40
|
temp_path = File.join(Dir.tmpdir,
|
|
40
41
|
"fontisan_#{name}_#{Process.pid}_#{rand(10000)}.zip")
|
|
41
42
|
|
|
42
|
-
# Download using IO.copy_stream for better Windows compatibility
|
|
43
|
-
URI.open(url, "rb") do |remote|
|
|
44
|
-
File.open(temp_path, "wb") do |file|
|
|
45
|
-
IO.copy_stream(remote, file)
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
puts "[fixtures:download] Extracting #{name}..."
|
|
50
|
-
|
|
51
|
-
# Open zip file and ensure it's fully closed before we're done
|
|
52
|
-
zip_file = Zip::File.open(temp_path)
|
|
53
43
|
begin
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
44
|
+
Fontisan::Tasks::FixtureDownloader.new(
|
|
45
|
+
url: url,
|
|
46
|
+
destination: temp_path,
|
|
47
|
+
).call
|
|
48
|
+
|
|
49
|
+
puts "[fixtures:download] Extracting #{name}..."
|
|
50
|
+
|
|
51
|
+
# Open zip file and ensure it's fully closed before we're done
|
|
52
|
+
zip_file = Zip::File.open(temp_path)
|
|
53
|
+
begin
|
|
54
|
+
zip_file.each do |entry|
|
|
55
|
+
# Skip macOS metadata files and directories
|
|
56
|
+
next if entry.name.start_with?("__MACOSX/") || entry.name.include?("/._")
|
|
57
|
+
next if entry.directory?
|
|
58
|
+
|
|
59
|
+
# Ensure entry.name is relative by stripping leading slashes
|
|
60
|
+
relative_name = entry.name.sub(%r{^/+}, "")
|
|
61
|
+
|
|
62
|
+
dest_path = File.join(target_dir, relative_name)
|
|
63
|
+
FileUtils.mkdir_p(File.dirname(dest_path))
|
|
64
|
+
|
|
65
|
+
# Skip if file already exists
|
|
66
|
+
next if File.exist?(dest_path)
|
|
67
|
+
|
|
68
|
+
# Write the file content directly using binary mode
|
|
69
|
+
File.open(dest_path, "wb") do |file|
|
|
70
|
+
IO.copy_stream(entry.get_input_stream, file)
|
|
71
|
+
end
|
|
71
72
|
end
|
|
73
|
+
ensure
|
|
74
|
+
# Explicitly close the zip file to release file handle on Windows
|
|
75
|
+
zip_file&.close
|
|
72
76
|
end
|
|
73
77
|
ensure
|
|
74
|
-
#
|
|
75
|
-
|
|
78
|
+
# Clean up the temp zip explicitly so the temp dir doesn't fill
|
|
79
|
+
# up on repeated runs. On Windows the just-closed zip file
|
|
80
|
+
# handle can briefly hold a lock that surfaces as
|
|
81
|
+
# Errno::EACCES; swallow that one error so the rake task can
|
|
82
|
+
# complete (OS will sweep the temp file later).
|
|
83
|
+
begin
|
|
84
|
+
File.delete(temp_path) if File.exist?(temp_path)
|
|
85
|
+
rescue Errno::EACCES
|
|
86
|
+
warn "[fixtures:download] could not delete temp zip #{temp_path}; " \
|
|
87
|
+
"OS will clean it up"
|
|
88
|
+
end
|
|
76
89
|
end
|
|
77
90
|
|
|
78
|
-
# Temp file left in Dir.tmpdir - OS will clean it up automatically
|
|
79
|
-
|
|
80
91
|
puts "[fixtures:download] #{name} downloaded successfully"
|
|
92
|
+
rescue Fontisan::Tasks::FixtureDownloader::Error => e
|
|
93
|
+
warn "[fixtures:download] #{name} failed after retries: #{e.message}"
|
|
94
|
+
raise
|
|
81
95
|
rescue LoadError => e
|
|
82
96
|
warn "[fixtures:download] Error: Required gem not installed. Please run: gem install rubyzip"
|
|
83
97
|
raise e
|
data/docs/STITCHER_GUIDE.adoc
CHANGED
|
@@ -165,8 +165,18 @@ must either raise the cap, drop codepoints, or switch to a format
|
|
|
165
165
|
with a higher glyph limit.
|
|
166
166
|
|
|
167
167
|
The Unicode plane metadata used by ByPlane lives in
|
|
168
|
-
`Fontisan::Unicode::Plane` (`Plane.of(cp)`, `Plane.label(n)
|
|
169
|
-
|
|
168
|
+
`Fontisan::Unicode::Plane` (`Plane.of(cp)`, `Plane.label(n)`).
|
|
169
|
+
|
|
170
|
+
The sub-plane block boundaries used for sub-splitting live on ByPlane
|
|
171
|
+
itself:
|
|
172
|
+
|
|
173
|
+
* `ByPlane::ATOMIC_BLOCKS` — five SIP CJK extension blocks that cannot
|
|
174
|
+
be sub-split; raise if one alone exceeds `cap`.
|
|
175
|
+
* `ByPlane::CARVE_OUT_BLOCKS` — BMP-resident blocks (CJK Unified
|
|
176
|
+
Ideographs, Hangul Syllables) that get their own partition bucket
|
|
177
|
+
when BMP overflows `cap`; chunkable, never raised.
|
|
178
|
+
* `ByPlane::RECOGNIZED_BLOCKS` — union of the two; the lookup table
|
|
179
|
+
the bucketing pass walks.
|
|
170
180
|
|
|
171
181
|
=== Partition / Blueprint
|
|
172
182
|
|
|
@@ -145,7 +145,7 @@ palette_index: 0)
|
|
|
145
145
|
#
|
|
146
146
|
# @param font [TrueTypeFont, OpenTypeFont] Source font
|
|
147
147
|
# @param options [Hash] Extraction options
|
|
148
|
-
# @return [Hash] Glyph data map (glyph_id => {outline,
|
|
148
|
+
# @return [Hash] Glyph data map (glyph_id => {outline, codepoints, name, advance})
|
|
149
149
|
def extract_glyph_data(font, options = {})
|
|
150
150
|
extractor = OutlineExtractor.new(font)
|
|
151
151
|
cmap = font.table("cmap")
|
|
@@ -157,33 +157,20 @@ palette_index: 0)
|
|
|
157
157
|
num_glyphs = maxp&.num_glyphs || 0
|
|
158
158
|
max_glyphs = options[:max_glyphs] || num_glyphs
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
gid_to_codepoints = build_glyph_to_codepoints(cmap)
|
|
161
|
+
glyph_names = post&.glyph_names || []
|
|
162
162
|
|
|
163
|
-
# Extract specified or all glyphs
|
|
164
163
|
glyph_ids = options[:glyph_ids] || (0...num_glyphs).to_a
|
|
165
164
|
glyph_ids = glyph_ids.take(max_glyphs) if max_glyphs
|
|
166
165
|
|
|
167
166
|
glyph_ids.each do |glyph_id|
|
|
168
167
|
next if glyph_id >= num_glyphs
|
|
169
168
|
|
|
170
|
-
# Extract outline
|
|
171
|
-
outline = extractor.extract(glyph_id)
|
|
172
|
-
|
|
173
|
-
# Get advance width
|
|
174
|
-
advance = extract_advance_width(hmtx, glyph_id)
|
|
175
|
-
|
|
176
|
-
# Get unicode character
|
|
177
|
-
unicode = unicode_map[glyph_id]
|
|
178
|
-
|
|
179
|
-
# Get glyph name
|
|
180
|
-
glyph_name = extract_glyph_name(post, glyph_id)
|
|
181
|
-
|
|
182
169
|
glyph_data[glyph_id] = {
|
|
183
|
-
outline:
|
|
184
|
-
|
|
185
|
-
name:
|
|
186
|
-
advance:
|
|
170
|
+
outline: extractor.extract(glyph_id),
|
|
171
|
+
codepoints: gid_to_codepoints[glyph_id] || [],
|
|
172
|
+
name: glyph_name_for(glyph_names, glyph_id),
|
|
173
|
+
advance: extract_advance_width(hmtx, glyph_id),
|
|
187
174
|
}
|
|
188
175
|
rescue StandardError => e
|
|
189
176
|
warn "Failed to extract glyph #{glyph_id}: #{e.message}"
|
|
@@ -193,61 +180,38 @@ palette_index: 0)
|
|
|
193
180
|
glyph_data
|
|
194
181
|
end
|
|
195
182
|
|
|
196
|
-
# Build
|
|
183
|
+
# Build reverse cmap: glyph_id => [codepoint, ...].
|
|
197
184
|
#
|
|
198
|
-
#
|
|
199
|
-
#
|
|
200
|
-
|
|
185
|
+
# A glyph can be referenced by multiple codepoints (e.g. space
|
|
186
|
+
# and non-breaking space might share a glyph). Uses
|
|
187
|
+
# +cmap.unicode_mappings+ directly — that is the canonical
|
|
188
|
+
# {codepoint => gid} hash.
|
|
189
|
+
#
|
|
190
|
+
# @param cmap [Tables::Cmap, nil]
|
|
191
|
+
# @return [Hash<Integer, Array<Integer>>]
|
|
192
|
+
def build_glyph_to_codepoints(cmap)
|
|
201
193
|
return {} unless cmap
|
|
202
194
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
# Get best cmap subtable (prefer Unicode BMP or full)
|
|
206
|
-
subtable = find_best_cmap_subtable(cmap)
|
|
207
|
-
return {} unless subtable
|
|
208
|
-
|
|
209
|
-
# Build reverse map: glyph_id => unicode
|
|
210
|
-
subtable.each do |code_point, glyph_id|
|
|
211
|
-
# Store first unicode for each glyph
|
|
212
|
-
next if unicode_map[glyph_id]
|
|
213
|
-
|
|
214
|
-
unicode_map[glyph_id] = [code_point].pack("U")
|
|
215
|
-
rescue StandardError
|
|
216
|
-
# Skip invalid code points
|
|
217
|
-
next
|
|
195
|
+
cmap.unicode_mappings.each_with_object(Hash.new { |h, k| h[k] = [] }) do |(cp, gid), h|
|
|
196
|
+
h[gid] << cp
|
|
218
197
|
end
|
|
219
|
-
|
|
220
|
-
unicode_map
|
|
221
198
|
rescue StandardError => e
|
|
222
|
-
warn "Failed to build
|
|
199
|
+
warn "Failed to build glyph to codepoints map: #{e.message}"
|
|
223
200
|
{}
|
|
224
201
|
end
|
|
225
202
|
|
|
226
|
-
#
|
|
203
|
+
# Look up glyph name from the post table's per-gid name array.
|
|
227
204
|
#
|
|
228
|
-
#
|
|
229
|
-
#
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
# Try Unicode (platform 0, encoding 3) - Unicode 2.0+ BMP
|
|
240
|
-
subtable = cmap.subtable(0, 3)
|
|
241
|
-
return subtable if subtable
|
|
242
|
-
|
|
243
|
-
# Try Unicode (platform 0, encoding 4) - Unicode 2.0+ full
|
|
244
|
-
subtable = cmap.subtable(0, 4)
|
|
245
|
-
return subtable if subtable
|
|
246
|
-
|
|
247
|
-
# Fallback to any available subtable
|
|
248
|
-
cmap.subtables.first
|
|
249
|
-
rescue StandardError
|
|
250
|
-
nil
|
|
205
|
+
# Falls back to +"gidN"+ when the post table has no name for the
|
|
206
|
+
# given gid (post version 3.0 fonts omit per-glyph names, and
|
|
207
|
+
# version 2.0 may have gaps). Always returns a non-nil string so
|
|
208
|
+
# the SVG <glyph> element carries a useful glyph-name attribute.
|
|
209
|
+
#
|
|
210
|
+
# @param glyph_names [Array<String>] post.glyph_names
|
|
211
|
+
# @param glyph_id [Integer]
|
|
212
|
+
# @return [String]
|
|
213
|
+
def glyph_name_for(glyph_names, glyph_id)
|
|
214
|
+
glyph_names[glyph_id] || "gid#{glyph_id}"
|
|
251
215
|
end
|
|
252
216
|
|
|
253
217
|
# Extract advance width for glyph
|
|
@@ -265,22 +229,6 @@ palette_index: 0)
|
|
|
265
229
|
rescue StandardError
|
|
266
230
|
0
|
|
267
231
|
end
|
|
268
|
-
|
|
269
|
-
# Extract glyph name from post table
|
|
270
|
-
#
|
|
271
|
-
# @param post [Tables::Post, nil] Post table
|
|
272
|
-
# @param glyph_id [Integer] Glyph ID
|
|
273
|
-
# @return [String, nil] Glyph name or nil
|
|
274
|
-
def extract_glyph_name(post, glyph_id)
|
|
275
|
-
return nil unless post
|
|
276
|
-
|
|
277
|
-
name = post.glyph_name_for(glyph_id)
|
|
278
|
-
return nil if name.nil? || name.empty?
|
|
279
|
-
|
|
280
|
-
name
|
|
281
|
-
rescue StandardError
|
|
282
|
-
nil
|
|
283
|
-
end
|
|
284
232
|
end
|
|
285
233
|
end
|
|
286
234
|
end
|
data/lib/fontisan/sfnt_font.rb
CHANGED
|
@@ -59,8 +59,16 @@ module Fontisan
|
|
|
59
59
|
header.num_tables
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
# Table data is stored separately since it's at variable offsets
|
|
63
|
-
|
|
62
|
+
# Table data is stored separately since it's at variable offsets.
|
|
63
|
+
# The setter normalizes all keys to UTF-8 so Hash lookups don't
|
|
64
|
+
# silently miss due to encoding mismatch — BinData-derived tag
|
|
65
|
+
# strings are ASCII-8BIT, literals callers pass are usually UTF-8,
|
|
66
|
+
# and String#eql? is encoding-sensitive.
|
|
67
|
+
attr_reader :table_data
|
|
68
|
+
|
|
69
|
+
def table_data=(hash)
|
|
70
|
+
@table_data = hash.transform_keys { |tag| normalize_tag(tag) }
|
|
71
|
+
end
|
|
64
72
|
|
|
65
73
|
# Parsed table instances cache
|
|
66
74
|
attr_accessor :parsed_tables
|
|
@@ -470,6 +478,8 @@ module Fontisan
|
|
|
470
478
|
# @return [Tables::*, nil] Parsed table object or nil if not found
|
|
471
479
|
# @raise [ArgumentError] if table is not available in current loading mode
|
|
472
480
|
def table(tag)
|
|
481
|
+
tag = normalize_tag(tag)
|
|
482
|
+
|
|
473
483
|
# Check mode restrictions
|
|
474
484
|
unless table_available?(tag)
|
|
475
485
|
if has_table?(tag)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
class Stitcher
|
|
5
|
+
# Immutable metadata about a stitcher output format. Single source
|
|
6
|
+
# of truth for "given a format symbol, what compiler / collection
|
|
7
|
+
# format / file extension does it use?". Adding a new format =
|
|
8
|
+
# adding a +when+ branch in {resolve}; no other site in Stitcher
|
|
9
|
+
# needs to know the mapping.
|
|
10
|
+
class FormatMetadata
|
|
11
|
+
attr_reader :name, :compiler_class, :collection_format, :extension
|
|
12
|
+
|
|
13
|
+
# @param name [Symbol] canonical format name (:ttf, :otf, :otf2)
|
|
14
|
+
# @param compiler_class [Class] the +Ufo::Compile::*Compiler+ that
|
|
15
|
+
# compiles a +Ufo::Font+ target into the format's binary
|
|
16
|
+
# @param collection_format [Symbol] :ttc or :otc — the
|
|
17
|
+
# +Collection::Builder+ format used when packing multiple
|
|
18
|
+
# subfonts of this format into a collection
|
|
19
|
+
# @param extension [String] file extension including the dot
|
|
20
|
+
def initialize(name:, compiler_class:, collection_format:, extension:)
|
|
21
|
+
@name = name
|
|
22
|
+
@compiler_class = compiler_class
|
|
23
|
+
@collection_format = collection_format
|
|
24
|
+
@extension = extension
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Resolve a format name (Symbol or String) to its metadata.
|
|
28
|
+
# Constants are referenced inside +when+ branches so autoload
|
|
29
|
+
# fires only for the requested format, not all of them.
|
|
30
|
+
#
|
|
31
|
+
# @param name [Symbol, String]
|
|
32
|
+
# @raise [ArgumentError] if +name+ is not a known stitcher format
|
|
33
|
+
# @return [FormatMetadata]
|
|
34
|
+
def self.resolve(name)
|
|
35
|
+
case name.to_sym
|
|
36
|
+
when :ttf
|
|
37
|
+
new(name: :ttf, compiler_class: Ufo::Compile::TtfCompiler,
|
|
38
|
+
collection_format: :ttc, extension: ".ttf")
|
|
39
|
+
when :otf
|
|
40
|
+
new(name: :otf, compiler_class: Ufo::Compile::OtfCompiler,
|
|
41
|
+
collection_format: :otc, extension: ".otf")
|
|
42
|
+
when :otf2
|
|
43
|
+
new(name: :otf2, compiler_class: Ufo::Compile::Otf2Compiler,
|
|
44
|
+
collection_format: :otc, extension: ".otf")
|
|
45
|
+
else
|
|
46
|
+
raise ArgumentError, "unknown format: #{name.inspect}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -6,9 +6,38 @@ module Fontisan
|
|
|
6
6
|
# Abstract base. Concrete partitioners (ByPlane, ByBlock, …)
|
|
7
7
|
# implement {#call} and return a {Blueprint}.
|
|
8
8
|
class Base
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
|
|
9
|
+
# Glyph-cap headroom below the format's hard cap
|
|
10
|
+
# (GlyphLimit::TTF_GLYPH_CAP / OTF_GLYPH_CAP = 65,535). The
|
|
11
|
+
# partitioner must keep each partition's *codepoint* count low
|
|
12
|
+
# enough that compile-time *glyph* expansion still fits under
|
|
13
|
+
# the hard cap.
|
|
14
|
+
#
|
|
15
|
+
# Reserved slots:
|
|
16
|
+
# - .notdef (1 slot, mandated by OpenType — every font has it
|
|
17
|
+
# at GID 0 even when no codepoint binds to it)
|
|
18
|
+
# - composite expansion: compiling adds composites (ligatures,
|
|
19
|
+
# decomposable glyphs, variant attachments) on top of the
|
|
20
|
+
# source codepoint count. Empirically ~5% of typical BMP
|
|
21
|
+
# size for CJK-heavy fonts (≈2,500 composites on a 50k-cp
|
|
22
|
+
# BMP font). 5,534 slots covers that with margin.
|
|
23
|
+
NOTDEF_RESERVED = 1
|
|
24
|
+
COMPOSITE_HEADROOM = 5_534
|
|
25
|
+
|
|
26
|
+
# Derive the partition cap from a format's hard glyph limit.
|
|
27
|
+
# Mirrors GlyphLimit so a future bump (e.g. CFF2 card24 INDEX
|
|
28
|
+
# counts) cascades automatically — no second magic number to
|
|
29
|
+
# keep in sync.
|
|
30
|
+
#
|
|
31
|
+
# @param format [Symbol] :ttf, :otf, or :otf2
|
|
32
|
+
# @return [Integer]
|
|
33
|
+
def self.cap_for(format)
|
|
34
|
+
GlyphLimit.for_format(format) - NOTDEF_RESERVED - COMPOSITE_HEADROOM
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Default cap for the most common format (TTF, where the glyph
|
|
38
|
+
# cap equals 65,535). Use {.cap_for} when partitioning for a
|
|
39
|
+
# different format.
|
|
40
|
+
DEFAULT_CAP = cap_for(:ttf)
|
|
12
41
|
|
|
13
42
|
# @param cp_map [Hash{Integer=>Object}] codepoint → donor label
|
|
14
43
|
# @param cap [Integer] max codepoints per partition
|