fontisan 0.4.2 → 0.4.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14c31b390c5721b221f5c04239baf1d2330b668d142addd3e321b3bd868f7b7a
4
- data.tar.gz: 726fe81623ef7f410aa09e3b7a7838b8e0df1c2304cf9b36d1ad0727bdcf03e8
3
+ metadata.gz: 46b8f1b8bf47cb6b91bc1871fc136e0077a7c2d18e99526161ae2ea4085c29d5
4
+ data.tar.gz: d374dea6fa7de74f605702da037e93fd6a94b8f5082459231b30223a7b8df86f
5
5
  SHA512:
6
- metadata.gz: f4c11a54cd876590a4e52df790eeadc490da5b3bad9ef2a0ff6186f560ff2b584c29bc2f7fac1881f25771a3a7031b1d1db5d7cf5c0c67c057ec254165d6b2ac
7
- data.tar.gz: 556737df14831d0bf14a82ed34e27f9438f93fe0d1c51b6324d0e3f938de3b7ce136bcb0b4a3a8a50698e7e603a02227fd87ee74de530df60e81fcbd5d3a4e5b
6
+ metadata.gz: 83dc37f826b6f03c37c5ebbac2c8ed5022d0265684af16e308d6aa4b3993b04bd44d61fbd6b538106daf7dd4051c7f5894618856193cc4c60bef547338eaac6f
7
+ data.tar.gz: f85fc773fbc61b7fb7ea7a41b8b34a05f063584e0992a88e8d0b96d0a5abc73716b4d25df0e75e80388ef582066f5ac009752dbc623d832d76237b4d0ac0d877
@@ -40,10 +40,11 @@ module Fontisan
40
40
 
41
41
  private
42
42
 
43
- # All tables every OTF/TTF must have.
43
+ # All tables every OTF/TTF must have, plus optional feature
44
+ # tables (GPOS for kerning) when the UFO source has kerning data.
44
45
  def build_tables
45
46
  glyphs = font.glyphs.values
46
- {
47
+ tables = {
47
48
  "head" => Head.build(font, glyphs: glyphs),
48
49
  "hhea" => Hhea.build(font, glyphs: glyphs),
49
50
  "maxp" => Maxp.build(font, glyphs: glyphs),
@@ -52,7 +53,13 @@ module Fontisan
52
53
  "post" => Post.build(font, glyphs: glyphs),
53
54
  "hmtx" => Hmtx.build(font, glyphs: glyphs),
54
55
  "cmap" => Cmap.build(font, glyphs: glyphs),
55
- }.merge(build_outline_tables)
56
+ }
57
+
58
+ # GPOS kern table (only when the UFO source has kerning pairs)
59
+ gpos = Gpos.build(font, glyphs: glyphs)
60
+ tables["GPOS"] = gpos if gpos
61
+
62
+ tables.merge(build_outline_tables)
56
63
  end
57
64
 
