fontisan 0.4.22 → 0.4.23
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 +5 -0
- data/lib/fontisan/config/subset_profiles.yml +2 -0
- data/lib/fontisan/stitcher/cbdt_propagator.rb +44 -22
- data/lib/fontisan/stitcher/glyph_cloner.rb +41 -0
- data/lib/fontisan/stitcher/glyph_copier.rb +3 -33
- data/lib/fontisan/stitcher/unique_glyph_name.rb +46 -0
- data/lib/fontisan/stitcher.rb +2 -0
- 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 +145 -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/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.rb +11 -0
- data/lib/fontisan/ufo/glyph_exists_error.rb +23 -0
- data/lib/fontisan/ufo/layer.rb +42 -2
- data/lib/fontisan/ufo.rb +2 -1
- data/lib/fontisan/version.rb +1 -1
- metadata +35 -2
data/lib/fontisan/subset.rb
CHANGED
|
@@ -8,6 +8,9 @@ module Fontisan
|
|
|
8
8
|
autoload :GlyphMapping, "fontisan/subset/glyph_mapping"
|
|
9
9
|
autoload :Options, "fontisan/subset/options"
|
|
10
10
|
autoload :Profile, "fontisan/subset/profile"
|
|
11
|
+
autoload :SharedState, "fontisan/subset/shared_state"
|
|
12
|
+
autoload :SubsetContext, "fontisan/subset/subset_context"
|
|
13
|
+
autoload :TableStrategy, "fontisan/subset/table_strategy"
|
|
11
14
|
autoload :TableSubsetter, "fontisan/subset/table_subsetter"
|
|
12
15
|
end
|
|
13
16
|
end
|
data/lib/fontisan/tables/cbdt.rb
CHANGED
|
@@ -1,167 +1,112 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "stringio"
|
|
4
|
-
|
|
5
3
|
module Fontisan
|
|
6
4
|
module Tables
|
|
7
|
-
# CBDT (Color Bitmap Data) table
|
|
8
|
-
#
|
|
9
|
-
# The CBDT table contains the actual bitmap data for color glyphs. It works
|
|
10
|
-
# together with the CBLC table which provides the location information for
|
|
11
|
-
# finding bitmaps in this table.
|
|
12
|
-
#
|
|
13
|
-
# CBDT Table Structure:
|
|
14
|
-
# ```
|
|
15
|
-
# CBDT Table = Header (8 bytes)
|
|
16
|
-
# + Bitmap Data (variable length)
|
|
17
|
-
# ```
|
|
18
|
-
#
|
|
19
|
-
# Header (8 bytes):
|
|
20
|
-
# - majorVersion (uint16): Major version (2 or 3)
|
|
21
|
-
# - minorVersion (uint16): Minor version (0)
|
|
22
|
-
# - reserved (uint32): Reserved, set to 0
|
|
5
|
+
# CBDT (Color Bitmap Data) table.
|
|
23
6
|
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# - Format 19: Metrics in CBLC, PNG data
|
|
7
|
+
# CBDT stores the raw bitmap data blocks for color glyphs. CBLC indexes
|
|
8
|
+
# into CBDT via `imageDataOffset` and per-glyph offset arrays. CBDT
|
|
9
|
+
# itself is essentially a blob: the 4-byte header (majorVersion +
|
|
10
|
+
# minorVersion) followed by an opaque sequence of bitmap blocks.
|
|
29
11
|
#
|
|
30
|
-
# This
|
|
31
|
-
#
|
|
12
|
+
# This model exposes the header fields via BinData and preserves the
|
|
13
|
+
# original bytes for subsetting and direct slicing. A Cbdt constructed
|
|
14
|
+
# via `Cbdt.new` (no `read`) has no captured bytes — `raw_data` is nil
|
|
15
|
+
# and `data_size` returns 0.
|
|
32
16
|
#
|
|
33
|
-
# Reference: OpenType CBDT specification
|
|
34
|
-
# https://
|
|
17
|
+
# Reference: OpenType CBDT specification.
|
|
18
|
+
# https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt
|
|
35
19
|
#
|
|
36
|
-
# @example Reading a
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
# bitmap_data = cbdt.bitmap_data_at(offset, length)
|
|
20
|
+
# @example Reading CBDT and slicing a bitmap block
|
|
21
|
+
# cbdt = Fontisan::Tables::Cbdt.read(font.table_data["CBDT"])
|
|
22
|
+
# bytes = cbdt.bitmap_data_at(120, 4096)
|
|
40
23
|
class Cbdt < Binary::BaseRecord
|
|
41
|
-
# OpenType table tag for CBDT
|
|
42
24
|
TAG = "CBDT"
|
|
43
25
|
|
|
44
|
-
#
|
|
45
|
-
VERSION_2_0 =
|
|
46
|
-
|
|
26
|
+
# CBDT v2.0 — original release
|
|
27
|
+
VERSION_2_0 = 0x00020000
|
|
28
|
+
# CBDT v3.0 — adds PNG image format support
|
|
29
|
+
VERSION_3_0 = 0x00030000
|
|
47
30
|
|
|
48
|
-
|
|
49
|
-
|
|
31
|
+
uint16 :major_version
|
|
32
|
+
uint16 :minor_version
|
|
50
33
|
|
|
51
|
-
#
|
|
52
|
-
attr_reader :minor_version
|
|
53
|
-
|
|
54
|
-
# @return [String] Raw binary data for the entire CBDT table
|
|
55
|
-
attr_reader :raw_data
|
|
56
|
-
|
|
57
|
-
# Override read to parse CBDT structure
|
|
34
|
+
# Whether this instance was populated from real table bytes.
|
|
58
35
|
#
|
|
59
|
-
# @
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
cbdt = new
|
|
63
|
-
return cbdt if io.nil?
|
|
64
|
-
|
|
65
|
-
data = io.is_a?(String) ? io : io.read
|
|
66
|
-
cbdt.parse!(data)
|
|
67
|
-
cbdt
|
|
36
|
+
# @return [Boolean]
|
|
37
|
+
def has_raw_data?
|
|
38
|
+
defined?(@raw_data) && !@raw_data.nil? ? true : false
|
|
68
39
|
end
|
|
69
40
|
|
|
70
|
-
#
|
|
41
|
+
# Raw bytes captured at read time, or nil for a fresh instance.
|
|
71
42
|
#
|
|
72
|
-
# @
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
@raw_data = data
|
|
76
|
-
io = StringIO.new(data)
|
|
77
|
-
|
|
78
|
-
# Parse CBDT header (8 bytes)
|
|
79
|
-
parse_header(io)
|
|
80
|
-
validate_header!
|
|
81
|
-
rescue StandardError => e
|
|
82
|
-
raise CorruptedTableError, "Failed to parse CBDT table: #{e.message}"
|
|
83
|
-
end
|
|
43
|
+
# @return [String, nil]
|
|
44
|
+
def raw_data
|
|
45
|
+
return @raw_data if defined?(@raw_data)
|
|
84
46
|
|
|
85
|
-
|
|
86
|
-
#
|
|
87
|
-
# Used together with CBLC index to extract bitmap data.
|
|
88
|
-
#
|
|
89
|
-
# @param offset [Integer] Offset from start of table
|
|
90
|
-
# @param length [Integer] Length of bitmap data
|
|
91
|
-
# @return [String, nil] Binary bitmap data or nil
|
|
92
|
-
def bitmap_data_at(offset, length)
|
|
93
|
-
return nil if offset.nil? || length.nil?
|
|
94
|
-
return nil if offset.negative? || length.negative?
|
|
95
|
-
return nil if offset + length > raw_data.length
|
|
96
|
-
|
|
97
|
-
raw_data[offset, length]
|
|
47
|
+
nil
|
|
98
48
|
end
|
|
99
49
|
|
|
100
|
-
#
|
|
50
|
+
# Combined version number (e.g. 0x00030000 for v3.0).
|
|
101
51
|
#
|
|
102
|
-
# @return [Integer]
|
|
52
|
+
# @return [Integer]
|
|
103
53
|
def version
|
|
104
|
-
return nil if major_version.nil? || minor_version.nil?
|
|
105
|
-
|
|
106
54
|
(major_version << 16) | minor_version
|
|
107
55
|
end
|
|
108
56
|
|
|
109
|
-
#
|
|
57
|
+
# Total byte size of the captured CBDT bytes, or 0 if none.
|
|
110
58
|
#
|
|
111
|
-
# @return [Integer]
|
|
59
|
+
# @return [Integer]
|
|
112
60
|
def data_size
|
|
113
|
-
raw_data&.
|
|
61
|
+
raw_data&.bytesize || 0
|
|
114
62
|
end
|
|
115
63
|
|
|
116
|
-
#
|
|
64
|
+
# Whether `offset` falls within the captured CBDT byte range.
|
|
117
65
|
#
|
|
118
|
-
# @param offset [Integer]
|
|
119
|
-
# @return [Boolean]
|
|
66
|
+
# @param offset [Integer, nil]
|
|
67
|
+
# @return [Boolean]
|
|
120
68
|
def valid_offset?(offset)
|
|
121
69
|
return false if offset.nil? || offset.negative?
|
|
122
|
-
return false if raw_data.nil?
|
|
123
70
|
|
|
124
|
-
offset <
|
|
71
|
+
offset < data_size
|
|
125
72
|
end
|
|
126
73
|
|
|
127
|
-
#
|
|
74
|
+
# Slice `length` bytes starting at `offset` from the CBDT blob.
|
|
75
|
+
# Returns nil for out-of-range offsets/lengths so callers can use a
|
|
76
|
+
# nil check instead of rescuing IndexError.
|
|
128
77
|
#
|
|
129
|
-
# @
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return
|
|
134
|
-
return
|
|
78
|
+
# @param offset [Integer, nil] start byte
|
|
79
|
+
# @param length [Integer, nil] number of bytes
|
|
80
|
+
# @return [String, nil]
|
|
81
|
+
def bitmap_data_at(offset, length)
|
|
82
|
+
return nil if offset.nil? || length.nil?
|
|
83
|
+
return nil if offset.negative? || length.negative?
|
|
84
|
+
return nil if offset + length > data_size
|
|
135
85
|
|
|
136
|
-
|
|
86
|
+
raw_data[offset, length]
|
|
137
87
|
end
|
|
138
88
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
#
|
|
89
|
+
# Serialize the captured bytes back to binary. Falls back to BinData's
|
|
90
|
+
# default serialization (4-byte header) if no bytes were captured,
|
|
91
|
+
# so consumers can construct a CBDT from scratch.
|
|
142
92
|
#
|
|
143
|
-
# @
|
|
144
|
-
def
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
93
|
+
# @return [String]
|
|
94
|
+
def to_binary_s
|
|
95
|
+
return raw_data if has_raw_data?
|
|
96
|
+
|
|
97
|
+
super
|
|
148
98
|
end
|
|
149
99
|
|
|
150
|
-
#
|
|
100
|
+
# Whether the header declares a CBDT version fontisan understands
|
|
101
|
+
# (v2.0 or v3.0) and any raw bytes were captured.
|
|
151
102
|
#
|
|
152
|
-
# @
|
|
153
|
-
def
|
|
154
|
-
unless [2, 3].include?(major_version)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
unless minor_version.zero?
|
|
161
|
-
raise CorruptedTableError,
|
|
162
|
-
"Unsupported CBDT minor version: #{minor_version} " \
|
|
163
|
-
"(only version 0 supported)"
|
|
164
|
-
end
|
|
103
|
+
# @return [Boolean]
|
|
104
|
+
def valid?
|
|
105
|
+
return false unless [2, 3].include?(major_version)
|
|
106
|
+
return false unless minor_version.zero?
|
|
107
|
+
return false unless has_raw_data?
|
|
108
|
+
|
|
109
|
+
true
|
|
165
110
|
end
|
|
166
111
|
end
|
|
167
112
|
end
|
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
|