fontisan 0.4.22 → 0.4.24
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/.rubocop_todo.yml +240 -24
- data/Gemfile +6 -0
- data/Rakefile +9 -6
- data/benchmark/compile_benchmark.rb +13 -5
- data/docs/.vitepress/config.ts +1 -0
- data/docs/STITCHER_GUIDE.adoc +47 -3
- data/docs/api/layer.md +89 -0
- data/docs/cli/subset.md +25 -6
- data/docs/guide/cli/subset.md +31 -0
- data/docs/guide/color-fonts/bitmaps.md +17 -0
- data/lib/fontisan/cli.rb +2 -1
- data/lib/fontisan/collection/writer.rb +2 -1
- data/lib/fontisan/commands/validate_collection_command.rb +7 -1
- data/lib/fontisan/commands.rb +2 -1
- data/lib/fontisan/config/subset_profiles.yml +2 -0
- data/lib/fontisan/converters/collection_converter.rb +6 -1
- data/lib/fontisan/converters/svg_generator.rb +3 -1
- data/lib/fontisan/stitcher/cbdt_propagator.rb +44 -22
- data/lib/fontisan/stitcher/collection_result.rb +2 -1
- data/lib/fontisan/stitcher/glyph_cloner.rb +41 -0
- data/lib/fontisan/stitcher/glyph_copier.rb +5 -34
- data/lib/fontisan/stitcher/partition_strategy/by_block.rb +3 -1
- data/lib/fontisan/stitcher/partition_strategy/by_script.rb +6 -2
- data/lib/fontisan/stitcher/source.rb +2 -1
- data/lib/fontisan/stitcher/unique_glyph_name.rb +46 -0
- data/lib/fontisan/stitcher.rb +4 -1
- data/lib/fontisan/subset/shared_state.rb +42 -0
- data/lib/fontisan/subset/subset_context.rb +20 -0
- data/lib/fontisan/subset/table_strategy/cbdt.rb +30 -0
- data/lib/fontisan/subset/table_strategy/cblc.rb +27 -0
- data/lib/fontisan/subset/table_strategy/cmap.rb +146 -0
- data/lib/fontisan/subset/table_strategy/color_bitmap_placement.rb +20 -0
- data/lib/fontisan/subset/table_strategy/color_bitmap_strike_plan.rb +45 -0
- data/lib/fontisan/subset/table_strategy/color_bitmap_subset_plan.rb +62 -0
- data/lib/fontisan/subset/table_strategy/color_bitmap_subsetter.rb +182 -0
- data/lib/fontisan/subset/table_strategy/color_bitmap_subtable_plan.rb +59 -0
- data/lib/fontisan/subset/table_strategy/glyf.rb +29 -0
- data/lib/fontisan/subset/table_strategy/glyf_loca_builder.rb +146 -0
- data/lib/fontisan/subset/table_strategy/head.rb +42 -0
- data/lib/fontisan/subset/table_strategy/hhea.rb +67 -0
- data/lib/fontisan/subset/table_strategy/hmtx.rb +52 -0
- data/lib/fontisan/subset/table_strategy/loca.rb +41 -0
- data/lib/fontisan/subset/table_strategy/maxp.rb +23 -0
- data/lib/fontisan/subset/table_strategy/name.rb +19 -0
- data/lib/fontisan/subset/table_strategy/os2.rb +21 -0
- data/lib/fontisan/subset/table_strategy/pass_through.rb +20 -0
- data/lib/fontisan/subset/table_strategy/post.rb +37 -0
- data/lib/fontisan/subset/table_strategy.rb +75 -0
- data/lib/fontisan/subset/table_subsetter.rb +49 -616
- data/lib/fontisan/subset.rb +3 -0
- data/lib/fontisan/svg/standalone_glyph.rb +4 -1
- data/lib/fontisan/svg_to_glyf/assembler.rb +6 -3
- data/lib/fontisan/svg_to_glyf/geometry/transform_parser.rb +11 -4
- data/lib/fontisan/svg_to_glyf/geometry.rb +4 -2
- data/lib/fontisan/tables/cbdt.rb +66 -121
- data/lib/fontisan/tables/cblc.rb +110 -231
- data/lib/fontisan/tables/cblc_big_glyph_metrics.rb +28 -0
- data/lib/fontisan/tables/cblc_bitmap_size.rb +67 -0
- data/lib/fontisan/tables/cblc_glyph_bitmap_location.rb +28 -0
- data/lib/fontisan/tables/cblc_index_subtable.rb +104 -0
- data/lib/fontisan/tables/cblc_index_subtable_array_entry.rb +20 -0
- data/lib/fontisan/tables/cblc_index_subtable_format_parser.rb +150 -0
- data/lib/fontisan/tables/cblc_index_subtable_header.rb +18 -0
- data/lib/fontisan/tables/cblc_sbit_line_metrics.rb +28 -0
- data/lib/fontisan/tables/cff/cff2_charstring_builder.rb +11 -5
- data/lib/fontisan/tables/cff.rb +2 -1
- data/lib/fontisan/tables/cff2/index_builder.rb +2 -1
- data/lib/fontisan/tables.rb +12 -0
- data/lib/fontisan/ufo/cli.rb +4 -1
- data/lib/fontisan/ufo/compile/cbdt_cblc.rb +2 -1
- data/lib/fontisan/ufo/compile/cff.rb +6 -2
- data/lib/fontisan/ufo/compile/cmap.rb +11 -5
- data/lib/fontisan/ufo/compile/cpal.rb +2 -1
- data/lib/fontisan/ufo/compile/feature_writers/gdef.rb +3 -1
- data/lib/fontisan/ufo/compile/feature_writers/mark_family_base.rb +3 -1
- data/lib/fontisan/ufo/compile/feature_writers.rb +2 -1
- data/lib/fontisan/ufo/compile/filters/cubic_to_quadratic.rb +4 -2
- data/lib/fontisan/ufo/compile/filters/remove_overlaps.rb +2 -1
- data/lib/fontisan/ufo/compile/fvar.rb +7 -3
- data/lib/fontisan/ufo/compile/glyf_loca.rb +4 -2
- data/lib/fontisan/ufo/compile/gvar.rb +5 -2
- data/lib/fontisan/ufo/compile/head.rb +2 -1
- data/lib/fontisan/ufo/compile/item_variation_store.rb +2 -1
- data/lib/fontisan/ufo/compile/name.rb +3 -1
- data/lib/fontisan/ufo/compile/sbix.rb +2 -1
- data/lib/fontisan/ufo/compile/stat.rb +6 -3
- data/lib/fontisan/ufo/convert/from_bin_data.rb +6 -2
- data/lib/fontisan/ufo/convert/to_dfont.rb +6 -2
- data/lib/fontisan/ufo/convert/to_otc.rb +4 -1
- data/lib/fontisan/ufo/convert/to_postscript.rb +8 -3
- data/lib/fontisan/ufo/convert/to_woff.rb +8 -3
- data/lib/fontisan/ufo/convert/to_woff2.rb +8 -3
- data/lib/fontisan/ufo/convert.rb +4 -1
- data/lib/fontisan/ufo/glyph_exists_error.rb +23 -0
- data/lib/fontisan/ufo/info.rb +4 -2
- data/lib/fontisan/ufo/layer.rb +42 -2
- data/lib/fontisan/ufo/plist.rb +7 -2
- data/lib/fontisan/ufo/reader.rb +3 -1
- data/lib/fontisan/ufo/writer.rb +13 -5
- data/lib/fontisan/ufo.rb +2 -1
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2/collection_decoder.rb +4 -1
- data/lib/fontisan/woff2/collection_encoder.rb +4 -1
- data/lib/fontisan/woff2/glyf_canonicalizer.rb +2 -1
- data/lib/fontisan/woff2/glyf_loca_reconstruct.rb +5 -2
- data/lib/fontisan/woff2/sfnt_checksum.rb +2 -1
- data/lib/fontisan.rb +0 -1
- metadata +36 -4
- data/lib/fontisan/tasks/fixture_downloader.rb +0 -162
- data/lib/fontisan/tasks.rb +0 -11
data/lib/fontisan/tables/cblc.rb
CHANGED
|
@@ -1,291 +1,170 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "stringio"
|
|
4
|
-
|
|
5
3
|
module Fontisan
|
|
6
4
|
module Tables
|
|
7
|
-
# CBLC (Color Bitmap Location) table
|
|
8
|
-
#
|
|
9
|
-
# The CBLC table contains location information for bitmap glyphs at various
|
|
10
|
-
# sizes (strikes). It works together with the CBDT table which contains the
|
|
11
|
-
# actual bitmap data.
|
|
5
|
+
# CBLC (Color Bitmap Location) table.
|
|
12
6
|
#
|
|
13
|
-
# CBLC
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# ```
|
|
7
|
+
# CBLC indexes glyph IDs into CBDT byte offsets across one or more
|
|
8
|
+
# bitmap strikes (each strike = a ppem + bit depth combination). For
|
|
9
|
+
# each strike, a list of IndexSubTable records describes how to locate
|
|
10
|
+
# the bitmap of each glyph in a contiguous glyph range.
|
|
18
11
|
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
12
|
+
# Layout:
|
|
13
|
+
# uint32 version (0x00020000 or 0x00030000)
|
|
14
|
+
# uint32 num_sizes (number of BitmapSize records)
|
|
15
|
+
# CblcBitmapSize[num_sizes] (48 bytes each)
|
|
16
|
+
# IndexSubTableArray entries (8 bytes each, per BitmapSize)
|
|
17
|
+
# IndexSubTable records (variable, format-specific)
|
|
22
18
|
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# - numberOfIndexSubTables (uint32): Number of index subtables
|
|
27
|
-
# - colorRef (uint32): Not used, set to 0
|
|
28
|
-
# - hori (SbitLineMetrics, 12 bytes): Horizontal line metrics
|
|
29
|
-
# - vert (SbitLineMetrics, 12 bytes): Vertical line metrics
|
|
30
|
-
# - startGlyphIndex (uint16): First glyph ID in strike
|
|
31
|
-
# - endGlyphIndex (uint16): Last glyph ID in strike
|
|
32
|
-
# - ppemX (uint8): Horizontal pixels per em
|
|
33
|
-
# - ppemY (uint8): Vertical pixels per em
|
|
34
|
-
# - bitDepth (uint8): Bit depth (1, 2, 4, 8, 32)
|
|
35
|
-
# - flags (int8): Flags
|
|
19
|
+
# This model parses the header + BitmapSize records via BinData, then
|
|
20
|
+
# lazily walks the IndexSubTableArray / IndexSubTable structures on
|
|
21
|
+
# demand so callers can iterate [(gid, strike) → CBDT location] pairs.
|
|
36
22
|
#
|
|
37
|
-
# Reference: OpenType CBLC specification
|
|
38
|
-
# https://
|
|
23
|
+
# Reference: OpenType CBLC specification.
|
|
24
|
+
# https://learn.microsoft.com/en-us/typography/opentype/spec/cblc
|
|
39
25
|
#
|
|
40
|
-
# @example
|
|
41
|
-
#
|
|
42
|
-
# cblc
|
|
43
|
-
#
|
|
44
|
-
#
|
|
26
|
+
# @example Enumerate every glyph's CBDT bitmap location
|
|
27
|
+
# cblc = Fontisan::Tables::Cblc.read(font.table_data["CBLC"])
|
|
28
|
+
# cblc.each_glyph_location do |loc|
|
|
29
|
+
# puts "gid=#{loc.glyph_id} cbdt_offset=#{loc.cbdt_offset}"
|
|
30
|
+
# end
|
|
45
31
|
class Cblc < Binary::BaseRecord
|
|
46
|
-
# OpenType table tag for CBLC
|
|
47
32
|
TAG = "CBLC"
|
|
48
33
|
|
|
49
|
-
#
|
|
34
|
+
# CBLC v2.0 — original release
|
|
50
35
|
VERSION_2_0 = 0x00020000
|
|
36
|
+
# CBLC v3.0 — adds PNG image format support
|
|
51
37
|
VERSION_3_0 = 0x00030000
|
|
52
38
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
int8 :ascender
|
|
59
|
-
int8 :descender
|
|
60
|
-
uint8 :width_max
|
|
61
|
-
int8 :caret_slope_numerator
|
|
62
|
-
int8 :caret_slope_denominator
|
|
63
|
-
int8 :caret_offset
|
|
64
|
-
int8 :min_origin_sb
|
|
65
|
-
int8 :min_advance_sb
|
|
66
|
-
int8 :max_before_bl
|
|
67
|
-
int8 :min_after_bl
|
|
68
|
-
int8 :pad1
|
|
69
|
-
int8 :pad2
|
|
70
|
-
end
|
|
39
|
+
uint32 :version
|
|
40
|
+
uint32 :num_sizes
|
|
41
|
+
array :bitmap_sizes,
|
|
42
|
+
type: Fontisan::Tables::CblcBitmapSize,
|
|
43
|
+
initial_length: :num_sizes
|
|
71
44
|
|
|
72
|
-
#
|
|
45
|
+
# Iterate every (glyph_id, strike) → CblcGlyphBitmapLocation pair
|
|
46
|
+
# across all strikes. Each strike's IndexSubTables are walked in
|
|
47
|
+
# turn so the caller sees every glyph's CBDT location exactly once
|
|
48
|
+
# per strike.
|
|
73
49
|
#
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
uint32 :index_tables_size
|
|
79
|
-
uint32 :number_of_index_subtables
|
|
80
|
-
uint32 :color_ref
|
|
81
|
-
|
|
82
|
-
# Read the SbitLineMetrics structures manually
|
|
83
|
-
def self.read(io)
|
|
84
|
-
data = io.is_a?(String) ? io : io.read
|
|
85
|
-
size = new
|
|
86
|
-
|
|
87
|
-
io = StringIO.new(data)
|
|
88
|
-
size.instance_variable_set(:@index_subtable_array_offset,
|
|
89
|
-
io.read(4).unpack1("N"))
|
|
90
|
-
size.instance_variable_set(:@index_tables_size,
|
|
91
|
-
io.read(4).unpack1("N"))
|
|
92
|
-
size.instance_variable_set(:@number_of_index_subtables,
|
|
93
|
-
io.read(4).unpack1("N"))
|
|
94
|
-
size.instance_variable_set(:@color_ref, io.read(4).unpack1("N"))
|
|
95
|
-
|
|
96
|
-
# Parse hori and vert metrics (12 bytes each)
|
|
97
|
-
hori_data = io.read(12)
|
|
98
|
-
vert_data = io.read(12)
|
|
99
|
-
size.instance_variable_set(:@hori, SbitLineMetrics.read(hori_data))
|
|
100
|
-
size.instance_variable_set(:@vert, SbitLineMetrics.read(vert_data))
|
|
101
|
-
|
|
102
|
-
# Parse remaining fields
|
|
103
|
-
size.instance_variable_set(:@start_glyph_index,
|
|
104
|
-
io.read(2).unpack1("n"))
|
|
105
|
-
size.instance_variable_set(:@end_glyph_index, io.read(2).unpack1("n"))
|
|
106
|
-
size.instance_variable_set(:@ppem_x, io.read(1).unpack1("C"))
|
|
107
|
-
size.instance_variable_set(:@ppem_y, io.read(1).unpack1("C"))
|
|
108
|
-
size.instance_variable_set(:@bit_depth, io.read(1).unpack1("C"))
|
|
109
|
-
size.instance_variable_set(:@flags, io.read(1).unpack1("c"))
|
|
110
|
-
|
|
111
|
-
size
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
attr_reader :index_subtable_array_offset, :index_tables_size,
|
|
115
|
-
:number_of_index_subtables, :color_ref, :hori, :vert,
|
|
116
|
-
:start_glyph_index, :end_glyph_index, :ppem_x, :ppem_y,
|
|
117
|
-
:bit_depth, :flags
|
|
118
|
-
|
|
119
|
-
# Get ppem size (assumes square pixels)
|
|
120
|
-
#
|
|
121
|
-
# @return [Integer] Pixels per em
|
|
122
|
-
def ppem
|
|
123
|
-
ppem_x
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
# Get glyph range for this strike
|
|
127
|
-
#
|
|
128
|
-
# @return [Range] Range of glyph IDs
|
|
129
|
-
def glyph_range
|
|
130
|
-
start_glyph_index..end_glyph_index
|
|
131
|
-
end
|
|
50
|
+
# @yield [CblcGlyphBitmapLocation]
|
|
51
|
+
# @return [Enumerator] if no block given
|
|
52
|
+
def each_glyph_location(&block)
|
|
53
|
+
return enum_for(:each_glyph_location) unless block
|
|
132
54
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
# @param glyph_id [Integer] Glyph ID to check
|
|
136
|
-
# @return [Boolean] True if glyph is in range
|
|
137
|
-
def includes_glyph?(glyph_id)
|
|
138
|
-
glyph_range.include?(glyph_id)
|
|
55
|
+
index_sub_tables.each do |sub|
|
|
56
|
+
sub.locations.each(&block)
|
|
139
57
|
end
|
|
140
58
|
end
|
|
141
59
|
|
|
142
|
-
#
|
|
143
|
-
attr_reader :version
|
|
144
|
-
|
|
145
|
-
# @return [Integer] Number of bitmap size records
|
|
146
|
-
attr_reader :num_sizes
|
|
147
|
-
|
|
148
|
-
# @return [Array<BitmapSize>] Parsed bitmap size records
|
|
149
|
-
attr_reader :bitmap_sizes
|
|
150
|
-
|
|
151
|
-
# @return [String] Raw binary data for the entire CBLC table
|
|
152
|
-
attr_reader :raw_data
|
|
153
|
-
|
|
154
|
-
# Override read to parse CBLC structure
|
|
155
|
-
#
|
|
156
|
-
# @param io [IO, String] Binary data to read
|
|
157
|
-
# @return [Cblc] Parsed CBLC table
|
|
158
|
-
def self.read(io)
|
|
159
|
-
cblc = new
|
|
160
|
-
return cblc if io.nil?
|
|
161
|
-
|
|
162
|
-
data = io.is_a?(String) ? io : io.read
|
|
163
|
-
cblc.parse!(data)
|
|
164
|
-
cblc
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# Parse the CBLC table structure
|
|
60
|
+
# All IndexSubTable records across every strike.
|
|
168
61
|
#
|
|
169
|
-
# @
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
@raw_data = data
|
|
173
|
-
io = StringIO.new(data)
|
|
174
|
-
|
|
175
|
-
# Parse CBLC header (8 bytes)
|
|
176
|
-
parse_header(io)
|
|
177
|
-
validate_header!
|
|
178
|
-
|
|
179
|
-
# Parse bitmap size records
|
|
180
|
-
parse_bitmap_sizes(io)
|
|
181
|
-
rescue StandardError => e
|
|
182
|
-
raise CorruptedTableError, "Failed to parse CBLC table: #{e.message}"
|
|
62
|
+
# @return [Array<CblcIndexSubTable>]
|
|
63
|
+
def index_sub_tables
|
|
64
|
+
@index_sub_tables ||= bitmap_sizes.flat_map { |s| sub_tables_for(s) }
|
|
183
65
|
end
|
|
184
66
|
|
|
185
|
-
#
|
|
67
|
+
# Locate a glyph's CBDT bitmap at a specific ppem.
|
|
186
68
|
#
|
|
187
|
-
# @
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
69
|
+
# @param glyph_id [Integer] source glyph ID
|
|
70
|
+
# @param ppem [Integer] strike ppem
|
|
71
|
+
# @return [CblcGlyphBitmapLocation, nil]
|
|
72
|
+
def bitmap_offset_for_gid(glyph_id, ppem)
|
|
73
|
+
strike = bitmap_sizes.find do |s|
|
|
74
|
+
s.ppem == ppem && s.includes_glyph?(glyph_id)
|
|
75
|
+
end
|
|
76
|
+
return nil unless strike
|
|
191
77
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
strikes.select { |size| size.ppem == ppem }
|
|
78
|
+
sub_tables_for(strike).each do |sub|
|
|
79
|
+
loc = sub.locations.find { |l| l.glyph_id == glyph_id }
|
|
80
|
+
return loc if loc
|
|
81
|
+
end
|
|
82
|
+
nil
|
|
198
83
|
end
|
|
199
84
|
|
|
200
|
-
#
|
|
85
|
+
# Iterate every IndexSubTable in a single strike.
|
|
201
86
|
#
|
|
202
|
-
# @param
|
|
203
|
-
# @
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
87
|
+
# @param strike [CblcBitmapSize]
|
|
88
|
+
# @return [Array<CblcIndexSubTable>]
|
|
89
|
+
def sub_tables_for(strike)
|
|
90
|
+
return [] if strike.number_of_index_subtables.zero?
|
|
91
|
+
|
|
92
|
+
array_offset = strike.index_subtable_array_offset
|
|
93
|
+
entries = read_index_subtable_array(strike, array_offset)
|
|
94
|
+
|
|
95
|
+
entries.map do |entry|
|
|
96
|
+
absolute = array_offset + entry.additional_offset_to_index_subtable
|
|
97
|
+
CblcIndexSubTable.parse(
|
|
98
|
+
raw_data,
|
|
99
|
+
index_subtable_offset: absolute,
|
|
100
|
+
first_glyph_index: entry.first_glyph_index,
|
|
101
|
+
last_glyph_index: entry.last_glyph_index,
|
|
102
|
+
)
|
|
208
103
|
end
|
|
209
104
|
end
|
|
210
105
|
|
|
211
|
-
#
|
|
106
|
+
# All available ppem sizes across every strike.
|
|
212
107
|
#
|
|
213
|
-
# @return [Array<Integer>]
|
|
108
|
+
# @return [Array<Integer>]
|
|
214
109
|
def ppem_sizes
|
|
215
|
-
|
|
110
|
+
bitmap_sizes.map(&:ppem).uniq.sort
|
|
216
111
|
end
|
|
217
112
|
|
|
218
|
-
#
|
|
113
|
+
# Number of bitmap strikes.
|
|
219
114
|
#
|
|
220
|
-
# @return [
|
|
221
|
-
def
|
|
222
|
-
|
|
115
|
+
# @return [Integer]
|
|
116
|
+
def num_strikes
|
|
117
|
+
num_sizes
|
|
223
118
|
end
|
|
224
119
|
|
|
225
|
-
#
|
|
120
|
+
# Strikes that cover a given glyph ID.
|
|
226
121
|
#
|
|
227
|
-
# @param glyph_id [Integer]
|
|
228
|
-
# @return [Array<
|
|
122
|
+
# @param glyph_id [Integer]
|
|
123
|
+
# @return [Array<CblcBitmapSize>]
|
|
229
124
|
def strikes_for_glyph(glyph_id)
|
|
230
|
-
|
|
125
|
+
bitmap_sizes.select { |s| s.includes_glyph?(glyph_id) }
|
|
231
126
|
end
|
|
232
127
|
|
|
233
|
-
#
|
|
128
|
+
# Strikes that match a specific ppem.
|
|
234
129
|
#
|
|
235
|
-
# @
|
|
236
|
-
|
|
237
|
-
|
|
130
|
+
# @param ppem [Integer]
|
|
131
|
+
# @return [Array<CblcBitmapSize>]
|
|
132
|
+
def strikes_for_ppem(ppem)
|
|
133
|
+
bitmap_sizes.select { |s| s.ppem == ppem }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# All glyph IDs that have any bitmap across every strike.
|
|
137
|
+
#
|
|
138
|
+
# @return [Array<Integer>]
|
|
139
|
+
def glyph_ids_with_bitmaps
|
|
140
|
+
bitmap_sizes.flat_map { |s| s.glyph_range.to_a }.uniq.sort
|
|
238
141
|
end
|
|
239
142
|
|
|
240
|
-
#
|
|
143
|
+
# Whether the CBLC header is one fontisan understands and at least
|
|
144
|
+
# the header fields parsed successfully.
|
|
241
145
|
#
|
|
242
|
-
# @return [Boolean]
|
|
146
|
+
# @return [Boolean]
|
|
243
147
|
def valid?
|
|
244
148
|
return false if version.nil?
|
|
245
149
|
return false unless [VERSION_2_0, VERSION_3_0].include?(version)
|
|
246
|
-
return false if num_sizes.nil? || num_sizes.negative?
|
|
247
|
-
return false unless bitmap_sizes
|
|
248
150
|
|
|
249
151
|
true
|
|
250
152
|
end
|
|
251
153
|
|
|
252
154
|
private
|
|
253
155
|
|
|
254
|
-
#
|
|
255
|
-
#
|
|
256
|
-
# @param io [StringIO] Input stream
|
|
257
|
-
def parse_header(io)
|
|
258
|
-
@version = io.read(4).unpack1("N")
|
|
259
|
-
@num_sizes = io.read(4).unpack1("N")
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
# Validate header values
|
|
263
|
-
#
|
|
264
|
-
# @raise [CorruptedTableError] If validation fails
|
|
265
|
-
def validate_header!
|
|
266
|
-
unless [VERSION_2_0, VERSION_3_0].include?(version)
|
|
267
|
-
raise CorruptedTableError,
|
|
268
|
-
"Unsupported CBLC version: 0x#{version.to_s(16).upcase} " \
|
|
269
|
-
"(only versions 2.0 and 3.0 supported)"
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
if num_sizes.negative?
|
|
273
|
-
raise CorruptedTableError,
|
|
274
|
-
"Invalid numSizes: #{num_sizes}"
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
# Parse bitmap size records
|
|
156
|
+
# Read the IndexSubTableArray entries for one strike.
|
|
279
157
|
#
|
|
280
|
-
# @param
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
158
|
+
# @param strike [CblcBitmapSize]
|
|
159
|
+
# @param array_offset [Integer] absolute offset within CBLC bytes
|
|
160
|
+
# @return [Array<CblcIndexSubTableArrayEntry>]
|
|
161
|
+
def read_index_subtable_array(strike, array_offset)
|
|
162
|
+
count = strike.number_of_index_subtables
|
|
163
|
+
bytes = raw_data[array_offset, count * 8]
|
|
164
|
+
return [] unless bytes
|
|
165
|
+
|
|
166
|
+
Array.new(count) do |i|
|
|
167
|
+
CblcIndexSubTableArrayEntry.read(bytes[i * 8, 8])
|
|
289
168
|
end
|
|
290
169
|
end
|
|
291
170
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Tables
|
|
5
|
+
# 8-byte bigGlyphMetrics record embedded in CBLC IndexSubTable formats
|
|
6
|
+
# 2 and 5 (constant-metrics strikes). Layout is smallGlyphMetrics (5
|
|
7
|
+
# bytes) plus four additional signed advance fields.
|
|
8
|
+
#
|
|
9
|
+
# uint8 height
|
|
10
|
+
# uint8 width
|
|
11
|
+
# int8 bearing_x
|
|
12
|
+
# int8 bearing_y
|
|
13
|
+
# int8 advance
|
|
14
|
+
# int8 reserved
|
|
15
|
+
# int8 advance_lsb
|
|
16
|
+
# int8 advance_rsb
|
|
17
|
+
class CblcBigGlyphMetrics < Binary::BaseRecord
|
|
18
|
+
uint8 :height
|
|
19
|
+
uint8 :width
|
|
20
|
+
int8 :bearing_x
|
|
21
|
+
int8 :bearing_y
|
|
22
|
+
int8 :advance
|
|
23
|
+
int8 :reserved
|
|
24
|
+
int8 :advance_lsb
|
|
25
|
+
int8 :advance_rsb
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Tables
|
|
7
|
+
# BitmapSize record embedded in a CBLC table (48 bytes).
|
|
8
|
+
#
|
|
9
|
+
# Each BitmapSize describes one bitmap strike: a glyph range rendered
|
|
10
|
+
# at a specific ppem with a specific bit depth. It points (via
|
|
11
|
+
# `index_subtable_array_offset`) at a sequence of IndexSubTableArray
|
|
12
|
+
# records that further describe how to find the bitmap data for each
|
|
13
|
+
# glyph in CBDT.
|
|
14
|
+
#
|
|
15
|
+
# Layout (48 bytes):
|
|
16
|
+
# uint32 index_subtable_array_offset
|
|
17
|
+
# uint32 index_tables_size
|
|
18
|
+
# uint32 number_of_index_subtables
|
|
19
|
+
# uint32 color_ref
|
|
20
|
+
# CblcSbitLineMetrics hori (12 bytes)
|
|
21
|
+
# CblcSbitLineMetrics vert (12 bytes)
|
|
22
|
+
# uint16 start_glyph_index
|
|
23
|
+
# uint16 end_glyph_index
|
|
24
|
+
# uint8 ppem_x
|
|
25
|
+
# uint8 ppem_y
|
|
26
|
+
# uint8 bit_depth
|
|
27
|
+
# int8 flags
|
|
28
|
+
#
|
|
29
|
+
# Reference: OpenType CBLC spec, "BitmapSize" record.
|
|
30
|
+
class CblcBitmapSize < Binary::BaseRecord
|
|
31
|
+
uint32 :index_subtable_array_offset
|
|
32
|
+
uint32 :index_tables_size
|
|
33
|
+
uint32 :number_of_index_subtables
|
|
34
|
+
uint32 :color_ref
|
|
35
|
+
CblcSbitLineMetrics :hori, type: Fontisan::Tables::CblcSbitLineMetrics
|
|
36
|
+
CblcSbitLineMetrics :vert, type: Fontisan::Tables::CblcSbitLineMetrics
|
|
37
|
+
uint16 :start_glyph_index
|
|
38
|
+
uint16 :end_glyph_index
|
|
39
|
+
uint8 :ppem_x
|
|
40
|
+
uint8 :ppem_y
|
|
41
|
+
uint8 :bit_depth
|
|
42
|
+
int8 :flags
|
|
43
|
+
|
|
44
|
+
# Pixels per em, assuming square pixels (the common case).
|
|
45
|
+
#
|
|
46
|
+
# @return [Integer] ppem value
|
|
47
|
+
def ppem
|
|
48
|
+
ppem_x
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Glyph ID range covered by this strike.
|
|
52
|
+
#
|
|
53
|
+
# @return [Range<Integer>] inclusive glyph ID range
|
|
54
|
+
def glyph_range
|
|
55
|
+
start_glyph_index..end_glyph_index
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Whether the strike covers a specific glyph.
|
|
59
|
+
#
|
|
60
|
+
# @param glyph_id [Integer] source glyph ID
|
|
61
|
+
# @return [Boolean]
|
|
62
|
+
def includes_glyph?(glyph_id)
|
|
63
|
+
glyph_range.include?(glyph_id)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Tables
|
|
5
|
+
# Immutable value object locating one glyph's bitmap inside a CBDT table.
|
|
6
|
+
#
|
|
7
|
+
# A CblcIndexSubTable produces one of these per glyph in its range.
|
|
8
|
+
# The subsetting pipeline reads them to know which CBDT byte range to
|
|
9
|
+
# retain, then computes fresh offsets for the subset output.
|
|
10
|
+
#
|
|
11
|
+
# @!attribute glyph_id
|
|
12
|
+
# @return [Integer] source glyph ID
|
|
13
|
+
# @!attribute image_format
|
|
14
|
+
# @return [Integer] CBDT image format (e.g. 17, 18, 19)
|
|
15
|
+
# @!attribute cbdt_offset
|
|
16
|
+
# @return [Integer] absolute byte offset within the source CBDT
|
|
17
|
+
# @!attribute byte_length
|
|
18
|
+
# @return [Integer] length of the bitmap block in CBDT
|
|
19
|
+
CblcGlyphBitmapLocation = Struct.new(:glyph_id, :image_format,
|
|
20
|
+
:cbdt_offset, :byte_length,
|
|
21
|
+
keyword_init: true) do
|
|
22
|
+
def initialize(*)
|
|
23
|
+
super
|
|
24
|
+
freeze
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Tables
|
|
5
|
+
# Parsed IndexSubTable from a CBLC table.
|
|
6
|
+
#
|
|
7
|
+
# The IndexSubTable is the format-specific directory describing how to
|
|
8
|
+
# locate each glyph's bitmap inside CBDT. Five formats are defined by
|
|
9
|
+
# the OpenType spec:
|
|
10
|
+
#
|
|
11
|
+
# 1 — variable metrics, variable size (uint32 offset array)
|
|
12
|
+
# 2 — constant metrics, constant size (single imageSize for the range)
|
|
13
|
+
# 3 — variable metrics, variable size, 4-byte-aligned (uint16 offsets × 4)
|
|
14
|
+
# 4 — variable metrics, variable size, bit-packed offsets (rare)
|
|
15
|
+
# 5 — constant metrics, constant size, with explicit uint32 offsetArray
|
|
16
|
+
#
|
|
17
|
+
# Each parsed IndexSubTable exposes one [CblcGlyphBitmapLocation] per
|
|
18
|
+
# glyph in its range, plus the raw bytes so a subsetter can preserve
|
|
19
|
+
# unchanged subtables verbatim if needed.
|
|
20
|
+
#
|
|
21
|
+
# Format-specific parsing is delegated to
|
|
22
|
+
# [CblcIndexSubTableFormatParser]; this class only owns the data model.
|
|
23
|
+
#
|
|
24
|
+
# Reference: OpenType CBLC spec, IndexSubTable formats 1–5.
|
|
25
|
+
class CblcIndexSubTable
|
|
26
|
+
# @return [Integer] indexFormat field (1, 2, 3, or 5)
|
|
27
|
+
attr_reader :index_format
|
|
28
|
+
|
|
29
|
+
# @return [Integer] imageFormat field (e.g. 17 = small metrics + PNG)
|
|
30
|
+
attr_reader :image_format
|
|
31
|
+
|
|
32
|
+
# @return [Integer] absolute imageDataOffset from start of CBDT
|
|
33
|
+
attr_reader :image_data_offset
|
|
34
|
+
|
|
35
|
+
# @return [Integer] first glyph ID covered by this subtable
|
|
36
|
+
attr_reader :first_glyph_index
|
|
37
|
+
|
|
38
|
+
# @return [Integer] last glyph ID covered by this subtable
|
|
39
|
+
attr_reader :last_glyph_index
|
|
40
|
+
|
|
41
|
+
# @return [Array<CblcGlyphBitmapLocation>] one per glyph in range
|
|
42
|
+
attr_reader :locations
|
|
43
|
+
|
|
44
|
+
# @return [String] raw bytes of this IndexSubTable in the source CBLC
|
|
45
|
+
attr_reader :raw_bytes
|
|
46
|
+
|
|
47
|
+
def initialize(index_format:, image_format:, image_data_offset:,
|
|
48
|
+
first_glyph_index:, last_glyph_index:, locations:,
|
|
49
|
+
raw_bytes:)
|
|
50
|
+
@index_format = index_format
|
|
51
|
+
@image_format = image_format
|
|
52
|
+
@image_data_offset = image_data_offset
|
|
53
|
+
@first_glyph_index = first_glyph_index
|
|
54
|
+
@last_glyph_index = last_glyph_index
|
|
55
|
+
@locations = locations
|
|
56
|
+
@raw_bytes = raw_bytes
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Parse an IndexSubTable from a CBLC binary blob.
|
|
60
|
+
#
|
|
61
|
+
# @param cblc_bytes [String] the full CBLC table bytes
|
|
62
|
+
# @param index_subtable_offset [Integer] absolute byte offset within
|
|
63
|
+
# `cblc_bytes` where this IndexSubTable begins
|
|
64
|
+
# @param first_glyph_index [Integer] first glyph ID covered
|
|
65
|
+
# @param last_glyph_index [Integer] last glyph ID covered
|
|
66
|
+
# @return [CblcIndexSubTable]
|
|
67
|
+
def self.parse(cblc_bytes, index_subtable_offset:, first_glyph_index:,
|
|
68
|
+
last_glyph_index:)
|
|
69
|
+
header = CblcIndexSubTableHeader.read(
|
|
70
|
+
cblc_bytes[index_subtable_offset, 8],
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
new(
|
|
74
|
+
index_format: header.index_format,
|
|
75
|
+
image_format: header.image_format,
|
|
76
|
+
image_data_offset: header.image_data_offset,
|
|
77
|
+
first_glyph_index: first_glyph_index,
|
|
78
|
+
last_glyph_index: last_glyph_index,
|
|
79
|
+
locations: CblcIndexSubTableFormatParser.locations(
|
|
80
|
+
header: header,
|
|
81
|
+
bytes: cblc_bytes,
|
|
82
|
+
base: index_subtable_offset,
|
|
83
|
+
first: first_glyph_index,
|
|
84
|
+
last: last_glyph_index,
|
|
85
|
+
),
|
|
86
|
+
raw_bytes: CblcIndexSubTableFormatParser.raw_bytes(
|
|
87
|
+
header: header,
|
|
88
|
+
bytes: cblc_bytes,
|
|
89
|
+
base: index_subtable_offset,
|
|
90
|
+
first: first_glyph_index,
|
|
91
|
+
last: last_glyph_index,
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Number of glyphs covered by this subtable.
|
|
97
|
+
#
|
|
98
|
+
# @return [Integer]
|
|
99
|
+
def glyph_count
|
|
100
|
+
last_glyph_index - first_glyph_index + 1
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Tables
|
|
5
|
+
# Entry in a CBLC IndexSubTableArray (8 bytes).
|
|
6
|
+
#
|
|
7
|
+
# The IndexSubTableArray sits at `BitmapSize#index_subtable_array_offset`
|
|
8
|
+
# (relative to the start of CBLC) and contains `number_of_index_subtables`
|
|
9
|
+
# of these entries. Each entry points (via `additional_offset_to_index_subtable`,
|
|
10
|
+
# relative to the IndexSubTableArray's own location) at an IndexSubTable
|
|
11
|
+
# record describing how to find the bitmaps for a glyph range.
|
|
12
|
+
#
|
|
13
|
+
# Reference: OpenType CBLC spec, IndexSubTableArray record.
|
|
14
|
+
class CblcIndexSubTableArrayEntry < Binary::BaseRecord
|
|
15
|
+
uint16 :first_glyph_index
|
|
16
|
+
uint16 :last_glyph_index
|
|
17
|
+
uint32 :additional_offset_to_index_subtable
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|