From 337dc29371428be8c4aa3909c6fb1fa9e8028d03 Mon Sep 17 00:00:00 2001 From: Aaron Ruan Date: Sun, 19 May 2024 11:15:04 +0800 Subject: [PATCH] resurrect font-casker --- developer/bin/font_casker | 206 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100755 developer/bin/font_casker diff --git a/developer/bin/font_casker b/developer/bin/font_casker new file mode 100755 index 000000000000..bd5ee9de3214 --- /dev/null +++ b/developer/bin/font_casker @@ -0,0 +1,206 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# rubocop:disable Style/TopLevelMethodDefinition + +# +# font_casker +# +# TODO: +# generate_font_cask_token +# relevant code is in generate_cask_token +# font version +# report/resolve version conflicts +# cask generation +# templating +# constants +# abbreviations +# URL +# from file metadata as in list_url_attributes_on_file +# homepage +# from "Vendor URL" field in otfinfo -i output +# + +### +### system dependencies: +### lcdf-typetools / other `otfinfo` with identical interface +### + +require "open3" +require "digest" + +### +### Arguments +### + +ARCHIVE_PATH = ARGV.first.freeze + +### +### Constants +### + +FONT_EXT_PATTERN = /.(otf|ttf)\Z/i + +# Font files typically denote their weight, style, and width in the filename. +# Note that these patterns capture regardless of additional modifiers, +# e.g. "semibold", "extralight". +FONT_WEIGHTS = [ + /black/i, + /bold/i, + /book/i, + /hairline/i, + /heavy/i, + /light/i, + /medium/i, + /normal/i, + /regular/i, + /roman/i, + /thin/i, + /ultra/i, +].freeze + +FONT_STYLES = [ + /italic/i, + /oblique/i, + /roman/i, + /slanted/i, + /upright/i, +].freeze + +FONT_WIDTHS = [ + /compressed/i, + /condensed/i, + /extended/i, + /narrow/i, + /wide/i, +].freeze + +### +### Utilia +### + +def mce(enum) + enum.group_by { |x| x } + .values + .max_by(&:size) + .first +end + +def eval_bin_cmd(cmd, blob) + IO.popen(cmd, "r+b") do |io| + io.print(blob) + io.close_write + io.read + end +end + +def font_paths(archive) + cmd = ["zipinfo", "-1", archive] + + IO.popen(cmd, "r", &:read) + .chomp + .split("\n") + .grep(FONT_EXT_PATTERN) + .reject { |x| x.start_with?("__MACOSX") } + .grep_v(%r{(?:\A|/)\._}) + .sort +end + +def font_blobs(archive, paths) + paths.map do |x| + IO.popen(["unzip", "-p", archive, x], "rb", &:read) + end +end + +### +### Templating +### + +def stanzify(stanza_name, val = "") + if val.respond_to?(:map) + val.map { |x| " #{stanza_name} \"#{x}\"" } + else + " #{stanza_name} \"#{val}\"" + end +end + +# TODO: named parameters, after switching to Ruby 2.x +def caskify(family, version, sha, paths) + output = ["FAMILY: #{family}"] + output << "cask 'FIXME' do" + output << stanzify("version", version) + output << stanzify("sha256", sha) + output << "" + output << stanzify("url", "") + output << stanzify("name", "") + output << stanzify("homepage", "") + output << "" + output << stanzify("font", paths) + output << "" + output << "# No zap stanza required" + output << "end" +end + +### +### Values +### + +def shasum(archive) + Digest::SHA256.file archive +end + +def font_version(fontblobs) + cmd = ["otfinfo", "-v"] + versions = fontblobs.map { |x| eval_bin_cmd(cmd, x) } + .map { |x| (m = /\A(?:Version\s+)?(\d[^\s,;]*)/i.match(x)) ? m[1] : x.delete("\n") } + + # assumption: the main version is the most common one + mce(versions) +end + +def font_family(fontblobs) + cmd = ["otfinfo", "-a"] + families = fontblobs.map { |x| eval_bin_cmd(cmd, x) } + .map { |x| x.delete("\n") } + + # assumption: the main family is the most common one + mce(families) +end + +def cask + paths = font_paths(ARCHIVE_PATH) + blobs = font_blobs(ARCHIVE_PATH, paths) + + caskify( + font_family(blobs), + font_version(blobs), + shasum(ARCHIVE_PATH), + paths, + ) +end + +### +### main +### + +usage = <<~EOS + Usage: font_casker + + Generates cask stanzas from OTF/TTF files within . + Currently covers: version, sha, font. + +EOS + +if /^-+h(elp)?$/i.match?(ARGV.first) + puts usage + exit 0 +end + +if ARGV.length != 1 + puts usage + exit 1 +end + +puts cask + +# rubocop:enable Style/TopLevelMethodDefinition