fontisan 0.2.22 → 0.2.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/.rubocop_todo.yml +93 -17
- data/CHANGELOG.md +12 -2
- data/README.adoc +6 -210
- data/fontisan.gemspec +48 -0
- data/lib/fontisan/cldr/unicode_set_parser.rb +23 -6
- data/lib/fontisan/cldr/version_resolver.rb +1 -1
- data/lib/fontisan/cli.rb +0 -170
- data/lib/fontisan/commands.rb +0 -3
- data/lib/fontisan/formatters/text_formatter.rb +0 -6
- data/lib/fontisan/formatters.rb +0 -3
- data/lib/fontisan/hints.rb +6 -3
- data/lib/fontisan/models.rb +4 -4
- data/lib/fontisan/pipeline/strategies.rb +4 -2
- data/lib/fontisan/pipeline.rb +2 -1
- data/lib/fontisan/tables/cff.rb +2 -1
- data/lib/fontisan/tables.rb +2 -1
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan.rb +0 -3
- metadata +7 -70
- data/lib/fontisan/audit/codepoint_range_coalescer.rb +0 -41
- data/lib/fontisan/audit/context.rb +0 -122
- data/lib/fontisan/audit/differ.rb +0 -124
- data/lib/fontisan/audit/extractors/aggregations.rb +0 -54
- data/lib/fontisan/audit/extractors/base.rb +0 -26
- data/lib/fontisan/audit/extractors/color_capabilities.rb +0 -141
- data/lib/fontisan/audit/extractors/coverage.rb +0 -48
- data/lib/fontisan/audit/extractors/hinting.rb +0 -197
- data/lib/fontisan/audit/extractors/identity.rb +0 -52
- data/lib/fontisan/audit/extractors/language_coverage.rb +0 -37
- data/lib/fontisan/audit/extractors/licensing.rb +0 -79
- data/lib/fontisan/audit/extractors/metrics.rb +0 -103
- data/lib/fontisan/audit/extractors/opentype_layout.rb +0 -69
- data/lib/fontisan/audit/extractors/provenance.rb +0 -29
- data/lib/fontisan/audit/extractors/style.rb +0 -32
- data/lib/fontisan/audit/extractors/variation_detail.rb +0 -99
- data/lib/fontisan/audit/extractors.rb +0 -27
- data/lib/fontisan/audit/library_aggregator.rb +0 -83
- data/lib/fontisan/audit/library_auditor.rb +0 -90
- data/lib/fontisan/audit/registry.rb +0 -60
- data/lib/fontisan/audit/style_extractor.rb +0 -80
- data/lib/fontisan/audit.rb +0 -20
- data/lib/fontisan/cli/ucd_cli.rb +0 -97
- data/lib/fontisan/commands/audit_command.rb +0 -123
- data/lib/fontisan/commands/audit_compare_command.rb +0 -66
- data/lib/fontisan/commands/audit_library_command.rb +0 -46
- data/lib/fontisan/config/ucd.yml +0 -23
- data/lib/fontisan/formatters/audit_diff_text_renderer.rb +0 -122
- data/lib/fontisan/formatters/audit_text_renderer.rb +0 -324
- data/lib/fontisan/formatters/library_summary_text_renderer.rb +0 -99
- data/lib/fontisan/models/audit/audit_axis.rb +0 -30
- data/lib/fontisan/models/audit/audit_block.rb +0 -32
- data/lib/fontisan/models/audit/audit_diff.rb +0 -77
- data/lib/fontisan/models/audit/audit_report.rb +0 -153
- data/lib/fontisan/models/audit/codepoint_range.rb +0 -40
- data/lib/fontisan/models/audit/codepoint_set_diff.rb +0 -34
- data/lib/fontisan/models/audit/color_capabilities.rb +0 -93
- data/lib/fontisan/models/audit/duplicate_group.rb +0 -23
- data/lib/fontisan/models/audit/embedding_type.rb +0 -76
- data/lib/fontisan/models/audit/field_change.rb +0 -28
- data/lib/fontisan/models/audit/fs_selection_flags.rb +0 -61
- data/lib/fontisan/models/audit/gasp_range.rb +0 -63
- data/lib/fontisan/models/audit/hinting.rb +0 -93
- data/lib/fontisan/models/audit/library_summary.rb +0 -40
- data/lib/fontisan/models/audit/licensing.rb +0 -48
- data/lib/fontisan/models/audit/metrics.rb +0 -111
- data/lib/fontisan/models/audit/named_instance.rb +0 -41
- data/lib/fontisan/models/audit/opentype_layout.rb +0 -40
- data/lib/fontisan/models/audit/script_coverage_row.rb +0 -26
- data/lib/fontisan/models/audit/script_features.rb +0 -28
- data/lib/fontisan/models/audit/variation_detail.rb +0 -44
- data/lib/fontisan/models/audit.rb +0 -33
- data/lib/fontisan/models/ucd/ucd.rb +0 -38
- data/lib/fontisan/models/ucd/ucd_char.rb +0 -67
- data/lib/fontisan/models/ucd.rb +0 -19
- data/lib/fontisan/ucd/aggregator.rb +0 -73
- data/lib/fontisan/ucd/cache_manager.rb +0 -111
- data/lib/fontisan/ucd/config.rb +0 -59
- data/lib/fontisan/ucd/download_error.rb +0 -9
- data/lib/fontisan/ucd/downloader.rb +0 -88
- data/lib/fontisan/ucd/error.rb +0 -8
- data/lib/fontisan/ucd/index.rb +0 -103
- data/lib/fontisan/ucd/index_builder.rb +0 -107
- data/lib/fontisan/ucd/range_entry.rb +0 -56
- data/lib/fontisan/ucd/unknown_version_error.rb +0 -9
- data/lib/fontisan/ucd/version_resolver.rb +0 -79
- data/lib/fontisan/ucd.rb +0 -23
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Fontisan
|
|
4
|
-
module Formatters
|
|
5
|
-
# Human-readable, sectioned view of an {Models::Audit::AuditReport}.
|
|
6
|
-
#
|
|
7
|
-
# The text formatter is the default `--format text` output for
|
|
8
|
-
# `fontisan audit`. Complements YAML/JSON (machine-facing) with a
|
|
9
|
-
# terse, scannable terminal view. Every section is nil-safe so the
|
|
10
|
-
# same renderer covers full OpenType/TrueType faces, Type 1 fonts
|
|
11
|
-
# (no OS/2, no metrics, no layout), and partial reports.
|
|
12
|
-
class AuditTextRenderer
|
|
13
|
-
SEPARATOR = "=" * 80
|
|
14
|
-
LABEL_WIDTH = 18
|
|
15
|
-
LIST_LIMIT = 10
|
|
16
|
-
|
|
17
|
-
WIDTH_NAMES = {
|
|
18
|
-
1 => "Ultra-condensed", 2 => "Extra-condensed", 3 => "Condensed",
|
|
19
|
-
4 => "Semi-condensed", 5 => "Medium", 6 => "Semi-expanded",
|
|
20
|
-
7 => "Expanded", 8 => "Extra-expanded", 9 => "Ultra-expanded"
|
|
21
|
-
}.freeze
|
|
22
|
-
|
|
23
|
-
# @param report [Models::Audit::AuditReport]
|
|
24
|
-
def initialize(report)
|
|
25
|
-
@report = report
|
|
26
|
-
@lines = []
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# @return [String]
|
|
30
|
-
def render
|
|
31
|
-
render_header
|
|
32
|
-
render_identity
|
|
33
|
-
render_style
|
|
34
|
-
render_metrics
|
|
35
|
-
render_coverage
|
|
36
|
-
render_blocks
|
|
37
|
-
render_licensing
|
|
38
|
-
render_hinting
|
|
39
|
-
render_color
|
|
40
|
-
render_variation
|
|
41
|
-
render_opentype_layout
|
|
42
|
-
render_language_coverage
|
|
43
|
-
render_warnings
|
|
44
|
-
@lines.join("\n")
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
def render_header
|
|
50
|
-
@lines << (@report.postscript_name || @report.family_name || "(unknown)")
|
|
51
|
-
@lines << SEPARATOR
|
|
52
|
-
@lines << two_col("generated_at:", @report.generated_at,
|
|
53
|
-
"fontisan:", @report.fontisan_version)
|
|
54
|
-
@lines << "source_sha256: #{@report.source_sha256}"
|
|
55
|
-
@lines << "source_file: #{@report.source_file}"
|
|
56
|
-
@lines << two_col("source_format:", @report.source_format,
|
|
57
|
-
"layout:", layout_descriptor)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def layout_descriptor
|
|
61
|
-
if @report.num_fonts_in_source.nil? || @report.num_fonts_in_source <= 1
|
|
62
|
-
"single face (1/1)"
|
|
63
|
-
else
|
|
64
|
-
format("collection face (%<idx>d/%<total>d)",
|
|
65
|
-
idx: (@report.font_index || 0) + 1,
|
|
66
|
-
total: @report.num_fonts_in_source)
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def render_identity
|
|
71
|
-
section("IDENTITY")
|
|
72
|
-
row("Family", @report.family_name)
|
|
73
|
-
row("Subfamily", @report.subfamily_name)
|
|
74
|
-
row("Full name", @report.full_name)
|
|
75
|
-
row("PostScript", @report.postscript_name)
|
|
76
|
-
row("Version", @report.version)
|
|
77
|
-
row("Revision", @report.font_revision)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def render_style
|
|
81
|
-
section("STYLE")
|
|
82
|
-
row("Weight class", weight_descriptor)
|
|
83
|
-
row("Width class", width_descriptor)
|
|
84
|
-
row("Bold", yes_no(@report.bold))
|
|
85
|
-
row("Italic", yes_no(@report.italic))
|
|
86
|
-
row("PANOSE", @report.panose)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def render_metrics
|
|
90
|
-
return unless @report.metrics
|
|
91
|
-
|
|
92
|
-
m = @report.metrics
|
|
93
|
-
section("METRICS")
|
|
94
|
-
row("unitsPerEm", m.units_per_em)
|
|
95
|
-
row("hhea", "ascent: #{m.hhea_ascent} / descent: #{m.hhea_descent} / line gap: #{m.hhea_line_gap}") if m.hhea_ascent
|
|
96
|
-
row("OS/2 typo", "ascent: #{m.typo_ascender} / descent: #{m.typo_descender} / line gap: #{m.typo_line_gap}") if m.typo_ascender
|
|
97
|
-
row("OS/2 win", "ascent: #{m.win_ascent} / descent: #{m.win_descent}") if m.win_ascent
|
|
98
|
-
row("x-height", m.x_height)
|
|
99
|
-
row("cap height", m.cap_height)
|
|
100
|
-
row("bbox", bbox_descriptor(m)) if m.bbox_x_min || m.bbox_x_max
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def render_coverage
|
|
104
|
-
section("COVERAGE")
|
|
105
|
-
row("Codepoints", @report.total_codepoints)
|
|
106
|
-
row("Glyphs", @report.total_glyphs)
|
|
107
|
-
row("cmap subtables", format("%s", Array(@report.cmap_subtables).join(", "))) unless Array(@report.cmap_subtables).empty?
|
|
108
|
-
row("Ranges (top #{LIST_LIMIT})", codepoint_range_preview)
|
|
109
|
-
row("Unicode scripts", truncate_list(@report.unicode_scripts))
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def render_blocks
|
|
113
|
-
blocks = Array(@report.blocks)
|
|
114
|
-
return if blocks.empty?
|
|
115
|
-
|
|
116
|
-
section("UNICODE BLOCKS (top #{LIST_LIMIT} by fill ratio)")
|
|
117
|
-
blocks.sort_by { |b| -(b.fill_ratio || 0) }.first(LIST_LIMIT).each do |block|
|
|
118
|
-
ratio = block.fill_ratio ? format("%<r>d%%", r: (block.fill_ratio * 100).round) : "?"
|
|
119
|
-
@lines << format(" %<name>-40s %<covered>d/%<total>d (%<ratio>s)",
|
|
120
|
-
name: "#{block.name}:", covered: block.covered || 0,
|
|
121
|
-
total: block.total || 0, ratio: ratio)
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def render_licensing
|
|
126
|
-
return unless @report.licensing
|
|
127
|
-
|
|
128
|
-
l = @report.licensing
|
|
129
|
-
section("LICENSING")
|
|
130
|
-
row("Copyright", l.copyright)
|
|
131
|
-
row("Trademark", l.trademark)
|
|
132
|
-
row("Manufacturer", l.manufacturer)
|
|
133
|
-
row("Designer", l.designer)
|
|
134
|
-
row("License", l.license_description)
|
|
135
|
-
row("License URL", l.license_url)
|
|
136
|
-
row("Vendor URL", l.vendor_url)
|
|
137
|
-
row("Designer URL", l.designer_url)
|
|
138
|
-
row("Vendor ID", l.vendor_id)
|
|
139
|
-
row("Embedding", l.embedding_type)
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def render_hinting
|
|
143
|
-
return unless @report.hinting
|
|
144
|
-
|
|
145
|
-
h = @report.hinting
|
|
146
|
-
section("HINTING")
|
|
147
|
-
row("Format", h.hinting_format || (h.is_unhinted ? "unhinted" : "unknown"))
|
|
148
|
-
row("fpgm", instruction_line(h.has_fpgm, h.fpgm_instruction_count))
|
|
149
|
-
row("prep", instruction_line(h.has_prep, h.prep_instruction_count))
|
|
150
|
-
row("cvt", cvt_line(h))
|
|
151
|
-
row("gasp", gasp_line(h))
|
|
152
|
-
row("CFF hints", h.cff_hint_count)
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def render_color
|
|
156
|
-
return unless @report.color_capabilities
|
|
157
|
-
|
|
158
|
-
c = @report.color_capabilities
|
|
159
|
-
section("COLOR")
|
|
160
|
-
formats = Array(c.color_formats)
|
|
161
|
-
row("Color formats", formats.empty? ? "(none)" : truncate_list(formats))
|
|
162
|
-
row("COLR", colr_line(c)) if c.has_colr
|
|
163
|
-
row("CPAL", "palettes: #{c.cpal_palette_count}, colors: #{c.cpal_color_count}") if c.has_cpal
|
|
164
|
-
row("SVG documents", c.svg_document_count) if c.has_svg && c.svg_document_count
|
|
165
|
-
row("CBDT strikes", c.cbdt_strike_count) if c.has_cbdt && c.cbdt_strike_count
|
|
166
|
-
row("sbix strikes", c.sbix_strike_count) if c.has_sbix && c.sbix_strike_count
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
def render_variation
|
|
170
|
-
v = @report.variation
|
|
171
|
-
section("VARIABLE FONT")
|
|
172
|
-
if v.nil? || Array(v.axes).empty?
|
|
173
|
-
@lines << " (not variable)"
|
|
174
|
-
return
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
v.axes.each do |axis|
|
|
178
|
-
row(axis.tag, format("%<min>s .. %<max>s default %<default>s",
|
|
179
|
-
min: axis.min_value, max: axis.max_value,
|
|
180
|
-
default: axis.default_value))
|
|
181
|
-
end
|
|
182
|
-
return if Array(v.named_instances).empty?
|
|
183
|
-
|
|
184
|
-
@lines << " Named instances:"
|
|
185
|
-
v.named_instances.each do |inst|
|
|
186
|
-
@lines << " #{inst.postscript_name || inst.subfamily_name}: #{inst.coordinates}"
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
def render_opentype_layout
|
|
191
|
-
return unless @report.opentype_layout
|
|
192
|
-
|
|
193
|
-
l = @report.opentype_layout
|
|
194
|
-
section("OPENTYPE LAYOUT")
|
|
195
|
-
row("GSUB", yes_no(l.has_gsub))
|
|
196
|
-
row("GPOS", yes_no(l.has_gpos))
|
|
197
|
-
row("Scripts (#{Array(l.scripts).size})", truncate_list(l.scripts))
|
|
198
|
-
row("Features (#{Array(l.features).size})", truncate_list(l.features))
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
def render_language_coverage
|
|
202
|
-
langs = Array(@report.language_coverage)
|
|
203
|
-
return if langs.empty?
|
|
204
|
-
|
|
205
|
-
section("LANGUAGE COVERAGE (CLDR #{@report.cldr_version})")
|
|
206
|
-
langs.first(LIST_LIMIT).each do |lang|
|
|
207
|
-
pct = lang.coverage_ratio ? format("%<r>d%%", r: (lang.coverage_ratio * 100).round) : "?"
|
|
208
|
-
mark = lang.fully_supported ? "*" : " "
|
|
209
|
-
@lines << format(" %<mark>s %<lang>-8s %<covered>d/%<total>d (%<pct>s)",
|
|
210
|
-
mark: mark, lang: "#{lang.language}:", covered: lang.covered,
|
|
211
|
-
total: lang.total, pct: pct)
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
def render_warnings
|
|
216
|
-
section("WARNINGS")
|
|
217
|
-
@lines << if @report.warning
|
|
218
|
-
" #{@report.warning}"
|
|
219
|
-
else
|
|
220
|
-
" (none)"
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
# ---- formatting helpers --------------------------------------------
|
|
225
|
-
|
|
226
|
-
def section(title)
|
|
227
|
-
@lines << ""
|
|
228
|
-
@lines << title
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
def row(label, value)
|
|
232
|
-
return if value.nil?
|
|
233
|
-
return if value.is_a?(String) && value.empty?
|
|
234
|
-
|
|
235
|
-
@lines << " #{label}:#{' ' * [LABEL_WIDTH - label.to_s.length - 1, 1].max}#{value}"
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
def two_col(left_label, left_value, right_label, right_value)
|
|
239
|
-
left = "#{left_label} #{left_value}".ljust(40)
|
|
240
|
-
"#{left}#{right_label} #{right_value}"
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
def yes_no(bool)
|
|
244
|
-
bool ? "yes" : "no"
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
def truncate_list(items)
|
|
248
|
-
list = Array(items)
|
|
249
|
-
return "(none)" if list.empty?
|
|
250
|
-
|
|
251
|
-
shown = list.first(LIST_LIMIT).join(", ")
|
|
252
|
-
shown += ", ..." if list.size > LIST_LIMIT
|
|
253
|
-
shown
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
def weight_descriptor
|
|
257
|
-
return nil unless @report.weight_class
|
|
258
|
-
|
|
259
|
-
name = weight_name(@report.weight_class)
|
|
260
|
-
"#{@report.weight_class}#{" (#{name})" if name}"
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
def width_descriptor
|
|
264
|
-
return nil unless @report.width_class
|
|
265
|
-
|
|
266
|
-
name = WIDTH_NAMES[@report.width_class]
|
|
267
|
-
"#{@report.width_class}#{" (#{name})" if name}"
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
def weight_name(value)
|
|
271
|
-
case value
|
|
272
|
-
when 100 then "Thin"
|
|
273
|
-
when 200 then "Extra-light"
|
|
274
|
-
when 300 then "Light"
|
|
275
|
-
when 400 then "Regular"
|
|
276
|
-
when 500 then "Medium"
|
|
277
|
-
when 600 then "Semi-bold"
|
|
278
|
-
when 700 then "Bold"
|
|
279
|
-
when 800 then "Extra-bold"
|
|
280
|
-
when 900 then "Black"
|
|
281
|
-
end
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
def bbox_descriptor(metrics)
|
|
285
|
-
"(#{metrics.bbox_x_min}, #{metrics.bbox_y_min}) → (#{metrics.bbox_x_max}, #{metrics.bbox_y_max})"
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
def codepoint_range_preview
|
|
289
|
-
ranges = Array(@report.codepoint_ranges)
|
|
290
|
-
return "(none)" if ranges.empty?
|
|
291
|
-
|
|
292
|
-
shown = ranges.first(LIST_LIMIT).map do |r|
|
|
293
|
-
"U+#{format('%04X', r.first_cp)}-U+#{format('%04X', r.last_cp)}"
|
|
294
|
-
end.join(", ")
|
|
295
|
-
shown += ", ..." if ranges.size > LIST_LIMIT
|
|
296
|
-
shown
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
def instruction_line(has, count)
|
|
300
|
-
return "no" unless has
|
|
301
|
-
|
|
302
|
-
count ? "#{count} instructions" : "present"
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
def cvt_line(hinting)
|
|
306
|
-
return "no" unless hinting.has_cvt
|
|
307
|
-
|
|
308
|
-
hinting.cvt_entry_count ? "#{hinting.cvt_entry_count} entries" : "present"
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
def gasp_line(hinting)
|
|
312
|
-
ranges = Array(hinting.gasp_ranges)
|
|
313
|
-
return "no" if ranges.empty?
|
|
314
|
-
|
|
315
|
-
ppems = ranges.map(&:max_ppem).compact
|
|
316
|
-
"#{ranges.size} ranges (#{ppems.join('/')} ppem)"
|
|
317
|
-
end
|
|
318
|
-
|
|
319
|
-
def colr_line(color)
|
|
320
|
-
"v#{color.colr_version}, #{color.colr_base_glyph_count} base glyphs, #{color.colr_layer_count} layers"
|
|
321
|
-
end
|
|
322
|
-
end
|
|
323
|
-
end
|
|
324
|
-
end
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Fontisan
|
|
4
|
-
module Formatters
|
|
5
|
-
# Human-readable overview of a {Models::Audit::LibrarySummary}.
|
|
6
|
-
#
|
|
7
|
-
# Lists the per-face rollup counts, aggregate metrics, script coverage
|
|
8
|
-
# matrix, duplicate groups, and license distribution. The full per-face
|
|
9
|
-
# AuditReports are attached to the model; this view only shows the
|
|
10
|
-
# cross-face summaries (use YAML/JSON output for the full per-face data).
|
|
11
|
-
class LibrarySummaryTextRenderer
|
|
12
|
-
SEPARATOR = "=" * 80
|
|
13
|
-
LIST_LIMIT = 15
|
|
14
|
-
|
|
15
|
-
# @param summary [Models::Audit::LibrarySummary]
|
|
16
|
-
def initialize(summary)
|
|
17
|
-
@summary = summary
|
|
18
|
-
@lines = []
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# @return [String]
|
|
22
|
-
def render
|
|
23
|
-
render_header
|
|
24
|
-
render_aggregates
|
|
25
|
-
render_script_coverage
|
|
26
|
-
render_duplicates
|
|
27
|
-
render_license_distribution
|
|
28
|
-
@lines.join("\n")
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
def render_header
|
|
34
|
-
@lines << "LIBRARY SUMMARY"
|
|
35
|
-
@lines << SEPARATOR
|
|
36
|
-
@lines << "root: #{@summary.root_path}"
|
|
37
|
-
@lines << "files: #{@summary.total_files} faces: #{@summary.total_faces}"
|
|
38
|
-
exts = Array(@summary.scanned_extensions)
|
|
39
|
-
@lines << "formats: #{exts.empty? ? '(none)' : exts.join(', ')}"
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def render_aggregates
|
|
43
|
-
m = @summary.aggregate_metrics || {}
|
|
44
|
-
section("AGGREGATES")
|
|
45
|
-
@lines << " codepoints: #{m[:total_codepoints] || 0}"
|
|
46
|
-
@lines << " glyphs: #{m[:total_glyphs] || 0}"
|
|
47
|
-
@lines << " total size: #{format_bytes(m[:total_size_bytes] || 0)}"
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def render_script_coverage
|
|
51
|
-
rows = Array(@summary.script_coverage)
|
|
52
|
-
return if rows.empty?
|
|
53
|
-
|
|
54
|
-
section("SCRIPT COVERAGE (top #{LIST_LIMIT})")
|
|
55
|
-
rows.first(LIST_LIMIT).each do |row|
|
|
56
|
-
@lines << " #{row.script}: #{row.face_count} face#{'s' unless row.face_count == 1}"
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def render_duplicates
|
|
61
|
-
groups = Array(@summary.duplicate_groups)
|
|
62
|
-
return if groups.empty?
|
|
63
|
-
|
|
64
|
-
section("DUPLICATES (#{groups.size} group#{'s' unless groups.size == 1})")
|
|
65
|
-
groups.each do |group|
|
|
66
|
-
@lines << " sha #{group.source_sha256[0, 12]}:"
|
|
67
|
-
group.files.each { |path| @lines << " #{path}" }
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def render_license_distribution
|
|
72
|
-
dist = @summary.license_distribution || {}
|
|
73
|
-
return if dist.empty?
|
|
74
|
-
|
|
75
|
-
section("LICENSE DISTRIBUTION")
|
|
76
|
-
dist.sort_by { |_url, count| -count }.each do |url, count|
|
|
77
|
-
@lines << " #{count} #{url}"
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def section(title)
|
|
82
|
-
@lines << ""
|
|
83
|
-
@lines << title
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def format_bytes(bytes)
|
|
87
|
-
return "0 B" if bytes.nil? || bytes.zero?
|
|
88
|
-
|
|
89
|
-
if bytes < 1024
|
|
90
|
-
"#{bytes} B"
|
|
91
|
-
elsif bytes < 1024 * 1024
|
|
92
|
-
"#{(bytes / 1024.0).round(2)} KB"
|
|
93
|
-
else
|
|
94
|
-
"#{(bytes / (1024.0 * 1024)).round(2)} MB"
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "lutaml/model"
|
|
4
|
-
|
|
5
|
-
module Fontisan
|
|
6
|
-
module Models
|
|
7
|
-
module Audit
|
|
8
|
-
# One fvar axis descriptor on an AuditReport.
|
|
9
|
-
#
|
|
10
|
-
# `min_value` / `default_value` / `max_value` are used (rather than
|
|
11
|
-
# `min` / `default` / `max`) to avoid colliding with Ruby's built-in
|
|
12
|
-
# `default` method on classes.
|
|
13
|
-
class AuditAxis < Lutaml::Model::Serializable
|
|
14
|
-
attribute :tag, :string
|
|
15
|
-
attribute :min_value, :float
|
|
16
|
-
attribute :default_value, :float
|
|
17
|
-
attribute :max_value, :float
|
|
18
|
-
attribute :name, :string
|
|
19
|
-
|
|
20
|
-
key_value do
|
|
21
|
-
map "tag", to: :tag
|
|
22
|
-
map "min_value", to: :min_value
|
|
23
|
-
map "default_value", to: :default_value
|
|
24
|
-
map "max_value", to: :max_value
|
|
25
|
-
map "name", to: :name
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "lutaml/model"
|
|
4
|
-
|
|
5
|
-
module Fontisan
|
|
6
|
-
module Models
|
|
7
|
-
module Audit
|
|
8
|
-
# One Unicode block coverage row on an AuditReport.
|
|
9
|
-
class AuditBlock < Lutaml::Model::Serializable
|
|
10
|
-
attribute :name, :string
|
|
11
|
-
attribute :first_cp, :integer
|
|
12
|
-
attribute :last_cp, :integer
|
|
13
|
-
attribute :range, :string
|
|
14
|
-
attribute :total, :integer
|
|
15
|
-
attribute :covered, :integer
|
|
16
|
-
attribute :fill_ratio, :float
|
|
17
|
-
attribute :complete, Lutaml::Model::Type::Boolean
|
|
18
|
-
|
|
19
|
-
key_value do
|
|
20
|
-
map "name", to: :name
|
|
21
|
-
map "first_cp", to: :first_cp
|
|
22
|
-
map "last_cp", to: :last_cp
|
|
23
|
-
map "range", to: :range
|
|
24
|
-
map "total", to: :total
|
|
25
|
-
map "covered", to: :covered
|
|
26
|
-
map "fill_ratio", to: :fill_ratio
|
|
27
|
-
map "complete", to: :complete
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "lutaml/model"
|
|
4
|
-
|
|
5
|
-
module Fontisan
|
|
6
|
-
module Models
|
|
7
|
-
module Audit
|
|
8
|
-
# Structural diff between two AuditReports.
|
|
9
|
-
#
|
|
10
|
-
# `left_source`/`right_source` are the original source_file paths
|
|
11
|
-
# (or report paths) so a consumer reading the diff alone can locate
|
|
12
|
-
# the inputs.
|
|
13
|
-
#
|
|
14
|
-
# `field_changes` lists scalar fields whose values changed.
|
|
15
|
-
# `codepoints` is the cmap delta (CodepointSetDiff).
|
|
16
|
-
# The remaining fields are array set-diffs over the report's
|
|
17
|
-
# structural inventory: OpenType features, scripts, UCD blocks, and
|
|
18
|
-
# CLDR languages. Each is split into `added_*` (in right, not left)
|
|
19
|
-
# and `removed_*` (in left, not right).
|
|
20
|
-
class AuditDiff < Lutaml::Model::Serializable
|
|
21
|
-
attribute :left_source, :string
|
|
22
|
-
attribute :right_source, :string
|
|
23
|
-
attribute :field_changes, FieldChange, collection: true
|
|
24
|
-
attribute :codepoints, CodepointSetDiff
|
|
25
|
-
attribute :added_features, :string, collection: true
|
|
26
|
-
attribute :removed_features, :string, collection: true
|
|
27
|
-
attribute :added_scripts, :string, collection: true
|
|
28
|
-
attribute :removed_scripts, :string, collection: true
|
|
29
|
-
attribute :added_blocks, :string, collection: true
|
|
30
|
-
attribute :removed_blocks, :string, collection: true
|
|
31
|
-
attribute :added_languages, :string, collection: true
|
|
32
|
-
attribute :removed_languages, :string, collection: true
|
|
33
|
-
|
|
34
|
-
key_value do
|
|
35
|
-
map "left_source", to: :left_source
|
|
36
|
-
map "right_source", to: :right_source
|
|
37
|
-
map "field_changes", to: :field_changes
|
|
38
|
-
map "codepoints", to: :codepoints
|
|
39
|
-
map "added_features", to: :added_features
|
|
40
|
-
map "removed_features", to: :removed_features
|
|
41
|
-
map "added_scripts", to: :added_scripts
|
|
42
|
-
map "removed_scripts", to: :removed_scripts
|
|
43
|
-
map "added_blocks", to: :added_blocks
|
|
44
|
-
map "removed_blocks", to: :removed_blocks
|
|
45
|
-
map "added_languages", to: :added_languages
|
|
46
|
-
map "removed_languages", to: :removed_languages
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# True when nothing differs. Useful for the text formatter.
|
|
50
|
-
#
|
|
51
|
-
# @return [Boolean]
|
|
52
|
-
def empty?
|
|
53
|
-
collection_empty?(field_changes) &&
|
|
54
|
-
added_codepoints.zero? && removed_codepoints.zero? &&
|
|
55
|
-
collection_empty?(added_features) && collection_empty?(removed_features) &&
|
|
56
|
-
collection_empty?(added_scripts) && collection_empty?(removed_scripts) &&
|
|
57
|
-
collection_empty?(added_blocks) && collection_empty?(removed_blocks) &&
|
|
58
|
-
collection_empty?(added_languages) && collection_empty?(removed_languages)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def added_codepoints
|
|
62
|
-
codepoints&.added_count || 0
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def removed_codepoints
|
|
66
|
-
codepoints&.removed_count || 0
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
private
|
|
70
|
-
|
|
71
|
-
def collection_empty?(value)
|
|
72
|
-
value.nil? || value.empty?
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|