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,405 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Models
|
|
7
|
+
# Container for all font hint data
|
|
8
|
+
#
|
|
9
|
+
# This model holds complete hint information from a font including
|
|
10
|
+
# font-level programs, control values, and per-glyph hints. It provides
|
|
11
|
+
# a format-agnostic representation that can be converted between
|
|
12
|
+
# TrueType and PostScript formats.
|
|
13
|
+
#
|
|
14
|
+
# @example Creating a HintSet
|
|
15
|
+
# hint_set = HintSet.new(
|
|
16
|
+
# format: :truetype,
|
|
17
|
+
# font_program: fpgm_data,
|
|
18
|
+
# control_value_program: prep_data
|
|
19
|
+
# )
|
|
20
|
+
class HintSet
|
|
21
|
+
# @return [String] Hint format (:truetype or :postscript)
|
|
22
|
+
attr_accessor :format
|
|
23
|
+
|
|
24
|
+
# TrueType font-level hint data
|
|
25
|
+
# @return [String] Font program (fpgm table) - bytecode executed once
|
|
26
|
+
attr_accessor :font_program
|
|
27
|
+
|
|
28
|
+
# @return [String] Control value program (prep table) - initialization code
|
|
29
|
+
attr_accessor :control_value_program
|
|
30
|
+
|
|
31
|
+
# @return [Array<Integer>] Control values (cvt table) - metrics for hinting
|
|
32
|
+
attr_accessor :control_values
|
|
33
|
+
|
|
34
|
+
# PostScript font-level hint data
|
|
35
|
+
# @return [String] CFF Private dict hint data (BlueValues, StdHW, etc.) as JSON
|
|
36
|
+
attr_accessor :private_dict_hints
|
|
37
|
+
|
|
38
|
+
# @return [Integer] Number of glyphs with hints
|
|
39
|
+
attr_accessor :hinted_glyph_count
|
|
40
|
+
|
|
41
|
+
# @return [Boolean] Whether hints are present
|
|
42
|
+
attr_accessor :has_hints
|
|
43
|
+
|
|
44
|
+
# Initialize a new HintSet
|
|
45
|
+
#
|
|
46
|
+
# @param format [String, Symbol] Hint format (:truetype or :postscript)
|
|
47
|
+
# @param font_program [String] Font program bytecode
|
|
48
|
+
# @param control_value_program [String] Control value program bytecode
|
|
49
|
+
# @param control_values [Array<Integer>] Control values
|
|
50
|
+
# @param private_dict_hints [String] Private dict hints as JSON
|
|
51
|
+
# @param hinted_glyph_count [Integer] Number of hinted glyphs
|
|
52
|
+
# @param has_hints [Boolean] Whether hints are present
|
|
53
|
+
def initialize(format: nil, font_program: "", control_value_program: "",
|
|
54
|
+
control_values: [], private_dict_hints: "{}",
|
|
55
|
+
hinted_glyph_count: 0, has_hints: false)
|
|
56
|
+
@format = format.to_s if format
|
|
57
|
+
@font_program = font_program || ""
|
|
58
|
+
@control_value_program = control_value_program || ""
|
|
59
|
+
@control_values = control_values || []
|
|
60
|
+
@private_dict_hints = private_dict_hints || "{}"
|
|
61
|
+
@glyph_hints = "{}"
|
|
62
|
+
@hinted_glyph_count = hinted_glyph_count
|
|
63
|
+
@has_hints = has_hints
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Add hints for a specific glyph
|
|
67
|
+
#
|
|
68
|
+
# @param glyph_id [Integer, String] Glyph identifier
|
|
69
|
+
# @param hints [Array<Hint>] Hints for the glyph
|
|
70
|
+
def add_glyph_hints(glyph_id, hints)
|
|
71
|
+
return if hints.nil? || hints.empty?
|
|
72
|
+
|
|
73
|
+
glyph_hints_hash = parse_glyph_hints
|
|
74
|
+
# Convert Hint objects to hashes for storage
|
|
75
|
+
hints_data = hints.map do |h|
|
|
76
|
+
{
|
|
77
|
+
type: h.type,
|
|
78
|
+
data: h.data,
|
|
79
|
+
source_format: h.source_format
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
glyph_hints_hash[glyph_id.to_s] = hints_data
|
|
83
|
+
@glyph_hints = glyph_hints_hash.to_json
|
|
84
|
+
@hinted_glyph_count = glyph_hints_hash.keys.length
|
|
85
|
+
@has_hints = true
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Get hints for a specific glyph
|
|
89
|
+
#
|
|
90
|
+
# @param glyph_id [Integer, String] Glyph identifier
|
|
91
|
+
# @return [Array<Hint>] Hints for the glyph
|
|
92
|
+
def get_glyph_hints(glyph_id)
|
|
93
|
+
glyph_hints_hash = parse_glyph_hints
|
|
94
|
+
hints_data = glyph_hints_hash[glyph_id.to_s]
|
|
95
|
+
return [] unless hints_data
|
|
96
|
+
|
|
97
|
+
# Reconstruct Hint objects from serialized data
|
|
98
|
+
hints_data.map { |h| Hint.new(**h.transform_keys(&:to_sym)) }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Get all glyph IDs with hints
|
|
102
|
+
#
|
|
103
|
+
# @return [Array<String>] Glyph identifiers
|
|
104
|
+
def hinted_glyph_ids
|
|
105
|
+
parse_glyph_hints.keys
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Check if empty (no hints)
|
|
109
|
+
#
|
|
110
|
+
# @return [Boolean] True if no hints present
|
|
111
|
+
def empty?
|
|
112
|
+
!has_hints &&
|
|
113
|
+
(font_program.nil? || font_program.empty?) &&
|
|
114
|
+
(control_value_program.nil? || control_value_program.empty?) &&
|
|
115
|
+
(control_values.nil? || control_values.empty?) &&
|
|
116
|
+
(private_dict_hints.nil? || private_dict_hints == "{}")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
# @return [String] Glyph hints as JSON
|
|
122
|
+
attr_accessor :glyph_hints
|
|
123
|
+
|
|
124
|
+
# Parse glyph hints JSON
|
|
125
|
+
def parse_glyph_hints
|
|
126
|
+
return {} if @glyph_hints.nil? || @glyph_hints.empty? || @glyph_hints == "{}"
|
|
127
|
+
JSON.parse(@glyph_hints)
|
|
128
|
+
rescue JSON::ParserError
|
|
129
|
+
{}
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Universal hint representation supporting both TrueType and PostScript hints
|
|
134
|
+
#
|
|
135
|
+
# Hints are instructions that improve font rendering at small sizes by
|
|
136
|
+
# providing information about how to align features to the pixel grid.
|
|
137
|
+
# This model provides a format-agnostic representation that can be
|
|
138
|
+
# converted between TrueType instructions and PostScript hint operators.
|
|
139
|
+
#
|
|
140
|
+
# **Hint Types:**
|
|
141
|
+
#
|
|
142
|
+
# - `:stem` - Vertical or horizontal stem hints (PostScript hstem/vstem)
|
|
143
|
+
# - `:stem3` - Multiple stem hints (PostScript hstem3/vstem3)
|
|
144
|
+
# - `:flex` - Flex hints for smooth curves (PostScript flex)
|
|
145
|
+
# - `:counter` - Counter control hints (PostScript counter)
|
|
146
|
+
# - `:hint_replacement` - Hint replacement (PostScript hintmask)
|
|
147
|
+
# - `:delta` - Delta hints for pixel-level adjustments (TrueType DELTA)
|
|
148
|
+
# - `:interpolate` - Interpolation hints (TrueType IUP)
|
|
149
|
+
# - `:shift` - Shift hints (TrueType SHP)
|
|
150
|
+
# - `:align` - Alignment hints (TrueType ALIGNRP)
|
|
151
|
+
#
|
|
152
|
+
# @example Creating a stem hint
|
|
153
|
+
# hint = Fontisan::Models::Hint.new(
|
|
154
|
+
# type: :stem,
|
|
155
|
+
# data: { position: 100, width: 50, orientation: :vertical }
|
|
156
|
+
# )
|
|
157
|
+
#
|
|
158
|
+
# @example Converting to TrueType
|
|
159
|
+
# tt_instructions = hint.to_truetype
|
|
160
|
+
#
|
|
161
|
+
# @example Converting to PostScript
|
|
162
|
+
# ps_operators = hint.to_postscript
|
|
163
|
+
class Hint
|
|
164
|
+
# @return [Symbol] Hint type
|
|
165
|
+
attr_reader :type
|
|
166
|
+
|
|
167
|
+
# @return [Hash] Hint-specific data
|
|
168
|
+
attr_reader :data
|
|
169
|
+
|
|
170
|
+
# @return [Symbol] Source format (:truetype or :postscript)
|
|
171
|
+
attr_reader :source_format
|
|
172
|
+
|
|
173
|
+
# Initialize a new hint
|
|
174
|
+
#
|
|
175
|
+
# @param type [Symbol] Hint type
|
|
176
|
+
# @param data [Hash] Hint-specific data
|
|
177
|
+
# @param source_format [Symbol] Source format (optional)
|
|
178
|
+
def initialize(type:, data:, source_format: nil)
|
|
179
|
+
@type = type
|
|
180
|
+
@data = data
|
|
181
|
+
@source_format = source_format
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Convert hint to TrueType instruction format
|
|
185
|
+
#
|
|
186
|
+
# @return [Array<Integer>] TrueType instruction bytes
|
|
187
|
+
def to_truetype
|
|
188
|
+
case type
|
|
189
|
+
when :stem
|
|
190
|
+
convert_stem_to_truetype
|
|
191
|
+
when :stem3
|
|
192
|
+
convert_stem3_to_truetype
|
|
193
|
+
when :flex
|
|
194
|
+
convert_flex_to_truetype
|
|
195
|
+
when :counter
|
|
196
|
+
convert_counter_to_truetype
|
|
197
|
+
when :hint_replacement
|
|
198
|
+
convert_hintmask_to_truetype
|
|
199
|
+
when :delta
|
|
200
|
+
# Already in TrueType format
|
|
201
|
+
data[:instructions] || []
|
|
202
|
+
when :interpolate
|
|
203
|
+
# IUP instruction
|
|
204
|
+
axis = data[:axis] || :y
|
|
205
|
+
axis == :x ? [0x31] : [0x30] # IUP[x] or IUP[y]
|
|
206
|
+
when :shift
|
|
207
|
+
# SHP instruction
|
|
208
|
+
data[:instructions] || []
|
|
209
|
+
when :align
|
|
210
|
+
# ALIGNRP instruction
|
|
211
|
+
[0x3C]
|
|
212
|
+
else
|
|
213
|
+
# Unknown hint type - return empty
|
|
214
|
+
[]
|
|
215
|
+
end
|
|
216
|
+
rescue StandardError => e
|
|
217
|
+
warn "Error converting hint type #{type} to TrueType: #{e.message}"
|
|
218
|
+
[]
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Convert hint to PostScript hint format
|
|
222
|
+
#
|
|
223
|
+
# @return [Hash] PostScript hint operators and arguments
|
|
224
|
+
def to_postscript
|
|
225
|
+
case type
|
|
226
|
+
when :stem
|
|
227
|
+
convert_stem_to_postscript
|
|
228
|
+
when :stem3
|
|
229
|
+
convert_stem3_to_postscript
|
|
230
|
+
when :flex
|
|
231
|
+
convert_flex_to_postscript
|
|
232
|
+
when :counter
|
|
233
|
+
convert_counter_to_postscript
|
|
234
|
+
when :hint_replacement
|
|
235
|
+
# Hintmask operator
|
|
236
|
+
{ operator: :hintmask, args: data[:mask] || [] }
|
|
237
|
+
when :delta, :interpolate, :shift, :align
|
|
238
|
+
# TrueType-specific hints don't have direct PS equivalents
|
|
239
|
+
# Return approximation using stem hints
|
|
240
|
+
approximate_as_postscript
|
|
241
|
+
else
|
|
242
|
+
# Unknown hint type
|
|
243
|
+
{}
|
|
244
|
+
end
|
|
245
|
+
rescue StandardError => e
|
|
246
|
+
warn "Error converting hint type #{type} to PostScript: #{e.message}"
|
|
247
|
+
{}
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Check if hint is compatible with target format
|
|
251
|
+
#
|
|
252
|
+
# @param format [Symbol] Target format (:truetype or :postscript)
|
|
253
|
+
# @return [Boolean] True if compatible
|
|
254
|
+
def compatible_with?(format)
|
|
255
|
+
case format
|
|
256
|
+
when :truetype
|
|
257
|
+
# Most PostScript hints can be converted to TrueType
|
|
258
|
+
%i[stem flex counter delta interpolate shift align].include?(type)
|
|
259
|
+
when :postscript
|
|
260
|
+
# Most TrueType hints can be approximated in PostScript
|
|
261
|
+
%i[stem stem3 flex counter hint_replacement].include?(type)
|
|
262
|
+
else
|
|
263
|
+
false
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
private
|
|
268
|
+
|
|
269
|
+
# Convert stem hint to TrueType instructions
|
|
270
|
+
def convert_stem_to_truetype
|
|
271
|
+
position = data[:position] || 0
|
|
272
|
+
width = data[:width] || 0
|
|
273
|
+
orientation = data[:orientation] || :vertical
|
|
274
|
+
|
|
275
|
+
# TrueType uses MDAP (Move Direct Absolute Point) and MDRP (Move Direct Relative Point)
|
|
276
|
+
# to control stem positioning
|
|
277
|
+
instructions = []
|
|
278
|
+
|
|
279
|
+
if orientation == :vertical
|
|
280
|
+
# Vertical stem: use Y-axis instructions
|
|
281
|
+
instructions << 0x2E # MDAP[rnd] - mark reference point
|
|
282
|
+
instructions << 0xC0 # MDRP[min,rnd,black] - move relative point
|
|
283
|
+
else
|
|
284
|
+
# Horizontal stem: use X-axis instructions
|
|
285
|
+
instructions << 0x2F # MDAP[rnd]
|
|
286
|
+
instructions << 0xC0 # MDRP[min,rnd,black]
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
instructions
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Convert flex hint to TrueType instructions
|
|
293
|
+
def convert_flex_to_truetype
|
|
294
|
+
# Flex hints ensure smooth curves
|
|
295
|
+
# TrueType approximates with smooth curve flags on contour points
|
|
296
|
+
# Return empty as this is handled at the contour level
|
|
297
|
+
[]
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Convert counter hint to TrueType instructions
|
|
301
|
+
def convert_counter_to_truetype
|
|
302
|
+
# Counter hints control interior space
|
|
303
|
+
# TrueType uses control value program (CVT) for this
|
|
304
|
+
# Return empty as this requires CVT table modification
|
|
305
|
+
[]
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Convert stem3 hint to TrueType instructions
|
|
309
|
+
def convert_stem3_to_truetype
|
|
310
|
+
stems = data[:stems] || []
|
|
311
|
+
orientation = data[:orientation] || :vertical
|
|
312
|
+
|
|
313
|
+
# Generate MDAP/MDRP pairs for each stem
|
|
314
|
+
instructions = []
|
|
315
|
+
|
|
316
|
+
stems.each do |stem|
|
|
317
|
+
if orientation == :vertical
|
|
318
|
+
# Vertical stem: use Y-axis instructions
|
|
319
|
+
instructions << 0x2E # MDAP[rnd] - mark reference point
|
|
320
|
+
instructions << 0xC0 # MDRP[min,rnd,black] - move relative point
|
|
321
|
+
else
|
|
322
|
+
# Horizontal stem: use X-axis instructions
|
|
323
|
+
instructions << 0x2F # MDAP[rnd]
|
|
324
|
+
instructions << 0xC0 # MDRP[min,rnd,black]
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
instructions
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Convert hintmask hint to TrueType instructions
|
|
332
|
+
def convert_hintmask_to_truetype
|
|
333
|
+
# Hintmask controls which hints are active at runtime
|
|
334
|
+
# TrueType doesn't have a direct equivalent
|
|
335
|
+
# We can use conditional instructions, but it's complex
|
|
336
|
+
# For now, return empty and let the main stems handle hinting
|
|
337
|
+
# TODO: Implement conditional instruction generation if needed
|
|
338
|
+
[]
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Convert stem hint to PostScript operators
|
|
342
|
+
def convert_stem_to_postscript
|
|
343
|
+
position = data[:position] || 0
|
|
344
|
+
width = data[:width] || 0
|
|
345
|
+
orientation = data[:orientation] || :vertical
|
|
346
|
+
|
|
347
|
+
operator = orientation == :vertical ? :vstem : :hstem
|
|
348
|
+
|
|
349
|
+
{
|
|
350
|
+
operator: operator,
|
|
351
|
+
args: [position, width],
|
|
352
|
+
}
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# Convert stem3 hint to PostScript operators
|
|
356
|
+
def convert_stem3_to_postscript
|
|
357
|
+
stems = data[:stems] || []
|
|
358
|
+
orientation = data[:orientation] || :vertical
|
|
359
|
+
|
|
360
|
+
operator = orientation == :vertical ? :vstem3 : :hstem3
|
|
361
|
+
|
|
362
|
+
# Flatten stem positions and widths
|
|
363
|
+
args = stems.flat_map { |s| [s[:position], s[:width]] }
|
|
364
|
+
|
|
365
|
+
{
|
|
366
|
+
operator: operator,
|
|
367
|
+
args: args,
|
|
368
|
+
}
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# Convert flex hint to PostScript operators
|
|
372
|
+
def convert_flex_to_postscript
|
|
373
|
+
points = data[:points] || []
|
|
374
|
+
|
|
375
|
+
{
|
|
376
|
+
operator: :flex,
|
|
377
|
+
args: points.flat_map { |p| [p[:x], p[:y]] },
|
|
378
|
+
}
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# Convert counter hint to PostScript operators
|
|
382
|
+
def convert_counter_to_postscript
|
|
383
|
+
zones = data[:zones] || []
|
|
384
|
+
|
|
385
|
+
{
|
|
386
|
+
operator: :counter,
|
|
387
|
+
args: zones,
|
|
388
|
+
}
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
# Approximate TrueType-specific hints as PostScript stem hints
|
|
392
|
+
def approximate_as_postscript
|
|
393
|
+
# Best effort: create a stem hint from available data
|
|
394
|
+
if data[:position] && data[:width]
|
|
395
|
+
{
|
|
396
|
+
operator: :vstem,
|
|
397
|
+
args: [data[:position], data[:width]],
|
|
398
|
+
}
|
|
399
|
+
else
|
|
400
|
+
{}
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
end
|