fontisan 0.2.10 → 0.2.12

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +216 -42
  3. data/README.adoc +160 -0
  4. data/lib/fontisan/cli.rb +177 -6
  5. data/lib/fontisan/collection/table_analyzer.rb +88 -3
  6. data/lib/fontisan/commands/convert_command.rb +32 -1
  7. data/lib/fontisan/config/conversion_matrix.yml +132 -4
  8. data/lib/fontisan/constants.rb +12 -0
  9. data/lib/fontisan/conversion_options.rb +378 -0
  10. data/lib/fontisan/converters/cff_table_builder.rb +198 -0
  11. data/lib/fontisan/converters/collection_converter.rb +45 -10
  12. data/lib/fontisan/converters/format_converter.rb +2 -0
  13. data/lib/fontisan/converters/glyf_table_builder.rb +63 -0
  14. data/lib/fontisan/converters/outline_converter.rb +111 -374
  15. data/lib/fontisan/converters/outline_extraction.rb +93 -0
  16. data/lib/fontisan/converters/outline_optimizer.rb +89 -0
  17. data/lib/fontisan/converters/type1_converter.rb +559 -0
  18. data/lib/fontisan/font_loader.rb +46 -3
  19. data/lib/fontisan/glyph_accessor.rb +29 -1
  20. data/lib/fontisan/type1/afm_generator.rb +436 -0
  21. data/lib/fontisan/type1/afm_parser.rb +298 -0
  22. data/lib/fontisan/type1/agl.rb +456 -0
  23. data/lib/fontisan/type1/charstring_converter.rb +240 -0
  24. data/lib/fontisan/type1/charstrings.rb +408 -0
  25. data/lib/fontisan/type1/conversion_options.rb +243 -0
  26. data/lib/fontisan/type1/decryptor.rb +183 -0
  27. data/lib/fontisan/type1/encodings.rb +697 -0
  28. data/lib/fontisan/type1/font_dictionary.rb +514 -0
  29. data/lib/fontisan/type1/generator.rb +220 -0
  30. data/lib/fontisan/type1/inf_generator.rb +332 -0
  31. data/lib/fontisan/type1/pfa_generator.rb +343 -0
  32. data/lib/fontisan/type1/pfa_parser.rb +158 -0
  33. data/lib/fontisan/type1/pfb_generator.rb +291 -0
  34. data/lib/fontisan/type1/pfb_parser.rb +166 -0
  35. data/lib/fontisan/type1/pfm_generator.rb +610 -0
  36. data/lib/fontisan/type1/pfm_parser.rb +433 -0
  37. data/lib/fontisan/type1/private_dict.rb +285 -0
  38. data/lib/fontisan/type1/ttf_to_type1_converter.rb +327 -0
  39. data/lib/fontisan/type1/upm_scaler.rb +118 -0
  40. data/lib/fontisan/type1.rb +73 -0
  41. data/lib/fontisan/type1_font.rb +331 -0
  42. data/lib/fontisan/variation/cache.rb +1 -0
  43. data/lib/fontisan/version.rb +1 -1
  44. data/lib/fontisan/woff2_font.rb +3 -3
  45. data/lib/fontisan.rb +2 -0
  46. metadata +30 -2
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../outline_extractor"
4
+ require_relative "../tables/cff/charstring_builder"
5
+ require_relative "../tables/glyf/glyph_builder"
6
+ require_relative "../tables/glyf/compound_glyph_resolver"
7
+
8
+ module Fontisan
9
+ module Converters
10
+ # Extracts all glyph outlines from a font for conversion purposes
11
+ #
12
+ # Unlike [`OutlineExtractor`](../outline_extractor.rb) which extracts
13
+ # single glyphs, this module extracts ALL glyphs from a font for
14
+ # bulk conversion operations.
15
+ #
16
+ # @see OutlineExtractor for single glyph extraction
17
+ module OutlineExtraction
18
+ # Extract all outlines from TrueType font
19
+ #
20
+ # @param font [TrueTypeFont] Source font
21
+ # @return [Array<Outline>] Array of outline objects
22
+ def extract_ttf_outlines(font)
23
+ # Get required tables
24
+ head = font.table("head")
25
+ maxp = font.table("maxp")
26
+ loca = font.table("loca")
27
+ glyf = font.table("glyf")
28
+
29
+ # Parse loca with context
30
+ loca.parse_with_context(head.index_to_loc_format, maxp.num_glyphs)
31
+
32
+ # Create resolver for compound glyphs
33
+ resolver = Tables::CompoundGlyphResolver.new(glyf, loca, head)
34
+
35
+ # Extract all glyphs
36
+ outlines = []
37
+ maxp.num_glyphs.times do |glyph_id|
38
+ glyph = glyf.glyph_for(glyph_id, loca, head)
39
+
40
+ outlines << if glyph.nil? || glyph.empty?
41
+ # Empty glyph - create empty outline
42
+ Models::Outline.new(
43
+ glyph_id: glyph_id,
44
+ commands: [],
45
+ bbox: { x_min: 0, y_min: 0, x_max: 0, y_max: 0 },
46
+ )
47
+ elsif glyph.simple?
48
+ # Convert simple glyph to outline
49
+ Models::Outline.from_truetype(glyph, glyph_id)
50
+ else
51
+ # Compound glyph - resolve to simple outline
52
+ resolver.resolve(glyph)
53
+ end
54
+ end
55
+
56
+ outlines
57
+ end
58
+
59
+ # Extract all outlines from CFF font
60
+ #
61
+ # @param font [OpenTypeFont] Source font
62
+ # @return [Array<Outline>] Array of outline objects
63
+ def extract_cff_outlines(font)
64
+ # Get CFF table
65
+ cff = font.table("CFF ")
66
+ raise Fontisan::Error, "CFF table not found" unless cff
67
+
68
+ # Get number of glyphs
69
+ num_glyphs = cff.glyph_count
70
+
71
+ # Extract all glyphs
72
+ outlines = []
73
+ num_glyphs.times do |glyph_id|
74
+ charstring = cff.charstring_for_glyph(glyph_id)
75
+
76
+ outlines << if charstring.nil? || charstring.path.empty?
77
+ # Empty glyph
78
+ Models::Outline.new(
79
+ glyph_id: glyph_id,
80
+ commands: [],
81
+ bbox: { x_min: 0, y_min: 0, x_max: 0, y_max: 0 },
82
+ )
83
+ else
84
+ # Convert CharString to outline
85
+ Models::Outline.from_cff(charstring, glyph_id)
86
+ end
87
+ end
88
+
89
+ outlines
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../optimizers/pattern_analyzer"
4
+ require_relative "../optimizers/subroutine_optimizer"
5
+ require_relative "../optimizers/subroutine_builder"
6
+ require_relative "../optimizers/charstring_rewriter"
7
+
8
+ module Fontisan
9
+ module Converters
10
+ # Optimizes CFF CharStrings using subroutine extraction
11
+ #
12
+ # This module analyzes CharStrings for repeated patterns, extracts
13
+ # them as subroutines, and rewrites the CharStrings to call the
14
+ # subroutines instead of repeating the code.
15
+ #
16
+ # The optimization process:
17
+ # 1. Analyze patterns across all CharStrings
18
+ # 2. Select optimal set of patterns for subroutines
19
+ # 3. Optimize subroutine ordering
20
+ # 4. Build subroutines from selected patterns
21
+ # 5. Rewrite CharStrings to call subroutines
22
+ module OutlineOptimizer
23
+ # Optimize CharStrings using subroutine extraction
24
+ #
25
+ # @param charstrings [Array<String>] Original CharString bytes
26
+ # @return [Array<Array<String>, Array<String>>] [optimized_charstrings, local_subrs]
27
+ def optimize_charstrings(charstrings)
28
+ # Convert to hash format expected by PatternAnalyzer
29
+ charstrings_hash = {}
30
+ charstrings.each_with_index do |cs, index|
31
+ charstrings_hash[index] = cs
32
+ end
33
+
34
+ # Analyze patterns
35
+ analyzer = Optimizers::PatternAnalyzer.new(
36
+ min_length: 10,
37
+ stack_aware: true,
38
+ )
39
+ patterns = analyzer.analyze(charstrings_hash)
40
+
41
+ # Return original if no patterns found
42
+ return [charstrings, []] if patterns.empty?
43
+
44
+ # Optimize selection
45
+ optimizer = Optimizers::SubroutineOptimizer.new(patterns,
46
+ max_subrs: 65_535)
47
+ selected_patterns = optimizer.optimize_selection
48
+
49
+ # Optimize ordering
50
+ selected_patterns = optimizer.optimize_ordering(selected_patterns)
51
+
52
+ # Return original if no patterns selected
53
+ return [charstrings, []] if selected_patterns.empty?
54
+
55
+ # Build subroutines
56
+ builder = Optimizers::SubroutineBuilder.new(selected_patterns,
57
+ type: :local)
58
+ local_subrs = builder.build
59
+
60
+ # Build subroutine map
61
+ subroutine_map = {}
62
+ selected_patterns.each_with_index do |pattern, index|
63
+ subroutine_map[pattern.bytes] = index
64
+ end
65
+
66
+ # Rewrite CharStrings
67
+ rewriter = Optimizers::CharstringRewriter.new(subroutine_map, builder)
68
+ optimized_charstrings = charstrings.map.with_index do |charstring, glyph_id|
69
+ # Find patterns for this glyph
70
+ glyph_patterns = selected_patterns.select do |p|
71
+ p.glyphs.include?(glyph_id)
72
+ end
73
+
74
+ if glyph_patterns.empty?
75
+ charstring
76
+ else
77
+ rewriter.rewrite(charstring, glyph_patterns, glyph_id)
78
+ end
79
+ end
80
+
81
+ [optimized_charstrings, local_subrs]
82
+ rescue StandardError => e
83
+ # If optimization fails for any reason, return original CharStrings
84
+ warn "Optimization warning: #{e.message}"
85
+ [charstrings, []]
86
+ end
87
+ end
88
+ end
89
+ end