fontisan 0.4.22 → 0.4.23

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +5 -0
  3. data/lib/fontisan/config/subset_profiles.yml +2 -0
  4. data/lib/fontisan/stitcher/cbdt_propagator.rb +44 -22
  5. data/lib/fontisan/stitcher/glyph_cloner.rb +41 -0
  6. data/lib/fontisan/stitcher/glyph_copier.rb +3 -33
  7. data/lib/fontisan/stitcher/unique_glyph_name.rb +46 -0
  8. data/lib/fontisan/stitcher.rb +2 -0
  9. data/lib/fontisan/subset/shared_state.rb +42 -0
  10. data/lib/fontisan/subset/subset_context.rb +20 -0
  11. data/lib/fontisan/subset/table_strategy/cbdt.rb +30 -0
  12. data/lib/fontisan/subset/table_strategy/cblc.rb +27 -0
  13. data/lib/fontisan/subset/table_strategy/cmap.rb +146 -0
  14. data/lib/fontisan/subset/table_strategy/color_bitmap_placement.rb +20 -0
  15. data/lib/fontisan/subset/table_strategy/color_bitmap_strike_plan.rb +45 -0
  16. data/lib/fontisan/subset/table_strategy/color_bitmap_subset_plan.rb +62 -0
  17. data/lib/fontisan/subset/table_strategy/color_bitmap_subsetter.rb +182 -0
  18. data/lib/fontisan/subset/table_strategy/color_bitmap_subtable_plan.rb +59 -0
  19. data/lib/fontisan/subset/table_strategy/glyf.rb +29 -0
  20. data/lib/fontisan/subset/table_strategy/glyf_loca_builder.rb +145 -0
  21. data/lib/fontisan/subset/table_strategy/head.rb +42 -0
  22. data/lib/fontisan/subset/table_strategy/hhea.rb +67 -0
  23. data/lib/fontisan/subset/table_strategy/hmtx.rb +52 -0
  24. data/lib/fontisan/subset/table_strategy/loca.rb +41 -0
  25. data/lib/fontisan/subset/table_strategy/maxp.rb +23 -0
  26. data/lib/fontisan/subset/table_strategy/name.rb +19 -0
  27. data/lib/fontisan/subset/table_strategy/os2.rb +21 -0
  28. data/lib/fontisan/subset/table_strategy/pass_through.rb +20 -0
  29. data/lib/fontisan/subset/table_strategy/post.rb +37 -0
  30. data/lib/fontisan/subset/table_strategy.rb +75 -0
  31. data/lib/fontisan/subset/table_subsetter.rb +49 -616
  32. data/lib/fontisan/subset.rb +3 -0
  33. data/lib/fontisan/tables/cbdt.rb +66 -121
  34. data/lib/fontisan/tables/cblc.rb +110 -231
  35. data/lib/fontisan/tables/cblc_big_glyph_metrics.rb +28 -0
  36. data/lib/fontisan/tables/cblc_bitmap_size.rb +67 -0
  37. data/lib/fontisan/tables/cblc_glyph_bitmap_location.rb +28 -0
  38. data/lib/fontisan/tables/cblc_index_subtable.rb +104 -0
  39. data/lib/fontisan/tables/cblc_index_subtable_array_entry.rb +20 -0
  40. data/lib/fontisan/tables/cblc_index_subtable_format_parser.rb +150 -0
  41. data/lib/fontisan/tables/cblc_index_subtable_header.rb +18 -0
  42. data/lib/fontisan/tables/cblc_sbit_line_metrics.rb +28 -0
  43. data/lib/fontisan/tables.rb +11 -0
  44. data/lib/fontisan/ufo/glyph_exists_error.rb +23 -0
  45. data/lib/fontisan/ufo/layer.rb +42 -2
  46. data/lib/fontisan/ufo.rb +2 -1
  47. data/lib/fontisan/version.rb +1 -1
  48. metadata +35 -2
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fontisan
4
+ module Subset
5
+ module TableStrategy
6
+ # Maxp strategy: rewrite numGlyphs (offset 4, uint16) to the
7
+ # subset's glyph count. The remaining maxp fields describe worst-
8
+ # case outline/instruction complexity and can be left as-is —
9
+ # a smaller subset cannot exceed the source's maxima.
10
+ class Maxp
11
+ # @param context [SubsetContext]
12
+ # @param tag [String] "maxp"
13
+ # @param table [Maxp] parsed maxp table
14
+ # @return [String] binary maxp bytes for the subset
15
+ def self.call(context:, tag:, table:)
16
+ data = table.to_binary_s.dup
17
+ data[4, 2] = [context.mapping.size].pack("n")
18
+ data
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fontisan
4
+ module Subset
5
+ module TableStrategy
6
+ # Name strategy: name records don't reference glyph IDs, so the
7
+ # full source table is preserved.
8
+ class Name
9
+ # @param context [SubsetContext]
10
+ # @param tag [String] "name"
11
+ # @param table [Name] parsed name table (unused)
12
+ # @return [String] source name bytes verbatim
13
+ def self.call(context:, tag:, table:)
14
+ context.font.table_data[tag]
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fontisan
4
+ module Subset
5
+ module TableStrategy
6
+ # OS/2 strategy: OS/2 currently passes through unchanged. Unicode
7
+ # range pruning is left as future work — the source OS/2 ranges
8
+ # remain valid (they describe the source character coverage, which
9
+ # is a superset of the subset's).
10
+ class Os2
11
+ # @param context [SubsetContext]
12
+ # @param tag [String] "OS/2"
13
+ # @param table [Os2] parsed OS/2 table (unused)
14
+ # @return [String] source OS/2 bytes verbatim
15
+ def self.call(context:, tag:, table:)
16
+ context.font.table_data[tag]
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fontisan
4
+ module Subset
5
+ module TableStrategy
6
+ # Default fallback for tags that have no dedicated strategy.
7
+ # Returns the source table bytes verbatim — used for tables that
8
+ # don't need any glyph-aware subsetting (e.g. name, OS/2).
9
+ class PassThrough
10
+ # @param context [SubsetContext]
11
+ # @param tag [String] table tag
12
+ # @param table [Object, nil] parsed table (unused)
13
+ # @return [String, nil] source binary bytes for `tag`, or nil
14
+ def self.call(context:, tag:, table:)
15
+ context.font.table_data[tag]
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fontisan
4
+ module Subset
5
+ module TableStrategy
6
+ # Post strategy: if the `drop_names` option is set, rewrite as a
7
+ # version 3.0 post table (no glyph names). Otherwise pass through
8
+ # the source post bytes.
9
+ class Post
10
+ # @param context [SubsetContext]
11
+ # @param tag [String] "post"
12
+ # @param table [Post] parsed post table
13
+ # @return [String] binary post bytes for the subset
14
+ def self.call(context:, tag:, table:)
15
+ return build_v3(context) if context.options.drop_names
16
+
17
+ context.font.table_data["post"]
18
+ end
19
+
20
+ def self.build_v3(context)
21
+ data = String.new(encoding: Encoding::BINARY)
22
+ data << [0x00030000].pack("N") # version 3.0
23
+
24
+ original = context.font.table_data["post"]
25
+ data << if original&.length && original.length >= 32
26
+ original[4, 28]
27
+ else
28
+ [0, 0, 0, 0, 0, 0, 0].pack("N7")
29
+ end
30
+
31
+ data
32
+ end
33
+ private_class_method :build_v3
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Autoload hub for the Fontisan::Subset::TableStrategy namespace.
4
+ #
5
+ # Each table tag handled by the subsetter has a corresponding strategy
6
+ # class (e.g. `TableStrategy::Maxp` for the "maxp" tag). Strategies are
7
+ # stateless — they receive a [SubsetContext] + tag + table and return
8
+ # the subset table's binary bytes. Unknown tags fall back to
9
+ # [TableStrategy::PassThrough], which preserves the source bytes.
10
+ #
11
+ # Registering a new tag is a one-line change: add the autoload here and
12
+ # an entry in REGISTRY. No edits to TableSubsetter required (Open/Closed).
13
+
14
+ module Fontisan
15
+ module Subset
16
+ module TableStrategy
17
+ autoload :Cblc, "fontisan/subset/table_strategy/cblc"
18
+ autoload :Cbdt, "fontisan/subset/table_strategy/cbdt"
19
+ autoload :Cmap, "fontisan/subset/table_strategy/cmap"
20
+ autoload :ColorBitmapPlacement,
21
+ "fontisan/subset/table_strategy/color_bitmap_placement"
22
+ autoload :ColorBitmapStrikePlan,
23
+ "fontisan/subset/table_strategy/color_bitmap_strike_plan"
24
+ autoload :ColorBitmapSubsetPlan,
25
+ "fontisan/subset/table_strategy/color_bitmap_subset_plan"
26
+ autoload :ColorBitmapSubsetter,
27
+ "fontisan/subset/table_strategy/color_bitmap_subsetter"
28
+ autoload :ColorBitmapSubtablePlan,
29
+ "fontisan/subset/table_strategy/color_bitmap_subtable_plan"
30
+ autoload :Glyf, "fontisan/subset/table_strategy/glyf"
31
+ autoload :GlyfLocaBuilder,
32
+ "fontisan/subset/table_strategy/glyf_loca_builder"
33
+ autoload :Head, "fontisan/subset/table_strategy/head"
34
+ autoload :Hhea, "fontisan/subset/table_strategy/hhea"
35
+ autoload :Hmtx, "fontisan/subset/table_strategy/hmtx"
36
+ autoload :Loca, "fontisan/subset/table_strategy/loca"
37
+ autoload :Maxp, "fontisan/subset/table_strategy/maxp"
38
+ autoload :Name, "fontisan/subset/table_strategy/name"
39
+ autoload :Os2, "fontisan/subset/table_strategy/os2"
40
+ autoload :PassThrough, "fontisan/subset/table_strategy/pass_through"
41
+ autoload :Post, "fontisan/subset/table_strategy/post"
42
+
43
+ # Tag → strategy class name (resolved lazily via const_get to keep
44
+ # this file loadable in any order). Add new subsetting logic by
45
+ # adding one autoload + one entry here, plus a strategy file under
46
+ # `lib/fontisan/subset/table_strategy/`.
47
+ REGISTRY = {
48
+ "maxp" => :Maxp,
49
+ "hhea" => :Hhea,
50
+ "hmtx" => :Hmtx,
51
+ "loca" => :Loca,
52
+ "glyf" => :Glyf,
53
+ "cmap" => :Cmap,
54
+ "post" => :Post,
55
+ "name" => :Name,
56
+ "head" => :Head,
57
+ "OS/2" => :Os2,
58
+ "CBDT" => :Cbdt,
59
+ "CBLC" => :Cblc,
60
+ }.freeze
61
+
62
+ # Resolve a table tag to its strategy class. Returns PassThrough
63
+ # for any tag without an explicit registration.
64
+ #
65
+ # @param tag [String] OpenType table tag (e.g. "maxp", "CBDT")
66
+ # @return [Class] a strategy class responding to `.call`
67
+ def self.for(tag)
68
+ const_name = REGISTRY[tag]
69
+ return PassThrough unless const_name
70
+
71
+ const_get(const_name)
72
+ end
73
+ end
74
+ end
75
+ end