Skip to content

Commit

Permalink
resurrect font-casker
Browse files Browse the repository at this point in the history
  • Loading branch information
Aaron-212 committed May 19, 2024
1 parent 1cc577a commit 337dc29
Showing 1 changed file with 206 additions and 0 deletions.
206 changes: 206 additions & 0 deletions developer/bin/font_casker
Original file line number Diff line number Diff line change
@@ -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 <font_archive.zip>
Generates cask stanzas from OTF/TTF files within <font_archive>.
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

0 comments on commit 337dc29

Please sign in to comment.