inisital commit

This commit is contained in:
2022-11-21 15:40:16 +01:00
commit a9e3c19fb9
41 changed files with 2338 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
$schema: https://json-schema.org/draft/2020-12/schema
$id: https://github.com/cupcakearmy/glyphance
title: Config
type: object
additionalProperties: false
properties:
fonts:
$ref: "#/$defs/fonts"
output:
$ref: "#/$defs/output"
context:
type: string
$defs:
fonts:
type: object
additionalProperties: false
patternProperties:
"^[a-zA-z \\-_]+$":
$ref: "#/$defs/font"
font:
type: array
items:
$ref: "#/$defs/font-variation"
css:
# https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face#descriptors
type: object
additionalProperties: false
patternProperties:
? "^ascent-override|descent-override|font-display|font-family|font-stretch|font-style|font-weight|font-feature-settings|font-variation-settings|line-gap-override|size-adjust|src|unicode-range$"
: type: string
font-variation:
type: object
additionalProperties: false
requiredProperties:
- file
properties:
file:
type: string
variable:
type: boolean
css:
$ref: "#/$defs/css"
range:
type: string
patter: ^U\+[\da-zA-z]{4}(-[\da-zA-z]{4})?$
output:
type: object
additionalProperties: false
properties:
dir:
type: string
prefix:
type: string
css:
$ref: "#/$defs/css"
clean:
type: boolean
formats:
type: array
items:
type: string
enum:
- woff2
- woff
ranges:
type: object
additionalProperties: false
patternProperties:
"^[a-zA-z \\-_]+$":
type: array
items:
$ref: "#/$defs/range"

7
src/assets/css.template Normal file
View File

@@ -0,0 +1,7 @@
/* $range */
@font-face {
font-family: '$font';
src: $src;
unicode-range: $unicodes;
$additional
}

1
src/assets/ranges.json Normal file
View File

@@ -0,0 +1 @@
{" cyrillic-ext": ["U+0460-052F", "U+1C80-1C88", "U+20B4", "U+2DE0-2DFF", "U+A640-A69F", "U+FE2E-FE2F"], " cyrillic": ["U+0301", "U+0400-045F", "U+0490-0491", "U+04B0-04B1", "U+2116"], " greek-ext": ["U+1F00-1FFF"], " greek": ["U+0370-03FF"], " vietnamese": ["U+0102-0103", "U+0110-0111", "U+0128-0129", "U+0168-0169", "U+01A0-01A1", "U+01AF-01B0", "U+1EA0-1EF9", "U+20AB"], " latin-ext": ["U+0100-024F", "U+0259", "U+1E00-1EFF", "U+2020", "U+20A0-20AB", "U+20AD-20CF", "U+2113", "U+2C60-2C7F", "U+A720-A7FF"], " latin": ["U+0000-00FF", "U+0131", "U+0152-0153", "U+02BB-02BC", "U+02C6", "U+02DA", "U+02DC", "U+2000-206F", "U+2074", "U+20AC", "U+2122", "U+2191", "U+2193", "U+2212", "U+2215", "U+FEFF", "U+FFFD"]}

77
src/cmd_optimise.py Normal file
View File

@@ -0,0 +1,77 @@
import copy
import hashlib
import json
import os
import shutil
import string
import subprocess
import utils
with open(utils.asset_path('ranges.json')) as f:
default_ranges = json.load(f)
default_variation = {
'variable': False,
'css': {}
}
def optimise(config):
# Check if destination is fine
destination = os.path.abspath(config['output']['dir'])
try:
if not os.path.isdir(destination):
os.makedirs(destination, exist_ok=True)
if not os.access(destination, os.W_OK):
raise Exception()
except:
print(f'Output directory not writable: "{destination}"')
exit(1)
# Clean
if config['output']['clean']:
print(f'Cleaning: "{destination}"')
shutil.rmtree(destination)
os.makedirs(destination)
# Go over each font and variation of it.
# Then go over every range and format and generate the subset
# Finally add the relevant CSS
css = ''
with open(utils.asset_path('css.template')) as f:
template = string.Template(f.read())
for font, variations in config['fonts'].items():
css += f'\n\n/* {font} */\n'
for variation in variations:
variation = utils.update_deep(default_variation, variation)
print(f"Processing: {font} {os.path.basename(variation['file'])}")
source = os.path.join(config['context'], variation['file'])
for format in config['output']['formats']:
for range, codes in default_ranges.items():
unicodes = ', '.join(codes)
# Create unique key on all parameters
key = font+variation['file']+format+unicodes
key = hashlib.sha1(key.encode()).hexdigest()
# Generate subset
output_file = os.path.join(destination, f'{key}.{format}')
print(f" {range}@{format} -> {output_file}")
command = f'pyftsubset --unicodes="{unicodes}" --layout-features="*" --flavor="{format}" --output-file="{output_file}" {source}'
subprocess.call(command, shell=True)
# Generate CSS
ending = format
if variation['variable']:
ending += '-variations'
src = f"url({config['output']['prefix']}{os.path.basename(output_file)}) format('{ending}')"
merged = utils.update_deep(config['output']['css'], variation['css'])
additional = '\n '.join([f"{key}: {value};" for key, value in merged.items()])
css += template.substitute(range=range, font=font, src=src,
unicodes=unicodes, additional=additional)
with open(os.path.join(destination, 'fonts.css'), 'w') as f:
f.write(css.strip())

