fontisan 0.2.0 → 0.2.1

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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +270 -131
  3. data/README.adoc +158 -4
  4. data/Rakefile +44 -47
  5. data/lib/fontisan/cli.rb +84 -33
  6. data/lib/fontisan/collection/builder.rb +81 -0
  7. data/lib/fontisan/collection/table_deduplicator.rb +76 -0
  8. data/lib/fontisan/commands/base_command.rb +16 -0
  9. data/lib/fontisan/commands/convert_command.rb +97 -170
  10. data/lib/fontisan/commands/instance_command.rb +71 -80
  11. data/lib/fontisan/commands/validate_command.rb +25 -0
  12. data/lib/fontisan/config/validation_rules.yml +1 -1
  13. data/lib/fontisan/constants.rb +10 -0
  14. data/lib/fontisan/converters/format_converter.rb +150 -1
  15. data/lib/fontisan/converters/outline_converter.rb +80 -18
  16. data/lib/fontisan/converters/woff_writer.rb +1 -1
  17. data/lib/fontisan/font_loader.rb +3 -5
  18. data/lib/fontisan/font_writer.rb +7 -6
  19. data/lib/fontisan/hints/hint_converter.rb +133 -0
  20. data/lib/fontisan/hints/postscript_hint_applier.rb +221 -140
  21. data/lib/fontisan/hints/postscript_hint_extractor.rb +100 -0
  22. data/lib/fontisan/hints/truetype_hint_applier.rb +90 -44
  23. data/lib/fontisan/hints/truetype_hint_extractor.rb +127 -0
  24. data/lib/fontisan/loading_modes.rb +2 -0
  25. data/lib/fontisan/models/font_export.rb +2 -2
  26. data/lib/fontisan/models/hint.rb +173 -1
  27. data/lib/fontisan/models/validation_report.rb +1 -1
  28. data/lib/fontisan/open_type_font.rb +25 -9
  29. data/lib/fontisan/open_type_font_extensions.rb +54 -0
  30. data/lib/fontisan/pipeline/format_detector.rb +249 -0
  31. data/lib/fontisan/pipeline/output_writer.rb +154 -0
  32. data/lib/fontisan/pipeline/strategies/base_strategy.rb +75 -0
  33. data/lib/fontisan/pipeline/strategies/instance_strategy.rb +93 -0
  34. data/lib/fontisan/pipeline/strategies/named_strategy.rb +118 -0
  35. data/lib/fontisan/pipeline/strategies/preserve_strategy.rb +56 -0
  36. data/lib/fontisan/pipeline/transformation_pipeline.rb +411 -0
  37. data/lib/fontisan/pipeline/variation_resolver.rb +165 -0
  38. data/lib/fontisan/tables/cff/charstring.rb +33 -4
  39. data/lib/fontisan/tables/cff/charstring_builder.rb +34 -0
  40. data/lib/fontisan/tables/cff/charstring_parser.rb +237 -0
  41. data/lib/fontisan/tables/cff/charstring_rebuilder.rb +172 -0
  42. data/lib/fontisan/tables/cff/dict_builder.rb +15 -0
  43. data/lib/fontisan/tables/cff/hint_operation_injector.rb +207 -0
  44. data/lib/fontisan/tables/cff/offset_recalculator.rb +70 -0
  45. data/lib/fontisan/tables/cff/private_dict_writer.rb +125 -0
  46. data/lib/fontisan/tables/cff/table_builder.rb +221 -0
  47. data/lib/fontisan/tables/cff.rb +2 -0
  48. data/lib/fontisan/tables/cff2/private_dict_blend_handler.rb +246 -0
  49. data/lib/fontisan/tables/cff2/region_matcher.rb +200 -0
  50. data/lib/fontisan/tables/cff2/table_builder.rb +574 -0
  51. data/lib/fontisan/tables/cff2/table_reader.rb +419 -0
  52. data/lib/fontisan/tables/cff2/variation_data_extractor.rb +212 -0
  53. data/lib/fontisan/tables/cff2.rb +9 -4
  54. data/lib/fontisan/tables/cvar.rb +2 -41
  55. data/lib/fontisan/tables/gvar.rb +2 -41
  56. data/lib/fontisan/true_type_font.rb +24 -9
  57. data/lib/fontisan/true_type_font_extensions.rb +54 -0
  58. data/lib/fontisan/utilities/checksum_calculator.rb +42 -0
  59. data/lib/fontisan/validation/checksum_validator.rb +2 -2
  60. data/lib/fontisan/validation/table_validator.rb +1 -1
  61. data/lib/fontisan/validation/variable_font_validator.rb +218 -0
  62. data/lib/fontisan/variation/converter.rb +120 -13
  63. data/lib/fontisan/variation/instance_writer.rb +341 -0
  64. data/lib/fontisan/variation/tuple_variation_header.rb +51 -0
  65. data/lib/fontisan/variation/variable_svg_generator.rb +268 -0
  66. data/lib/fontisan/variation/variation_preserver.rb +288 -0
  67. data/lib/fontisan/version.rb +1 -1
  68. data/lib/fontisan/version.rb.orig +9 -0
  69. data/lib/fontisan/woff2/glyf_transformer.rb +666 -0
  70. data/lib/fontisan/woff2/hmtx_transformer.rb +164 -0
  71. data/lib/fontisan/woff2_font.rb +475 -470
  72. data/lib/fontisan/woff_font.rb +16 -11
  73. data/lib/fontisan.rb +12 -0
  74. metadata +31 -2
