fontisan 0.4.19 → 0.4.20
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 +4 -4
- data/lib/fontisan/stitcher.rb +6 -10
- data/lib/fontisan/tables/cff2/index_builder.rb +1 -1
- data/lib/fontisan/ufo/cli.rb +5 -1
- data/lib/fontisan/ufo/compile/base_compiler.rb +26 -1
- data/lib/fontisan/ufo/compile/head.rb +1 -1
- data/lib/fontisan/ufo/compile/otf2_compiler.rb +1 -7
- data/lib/fontisan/ufo/compile/otf_compiler.rb +1 -7
- data/lib/fontisan/ufo/compile/ttf_compiler.rb +1 -1
- data/lib/fontisan/ufo/glyph.rb +24 -0
- data/lib/fontisan/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7eea2bdc6a2142dc7aec297e4753ea530f9b66d1e89f7cd4a83d8582bd511a8b
|
|
4
|
+
data.tar.gz: 2d6c594a71b01982f226e2c1db4f952280f167aaf2bc404b8bbfc2ef95bfea79
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 41e75040b3020c6ddcd22ca1567c8a7d44c6511d6fbdd886e6712de23b4ae7146a85fb64ef135c0e3aa73af3054c9c622315663c1bbd60ca744092cc2c6c6b0b
|
|
7
|
+
data.tar.gz: 7ffb7e4f4a4546d3c42f18ef92566693ca8250e287b307011afa8cd782769c328917eb7929c1bb664581b9c82fafa1355801893e08b8cddda60f222c68e467cc
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on 2026-07-07
|
|
3
|
+
# on 2026-07-07 23:48:55 UTC using RuboCop version 1.86.1.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
|
@@ -12,7 +12,7 @@ Gemspec/RequiredRubyVersion:
|
|
|
12
12
|
- 'docs/node_modules/speakingurl/speakingurl-rails.gemspec'
|
|
13
13
|
- 'fontisan.gemspec'
|
|
14
14
|
|
|
15
|
-
# Offense count:
|
|
15
|
+
# Offense count: 2102
|
|
16
16
|
# This cop supports safe autocorrection (--autocorrect).
|
|
17
17
|
# Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
|
|
18
18
|
# URISchemes: http, https
|
|
@@ -221,7 +221,7 @@ RSpec/DescribeMethod:
|
|
|
221
221
|
- 'spec/fontisan/type1/seac_expander_spec.rb'
|
|
222
222
|
- 'spec/fontisan/type1_real_fonts_spec.rb'
|
|
223
223
|
|
|
224
|
-
# Offense count:
|
|
224
|
+
# Offense count: 1664
|
|
225
225
|
# Configuration parameters: CountAsOne.
|
|
226
226
|
RSpec/ExampleLength:
|
|
227
227
|
Max: 66
|
|
@@ -298,7 +298,7 @@ RSpec/MultipleDescribes:
|
|
|
298
298
|
- 'spec/fontisan/utils/thread_pool_spec.rb'
|
|
299
299
|
- 'spec/fontisan/variation/cache_spec.rb'
|
|
300
300
|
|
|
301
|
-
# Offense count:
|
|
301
|
+
# Offense count: 2102
|
|
302
302
|
RSpec/MultipleExpectations:
|
|
303
303
|
Max: 19
|
|
304
304
|
|
data/lib/fontisan/stitcher.rb
CHANGED
|
@@ -273,9 +273,14 @@ module Fontisan
|
|
|
273
273
|
|
|
274
274
|
compiled = Fontisan::FontLoader.load(path)
|
|
275
275
|
|
|
276
|
+
# Read every table as raw bytes straight from the file's table
|
|
277
|
+
# directory. We deliberately bypass #table (which parses via
|
|
278
|
+
# BinData) because some tables — notably CFF2 — don't yet have
|
|
279
|
+
# round-trippable BinData models; calling #table on them returns
|
|
280
|
+
# nil and would silently drop them from the rewritten font.
|
|
276
281
|
tables = {}
|
|
277
282
|
compiled.table_names.each do |tag|
|
|
278
|
-
raw =
|
|
283
|
+
raw = compiled.table_data[tag]
|
|
279
284
|
tables[tag] = raw if raw
|
|
280
285
|
end
|
|
281
286
|
|
|
@@ -288,15 +293,6 @@ module Fontisan
|
|
|
288
293
|
Fontisan::FontWriter.write_to_file(tables, path, sfnt_version: sfnt)
|
|
289
294
|
end
|
|
290
295
|
|
|
291
|
-
def extract_raw_table(font, tag)
|
|
292
|
-
sfnt_table = font.table(tag)
|
|
293
|
-
return nil unless sfnt_table
|
|
294
|
-
|
|
295
|
-
sfnt_table.raw_data
|
|
296
|
-
rescue StandardError
|
|
297
|
-
nil
|
|
298
|
-
end
|
|
299
|
-
|
|
300
296
|
def add_all_cbdt_glyphs(source, target)
|
|
301
297
|
ufo = source.font.is_a?(Ufo::Font) ? source.font : nil
|
|
302
298
|
if ufo
|
|
@@ -66,7 +66,7 @@ module Fontisan
|
|
|
66
66
|
case off_size
|
|
67
67
|
when 1 then [value].pack("C")
|
|
68
68
|
when 2 then [value].pack("n")
|
|
69
|
-
when 3 then [value].pack("
|
|
69
|
+
when 3 then [(value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF].pack("CCC")
|
|
70
70
|
when 4 then [value].pack("N")
|
|
71
71
|
else raise ArgumentError, "invalid off_size: #{off_size}"
|
|
72
72
|
end
|
data/lib/fontisan/ufo/cli.rb
CHANGED
|
@@ -55,7 +55,11 @@ module Fontisan
|
|
|
55
55
|
issues << "no glyphs in default layer" if font.glyphs.empty?
|
|
56
56
|
issues << "no family name" unless font.info.family_name
|
|
57
57
|
issues << "unitsPerEm not set" unless font.info.units_per_em
|
|
58
|
-
|
|
58
|
+
# The compiler auto-injects an empty .notdef at GID 0 when the
|
|
59
|
+
# UFO source omits it (OpenType requires GID 0 to be .notdef).
|
|
60
|
+
# Surface this as a warning, not a failure — compilation will
|
|
61
|
+
# still succeed.
|
|
62
|
+
warn "NOTE missing .notdef glyph — compiler will inject one at GID 0" unless font.glyph(".notdef")
|
|
59
63
|
|
|
60
64
|
if issues.empty?
|
|
61
65
|
puts "OK #{ufo}"
|
|
@@ -43,7 +43,7 @@ module Fontisan
|
|
|
43
43
|
# All tables every OTF/TTF must have, plus optional feature
|
|
44
44
|
# tables (GPOS for kerning) when the UFO source has kerning data.
|
|
45
45
|
def build_tables
|
|
46
|
-
glyphs =
|
|
46
|
+
glyphs = glyphs_with_notdef
|
|
47
47
|
tables = {
|
|
48
48
|
"head" => Head.build(font, glyphs: glyphs),
|
|
49
49
|
"hhea" => Hhea.build(font, glyphs: glyphs),
|
|
@@ -62,6 +62,31 @@ module Fontisan
|
|
|
62
62
|
tables.merge(build_outline_tables)
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
# OpenType requires GID 0 to be `.notdef`. Normalize the source
|
|
66
|
+
# glyph list so that:
|
|
67
|
+
# - if `.notdef` is already at GID 0, the list is unchanged;
|
|
68
|
+
# - if `.notdef` exists elsewhere, it's moved to GID 0;
|
|
69
|
+
# - if no `.notdef` exists, an empty one is prepended.
|
|
70
|
+
#
|
|
71
|
+
# Without this, the alphabetically-first source glyph lands at
|
|
72
|
+
# GID 0 and the cmap parser (which treats GID 0 as `.notdef` per
|
|
73
|
+
# spec) silently drops it from the cmap.
|
|
74
|
+
def glyphs_with_notdef
|
|
75
|
+
source = font.glyphs.values
|
|
76
|
+
return source if source.empty?
|
|
77
|
+
|
|
78
|
+
existing = source.find { |g| g.name == ".notdef" }
|
|
79
|
+
return source if existing == source.first
|
|
80
|
+
|
|
81
|
+
rest = source - [existing]
|
|
82
|
+
notdef = existing || Ufo::Glyph.notdef(units_per_em: default_units_per_em)
|
|
83
|
+
[notdef, *rest]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def default_units_per_em
|
|
87
|
+
font.info&.units_per_em&.to_i || Ufo::Glyph::DEFAULT_UNITS_PER_EM
|
|
88
|
+
end
|
|
89
|
+
|
|
65
90
|
def write(tables_hash, output_path)
|
|
66
91
|
dir = File.dirname(output_path)
|
|
67
92
|
FileUtils.mkpath(dir) unless dir == "."
|
|
@@ -27,7 +27,7 @@ module Fontisan
|
|
|
27
27
|
checksum_adjustment: 0, # patched by FontWriter
|
|
28
28
|
magic_number: MAGIC_NUMBER,
|
|
29
29
|
flags: DEFAULT_FLAGS,
|
|
30
|
-
units_per_em: units_per_em || font.info.units_per_em ||
|
|
30
|
+
units_per_em: units_per_em || font.info.units_per_em || Glyph::DEFAULT_UNITS_PER_EM,
|
|
31
31
|
created_raw: time_to_longdatetime(font.info.created),
|
|
32
32
|
modified_raw: time_to_longdatetime(font.info.modified),
|
|
33
33
|
x_min: bbox[:x_min].to_i,
|
|
@@ -16,14 +16,8 @@ module Fontisan
|
|
|
16
16
|
class Otf2Compiler < BaseCompiler
|
|
17
17
|
SFNT_VERSION = SFNT_VERSION_OPEN_TYPE
|
|
18
18
|
|
|
19
|
-
def build_outline_tables
|
|
20
|
-
{
|
|
21
|
-
"CFF2" => Cff2.build(font, glyphs: font.glyphs.values),
|
|
22
|
-
}
|
|
23
|
-
end
|
|
24
|
-
|
|
25
19
|
def compile(output_path:)
|
|
26
|
-
glyphs =
|
|
20
|
+
glyphs = glyphs_with_notdef
|
|
27
21
|
|
|
28
22
|
tables = {
|
|
29
23
|
"head" => Head.build(font, glyphs: glyphs, loca_format: Head::LOCA_FORMAT_LONG),
|
|
@@ -13,14 +13,8 @@ module Fontisan
|
|
|
13
13
|
class OtfCompiler < BaseCompiler
|
|
14
14
|
SFNT_VERSION = SFNT_VERSION_OPEN_TYPE
|
|
15
15
|
|
|
16
|
-
def build_outline_tables
|
|
17
|
-
{
|
|
18
|
-
"CFF " => Cff.build(font, glyphs: font.glyphs.values),
|
|
19
|
-
}
|
|
20
|
-
end
|
|
21
|
-
|
|
22
16
|
def compile(output_path:)
|
|
23
|
-
glyphs =
|
|
17
|
+
glyphs = glyphs_with_notdef
|
|
24
18
|
|
|
25
19
|
tables = {
|
|
26
20
|
"head" => Head.build(font, glyphs: glyphs, loca_format: Head::LOCA_FORMAT_LONG),
|
|
@@ -14,7 +14,7 @@ module Fontisan
|
|
|
14
14
|
|
|
15
15
|
# @return [Hash<String, #to_binary_s>] all TTF tables, not yet written
|
|
16
16
|
def build_tables
|
|
17
|
-
glyphs =
|
|
17
|
+
glyphs = glyphs_with_notdef
|
|
18
18
|
|
|
19
19
|
# Deep-clone glyphs so filters don't mutate the source UFO.
|
|
20
20
|
filtered = clone_glyphs(glyphs)
|
data/lib/fontisan/ufo/glyph.rb
CHANGED
|
@@ -11,6 +11,30 @@ module Fontisan
|
|
|
11
11
|
# The `.glif` XML format has multiple revisions (1, 2, 3); the
|
|
12
12
|
# parser accepts all of them.
|
|
13
13
|
class Glyph
|
|
14
|
+
# OpenType requires GID 0 to be a glyph named `.notdef`. UFO
|
|
15
|
+
# sources that lack an explicit `.notdef` (synthetic SVG donors,
|
|
16
|
+
# programmatic sources) need one injected before compile. This
|
|
17
|
+
# constant is the default width used for that injected glyph, and
|
|
18
|
+
# doubles as the fallback unitsPerEm in Head.build when UFO info
|
|
19
|
+
# omits the field. Single source of truth for both call sites.
|
|
20
|
+
DEFAULT_UNITS_PER_EM = 1000
|
|
21
|
+
|
|
22
|
+
# Synthesize an empty `.notdef` glyph suitable for GID 0.
|
|
23
|
+
#
|
|
24
|
+
# OpenType mandates GID 0 be `.notdef`. UFO sources that lack an
|
|
25
|
+
# explicit `.notdef` (synthetic SVG donors, programmatic sources)
|
|
26
|
+
# need one injected before compile. The width defaults to the
|
|
27
|
+
# font's units_per_em so the .notdef consumes one em of advance.
|
|
28
|
+
#
|
|
29
|
+
# @param units_per_em [Integer, nil] Advance width for the
|
|
30
|
+
# synthesized glyph. Defaults to DEFAULT_UNITS_PER_EM when nil.
|
|
31
|
+
# @return [Glyph]
|
|
32
|
+
def self.notdef(units_per_em: nil)
|
|
33
|
+
new(name: ".notdef").tap do |g|
|
|
34
|
+
g.width = units_per_em&.to_i || DEFAULT_UNITS_PER_EM
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
14
38
|
attr_accessor :width, :height, :note, :lib
|
|
15
39
|
attr_reader :name, :unicodes, :contours, :components, :anchors,
|
|
16
40
|
:guidelines, :images
|
data/lib/fontisan/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fontisan
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.20
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|