48
src/config.py Normal file
View File

@@ -0,0 +1,48 @@
import os
import jsonschema
import yaml
import flags
import utils
default_config = {
'output': {
'dir': 'generated',
'formats': ['woff2'],
'prefix': '/',
'css': {
'font-display': 'swap',
'font-style': 'normal',
'font-weight': '400',
},
'clean': False,
},
"context": ".",
}
def validate(config):
schema_file = utils.asset_path('config.schema.yaml')
with open(schema_file, 'r') as f:
schema = yaml.safe_load(f)
return jsonschema.validate(config, schema)
def load(path):
# Load config
path = os.path.abspath(path)
with open(path, 'r') as f:
config = yaml.safe_load(f)
# Setting dynamic defaults
default_config['context'] = os.path.dirname(path)
if flags.OUTPUT_DIRECTORY != None:
default_config['output']['dir'] = flags.OUTPUT_DIRECTORY
if flags.CLEAN != None:
default_config['output']['clean'] = flags.CLEAN
if flags.PREFIX != None:
default_config['output']['prefix'] = flags.PREFIX
# Merge defaults
return utils.update_deep(default_config, config)

4
src/flags.py Normal file
View File

@@ -0,0 +1,4 @@
VERBOSE = False
OUTPUT_DIRECTORY = None
CLEAN = None
PREFIX = None

26
src/gen_ranges.py Normal file
View File

@@ -0,0 +1,26 @@
import json
import os
import requests
output = './src/json/ranges.json'
guide = requests.get('https://fonts.googleapis.com/css2?family=Roboto&display=swap',
headers={'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:107.0) Gecko/20100101 Firefox/107.0'})
block_height = 9
lines = guide.text.strip().split('\n')
ranges = {}
for i in range(len(lines)//9):
offset = i * block_height
variation = lines[offset: offset+block_height]
name = variation[0][2:-3]
codes = variation[7].replace('unicode-range:', '').replace(';', '').split(',')
codes = [code.strip() for code in codes]
ranges[name] = codes
output = os.path.abspath(output)
os.makedirs(os.path.dirname(output), exist_ok=True)
with open('./src/json/ranges.json', 'w') as f:
json.dump(ranges, f)

32
src/main.py Normal file
View File

@@ -0,0 +1,32 @@
import click
import cmd_optimise
import flags
from config import load, validate
@click.command()
@click.version_option("0.1.0")
@click.option('-v', '--verbose', is_flag=True, default=False, help="Run in verbose mode.")
@click.option('-c', '--config', type=click.Path(), required=True, help="Path to the config file.")
@click.option('-o', '--output-directory', type=click.Path(), help="Path to the output directory.")
@click.option('--clean', is_flag=True, help="Clean the output directory before generating.")
@click.option('--prefix', help="Prefix for the generated css font URLs.")
def cli(verbose, config, output_directory, clean, prefix):
# Flags
flags.VERBOSE = verbose
if flags.VERBOSE:
click.echo("Running in verbose mode.")
flags.OUTPUT_DIRECTORY = output_directory
flags.CLEAN = clean
flags.PREFIX = prefix
# Run
c = load(config)
validate(c)
cmd_optimise.optimise(c)
if __name__ == '__main__':
cli(auto_envvar_prefix="GLYPHANCE")

23
src/utils.py Normal file
View File

@@ -0,0 +1,23 @@
import collections.abc
import copy
import os
def update_deep(d, u, first=True):
"""
Deep update
https://stackoverflow.com/a/3233356/2425183
"""
if first:
d = copy.deepcopy(d)
u = copy.deepcopy(u)
for k, v in u.items():
if isinstance(v, collections.abc.Mapping):
d[k] = update_deep(d.get(k, {}), v, False)
else:
d[k] = v
return d
def asset_path(name: str) -> str:
return os.path.join(os.path.dirname(__file__), 'assets', name)