Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add font-casker tool back #174242

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading