fontisan 0.4.42 → 0.4.44
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/TODO.bug-fixes/09-type1-cff-seac-expansion.md +22 -0
- data/TODO.bug-fixes/10-ttf-type1-composite-glyphs.md +23 -0
- data/TODO.bug-fixes/11-remaining-respond-to.md +25 -0
- data/TODO.bug-fixes/12-type1-generators-duck-typing.md +80 -0
- data/TODO.bug-fixes/13-hint-extractor-duck-typing.md +30 -0
- data/TODO.bug-fixes/14-font-interface-type-checks.md +82 -0
- data/TODO.bug-fixes/15-glyph-type-checks-and-simple-glyph-points-bug.md +49 -0
- data/TODO.bug-fixes/16-variation-table-checks.md +31 -0
- data/TODO.bug-fixes/17-ufo-and-stitcher-cleanup.md +32 -0
- data/TODO.bug-fixes/18-generic-value-and-io-checks.md +56 -0
- data/TODO.bug-fixes/19-serialization-migration.md +52 -0
- data/TODO.bug-fixes/20-variation-validator-type-checks.md +29 -0
- data/TODO.bug-fixes/21-xml-builder-send.md +24 -0
- data/TODO.bug-fixes/22-type1-converter-font-info.md +25 -0
- data/TODO.bug-fixes/23-boundary-type-checks.md +31 -0
- data/TODO.bug-fixes/24-validator-dynamic-field-key.md +25 -0
- data/TODO.bug-fixes/25-serialization-migration.md +49 -0
- data/TODO.bug-fixes/26-variation-preserver-spec-migration.md +25 -0
- data/TODO.bug-fixes/27-table-class-registry.md +50 -0
- data/TODO.bug-fixes/28-spec-doubles-cleanup.md +40 -0
- data/TODO.bug-fixes/29-strategy-registries.md +30 -0
- data/TODO.bug-fixes/README.md +29 -2
- data/lib/fontisan/binary/base_record.rb +13 -12
- data/lib/fontisan/collection/builder.rb +3 -5
- data/lib/fontisan/collection/dfont_builder.rb +4 -11
- data/lib/fontisan/collection/writer.rb +1 -1
- data/lib/fontisan/commands/pack_command.rb +4 -4
- data/lib/fontisan/commands/unpack_command.rb +1 -1
- data/lib/fontisan/commands/validate_command.rb +1 -1
- data/lib/fontisan/converters/format_converter.rb +2 -2
- data/lib/fontisan/converters/glyf_table_builder.rb +1 -1
- data/lib/fontisan/converters/outline_converter.rb +2 -6
- data/lib/fontisan/converters/table_copier.rb +4 -12
- data/lib/fontisan/converters/type1_converter.rb +12 -37
- data/lib/fontisan/converters/woff2_encoder.rb +8 -10
- data/lib/fontisan/converters/woff_writer.rb +3 -3
- data/lib/fontisan/export/exporter.rb +8 -9
- data/lib/fontisan/export/table_serializer.rb +71 -186
- data/lib/fontisan/export/transformers/font_to_ttx.rb +18 -18
- data/lib/fontisan/export/transformers/head_transformer.rb +1 -1
- data/lib/fontisan/export/transformers/hhea_transformer.rb +1 -1
- data/lib/fontisan/export/transformers/maxp_transformer.rb +1 -1
- data/lib/fontisan/export/transformers/name_transformer.rb +1 -1
- data/lib/fontisan/export/transformers/os2_transformer.rb +1 -1
- data/lib/fontisan/export/transformers/post_transformer.rb +1 -1
- data/lib/fontisan/export/ttx_generator.rb +36 -37
- data/lib/fontisan/font_writer.rb +1 -1
- data/lib/fontisan/glyph_accessor.rb +9 -15
- data/lib/fontisan/hints/postscript_hint_extractor.rb +24 -64
- data/lib/fontisan/hints/truetype_hint_extractor.rb +1 -1
- data/lib/fontisan/models/ttx/tables/binary_table.rb +1 -1
- data/lib/fontisan/optimizers/charstring_rewriter.rb +1 -1
- data/lib/fontisan/outline_extractor.rb +1 -5
- data/lib/fontisan/sfnt_font.rb +3 -3
- data/lib/fontisan/sfnt_source.rb +28 -0
- data/lib/fontisan/stitcher/source.rb +8 -10
- data/lib/fontisan/subset/builder.rb +2 -2
- data/lib/fontisan/subset/table_strategy/cff2.rb +2 -3
- data/lib/fontisan/svg/font_face_generator.rb +0 -4
- data/lib/fontisan/svg/font_generator.rb +0 -4
- data/lib/fontisan/svg_to_glyf/assembler.rb +59 -20
- data/lib/fontisan/svg_to_glyf/document.rb +28 -9
- data/lib/fontisan/svg_to_glyf/geometry/normalizer.rb +34 -17
- data/lib/fontisan/tables/cff/charstring.rb +3 -0
- data/lib/fontisan/tables/cff/dict.rb +6 -3
- data/lib/fontisan/tables/cff/index.rb +1 -1
- data/lib/fontisan/tables/cff/private_dict_writer.rb +2 -2
- data/lib/fontisan/tables/cff/table_builder.rb +1 -1
- data/lib/fontisan/tables/cff.rb +3 -0
- data/lib/fontisan/tables/cff2/table_builder.rb +2 -4
- data/lib/fontisan/tables/cff2.rb +3 -0
- data/lib/fontisan/tables/cmap.rb +3 -0
- data/lib/fontisan/tables/cvar.rb +3 -0
- data/lib/fontisan/tables/fvar.rb +3 -0
- data/lib/fontisan/tables/glyf/simple_glyph.rb +9 -0
- data/lib/fontisan/tables/glyf.rb +12 -8
- data/lib/fontisan/tables/glyf_table.rb +4 -4
- data/lib/fontisan/tables/gvar.rb +3 -0
- data/lib/fontisan/tables/head.rb +3 -0
- data/lib/fontisan/tables/hhea.rb +3 -0
- data/lib/fontisan/tables/hmtx.rb +3 -7
- data/lib/fontisan/tables/hvar.rb +4 -0
- data/lib/fontisan/tables/loca.rb +3 -0
- data/lib/fontisan/tables/maxp.rb +3 -0
- data/lib/fontisan/tables/mvar.rb +3 -0
- data/lib/fontisan/tables/name.rb +3 -0
- data/lib/fontisan/tables/os2.rb +3 -0
- data/lib/fontisan/tables/post.rb +3 -0
- data/lib/fontisan/tables/registry.rb +71 -0
- data/lib/fontisan/tables/vvar.rb +3 -0
- data/lib/fontisan/tables.rb +2 -0
- data/lib/fontisan/type1/afm_generator.rb +12 -65
- data/lib/fontisan/type1/charstring_converter.rb +21 -23
- data/lib/fontisan/type1/conversion_options.rb +3 -2
- data/lib/fontisan/type1/decryptor.rb +1 -1
- data/lib/fontisan/type1/generator.rb +14 -9
- data/lib/fontisan/type1/inf_generator.rb +10 -35
- data/lib/fontisan/type1/pfa_generator.rb +36 -26
- data/lib/fontisan/type1/pfb_generator.rb +24 -12
- data/lib/fontisan/type1/pfm_generator.rb +14 -31
- data/lib/fontisan/type1/ttf_to_type1_converter.rb +88 -26
- data/lib/fontisan/ufo/bounds.rb +91 -0
- data/lib/fontisan/ufo/compile/fvar.rb +2 -2
- data/lib/fontisan/ufo/contour.rb +5 -0
- data/lib/fontisan/ufo/convert/from_bin_data.rb +15 -29
- data/lib/fontisan/ufo/info.rb +4 -0
- data/lib/fontisan/ufo/point.rb +0 -7
- data/lib/fontisan/ufo.rb +1 -0
- data/lib/fontisan/utilities/brotli_wrapper.rb +3 -5
- data/lib/fontisan/utilities/padding.rb +9 -8
- data/lib/fontisan/validation/collection_validator.rb +2 -2
- data/lib/fontisan/validators/validator.rb +4 -4
- data/lib/fontisan/variable/delta_applicator.rb +4 -11
- data/lib/fontisan/variable/metric_delta_processor.rb +3 -3
- data/lib/fontisan/variable/static_font_builder.rb +19 -23
- data/lib/fontisan/variation/instance_font_wrapper.rb +41 -0
- data/lib/fontisan/variation/instance_writer.rb +19 -17
- data/lib/fontisan/variation/metrics_adjuster.rb +17 -20
- data/lib/fontisan/variation/validator.rb +26 -38
- data/lib/fontisan/variation/variable_svg_generator.rb +0 -48
- data/lib/fontisan/variation/variation_preserver.rb +2 -3
- data/lib/fontisan/variation.rb +1 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2/sfnt_checksum.rb +1 -1
- data/lib/fontisan/woff2/table_transformer.rb +22 -29
- data/lib/fontisan/woff2_font.rb +11 -10
- data/lib/fontisan/woff_font.rb +2 -1
- data/lib/fontisan.rb +1 -0
- metadata +27 -2
|
@@ -161,9 +161,7 @@ module Fontisan
|
|
|
161
161
|
# @return [Integer] Total stem count (hstem + vstem)
|
|
162
162
|
def calculate_stem_count
|
|
163
163
|
return 0 unless @hint_set
|
|
164
|
-
|
|
165
|
-
# Get font-level hints (from private_dict_hints JSON)
|
|
166
|
-
return 0 unless @hint_set.respond_to?(:private_dict_hints)
|
|
164
|
+
return 0 unless @hint_set.is_a?(Models::HintSet)
|
|
167
165
|
|
|
168
166
|
begin
|
|
169
167
|
font_hints = JSON.parse(@hint_set.private_dict_hints || "{}")
|
|
@@ -194,7 +192,7 @@ module Fontisan
|
|
|
194
192
|
#
|
|
195
193
|
# @return [Boolean] True if private_dict_hints are present
|
|
196
194
|
def has_font_level_hints?
|
|
197
|
-
return false unless @hint_set.
|
|
195
|
+
return false unless @hint_set.is_a?(Models::HintSet)
|
|
198
196
|
|
|
199
197
|
hints = JSON.parse(@hint_set.private_dict_hints || "{}")
|
|
200
198
|
!hints.empty?
|
data/lib/fontisan/tables/cff2.rb
CHANGED
|
@@ -20,6 +20,9 @@ module Fontisan
|
|
|
20
20
|
# cff2 = Fontisan::Tables::Cff2.read(data)
|
|
21
21
|
# num_glyphs = cff2.glyph_count
|
|
22
22
|
class Cff2 < Binary::BaseRecord
|
|
23
|
+
extend Registered
|
|
24
|
+
|
|
25
|
+
register_tag "CFF2"
|
|
23
26
|
# Inner namespace autoloads — declared here so Cff2::* constants
|
|
24
27
|
# resolve on first reference without require_relative.
|
|
25
28
|
autoload :BlendOperator, "fontisan/tables/cff2/blend_operator"
|
data/lib/fontisan/tables/cmap.rb
CHANGED
data/lib/fontisan/tables/cvar.rb
CHANGED
|
@@ -20,6 +20,9 @@ module Fontisan
|
|
|
20
20
|
# cvar = Fontisan::Tables::Cvar.read(data)
|
|
21
21
|
# cvt_deltas = cvar.cvt_variations
|
|
22
22
|
class Cvar < Binary::BaseRecord
|
|
23
|
+
extend Registered
|
|
24
|
+
|
|
25
|
+
register_tag "cvar"
|
|
23
26
|
uint16 :major_version
|
|
24
27
|
uint16 :minor_version
|
|
25
28
|
uint16 :tuple_variation_count
|
data/lib/fontisan/tables/fvar.rb
CHANGED
|
@@ -155,6 +155,15 @@ module Fontisan
|
|
|
155
155
|
end
|
|
156
156
|
end
|
|
157
157
|
|
|
158
|
+
# All points across every contour, in contour order.
|
|
159
|
+
#
|
|
160
|
+
# @return [Array<Hash>] Array of {x:, y:, on_curve:} hashes
|
|
161
|
+
def points
|
|
162
|
+
return [] if empty?
|
|
163
|
+
|
|
164
|
+
num_contours.times.flat_map { |c| points_for_contour(c) }
|
|
165
|
+
end
|
|
166
|
+
|
|
158
167
|
private
|
|
159
168
|
|
|
160
169
|
# Parse glyph header (10 bytes)
|
data/lib/fontisan/tables/glyf.rb
CHANGED
|
@@ -38,6 +38,9 @@ module Fontisan
|
|
|
38
38
|
# puts glyph.simple? ? "Simple glyph" : "Compound glyph"
|
|
39
39
|
# puts glyph.bounding_box # => [xMin, yMin, xMax, yMax]
|
|
40
40
|
class Glyf < Binary::BaseRecord
|
|
41
|
+
extend Registered
|
|
42
|
+
|
|
43
|
+
register_tag "glyf"
|
|
41
44
|
# Store the raw data for deferred parsing
|
|
42
45
|
attr_accessor :raw_data
|
|
43
46
|
|
|
@@ -215,13 +218,13 @@ module Fontisan
|
|
|
215
218
|
next true if glyph.nil? # Empty glyphs are OK
|
|
216
219
|
|
|
217
220
|
# Simple glyphs have instructions
|
|
218
|
-
if glyph.
|
|
221
|
+
if glyph.nil?
|
|
222
|
+
# Compound glyphs may have instructions too
|
|
223
|
+
true
|
|
224
|
+
else
|
|
219
225
|
inst_len = glyph.instruction_length
|
|
220
226
|
# If instructions present, length should be reasonable
|
|
221
227
|
inst_len.nil? || inst_len >= 0
|
|
222
|
-
else
|
|
223
|
-
# Compound glyphs may have instructions too
|
|
224
|
-
true
|
|
225
228
|
end
|
|
226
229
|
end
|
|
227
230
|
rescue StandardError
|
|
@@ -240,7 +243,8 @@ module Fontisan
|
|
|
240
243
|
|
|
241
244
|
# Simple glyphs: contours should be >= 0
|
|
242
245
|
# Compound glyphs: numberOfContours = -1
|
|
243
|
-
|
|
246
|
+
case glyph
|
|
247
|
+
when SimpleGlyph, CompoundGlyph
|
|
244
248
|
glyph.num_contours >= -1
|
|
245
249
|
else
|
|
246
250
|
true
|
|
@@ -277,9 +281,9 @@ module Fontisan
|
|
|
277
281
|
# @param glyph_id [Integer] Glyph ID
|
|
278
282
|
# @raise [ArgumentError] If validation fails
|
|
279
283
|
def validate_context!(loca, head, glyph_id)
|
|
280
|
-
unless loca.
|
|
284
|
+
unless loca.is_a?(Loca)
|
|
281
285
|
raise ArgumentError,
|
|
282
|
-
"loca must be a parsed Loca table
|
|
286
|
+
"loca must be a parsed Loca table"
|
|
283
287
|
end
|
|
284
288
|
|
|
285
289
|
unless loca.parsed?
|
|
@@ -287,7 +291,7 @@ module Fontisan
|
|
|
287
291
|
"loca table must be parsed with parse_with_context before use"
|
|
288
292
|
end
|
|
289
293
|
|
|
290
|
-
unless head.
|
|
294
|
+
unless head.is_a?(Head)
|
|
291
295
|
raise ArgumentError,
|
|
292
296
|
"head must be a parsed Head table"
|
|
293
297
|
end
|
|
@@ -76,7 +76,7 @@ module Fontisan
|
|
|
76
76
|
glyph = glyph_for(glyph_id)
|
|
77
77
|
return false if glyph.nil?
|
|
78
78
|
|
|
79
|
-
glyph.
|
|
79
|
+
!glyph.nil? && glyph.num_contours >= 0
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
# Check if a glyph is compound
|
|
@@ -87,7 +87,7 @@ module Fontisan
|
|
|
87
87
|
glyph = glyph_for(glyph_id)
|
|
88
88
|
return false if glyph.nil?
|
|
89
89
|
|
|
90
|
-
glyph.
|
|
90
|
+
!glyph.nil? && glyph.num_contours == -1
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
# Check if a glyph is empty
|
|
@@ -117,7 +117,7 @@ module Fontisan
|
|
|
117
117
|
glyph = glyph_for(glyph_id)
|
|
118
118
|
return nil if glyph.nil?
|
|
119
119
|
|
|
120
|
-
glyph
|
|
120
|
+
glyph&.num_contours
|
|
121
121
|
end
|
|
122
122
|
|
|
123
123
|
# Get number of points for a simple glyph
|
|
@@ -126,7 +126,7 @@ module Fontisan
|
|
|
126
126
|
# @return [Integer, nil] Number of points, or nil
|
|
127
127
|
def glyph_point_count(glyph_id)
|
|
128
128
|
glyph = glyph_for(glyph_id)
|
|
129
|
-
return nil if glyph.nil? ||
|
|
129
|
+
return nil if glyph.nil? || glyph.num_points.nil?
|
|
130
130
|
|
|
131
131
|
glyph.num_points
|
|
132
132
|
end
|
data/lib/fontisan/tables/gvar.rb
CHANGED
|
@@ -20,6 +20,9 @@ module Fontisan
|
|
|
20
20
|
# gvar = Fontisan::Tables::Gvar.read(data)
|
|
21
21
|
# deltas = gvar.glyph_variations(glyph_id)
|
|
22
22
|
class Gvar < Binary::BaseRecord
|
|
23
|
+
extend Registered
|
|
24
|
+
|
|
25
|
+
register_tag "gvar"
|
|
23
26
|
uint16 :major_version
|
|
24
27
|
uint16 :minor_version
|
|
25
28
|
uint16 :axis_count
|
data/lib/fontisan/tables/head.rb
CHANGED
|
@@ -15,6 +15,9 @@ module Fontisan
|
|
|
15
15
|
# puts head.units_per_em # => 2048
|
|
16
16
|
# puts head.version_number # => 1.0
|
|
17
17
|
class Head < Binary::BaseRecord
|
|
18
|
+
extend Registered
|
|
19
|
+
|
|
20
|
+
register_tag "head"
|
|
18
21
|
# Magic number that must be present in the head table
|
|
19
22
|
MAGIC_NUMBER = 0x5F0F3CF5
|
|
20
23
|
|
data/lib/fontisan/tables/hhea.rb
CHANGED
data/lib/fontisan/tables/hmtx.rb
CHANGED
|
@@ -38,6 +38,9 @@ module Fontisan
|
|
|
38
38
|
# puts "Advance width: #{metric[:advance_width]}"
|
|
39
39
|
# puts "LSB: #{metric[:lsb]}"
|
|
40
40
|
class Hmtx < Binary::BaseRecord
|
|
41
|
+
extend Registered
|
|
42
|
+
|
|
43
|
+
register_tag "hmtx"
|
|
41
44
|
# LongHorMetric record structure
|
|
42
45
|
#
|
|
43
46
|
# @!attribute advance_width
|
|
@@ -47,13 +50,6 @@ module Fontisan
|
|
|
47
50
|
class LongHorMetric < Binary::BaseRecord
|
|
48
51
|
uint16 :advance_width
|
|
49
52
|
int16 :lsb
|
|
50
|
-
|
|
51
|
-
# Convert to hash for convenience
|
|
52
|
-
#
|
|
53
|
-
# @return [Hash] Hash with :advance_width and :lsb keys
|
|
54
|
-
def to_h
|
|
55
|
-
{ advance_width: advance_width, lsb: lsb }
|
|
56
|
-
end
|
|
57
53
|
end
|
|
58
54
|
|
|
59
55
|
# Store the raw data for deferred parsing
|
data/lib/fontisan/tables/hvar.rb
CHANGED
|
@@ -18,6 +18,10 @@ module Fontisan
|
|
|
18
18
|
# hvar = Fontisan::Tables::Hvar.read(data)
|
|
19
19
|
# advance_deltas = hvar.advance_width_deltas(glyph_id, coordinates)
|
|
20
20
|
class Hvar < Binary::BaseRecord
|
|
21
|
+
extend Registered
|
|
22
|
+
|
|
23
|
+
register_tag "HVAR"
|
|
24
|
+
|
|
21
25
|
uint16 :major_version
|
|
22
26
|
uint16 :minor_version
|
|
23
27
|
uint32 :item_variation_store_offset
|
data/lib/fontisan/tables/loca.rb
CHANGED
data/lib/fontisan/tables/maxp.rb
CHANGED
data/lib/fontisan/tables/mvar.rb
CHANGED
|
@@ -21,6 +21,9 @@ module Fontisan
|
|
|
21
21
|
# mvar = Fontisan::Tables::Mvar.read(data)
|
|
22
22
|
# hasc_deltas = mvar.metric_deltas("hasc")
|
|
23
23
|
class Mvar < Binary::BaseRecord
|
|
24
|
+
extend Registered
|
|
25
|
+
|
|
26
|
+
register_tag "MVAR"
|
|
24
27
|
uint16 :major_version
|
|
25
28
|
uint16 :minor_version
|
|
26
29
|
uint16 :reserved
|
data/lib/fontisan/tables/name.rb
CHANGED
|
@@ -64,6 +64,9 @@ module Fontisan
|
|
|
64
64
|
# name = Fontisan::Tables::Name.read(data)
|
|
65
65
|
# puts name.english_name(Fontisan::Tables::Name::FAMILY)
|
|
66
66
|
class Name < Binary::BaseRecord
|
|
67
|
+
extend Registered
|
|
68
|
+
|
|
69
|
+
register_tag "name"
|
|
67
70
|
# Name ID constants for common name records
|
|
68
71
|
COPYRIGHT = 0
|
|
69
72
|
FAMILY = 1
|
data/lib/fontisan/tables/os2.rb
CHANGED
data/lib/fontisan/tables/post.rb
CHANGED
|
@@ -10,6 +10,9 @@ module Fontisan
|
|
|
10
10
|
#
|
|
11
11
|
# Reference: OpenType specification, post table
|
|
12
12
|
class Post < Binary::BaseRecord
|
|
13
|
+
extend Registered
|
|
14
|
+
|
|
15
|
+
register_tag "post"
|
|
13
16
|
# Standard Mac glyph names for version 1.0 (258 glyphs)
|
|
14
17
|
# rubocop:disable Metrics/CollectionLiteralLength
|
|
15
18
|
STANDARD_NAMES = %w[
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Tables
|
|
5
|
+
# Single source of truth mapping OpenType table tags to Ruby classes.
|
|
6
|
+
#
|
|
7
|
+
# Each table class registers itself via `register_tag`:
|
|
8
|
+
#
|
|
9
|
+
# module Fontisan::Tables
|
|
10
|
+
# class Hvar < Binary::BaseRecord
|
|
11
|
+
# register_tag "HVAR"
|
|
12
|
+
# ...
|
|
13
|
+
# end
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# Dispatch sites use `Tables::Registry.for(tag)` instead of a
|
|
17
|
+
# `case tag when "HVAR"` switch. This is OCP-compliant: adding a
|
|
18
|
+
# new table means adding one `register_tag` line in the table's
|
|
19
|
+
# file, not editing every dispatch site.
|
|
20
|
+
module Registry
|
|
21
|
+
@by_tag = {}
|
|
22
|
+
@by_class = {}
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
# @api internal Called by table classes at load time.
|
|
26
|
+
# @param tag [String] Four-byte OpenType table tag (e.g., "HVAR").
|
|
27
|
+
# @param klass [Class] The Ruby class implementing the table.
|
|
28
|
+
def register(tag, klass)
|
|
29
|
+
@by_tag[tag] = klass
|
|
30
|
+
@by_class[klass] = tag
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Look up the table class for a tag.
|
|
34
|
+
#
|
|
35
|
+
# @param tag [String] Four-byte OpenType table tag.
|
|
36
|
+
# @return [Class, nil] The table class, or nil if unknown.
|
|
37
|
+
def for(tag)
|
|
38
|
+
@by_tag[tag]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Reverse lookup: tag for a given class.
|
|
42
|
+
#
|
|
43
|
+
# @param klass [Class] A table class.
|
|
44
|
+
# @return [String, nil] The tag, or nil if not registered.
|
|
45
|
+
def tag_for(klass)
|
|
46
|
+
@by_class[klass]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [Array<String>] All registered tags.
|
|
50
|
+
def tags
|
|
51
|
+
@by_tag.keys
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Clear all registrations (testing only).
|
|
55
|
+
def __clear__
|
|
56
|
+
@by_tag.clear
|
|
57
|
+
@by_class.clear
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Mixin applied to table classes that register themselves with the
|
|
63
|
+
# Registry at load time. Used via `register_tag "HVAR"` in the
|
|
64
|
+
# class body.
|
|
65
|
+
module Registered
|
|
66
|
+
def register_tag(tag)
|
|
67
|
+
Tables::Registry.register(tag, self)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
data/lib/fontisan/tables/vvar.rb
CHANGED
|
@@ -18,6 +18,9 @@ module Fontisan
|
|
|
18
18
|
# vvar = Fontisan::Tables::Vvar.read(data)
|
|
19
19
|
# advance_deltas = vvar.advance_height_deltas(glyph_id, coordinates)
|
|
20
20
|
class Vvar < Binary::BaseRecord
|
|
21
|
+
extend Registered
|
|
22
|
+
|
|
23
|
+
register_tag "VVAR"
|
|
21
24
|
uint16 :major_version
|
|
22
25
|
uint16 :minor_version
|
|
23
26
|
uint32 :item_variation_store_offset
|
data/lib/fontisan/tables.rb
CHANGED
|
@@ -57,6 +57,8 @@ module Fontisan
|
|
|
57
57
|
autoload :Os2Table, "fontisan/tables/os2_table"
|
|
58
58
|
autoload :Post, "fontisan/tables/post"
|
|
59
59
|
autoload :PostTable, "fontisan/tables/post_table"
|
|
60
|
+
autoload :Registered, "fontisan/tables/registry"
|
|
61
|
+
autoload :Registry, "fontisan/tables/registry"
|
|
60
62
|
autoload :Sbix, "fontisan/tables/sbix"
|
|
61
63
|
autoload :SimpleGlyph, "fontisan/tables/glyf/simple_glyph"
|
|
62
64
|
autoload :Svg, "fontisan/tables/svg"
|
|
@@ -167,10 +167,10 @@ module Fontisan
|
|
|
167
167
|
|
|
168
168
|
# Underline properties
|
|
169
169
|
post = @font.table(Constants::POST_TAG)
|
|
170
|
-
underline_position = post&.underline_position
|
|
170
|
+
underline_position = post&.underline_position
|
|
171
171
|
afm_lines << "UnderlinePosition #{underline_position}" if underline_position
|
|
172
172
|
|
|
173
|
-
underline_thickness = post&.underline_thickness
|
|
173
|
+
underline_thickness = post&.underline_thickness
|
|
174
174
|
afm_lines << "UnderlineThickness #{underline_thickness}" if underline_thickness
|
|
175
175
|
end
|
|
176
176
|
|
|
@@ -252,11 +252,7 @@ module Fontisan
|
|
|
252
252
|
os2 = @font.table(Constants::OS2_TAG)
|
|
253
253
|
return "Regular" unless os2
|
|
254
254
|
|
|
255
|
-
weight_class =
|
|
256
|
-
os2.us_weight_class
|
|
257
|
-
elsif os2.respond_to?(:weight_class)
|
|
258
|
-
os2.weight_class
|
|
259
|
-
end
|
|
255
|
+
weight_class = os2.us_weight_class
|
|
260
256
|
return "Regular" unless weight_class
|
|
261
257
|
|
|
262
258
|
case weight_class
|
|
@@ -280,11 +276,7 @@ module Fontisan
|
|
|
280
276
|
post = @font.table(Constants::POST_TAG)
|
|
281
277
|
return 0.0 unless post
|
|
282
278
|
|
|
283
|
-
|
|
284
|
-
post.italic_angle
|
|
285
|
-
else
|
|
286
|
-
0.0
|
|
287
|
-
end
|
|
279
|
+
post.italic_angle
|
|
288
280
|
end
|
|
289
281
|
|
|
290
282
|
# Check if font is monospace
|
|
@@ -294,11 +286,7 @@ module Fontisan
|
|
|
294
286
|
post = @font.table(Constants::POST_TAG)
|
|
295
287
|
return false unless post
|
|
296
288
|
|
|
297
|
-
|
|
298
|
-
post.is_fixed_pitch
|
|
299
|
-
else
|
|
300
|
-
false
|
|
301
|
-
end
|
|
289
|
+
post.is_fixed_pitch
|
|
302
290
|
end
|
|
303
291
|
|
|
304
292
|
# Extract version from name table
|
|
@@ -308,9 +296,7 @@ module Fontisan
|
|
|
308
296
|
name_table = @font.table(Constants::NAME_TAG)
|
|
309
297
|
return nil unless name_table
|
|
310
298
|
|
|
311
|
-
|
|
312
|
-
name_table.version_string(1) || name_table.version_string(3)
|
|
313
|
-
end
|
|
299
|
+
name_table.english_name(Tables::Name::VERSION)
|
|
314
300
|
end
|
|
315
301
|
|
|
316
302
|
# Extract copyright from name table
|
|
@@ -320,9 +306,7 @@ module Fontisan
|
|
|
320
306
|
name_table = @font.table(Constants::NAME_TAG)
|
|
321
307
|
return nil unless name_table
|
|
322
308
|
|
|
323
|
-
|
|
324
|
-
name_table.copyright(1) || name_table.copyright(3)
|
|
325
|
-
end
|
|
309
|
+
name_table.english_name(Tables::Name::COPYRIGHT)
|
|
326
310
|
end
|
|
327
311
|
|
|
328
312
|
# Extract character mappings from cmap table
|
|
@@ -332,30 +316,7 @@ module Fontisan
|
|
|
332
316
|
cmap = @font.table(Constants::CMAP_TAG)
|
|
333
317
|
return {} unless cmap
|
|
334
318
|
|
|
335
|
-
@extract_character_mappings ||=
|
|
336
|
-
mappings = {}
|
|
337
|
-
|
|
338
|
-
# Try to get Unicode mappings (most reliable method)
|
|
339
|
-
if cmap.respond_to?(:unicode_mappings)
|
|
340
|
-
mappings = cmap.unicode_mappings || {}
|
|
341
|
-
elsif cmap.respond_to?(:unicode_bmp_mapping)
|
|
342
|
-
mappings = cmap.unicode_bmp_mapping || {}
|
|
343
|
-
elsif cmap.respond_to?(:subtables)
|
|
344
|
-
# Look for Unicode BMP subtable
|
|
345
|
-
unicode_subtable = cmap.subtables.find do |subtable|
|
|
346
|
-
subtable.respond_to?(:platform_id) &&
|
|
347
|
-
subtable.platform_id == 3 &&
|
|
348
|
-
subtable.respond_to?(:encoding_id) &&
|
|
349
|
-
subtable.encoding_id == 1
|
|
350
|
-
end
|
|
351
|
-
|
|
352
|
-
if unicode_subtable.respond_to?(:glyph_index_map)
|
|
353
|
-
mappings = unicode_subtable.glyph_index_map
|
|
354
|
-
end
|
|
355
|
-
end
|
|
356
|
-
|
|
357
|
-
mappings
|
|
358
|
-
end
|
|
319
|
+
@extract_character_mappings ||= cmap.unicode_mappings || {}
|
|
359
320
|
end
|
|
360
321
|
|
|
361
322
|
# Extract font bounding box
|
|
@@ -365,12 +326,7 @@ module Fontisan
|
|
|
365
326
|
head = @font.table(Constants::HEAD_TAG)
|
|
366
327
|
return nil unless head
|
|
367
328
|
|
|
368
|
-
|
|
369
|
-
head.font_bounding_box
|
|
370
|
-
elsif head.respond_to?(:x_min) && head.respond_to?(:y_min) &&
|
|
371
|
-
head.respond_to?(:x_max) && head.respond_to?(:y_max)
|
|
372
|
-
[head.x_min, head.y_min, head.x_max, head.y_max]
|
|
373
|
-
end
|
|
329
|
+
[head.x_min, head.y_min, head.x_max, head.y_max]
|
|
374
330
|
end
|
|
375
331
|
|
|
376
332
|
# Extract glyph bounding box
|
|
@@ -390,7 +346,7 @@ module Fontisan
|
|
|
390
346
|
return nil unless head_table
|
|
391
347
|
|
|
392
348
|
# Ensure loca is parsed with context
|
|
393
|
-
if loca_table.
|
|
349
|
+
if loca_table.is_a?(Tables::Loca) && !loca_table.parsed?
|
|
394
350
|
maxp = @font.table(Constants::MAXP_TAG)
|
|
395
351
|
if maxp
|
|
396
352
|
loca_table.parse_with_context(head_table.index_to_loc_format,
|
|
@@ -398,17 +354,8 @@ module Fontisan
|
|
|
398
354
|
end
|
|
399
355
|
end
|
|
400
356
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
return nil unless glyph
|
|
404
|
-
|
|
405
|
-
if glyph.respond_to?(:bounding_box)
|
|
406
|
-
glyph.bounding_box
|
|
407
|
-
elsif glyph.respond_to?(:x_min) && glyph.respond_to?(:y_min) &&
|
|
408
|
-
glyph.respond_to?(:x_max) && glyph.respond_to?(:y_max)
|
|
409
|
-
[glyph.x_min, glyph.y_min, glyph.x_max, glyph.y_max]
|
|
410
|
-
end
|
|
411
|
-
end
|
|
357
|
+
glyph = glyf_table.glyph_for(glyph_id, loca_table, head_table)
|
|
358
|
+
glyph&.bounding_box
|
|
412
359
|
end
|
|
413
360
|
|
|
414
361
|
# Extract kerning pairs from GPOS table
|
|
@@ -136,29 +136,27 @@ module Fontisan
|
|
|
136
136
|
# @param seac_data [Hash] seac component data
|
|
137
137
|
# @return [String] CFF CharString bytecode with expanded seac
|
|
138
138
|
def expand_seac(seac_data)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
# For now, return endchar as placeholder
|
|
161
|
-
# TODO: Implement full seac expansion by merging glyph outlines
|
|
139
|
+
return encode_cff_operator(TYPE1_TO_CFF[:endchar]) unless @charstrings
|
|
140
|
+
|
|
141
|
+
base_name = @charstrings.encoding[seac_data[:base]]
|
|
142
|
+
accent_name = @charstrings.encoding[seac_data[:accent]]
|
|
143
|
+
return encode_cff_operator(TYPE1_TO_CFF[:endchar]) unless base_name && accent_name
|
|
144
|
+
|
|
145
|
+
base_cs = @charstrings[base_name]
|
|
146
|
+
accent_cs = @charstrings[accent_name]
|
|
147
|
+
return encode_cff_operator(TYPE1_TO_CFF[:endchar]) unless base_cs && accent_cs
|
|
148
|
+
|
|
149
|
+
base_cff = convert(base_cs)
|
|
150
|
+
accent_cff = convert(accent_cs)
|
|
151
|
+
return base_cff unless accent_cff
|
|
152
|
+
|
|
153
|
+
io = String.new(encoding: Encoding::ASCII_8BIT)
|
|
154
|
+
io << base_cff.delete_suffix("\u000E")
|
|
155
|
+
io << encode_cff_number(seac_data[:adx].to_i) if seac_data[:adx].to_i != 0
|
|
156
|
+
io << encode_cff_number(seac_data[:ady].to_i) if seac_data[:ady].to_i != 0
|
|
157
|
+
io << accent_cff.delete_suffix("\u000E")
|
|
158
|
+
io << encode_cff_operator(TYPE1_TO_CFF[:endchar])
|
|
159
|
+
rescue StandardError
|
|
162
160
|
encode_cff_operator(TYPE1_TO_CFF[:endchar])
|
|
163
161
|
end
|
|
164
162
|
|
|
@@ -99,10 +99,11 @@ module Fontisan
|
|
|
99
99
|
@autohint
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
-
#
|
|
102
|
+
# Expose the options as a Hash. Internal accessor for code that
|
|
103
|
+
# passes options as a plain Hash (e.g., to legacy generators).
|
|
103
104
|
#
|
|
104
105
|
# @return [Hash] Options as hash
|
|
105
|
-
def
|
|
106
|
+
def options_hash
|
|
106
107
|
{
|
|
107
108
|
upm_scale: @upm_scale,
|
|
108
109
|
encoding: @encoding,
|