diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index c7da7e153ae2..000000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,129 +0,0 @@ -# How To Contribute - -[Instructions from the main repository](https://github.com/Homebrew/homebrew-cask/blob/master/CONTRIBUTING.md) apply. Exceptions are documented on the [README](README.md) and this document. - -## When to contribute - -The preferred way to add a font to this repository is to [submit it to Google Fonts](https://github.com/google/fonts/blob/master/CONTRIBUTING.md). Shortly after its inclusion, a Cask will be automatically generated and updated on this repository. - -We accept fonts from other sources **but they must be provably popular**, like [JetBrains Mono](https://github.com/JetBrains/JetBrainsMono) and [iA Fonts](https://github.com/iaolo/iA-Fonts). To build one of those, keep reading. - -## Adding a Font Cask - -Making a Font Cask is easy: a Cask is a small Ruby file. - -Here’s a Cask for the font [Ionicons](https://github.com/ionic-team/ionicons) as an example: -```ruby -cask "font-ionicons" do - version "2.0.1" - sha256 "b222fcaede908b71d5b206db9fb7b625a07d313be00ee908eabd267604868661" - - url "https://github.com/ionic-team/ionicons/archive/v#{version}.zip" - name "Ionicons" - homepage "https://github.com/ionic-team/ionicons" - - font "ionicons-#{version}/fonts/ionicons.ttf" -end -``` - -Here’s a more complex Cask for the font [Fantasque Sans Mono](https://github.com/belluzj/fantasque-sans). Note that you may repeat the `font` stanza as many times as you need to, if multiple files must be installed from the same package: - -```ruby -cask "font-fantasque-sans-mono" do - version "1.8.0" - sha256 "84be689e231ff773ed9d352e83dccd8151d9e445f1cb0b88cb0e9330fc4d9cfc" - - url "https://github.com/belluzj/fantasque-sans/releases/download/v#{version}/FantasqueSansMono-Normal.zip" - name "Fantasque Sans Mono" - homepage "https://github.com/belluzj/fantasque-sans" - - font "OTF/FantasqueSansMono-Bold.otf" - font "OTF/FantasqueSansMono-BoldItalic.otf" - font "OTF/FantasqueSansMono-Italic.otf" - font "OTF/FantasqueSansMono-Regular.otf" -end -``` - -## Font Cask Fields - -The `url`, `homepage`, `version`, and `sha256` fields in a Font Cask are required, as described in [CONTRIBUTING.md](https://github.com/Homebrew/homebrew-cask/blob/master/CONTRIBUTING.md) for the main Homebrew Cask repo. Note that if the download `url` is not a versioned file, `sha256 ` should be replaced with `sha256 :no_check`, and `version` should be set to `:latest`. - -The string which follows the `font` field is a relative path to the font file within the downloaded archive. That font will be linked into the user’s `~/Library/Fonts` directory at install time. - -### Automatic Generation - -For OTF and TTF fonts, the easiest way to create a cask is to run the `font_casker` script on their containing archive. - -```bash -"$(brew --repository)/Library/Taps/homebrew/homebrew-cask-fonts/developer/bin/font_casker" font_archive.zip -``` - -`font_casker` produces a preformatted cask including the values of `version`, `sha256`, and all `font` stanzas, and writes it to stdout. - -Note that `font_casker` depends on `otfinfo`, a command-line utility from the lcdf-typetools suite of typographical software. You can obtain it as part of a TeX distribution with `brew install --cask mactex`, or from upstream with `brew install lcdf-typetools`. - -## Naming Font Casks - -We try to maintain a consistent naming policy so everything stays clean and predictable. - -### Start From the Font’s Canonical Name - -The canonical font name is the font family name as returned by the command `fc-query --format='%{family}' {{font_file}}`. - -If there is more than one family in the distribution, use your judgment to choose the “most famous” one. If there is more than one style, choose the “Regular” variant. - -Convert the font name to ASCII by transliteration or decomposition. Translate the name into English if necessary. - -## Converting the Canonical Name To a Token - -The token is the primary identifier for a package in our project. It’s the unique string users refer to when operating on the Cask. - -To get from the Font’s canonical name to a Cask token: - - * remove strings such as “font”, “ttf”, “otf”, “true type”, etc. from the - canonical name that don’t add meaning not assumed in the context of a font - package - * prepend the string `font-` to the canonical name, to prevent clashes - with Application tokens - * expand the `+` symbol into a separated English word: `-plus-` - * expand the `@` symbol into a separated English word: `-at-` - * convert all letters to lower case - * spaces become hyphens - * hyphens stay hyphens - * digits stay digits - * delete any characters which are not alphanumeric or hyphens - * collapse a series of multiple hyphens into one hyphen - * delete a leading or trailing hyphen - -Casks are stored in a Ruby file matching their token, followed by the `.rb` file extension. - -### Font Token Examples - -Canonical Font Name | Cask Token | Filename ---------------------|---------------------- |------------------------ -Anonymous Pro | `font-anonymous-pro` | `font-anonymous-pro.rb` -FreeSans | `font-freesans` | `font-freesans.rb` - -## Multiple Fonts per Cask - -Multiple font faces or families are often supplied in a single distribution. When fonts are distributed together, they should be installed together. Each Cask should correspond to a single binary distribution, not necessarily a single font face. - -Similarly, different weights of the same font may be distributed in separate binaries. Here we follow the same rule: each distribution equals a separate Cask. - -This constraint may change in the future, when the backend Ruby code becomes more sophisticated. - -### Multiple Font Formats - -If a distribution provides the same font in multiple file formats, for example both OpenType and TrueType, only include one kind. We prefer OTF to TTF, and variable fonts to static files for different instantiations. Where a distribution provides variable and static fonts under different family names, both can be included for backwards compatibility as long as they can be contained within a single cask. Example: [font-source-serif-pro.rb](https://github.com/Homebrew/homebrew-cask-fonts/blob/ab3e5d7d313b64c0a2c4041196a8eaa8e1539c9c/Casks/font-source-serif-pro.rb#L10#L23). - -Note that `font_casker` generates font stanzas for all files, so its output should be edited as needed. - -## Check Your Font Licenses - -At this time, homebrew-fonts is only accepting Casks for fonts which are freely redistributable. Just because a font is freely downloadable does not mean it is licensed for distribution, so please check that a license is available. - -## URL Notes - -### Upstream Links Are Preferred - -Generally, we prefer to have Casks point to font download links as high up the distribution chain as possible. This means linking to the download from the font’s author rather than from an aggregator site. The exception is when the font is available on the [Google Fonts GitHub repository](https://github.com/google/fonts), because those are added and managed automatically [with a script](https://github.com/Homebrew/homebrew-cask-fonts/blob/master/developer/bin/import_google_fonts). diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 6f2b7b0415b4..000000000000 --- a/LICENSE +++ /dev/null @@ -1,23 +0,0 @@ -Copyright (c) 2013, Paul Hinze & Contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 4dcb6a3a18c7..0e248ed3cb90 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,5 @@ -# homebrew-cask-fonts +# Homebrew/cask-fonts (deprecated) -Here is where you can find or submit font Casks for the [Homebrew Cask](https://github.com/Homebrew/homebrew-cask) project. +Formerly contained font Casks. -## Let’s try it! - -```bash -$ brew tap homebrew/cask-fonts # You only need to do this once! -$ brew install font-inconsolata -``` - -## Submitting a Cask to this repository - -See [CONTRIBUTING.md](CONTRIBUTING.md). - -## Font Licenses - -homebrew-cask-fonts will only accept fonts which are freely-distributable. However, even freely-distributable fonts may have limitations (for instance, if you use them in a commercial enterprise). It is the responsibility of the user to know and respect the license of each font. - -## homebrew-cask-fonts License - -Code is under the [BSD 2 Clause (NetBSD) license](https://github.com/Homebrew/homebrew-cask-fonts/blob/master/LICENSE) +These have mostly been migrated to [Homebrew/homebrew-cask](https://github.com/Homebrew/homebrew-cask) diff --git a/developer/bin/font_casker b/developer/bin/font_casker deleted file mode 100755 index bd5ee9de3214..000000000000 --- a/developer/bin/font_casker +++ /dev/null @@ -1,206 +0,0 @@ -#!/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 diff --git a/developer/bin/import_google_fonts b/developer/bin/import_google_fonts deleted file mode 100755 index 6eab69b5a6f6..000000000000 --- a/developer/bin/import_google_fonts +++ /dev/null @@ -1,361 +0,0 @@ -#!/usr/bin/env python3 -# -# import_google_fonts -# -# Using Python rather than Ruby for the Protocol Buffer parser. -# https://github.com/protocolbuffers/protobuf/issues/6508#issuecomment-522165498 -# -# To install dependencies: -# -# pip3 install gftools html2text jinja2 protobuf - -from functools import reduce -from glob import glob -import os -import re -import sys -from google.protobuf import text_format -import gftools.fonts_public_pb2 as fonts_pb2 -import jinja2 -import html2text -import urllib.request - -def parse_metadata(filename): - # based off of - # https://github.com/googlefonts/gftools/blob/2bfd4acd402b353aaeb46b991e6cad855001e4c8/Lib/gftools/util/google_fonts.py - with open(filename) as f: - meta = fonts_pb2.FamilyProto() - text_format.Merge(f.read(), meta) - return meta - - -class FontCask: - ENVIRONMENT = jinja2.Environment( - keep_trailing_newline=True, trim_blocks=True, undefined=jinja2.StrictUndefined, - ) - TEMPLATE = ENVIRONMENT.from_string( - """cask "{{token}}" do - version :latest - sha256 :no_check - -{% if files|length == 1 %} - url "https://github.com/google/fonts/raw/main/{{folder}}/{{files[0] | urlencode}}" - {%- if not 'github.com/' in homepage %}, - verified: "github.com/google/fonts/" - {%- endif +%} -{% else %} - url "https://github.com/google/fonts.git", - {%- if not 'github.com/' in homepage +%} - verified: "github.com/google/fonts", - {%- endif +%} - branch: "main", - only_path: "{{folder}}" -{% endif %} - name "{{font_name}}" -{% if desc %} - desc "{{desc}}" -{% endif %} - homepage "{{homepage}}" - -{% for file in files %} - font "{{file}}" -{% endfor %} - - # No zap stanza required -end -""" - ) - - def __init__(self, folder, meta, description=None, early_access=False): - self.folder = folder - self.meta = meta - self.desc = description - self.early_access = early_access - self.homepage_override = None - - def font_name(self): - return self.meta.name - - def description(self): - if not self.desc: - return None - - if len(self.desc) == 0: - return None - - return self.desc - - def token(self): - # https://github.com/Homebrew/homebrew-cask-fonts/blob/master/CONTRIBUTING.md#converting-the-canonical-name-to-a-token - token = self.font_name().lower().replace(" ", "-") - return f"font-{token}" - - def dest_path(self): - return os.path.join("Casks", f"{self.token()}.rb") - - def name_path(self): - return self.font_name().replace(" ", "+") - - def homepage(self): - if self.homepage_override is not None: - return self.homepage_override - - if self.early_access: - return f"https://fonts.google.com/earlyaccess" - - return f"https://fonts.google.com/specimen/{self.name_path()}" - - def files(self): - results = [font.filename for font in self.meta.fonts] - results.sort() - return results - - def cask_content(self): - return self.TEMPLATE.render( - token=self.token(), - folder=self.folder, - font_name=self.font_name(), - desc=self.description(), - homepage=self.homepage(), - files=self.files(), - ) - - -def is_other_foundry(cask_path): - with open(cask_path) as f: - contents = f.read() - - return not re.search(r"url ['\"]https://github.com/google/fonts", contents) - - -def should_skip(cask_path): - if os.path.exists(cask_path): - # Cask already exists - if is_other_foundry(cask_path): - print("Other foundry:", cask_path) - # don't overwrite it, per - # https://github.com/Homebrew/homebrew-cask-fonts/blob/master/CONTRIBUTING.md#google-web-font-directory - return True - - return False - - -def metadata_to_cask(meta_file, repo_dir): - folder = os.path.dirname(os.path.relpath(meta_file, start=repo_dir)) - meta = parse_metadata(meta_file) - - description_path = os.path.join(os.path.dirname(meta_file), "DESCRIPTION.en_us.html") - - description = None - - if os.path.exists(description_path): - with open(description_path) as f: - h2t = html2text.HTML2Text() - h2t.ignore_links = True - h2t.ignore_images = True - h2t.ignore_tables = True - h2t.ignore_emphasis = True - - contents = " ".join(h2t.handle(f.read()).replace('"', '').split()) - - regex = r".*" + re.escape(meta.name) + r"\s+(?:.*\s+)?is(?:\s+an?|the)?\s+" - parts = re.split(regex, contents, maxsplit=1) - - if len(parts) > 1: - description = parts[1].split(".")[0].capitalize() - - return FontCask(folder, meta, description=description) - - -def write_cask(cask): - path = cask.dest_path() - if should_skip(path): - return False - - content = cask.cask_content() - - if os.path.exists(path): - with open(path, "r") as f: - if f.read() == content: - return False - - with open(path, "w") as f: - f.write(content) - - return True - - -def find_google_casks(): - casks = {} - - for cask_path in glob('Casks/*.rb'): - token = os.path.splitext(os.path.basename(cask_path))[0] - - with open(cask_path, "r") as f: - contents = f.read() - - # Skip "font-material-symbols" as it matches the url regex, but is not included in the Google Fonts repo - if os.path.basename(cask_path) == "font-material-symbols.rb": - continue - - if not re.search(r"(github\.com\/google\/fonts|fonts\.google\.com|google\.com/fonts)", contents): - continue - - homepage = re.search(r"homepage\s+([\"'])(.*)(\1)\s*", contents) - if homepage: - homepage = homepage[2] - - desc = re.search(r"desc\s+([\"'])(.*)(\1)\s*", contents) - if desc: - description = desc[2] - else: - description = None - - casks[token] = { - 'path': cask_path, - 'description': description, - 'homepage': homepage, - } - - return casks - -def find_family_folders(repo_dir): - SUBDIRS = ["apache", "ofl", "ufl"] - folders_list = [glob(os.path.join(repo_dir, subdir, "*")) for subdir in SUBDIRS] - # flatten - return reduce(lambda x, y: x + y, folders_list) - - -# https://www.geeksforgeeks.org/python-split-camelcase-string-to-individual-strings/ -def camel_case_split(str): - return re.findall(r"[A-Z](?:[a-z]+|[A-Z]*(?=[A-Z]|$))", str) - - -def derive_name(font_file): - parent_dir_name = os.path.basename(os.path.dirname(font_file)) - - font_file = os.path.splitext(os.path.basename(font_file))[0] - font_file = font_file[: len(parent_dir_name)] - font_file = re.sub(r"-\w+$", "", font_file) - - name_parts = camel_case_split(font_file) - result = " ".join(name_parts) - return result - - -def derive_metadata(family_folder): - """Create a metadata object based on the contents of the folder.""" - - meta = fonts_pb2.FamilyProto() - - font_files = glob(os.path.join(family_folder, "*.ttf")) - # grab the first font, arbitrarily - meta.name = derive_name(font_files[0]) - - fonts = [ - fonts_pb2.FontProto(filename=os.path.basename(filename)) - for filename in font_files - ] - meta.fonts.extend(fonts) - - return meta - - -def derive_cask(family_folder, repo_dir): - meta = derive_metadata(family_folder) - folder = os.path.relpath(family_folder, start=repo_dir) - - early_access_file = os.path.join(family_folder, "EARLY_ACCESS.category") - early_access = os.path.exists(early_access_file) - - return FontCask(folder, meta, early_access=early_access) - - -def run(): - if len(sys.argv) != 3: - print( - """Usage: ./import_google_fonts - -Download the or clone the repository from https://github.com/google/fonts, then provide the path to the script. - """ - ) - sys.exit(1) - - repo_dir = sys.argv[1] - mode = sys.argv[2] - family_folders = find_family_folders(repo_dir) - - existing_casks = find_google_casks() - added_casks = {} - updated_casks = {} - - for family_folder in family_folders: - meta_file = os.path.join(family_folder, "METADATA.pb") - # check if the metadata file is present - # https://github.com/google/fonts/issues/2512 - if os.path.exists(meta_file): - try: - cask = metadata_to_cask(meta_file, repo_dir) - except text_format.ParseError: - continue - else: - cask = derive_cask(family_folder, repo_dir) - - # Ek Mukta has been renamed to just Mukta but still exists. - if cask.token() == 'font-ek-mukta': - continue - - # Skip cask if already handled (i.e. if it exists in multiple license sub-directories). - if cask.token() in added_casks or cask.token() in updated_casks: - continue - - existing_cask = existing_casks.pop(cask.token(), None) - if existing_cask: - cask.desc = existing_cask['description'] - - # If font is unreleased, re-use previous homepage URL. - if cask.homepage() != existing_cask['homepage']: - try: - urllib.request.urlopen(cask.homepage()) - except urllib.request.URLError as e: - if e.code == 404: - cask.homepage_override = existing_cask['homepage'] - - updated_casks[cask.token()] = cask - else: - try: - urllib.request.urlopen(cask.homepage()) - except urllib.request.URLError as e: - if e.code == 404: - cask.homepage_override = cask.meta.source.repository_url - - added_casks[cask.token()] = cask - - changed_casks = {} - - print("Mode " + mode) - - if not mode or mode == 'add': - print("Adding added casks") - - changed_casks |= added_casks - - if not mode or mode == 'update': - print("Adding updated casks") - changed_casks |= updated_casks - - written_casks = 0 - for token, cask in changed_casks.items(): - if write_cask(cask): - written_casks += 1 - - # Limit cask changes per PR. - if mode is not None and written_casks >= 50: - break - - if not mode or mode == 'delete': - # Delete casks which don't exist anymore. - for deleted_cask in existing_casks.values(): - os.remove(deleted_cask["path"]) - -run()