fontisan 0.1.0 → 0.2.0
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 +529 -65
- data/Gemfile +1 -0
- data/LICENSE +5 -1
- data/README.adoc +1301 -275
- data/Rakefile +27 -2
- 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 +309 -0
- data/lib/fontisan/collection/builder.rb +260 -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 +241 -0
- data/lib/fontisan/collection/writer.rb +306 -0
- data/lib/fontisan/commands/base_command.rb +8 -1
- data/lib/fontisan/commands/convert_command.rb +291 -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 +295 -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 +178 -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 +69 -0
- data/lib/fontisan/converters/conversion_strategy.rb +96 -0
- data/lib/fontisan/converters/format_converter.rb +259 -0
- data/lib/fontisan/converters/outline_converter.rb +936 -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 +121 -12
- data/lib/fontisan/font_writer.rb +301 -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 +177 -0
- data/lib/fontisan/hints/postscript_hint_applier.rb +185 -0
- data/lib/fontisan/hints/postscript_hint_extractor.rb +254 -0
- data/lib/fontisan/hints/truetype_hint_applier.rb +71 -0
- data/lib/fontisan/hints/truetype_hint_extractor.rb +162 -0
- data/lib/fontisan/loading_modes.rb +113 -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 +233 -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 +296 -10
- 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/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 +905 -0
- data/lib/fontisan/tables/cff/charstring_builder.rb +322 -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 +242 -0
- data/lib/fontisan/tables/cff/encoding.rb +274 -0
- data/lib/fontisan/tables/cff/header.rb +102 -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/private_dict.rb +284 -0
- data/lib/fontisan/tables/cff/top_dict.rb +236 -0
- data/lib/fontisan/tables/cff.rb +487 -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.rb +341 -0
- data/lib/fontisan/tables/cvar.rb +242 -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 +270 -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 +297 -11
- data/lib/fontisan/utilities/brotli_wrapper.rb +159 -0
- data/lib/fontisan/utilities/checksum_calculator.rb +18 -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/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 +268 -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/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/validator.rb +345 -0
- data/lib/fontisan/variation/variation_context.rb +211 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2/directory.rb +257 -0
- data/lib/fontisan/woff2/header.rb +101 -0
- data/lib/fontisan/woff2/table_transformer.rb +163 -0
- data/lib/fontisan/woff2_font.rb +712 -0
- data/lib/fontisan/woff_font.rb +483 -0
- data/lib/fontisan.rb +120 -0
- data/scripts/compare_stack_aware.rb +187 -0
- data/scripts/measure_optimization.rb +141 -0
- metadata +205 -4
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../models/ttx/tables/head_table"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Export
|
|
7
|
+
module Transformers
|
|
8
|
+
# HeadTransformer transforms head table to TTX format
|
|
9
|
+
#
|
|
10
|
+
# Converts Fontisan::Tables::Head to Models::Ttx::Tables::HeadTable
|
|
11
|
+
# following proper model-to-model transformation principles.
|
|
12
|
+
class HeadTransformer
|
|
13
|
+
# Transform head table to TTX model
|
|
14
|
+
#
|
|
15
|
+
# @param head_table [Tables::Head] Source head table
|
|
16
|
+
# @return [Models::Ttx::Tables::HeadTable] TTX head table model
|
|
17
|
+
def self.transform(head_table)
|
|
18
|
+
return nil unless head_table
|
|
19
|
+
|
|
20
|
+
Models::Ttx::Tables::HeadTable.new.tap do |ttx|
|
|
21
|
+
ttx.table_version = format_fixed(to_int(head_table.version))
|
|
22
|
+
ttx.font_revision = format_fixed(to_int(head_table.font_revision))
|
|
23
|
+
ttx.checksum_adjustment = format_hex(to_int(head_table.checksum_adjustment))
|
|
24
|
+
ttx.magic_number = format_hex(to_int(head_table.magic_number))
|
|
25
|
+
ttx.flags = to_int(head_table.flags).to_s
|
|
26
|
+
ttx.units_per_em = to_int(head_table.units_per_em).to_s
|
|
27
|
+
ttx.created = format_timestamp(to_int(head_table.created))
|
|
28
|
+
ttx.modified = format_timestamp(to_int(head_table.modified))
|
|
29
|
+
ttx.x_min = to_int(head_table.x_min).to_s
|
|
30
|
+
ttx.y_min = to_int(head_table.y_min).to_s
|
|
31
|
+
ttx.x_max = to_int(head_table.x_max).to_s
|
|
32
|
+
ttx.y_max = to_int(head_table.y_max).to_s
|
|
33
|
+
ttx.mac_style = format_binary_flags(to_int(head_table.mac_style),
|
|
34
|
+
16)
|
|
35
|
+
ttx.lowest_rec_ppem = to_int(head_table.lowest_rec_ppem).to_s
|
|
36
|
+
ttx.font_direction_hint = to_int(head_table.font_direction_hint).to_s
|
|
37
|
+
ttx.index_to_loc_format = to_int(head_table.index_to_loc_format).to_s
|
|
38
|
+
ttx.glyph_data_format = to_int(head_table.glyph_data_format).to_s
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Convert BinData value to native Ruby integer
|
|
43
|
+
#
|
|
44
|
+
# @param value [Object] BinData value or integer
|
|
45
|
+
# @return [Integer] Native integer
|
|
46
|
+
def self.to_int(value)
|
|
47
|
+
value.respond_to?(:to_i) ? value.to_i : value
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Format fixed-point number (16.16)
|
|
51
|
+
#
|
|
52
|
+
# @param value [Integer] Fixed-point value
|
|
53
|
+
# @return [String] Decimal string
|
|
54
|
+
def self.format_fixed(value)
|
|
55
|
+
result = value.to_f / 65536.0
|
|
56
|
+
if result == result.to_i
|
|
57
|
+
"#{result.to_i}.0"
|
|
58
|
+
else
|
|
59
|
+
result.to_s
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Format hex value
|
|
64
|
+
#
|
|
65
|
+
# @param value [Integer] Integer value
|
|
66
|
+
# @param width [Integer] Minimum hex width
|
|
67
|
+
# @return [String] Hex string (e.g., "0x1234")
|
|
68
|
+
def self.format_hex(value, width: 8)
|
|
69
|
+
"0x#{value.to_s(16).rjust(width, '0')}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Format binary flags
|
|
73
|
+
#
|
|
74
|
+
# @param value [Integer] Integer value
|
|
75
|
+
# @param bits [Integer] Number of bits
|
|
76
|
+
# @return [String] Binary string with spaces every 8 bits
|
|
77
|
+
def self.format_binary_flags(value, bits)
|
|
78
|
+
binary = value.to_s(2).rjust(bits, "0")
|
|
79
|
+
binary.scan(/.{1,8}/).join(" ")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Format timestamp
|
|
83
|
+
#
|
|
84
|
+
# @param timestamp [Integer] Mac timestamp (seconds since 1904-01-01)
|
|
85
|
+
# @return [String] Human-readable date string
|
|
86
|
+
def self.format_timestamp(timestamp)
|
|
87
|
+
mac_epoch = Time.utc(1904, 1, 1)
|
|
88
|
+
time = mac_epoch + timestamp
|
|
89
|
+
time.strftime("%a %b %e %H:%M:%S %Y")
|
|
90
|
+
rescue StandardError
|
|
91
|
+
"Invalid Date"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../models/ttx/tables/hhea_table"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Export
|
|
7
|
+
module Transformers
|
|
8
|
+
# HheaTransformer transforms hhea table to TTX format
|
|
9
|
+
#
|
|
10
|
+
# Converts Fontisan horizontal header table to Models::Ttx::Tables::HheaTable
|
|
11
|
+
# following proper model-to-model transformation principles.
|
|
12
|
+
class HheaTransformer
|
|
13
|
+
# Transform hhea table to TTX model
|
|
14
|
+
#
|
|
15
|
+
# @param hhea_table [Object] Source hhea table
|
|
16
|
+
# @return [Models::Ttx::Tables::HheaTable] TTX hhea table model
|
|
17
|
+
def self.transform(hhea_table)
|
|
18
|
+
return nil unless hhea_table
|
|
19
|
+
|
|
20
|
+
Models::Ttx::Tables::HheaTable.new.tap do |ttx|
|
|
21
|
+
ttx.table_version = format_hex(to_int(hhea_table.version_raw))
|
|
22
|
+
ttx.ascent = to_int(hhea_table.ascent)
|
|
23
|
+
ttx.descent = to_int(hhea_table.descent)
|
|
24
|
+
ttx.line_gap = to_int(hhea_table.line_gap)
|
|
25
|
+
ttx.advance_width_max = to_int(hhea_table.advance_width_max)
|
|
26
|
+
ttx.min_left_side_bearing = to_int(hhea_table.min_left_side_bearing)
|
|
27
|
+
ttx.min_right_side_bearing = to_int(hhea_table.min_right_side_bearing)
|
|
28
|
+
ttx.x_max_extent = to_int(hhea_table.x_max_extent)
|
|
29
|
+
ttx.caret_slope_rise = to_int(hhea_table.caret_slope_rise)
|
|
30
|
+
ttx.caret_slope_run = to_int(hhea_table.caret_slope_run)
|
|
31
|
+
ttx.caret_offset = to_int(hhea_table.caret_offset)
|
|
32
|
+
ttx.reserved0 = 0
|
|
33
|
+
ttx.reserved1 = 0
|
|
34
|
+
ttx.reserved2 = 0
|
|
35
|
+
ttx.reserved3 = 0
|
|
36
|
+
ttx.metric_data_format = to_int(hhea_table.metric_data_format)
|
|
37
|
+
ttx.number_of_h_metrics = to_int(hhea_table.number_of_h_metrics)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Convert BinData value to native Ruby integer
|
|
42
|
+
#
|
|
43
|
+
# @param value [Object] BinData value or integer
|
|
44
|
+
# @return [Integer] Native integer
|
|
45
|
+
def self.to_int(value)
|
|
46
|
+
value.respond_to?(:to_i) ? value.to_i : value
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Format hex value
|
|
50
|
+
#
|
|
51
|
+
# @param value [Integer] Integer value
|
|
52
|
+
# @return [String] Hex string (e.g., "0x00010000")
|
|
53
|
+
def self.format_hex(value)
|
|
54
|
+
"0x#{value.to_s(16).rjust(8, '0')}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../models/ttx/tables/maxp_table"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Export
|
|
7
|
+
module Transformers
|
|
8
|
+
# MaxpTransformer transforms maxp table to TTX format
|
|
9
|
+
#
|
|
10
|
+
# Converts Fontisan maximum profile table to Models::Ttx::Tables::MaxpTable
|
|
11
|
+
# following proper model-to-model transformation principles.
|
|
12
|
+
class MaxpTransformer
|
|
13
|
+
# Transform maxp table to TTX model
|
|
14
|
+
#
|
|
15
|
+
# @param maxp_table [Object] Source maxp table
|
|
16
|
+
# @return [Models::Ttx::Tables::MaxpTable] TTX maxp table model
|
|
17
|
+
def self.transform(maxp_table)
|
|
18
|
+
return nil unless maxp_table
|
|
19
|
+
|
|
20
|
+
version = to_int(maxp_table.version)
|
|
21
|
+
|
|
22
|
+
Models::Ttx::Tables::MaxpTable.new.tap do |ttx|
|
|
23
|
+
ttx.table_version = format_hex(version)
|
|
24
|
+
ttx.num_glyphs = to_int(maxp_table.num_glyphs)
|
|
25
|
+
|
|
26
|
+
# Version 1.0 fields (TrueType)
|
|
27
|
+
if version >= 0x00010000
|
|
28
|
+
ttx.max_points = to_int(maxp_table.max_points)
|
|
29
|
+
ttx.max_contours = to_int(maxp_table.max_contours)
|
|
30
|
+
ttx.max_composite_points = to_int(maxp_table.max_component_points)
|
|
31
|
+
ttx.max_composite_contours = to_int(maxp_table.max_component_contours)
|
|
32
|
+
ttx.max_zones = to_int(maxp_table.max_zones)
|
|
33
|
+
ttx.max_twilight_points = to_int(maxp_table.max_twilight_points)
|
|
34
|
+
ttx.max_storage = to_int(maxp_table.max_storage)
|
|
35
|
+
ttx.max_function_defs = to_int(maxp_table.max_function_defs)
|
|
36
|
+
ttx.max_instruction_defs = to_int(maxp_table.max_instruction_defs)
|
|
37
|
+
ttx.max_stack_elements = to_int(maxp_table.max_stack_elements)
|
|
38
|
+
ttx.max_size_of_instructions = to_int(maxp_table.max_size_of_instructions)
|
|
39
|
+
ttx.max_component_elements = to_int(maxp_table.max_component_elements)
|
|
40
|
+
ttx.max_component_depth = to_int(maxp_table.max_component_depth)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Convert BinData value to native Ruby integer
|
|
46
|
+
#
|
|
47
|
+
# @param value [Object] BinData value or integer
|
|
48
|
+
# @return [Integer] Native integer
|
|
49
|
+
def self.to_int(value)
|
|
50
|
+
value.respond_to?(:to_i) ? value.to_i : value
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Format hex value
|
|
54
|
+
#
|
|
55
|
+
# @param value [Integer] Integer value
|
|
56
|
+
# @return [String] Hex string (e.g., "0x00010000")
|
|
57
|
+
def self.format_hex(value)
|
|
58
|
+
"0x#{value.to_s(16).rjust(8, '0')}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../models/ttx/tables/name_table"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Export
|
|
7
|
+
module Transformers
|
|
8
|
+
# NameTransformer transforms name table to TTX format
|
|
9
|
+
#
|
|
10
|
+
# Converts Fontisan::Tables::Name to Models::Ttx::Tables::NameTable
|
|
11
|
+
# following proper model-to-model transformation principles.
|
|
12
|
+
class NameTransformer
|
|
13
|
+
# Transform name table to TTX model
|
|
14
|
+
#
|
|
15
|
+
# @param name_table [Tables::Name] Source name table
|
|
16
|
+
# @return [Models::Ttx::Tables::NameTable] TTX name table model
|
|
17
|
+
def self.transform(name_table)
|
|
18
|
+
return nil unless name_table
|
|
19
|
+
|
|
20
|
+
Models::Ttx::Tables::NameTable.new.tap do |ttx|
|
|
21
|
+
ttx.name_records = transform_name_records(name_table.name_records)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Transform name records
|
|
26
|
+
#
|
|
27
|
+
# @param records [Array] Name records from source table
|
|
28
|
+
# @return [Array<Models::Ttx::Tables::NameRecord>] TTX name records
|
|
29
|
+
def self.transform_name_records(records)
|
|
30
|
+
return [] unless records
|
|
31
|
+
|
|
32
|
+
records.map do |record|
|
|
33
|
+
Models::Ttx::Tables::NameRecord.new.tap do |ttx_record|
|
|
34
|
+
ttx_record.name_id = to_int(record.name_id)
|
|
35
|
+
ttx_record.platform_id = to_int(record.platform_id)
|
|
36
|
+
ttx_record.plat_enc_id = to_int(record.encoding_id)
|
|
37
|
+
ttx_record.lang_id = format_hex(to_int(record.language_id),
|
|
38
|
+
width: 3)
|
|
39
|
+
ttx_record.string = record.string
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Convert BinData value to native Ruby integer
|
|
45
|
+
#
|
|
46
|
+
# @param value [Object] BinData value or integer
|
|
47
|
+
# @return [Integer] Native integer
|
|
48
|
+
def self.to_int(value)
|
|
49
|
+
value.respond_to?(:to_i) ? value.to_i : value
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Format hex value
|
|
53
|
+
#
|
|
54
|
+
# @param value [Integer] Integer value
|
|
55
|
+
# @param width [Integer] Minimum hex width
|
|
56
|
+
# @return [String] Hex string (e.g., "0x1234")
|
|
57
|
+
def self.format_hex(value, width: 8)
|
|
58
|
+
"0x#{value.to_s(16).rjust(width, '0')}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../models/ttx/tables/os2_table"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Export
|
|
7
|
+
module Transformers
|
|
8
|
+
# Os2Transformer transforms OS/2 table to TTX format
|
|
9
|
+
#
|
|
10
|
+
# Converts Fontisan::Tables::Os2 to Models::Ttx::Tables::Os2Table
|
|
11
|
+
# following proper model-to-model transformation principles.
|
|
12
|
+
class Os2Transformer
|
|
13
|
+
# Transform OS/2 table to TTX model
|
|
14
|
+
#
|
|
15
|
+
# @param os2_table [Tables::Os2] Source OS/2 table
|
|
16
|
+
# @return [Models::Ttx::Tables::Os2Table] TTX OS/2 table model
|
|
17
|
+
def self.transform(os2_table)
|
|
18
|
+
return nil unless os2_table
|
|
19
|
+
|
|
20
|
+
version = to_int(os2_table.version)
|
|
21
|
+
|
|
22
|
+
Models::Ttx::Tables::Os2Table.new.tap do |ttx|
|
|
23
|
+
ttx.version = version
|
|
24
|
+
ttx.x_avg_char_width = to_int(os2_table.x_avg_char_width)
|
|
25
|
+
ttx.us_weight_class = to_int(os2_table.us_weight_class)
|
|
26
|
+
ttx.us_width_class = to_int(os2_table.us_width_class)
|
|
27
|
+
ttx.fs_type = format_binary_flags(to_int(os2_table.fs_type), 16)
|
|
28
|
+
ttx.y_subscript_x_size = to_int(os2_table.y_subscript_x_size)
|
|
29
|
+
ttx.y_subscript_y_size = to_int(os2_table.y_subscript_y_size)
|
|
30
|
+
ttx.y_subscript_x_offset = to_int(os2_table.y_subscript_x_offset)
|
|
31
|
+
ttx.y_subscript_y_offset = to_int(os2_table.y_subscript_y_offset)
|
|
32
|
+
ttx.y_superscript_x_size = to_int(os2_table.y_superscript_x_size)
|
|
33
|
+
ttx.y_superscript_y_size = to_int(os2_table.y_superscript_y_size)
|
|
34
|
+
ttx.y_superscript_x_offset = to_int(os2_table.y_superscript_x_offset)
|
|
35
|
+
ttx.y_superscript_y_offset = to_int(os2_table.y_superscript_y_offset)
|
|
36
|
+
ttx.y_strikeout_size = to_int(os2_table.y_strikeout_size)
|
|
37
|
+
ttx.y_strikeout_position = to_int(os2_table.y_strikeout_position)
|
|
38
|
+
ttx.s_family_class = to_int(os2_table.s_family_class)
|
|
39
|
+
ttx.panose = transform_panose(os2_table.panose)
|
|
40
|
+
ttx.ul_unicode_range_1 = format_binary_flags(
|
|
41
|
+
to_int(os2_table.ul_unicode_range1), 32
|
|
42
|
+
)
|
|
43
|
+
ttx.ul_unicode_range_2 = format_binary_flags(
|
|
44
|
+
to_int(os2_table.ul_unicode_range2), 32
|
|
45
|
+
)
|
|
46
|
+
ttx.ul_unicode_range_3 = format_binary_flags(
|
|
47
|
+
to_int(os2_table.ul_unicode_range3), 32
|
|
48
|
+
)
|
|
49
|
+
ttx.ul_unicode_range_4 = format_binary_flags(
|
|
50
|
+
to_int(os2_table.ul_unicode_range4), 32
|
|
51
|
+
)
|
|
52
|
+
ttx.ach_vend_id = os2_table.vendor_id.to_s.strip
|
|
53
|
+
ttx.fs_selection = format_binary_flags(
|
|
54
|
+
to_int(os2_table.fs_selection), 16
|
|
55
|
+
)
|
|
56
|
+
ttx.us_first_char_index = to_int(os2_table.us_first_char_index)
|
|
57
|
+
ttx.us_last_char_index = to_int(os2_table.us_last_char_index)
|
|
58
|
+
|
|
59
|
+
if version >= 1
|
|
60
|
+
ttx.s_typo_ascender = to_int(os2_table.s_typo_ascender)
|
|
61
|
+
ttx.s_typo_descender = to_int(os2_table.s_typo_descender)
|
|
62
|
+
ttx.s_typo_line_gap = to_int(os2_table.s_typo_line_gap)
|
|
63
|
+
ttx.us_win_ascent = to_int(os2_table.us_win_ascent)
|
|
64
|
+
ttx.us_win_descent = to_int(os2_table.us_win_descent)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if version >= 2
|
|
68
|
+
ttx.ul_code_page_range_1 = format_binary_flags(
|
|
69
|
+
to_int(os2_table.ul_code_page_range1), 32
|
|
70
|
+
)
|
|
71
|
+
ttx.ul_code_page_range_2 = format_binary_flags(
|
|
72
|
+
to_int(os2_table.ul_code_page_range2), 32
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Transform Panose data
|
|
79
|
+
#
|
|
80
|
+
# @param panose [Object] Panose data (String or Array)
|
|
81
|
+
# @return [Models::Ttx::Tables::Panose] TTX Panose model
|
|
82
|
+
def self.transform_panose(panose)
|
|
83
|
+
return nil unless panose
|
|
84
|
+
|
|
85
|
+
bytes = panose.is_a?(String) ? panose.bytes : panose.to_a
|
|
86
|
+
|
|
87
|
+
Models::Ttx::Tables::Panose.new.tap do |ttx_panose|
|
|
88
|
+
ttx_panose.b_family_type = bytes[0]
|
|
89
|
+
ttx_panose.b_serif_style = bytes[1]
|
|
90
|
+
ttx_panose.b_weight = bytes[2]
|
|
91
|
+
ttx_panose.b_proportion = bytes[3]
|
|
92
|
+
ttx_panose.b_contrast = bytes[4]
|
|
93
|
+
ttx_panose.b_stroke_variation = bytes[5]
|
|
94
|
+
ttx_panose.b_arm_style = bytes[6]
|
|
95
|
+
ttx_panose.b_letter_form = bytes[7]
|
|
96
|
+
ttx_panose.b_midline = bytes[8]
|
|
97
|
+
ttx_panose.b_x_height = bytes[9]
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Convert BinData value to native Ruby integer
|
|
102
|
+
#
|
|
103
|
+
# @param value [Object] BinData value or integer
|
|
104
|
+
# @return [Integer] Native integer
|
|
105
|
+
def self.to_int(value)
|
|
106
|
+
value.respond_to?(:to_i) ? value.to_i : value
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Format binary flags
|
|
110
|
+
#
|
|
111
|
+
# @param value [Integer] Integer value
|
|
112
|
+
# @param bits [Integer] Number of bits
|
|
113
|
+
# @return [String] Binary string with spaces every 8 bits
|
|
114
|
+
def self.format_binary_flags(value, bits)
|
|
115
|
+
binary = value.to_s(2).rjust(bits, "0")
|
|
116
|
+
binary.scan(/.{1,8}/).join(" ")
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../models/ttx/tables/post_table"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Export
|
|
7
|
+
module Transformers
|
|
8
|
+
# PostTransformer transforms post table to TTX format
|
|
9
|
+
#
|
|
10
|
+
# Converts Fontisan::Tables::Post to Models::Ttx::Tables::PostTable
|
|
11
|
+
# following proper model-to-model transformation principles.
|
|
12
|
+
class PostTransformer
|
|
13
|
+
# Transform post table to TTX model
|
|
14
|
+
#
|
|
15
|
+
# @param post_table [Tables::Post] Source post table
|
|
16
|
+
# @return [Models::Ttx::Tables::PostTable] TTX post table model
|
|
17
|
+
def self.transform(post_table)
|
|
18
|
+
return nil unless post_table
|
|
19
|
+
|
|
20
|
+
Models::Ttx::Tables::PostTable.new.tap do |ttx|
|
|
21
|
+
ttx.format_type = format_fixed(to_int(post_table.version_raw))
|
|
22
|
+
ttx.italic_angle = format_fixed(to_int(post_table.italic_angle_raw))
|
|
23
|
+
ttx.underline_position = to_int(post_table.underline_position)
|
|
24
|
+
ttx.underline_thickness = to_int(post_table.underline_thickness)
|
|
25
|
+
ttx.is_fixed_pitch = to_int(post_table.is_fixed_pitch)
|
|
26
|
+
ttx.min_mem_type42 = to_int(post_table.min_mem_type42)
|
|
27
|
+
ttx.max_mem_type42 = to_int(post_table.max_mem_type42)
|
|
28
|
+
ttx.min_mem_type1 = to_int(post_table.min_mem_type1)
|
|
29
|
+
ttx.max_mem_type1 = to_int(post_table.max_mem_type1)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Convert BinData value to native Ruby integer
|
|
34
|
+
#
|
|
35
|
+
# @param value [Object] BinData value or integer
|
|
36
|
+
# @return [Integer] Native integer
|
|
37
|
+
def self.to_int(value)
|
|
38
|
+
value.respond_to?(:to_i) ? value.to_i : value
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Format fixed-point number (16.16)
|
|
42
|
+
#
|
|
43
|
+
# @param value [Integer] Fixed-point value
|
|
44
|
+
# @return [Float] Decimal value
|
|
45
|
+
def self.format_fixed(value)
|
|
46
|
+
value.to_f / 65536.0
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|