@@ -0,0 +1,268 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "instance_generator"
4
+ require_relative "../converters/svg_generator"
5
+
6
+ module Fontisan
7
+ module Variation
8
+ # Generates SVG fonts from variable fonts at specific coordinates
9
+ #
10
+ # [`VariableSvgGenerator`](lib/fontisan/variation/variable_svg_generator.rb)
11
+ # combines instance generation with SVG conversion to create static SVG
12
+ # fonts from variable fonts at any point in the design space.
13
+ #
14
+ # Process:
15
+ # 1. Accept variable font + axis coordinates
16
+ # 2. Generate static instance using InstanceGenerator
17
+ # 3. Build temporary font from instance tables
18
+ # 4. Delegate to SvgGenerator for SVG creation
19
+ # 5. Return SVG with variation metadata
20
+ #
21
+ # This enables generating SVG fonts at specific weights, widths, or other
22
+ # variation axes without creating intermediate font files.
23
+ #
24
+ # @example Generate SVG at Bold weight
25
+ # generator = VariableSvgGenerator.new(variable_font, { "wght" => 700.0 })
26
+ # svg_result = generator.generate
27
+ # File.write("bold.svg", svg_result[:svg_xml])
28
+ #
29
+ # @example Generate SVG at specific width and weight
30
+ # coords = { "wght" => 700.0, "wdth" => 75.0 }
31
+ # generator = VariableSvgGenerator.new(variable_font, coords)
32
+ # svg_result = generator.generate(pretty_print: true)
33
+ class VariableSvgGenerator
34
+ # @return [TrueTypeFont, OpenTypeFont] Variable font
35
+ attr_reader :font
36
+
37
+ # @return [Hash<String, Float>] Design space coordinates
38
+ attr_reader :coordinates
39
+
40
+ # Initialize generator
41
+ #
42
+ # @param font [TrueTypeFont, OpenTypeFont] Variable font
43
+ # @param coordinates [Hash<String, Float>] Design space coordinates
44
+ # @raise [Error] If font is not a variable font
45
+ def initialize(font, coordinates = {})
46
+ @font = font
47
+ @coordinates = coordinates || {}
48
+
49
+ validate_variable_font!
50
+ end
51
+
52
+ # Generate SVG font at specified coordinates
53
+ #
54
+ # Creates a static instance at the given coordinates and converts
55
+ # it to SVG format. Returns the same format as SvgGenerator for
56
+ # consistency.
57
+ #
58
+ # @param options [Hash] SVG generation options
59
+ # @option options [Boolean] :pretty_print Pretty print XML (default: true)
60
+ # @option options [Array<Integer>] :glyph_ids Specific glyphs (default: all)
61
+ # @option options [Integer] :max_glyphs Maximum glyphs (default: all)
62
+ # @option options [String] :font_id Font ID for SVG
63
+ # @option options [Integer] :default_advance Default advance width
64
+ # @return [Hash] Hash with :svg_xml key containing SVG XML string
65
+ # @raise [Error] If generation fails
66
+ def generate(options = {})
67
+ # Generate static instance tables
68
+ instance_tables = generate_static_instance
69
+
70
+ # Build temporary font from instance tables
71
+ static_font = build_font_from_tables(instance_tables)
72
+
73
+ # Generate SVG using standard generator
74
+ svg_generator = Converters::SvgGenerator.new
75
+ result = svg_generator.convert(static_font, options)
76
+
77
+ # Add variation metadata to result
78
+ result[:variation_metadata] = {
79
+ coordinates: @coordinates,
80
+ source_font: extract_font_name,
81
+ }
82
+
83
+ result
84
+ end
85
+
86
+ # Generate SVG for a named instance
87
+ #
88
+ # @param instance_index [Integer] Index of named instance in fvar
89
+ # @param options [Hash] SVG generation options
90
+ # @return [Hash] Hash with :svg_xml key
91
+ def generate_named_instance(instance_index, options = {})
92
+ instance_generator = InstanceGenerator.new(@font)
93
+ instance_tables = instance_generator.generate_named_instance(instance_index)
94
+
95
+ static_font = build_font_from_tables(instance_tables)
96
+ svg_generator = Converters::SvgGenerator.new
97
+ result = svg_generator.convert(static_font, options)
98
+
99
+ # Add instance metadata
100
+ result[:variation_metadata] = {
101
+ instance_index: instance_index,
102
+ source_font: extract_font_name,
103
+ }
104
+
105
+ result
106
+ end
107
+
108
+ # Get default coordinates for font
109
+ #
110
+ # Returns all axes at their default values.
111
+ #
112
+ # @return [Hash<String, Float>] Default coordinates
113
+ def default_coordinates
114
+ return {} unless @font.has_table?("fvar")
115
+
116
+ fvar = @font.table("fvar")
117
+ return {} unless fvar
118
+
119
+ coords = {}
120
+ fvar.axes.each do |axis|
121
+ coords[axis.axis_tag] = axis.default_value
122
+ end
123
+ coords
124
+ end
125
+
126
+ # Get list of named instances
127
+ #
128
+ # @return [Array<Hash>] Array of instance info
129
+ def named_instances
130
+ return [] unless @font.has_table?("fvar")
131
+
132
+ fvar = @font.table("fvar")
133
+ return [] unless fvar
134
+
135
+ fvar.instances.map.with_index do |instance, index|
136
+ {
137
+ index: index,
138
+ name: instance[:subfamily_name_id],
139
+ coordinates: build_instance_coordinates(instance, fvar.axes),
140
+ }
141
+ end
142
+ end
143
+
144
+ private
145
+
146
+ # Validate that font is a variable font
147
+ #
148
+ # @raise [Error] If not a variable font
149
+ def validate_variable_font!
150
+ unless @font.has_table?("fvar")
151
+ raise Fontisan::Error,
152
+ "Font must be a variable font (missing fvar table)"
153
+ end
154
+
155
+ # Check for variation data
156
+ has_gvar = @font.has_table?("gvar")
157
+ has_cff2 = @font.has_table?("CFF2")
158
+
159
+ unless has_gvar || has_cff2
160
+ raise Fontisan::Error,
161
+ "Variable font must have gvar (TrueType) or CFF2 (PostScript) table"
162
+ end
163
+ end
164
+
165
+ # Generate static instance at current coordinates
166
+ #
167
+ # @return [Hash<String, String>] Instance tables
168
+ def generate_static_instance
169
+ # Use coordinates or defaults if none specified
170
+ coords = @coordinates.empty? ? default_coordinates : @coordinates
171
+
172
+ instance_generator = InstanceGenerator.new(@font, coords)
173
+ instance_generator.generate
174
+ end
175
+
176
+ # Build a font object from instance tables
177
+ #
178
+ # Creates a minimal font object that can be used by SvgGenerator.
179
+ # This is a lightweight wrapper around the table data.
180
+ #
181
+ # @param tables [Hash<String, String>] Font tables
182
+ # @return [Object] Font-like object
183
+ def build_font_from_tables(tables)
184
+ # Create a simple font wrapper that implements the minimal
185
+ # interface needed by SvgGenerator
186
+ InstanceFontWrapper.new(@font, tables)
187
+ end
188
+
189
+ # Extract font name for metadata
190
+ #
191
+ # @return [String] Font name
192
+ def extract_font_name
193
+ name_table = @font.table("name")
194
+ return "Unknown" unless name_table
195
+
196
+ # Try font family name
197
+ family = name_table.font_family.first
198
+ return family if family && !family.empty?
199
+
200
+ "Unknown"
201
+ rescue StandardError
202
+ "Unknown"
203
+ end
204
+
205
+ # Build coordinates from instance
206
+ #
207
+ # @param instance [Hash] Instance data
208
+ # @param axes [Array] Variation axes
209
+ # @return [Hash<String, Float>] Coordinates
210
+ def build_instance_coordinates(instance, axes)
211
+ coords = {}
212
+ instance[:coordinates].each_with_index do |value, index|
213
+ next if index >= axes.length
214
+
215
+ axis = axes[index]
216
+ coords[axis.axis_tag] = value
217
+ end
218
+ coords
219
+ end
220
+
221
+ # Wrapper class for instance font tables
222
+ #
223
+ # Provides minimal interface needed by SvgGenerator while using
224
+ # instance tables instead of original font tables.
225
+ class InstanceFontWrapper
226
+ # @return [Hash<String, String>] Font tables
227
+ attr_reader :table_data
228
+
229
+ # Initialize wrapper
230
+ #
231
+ # @param original_font [Object] Original variable font
232
+ # @param instance_tables [Hash<String, String>] Instance tables
233
+ def initialize(original_font, instance_tables)
234
+ @original_font = original_font
235
+ @table_data = instance_tables
236
+ end
237
+
238
+ # Get table by tag
239
+ #
240
+ # @param tag [String] Table tag
241
+ # @return [Object, nil] Table or nil
242
+ def table(tag)
243
+ # Use instance table if available, otherwise fall back to original
244
+ if @table_data.key?(tag)
245
+ end
246
+ @original_font.table(tag)
247
+ end
248
+
249
+ # Check if table exists
250
+ #
251
+ # @param tag [String] Table tag
252
+ # @return [Boolean] True if table exists
253
+ def has_table?(tag)
254
+ @table_data.key?(tag) || @original_font.has_table?(tag)
255
+ end
256
+
257
+ # Forward other methods to original font
258
+ def method_missing(method, ...)
259
+ @original_font.send(method, ...)
260
+ end
261
+
262
+ def respond_to_missing?(method, include_private = false)
263
+ @original_font.respond_to?(method, include_private) || super
264
+ end
265
+ end
266
+ end
267
+ end
268
+ end
@@ -0,0 +1,288 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "variation_context"
4
+ require_relative "../error"
5
+
6
+ module Fontisan
7
+ module Variation
8
+ # Preserves variation data when converting between compatible font formats
9
+ #
10
+ # [`VariationPreserver`](lib/fontisan/variation/variation_preserver.rb)
11
+ # copies variation tables from source to target font during format
12
+ # conversion. It handles:
13
+ # - Common variation tables (fvar, avar, STAT) - shared by all variable fonts
14
+ # - Format-specific tables (gvar for TTF, CFF2 for OTF)
15
+ # - Metrics variation tables (HVAR, VVAR, MVAR)
16
+ # - Table checksum updates
17
+ # - Validation of table consistency
18
+ #
19
+ # **Use Cases:**
20
+ #
21
+ # 1. **Variable TTF → Variable WOFF**: Preserve all gvar-based variation
22
+ # 2. **Variable OTF → Variable WOFF**: Preserve all CFF2-based variation
23
+ # 3. **Variable TTF → Variable OTF**: Copy common tables (fvar, avar, STAT)
24
+ # but variation data conversion handled by Converter
25
+ # 4. **Variable OTF → Variable TTF**: Copy common tables (fvar, avar, STAT)
26
+ # but variation data conversion handled by Converter
27
+ #
28
+ # **Preserved Tables:**
29
+ #
30
+ # Common (all variable fonts):
31
+ # - fvar (Font Variations)
32
+ # - avar (Axis Variations, optional)
33
+ # - STAT (Style Attributes)
34
+ #
35
+ # TrueType-specific:
36
+ # - gvar (Glyph Variations)
37
+ # - cvar (CVT Variations, optional)
38
+ #
39
+ # CFF2-specific:
40
+ # - CFF2 (with blend operators)
41
+ #
42
+ # Metrics (optional):
43
+ # - HVAR (Horizontal Metrics Variations)
44
+ # - VVAR (Vertical Metrics Variations)
45
+ # - MVAR (Metrics Variations)
46
+ #
47
+ # @example Preserve variation when converting TTF to WOFF
48
+ # preserver = VariationPreserver.new(ttf_font, woff_tables)
49
+ # preserved_tables = preserver.preserve
50
+ #
51
+ # @example Preserve only common tables for outline conversion
52
+ # preserver = VariationPreserver.new(ttf_font, otf_tables,
53
+ # preserve_format_specific: false)
54
+ # preserved_tables = preserver.preserve
55
+ class VariationPreserver
56
+ # Common variation tables present in all variable fonts
57
+ COMMON_TABLES = %w[fvar avar STAT].freeze
58
+
59
+ # TrueType-specific variation tables
60
+ TRUETYPE_TABLES = %w[gvar cvar].freeze
61
+
62
+ # CFF2-specific variation tables
63
+ CFF2_TABLES = %w[CFF2].freeze
64
+
65
+ # Metrics variation tables
66
+ METRICS_TABLES = %w[HVAR VVAR MVAR].freeze
67
+
68
+ # All variation-related tables
69
+ ALL_VARIATION_TABLES = (COMMON_TABLES + TRUETYPE_TABLES +
70
+ CFF2_TABLES + METRICS_TABLES).freeze
71
+
72
+ # Preserve variation data from source to target
73
+ #
74
+ # @param source_font [TrueTypeFont, OpenTypeFont] Variable font
75
+ # @param target_tables [Hash<String, String>] Target font tables
76
+ # @param options [Hash] Preservation options
77
+ # @return [Hash<String, String>] Tables with variation data preserved
78
+ def self.preserve(source_font, target_tables, options = {})
79
+ new(source_font, target_tables, options).preserve
80
+ end
81
+
82
+ # @return [Object] Source font
83
+ attr_reader :source_font
84
+
85
+ # @return [Hash<String, String>] Target tables
86
+ attr_reader :target_tables
87
+
88
+ # @return [Hash] Preservation options
89
+ attr_reader :options
90
+
91
+ # Initialize preserver
92
+ #
93
+ # @param source_font [TrueTypeFont, OpenTypeFont] Variable font
94
+ # @param target_tables [Hash<String, String>] Target font tables
95
+ # @param options [Hash] Preservation options
96
+ # @option options [Boolean] :preserve_format_specific Preserve format-
97
+ # specific variation tables (default: true)
98
+ # @option options [Boolean] :preserve_metrics Preserve metrics variation
99
+ # tables (default: true)
100
+ # @option options [Boolean] :validate Validate table consistency
101
+ # (default: true)
102
+ def initialize(source_font, target_tables, options = {})
103
+ @source_font = source_font
104
+ @target_tables = target_tables.dup
105
+ @options = options
106
+
107
+ validate_source!
108
+ @context = VariationContext.new(source_font)
109
+ end
110
+
111
+ # Preserve variation tables
112
+ #
113
+ # @return [Hash<String, String>] Target tables with variation preserved
114
+ def preserve
115
+ # Copy common variation tables (fvar, avar, STAT)
116
+ copy_common_tables
117
+
118
+ # Copy format-specific variation tables if requested
119
+ if preserve_format_specific?
120
+ copy_format_specific_tables
121
+ end
122
+
123
+ # Copy metrics variation tables if requested
124
+ copy_metrics_tables if preserve_metrics?
125
+
126
+ # Validate consistency if requested
127
+ validate_consistency if validate?
128
+
129
+ @target_tables
130
+ end
131
+
132
+ # Check if source font is a variable font
133
+ #
134
+ # @return [Boolean] True if source has fvar table
135
+ def variable_font?
136
+ @context.variable_font?
137
+ end
138
+
139
+ # Get variation type of source font
140
+ #
141
+ # @return [Symbol, nil] :truetype, :cff2, or nil
142
+ def variation_type
143
+ @context.variation_type
144
+ end
145
+
146
+ private
147
+
148
+ # Validate source font
149
+ #
150
+ # @raise [ArgumentError] If source is invalid
151
+ def validate_source!
152
+ raise ArgumentError, "Source font cannot be nil" if @source_font.nil?
153
+
154
+ unless @source_font.respond_to?(:has_table?) &&
155
+ @source_font.respond_to?(:table_data)
156
+ raise ArgumentError,
157
+ "Source font must respond to :has_table? and :table_data"
158
+ end
159
+
160
+ raise ArgumentError, "Target tables cannot be nil" if @target_tables.nil?
161
+
162
+ unless @target_tables.is_a?(Hash)
163
+ raise ArgumentError,
164
+ "Target tables must be a Hash, got: #{@target_tables.class}"
165
+ end
166
+ end
167
+
168
+ # Copy common variation tables (fvar, avar, STAT)
169
+ #
170
+ # These tables are independent of outline format and can always be copied
171
+ def copy_common_tables
172
+ COMMON_TABLES.each do |tag|
173
+ copy_table(tag) if @source_font.has_table?(tag)
174
+ end
175
+ end
176
+
177
+ # Copy format-specific variation tables
178
+ #
179
+ # For TrueType: gvar, cvar
180
+ # For CFF2: CFF2 table
181
+ def copy_format_specific_tables
182
+ case variation_type
183
+ when :truetype
184
+ copy_truetype_variation_tables
185
+ when :postscript
186
+ copy_cff2_variation_tables
187
+ end
188
+ end
189
+
190
+ # Copy TrueType variation tables
191
+ def copy_truetype_variation_tables
192
+ TRUETYPE_TABLES.each do |tag|
193
+ copy_table(tag) if @source_font.has_table?(tag)
194
+ end
195
+ end
196
+
197
+ # Copy CFF2 variation tables
198
+ def copy_cff2_variation_tables
199
+ # CFF2 table contains both outlines and variation data
200
+ # Only copy if target doesn't already have CFF2 and source has it
201
+ return unless @source_font.has_table?("CFF2")
202
+ return if @target_tables.key?("CFF2")
203
+
204
+ copy_table("CFF2")
205
+ end
206
+
207
+ # Copy metrics variation tables (HVAR, VVAR, MVAR)
208
+ def copy_metrics_tables
209
+ METRICS_TABLES.each do |tag|
210
+ copy_table(tag) if @source_font.has_table?(tag)
211
+ end
212
+ end
213
+
214
+ # Copy a single table from source to target
215
+ #
216
+ # @param tag [String] Table tag
217
+ def copy_table(tag)
218
+ return unless @source_font.has_table?(tag)
219
+
220
+ table_data = @source_font.table_data[tag]
221
+ return unless table_data
222
+
223
+ @target_tables[tag] = table_data.dup
224
+ end
225
+
226
+ # Validate table consistency
227
+ #
228
+ # Ensures that copied variation tables are consistent with target font
229
+ # @raise [Error] If validation fails
230
+ def validate_consistency
231
+ # Must have fvar if we're preserving variations
232
+ unless @target_tables.key?("fvar")
233
+ raise Fontisan::Error,
234
+ "Cannot preserve variations: fvar table missing"
235
+ end
236
+
237
+ # If we have gvar, we must have glyf (TrueType outlines)
238
+ if @target_tables.key?("gvar") && !@target_tables.key?("glyf")
239
+ raise Fontisan::Error,
240
+ "Invalid variation preservation: gvar present without glyf"
241
+ end
242
+
243
+ # If we have CFF2, we shouldn't have glyf (CFF2 has CFF outlines)
244
+ # Check both source and target to catch conflicts
245
+ has_cff2 = @target_tables.key?("CFF2") ||
246
+ (@source_font.has_table?("CFF2") && preserve_format_specific?)
247
+ if has_cff2 && @target_tables.key?("glyf")
248
+ raise Fontisan::Error,
249
+ "Invalid variation preservation: CFF2 and glyf both present"
250
+ end
251
+
252
+ # Metrics variation tables require fvar
253
+ if metrics_tables_present? && !@target_tables.key?("fvar")
254
+ raise Fontisan::Error,
255
+ "Metrics variation tables require fvar table"
256
+ end
257
+ end
258
+
259
+ # Check if any metrics variation tables are present
260
+ #
261
+ # @return [Boolean] True if HVAR, VVAR, or MVAR present
262
+ def metrics_tables_present?
263
+ METRICS_TABLES.any? { |tag| @target_tables.key?(tag) }
264
+ end
265
+
266
+ # Get preserve_format_specific option
267
+ #
268
+ # @return [Boolean] True if format-specific tables should be preserved
269
+ def preserve_format_specific?
270
+ @options.fetch(:preserve_format_specific, true)
271
+ end
272
+
273
+ # Get preserve_metrics option
274
+ #
275
+ # @return [Boolean] True if metrics tables should be preserved
276
+ def preserve_metrics?
277
+ @options.fetch(:preserve_metrics, true)
278
+ end
279
+
280
+ # Get validate option
281
+ #
282
+ # @return [Boolean] True if consistency should be validated
283
+ def validate?
284
+ @options.fetch(:validate, true)
285
+ end
286
+ end
287
+ end
288
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fontisan
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1"
5
5
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fontisan
4
+ <<<<<<< HEAD
5
+ VERSION = "2.0.0"
6
+ =======
7
+ VERSION = "0.2.0"
8
+ >>>>>>> 4bcec10 (fix: update loading modes)
9
+ end