fontisan 0.2.3 → 0.2.4
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 +92 -40
- data/README.adoc +262 -3
- data/Rakefile +20 -7
- data/lib/fontisan/commands/base_command.rb +2 -19
- data/lib/fontisan/commands/convert_command.rb +16 -13
- data/lib/fontisan/commands/info_command.rb +88 -0
- data/lib/fontisan/config/conversion_matrix.yml +58 -20
- data/lib/fontisan/converters/outline_converter.rb +6 -3
- data/lib/fontisan/converters/svg_generator.rb +45 -0
- data/lib/fontisan/converters/woff2_encoder.rb +106 -13
- data/lib/fontisan/models/bitmap_glyph.rb +123 -0
- data/lib/fontisan/models/bitmap_strike.rb +94 -0
- data/lib/fontisan/models/color_glyph.rb +57 -0
- data/lib/fontisan/models/color_layer.rb +53 -0
- data/lib/fontisan/models/color_palette.rb +60 -0
- data/lib/fontisan/models/font_info.rb +26 -0
- data/lib/fontisan/models/svg_glyph.rb +89 -0
- data/lib/fontisan/open_type_font.rb +6 -0
- data/lib/fontisan/optimizers/charstring_rewriter.rb +19 -8
- data/lib/fontisan/optimizers/pattern_analyzer.rb +4 -2
- data/lib/fontisan/optimizers/subroutine_builder.rb +6 -5
- data/lib/fontisan/optimizers/subroutine_optimizer.rb +5 -2
- data/lib/fontisan/pipeline/output_writer.rb +2 -2
- data/lib/fontisan/tables/cbdt.rb +169 -0
- data/lib/fontisan/tables/cblc.rb +290 -0
- data/lib/fontisan/tables/cff.rb +6 -12
- data/lib/fontisan/tables/colr.rb +291 -0
- data/lib/fontisan/tables/cpal.rb +281 -0
- data/lib/fontisan/tables/glyf/glyph_builder.rb +5 -1
- data/lib/fontisan/tables/sbix.rb +379 -0
- data/lib/fontisan/tables/svg.rb +301 -0
- data/lib/fontisan/true_type_font.rb +6 -0
- data/lib/fontisan/validation/woff2_header_validator.rb +278 -0
- data/lib/fontisan/validation/woff2_table_validator.rb +270 -0
- data/lib/fontisan/validation/woff2_validator.rb +248 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2/directory.rb +40 -11
- data/lib/fontisan/woff2/table_transformer.rb +506 -73
- data/lib/fontisan/woff2_font.rb +29 -9
- data/lib/fontisan/woff_font.rb +17 -4
- data/lib/fontisan.rb +12 -0
- metadata +17 -2
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
require_relative "../binary/base_record"
|
|
5
|
+
|
|
6
|
+
module Fontisan
|
|
7
|
+
module Tables
|
|
8
|
+
# CBDT (Color Bitmap Data) table parser
|
|
9
|
+
#
|
|
10
|
+
# The CBDT table contains the actual bitmap data for color glyphs. It works
|
|
11
|
+
# together with the CBLC table which provides the location information for
|
|
12
|
+
# finding bitmaps in this table.
|
|
13
|
+
#
|
|
14
|
+
# CBDT Table Structure:
|
|
15
|
+
# ```
|
|
16
|
+
# CBDT Table = Header (8 bytes)
|
|
17
|
+
# + Bitmap Data (variable length)
|
|
18
|
+
# ```
|
|
19
|
+
#
|
|
20
|
+
# Header (8 bytes):
|
|
21
|
+
# - majorVersion (uint16): Major version (2 or 3)
|
|
22
|
+
# - minorVersion (uint16): Minor version (0)
|
|
23
|
+
# - reserved (uint32): Reserved, set to 0
|
|
24
|
+
#
|
|
25
|
+
# The bitmap data format depends on the index subtable format in CBLC.
|
|
26
|
+
# Common formats include:
|
|
27
|
+
# - Format 17: Small metrics, PNG data
|
|
28
|
+
# - Format 18: Big metrics, PNG data
|
|
29
|
+
# - Format 19: Metrics in CBLC, PNG data
|
|
30
|
+
#
|
|
31
|
+
# This parser provides low-level access to bitmap data. For proper bitmap
|
|
32
|
+
# extraction, use together with CBLC table which contains the index.
|
|
33
|
+
#
|
|
34
|
+
# Reference: OpenType CBDT specification
|
|
35
|
+
# https://docs.microsoft.com/en-us/typography/opentype/spec/cbdt
|
|
36
|
+
#
|
|
37
|
+
# @example Reading a CBDT table
|
|
38
|
+
# data = font.table_data['CBDT']
|
|
39
|
+
# cbdt = Fontisan::Tables::Cbdt.read(data)
|
|
40
|
+
# bitmap_data = cbdt.bitmap_data_at(offset, length)
|
|
41
|
+
class Cbdt < Binary::BaseRecord
|
|
42
|
+
# OpenType table tag for CBDT
|
|
43
|
+
TAG = "CBDT"
|
|
44
|
+
|
|
45
|
+
# Supported CBDT versions
|
|
46
|
+
VERSION_2_0 = 0x0002_0000
|
|
47
|
+
VERSION_3_0 = 0x0003_0000
|
|
48
|
+
|
|
49
|
+
# @return [Integer] Major version (2 or 3)
|
|
50
|
+
attr_reader :major_version
|
|
51
|
+
|
|
52
|
+
# @return [Integer] Minor version (0)
|
|
53
|
+
attr_reader :minor_version
|
|
54
|
+
|
|
55
|
+
# @return [String] Raw binary data for the entire CBDT table
|
|
56
|
+
attr_reader :raw_data
|
|
57
|
+
|
|
58
|
+
# Override read to parse CBDT structure
|
|
59
|
+
#
|
|
60
|
+
# @param io [IO, String] Binary data to read
|
|
61
|
+
# @return [Cbdt] Parsed CBDT table
|
|
62
|
+
def self.read(io)
|
|
63
|
+
cbdt = new
|
|
64
|
+
return cbdt if io.nil?
|
|
65
|
+
|
|
66
|
+
data = io.is_a?(String) ? io : io.read
|
|
67
|
+
cbdt.parse!(data)
|
|
68
|
+
cbdt
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Parse the CBDT table structure
|
|
72
|
+
#
|
|
73
|
+
# @param data [String] Binary data for the CBDT table
|
|
74
|
+
# @raise [CorruptedTableError] If CBDT structure is invalid
|
|
75
|
+
def parse!(data)
|
|
76
|
+
@raw_data = data
|
|
77
|
+
io = StringIO.new(data)
|
|
78
|
+
|
|
79
|
+
# Parse CBDT header (8 bytes)
|
|
80
|
+
parse_header(io)
|
|
81
|
+
validate_header!
|
|
82
|
+
rescue StandardError => e
|
|
83
|
+
raise CorruptedTableError, "Failed to parse CBDT table: #{e.message}"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Get bitmap data at specific offset and length
|
|
87
|
+
#
|
|
88
|
+
# Used together with CBLC index to extract bitmap data.
|
|
89
|
+
#
|
|
90
|
+
# @param offset [Integer] Offset from start of table
|
|
91
|
+
# @param length [Integer] Length of bitmap data
|
|
92
|
+
# @return [String, nil] Binary bitmap data or nil
|
|
93
|
+
def bitmap_data_at(offset, length)
|
|
94
|
+
return nil if offset.nil? || length.nil?
|
|
95
|
+
return nil if offset.negative? || length.negative?
|
|
96
|
+
return nil if offset + length > raw_data.length
|
|
97
|
+
|
|
98
|
+
raw_data[offset, length]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Get combined version number
|
|
102
|
+
#
|
|
103
|
+
# @return [Integer] Combined version (e.g., 0x00020000 for v2.0)
|
|
104
|
+
def version
|
|
105
|
+
return nil if major_version.nil? || minor_version.nil?
|
|
106
|
+
|
|
107
|
+
(major_version << 16) | minor_version
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Get table data size
|
|
111
|
+
#
|
|
112
|
+
# @return [Integer] Size of CBDT table in bytes
|
|
113
|
+
def data_size
|
|
114
|
+
raw_data&.length || 0
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Check if offset is valid for this table
|
|
118
|
+
#
|
|
119
|
+
# @param offset [Integer] Offset to check
|
|
120
|
+
# @return [Boolean] True if offset is within table bounds
|
|
121
|
+
def valid_offset?(offset)
|
|
122
|
+
return false if offset.nil? || offset.negative?
|
|
123
|
+
return false if raw_data.nil?
|
|
124
|
+
|
|
125
|
+
offset < raw_data.length
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Validate the CBDT table structure
|
|
129
|
+
#
|
|
130
|
+
# @return [Boolean] True if valid
|
|
131
|
+
def valid?
|
|
132
|
+
return false if major_version.nil? || minor_version.nil?
|
|
133
|
+
return false unless [2, 3].include?(major_version)
|
|
134
|
+
return false unless minor_version.zero?
|
|
135
|
+
return false unless raw_data
|
|
136
|
+
|
|
137
|
+
true
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
# Parse CBDT header (8 bytes)
|
|
143
|
+
#
|
|
144
|
+
# @param io [StringIO] Input stream
|
|
145
|
+
def parse_header(io)
|
|
146
|
+
@major_version = io.read(2).unpack1("n")
|
|
147
|
+
@minor_version = io.read(2).unpack1("n")
|
|
148
|
+
@reserved = io.read(4).unpack1("N")
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Validate header values
|
|
152
|
+
#
|
|
153
|
+
# @raise [CorruptedTableError] If validation fails
|
|
154
|
+
def validate_header!
|
|
155
|
+
unless [2, 3].include?(major_version)
|
|
156
|
+
raise CorruptedTableError,
|
|
157
|
+
"Unsupported CBDT major version: #{major_version} " \
|
|
158
|
+
"(only versions 2 and 3 supported)"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
unless minor_version.zero?
|
|
162
|
+
raise CorruptedTableError,
|
|
163
|
+
"Unsupported CBDT minor version: #{minor_version} " \
|
|
164
|
+
"(only version 0 supported)"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
require_relative "../binary/base_record"
|
|
5
|
+
|
|
6
|
+
module Fontisan
|
|
7
|
+
module Tables
|
|
8
|
+
# CBLC (Color Bitmap Location) table parser
|
|
9
|
+
#
|
|
10
|
+
# The CBLC table contains location information for bitmap glyphs at various
|
|
11
|
+
# sizes (strikes). It works together with the CBDT table which contains the
|
|
12
|
+
# actual bitmap data.
|
|
13
|
+
#
|
|
14
|
+
# CBLC Table Structure:
|
|
15
|
+
# ```
|
|
16
|
+
# CBLC Table = Header (8 bytes)
|
|
17
|
+
# + BitmapSize Records (48 bytes each)
|
|
18
|
+
# ```
|
|
19
|
+
#
|
|
20
|
+
# Header (8 bytes):
|
|
21
|
+
# - version (uint32): Table version (0x00020000 or 0x00030000)
|
|
22
|
+
# - numSizes (uint32): Number of BitmapSize records
|
|
23
|
+
#
|
|
24
|
+
# Each BitmapSize record (48 bytes) contains:
|
|
25
|
+
# - indexSubTableArrayOffset (uint32): Offset to index subtable array
|
|
26
|
+
# - indexTablesSize (uint32): Size of index subtables
|
|
27
|
+
# - numberOfIndexSubTables (uint32): Number of index subtables
|
|
28
|
+
# - colorRef (uint32): Not used, set to 0
|
|
29
|
+
# - hori (SbitLineMetrics, 12 bytes): Horizontal line metrics
|
|
30
|
+
# - vert (SbitLineMetrics, 12 bytes): Vertical line metrics
|
|
31
|
+
# - startGlyphIndex (uint16): First glyph ID in strike
|
|
32
|
+
# - endGlyphIndex (uint16): Last glyph ID in strike
|
|
33
|
+
# - ppemX (uint8): Horizontal pixels per em
|
|
34
|
+
# - ppemY (uint8): Vertical pixels per em
|
|
35
|
+
# - bitDepth (uint8): Bit depth (1, 2, 4, 8, 32)
|
|
36
|
+
# - flags (int8): Flags
|
|
37
|
+
#
|
|
38
|
+
# Reference: OpenType CBLC specification
|
|
39
|
+
# https://docs.microsoft.com/en-us/typography/opentype/spec/cblc
|
|
40
|
+
#
|
|
41
|
+
# @example Reading a CBLC table
|
|
42
|
+
# data = font.table_data['CBLC']
|
|
43
|
+
# cblc = Fontisan::Tables::Cblc.read(data)
|
|
44
|
+
# strikes = cblc.strikes
|
|
45
|
+
# puts "Font has #{strikes.length} bitmap strikes"
|
|
46
|
+
class Cblc < Binary::BaseRecord
|
|
47
|
+
# OpenType table tag for CBLC
|
|
48
|
+
TAG = "CBLC"
|
|
49
|
+
|
|
50
|
+
# Supported CBLC versions
|
|
51
|
+
VERSION_2_0 = 0x00020000
|
|
52
|
+
VERSION_3_0 = 0x00030000
|
|
53
|
+
|
|
54
|
+
# SbitLineMetrics structure (12 bytes)
|
|
55
|
+
#
|
|
56
|
+
# Contains metrics for horizontal or vertical layout
|
|
57
|
+
class SbitLineMetrics < Binary::BaseRecord
|
|
58
|
+
endian :big
|
|
59
|
+
int8 :ascender
|
|
60
|
+
int8 :descender
|
|
61
|
+
uint8 :width_max
|
|
62
|
+
int8 :caret_slope_numerator
|
|
63
|
+
int8 :caret_slope_denominator
|
|
64
|
+
int8 :caret_offset
|
|
65
|
+
int8 :min_origin_sb
|
|
66
|
+
int8 :min_advance_sb
|
|
67
|
+
int8 :max_before_bl
|
|
68
|
+
int8 :min_after_bl
|
|
69
|
+
int8 :pad1
|
|
70
|
+
int8 :pad2
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# BitmapSize record structure (48 bytes)
|
|
74
|
+
#
|
|
75
|
+
# Describes a bitmap strike at a specific ppem size
|
|
76
|
+
class BitmapSize < Binary::BaseRecord
|
|
77
|
+
endian :big
|
|
78
|
+
uint32 :index_subtable_array_offset
|
|
79
|
+
uint32 :index_tables_size
|
|
80
|
+
uint32 :number_of_index_subtables
|
|
81
|
+
uint32 :color_ref
|
|
82
|
+
|
|
83
|
+
# Read the SbitLineMetrics structures manually
|
|
84
|
+
def self.read(io)
|
|
85
|
+
data = io.is_a?(String) ? io : io.read
|
|
86
|
+
size = new
|
|
87
|
+
|
|
88
|
+
io = StringIO.new(data)
|
|
89
|
+
size.instance_variable_set(:@index_subtable_array_offset, io.read(4).unpack1("N"))
|
|
90
|
+
size.instance_variable_set(:@index_tables_size, io.read(4).unpack1("N"))
|
|
91
|
+
size.instance_variable_set(:@number_of_index_subtables, io.read(4).unpack1("N"))
|
|
92
|
+
size.instance_variable_set(:@color_ref, io.read(4).unpack1("N"))
|
|
93
|
+
|
|
94
|
+
# Parse hori and vert metrics (12 bytes each)
|
|
95
|
+
hori_data = io.read(12)
|
|
96
|
+
vert_data = io.read(12)
|
|
97
|
+
size.instance_variable_set(:@hori, SbitLineMetrics.read(hori_data))
|
|
98
|
+
size.instance_variable_set(:@vert, SbitLineMetrics.read(vert_data))
|
|
99
|
+
|
|
100
|
+
# Parse remaining fields
|
|
101
|
+
size.instance_variable_set(:@start_glyph_index, io.read(2).unpack1("n"))
|
|
102
|
+
size.instance_variable_set(:@end_glyph_index, io.read(2).unpack1("n"))
|
|
103
|
+
size.instance_variable_set(:@ppem_x, io.read(1).unpack1("C"))
|
|
104
|
+
size.instance_variable_set(:@ppem_y, io.read(1).unpack1("C"))
|
|
105
|
+
size.instance_variable_set(:@bit_depth, io.read(1).unpack1("C"))
|
|
106
|
+
size.instance_variable_set(:@flags, io.read(1).unpack1("c"))
|
|
107
|
+
|
|
108
|
+
size
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
attr_reader :index_subtable_array_offset, :index_tables_size,
|
|
112
|
+
:number_of_index_subtables, :color_ref, :hori, :vert,
|
|
113
|
+
:start_glyph_index, :end_glyph_index, :ppem_x, :ppem_y,
|
|
114
|
+
:bit_depth, :flags
|
|
115
|
+
|
|
116
|
+
# Get ppem size (assumes square pixels)
|
|
117
|
+
#
|
|
118
|
+
# @return [Integer] Pixels per em
|
|
119
|
+
def ppem
|
|
120
|
+
ppem_x
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Get glyph range for this strike
|
|
124
|
+
#
|
|
125
|
+
# @return [Range] Range of glyph IDs
|
|
126
|
+
def glyph_range
|
|
127
|
+
start_glyph_index..end_glyph_index
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Check if this strike includes a specific glyph ID
|
|
131
|
+
#
|
|
132
|
+
# @param glyph_id [Integer] Glyph ID to check
|
|
133
|
+
# @return [Boolean] True if glyph is in range
|
|
134
|
+
def includes_glyph?(glyph_id)
|
|
135
|
+
glyph_range.include?(glyph_id)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @return [Integer] CBLC version
|
|
140
|
+
attr_reader :version
|
|
141
|
+
|
|
142
|
+
# @return [Integer] Number of bitmap size records
|
|
143
|
+
attr_reader :num_sizes
|
|
144
|
+
|
|
145
|
+
# @return [Array<BitmapSize>] Parsed bitmap size records
|
|
146
|
+
attr_reader :bitmap_sizes
|
|
147
|
+
|
|
148
|
+
# @return [String] Raw binary data for the entire CBLC table
|
|
149
|
+
attr_reader :raw_data
|
|
150
|
+
|
|
151
|
+
# Override read to parse CBLC structure
|
|
152
|
+
#
|
|
153
|
+
# @param io [IO, String] Binary data to read
|
|
154
|
+
# @return [Cblc] Parsed CBLC table
|
|
155
|
+
def self.read(io)
|
|
156
|
+
cblc = new
|
|
157
|
+
return cblc if io.nil?
|
|
158
|
+
|
|
159
|
+
data = io.is_a?(String) ? io : io.read
|
|
160
|
+
cblc.parse!(data)
|
|
161
|
+
cblc
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Parse the CBLC table structure
|
|
165
|
+
#
|
|
166
|
+
# @param data [String] Binary data for the CBLC table
|
|
167
|
+
# @raise [CorruptedTableError] If CBLC structure is invalid
|
|
168
|
+
def parse!(data)
|
|
169
|
+
@raw_data = data
|
|
170
|
+
io = StringIO.new(data)
|
|
171
|
+
|
|
172
|
+
# Parse CBLC header (8 bytes)
|
|
173
|
+
parse_header(io)
|
|
174
|
+
validate_header!
|
|
175
|
+
|
|
176
|
+
# Parse bitmap size records
|
|
177
|
+
parse_bitmap_sizes(io)
|
|
178
|
+
rescue StandardError => e
|
|
179
|
+
raise CorruptedTableError, "Failed to parse CBLC table: #{e.message}"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Get bitmap strikes (sizes)
|
|
183
|
+
#
|
|
184
|
+
# @return [Array<BitmapSize>] Array of bitmap strikes
|
|
185
|
+
def strikes
|
|
186
|
+
bitmap_sizes || []
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Get strikes for specific ppem size
|
|
190
|
+
#
|
|
191
|
+
# @param ppem [Integer] Pixels per em
|
|
192
|
+
# @return [Array<BitmapSize>] Strikes matching ppem
|
|
193
|
+
def strikes_for_ppem(ppem)
|
|
194
|
+
strikes.select { |size| size.ppem == ppem }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Check if glyph has bitmap at ppem size
|
|
198
|
+
#
|
|
199
|
+
# @param glyph_id [Integer] Glyph ID
|
|
200
|
+
# @param ppem [Integer] Pixels per em
|
|
201
|
+
# @return [Boolean] True if glyph has bitmap
|
|
202
|
+
def has_bitmap_for_glyph?(glyph_id, ppem)
|
|
203
|
+
strikes_for_ppem(ppem).any? do |strike|
|
|
204
|
+
strike.includes_glyph?(glyph_id)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Get all available ppem sizes
|
|
209
|
+
#
|
|
210
|
+
# @return [Array<Integer>] Sorted array of ppem sizes
|
|
211
|
+
def ppem_sizes
|
|
212
|
+
strikes.map(&:ppem).uniq.sort
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Get all glyph IDs that have bitmaps across all strikes
|
|
216
|
+
#
|
|
217
|
+
# @return [Array<Integer>] Array of glyph IDs
|
|
218
|
+
def glyph_ids_with_bitmaps
|
|
219
|
+
strikes.flat_map { |strike| strike.glyph_range.to_a }.uniq.sort
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Get strikes that include a specific glyph ID
|
|
223
|
+
#
|
|
224
|
+
# @param glyph_id [Integer] Glyph ID
|
|
225
|
+
# @return [Array<BitmapSize>] Strikes containing glyph
|
|
226
|
+
def strikes_for_glyph(glyph_id)
|
|
227
|
+
strikes.select { |strike| strike.includes_glyph?(glyph_id) }
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Get the number of bitmap strikes
|
|
231
|
+
#
|
|
232
|
+
# @return [Integer] Number of strikes
|
|
233
|
+
def num_strikes
|
|
234
|
+
num_sizes || 0
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Validate the CBLC table structure
|
|
238
|
+
#
|
|
239
|
+
# @return [Boolean] True if valid
|
|
240
|
+
def valid?
|
|
241
|
+
return false if version.nil?
|
|
242
|
+
return false unless [VERSION_2_0, VERSION_3_0].include?(version)
|
|
243
|
+
return false if num_sizes.nil? || num_sizes.negative?
|
|
244
|
+
return false unless bitmap_sizes
|
|
245
|
+
|
|
246
|
+
true
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
private
|
|
250
|
+
|
|
251
|
+
# Parse CBLC header (8 bytes)
|
|
252
|
+
#
|
|
253
|
+
# @param io [StringIO] Input stream
|
|
254
|
+
def parse_header(io)
|
|
255
|
+
@version = io.read(4).unpack1("N")
|
|
256
|
+
@num_sizes = io.read(4).unpack1("N")
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Validate header values
|
|
260
|
+
#
|
|
261
|
+
# @raise [CorruptedTableError] If validation fails
|
|
262
|
+
def validate_header!
|
|
263
|
+
unless [VERSION_2_0, VERSION_3_0].include?(version)
|
|
264
|
+
raise CorruptedTableError,
|
|
265
|
+
"Unsupported CBLC version: 0x#{version.to_s(16).upcase} " \
|
|
266
|
+
"(only versions 2.0 and 3.0 supported)"
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
if num_sizes.negative?
|
|
270
|
+
raise CorruptedTableError,
|
|
271
|
+
"Invalid numSizes: #{num_sizes}"
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Parse bitmap size records
|
|
276
|
+
#
|
|
277
|
+
# @param io [StringIO] Input stream
|
|
278
|
+
def parse_bitmap_sizes(io)
|
|
279
|
+
@bitmap_sizes = []
|
|
280
|
+
return if num_sizes.zero?
|
|
281
|
+
|
|
282
|
+
# Each BitmapSize record is 48 bytes
|
|
283
|
+
num_sizes.times do
|
|
284
|
+
size_data = io.read(48)
|
|
285
|
+
@bitmap_sizes << BitmapSize.read(size_data)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
data/lib/fontisan/tables/cff.rb
CHANGED
|
@@ -268,8 +268,7 @@ module Fontisan
|
|
|
268
268
|
|
|
269
269
|
PrivateDict.new(private_data)
|
|
270
270
|
rescue StandardError => e
|
|
271
|
-
|
|
272
|
-
nil
|
|
271
|
+
raise CorruptedTableError, "Failed to parse Private DICT: #{e.message}"
|
|
273
272
|
end
|
|
274
273
|
|
|
275
274
|
# Get the Local Subr INDEX for a specific font
|
|
@@ -300,8 +299,7 @@ module Fontisan
|
|
|
300
299
|
io.seek(absolute_offset)
|
|
301
300
|
Index.new(io, start_offset: absolute_offset)
|
|
302
301
|
rescue StandardError => e
|
|
303
|
-
|
|
304
|
-
nil
|
|
302
|
+
raise CorruptedTableError, "Failed to parse Local Subr INDEX: #{e.message}"
|
|
305
303
|
end
|
|
306
304
|
|
|
307
305
|
# Get the CharStrings INDEX for a specific font
|
|
@@ -322,8 +320,7 @@ module Fontisan
|
|
|
322
320
|
io.seek(charstrings_offset)
|
|
323
321
|
CharstringsIndex.new(io, start_offset: charstrings_offset)
|
|
324
322
|
rescue StandardError => e
|
|
325
|
-
|
|
326
|
-
nil
|
|
323
|
+
raise CorruptedTableError, "Failed to parse CharStrings INDEX: #{e.message}"
|
|
327
324
|
end
|
|
328
325
|
|
|
329
326
|
# Get a CharString for a specific glyph
|
|
@@ -358,8 +355,7 @@ module Fontisan
|
|
|
358
355
|
local_subr_index,
|
|
359
356
|
)
|
|
360
357
|
rescue StandardError => e
|
|
361
|
-
|
|
362
|
-
nil
|
|
358
|
+
raise CorruptedTableError, "Failed to get CharString for glyph #{glyph_index}: #{e.message}"
|
|
363
359
|
end
|
|
364
360
|
|
|
365
361
|
# Get the number of glyphs in a font
|
|
@@ -437,8 +433,7 @@ module Fontisan
|
|
|
437
433
|
num_glyphs = glyph_count(index)
|
|
438
434
|
Charset.new(charset_data, num_glyphs, self)
|
|
439
435
|
rescue StandardError => e
|
|
440
|
-
|
|
441
|
-
nil
|
|
436
|
+
raise CorruptedTableError, "Failed to parse Charset: #{e.message}"
|
|
442
437
|
end
|
|
443
438
|
|
|
444
439
|
# Get the Encoding for a specific font
|
|
@@ -467,8 +462,7 @@ module Fontisan
|
|
|
467
462
|
num_glyphs = glyph_count(index)
|
|
468
463
|
Encoding.new(encoding_data, num_glyphs)
|
|
469
464
|
rescue StandardError => e
|
|
470
|
-
|
|
471
|
-
nil
|
|
465
|
+
raise CorruptedTableError, "Failed to parse Encoding: #{e.message}"
|
|
472
466
|
end
|
|
473
467
|
end
|
|
474
468
|
|