fontisan 0.2.22 → 0.3.0
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 +4 -168
- 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/stitcher/selector/codepoints.rb +29 -0
- data/lib/fontisan/stitcher/selector/gid.rb +25 -0
- data/lib/fontisan/stitcher/selector/range.rb +30 -0
- data/lib/fontisan/stitcher/selector.rb +26 -0
- data/lib/fontisan/stitcher/source.rb +97 -0
- data/lib/fontisan/stitcher.rb +182 -0
- data/lib/fontisan/stitcher_cli.rb +69 -0
- data/lib/fontisan/tables/cff.rb +2 -1
- data/lib/fontisan/tables.rb +2 -1
- data/lib/fontisan/ufo/anchor.rb +17 -0
- data/lib/fontisan/ufo/cli.rb +85 -0
- data/lib/fontisan/ufo/compile/base_compiler.rb +81 -0
- data/lib/fontisan/ufo/compile/cff.rb +224 -0
- data/lib/fontisan/ufo/compile/cmap.rb +129 -0
- data/lib/fontisan/ufo/compile/filters/cubic_to_quadratic.rb +174 -0
- data/lib/fontisan/ufo/compile/filters/decompose_components.rb +33 -0
- data/lib/fontisan/ufo/compile/filters/flatten_components.rb +22 -0
- data/lib/fontisan/ufo/compile/filters/reverse_contour_direction.rb +27 -0
- data/lib/fontisan/ufo/compile/filters.rb +57 -0
- data/lib/fontisan/ufo/compile/glyf_loca.rb +145 -0
- data/lib/fontisan/ufo/compile/head.rb +98 -0
- data/lib/fontisan/ufo/compile/hhea.rb +36 -0
- data/lib/fontisan/ufo/compile/hmtx.rb +27 -0
- data/lib/fontisan/ufo/compile/maxp.rb +57 -0
- data/lib/fontisan/ufo/compile/name.rb +79 -0
- data/lib/fontisan/ufo/compile/os2.rb +81 -0
- data/lib/fontisan/ufo/compile/otf_compiler.rb +43 -0
- data/lib/fontisan/ufo/compile/post.rb +32 -0
- data/lib/fontisan/ufo/compile/ttf_compiler.rb +69 -0
- data/lib/fontisan/ufo/compile.rb +48 -0
- data/lib/fontisan/ufo/component.rb +18 -0
- data/lib/fontisan/ufo/contour.rb +29 -0
- data/lib/fontisan/ufo/convert/from_bin_data.rb +246 -0
- data/lib/fontisan/ufo/convert.rb +18 -0
- data/lib/fontisan/ufo/data_set.rb +21 -0
- data/lib/fontisan/ufo/features.rb +17 -0
- data/lib/fontisan/ufo/font.rb +61 -0
- data/lib/fontisan/ufo/glyph.rb +421 -0
- data/lib/fontisan/ufo/guideline.rb +19 -0
- data/lib/fontisan/ufo/image.rb +16 -0
- data/lib/fontisan/ufo/image_set.rb +19 -0
- data/lib/fontisan/ufo/info.rb +79 -0
- data/lib/fontisan/ufo/kerning.rb +32 -0
- data/lib/fontisan/ufo/layer.rb +38 -0
- data/lib/fontisan/ufo/layer_set.rb +37 -0
- data/lib/fontisan/ufo/lib.rb +24 -0
- data/lib/fontisan/ufo/plist.rb +118 -0
- data/lib/fontisan/ufo/point.rb +38 -0
- data/lib/fontisan/ufo/reader.rb +144 -0
- data/lib/fontisan/ufo/transformation.rb +39 -0
- data/lib/fontisan/ufo/writer.rb +115 -0
- data/lib/fontisan/ufo.rb +44 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan.rb +3 -3
- metadata +57 -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,123 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "fileutils"
|
|
4
|
-
|
|
5
|
-
module Fontisan
|
|
6
|
-
module Commands
|
|
7
|
-
# Produces a complete per-face font audit report.
|
|
8
|
-
#
|
|
9
|
-
# One AuditReport per face. For standalone fonts (TTF/OTF/WOFF/WOFF2),
|
|
10
|
-
# #run returns a single AuditReport. For collections (TTC/OTC/dfont),
|
|
11
|
-
# #run returns an Array<AuditReport> — one per face, in source order.
|
|
12
|
-
#
|
|
13
|
-
# The report is assembled by running every extractor in
|
|
14
|
-
# {Audit::Registry} against an {Audit::Context}. Each extractor
|
|
15
|
-
# owns one concern (provenance, identity, style, coverage,
|
|
16
|
-
# aggregations, …). Adding a new concern means adding one
|
|
17
|
-
# extractor class and one line in the registry — AuditCommand
|
|
18
|
-
# itself never changes.
|
|
19
|
-
class AuditCommand < BaseCommand
|
|
20
|
-
# @return [Models::Audit::AuditReport, Array<Models::Audit::AuditReport>]
|
|
21
|
-
def run
|
|
22
|
-
if FontLoader.collection?(@font_path)
|
|
23
|
-
audit_collection
|
|
24
|
-
else
|
|
25
|
-
audit_face(@font, 0, 1)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Write one file per face under `to` (a directory). Pure utility —
|
|
30
|
-
# operates on a pre-built reports array, no font_path required.
|
|
31
|
-
#
|
|
32
|
-
# @param reports [Array<Models::Audit::AuditReport>]
|
|
33
|
-
# @param to [String] output directory; created if missing
|
|
34
|
-
# @param format [Symbol] :yaml or :json
|
|
35
|
-
# @return [Array<String>] written file paths
|
|
36
|
-
def self.write_reports(reports, to:, format: :yaml)
|
|
37
|
-
FileUtils.mkdir_p(to)
|
|
38
|
-
|
|
39
|
-
reports.map do |report|
|
|
40
|
-
path = File.join(to, output_filename(report, format))
|
|
41
|
-
content = format == :json ? report.to_json : report.to_yaml
|
|
42
|
-
File.write(path, content)
|
|
43
|
-
path
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Compute the per-face filename for a report.
|
|
48
|
-
#
|
|
49
|
-
# @param report [Models::Audit::AuditReport]
|
|
50
|
-
# @param format [Symbol] :yaml or :json
|
|
51
|
-
# @return [String] filename only (no directory)
|
|
52
|
-
def self.output_filename(report, format)
|
|
53
|
-
ext = format == :json ? "json" : "yaml"
|
|
54
|
-
base = if report.num_fonts_in_source == 1
|
|
55
|
-
safe_filename(report.postscript_name || report.family_name || "font")
|
|
56
|
-
else
|
|
57
|
-
format("%<idx>02d-%<name>s",
|
|
58
|
-
idx: report.font_index,
|
|
59
|
-
name: safe_filename(report.postscript_name || "face"))
|
|
60
|
-
end
|
|
61
|
-
"#{base}.#{ext}"
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Sanitize an arbitrary string into a filesystem-safe basename.
|
|
65
|
-
#
|
|
66
|
-
# @param name [String, nil]
|
|
67
|
-
# @return [String]
|
|
68
|
-
def self.safe_filename(name)
|
|
69
|
-
return "font" if name.nil? || name.empty?
|
|
70
|
-
|
|
71
|
-
name.gsub(/[^A-Za-z0-9._-]/, "_")
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
private
|
|
75
|
-
|
|
76
|
-
def audit_collection
|
|
77
|
-
collection = FontLoader.load_collection(@font_path)
|
|
78
|
-
num = collection.num_fonts
|
|
79
|
-
Array.new(num) do |index|
|
|
80
|
-
font = FontLoader.load(@font_path, font_index: index,
|
|
81
|
-
mode: LoadingModes::FULL)
|
|
82
|
-
audit_face(font, index, num)
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def audit_face(font, font_index, num_fonts_in_source)
|
|
87
|
-
context = Audit::Context.new(
|
|
88
|
-
font: font,
|
|
89
|
-
font_path: @font_path,
|
|
90
|
-
font_index: font_index,
|
|
91
|
-
num_fonts_in_source: num_fonts_in_source,
|
|
92
|
-
options: @options,
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
fields = {}
|
|
96
|
-
Audit::Registry.each(mode: audit_mode) do |extractor_class|
|
|
97
|
-
fields.merge!(extractor_class.new.extract(context))
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
fields[:warning] = combine_warnings(
|
|
101
|
-
context.ucd[:warning],
|
|
102
|
-
context.cldr&.dig(:warning),
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
Models::Audit::AuditReport.new(**fields)
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
# Audit's --brief selects a cheap extractor subset (identity, style,
|
|
109
|
-
# licensing, coverage) but still requires FULL font loading — the
|
|
110
|
-
# Coverage extractor reads `cmap`. The CLI translates the user-facing
|
|
111
|
-
# `--brief` flag into `:audit_brief` so BaseCommand's `:brief →
|
|
112
|
-
# LoadingModes::METADATA` shortcut doesn't fire.
|
|
113
|
-
def audit_mode
|
|
114
|
-
@options[:audit_brief] ? :brief : :full
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def combine_warnings(*warnings)
|
|
118
|
-
compacted = warnings.flatten.compact
|
|
119
|
-
compacted.empty? ? nil : compacted.join("; ")
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
end
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Fontisan
|
|
4
|
-
module Commands
|
|
5
|
-
# Diffs two faces or two saved audit reports.
|
|
6
|
-
#
|
|
7
|
-
# Each input is one of:
|
|
8
|
-
# - A path to a `.yaml`/`.json` file previously written by
|
|
9
|
-
# `fontisan audit -o`. Loaded as an AuditReport.
|
|
10
|
-
# - A path to a font file. Audited on-the-fly via AuditCommand.
|
|
11
|
-
#
|
|
12
|
-
# Returns an {Models::Audit::AuditDiff}. The CLI renders it as
|
|
13
|
-
# YAML/JSON (text formatter lands in TODO 25).
|
|
14
|
-
#
|
|
15
|
-
# Mixed inputs are allowed (font vs. saved report), which is useful
|
|
16
|
-
# for tracking a font's evolution against a checked-in baseline.
|
|
17
|
-
class AuditCompareCommand
|
|
18
|
-
# @param left_path [String] path to font file or saved report
|
|
19
|
-
# @param right_path [String] path to font file or saved report
|
|
20
|
-
# @param options [Hash] forwarded to AuditCommand for any input
|
|
21
|
-
# that needs to be audited fresh
|
|
22
|
-
def initialize(left_path, right_path, options = {})
|
|
23
|
-
@left_path = left_path
|
|
24
|
-
@right_path = right_path
|
|
25
|
-
@options = options
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# @return [Models::Audit::AuditDiff]
|
|
29
|
-
def run
|
|
30
|
-
left_report = load_report(@left_path)
|
|
31
|
-
right_report = load_report(@right_path)
|
|
32
|
-
Audit::Differ.new(left_report, right_report).diff
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def load_report(path)
|
|
38
|
-
if saved_report?(path)
|
|
39
|
-
load_saved_report(path)
|
|
40
|
-
else
|
|
41
|
-
AuditCommand.new(path, audit_options).run
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def saved_report?(path)
|
|
46
|
-
ext = File.extname(path).downcase
|
|
47
|
-
[".yaml", ".yml", ".json"].include?(ext)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def load_saved_report(path)
|
|
51
|
-
case File.extname(path).downcase
|
|
52
|
-
when ".json"
|
|
53
|
-
Models::Audit::AuditReport.from_json(File.read(path))
|
|
54
|
-
else
|
|
55
|
-
Models::Audit::AuditReport.from_yaml(File.read(path))
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Forward only the audit-relevant options when auditing fresh fonts.
|
|
60
|
-
# Drops `--compare` (consumed here) and `--output` (no file output).
|
|
61
|
-
def audit_options
|
|
62
|
-
@options.except(:compare, :output)
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Fontisan
|
|
4
|
-
module Commands
|
|
5
|
-
# Audits every font in a directory (tree) and rolls the per-face
|
|
6
|
-
# reports up into a {Models::Audit::LibrarySummary}.
|
|
7
|
-
#
|
|
8
|
-
# Thin wrapper over {Audit::LibraryAuditor}: validates the root
|
|
9
|
-
# path exists, delegates to the auditor, returns the summary.
|
|
10
|
-
# The auditor itself owns file discovery and per-face auditing;
|
|
11
|
-
# this command is the CLI-facing boundary that maps user-facing
|
|
12
|
-
# options onto auditor inputs.
|
|
13
|
-
class AuditLibraryCommand
|
|
14
|
-
# @param root_path [String] directory containing fonts
|
|
15
|
-
# @param recursive [Boolean] walk into subdirectories
|
|
16
|
-
# @param options [Hash] forwarded to AuditCommand for each face
|
|
17
|
-
def initialize(root_path, recursive:, options:)
|
|
18
|
-
@root_path = root_path
|
|
19
|
-
@recursive = recursive
|
|
20
|
-
@options = options
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# @return [Models::Audit::LibrarySummary]
|
|
24
|
-
def run
|
|
25
|
-
raise Error, "library audit requires an existing directory: #{@root_path}" unless Dir.exist?(@root_path)
|
|
26
|
-
|
|
27
|
-
auditor.audit
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# @return [Array<String>] files skipped during the audit pass
|
|
31
|
-
def skipped
|
|
32
|
-
auditor.skipped
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def auditor
|
|
38
|
-
@auditor ||= Audit::LibraryAuditor.new(
|
|
39
|
-
@root_path,
|
|
40
|
-
recursive: @recursive,
|
|
41
|
-
options: @options,
|
|
42
|
-
)
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
data/lib/fontisan/config/ucd.yml
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# UCD (Unicode Character Database) configuration.
|
|
2
|
-
#
|
|
3
|
-
# This file is the single source of truth for which UCD version Fontisan
|
|
4
|
-
# uses by default, what versions are recognized, and where to fetch them.
|
|
5
|
-
#
|
|
6
|
-
# Update `default_version` and `known_versions` when a new Unicode version
|
|
7
|
-
# is released (typically twice a year). Users may override at runtime via
|
|
8
|
-
# `fontisan ucd download --version X.Y.Z` or `fontisan audit --ucd-version X.Y.Z`.
|
|
9
|
-
|
|
10
|
-
default_version: "17.0.0"
|
|
11
|
-
|
|
12
|
-
known_versions:
|
|
13
|
-
- "15.0.0"
|
|
14
|
-
- "15.1.0"
|
|
15
|
-
- "16.0.0"
|
|
16
|
-
- "17.0.0"
|
|
17
|
-
|
|
18
|
-
# Base URL for fetching UCDXML artifacts.
|
|
19
|
-
# Full URL: <base_url>/<version>/ucdxml/ucd.all.flat.zip
|
|
20
|
-
base_url: "https://www.unicode.org/Public"
|
|
21
|
-
|
|
22
|
-
# Listing URL for `--latest` probing. HTML scraping, best-effort.
|
|
23
|
-
listing_url: "https://www.unicode.org/Public/ucdxml/"
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Fontisan
|
|
4
|
-
module Formatters
|
|
5
|
-
# Human-readable diff of two {Models::Audit::AuditReport}s.
|
|
6
|
-
#
|
|
7
|
-
# Output groups changes by kind: scalar field changes, codepoint set
|
|
8
|
-
# deltas (added/removed counts and a preview of the ranges), then
|
|
9
|
-
# structural inventory changes (scripts, features, blocks, languages).
|
|
10
|
-
# Empty sections are omitted so a no-op diff prints only the header.
|
|
11
|
-
class AuditDiffTextRenderer
|
|
12
|
-
SEPARATOR = "=" * 80
|
|
13
|
-
LIST_LIMIT = 10
|
|
14
|
-
|
|
15
|
-
# @param diff [Models::Audit::AuditDiff]
|
|
16
|
-
def initialize(diff)
|
|
17
|
-
@diff = diff
|
|
18
|
-
@lines = []
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# @return [String]
|
|
22
|
-
def render
|
|
23
|
-
render_header
|
|
24
|
-
render_field_changes
|
|
25
|
-
render_codepoint_delta
|
|
26
|
-
render_structural_changes
|
|
27
|
-
render_empty_note
|
|
28
|
-
@lines.join("\n")
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
def render_header
|
|
34
|
-
@lines << "AUDIT DIFF"
|
|
35
|
-
@lines << SEPARATOR
|
|
36
|
-
@lines << "left: #{@diff.left_source}"
|
|
37
|
-
@lines << "right: #{@diff.right_source}"
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def render_field_changes
|
|
41
|
-
changes = Array(@diff.field_changes)
|
|
42
|
-
return if changes.empty?
|
|
43
|
-
|
|
44
|
-
section("FIELD CHANGES (#{changes.size})")
|
|
45
|
-
changes.each do |change|
|
|
46
|
-
@lines << " #{change.field}: #{change.left.inspect} → #{change.right.inspect}"
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def render_codepoint_delta
|
|
51
|
-
delta = @diff.codepoints
|
|
52
|
-
return unless delta && (delta.added_count.to_i.positive? || delta.removed_count.to_i.positive?)
|
|
53
|
-
|
|
54
|
-
section("CODEPOINT COVERAGE")
|
|
55
|
-
@lines << " added: #{delta.added_count}"
|
|
56
|
-
@lines << " removed: #{delta.removed_count}"
|
|
57
|
-
@lines << " unchanged: #{delta.unchanged_count}"
|
|
58
|
-
preview_added(delta)
|
|
59
|
-
preview_removed(delta)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def preview_added(delta)
|
|
63
|
-
ranges = Array(delta.added)
|
|
64
|
-
return if ranges.empty?
|
|
65
|
-
|
|
66
|
-
@lines << " + #{format_ranges(ranges)}"
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def preview_removed(delta)
|
|
70
|
-
ranges = Array(delta.removed)
|
|
71
|
-
return if ranges.empty?
|
|
72
|
-
|
|
73
|
-
@lines << " - #{format_ranges(ranges)}"
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def render_structural_changes
|
|
77
|
-
render_set("SCRIPTS", @diff.added_scripts, @diff.removed_scripts)
|
|
78
|
-
render_set("FEATURES", @diff.added_features, @diff.removed_features)
|
|
79
|
-
render_set("BLOCKS", @diff.added_blocks, @diff.removed_blocks)
|
|
80
|
-
render_set("LANGUAGES", @diff.added_languages, @diff.removed_languages)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def render_set(name, added, removed)
|
|
84
|
-
added = Array(added)
|
|
85
|
-
removed = Array(removed)
|
|
86
|
-
return if added.empty? && removed.empty?
|
|
87
|
-
|
|
88
|
-
section("#{name} CHANGES")
|
|
89
|
-
@lines << " + #{truncate(added)}" unless added.empty?
|
|
90
|
-
@lines << " - #{truncate(removed)}" unless removed.empty?
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def render_empty_note
|
|
94
|
-
return unless @diff.empty?
|
|
95
|
-
|
|
96
|
-
@lines << ""
|
|
97
|
-
@lines << "(no differences)"
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# ---- helpers --------------------------------------------------------
|
|
101
|
-
|
|
102
|
-
def section(title)
|
|
103
|
-
@lines << ""
|
|
104
|
-
@lines << title
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def truncate(list)
|
|
108
|
-
shown = list.first(LIST_LIMIT).join(", ")
|
|
109
|
-
shown += ", ..." if list.size > LIST_LIMIT
|
|
110
|
-
shown
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def format_ranges(ranges)
|
|
114
|
-
shown = ranges.first(LIST_LIMIT).map do |r|
|
|
115
|
-
"U+#{format('%04X', r.first_cp)}-U+#{format('%04X', r.last_cp)}"
|
|
116
|
-
end.join(", ")
|
|
117
|
-
shown += ", ..." if ranges.size > LIST_LIMIT
|
|
118
|
-
shown
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
end
|