ea 0.6.18 → 0.6.19
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/lib/ea/fonts/carlito_metrics.json +1 -1
- data/lib/ea/fonts/metrics.rb +74 -34
- data/lib/ea/version.rb +1 -1
- data/lib/tasks/fonts.rake +20 -15
- metadata +1 -1
data/lib/ea/fonts/metrics.rb
CHANGED
|
@@ -4,12 +4,14 @@ require "json"
|
|
|
4
4
|
|
|
5
5
|
module Ea
|
|
6
6
|
module Fonts
|
|
7
|
-
# Advance-width metrics for
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
7
|
+
# Advance-width metrics for the fonts EA's published SVGs use:
|
|
8
|
+
# Carlito (metric-compatible with Calibri), Arial, and Arial
|
|
9
|
+
# Narrow. EA's exported textLength values encode per-family,
|
|
10
|
+
# per-style, per-POINT-SIZE scale factors applied to the font's
|
|
11
|
+
# design advances (fitted by exact-count maximization against
|
|
12
|
+
# EA-published SVGs), so text widths computed here match EA's
|
|
13
|
+
# output to sub-pixel precision — far tighter than any legacy
|
|
14
|
+
# fitted-codepoint table, and style/size aware.
|
|
13
15
|
module Metrics
|
|
14
16
|
DATA_PATH = File.expand_path("carlito_metrics.json", __dir__)
|
|
15
17
|
|
|
@@ -21,16 +23,23 @@ module Ea
|
|
|
21
23
|
[true, true] => :bold_italic
|
|
22
24
|
}.freeze
|
|
23
25
|
|
|
24
|
-
# Families
|
|
25
|
-
|
|
26
|
+
# Families metric-compatible with a data-file family (the key
|
|
27
|
+
# is rendered even when the QEA named the other one).
|
|
28
|
+
FAMILY_ALIASES = {
|
|
29
|
+
"calibri" => "Carlito",
|
|
30
|
+
"carlito" => "Carlito",
|
|
31
|
+
"arial" => "Arial",
|
|
32
|
+
"arial narrow" => "Arial Narrow"
|
|
33
|
+
}.freeze
|
|
26
34
|
|
|
27
35
|
# Advance used for codepoints missing from the tables.
|
|
28
36
|
DEFAULT_ADVANCE = 0.25
|
|
29
37
|
|
|
30
|
-
# Carlito
|
|
31
|
-
# proportional CJK font whose advances measure ~0.709em at
|
|
32
|
-
# NOMINAL size (not scaled by the per-style factors) —
|
|
33
|
-
# from EA-published textLengths (52 mixed-script
|
|
38
|
+
# Carlito/Arial have no CJK glyphs; EA's renderer falls back to
|
|
39
|
+
# a proportional CJK font whose advances measure ~0.709em at
|
|
40
|
+
# the NOMINAL size (not scaled by the per-style factors) —
|
|
41
|
+
# fitted from EA-published textLengths (52 mixed-script
|
|
42
|
+
# samples).
|
|
34
43
|
CJK_ADVANCE = 0.709
|
|
35
44
|
CJK_RANGES = [
|
|
36
45
|
(0x3000..0x303F), # CJK punctuation
|
|
@@ -48,18 +57,23 @@ module Ea
|
|
|
48
57
|
return nil unless supported_family?(family)
|
|
49
58
|
return nil if text.nil? || text.empty?
|
|
50
59
|
|
|
51
|
-
latin, cjk_count = split_advance(text,
|
|
52
|
-
|
|
60
|
+
latin, cjk_count = split_advance(text, family: family,
|
|
61
|
+
weight: weight, style: style)
|
|
62
|
+
(latin * size_pt.to_f * factor_for(family, size_pt, weight, style) +
|
|
53
63
|
cjk_count * CJK_ADVANCE * size_pt.to_f).round(3)
|
|
54
64
|
end
|
|
55
65
|
|
|
56
66
|
def supported_family?(family)
|
|
57
|
-
|
|
67
|
+
!resolve_family(family).nil?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def resolve_family(family)
|
|
71
|
+
FAMILY_ALIASES[family.to_s.downcase]
|
|
58
72
|
end
|
|
59
73
|
|
|
60
74
|
# Returns [latin_em (factor-scaled later), cjk_glyph_count].
|
|
61
|
-
def split_advance(text, weight: nil, style: nil)
|
|
62
|
-
table = table_for(weight, style)
|
|
75
|
+
def split_advance(text, family: nil, weight: nil, style: nil)
|
|
76
|
+
table = table_for(family, weight, style)
|
|
63
77
|
latin = 0.0
|
|
64
78
|
cjk = 0
|
|
65
79
|
text.each_char.each do |ch|
|
|
@@ -76,12 +90,20 @@ module Ea
|
|
|
76
90
|
CJK_RANGES.any? { |range| range.cover?(cp) }
|
|
77
91
|
end
|
|
78
92
|
|
|
79
|
-
|
|
80
|
-
|
|
93
|
+
# EA's scale factor varies by point size (GDI quantization):
|
|
94
|
+
# Carlito regular fits 1.390 at 7pt but 1.432 at 9pt. Sizes
|
|
95
|
+
# without a fitted entry use the style's default (7pt, the
|
|
96
|
+
# corpus's dominant size).
|
|
97
|
+
def factor_for(family, size_pt, weight, style)
|
|
98
|
+
style = STYLE_KEYS.fetch([bold?(weight), italic?(style)], :regular).to_s
|
|
99
|
+
table = data["factors"].fetch(resolve_family(family), {})
|
|
100
|
+
.fetch(style, {})
|
|
101
|
+
table[size_pt.to_i.to_s] || table["default"] || 1.0
|
|
81
102
|
end
|
|
82
103
|
|
|
83
|
-
def table_for(weight, style)
|
|
84
|
-
|
|
104
|
+
def table_for(family, weight, style)
|
|
105
|
+
style = STYLE_KEYS.fetch([bold?(weight), italic?(style)], :regular).to_s
|
|
106
|
+
data["advance"].fetch(resolve_family(family), {}).fetch(style, {})
|
|
85
107
|
end
|
|
86
108
|
|
|
87
109
|
def data
|
|
@@ -100,28 +122,46 @@ module Ea
|
|
|
100
122
|
format("U+%04X", ch.ord)
|
|
101
123
|
end
|
|
102
124
|
|
|
103
|
-
# Resolves
|
|
104
|
-
# (system index + downloaded formulas).
|
|
105
|
-
#
|
|
106
|
-
|
|
125
|
+
# Resolves a family's four style TTFs through the fontist gem
|
|
126
|
+
# (system index + downloaded formulas). Style variants are
|
|
127
|
+
# identified from each file's head.macStyle bits — filenames
|
|
128
|
+
# vary across fontist formulas and system copies ("ArialBd",
|
|
129
|
+
# "Arial Narrow Bold"), so name-matching is unreliable. The
|
|
130
|
+
# first file matching each style wins (fontist orders the
|
|
131
|
+
# downloaded formulas ahead of system copies). Empty when
|
|
132
|
+
# fontist is unavailable or the family is not installed.
|
|
133
|
+
def font_files_for(family)
|
|
107
134
|
require "fontist"
|
|
108
|
-
|
|
109
|
-
Fontist::SystemFont.find("Carlito").filter_map do |path|
|
|
135
|
+
Fontist::SystemFont.find(family).each_with_object({}) do |path, out|
|
|
110
136
|
next unless path.downcase.end_with?(".ttf")
|
|
111
137
|
|
|
112
|
-
style =
|
|
113
|
-
|
|
114
|
-
next unless match
|
|
138
|
+
style = style_from_mac_style(path)
|
|
139
|
+
next unless style
|
|
115
140
|
|
|
116
|
-
[
|
|
117
|
-
end
|
|
141
|
+
out[style] ||= path
|
|
142
|
+
end
|
|
118
143
|
rescue LoadError, StandardError => e
|
|
119
|
-
raise if ENV[
|
|
144
|
+
raise if ENV["EA_FONTS_DEBUG"]
|
|
120
145
|
|
|
121
146
|
{}
|
|
122
147
|
end
|
|
123
148
|
|
|
124
|
-
#
|
|
149
|
+
# head.macStyle: bit 0 = BOLD, bit 1 = ITALIC.
|
|
150
|
+
def style_from_mac_style(path)
|
|
151
|
+
require "fontisan"
|
|
152
|
+
font = Fontisan::FontLoader.load(path)
|
|
153
|
+
mac_style = font.table("head").mac_style
|
|
154
|
+
key = [mac_style & 1 == 1, mac_style & 2 == 2]
|
|
155
|
+
STYLE_KEYS[key]
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Carlito's data-file name (historical) differs from the
|
|
159
|
+
# fontist family name used to resolve its TTFs.
|
|
160
|
+
def carlito_font_files
|
|
161
|
+
font_files_for("Carlito")
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Reads advance widths from a TTF via the fontisan gem (used by
|
|
125
165
|
# `rake fonts:validate` to re-derive the tables).
|
|
126
166
|
def advances_from_font(path)
|
|
127
167
|
require "fontisan"
|
data/lib/ea/version.rb
CHANGED
data/lib/tasks/fonts.rake
CHANGED
|
@@ -3,30 +3,35 @@
|
|
|
3
3
|
require "ea/fonts/metrics"
|
|
4
4
|
|
|
5
5
|
namespace :fonts do
|
|
6
|
-
desc "Validate the shipped
|
|
6
|
+
desc "Validate the shipped font metrics against Fontist-resolved TTFs"
|
|
7
7
|
task :validate do
|
|
8
|
-
files = Ea::Fonts::Metrics.carlito_font_files
|
|
9
|
-
abort "Carlito not resolvable via Fontist — install it first" if files.empty?
|
|
10
|
-
|
|
11
8
|
shipped = JSON.parse(File.read(Ea::Fonts::Metrics::DATA_PATH))["advance"]
|
|
12
9
|
style_key = { "regular" => "regular", "bold" => "bold",
|
|
13
10
|
"italic" => "italic", "bold_italic" => "bold_italic",
|
|
14
11
|
"bolditalic" => "bold_italic" }
|
|
15
12
|
drift = []
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
shipped.keys.each do |family|
|
|
15
|
+
files = Ea::Fonts::Metrics.font_files_for(family)
|
|
16
|
+
if files.empty?
|
|
17
|
+
puts "#{family}: NOT RESOLVABLE VIA FONTIST — skipped"
|
|
18
|
+
next
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
files.each do |style, path|
|
|
22
|
+
table = shipped[family][style_key[style.downcase]] || {}
|
|
23
|
+
actual = Ea::Fonts::Metrics.advances_from_font(path)
|
|
24
|
+
missing = actual.keys.count { |cp| table[format("U+%04X", cp)].nil? }
|
|
25
|
+
puts " (shipped #{family}/#{style} table lacks #{missing} of #{actual.size} codepoints — skipped)"
|
|
26
|
+
actual.each do |cp, adv|
|
|
27
|
+
key = format("U+%04X", cp)
|
|
28
|
+
ours = table[key]
|
|
29
|
+
next if ours.nil? || (ours - adv).abs <= 0.0005
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
drift << format("%s/%s %s: json=%s font=%s", family, style, key, ours, adv)
|
|
32
|
+
end
|
|
33
|
+
puts "#{family}/#{style}: #{path} — #{actual.size} codepoints checked"
|
|
28
34
|
end
|
|
29
|
-
puts "#{style}: #{path} — #{actual.size} codepoints checked"
|
|
30
35
|
end
|
|
31
36
|
|
|
32
37
|
if drift.empty?
|