fontisan 0.2.3 → 0.2.5
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 +221 -49
- data/README.adoc +519 -5
- data/Rakefile +20 -7
- data/lib/fontisan/cli.rb +67 -6
- 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/commands/validate_command.rb +107 -151
- 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 +84 -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/models/validation_report.rb +227 -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/pipeline/transformation_pipeline.rb +4 -8
- 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/cmap.rb +82 -2
- 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/glyf.rb +118 -0
- data/lib/fontisan/tables/head.rb +60 -0
- data/lib/fontisan/tables/hhea.rb +74 -0
- data/lib/fontisan/tables/maxp.rb +60 -0
- data/lib/fontisan/tables/name.rb +76 -0
- data/lib/fontisan/tables/os2.rb +113 -0
- data/lib/fontisan/tables/post.rb +57 -0
- 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/validators/basic_validator.rb +85 -0
- data/lib/fontisan/validators/font_book_validator.rb +130 -0
- data/lib/fontisan/validators/opentype_validator.rb +112 -0
- data/lib/fontisan/validators/profile_loader.rb +139 -0
- data/lib/fontisan/validators/validator.rb +484 -0
- data/lib/fontisan/validators/web_font_validator.rb +102 -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 +90 -6
- metadata +20 -9
- data/lib/fontisan/config/validation_rules.yml +0 -149
- data/lib/fontisan/validation/checksum_validator.rb +0 -170
- data/lib/fontisan/validation/consistency_validator.rb +0 -197
- data/lib/fontisan/validation/structure_validator.rb +0 -198
- data/lib/fontisan/validation/table_validator.rb +0 -158
- data/lib/fontisan/validation/validator.rb +0 -152
- data/lib/fontisan/validation/variable_font_validator.rb +0 -218
data/lib/fontisan/tables/glyf.rb
CHANGED
|
@@ -158,6 +158,124 @@ module Fontisan
|
|
|
158
158
|
@glyphs_cache ||= {}
|
|
159
159
|
end
|
|
160
160
|
|
|
161
|
+
# Validation helper: Check if all non-special glyphs have contours
|
|
162
|
+
#
|
|
163
|
+
# The .notdef glyph (ID 0) can be empty, but other glyphs should have geometry
|
|
164
|
+
#
|
|
165
|
+
# @param loca [Loca] Loca table for glyph access
|
|
166
|
+
# @param head [Head] Head table for context
|
|
167
|
+
# @param num_glyphs [Integer] Total number of glyphs to check
|
|
168
|
+
# @return [Boolean] True if all non-special glyphs have contours
|
|
169
|
+
def no_empty_glyphs_except_special?(loca, head, num_glyphs)
|
|
170
|
+
# Check glyphs 1 through num_glyphs-1 (.notdef at 0 can be empty)
|
|
171
|
+
(1...num_glyphs).all? do |glyph_id|
|
|
172
|
+
size = loca.size_of(glyph_id)
|
|
173
|
+
# Empty glyphs (like space) are allowed, but check if they should be empty
|
|
174
|
+
# This is a basic check - we just ensure non-control glyphs have data
|
|
175
|
+
size.nil? || size.positive?
|
|
176
|
+
end
|
|
177
|
+
rescue StandardError
|
|
178
|
+
false
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Validation helper: Check if any glyphs are clipped (exceed bounds)
|
|
182
|
+
#
|
|
183
|
+
# Validates that glyph coordinates don't exceed head table's bounding box
|
|
184
|
+
#
|
|
185
|
+
# @param loca [Loca] Loca table for glyph access
|
|
186
|
+
# @param head [Head] Head table for bounds reference
|
|
187
|
+
# @param num_glyphs [Integer] Total number of glyphs to check
|
|
188
|
+
# @return [Boolean] True if no glyphs exceed the font's bounding box
|
|
189
|
+
def no_clipped_glyphs?(loca, head, num_glyphs)
|
|
190
|
+
font_x_min = head.x_min
|
|
191
|
+
font_y_min = head.y_min
|
|
192
|
+
font_x_max = head.x_max
|
|
193
|
+
font_y_max = head.y_max
|
|
194
|
+
|
|
195
|
+
(0...num_glyphs).all? do |glyph_id|
|
|
196
|
+
glyph = glyph_for(glyph_id, loca, head)
|
|
197
|
+
next true if glyph.nil? # Empty glyphs are OK
|
|
198
|
+
|
|
199
|
+
# Check if glyph bounds are within font bounds
|
|
200
|
+
glyph.x_min >= font_x_min &&
|
|
201
|
+
glyph.y_min >= font_y_min &&
|
|
202
|
+
glyph.x_max <= font_x_max &&
|
|
203
|
+
glyph.y_max <= font_y_max
|
|
204
|
+
end
|
|
205
|
+
rescue StandardError
|
|
206
|
+
false
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Validation helper: Check if TrueType instructions are sound
|
|
210
|
+
#
|
|
211
|
+
# Validates that glyph instructions (if present) are parseable
|
|
212
|
+
# This is a basic check that ensures instructions exist and have valid length
|
|
213
|
+
#
|
|
214
|
+
# @param loca [Loca] Loca table for glyph access
|
|
215
|
+
# @param head [Head] Head table for context
|
|
216
|
+
# @param num_glyphs [Integer] Total number of glyphs to check
|
|
217
|
+
# @return [Boolean] True if all instructions are valid or absent
|
|
218
|
+
def instructions_sound?(loca, head, num_glyphs)
|
|
219
|
+
(0...num_glyphs).all? do |glyph_id|
|
|
220
|
+
glyph = glyph_for(glyph_id, loca, head)
|
|
221
|
+
next true if glyph.nil? # Empty glyphs are OK
|
|
222
|
+
|
|
223
|
+
# Simple glyphs have instructions
|
|
224
|
+
if glyph.respond_to?(:instruction_length)
|
|
225
|
+
inst_len = glyph.instruction_length
|
|
226
|
+
# If instructions present, length should be reasonable
|
|
227
|
+
inst_len.nil? || inst_len >= 0
|
|
228
|
+
else
|
|
229
|
+
# Compound glyphs may have instructions too
|
|
230
|
+
true
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
rescue StandardError
|
|
234
|
+
false
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Validation helper: Check if glyph has valid number of contours
|
|
238
|
+
#
|
|
239
|
+
# @param glyph_id [Integer] Glyph ID to check
|
|
240
|
+
# @param loca [Loca] Loca table for glyph access
|
|
241
|
+
# @param head [Head] Head table for context
|
|
242
|
+
# @return [Boolean] True if contour count is valid
|
|
243
|
+
def valid_contour_count?(glyph_id, loca, head)
|
|
244
|
+
glyph = glyph_for(glyph_id, loca, head)
|
|
245
|
+
return true if glyph.nil? # Empty glyphs are OK
|
|
246
|
+
|
|
247
|
+
# Simple glyphs: contours should be >= 0
|
|
248
|
+
# Compound glyphs: numberOfContours = -1
|
|
249
|
+
if glyph.respond_to?(:num_contours)
|
|
250
|
+
glyph.num_contours >= -1
|
|
251
|
+
else
|
|
252
|
+
true
|
|
253
|
+
end
|
|
254
|
+
rescue StandardError
|
|
255
|
+
false
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Validation helper: Check if all glyphs are accessible
|
|
259
|
+
#
|
|
260
|
+
# Attempts to access each glyph to ensure no corruption
|
|
261
|
+
#
|
|
262
|
+
# @param loca [Loca] Loca table for glyph access
|
|
263
|
+
# @param head [Head] Head table for context
|
|
264
|
+
# @param num_glyphs [Integer] Total number of glyphs
|
|
265
|
+
# @return [Boolean] True if all glyphs can be accessed
|
|
266
|
+
def all_glyphs_accessible?(loca, head, num_glyphs)
|
|
267
|
+
(0...num_glyphs).all? do |glyph_id|
|
|
268
|
+
begin
|
|
269
|
+
glyph_for(glyph_id, loca, head)
|
|
270
|
+
true
|
|
271
|
+
rescue Fontisan::CorruptedTableError
|
|
272
|
+
false
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
rescue StandardError
|
|
276
|
+
false
|
|
277
|
+
end
|
|
278
|
+
|
|
161
279
|
private
|
|
162
280
|
|
|
163
281
|
# Validate context and glyph ID
|
data/lib/fontisan/tables/head.rb
CHANGED
|
@@ -82,6 +82,66 @@ module Fontisan
|
|
|
82
82
|
magic_number == MAGIC_NUMBER
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
+
# Validation helper: Check if magic number is valid
|
|
86
|
+
#
|
|
87
|
+
# @return [Boolean] True if magic number equals 0x5F0F3CF5
|
|
88
|
+
def valid_magic?
|
|
89
|
+
magic_number == MAGIC_NUMBER
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Validation helper: Check if version is valid
|
|
93
|
+
#
|
|
94
|
+
# OpenType spec requires version to be 1.0
|
|
95
|
+
#
|
|
96
|
+
# @return [Boolean] True if version is 1.0
|
|
97
|
+
def valid_version?
|
|
98
|
+
version_raw == 0x00010000 # Version 1.0
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Validation helper: Check if units per em is valid
|
|
102
|
+
#
|
|
103
|
+
# Units per em should be a power of 2 between 16 and 16384
|
|
104
|
+
#
|
|
105
|
+
# @return [Boolean] True if units_per_em is valid
|
|
106
|
+
def valid_units_per_em?
|
|
107
|
+
return false if units_per_em.nil? || units_per_em.zero?
|
|
108
|
+
|
|
109
|
+
# Must be between 16 and 16384
|
|
110
|
+
return false unless units_per_em.between?(16, 16384)
|
|
111
|
+
|
|
112
|
+
# Should be a power of 2 (recommended but not required)
|
|
113
|
+
# Common values: 1000, 1024, 2048
|
|
114
|
+
# We'll allow any value in range for flexibility
|
|
115
|
+
true
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Validation helper: Check if bounding box is valid
|
|
119
|
+
#
|
|
120
|
+
# The bounding box should have xMin < xMax and yMin < yMax
|
|
121
|
+
#
|
|
122
|
+
# @return [Boolean] True if bounding box coordinates are valid
|
|
123
|
+
def valid_bounding_box?
|
|
124
|
+
x_min < x_max && y_min < y_max
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Validation helper: Check if index_to_loc_format is valid
|
|
128
|
+
#
|
|
129
|
+
# Must be 0 (short) or 1 (long)
|
|
130
|
+
#
|
|
131
|
+
# @return [Boolean] True if format is 0 or 1
|
|
132
|
+
def valid_index_to_loc_format?
|
|
133
|
+
index_to_loc_format == 0 || index_to_loc_format == 1
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Validation helper: Check if glyph_data_format is valid
|
|
137
|
+
#
|
|
138
|
+
# Must be 0 for current format
|
|
139
|
+
#
|
|
140
|
+
# @return [Boolean] True if format is 0
|
|
141
|
+
def valid_glyph_data_format?
|
|
142
|
+
glyph_data_format == 0
|
|
143
|
+
end
|
|
144
|
+
|
|
85
145
|
# Validate magic number and raise error if invalid
|
|
86
146
|
#
|
|
87
147
|
# @raise [Fontisan::CorruptedTableError] If magic number is invalid
|
data/lib/fontisan/tables/hhea.rb
CHANGED
|
@@ -97,6 +97,80 @@ module Fontisan
|
|
|
97
97
|
true
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
+
# Validation helper: Check if version is valid
|
|
101
|
+
#
|
|
102
|
+
# OpenType spec requires version to be 1.0
|
|
103
|
+
#
|
|
104
|
+
# @return [Boolean] True if version is 1.0
|
|
105
|
+
def valid_version?
|
|
106
|
+
version_raw == 0x00010000
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Validation helper: Check if metric data format is valid
|
|
110
|
+
#
|
|
111
|
+
# Must be 0 for current format
|
|
112
|
+
#
|
|
113
|
+
# @return [Boolean] True if format is 0
|
|
114
|
+
def valid_metric_data_format?
|
|
115
|
+
metric_data_format == 0
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Validation helper: Check if number of h metrics is valid
|
|
119
|
+
#
|
|
120
|
+
# Must be at least 1
|
|
121
|
+
#
|
|
122
|
+
# @return [Boolean] True if number_of_h_metrics >= 1
|
|
123
|
+
def valid_number_of_h_metrics?
|
|
124
|
+
number_of_h_metrics && number_of_h_metrics >= 1
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Validation helper: Check if ascent/descent values are reasonable
|
|
128
|
+
#
|
|
129
|
+
# Ascent should be positive, descent should be negative
|
|
130
|
+
#
|
|
131
|
+
# @return [Boolean] True if ascent/descent have correct signs
|
|
132
|
+
def valid_ascent_descent?
|
|
133
|
+
ascent > 0 && descent < 0
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Validation helper: Check if line gap is non-negative
|
|
137
|
+
#
|
|
138
|
+
# Line gap should be >= 0
|
|
139
|
+
#
|
|
140
|
+
# @return [Boolean] True if line_gap >= 0
|
|
141
|
+
def valid_line_gap?
|
|
142
|
+
line_gap >= 0
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Validation helper: Check if advance width max is positive
|
|
146
|
+
#
|
|
147
|
+
# Maximum advance width should be > 0
|
|
148
|
+
#
|
|
149
|
+
# @return [Boolean] True if advance_width_max > 0
|
|
150
|
+
def valid_advance_width_max?
|
|
151
|
+
advance_width_max && advance_width_max > 0
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Validation helper: Check if caret slope is valid
|
|
155
|
+
#
|
|
156
|
+
# For vertical text: rise=1, run=0
|
|
157
|
+
# For horizontal italic: both should be non-zero
|
|
158
|
+
#
|
|
159
|
+
# @return [Boolean] True if caret slope values are sensible
|
|
160
|
+
def valid_caret_slope?
|
|
161
|
+
# At least one should be non-zero
|
|
162
|
+
caret_slope_rise != 0 || caret_slope_run != 0
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Validation helper: Check if extent is reasonable
|
|
166
|
+
#
|
|
167
|
+
# x_max_extent should be positive
|
|
168
|
+
#
|
|
169
|
+
# @return [Boolean] True if x_max_extent > 0
|
|
170
|
+
def valid_x_max_extent?
|
|
171
|
+
x_max_extent > 0
|
|
172
|
+
end
|
|
173
|
+
|
|
100
174
|
# Validate the table and raise error if invalid
|
|
101
175
|
#
|
|
102
176
|
# @raise [Fontisan::CorruptedTableError] If table is invalid
|
data/lib/fontisan/tables/maxp.rb
CHANGED
|
@@ -157,6 +157,66 @@ module Fontisan
|
|
|
157
157
|
true
|
|
158
158
|
end
|
|
159
159
|
|
|
160
|
+
# Validation helper: Check if version is valid (0.5 or 1.0)
|
|
161
|
+
#
|
|
162
|
+
# @return [Boolean] True if version is 0.5 or 1.0
|
|
163
|
+
def valid_version?
|
|
164
|
+
version_0_5? || version_1_0?
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Validation helper: Check if number of glyphs is valid
|
|
168
|
+
#
|
|
169
|
+
# Must be at least 1 (.notdef glyph must exist)
|
|
170
|
+
#
|
|
171
|
+
# @return [Boolean] True if num_glyphs >= 1
|
|
172
|
+
def valid_num_glyphs?
|
|
173
|
+
num_glyphs && num_glyphs >= 1
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Validation helper: Check if maxZones is valid (version 1.0 only)
|
|
177
|
+
#
|
|
178
|
+
# For TrueType fonts, maxZones must be 1 or 2
|
|
179
|
+
#
|
|
180
|
+
# @return [Boolean] True if maxZones is valid or not applicable
|
|
181
|
+
def valid_max_zones?
|
|
182
|
+
return true if version_0_5? # Not applicable for CFF
|
|
183
|
+
|
|
184
|
+
max_zones && max_zones.between?(1, 2)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Validation helper: Check if all TrueType metrics are present
|
|
188
|
+
#
|
|
189
|
+
# For version 1.0, all max* fields should be present
|
|
190
|
+
#
|
|
191
|
+
# @return [Boolean] True if all required fields are present
|
|
192
|
+
def has_truetype_metrics?
|
|
193
|
+
version_1_0? &&
|
|
194
|
+
!max_points.nil? &&
|
|
195
|
+
!max_contours.nil? &&
|
|
196
|
+
!max_composite_points.nil? &&
|
|
197
|
+
!max_composite_contours.nil?
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Validation helper: Check if metrics are reasonable
|
|
201
|
+
#
|
|
202
|
+
# Checks that values don't exceed reasonable limits
|
|
203
|
+
#
|
|
204
|
+
# @return [Boolean] True if metrics are within reasonable bounds
|
|
205
|
+
def reasonable_metrics?
|
|
206
|
+
# num_glyphs should not exceed 65535
|
|
207
|
+
return false if num_glyphs > 65535
|
|
208
|
+
|
|
209
|
+
if version_1_0?
|
|
210
|
+
# Check reasonable limits for TrueType metrics
|
|
211
|
+
# These are generous limits to allow for complex fonts
|
|
212
|
+
return false if max_points && max_points > 50000
|
|
213
|
+
return false if max_contours && max_contours > 10000
|
|
214
|
+
return false if max_stack_elements && max_stack_elements > 1000
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
true
|
|
218
|
+
end
|
|
219
|
+
|
|
160
220
|
# Validate the table and raise error if invalid
|
|
161
221
|
#
|
|
162
222
|
# @raise [Fontisan::CorruptedTableError] If table is invalid
|
data/lib/fontisan/tables/name.rb
CHANGED
|
@@ -199,6 +199,82 @@ module Fontisan
|
|
|
199
199
|
false
|
|
200
200
|
end
|
|
201
201
|
|
|
202
|
+
# Validation helper: Check if version is valid (0 or 1)
|
|
203
|
+
#
|
|
204
|
+
# @return [Boolean] True if version is 0 or 1
|
|
205
|
+
def valid_version?
|
|
206
|
+
format == 0 || format == 1
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Validation helper: Check if encoding combinations are valid
|
|
210
|
+
#
|
|
211
|
+
# According to OpenType spec, certain platform/encoding combinations are valid:
|
|
212
|
+
# - Platform 0 (Unicode): encoding 0-6
|
|
213
|
+
# - Platform 1 (Mac): encoding 0-32
|
|
214
|
+
# - Platform 3 (Windows): encoding 0-10
|
|
215
|
+
#
|
|
216
|
+
# @return [Boolean] True if all encoding heuristics are valid
|
|
217
|
+
def valid_encoding_heuristics?
|
|
218
|
+
name_records.all? do |rec|
|
|
219
|
+
case rec.platform_id
|
|
220
|
+
when PLATFORM_UNICODE
|
|
221
|
+
rec.encoding_id.between?(0, 6)
|
|
222
|
+
when PLATFORM_MACINTOSH
|
|
223
|
+
rec.encoding_id.between?(0, 32)
|
|
224
|
+
when PLATFORM_WINDOWS
|
|
225
|
+
rec.encoding_id.between?(0, 10)
|
|
226
|
+
else
|
|
227
|
+
# Unknown platform - consider invalid
|
|
228
|
+
false
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Validation helper: Check if required platform combinations exist
|
|
234
|
+
#
|
|
235
|
+
# @param combos [Array<Array<Integer>>] Array of [platform_id, encoding_id, language_id] arrays
|
|
236
|
+
# @return [Boolean] True if all required combinations are present
|
|
237
|
+
#
|
|
238
|
+
# @example Check for Windows English name
|
|
239
|
+
# name.has_valid_platform_combos?([3, 1, 0x0409])
|
|
240
|
+
def has_valid_platform_combos?(*combos)
|
|
241
|
+
combos.all? do |platform_id, encoding_id, language_id|
|
|
242
|
+
name_records.any? do |rec|
|
|
243
|
+
rec.platform_id == platform_id &&
|
|
244
|
+
rec.encoding_id == encoding_id &&
|
|
245
|
+
rec.language_id == language_id
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Validation helper: Check if family name is present and non-empty
|
|
251
|
+
#
|
|
252
|
+
# @return [Boolean] True if family name exists and is not empty
|
|
253
|
+
def family_name_present?
|
|
254
|
+
name = english_name(FAMILY)
|
|
255
|
+
!name.nil? && !name.empty?
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Validation helper: Check if PostScript name is present and non-empty
|
|
259
|
+
#
|
|
260
|
+
# @return [Boolean] True if PostScript name exists and is not empty
|
|
261
|
+
def postscript_name_present?
|
|
262
|
+
name = english_name(POSTSCRIPT_NAME)
|
|
263
|
+
!name.nil? && !name.empty?
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Validation helper: Check if PostScript name is valid
|
|
267
|
+
#
|
|
268
|
+
# PostScript names must contain only ASCII alphanumerics and hyphens
|
|
269
|
+
#
|
|
270
|
+
# @return [Boolean] True if PostScript name matches the required pattern
|
|
271
|
+
def postscript_name_valid?
|
|
272
|
+
name = english_name(POSTSCRIPT_NAME)
|
|
273
|
+
return false if name.nil? || name.empty?
|
|
274
|
+
|
|
275
|
+
name.match?(/^[A-Za-z0-9-]+$/)
|
|
276
|
+
end
|
|
277
|
+
|
|
202
278
|
private
|
|
203
279
|
|
|
204
280
|
# Find a name record matching the criteria
|
data/lib/fontisan/tables/os2.rb
CHANGED
|
@@ -170,6 +170,119 @@ module Fontisan
|
|
|
170
170
|
|
|
171
171
|
us_upper_optical_point_size / 20.0
|
|
172
172
|
end
|
|
173
|
+
|
|
174
|
+
# Validation helper: Check if version is valid
|
|
175
|
+
#
|
|
176
|
+
# Valid versions are 0 through 5
|
|
177
|
+
#
|
|
178
|
+
# @return [Boolean] True if version is 0-5
|
|
179
|
+
def valid_version?
|
|
180
|
+
version && version.between?(0, 5)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Validation helper: Check if weight class is valid
|
|
184
|
+
#
|
|
185
|
+
# Valid values are 1-1000, common values are multiples of 100
|
|
186
|
+
#
|
|
187
|
+
# @return [Boolean] True if weight class is valid
|
|
188
|
+
def valid_weight_class?
|
|
189
|
+
us_weight_class && us_weight_class.between?(1, 1000)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Validation helper: Check if width class is valid
|
|
193
|
+
#
|
|
194
|
+
# Valid values are 1-9
|
|
195
|
+
#
|
|
196
|
+
# @return [Boolean] True if width class is 1-9
|
|
197
|
+
def valid_width_class?
|
|
198
|
+
us_width_class && us_width_class.between?(1, 9)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Validation helper: Check if vendor ID is present
|
|
202
|
+
#
|
|
203
|
+
# Vendor ID should be a 4-character code
|
|
204
|
+
#
|
|
205
|
+
# @return [Boolean] True if vendor ID exists and is non-empty
|
|
206
|
+
def has_vendor_id?
|
|
207
|
+
!vendor_id.empty?
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Validation helper: Check if typo metrics are reasonable
|
|
211
|
+
#
|
|
212
|
+
# Ascent should be positive, descender negative, line gap non-negative
|
|
213
|
+
#
|
|
214
|
+
# @return [Boolean] True if typo metrics have correct signs
|
|
215
|
+
def valid_typo_metrics?
|
|
216
|
+
s_typo_ascender > 0 && s_typo_descender < 0 && s_typo_line_gap >= 0
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Validation helper: Check if Win metrics are valid
|
|
220
|
+
#
|
|
221
|
+
# Both should be positive (unsigned in spec)
|
|
222
|
+
#
|
|
223
|
+
# @return [Boolean] True if Win ascent and descent are positive
|
|
224
|
+
def valid_win_metrics?
|
|
225
|
+
us_win_ascent > 0 && us_win_descent > 0
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Validation helper: Check if Unicode ranges are set
|
|
229
|
+
#
|
|
230
|
+
# At least one Unicode range bit should be set
|
|
231
|
+
#
|
|
232
|
+
# @return [Boolean] True if any Unicode range bits are set
|
|
233
|
+
def has_unicode_ranges?
|
|
234
|
+
(ul_unicode_range1 | ul_unicode_range2 | ul_unicode_range3 | ul_unicode_range4) != 0
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Validation helper: Check if PANOSE data is present
|
|
238
|
+
#
|
|
239
|
+
# All PANOSE values should not be zero
|
|
240
|
+
#
|
|
241
|
+
# @return [Boolean] True if PANOSE seems to be set
|
|
242
|
+
def has_panose?
|
|
243
|
+
panose && panose.any? { |val| val != 0 }
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Validation helper: Check if embedding permissions are set
|
|
247
|
+
#
|
|
248
|
+
# fs_type indicates embedding and subsetting permissions
|
|
249
|
+
#
|
|
250
|
+
# @return [Boolean] True if embedding permissions are defined
|
|
251
|
+
def has_embedding_permissions?
|
|
252
|
+
!fs_type.nil?
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Validation helper: Check if selection flags are valid
|
|
256
|
+
#
|
|
257
|
+
# Checks for valid combinations of selection flags
|
|
258
|
+
#
|
|
259
|
+
# @return [Boolean] True if fs_selection has valid flags
|
|
260
|
+
def valid_selection_flags?
|
|
261
|
+
return false if fs_selection.nil?
|
|
262
|
+
|
|
263
|
+
# Bits 0-9 are defined, others should be zero
|
|
264
|
+
(fs_selection & 0xFC00).zero?
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Validation helper: Check if x_height and cap_height are present (v2+)
|
|
268
|
+
#
|
|
269
|
+
# For version 2+, these should be set
|
|
270
|
+
#
|
|
271
|
+
# @return [Boolean] True if metrics are present (or not required)
|
|
272
|
+
def has_x_height_cap_height?
|
|
273
|
+
return true if version < 2 # Not required for v0-1
|
|
274
|
+
|
|
275
|
+
!sx_height.nil? && !s_cap_height.nil? && sx_height > 0 && s_cap_height > 0
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Validation helper: Check if first/last char indices are reasonable
|
|
279
|
+
#
|
|
280
|
+
# first should be <= last
|
|
281
|
+
#
|
|
282
|
+
# @return [Boolean] True if character range is valid
|
|
283
|
+
def valid_char_range?
|
|
284
|
+
us_first_char_index <= us_last_char_index
|
|
285
|
+
end
|
|
173
286
|
end
|
|
174
287
|
end
|
|
175
288
|
end
|
data/lib/fontisan/tables/post.rb
CHANGED
|
@@ -143,6 +143,63 @@ module Fontisan
|
|
|
143
143
|
end
|
|
144
144
|
end
|
|
145
145
|
# rubocop:enable Metrics/PerceivedComplexity
|
|
146
|
+
|
|
147
|
+
public
|
|
148
|
+
|
|
149
|
+
# Validation helper: Check if version is valid
|
|
150
|
+
#
|
|
151
|
+
# Common versions: 1.0, 2.0, 2.5, 3.0, 4.0
|
|
152
|
+
#
|
|
153
|
+
# @return [Boolean] True if version is recognized
|
|
154
|
+
def valid_version?
|
|
155
|
+
[1.0, 2.0, 2.5, 3.0, 4.0].include?(version)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Validation helper: Check if italic angle is reasonable
|
|
159
|
+
#
|
|
160
|
+
# Italic angle should be between -60 and 60 degrees
|
|
161
|
+
#
|
|
162
|
+
# @return [Boolean] True if italic angle is within reasonable bounds
|
|
163
|
+
def valid_italic_angle?
|
|
164
|
+
italic_angle.abs <= 60.0
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Validation helper: Check if underline values are present
|
|
168
|
+
#
|
|
169
|
+
# Both position and thickness should be non-zero for valid underline
|
|
170
|
+
#
|
|
171
|
+
# @return [Boolean] True if underline metrics exist
|
|
172
|
+
def has_underline_metrics?
|
|
173
|
+
underline_position != 0 && underline_thickness != 0
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Validation helper: Check if fixed pitch flag is consistent
|
|
177
|
+
#
|
|
178
|
+
# @return [Boolean] True if is_fixed_pitch is 0 or 1
|
|
179
|
+
def valid_fixed_pitch_flag?
|
|
180
|
+
is_fixed_pitch == 0 || is_fixed_pitch == 1
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Validation helper: Check if glyph names are available
|
|
184
|
+
#
|
|
185
|
+
# For versions 1.0 and 2.0, glyph names should be accessible
|
|
186
|
+
#
|
|
187
|
+
# @return [Boolean] True if glyph names can be retrieved
|
|
188
|
+
def has_glyph_names?
|
|
189
|
+
names = glyph_names
|
|
190
|
+
!names.nil? && !names.empty?
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Validation helper: Check if version 2.0 data is complete
|
|
194
|
+
#
|
|
195
|
+
# For version 2.0, we should have glyph count and name data
|
|
196
|
+
#
|
|
197
|
+
# @return [Boolean] True if version 2.0 data is present and complete
|
|
198
|
+
def complete_version_2_data?
|
|
199
|
+
return true unless version == 2.0
|
|
200
|
+
|
|
201
|
+
!num_glyphs_v2.nil? && num_glyphs_v2 > 0 && !remaining_data.empty?
|
|
202
|
+
end
|
|
146
203
|
end
|
|
147
204
|
end
|
|
148
205
|
end
|