fontisan 0.4.3 → 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: 5b68479ccaac604474e929d9dc4b3de24ec1c79f53e187fd7394cafdf3e02a11
4
- data.tar.gz: 27c7dd8fd5afed7656aaaba9a57ea52c78144d66a4346bd4d4b0285c6327c176
3
+ metadata.gz: 46b8f1b8bf47cb6b91bc1871fc136e0077a7c2d18e99526161ae2ea4085c29d5
4
+ data.tar.gz: d374dea6fa7de74f605702da037e93fd6a94b8f5082459231b30223a7b8df86f
5
5
  SHA512:
6
- metadata.gz: 55d3133ec31fb397c671039a8181c5182b62a9cac0108df9a45d551b5def47475d2edbef7e9d921727ad812734351234da82f71c7b8fd1314a16dab83261aa6a
7
- data.tar.gz: 4a59af9b12d398575f45018511eeeaea0e49b827efd223f3052ff48f12053087f0e0749498a6be3049f5adb8c880e8461c28167ae7601a7d3dbcaa403e7380e1
6
+ metadata.gz: 83dc37f826b6f03c37c5ebbac2c8ed5022d0265684af16e308d6aa4b3993b04bd44d61fbd6b538106daf7dd4051c7f5894618856193cc4c60bef547338eaac6f
7
+ data.tar.gz: f85fc773fbc61b7fb7ea7a41b8b34a05f063584e0992a88e8d0b96d0a5abc73716b4d25df0e75e80388ef582066f5ac009752dbc623d832d76237b4d0ac0d877
@@ -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.3"
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.3
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