58
65
  def write(tables_hash, output_path)
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fontisan
4
+ module Ufo
5
+ module Compile
6
+ # Builds the OpenType `fvar` (Font Variations) table from a
7
+ # list of axis definitions and named instances.
8
+ #
9
+ # @see https://learn.microsoft.com/en-us/typography/opentype/spec/fvar
10
+ module Fvar
11
+ VERSION_MAJOR = 1
12
+ VERSION_MINOR = 0
13
+ AXIS_SIZE = 20
14
+ INSTANCE_SIZE = 4 # + 4 per axis
15
+ FIXED_SHIFT = 16
16
+ FIXED_ONE = 1 << FIXED_SHIFT
17
+
18
+ # @param font [Fontisan::Ufo::Font]
19
+ # @param axes [Array<Hash>] axis definitions
20
+ # @param instances [Array<Hash>] named instance definitions
21
+ # @return [String, nil] fvar table bytes, or nil if no axes
22
+ def self.build(font, axes: nil, instances: nil)
23
+ axes ||= font.info.respond_to?(:axes) ? font.info.axes : nil
24
+ instances ||= font.info.respond_to?(:named_instances) ? font.info.named_instances : nil
25
+
26
+ axes ||= []
27
+ return nil if axes.empty?
28
+
29
+ axes_bytes = build_axes(axes)
30
+ instances_bytes = build_instances(instances || [], axes.size)
31
+ header = build_header(axes.size, axes_bytes, instances_bytes, instances)
32
+ header + axes_bytes + instances_bytes
33
+ end
34
+
35
+ # ---------- header ----------
36
+
37
+ # Header is 16 bytes:
38
+ # majorVersion (2) + minorVersion (2) + axesArrayOffset (2)
39
+ # + reserved (2) + axisCount (2) + axisSize (2)
40
+ # + instanceCount (2) + instanceSize (2)
41
+ def self.build_header(axis_count, axes_bytes, _instances_bytes, instances)
42
+ axes_offset = 16
43
+ axes_offset + axes_bytes.bytesize
44
+
45
+ [
46
+ VERSION_MAJOR,
47
+ VERSION_MINOR,
48
+ axes_offset,
49
+ 0, # reserved
50
+ axis_count,
51
+ AXIS_SIZE,
52
+ instances&.size || 0,
53
+ (instances&.size || 0).zero? ? 0 : (INSTANCE_SIZE + axis_count * 4),
54
+ ].pack("nnnnnnnn")
55
+ end
56
+
57
+ # ---------- axes ----------
58
+
59
+ # Each axis record is 20 bytes:
60
+ # axisTag (4) + minValue (4) + defaultValue (4) + maxValue (4)
61
+ # + flags (2) + axisNameID (2)
62
+ def self.build_axes(axes)
63
+ io = +""
64
+ axes.each do |axis|
65
+ tag = (axis[:tag] || axis["tag"] || "wght").to_s.ljust(4, " ")[0, 4]
66
+ min = fixed_value(axis[:min] || axis["min"] || 100)
67
+ default = fixed_value(axis[:default] || axis["default"] || 400)
68
+ max = fixed_value(axis[:max] || axis["max"] || 900)
69
+ flags = axis[:flags] || axis["flags"] || 0
70
+ name_id = axis[:name_id] || axis["name_id"] || 0
71
+
72
+ io << tag
73
+ io << [min, default, max, flags, name_id].pack("NNNnn")
74
+ end
75
+ io
76
+ end
77
+
78
+ # ---------- instances ----------
79
+
80
+ # Each instance record: subfamilyNameID (2) + flags (2) + per-axis coords
81
+ def self.build_instances(instances, axis_count)
82
+ return +"" if instances.empty?
83
+
84
+ io = +""
85
+ instances.each do |inst|
86
+ name_id = inst[:name_id] || inst["name_id"] || 0
87
+ flags = inst[:flags] || inst["flags"] || 0
88
+ coords = inst[:coords] || inst["coords"] || []
89
+ padded = coords.first(axis_count) + Array.new([axis_count - coords.size, 0].max, 0)
90
+ padded = padded.first(axis_count).map { |c| fixed_value(c) }
91
+
92
+ io << [name_id, flags].pack("nn")
93
+ io << padded.pack("N*")
94
+ end
95
+ io
96
+ end
97
+
98
+ # ---------- helpers ----------
99
+
100
+ # Convert a float to OpenType Fixed 16.16 (int32).
101
+ def self.fixed_value(value)
102
+ (value.to_f * FIXED_ONE).to_i
103
+ end
104
+ private_class_method :build_header, :build_axes, :build_instances,
105
+ :fixed_value
106
+ end
107
+ end
108
+ end
109
+ end
@@ -33,6 +33,7 @@ module Fontisan
33
33
  autoload :TtfCompiler, "fontisan/ufo/compile/ttf_compiler"
34
34
  autoload :OtfCompiler, "fontisan/ufo/compile/otf_compiler"
35
35
  autoload :Filters, "fontisan/ufo/compile/filters"
36
+ autoload :Fvar, "fontisan/ufo/compile/fvar"
36
37
  autoload :Gpos, "fontisan/ufo/compile/gpos"
37
38
  autoload :Head, "fontisan/ufo/compile/head"
38
39
  autoload :Hhea, "fontisan/ufo/compile/hhea"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fontisan
4
- VERSION = "0.4.2"
4
+ VERSION = "0.4.4"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fontisan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -563,6 +563,7 @@ files:
563
563
  - lib/fontisan/ufo/compile/filters/decompose_components.rb
564
564
  - lib/fontisan/ufo/compile/filters/flatten_components.rb
565
565
  - lib/fontisan/ufo/compile/filters/reverse_contour_direction.rb
566
+ - lib/fontisan/ufo/compile/fvar.rb
566
567
  - lib/fontisan/ufo/compile/glyf_loca.rb
567
568
  - lib/fontisan/ufo/compile/gpos.rb
568
569
  - lib/fontisan/ufo/compile/head.rb