fontisan 0.1.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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +672 -69
- data/Gemfile +1 -0
- data/LICENSE +5 -1
- data/README.adoc +1477 -297
- data/Rakefile +63 -41
- data/benchmark/variation_quick_bench.rb +47 -0
- data/docs/EXTRACT_TTC_MIGRATION.md +549 -0
- data/fontisan.gemspec +4 -1
- data/lib/fontisan/binary/base_record.rb +22 -1
- data/lib/fontisan/cli.rb +364 -4
- data/lib/fontisan/collection/builder.rb +341 -0
- data/lib/fontisan/collection/offset_calculator.rb +227 -0
- data/lib/fontisan/collection/table_analyzer.rb +204 -0
- data/lib/fontisan/collection/table_deduplicator.rb +317 -0
- data/lib/fontisan/collection/writer.rb +306 -0
- data/lib/fontisan/commands/base_command.rb +24 -1
- data/lib/fontisan/commands/convert_command.rb +218 -0
- data/lib/fontisan/commands/export_command.rb +161 -0
- data/lib/fontisan/commands/info_command.rb +40 -6
- data/lib/fontisan/commands/instance_command.rb +286 -0
- data/lib/fontisan/commands/ls_command.rb +113 -0
- data/lib/fontisan/commands/pack_command.rb +241 -0
- data/lib/fontisan/commands/subset_command.rb +245 -0
- data/lib/fontisan/commands/unpack_command.rb +338 -0
- data/lib/fontisan/commands/validate_command.rb +203 -0
- data/lib/fontisan/commands/variable_command.rb +30 -1
- data/lib/fontisan/config/collection_settings.yml +56 -0
- data/lib/fontisan/config/conversion_matrix.yml +212 -0
- data/lib/fontisan/config/export_settings.yml +66 -0
- data/lib/fontisan/config/subset_profiles.yml +100 -0
- data/lib/fontisan/config/svg_settings.yml +60 -0
- data/lib/fontisan/config/validation_rules.yml +149 -0
- data/lib/fontisan/config/variable_settings.yml +99 -0
- data/lib/fontisan/config/woff2_settings.yml +77 -0
- data/lib/fontisan/constants.rb +79 -0
- data/lib/fontisan/converters/conversion_strategy.rb +96 -0
- data/lib/fontisan/converters/format_converter.rb +408 -0
- data/lib/fontisan/converters/outline_converter.rb +998 -0
- data/lib/fontisan/converters/svg_generator.rb +244 -0
- data/lib/fontisan/converters/table_copier.rb +117 -0
- data/lib/fontisan/converters/woff2_encoder.rb +416 -0
- data/lib/fontisan/converters/woff_writer.rb +391 -0
- data/lib/fontisan/error.rb +203 -0
- data/lib/fontisan/export/exporter.rb +262 -0
- data/lib/fontisan/export/table_serializer.rb +255 -0
- data/lib/fontisan/export/transformers/font_to_ttx.rb +172 -0
- data/lib/fontisan/export/transformers/head_transformer.rb +96 -0
- data/lib/fontisan/export/transformers/hhea_transformer.rb +59 -0
- data/lib/fontisan/export/transformers/maxp_transformer.rb +63 -0
- data/lib/fontisan/export/transformers/name_transformer.rb +63 -0
- data/lib/fontisan/export/transformers/os2_transformer.rb +121 -0
- data/lib/fontisan/export/transformers/post_transformer.rb +51 -0
- data/lib/fontisan/export/ttx_generator.rb +527 -0
- data/lib/fontisan/export/ttx_parser.rb +300 -0
- data/lib/fontisan/font_loader.rb +122 -15
- data/lib/fontisan/font_writer.rb +302 -0
- data/lib/fontisan/formatters/text_formatter.rb +102 -0
- data/lib/fontisan/glyph_accessor.rb +503 -0
- data/lib/fontisan/hints/hint_converter.rb +310 -0
- data/lib/fontisan/hints/postscript_hint_applier.rb +266 -0
- data/lib/fontisan/hints/postscript_hint_extractor.rb +354 -0
- data/lib/fontisan/hints/truetype_hint_applier.rb +117 -0
- data/lib/fontisan/hints/truetype_hint_extractor.rb +289 -0
- data/lib/fontisan/loading_modes.rb +115 -0
- data/lib/fontisan/metrics_calculator.rb +277 -0
- data/lib/fontisan/models/collection_font_summary.rb +52 -0
- data/lib/fontisan/models/collection_info.rb +76 -0
- data/lib/fontisan/models/collection_list_info.rb +37 -0
- data/lib/fontisan/models/font_export.rb +158 -0
- data/lib/fontisan/models/font_summary.rb +48 -0
- data/lib/fontisan/models/glyph_outline.rb +343 -0
- data/lib/fontisan/models/hint.rb +405 -0
- data/lib/fontisan/models/outline.rb +664 -0
- data/lib/fontisan/models/table_sharing_info.rb +40 -0
- data/lib/fontisan/models/ttx/glyph_order.rb +31 -0
- data/lib/fontisan/models/ttx/tables/binary_table.rb +67 -0
- data/lib/fontisan/models/ttx/tables/head_table.rb +74 -0
- data/lib/fontisan/models/ttx/tables/hhea_table.rb +74 -0
- data/lib/fontisan/models/ttx/tables/maxp_table.rb +55 -0
- data/lib/fontisan/models/ttx/tables/name_table.rb +45 -0
- data/lib/fontisan/models/ttx/tables/os2_table.rb +157 -0
- data/lib/fontisan/models/ttx/tables/post_table.rb +50 -0
- data/lib/fontisan/models/ttx/ttfont.rb +49 -0
- data/lib/fontisan/models/validation_report.rb +203 -0
- data/lib/fontisan/open_type_collection.rb +156 -2
- data/lib/fontisan/open_type_font.rb +321 -19
- data/lib/fontisan/open_type_font_extensions.rb +54 -0
- data/lib/fontisan/optimizers/charstring_rewriter.rb +161 -0
- data/lib/fontisan/optimizers/pattern_analyzer.rb +308 -0
- data/lib/fontisan/optimizers/stack_tracker.rb +246 -0
- data/lib/fontisan/optimizers/subroutine_builder.rb +134 -0
- data/lib/fontisan/optimizers/subroutine_generator.rb +207 -0
- data/lib/fontisan/optimizers/subroutine_optimizer.rb +107 -0
- data/lib/fontisan/outline_extractor.rb +423 -0
- data/lib/fontisan/pipeline/format_detector.rb +249 -0
- data/lib/fontisan/pipeline/output_writer.rb +154 -0
- data/lib/fontisan/pipeline/strategies/base_strategy.rb +75 -0
- data/lib/fontisan/pipeline/strategies/instance_strategy.rb +93 -0
- data/lib/fontisan/pipeline/strategies/named_strategy.rb +118 -0
- data/lib/fontisan/pipeline/strategies/preserve_strategy.rb +56 -0
- data/lib/fontisan/pipeline/transformation_pipeline.rb +411 -0
- data/lib/fontisan/pipeline/variation_resolver.rb +165 -0
- data/lib/fontisan/subset/builder.rb +268 -0
- data/lib/fontisan/subset/glyph_mapping.rb +215 -0
- data/lib/fontisan/subset/options.rb +142 -0
- data/lib/fontisan/subset/profile.rb +152 -0
- data/lib/fontisan/subset/table_subsetter.rb +461 -0
- data/lib/fontisan/svg/font_face_generator.rb +278 -0
- data/lib/fontisan/svg/font_generator.rb +264 -0
- data/lib/fontisan/svg/glyph_generator.rb +168 -0
- data/lib/fontisan/svg/view_box_calculator.rb +137 -0
- data/lib/fontisan/tables/cff/cff_glyph.rb +176 -0
- data/lib/fontisan/tables/cff/charset.rb +282 -0
- data/lib/fontisan/tables/cff/charstring.rb +934 -0
- data/lib/fontisan/tables/cff/charstring_builder.rb +356 -0
- data/lib/fontisan/tables/cff/charstring_parser.rb +237 -0
- data/lib/fontisan/tables/cff/charstring_rebuilder.rb +172 -0
- data/lib/fontisan/tables/cff/charstrings_index.rb +162 -0
- data/lib/fontisan/tables/cff/dict.rb +351 -0
- data/lib/fontisan/tables/cff/dict_builder.rb +257 -0
- data/lib/fontisan/tables/cff/encoding.rb +274 -0
- data/lib/fontisan/tables/cff/header.rb +102 -0
- data/lib/fontisan/tables/cff/hint_operation_injector.rb +207 -0
- data/lib/fontisan/tables/cff/index.rb +237 -0
- data/lib/fontisan/tables/cff/index_builder.rb +170 -0
- data/lib/fontisan/tables/cff/offset_recalculator.rb +70 -0
- data/lib/fontisan/tables/cff/private_dict.rb +284 -0
- data/lib/fontisan/tables/cff/private_dict_writer.rb +125 -0
- data/lib/fontisan/tables/cff/table_builder.rb +221 -0
- data/lib/fontisan/tables/cff/top_dict.rb +236 -0
- data/lib/fontisan/tables/cff.rb +489 -0
- data/lib/fontisan/tables/cff2/blend_operator.rb +240 -0
- data/lib/fontisan/tables/cff2/charstring_parser.rb +591 -0
- data/lib/fontisan/tables/cff2/operand_stack.rb +232 -0
- data/lib/fontisan/tables/cff2/private_dict_blend_handler.rb +246 -0
- data/lib/fontisan/tables/cff2/region_matcher.rb +200 -0
- data/lib/fontisan/tables/cff2/table_builder.rb +574 -0
- data/lib/fontisan/tables/cff2/table_reader.rb +419 -0
- data/lib/fontisan/tables/cff2/variation_data_extractor.rb +212 -0
- data/lib/fontisan/tables/cff2.rb +346 -0
- data/lib/fontisan/tables/cvar.rb +203 -0
- data/lib/fontisan/tables/fvar.rb +2 -2
- data/lib/fontisan/tables/glyf/compound_glyph.rb +483 -0
- data/lib/fontisan/tables/glyf/compound_glyph_resolver.rb +136 -0
- data/lib/fontisan/tables/glyf/curve_converter.rb +343 -0
- data/lib/fontisan/tables/glyf/glyph_builder.rb +450 -0
- data/lib/fontisan/tables/glyf/simple_glyph.rb +382 -0
- data/lib/fontisan/tables/glyf.rb +235 -0
- data/lib/fontisan/tables/gvar.rb +231 -0
- data/lib/fontisan/tables/hhea.rb +124 -0
- data/lib/fontisan/tables/hmtx.rb +287 -0
- data/lib/fontisan/tables/hvar.rb +191 -0
- data/lib/fontisan/tables/loca.rb +322 -0
- data/lib/fontisan/tables/maxp.rb +192 -0
- data/lib/fontisan/tables/mvar.rb +185 -0
- data/lib/fontisan/tables/name.rb +99 -30
- data/lib/fontisan/tables/variation_common.rb +346 -0
- data/lib/fontisan/tables/vvar.rb +234 -0
- data/lib/fontisan/true_type_collection.rb +156 -2
- data/lib/fontisan/true_type_font.rb +321 -20
- data/lib/fontisan/true_type_font_extensions.rb +54 -0
- data/lib/fontisan/utilities/brotli_wrapper.rb +159 -0
- data/lib/fontisan/utilities/checksum_calculator.rb +60 -0
- data/lib/fontisan/utils/thread_pool.rb +134 -0
- data/lib/fontisan/validation/checksum_validator.rb +170 -0
- data/lib/fontisan/validation/consistency_validator.rb +197 -0
- data/lib/fontisan/validation/structure_validator.rb +198 -0
- data/lib/fontisan/validation/table_validator.rb +158 -0
- data/lib/fontisan/validation/validator.rb +152 -0
- data/lib/fontisan/validation/variable_font_validator.rb +218 -0
- data/lib/fontisan/variable/axis_normalizer.rb +215 -0
- data/lib/fontisan/variable/delta_applicator.rb +313 -0
- data/lib/fontisan/variable/glyph_delta_processor.rb +218 -0
- data/lib/fontisan/variable/instancer.rb +344 -0
- data/lib/fontisan/variable/metric_delta_processor.rb +282 -0
- data/lib/fontisan/variable/region_matcher.rb +208 -0
- data/lib/fontisan/variable/static_font_builder.rb +213 -0
- data/lib/fontisan/variable/table_updater.rb +219 -0
- data/lib/fontisan/variation/blend_applier.rb +199 -0
- data/lib/fontisan/variation/cache.rb +298 -0
- data/lib/fontisan/variation/cache_key_builder.rb +162 -0
- data/lib/fontisan/variation/converter.rb +375 -0
- data/lib/fontisan/variation/data_extractor.rb +86 -0
- data/lib/fontisan/variation/delta_applier.rb +266 -0
- data/lib/fontisan/variation/delta_parser.rb +228 -0
- data/lib/fontisan/variation/inspector.rb +275 -0
- data/lib/fontisan/variation/instance_generator.rb +273 -0
- data/lib/fontisan/variation/instance_writer.rb +341 -0
- data/lib/fontisan/variation/interpolator.rb +231 -0
- data/lib/fontisan/variation/metrics_adjuster.rb +318 -0
- data/lib/fontisan/variation/optimizer.rb +418 -0
- data/lib/fontisan/variation/parallel_generator.rb +150 -0
- data/lib/fontisan/variation/region_matcher.rb +221 -0
- data/lib/fontisan/variation/subsetter.rb +463 -0
- data/lib/fontisan/variation/table_accessor.rb +105 -0
- data/lib/fontisan/variation/tuple_variation_header.rb +51 -0
- data/lib/fontisan/variation/validator.rb +345 -0
- data/lib/fontisan/variation/variable_svg_generator.rb +268 -0
- data/lib/fontisan/variation/variation_context.rb +211 -0
- data/lib/fontisan/variation/variation_preserver.rb +288 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/version.rb.orig +9 -0
- data/lib/fontisan/woff2/directory.rb +257 -0
- data/lib/fontisan/woff2/glyf_transformer.rb +666 -0
- data/lib/fontisan/woff2/header.rb +101 -0
- data/lib/fontisan/woff2/hmtx_transformer.rb +164 -0
- data/lib/fontisan/woff2/table_transformer.rb +163 -0
- data/lib/fontisan/woff2_font.rb +717 -0
- data/lib/fontisan/woff_font.rb +488 -0
- data/lib/fontisan.rb +132 -0
- data/scripts/compare_stack_aware.rb +187 -0
- data/scripts/measure_optimization.rb +141 -0
- metadata +234 -4
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../font_writer"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Pipeline
|
|
7
|
+
# Handles writing font tables to various output formats
|
|
8
|
+
#
|
|
9
|
+
# This class abstracts the complexity of writing different font formats:
|
|
10
|
+
# - SFNT formats (TTF, OTF) via FontWriter
|
|
11
|
+
# - WOFF via WoffWriter
|
|
12
|
+
# - WOFF2 via Woff2Encoder
|
|
13
|
+
#
|
|
14
|
+
# Single Responsibility: Coordinate output writing for different formats
|
|
15
|
+
#
|
|
16
|
+
# @example Write TTF font
|
|
17
|
+
# writer = OutputWriter.new("output.ttf", :ttf)
|
|
18
|
+
# writer.write(tables)
|
|
19
|
+
#
|
|
20
|
+
# @example Write OTF font
|
|
21
|
+
# writer = OutputWriter.new("output.otf", :otf)
|
|
22
|
+
# writer.write(tables)
|
|
23
|
+
class OutputWriter
|
|
24
|
+
# @return [String] Output file path
|
|
25
|
+
attr_reader :output_path
|
|
26
|
+
|
|
27
|
+
# @return [Symbol] Target format
|
|
28
|
+
attr_reader :format
|
|
29
|
+
|
|
30
|
+
# @return [Hash] Writing options
|
|
31
|
+
attr_reader :options
|
|
32
|
+
|
|
33
|
+
# Initialize output writer
|
|
34
|
+
#
|
|
35
|
+
# @param output_path [String] Path to write output
|
|
36
|
+
# @param format [Symbol] Target format (:ttf, :otf, :woff, :woff2)
|
|
37
|
+
# @param options [Hash] Writing options
|
|
38
|
+
def initialize(output_path, format, options = {})
|
|
39
|
+
@output_path = output_path
|
|
40
|
+
@format = format
|
|
41
|
+
@options = options
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Write font tables to output file
|
|
45
|
+
#
|
|
46
|
+
# @param tables [Hash<String, String>, Hash] Font tables (tag => binary data) or special format result
|
|
47
|
+
# @return [Integer] Number of bytes written
|
|
48
|
+
# @raise [ArgumentError] If format is unsupported
|
|
49
|
+
def write(tables)
|
|
50
|
+
case @format
|
|
51
|
+
when :ttf, :otf
|
|
52
|
+
write_sfnt(tables)
|
|
53
|
+
when :woff
|
|
54
|
+
write_woff(tables)
|
|
55
|
+
when :woff2
|
|
56
|
+
write_woff2(tables)
|
|
57
|
+
when :svg
|
|
58
|
+
write_svg(tables)
|
|
59
|
+
else
|
|
60
|
+
raise ArgumentError, "Unsupported output format: #{@format}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
# Write SVG format
|
|
67
|
+
#
|
|
68
|
+
# @param result [Hash] Result with :svg_xml key
|
|
69
|
+
# @return [Integer] Number of bytes written
|
|
70
|
+
def write_svg(result)
|
|
71
|
+
svg_xml = result[:svg_xml] || result["svg_xml"]
|
|
72
|
+
raise ArgumentError, "SVG result must contain :svg_xml key" unless svg_xml
|
|
73
|
+
|
|
74
|
+
File.write(@output_path, svg_xml)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Write SFNT format (TTF or OTF)
|
|
78
|
+
#
|
|
79
|
+
# @param tables [Hash<String, String>] Font tables
|
|
80
|
+
# @return [Integer] Number of bytes written
|
|
81
|
+
def write_sfnt(tables)
|
|
82
|
+
sfnt_version = determine_sfnt_version
|
|
83
|
+
FontWriter.write_to_file(tables, @output_path, sfnt_version: sfnt_version)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Write WOFF format
|
|
87
|
+
#
|
|
88
|
+
# @param tables [Hash<String, String>] Font tables
|
|
89
|
+
# @return [Integer] Number of bytes written
|
|
90
|
+
def write_woff(tables)
|
|
91
|
+
require_relative "../converters/woff_writer"
|
|
92
|
+
|
|
93
|
+
writer = Converters::WoffWriter.new
|
|
94
|
+
font = build_font_from_tables(tables)
|
|
95
|
+
result = writer.convert(font, @options)
|
|
96
|
+
|
|
97
|
+
File.binwrite(@output_path, result[:woff_data])
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Write WOFF2 format
|
|
101
|
+
#
|
|
102
|
+
# @param tables [Hash<String, String>] Font tables
|
|
103
|
+
# @return [Integer] Number of bytes written
|
|
104
|
+
def write_woff2(tables)
|
|
105
|
+
require_relative "../converters/woff2_encoder"
|
|
106
|
+
|
|
107
|
+
encoder = Converters::Woff2Encoder.new
|
|
108
|
+
font = build_font_from_tables(tables)
|
|
109
|
+
result = encoder.convert(font, @options)
|
|
110
|
+
|
|
111
|
+
File.binwrite(@output_path, result[:woff2_binary])
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Determine SFNT version based on format and tables
|
|
115
|
+
#
|
|
116
|
+
# @return [Integer] SFNT version (0x00010000 for TTF, 0x4F54544F for OTF)
|
|
117
|
+
def determine_sfnt_version
|
|
118
|
+
case @format
|
|
119
|
+
when :ttf, :woff, :woff2 then 0x00010000
|
|
120
|
+
when :otf then 0x4F54544F # 'OTTO'
|
|
121
|
+
else raise ArgumentError, "Unsupported format: #{@format}"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Build font object from tables
|
|
126
|
+
#
|
|
127
|
+
# Helper to create font object from tables for converters that need it.
|
|
128
|
+
#
|
|
129
|
+
# @param tables [Hash<String, String>] Font tables
|
|
130
|
+
# @return [Font] Font object
|
|
131
|
+
def build_font_from_tables(tables)
|
|
132
|
+
# Detect font type from tables
|
|
133
|
+
has_cff = tables.key?("CFF ") || tables.key?("CFF2")
|
|
134
|
+
has_glyf = tables.key?("glyf")
|
|
135
|
+
|
|
136
|
+
if has_cff
|
|
137
|
+
OpenTypeFont.from_tables(tables)
|
|
138
|
+
elsif has_glyf
|
|
139
|
+
TrueTypeFont.from_tables(tables)
|
|
140
|
+
else
|
|
141
|
+
# Default based on format
|
|
142
|
+
case @format
|
|
143
|
+
when :ttf, :woff, :woff2
|
|
144
|
+
TrueTypeFont.from_tables(tables)
|
|
145
|
+
when :otf
|
|
146
|
+
OpenTypeFont.from_tables(tables)
|
|
147
|
+
else
|
|
148
|
+
raise ArgumentError, "Cannot determine font type for format: #{@format}"
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Pipeline
|
|
5
|
+
module Strategies
|
|
6
|
+
# Base class for variation resolution strategies
|
|
7
|
+
#
|
|
8
|
+
# This abstract class defines the interface that all variation resolution
|
|
9
|
+
# strategies must implement. It follows the Strategy pattern to allow
|
|
10
|
+
# different approaches to handling variable font data during conversion.
|
|
11
|
+
#
|
|
12
|
+
# Subclasses must implement:
|
|
13
|
+
# - resolve(font): Process the font and return tables
|
|
14
|
+
# - preserves_variation?: Indicate if variation data is preserved
|
|
15
|
+
# - strategy_name: Return the strategy identifier
|
|
16
|
+
#
|
|
17
|
+
# @example Implementing a strategy
|
|
18
|
+
# class MyStrategy < BaseStrategy
|
|
19
|
+
# def resolve(font)
|
|
20
|
+
# # Implementation
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# def preserves_variation?
|
|
24
|
+
# false
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# def strategy_name
|
|
28
|
+
# :my_strategy
|
|
29
|
+
# end
|
|
30
|
+
# end
|
|
31
|
+
class BaseStrategy
|
|
32
|
+
# @return [Hash] Strategy options
|
|
33
|
+
attr_reader :options
|
|
34
|
+
|
|
35
|
+
# Initialize strategy with options
|
|
36
|
+
#
|
|
37
|
+
# @param options [Hash] Strategy-specific options
|
|
38
|
+
def initialize(options = {})
|
|
39
|
+
@options = options
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Resolve variation data
|
|
43
|
+
#
|
|
44
|
+
# This method must be implemented by subclasses to process the font
|
|
45
|
+
# and return the appropriate tables based on the strategy.
|
|
46
|
+
#
|
|
47
|
+
# @param font [TrueTypeFont, OpenTypeFont] Font to process
|
|
48
|
+
# @return [Hash<String, String>] Map of table tags to binary data
|
|
49
|
+
# @raise [NotImplementedError] If not implemented by subclass
|
|
50
|
+
def resolve(font)
|
|
51
|
+
raise NotImplementedError,
|
|
52
|
+
"#{self.class.name} must implement #resolve"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Check if strategy preserves variation data
|
|
56
|
+
#
|
|
57
|
+
# @return [Boolean] True if variation data is preserved
|
|
58
|
+
# @raise [NotImplementedError] If not implemented by subclass
|
|
59
|
+
def preserves_variation?
|
|
60
|
+
raise NotImplementedError,
|
|
61
|
+
"#{self.class.name} must implement #preserves_variation?"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Get strategy name
|
|
65
|
+
#
|
|
66
|
+
# @return [Symbol] Strategy identifier
|
|
67
|
+
# @raise [NotImplementedError] If not implemented by subclass
|
|
68
|
+
def strategy_name
|
|
69
|
+
raise NotImplementedError,
|
|
70
|
+
"#{self.class.name} must implement #strategy_name"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_strategy"
|
|
4
|
+
require_relative "../../variation/instance_generator"
|
|
5
|
+
require_relative "../../variation/variation_context"
|
|
6
|
+
|
|
7
|
+
module Fontisan
|
|
8
|
+
module Pipeline
|
|
9
|
+
module Strategies
|
|
10
|
+
# Strategy for generating static instances from variable fonts
|
|
11
|
+
#
|
|
12
|
+
# This strategy creates a static font instance at specific design space
|
|
13
|
+
# coordinates by applying variation deltas and removing variation tables.
|
|
14
|
+
# It's used for:
|
|
15
|
+
# - Variable TTF → Static TTF at specific weight
|
|
16
|
+
# - Variable OTF → Static OTF at specific coordinates
|
|
17
|
+
# - Variable → Static for any format conversion
|
|
18
|
+
#
|
|
19
|
+
# The strategy uses the InstanceGenerator to:
|
|
20
|
+
# 1. Apply variation deltas (gvar or CFF2 blend)
|
|
21
|
+
# 2. Apply metrics variations (HVAR, VVAR, MVAR)
|
|
22
|
+
# 3. Remove variation tables (fvar, gvar, CFF2, avar, etc.)
|
|
23
|
+
#
|
|
24
|
+
# If no coordinates are provided, uses default coordinates (axis default values).
|
|
25
|
+
#
|
|
26
|
+
# @example Generate instance at specific weight
|
|
27
|
+
# strategy = InstanceStrategy.new(coordinates: { "wght" => 700.0 })
|
|
28
|
+
# tables = strategy.resolve(variable_font)
|
|
29
|
+
# # tables has no variation tables
|
|
30
|
+
#
|
|
31
|
+
# @example Generate instance at default coordinates
|
|
32
|
+
# strategy = InstanceStrategy.new
|
|
33
|
+
# tables = strategy.resolve(variable_font)
|
|
34
|
+
class InstanceStrategy < BaseStrategy
|
|
35
|
+
# @return [Hash<String, Float>] Design space coordinates
|
|
36
|
+
attr_reader :coordinates
|
|
37
|
+
|
|
38
|
+
# Initialize strategy with coordinates
|
|
39
|
+
#
|
|
40
|
+
# @param options [Hash] Strategy options
|
|
41
|
+
# @option options [Hash<String, Float>] :coordinates Design space coordinates
|
|
42
|
+
# (axis tag => value). If not provided, uses default coordinates.
|
|
43
|
+
def initialize(options = {})
|
|
44
|
+
super
|
|
45
|
+
@coordinates = options[:coordinates] || {}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Resolve by generating static instance
|
|
49
|
+
#
|
|
50
|
+
# Creates a static font instance at the specified coordinates using
|
|
51
|
+
# the InstanceGenerator. If coordinates are not provided, uses the
|
|
52
|
+
# default coordinates from the font's axes.
|
|
53
|
+
#
|
|
54
|
+
# @param font [TrueTypeFont, OpenTypeFont] Variable font
|
|
55
|
+
# @return [Hash<String, String>] Static font tables
|
|
56
|
+
# @raise [Variation::InvalidCoordinatesError] If coordinates out of range
|
|
57
|
+
def resolve(font)
|
|
58
|
+
# Validate coordinates if provided
|
|
59
|
+
validate_coordinates(font) unless @coordinates.empty?
|
|
60
|
+
|
|
61
|
+
# Use InstanceGenerator to create static instance
|
|
62
|
+
generator = Variation::InstanceGenerator.new(font, @coordinates)
|
|
63
|
+
generator.generate
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Check if strategy preserves variation data
|
|
67
|
+
#
|
|
68
|
+
# @return [Boolean] Always false for this strategy
|
|
69
|
+
def preserves_variation?
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Get strategy name
|
|
74
|
+
#
|
|
75
|
+
# @return [Symbol] :instance
|
|
76
|
+
def strategy_name
|
|
77
|
+
:instance
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
# Validate coordinates against font axes
|
|
83
|
+
#
|
|
84
|
+
# @param font [TrueTypeFont, OpenTypeFont] Variable font
|
|
85
|
+
# @raise [Variation::InvalidCoordinatesError] If invalid
|
|
86
|
+
def validate_coordinates(font)
|
|
87
|
+
context = Variation::VariationContext.new(font)
|
|
88
|
+
context.validate_coordinates(@coordinates)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_strategy"
|
|
4
|
+
require_relative "instance_strategy"
|
|
5
|
+
require_relative "../../variation/variation_context"
|
|
6
|
+
|
|
7
|
+
module Fontisan
|
|
8
|
+
module Pipeline
|
|
9
|
+
module Strategies
|
|
10
|
+
# Strategy for generating instances from named instances
|
|
11
|
+
#
|
|
12
|
+
# This strategy creates a static font instance using coordinates from
|
|
13
|
+
# a named instance defined in the fvar table. It extracts the coordinates
|
|
14
|
+
# from the specified instance and delegates to InstanceStrategy for
|
|
15
|
+
# actual generation.
|
|
16
|
+
#
|
|
17
|
+
# Named instances are predefined design space coordinates stored in the
|
|
18
|
+
# fvar table, typically representing common styles like "Bold", "Light",
|
|
19
|
+
# "Condensed", etc.
|
|
20
|
+
#
|
|
21
|
+
# @example Generate "Bold" instance
|
|
22
|
+
# strategy = NamedStrategy.new(instance_index: 0)
|
|
23
|
+
# tables = strategy.resolve(variable_font)
|
|
24
|
+
#
|
|
25
|
+
# @example Use specific named instance
|
|
26
|
+
# # Find instance by name first, then use index
|
|
27
|
+
# fvar = font.table("fvar")
|
|
28
|
+
# bold_index = fvar.instances.find_index { |i| i[:name] =~ /Bold/ }
|
|
29
|
+
# strategy = NamedStrategy.new(instance_index: bold_index)
|
|
30
|
+
# tables = strategy.resolve(variable_font)
|
|
31
|
+
class NamedStrategy < BaseStrategy
|
|
32
|
+
# @return [Integer] Named instance index
|
|
33
|
+
attr_reader :instance_index
|
|
34
|
+
|
|
35
|
+
# Initialize strategy with instance index
|
|
36
|
+
#
|
|
37
|
+
# @param options [Hash] Strategy options
|
|
38
|
+
# @option options [Integer] :instance_index Index of named instance in fvar
|
|
39
|
+
# @raise [ArgumentError] If instance_index not provided
|
|
40
|
+
def initialize(options = {})
|
|
41
|
+
super
|
|
42
|
+
@instance_index = options[:instance_index]
|
|
43
|
+
|
|
44
|
+
if @instance_index.nil?
|
|
45
|
+
raise ArgumentError, "instance_index is required for NamedStrategy"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Resolve by using named instance coordinates
|
|
50
|
+
#
|
|
51
|
+
# Extracts coordinates from the fvar table's named instance and
|
|
52
|
+
# delegates to InstanceStrategy for actual instance generation.
|
|
53
|
+
#
|
|
54
|
+
# @param font [TrueTypeFont, OpenTypeFont] Variable font
|
|
55
|
+
# @return [Hash<String, String>] Static font tables
|
|
56
|
+
# @raise [ArgumentError] If instance index is invalid
|
|
57
|
+
def resolve(font)
|
|
58
|
+
# Extract coordinates from named instance
|
|
59
|
+
coordinates = extract_coordinates(font)
|
|
60
|
+
|
|
61
|
+
# Use InstanceStrategy to generate instance
|
|
62
|
+
instance_strategy = InstanceStrategy.new(coordinates: coordinates)
|
|
63
|
+
instance_strategy.resolve(font)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Check if strategy preserves variation data
|
|
67
|
+
#
|
|
68
|
+
# @return [Boolean] Always false for this strategy
|
|
69
|
+
def preserves_variation?
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Get strategy name
|
|
74
|
+
#
|
|
75
|
+
# @return [Symbol] :named
|
|
76
|
+
def strategy_name
|
|
77
|
+
:named
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
# Extract coordinates from named instance in fvar table
|
|
83
|
+
#
|
|
84
|
+
# @param font [TrueTypeFont, OpenTypeFont] Variable font
|
|
85
|
+
# @return [Hash<String, Float>] Design space coordinates
|
|
86
|
+
# @raise [ArgumentError] If instance index is invalid
|
|
87
|
+
def extract_coordinates(font)
|
|
88
|
+
context = Variation::VariationContext.new(font)
|
|
89
|
+
|
|
90
|
+
unless context.fvar
|
|
91
|
+
raise ArgumentError, "Font is not a variable font (no fvar table)"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
instances = context.fvar.instances
|
|
95
|
+
if @instance_index.negative? || @instance_index >= instances.length
|
|
96
|
+
raise ArgumentError,
|
|
97
|
+
"Invalid instance index #{@instance_index}. " \
|
|
98
|
+
"Font has #{instances.length} named instances."
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
instance = instances[@instance_index]
|
|
102
|
+
axes = context.axes
|
|
103
|
+
|
|
104
|
+
# Map instance coordinates to axis tags
|
|
105
|
+
coordinates = {}
|
|
106
|
+
instance[:coordinates].each_with_index do |value, i|
|
|
107
|
+
next if i >= axes.length
|
|
108
|
+
|
|
109
|
+
axis = axes[i]
|
|
110
|
+
coordinates[axis.axis_tag] = value
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
coordinates
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_strategy"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Pipeline
|
|
7
|
+
module Strategies
|
|
8
|
+
# Strategy for preserving variation data during conversion
|
|
9
|
+
#
|
|
10
|
+
# This strategy maintains all variation tables intact, making it suitable
|
|
11
|
+
# for conversions between compatible formats:
|
|
12
|
+
# - Variable TTF → Variable TTF (same format)
|
|
13
|
+
# - Variable OTF → Variable OTF (same format)
|
|
14
|
+
# - Variable TTF → Variable WOFF/WOFF2 (packaging change only)
|
|
15
|
+
# - Variable OTF → Variable WOFF/WOFF2 (packaging change only)
|
|
16
|
+
#
|
|
17
|
+
# The strategy copies all font tables including:
|
|
18
|
+
# - Variation tables: fvar, gvar/CFF2, avar, HVAR, VVAR, MVAR
|
|
19
|
+
# - Base tables: All non-variation tables
|
|
20
|
+
#
|
|
21
|
+
# @example Preserve variation data
|
|
22
|
+
# strategy = PreserveStrategy.new
|
|
23
|
+
# tables = strategy.resolve(variable_font)
|
|
24
|
+
# # tables includes fvar, gvar, etc.
|
|
25
|
+
class PreserveStrategy < BaseStrategy
|
|
26
|
+
# Resolve by preserving all variation data
|
|
27
|
+
#
|
|
28
|
+
# Returns all font tables including variation tables. This is a simple
|
|
29
|
+
# copy operation that maintains the variable font's full capabilities.
|
|
30
|
+
#
|
|
31
|
+
# @param font [TrueTypeFont, OpenTypeFont] Variable font
|
|
32
|
+
# @return [Hash<String, String>] All font tables
|
|
33
|
+
def resolve(font)
|
|
34
|
+
# Return a copy of all font tables
|
|
35
|
+
# This preserves variation tables (fvar, gvar, CFF2, avar, HVAR, etc.)
|
|
36
|
+
# and all base tables
|
|
37
|
+
font.table_data.dup
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Check if strategy preserves variation data
|
|
41
|
+
#
|
|
42
|
+
# @return [Boolean] Always true for this strategy
|
|
43
|
+
def preserves_variation?
|
|
44
|
+
true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Get strategy name
|
|
48
|
+
#
|
|
49
|
+
# @return [Symbol] :preserve
|
|
50
|
+
def strategy_name
|
|
51
|
+
:preserve
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|