Commit c4ecb92a authored by Sebastian Friedl's avatar Sebastian Friedl
Browse files

Merge branch 'staging' into 'release'

GYPT Fonts 0.1.0

See merge request !1
parents 1da10384 d47d3843
# .editorconfig file
# see https://editorconfig.org/ for documentation
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
[*.py]
insert_final_newline = true
trim_trailing_whitespace = true
# Python Virtual Environment
venv/
# Files generated by scripts
out/
# macOS
.DS_Store
# IDEs, Editors
/.idea
*.swp
---
stages:
- prepare
- build
- upload
- release
variables:
GIT_SUBMODULE_STRATEGY: recursive
prepare_environment:
stage: prepare
rules:
- if: $CI_COMMIT_TAG
when: never
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
image:
name: alpine:3.14
script:
- FNT_REV="$(cat VERSION)"
- FNT_REV="${FNT_REV//[[:space:]]/}"
- echo "FNT_REV=${FNT_REV}" >> variables.env
- echo "REG_GEN=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/gypt-fonts/${FNT_REV}" >> variables.env
artifacts:
reports:
dotenv: variables.env
tags:
- docker
build:
stage: build
rules:
- if: $CI_COMMIT_TAG
when: never
- if: $CI_COMMIT_BRANCH
image:
name: debian:bullseye
before_script:
- apt-get update
- DEBIAN_FRONTEND=noninteractive apt-get install -y $(grep -o ^[^#][[:alnum:]-]* dep/dep.apt)
- python3 -m venv venv
- source venv/bin/activate
- pip install -r dep/dep.pip
- echo "Git commit $CI_COMMIT_SHORT_SHA" > BUILD
- echo "Build started on $(date -R)" >> BUILD
script:
- ./build_fonts.py
- ./bundle.sh
after_script:
- echo "Build finished on $(date -R)" >> BUILD
artifacts:
name: "gypt-fonts_$CI_COMMIT_SHORT_SHA"
paths:
- BUILD
- gypt-fonts.zip
expire_in: 6 hrs
tags:
- docker
gitlab_upload:
stage: upload
rules:
- if: $CI_COMMIT_TAG
when: never
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
image:
name: alpine:3.14
before_script:
- apk -U add curl
script:
- |
curl \
--header "JOB-TOKEN: ${CI_JOB_TOKEN}" \
--upload-file "CHANGELOG" \
"${REG_GEN}/CHANGELOG"
- |
curl \
--header "JOB-TOKEN: ${CI_JOB_TOKEN}" \
--upload-file "gypt-fonts.zip" \
"${REG_GEN}/gypt-fonts.zip"
tags:
- docker
gitlab_release:
stage: release
rules:
- if: $CI_COMMIT_TAG
when: never
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
image:
name: registry.gitlab.com/gitlab-org/release-cli:latest
script:
- |
release-cli create \
--name "GYPT Fonts, Version ${FNT_REV}" --tag-name "v${FNT_REV}" \
--assets-link "{\"name\": \"Changelog\", \"url\": \"${REG_GEN}/CHANGELOG\"}" \
--assets-link "{\"name\": \"GYPT Fonts v${FNT_REV}\", \"url\": \"${REG_GEN}/gypt-fonts.zip\"}"
tags:
- docker
[submodule "adobe/source-serif"]
path = adobe/source-serif
url = https://github.com/adobe-fonts/source-serif.git
shallow = true
[submodule "adobe/source-sans"]
path = adobe/source-sans
url = https://github.com/adobe-fonts/source-sans.git
shallow = true
[submodule "adobe/source-code"]
path = adobe/source-code
url = https://github.com/adobe-fonts/source-code-pro.git
shallow = true
0.1.0 (2021-09-13)
------------------
- Inital Release, based on:
Source Serif Pro 4.004
Source Sans Pro 3.046
Source Code Pro 2.038
Copyright (c) 2010-2021 Adobe (http://www.adobe.com/),
with Reserved Font Name 'Source'.
Copyright (c) 2021 German Young Physicists' Tournament,
with Reserved Font Name 'GYPT'.
All Rights Reserved.
Source is a trademark of Adobe in the United States and/or other countries.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
# GYPT Fonts
This repository contains scripts for generating the GYPT fonts from Adobe’s [Source Serif](https://github.com/adobe-fonts/source-serif/), [Source Sans](https://github.com/adobe-fonts/source-sans/) and [Source Code](https://github.com/adobe-fonts/source-code-pro/) variable font families.
## Development
Development takes place on three layers:
- Feature branches, which are branched off from `staging`, for developing new features.
- `staging` as staging area for the next release.
- `release` contains the currently released version.
The persistent branches are protected. You will need _Maintainer_ permissions to push to the `staging` branch and merge into the `release` branch. Pushing to the `release` branch is forbidden.
Pushing to the repository triggers a CI pipeline that executes all build scripts for the most recent commit of the pushed branch. If the pipeline passes, the build artifacts can be downloaded and inspected.
Merging commits into the `release` branch creates a GitLab release.
Further information regarding development is available in the [project’s wiki](https://git.gypt.org/sebastian.friedl/gypt-fonts/-/wikis/home).
## Obtaining Font Files
Font files are available on the [Releases page](https://git.gypt.org/sebastian.friedl/gypt-fonts/-/releases).
Subproject commit 29fdb884c6e9dc2a312f4a5e2bb3b2dad2350777
Subproject commit c1a1caa4c0b8c13287ee6fac1f80385de03f23ef
Subproject commit 75115e887837d58c1892b24952c8e373616302d5
#!/usr/bin/env python3
from multiprocessing import Pool
import os
from pathlib import Path
import sys
from time import time
from fontTools.ttLib import TTFont
from fontTools.varLib import instancer
_root_dir = Path(__file__).parent.resolve(strict=True)
_timestamp = int(time())
_copyright = '© 2021 German Young Physicists’ Tournament, with reserved Font Name ‘GYPT’.'
_version = os.getenv('FNT_REV', f'0.0.{_timestamp}')
STYLES = [
('Serif', 'Source Serif 4 Variable'),
('Sans', 'Source Sans 3 VF'),
('Mono', 'Source Code Variable'),
]
INSTANCES = {
100: (220, 'UltraLight'),
200: (300, 'ExtraLight'),
300: (340, 'Light'),
400: (400, 'Regular'),
500: (480, 'Book'),
600: (580, 'Semibold'),
700: (640, 'Bold'),
800: (720, 'ExtraBold'),
900: (900, 'Black'),
}
def update_name_table(instance, style, weight, verbose_weight, italic=False):
# Updates information given in the instance's 'name' table.
#
# Documentation regarding the 'name' table is available at:
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html
table = instance['name']
family = f'GYPT {style}'
subfamily = f'{(weight != 400 or not italic)*verbose_weight} {italic*"Italic"}'.strip()
if weight not in (400, 700):
legacy_family = f'{family} {verbose_weight}'
legacy_subfamily = 'Regular' if not italic else 'Italic'
else:
legacy_family, legacy_subfamily = family, subfamily
name = f'{family} {subfamily}' if weight != 400 or italic else family
ps_name = f'{family}-{subfamily}'.replace('Italic', 'It').replace(' ', '')
old_copyright = table.getName(0, 1, 0, 0) or table.getName(0, 3, 1, 1033)
assert old_copyright is not None, f'No copyright information in source for {style} {weight}{italic*"i"}'
old_copyright = old_copyright.toStr()
values = {
0: old_copyright[:(-1 if old_copyright.endswith('.') else None)] + '; ' + _copyright,
1: legacy_family,
2: legacy_subfamily,
3: f'{_timestamp}:{style.lower()}:{weight}{italic*"i"}',
4: name,
5: _version,
6: ps_name,
16: family,
17: subfamily,
25: family.replace(' ', ''),
}
# assert that all entries which should be replaced will be replaced
for nr in [nr for nr in table.names if nr.nameID in values.keys()]:
assert (nr.platformID, nr.platEncID, nr.langID) in [(1, 0, 0), (3, 1, 1033)]
for platform_id, platform_enc_id, lang_id in [(1, 0, 0), (3, 1, 1033)]:
for name_id, string in values.items():
table.setName(string, name_id, platform_id, platform_enc_id, lang_id)
# remove manufacturer name and font vendor url
for name_id in [8, 11]:
table.removeNames(nameID=name_id, platformID=None, platEncID=None, langID=None)
def update_flags(instance, weight, italic=False):
# Adjust font selection flags and weight classes given in the 'OS/2' and 'head' tables.
#
# Documentation regarding the 'OS/2' and 'head' tables is available at:
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6OS2.html
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6head.html
fs_selection = 0
mac_style = 0
if weight == 400 and not italic:
fs_selection = 64
if weight == 700:
fs_selection += 32
mac_style += 1
if italic:
fs_selection += 1
mac_style += 2
instance['OS/2'].fsSelection = fs_selection
instance['OS/2'].usWeightClass = weight
instance['OS/2'].achVendID = 'GYPT'
instance['head'].macStyle = mac_style
def patch_small_caps(font, style, italic=False):
gsub = font['GSUB'].table
lookup_list = []
for fr in [fr for fr in gsub.FeatureList.FeatureRecord if fr.FeatureTag == 'smcp']:
lookup_list += fr.Feature.LookupListIndex
for lookup_id in list(sorted(set(lookup_list))):
lookup = gsub.LookupList.Lookup[lookup_id]
for substitution_table in lookup.SubTable:
if substitution_table.LookupType != 1:
continue
if style == 'Serif' and not italic:
# lookup 35 sets different numbers for smallcaps
if lookup_id == 35:
for g in list(substitution_table.mapping.keys()):
del substitution_table.mapping[g]
# replace ß with appropriate glyph (not ss)
if 'germandbls' in substitution_table.mapping:
substitution_table.mapping['germandbls'] = 'glyph01192'
if style == 'Sans' and not italic:
# lookup 88 sets different numbers for smallcaps
if lookup_id == 88:
for g in list(substitution_table.mapping.keys()):
del substitution_table.mapping[g]
# replace ß with appropriate glyph (not ss)
if 'germandbls' in substitution_table.mapping:
substitution_table.mapping['germandbls'] = 'glyph01708'
def slice_font(style, source, weight, adobe_weight, verbose_weight, italic=False):
source_dir = _root_dir / 'adobe' / '-'.join(source.split(' ')[:2]).lower() / 'VAR'
source_fnt = f'{source.replace(" ", "")}-{"Roman" if not italic else "Italic"}.ttf'
var_font = TTFont(source_dir / source_fnt)
attrs = {'wght': adobe_weight, 'opsz': 20}
instance = instancer.instantiateVariableFont(var_font, {a.axisTag: attrs[a.axisTag] for a in var_font['fvar'].axes})
assert 'fvar' not in instance, 'Instantiated variable font still has variation axes'
update_name_table(instance, style, weight, verbose_weight, italic)
update_flags(instance, weight, italic)
patch_small_caps(instance, style, italic)
for flavor in (None, 'woff', 'woff2'):
output_dir = _root_dir / 'out' / f'gypt-{style.lower()}' / f'{(flavor or "ttf").upper()}'
output_fnt = f'GYPT{style}-{(weight != 400 or not italic)*verbose_weight}{italic*"It"}.{flavor or "ttf"}'
instance.flavor = flavor
os.makedirs(output_dir, exist_ok=True)
instance.save(output_dir / output_fnt)
def generate_stylesheet(style):
def render(sty, wght, verb_wght, it=False, prefix=''):
return '\n'.join([
f"@font-face {{",
f" font-family: 'GYPT {sty}';",
f" font-weight: {wght};",
f" font-style: {'normal' if not it else 'italic'};",
f" font-stretch: normal;",
f" font-display: swap;",
f" src: url({prefix}WOFF2/GYPT{sty}-{(1 - (wght == 400)*it)*verb_wght}{it*'It'}.woff2) format('woff2'),",
f" url({prefix}WOFF/GYPT{sty}-{(1 - (wght == 400)*it)*verb_wght}{it*'It'}.woff) format('woff'),",
f" url({prefix}TTF/GYPT{sty}-{(1 - (wght == 400)*it)*verb_wght}{it*'It'}.ttf) format('truetype');",
f"}}",
])
chunks = {'css': [], 'scss': []}
for weight, options in INSTANCES.items():
_, verbose_weight = options
chunks['css'].extend([
render(style, weight, verbose_weight, it=False, prefix=''),
render(style, weight, verbose_weight, it=True, prefix=''),
])
chunks['scss'].extend([
render(style, weight, verbose_weight, it=False, prefix=f'#{{$gypt{style.lower()}-font-path}}/'),
render(style, weight, verbose_weight, it=True, prefix=f'#{{$gypt{style.lower()}-font-path}}/'),
])
os.makedirs(out_dir := _root_dir / 'out' / f'gypt-{style.lower()}', exist_ok=True)
for file_format, chunks in chunks.items():
with open(out_dir / f'gypt-{style.lower()}.{file_format}', 'w') as file:
file.write('\n'.join(chunks) + '\n')
def main():
exit_code = 0
def term(pool):
def handler(e):
print(e)
pool.terminate()
nonlocal exit_code
exit_code = 1
return handler
with Pool(processes=min(os.cpu_count(), 8), maxtasksperchild=1) as p:
for style, source in STYLES:
for weight, options in INSTANCES.items():
p.apply_async(slice_font, (style, source, weight, *options), {'italic': False}, error_callback=term(p))
p.apply_async(slice_font, (style, source, weight, *options), {'italic': True}, error_callback=term(p))
generate_stylesheet(style)
p.close()
p.join()
if exit_code:
# pool has been terminated, exit with error code
sys.exit(exit_code)
if __name__ == '__main__':
main()
#!/usr/bin/env bash
set -e
# get absolute path to root dir
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
# clean up artifacts from previous builds
rm -f "$ROOT_DIR/gypt-fonts.zip"
# create ZIP archive with created fonts
7za a -tzip -mm=Deflate "$ROOT_DIR/gypt-fonts.zip" "$ROOT_DIR/CHANGELOG" "$ROOT_DIR/COPYING"
for style in "serif" "sans" "mono"; do
7za a -tzip -mm=Deflate "$ROOT_DIR/gypt-fonts.zip" "$ROOT_DIR/out/gypt-$style"
done
git
python3
python3-venv
p7zip-full
brotli==1.0.9
fonttools==4.26.2
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment