build: move font generation to the main repo (#2837)

* build: move font generation to the main repo

* Update fonts

* chore: remove submodules

* Update paths

* Update fonts.yml

Co-authored-by: Kevin Barabash <kevinb@khanacademy.org>
This commit is contained in:
ylemkimon
2021-03-30 03:21:59 +09:00
committed by GitHub
parent 1643b5deb5
commit 266fcb046c
100 changed files with 6872 additions and 37 deletions

View File

@@ -10,5 +10,5 @@
"settings": {
"terminal.integrated.shell.linux": "/bin/bash"
},
"postCreateCommand": "git submodule update --init --recursive && CI=true yarn install"
"postCreateCommand": "CI=true yarn install"
}

View File

@@ -16,7 +16,6 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
persist-credentials: false # minimize exposure and prevent accidental pushes
- name: Use Node.js 12.x
@@ -64,7 +63,6 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
fetch-depth: 0
- name: Use Node.js 12.x

122
.github/workflows/fonts.yml vendored Normal file
View File

@@ -0,0 +1,122 @@
name: Fonts
on:
pull_request_target:
branches: [ master ]
types: [ labeled ] # 'build fonts' label
jobs:
docker:
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'build fonts')
outputs:
image: ${{ steps.check-image.outputs.result }}
steps:
- uses: actions/checkout@v2
with:
ref: ${{ format('refs/pull/{0}/merge', github.event.pull_request.number) }}
persist-credentials: false # minimize exposure and prevent accidental pushes
- name: Remove label
uses: actions/github-script@v3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.issues.removeLabel({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: "build fonts",
});
- name: Check image
id: check-image
uses: actions/github-script@v3
with:
# https://github.community/t/github-token-has-no-access-to-new-container-rest-apis/170395
github-token: ${{ secrets.GH_PACKAGES_TOKEN }}
result-encoding: string
script: |
/* eslint-disable camelcase, max-len, no-console */
const org = context.repo.owner;
const package_name = "fonts";
const version = "${{ hashFiles('dockers/fonts/Dockerfile') }}";
let packages;
try {
packages = (await github.request('GET /orgs/{org}/packages/{package_type}/{package_name}/versions', {
org,
package_type: "container",
package_name,
})).data;
} catch (e) {
packages = [];
console.error(e);
}
core.setOutput("exists", packages.some(p => p.metadata.container.tags.includes(version)));
return `ghcr.io/${org}/${package_name}:${version}`.toLowerCase();
- name: Login to GitHub Container Registry
if: ${{ !fromJSON(steps.check-image.outputs.exists) }}
uses: docker/login-action@v1
with:
registry: ghcr.io
username: KaTeX-bot
# https://github.community/t/cant-authenticate-with-actions-token-for-pr-event/170752
password: ${{ secrets.GH_PACKAGES_TOKEN }}
- name: Build and push
if: ${{ !fromJSON(steps.check-image.outputs.exists) }}
uses: docker/build-push-action@v2
with:
context: dockers/fonts
tags: ${{ steps.check-image.outputs.result }}
push: true
fonts:
runs-on: ubuntu-latest
needs: docker
container:
image: ${{ needs.docker.outputs.image }}
credentials:
username: KaTeX-bot
# https://github.community/t/cant-authenticate-with-actions-token-for-pr-event/170752
password: ${{ secrets.GH_PACKAGES_TOKEN }}
steps:
- uses: actions/checkout@v2
with:
ref: ${{ format('refs/pull/{0}/merge', github.event.pull_request.number) }}
fetch-depth: 0
persist-credentials: false
- name: Build fonts
run: |
export SOURCE_DATE_EPOCH=$(git log -1 --format=%ct -- src/fonts)
make -C src/fonts all
rm -f fonts/*.*
cp src/fonts/ttf/*.ttf fonts
cp src/fonts/woff/*.woff fonts
cp src/fonts/woff2/*.woff2 fonts
echo $SOURCE_DATE_EPOCH > fonts/VERSION
- name: Build metrics
run: ./dockers/fonts/buildMetrics.sh
- name: Run git diff
run: |
printf '[diff "font"]\n\tbinary = true\n\ttextconv = ttx -q -i -o -' >> ~/.gitconfig
git diff --exit-code
- uses: actions/upload-artifact@v2
if: failure()
with:
name: fonts
path: fonts
- uses: actions/upload-artifact@v2
if: failure()
with:
name: metrics
path: src/fontMetricsData.js

View File

@@ -40,7 +40,6 @@ jobs:
- uses: actions/checkout@v2
with:
ref: ${{ github.event_name == 'pull_request_target' && format('refs/pull/{0}/merge', github.event.pull_request.number) || '' }}
submodules: recursive
persist-credentials: false # do not persist credentials
- name: Use Node.js 14

4
.gitignore vendored
View File

@@ -40,3 +40,7 @@ website/.yarn/*
!website/.yarn/plugins
!website/.yarn/sdks
!website/.yarn/versions
fonts.tar
fonts.tar.gz
temp/

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "submodules/katex-fonts"]
path = submodules/katex-fonts
url = https://github.com/KaTeX/katex-fonts

View File

@@ -151,21 +151,6 @@ Flow by running `yarn test:flow`. See [Flow](https://flow.org/) for more details
- commits should be squashed before merging
- large pull requests should be broken into separate pull requests (or multiple logically cohesive commits), if possible
## Working with submodules
The fonts for KaTeX live in a submodule stored in `submodules/katex-fonts`.
When you first clone the KaTeX repository, use
`git submodule update --init --recursive` to download the corresponding
fonts repository. After running `yarn`, you should have Git hooks that
will automatically run this command after switching to branches
where `submodules/katex-fonts` point to different commits.
When submitting pull requests that update katex-fonts, you'll need to submit
two pull requests: one for [KaTeX/katex-fonts](https://github.com/KaTeX/katex-fonts) and one for [KaTeX/KaTeX](https://github.com/KaTeX/KaTeX).
For more info about how to use git submodules,
see https://chrisjean.com/git-submodules-adding-using-removing-and-updating/.
## CLA
In order to contribute to KaTeX, you must first sign the CLA, found at www.khanacademy.org/r/cla

40
dockers/fonts/Dockerfile Normal file
View File

@@ -0,0 +1,40 @@
FROM ubuntu:14.04
MAINTAINER xymostech <xymostech@gmail.com>
# Install things
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get -y upgrade \
&& DEBIAN_FRONTEND=noninteractive apt-get -y install \
--no-install-recommends --auto-remove \
software-properties-common \
texlive \
wget \
fontforge \
mftrace \
man-db \
build-essential \
python-fontforge \
python-dev \
python-pip \
pkg-config \
libharfbuzz-dev \
libfreetype6-dev \
libjson-perl \
&& add-apt-repository ppa:git-core/ppa \
&& apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get -y install \
--no-install-recommends --auto-remove \
git \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& pip install fonttools==3.28.0 brotli zopfli
# Download and compile ttfautohint
RUN wget "http://download.savannah.gnu.org/releases/freetype/ttfautohint-1.3.tar.gz" \
&& tar -xzf ttfautohint-*.tar.gz \
&& cd ttfautohint-*/ \
&& ./configure --without-qt \
&& make \
&& mv frontend/ttfautohint /usr/bin \
&& cd .. \
&& rm -r ttfautohint-*

31
dockers/fonts/README.md Normal file
View File

@@ -0,0 +1,31 @@
### How to generate KaTeX fonts and metrics
Originally based on MathJax font generation
#### Fonts
The `buildFonts.sh` script should do everything automatically,
as long as Docker is installed.
If you want to try out a change
to [the katex-fonts repository](https://github.com/KaTeX/katex-fonts),
create a local clone (or download and unpack the ZIP file)
and specify the path to this directory as an argument to `buildFonts.sh`.
You can also specify a local or remote tarball,
e.g. a GitHub download of your own personal feature branch.
The script `buildFonts.sh` automatically creates Docker images
from the supplied `Dockerfile`.
It uses the hash of the file to tag the image, so a change to the file
will result in the creation of a new image.
If you want to see all created images, run `docker images katex/fonts`.
To remove all generated images, you can run
`docker rmi $(docker images --format '{{.Repository}}:{{.Tag}}' katex/fonts)`.
#### Metrics
The script `buildMetrics.sh` generates [metrics](../../src/fontMetricsData.js)
(dimensions of each character) for the generated fonts.
See [detailed requirements for running this script](../../src/metrics/).
If there is a problem, file a bug report.

101
dockers/fonts/buildFonts.sh Executable file
View File

@@ -0,0 +1,101 @@
#!/usr/bin/env bash
shopt -s extglob
usage() {
while [[ $# -gt 1 ]]; do
echo "$1" >&2
shift
done
echo "Usage: ${0##*/} [OPTIONS]"
echo ""
echo "OPTIONS:"
echo " -h|--help display this help"
echo " --image NAME:TAG use the named docker image [$IMAGE]"
exit $1
}
used_fonts=(
KaTeX_AMS-Regular
KaTeX_Caligraphic-Bold
KaTeX_Caligraphic-Regular
KaTeX_Fraktur-Bold
KaTeX_Fraktur-Regular
KaTeX_Main-Regular
KaTeX_Main-Bold
KaTeX_Main-Italic
KaTeX_Main-BoldItalic
KaTeX_Math-Italic
KaTeX_Math-BoldItalic
KaTeX_SansSerif-Bold
KaTeX_SansSerif-Italic
KaTeX_SansSerif-Regular
KaTeX_Script-Regular
KaTeX_Size1-Regular
KaTeX_Size2-Regular
KaTeX_Size3-Regular
KaTeX_Size4-Regular
KaTeX_Typewriter-Regular
)
filetypes=( ttf woff woff2 )
set -e
cd "$(dirname "$0")/../.."
cleanup() {
[[ "${CONTAINER}" ]] \
&& docker stop "${CONTAINER}" >/dev/null \
&& docker rm "${CONTAINER}" >/dev/null
CONTAINER=
[[ -f "${TMPFILE}" ]] && rm "${TMPFILE}"
TMPFILE=
}
CONTAINER=
trap cleanup EXIT
LAST_COMMIT_DATE="$(git log -1 --format=%ct -- src/fonts)"
IMAGE="katex/fonts:DF-$(openssl sha1 $(dirname "$0")/Dockerfile | tail -c 9)"
TMPFILE="$(mktemp "${TMPDIR:-/tmp}/mjf.XXXXXXXX")"
FILE="$TMPFILE"
pushd "src"
if [[ ! -f fonts/Makefile ]]; then
echo "src does not look like katex-fonts" >&2
exit 1
fi
tar cfP "$FILE" ../package.json fonts
popd
# build image if missing
if [[ $(docker images "$IMAGE" | wc -l) -lt 2 ]]; then
echo "Need to build docker image $IMAGE"
docker build --tag "$IMAGE" "$(dirname "$0")"
fi
CMDS="set -ex
export SOURCE_DATE_EPOCH=${LAST_COMMIT_DATE}
tar xfP katex-fonts.tar.gz
make -C fonts all
tar cf /fonts.tar ${filetypes[*]/#/fonts/}"
echo "Creating and starting docker container from image $IMAGE"
CONTAINER=$(docker create "$IMAGE" /bin/sh -c "${CMDS}")
if [[ ${FILE} ]]; then
docker cp "${FILE}" $CONTAINER:/katex-fonts.tar.gz
fi
docker start --attach $CONTAINER
docker cp $CONTAINER:/fonts.tar .
cleanup
echo "Docker executed successfully, will now unpack the fonts"
tar xf fonts.tar
mv fonts temp
mkdir fonts
for filetype in "${filetypes[@]}"; do
for font in "${used_fonts[@]}"; do
echo "$filetype/$font"
mv "temp/$filetype/$font".* ./fonts/
done
done
rm -rf temp fonts.tar
echo $LAST_COMMIT_DATE > fonts/VERSION

7
dockers/fonts/buildMetrics.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
# Generates fontMetricsData.js
PERL="perl"
PYTHON=`python2 --version >/dev/null 2>&1 && echo python2 || echo python`
cd src/metrics
$PERL ./mapping.pl | $PYTHON ./extract_tfms.py | $PYTHON ./extract_ttfs.py | $PYTHON ./format_json.py --width > ../fontMetricsData.js

View File

@@ -2,7 +2,7 @@
id: font
title: Font
---
By changing the variables in the `fonts.less` file at the [katex-fonts submodule](https://github.com/KaTeX/katex-fonts/),
By changing the variables in the `src/fonts.less` file,
several properties of the way fonts are used can be changed.
## Font size and lengths

View File

@@ -24,9 +24,9 @@ yarn global add katex
### Building from Source
To build you will need Git, Node.js 10 or later, and Yarn.
Clone a copy of the GitHub source repository and its submodules:
Clone a copy of the GitHub source repository:
```bash
git clone --recursive https://github.com/KaTeX/KaTeX.git
git clone https://github.com/KaTeX/KaTeX.git
cd KaTeX
```

BIN
fonts/KaTeX_AMS-Regular.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
fonts/KaTeX_Main-Bold.ttf Normal file

Binary file not shown.

BIN
fonts/KaTeX_Main-Bold.woff Normal file

Binary file not shown.

BIN
fonts/KaTeX_Main-Bold.woff2 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
fonts/KaTeX_Main-Italic.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
fonts/KaTeX_Math-Italic.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
fonts/VERSION Normal file
View File

@@ -0,0 +1 @@
1615612969

View File

@@ -114,9 +114,7 @@
},
"husky": {
"hooks": {
"pre-commit": "yarn test:lint",
"post-merge": "git submodule update --init --recursive",
"post-checkout": "git submodule update --init --recursive"
"pre-commit": "yarn test:lint"
}
},
"jest": {

View File

@@ -6,9 +6,6 @@
":prNotPending",
"group:linters"
],
"git-submodules": {
"enabled": true
},
"lockFileMaintenance": {
"enabled": true,
"schedule": ["at any time"],

View File

@@ -30,7 +30,7 @@ import buildCommon from "./buildCommon";
import {getCharacterMetrics} from "./fontMetrics";
import symbols from "./symbols";
import utils from "./utils";
import fontMetricsData from "../submodules/katex-fonts/fontMetricsData";
import fontMetricsData from "./fontMetricsData";
import type Options from "./Options";
import type {CharacterMetrics} from "./fontMetrics";

View File

@@ -96,7 +96,7 @@ const sigmasAndXis = {
// metrics, including height, depth, italic correction, and skew (kern from the
// character to the corresponding \skewchar)
// This map is generated via `make metrics`. It should not be changed manually.
import metricMap from "../submodules/katex-fonts/fontMetricsData";
import metricMap from "./fontMetricsData";
// These are very rough approximations. We default to Times New Roman which
// should have Latin-1 and Cyrillic characters, but may not depending on the

2076
src/fontMetricsData.js Normal file

File diff suppressed because it is too large Load Diff

64
src/fonts.less Normal file
View File

@@ -0,0 +1,64 @@
@font-folder: "../fonts";
@use-ttf: true;
@use-woff: true;
@use-woff2: true;
.use-woff2(@family, @family-suffix) when (@use-woff2 = true) {
src+: url('@{font-folder}/KaTeX_@{family}-@{family-suffix}.woff2') format('woff2')
}
.use-woff(@family, @family-suffix) when (@use-woff = true) {
src+: url('@{font-folder}/KaTeX_@{family}-@{family-suffix}.woff') format('woff')
}
.use-ttf(@family, @family-suffix) when (@use-ttf = true) {
src+: url('@{font-folder}/KaTeX_@{family}-@{family-suffix}.ttf') format('truetype')
}
.generate-suffix(@weight, @style) when (@weight = normal) and (@style = normal) {
@suffix: 'Regular';
}
.generate-suffix(@weight, @style) when (@weight = normal) and (@style = italic) {
@suffix: 'Italic';
}
.generate-suffix(@weight, @style) when (@weight = bold) and (@style = normal) {
@suffix: 'Bold';
}
.generate-suffix(@weight, @style) when (@weight = bold) and (@style = italic) {
@suffix: 'BoldItalic';
}
.font-face(@family, @weight, @style) {
.generate-suffix(@weight, @style);
@font-face {
font-family: 'KaTeX_@{family}';
.use-woff2(@family, @suffix);
.use-woff(@family, @suffix);
.use-ttf(@family, @suffix);
font-weight: @weight;
font-style: @style;
}
}
.font-face('AMS', normal, normal);
.font-face('Caligraphic', bold, normal);
.font-face('Caligraphic', normal, normal);
.font-face('Fraktur', bold, normal);
.font-face('Fraktur', normal, normal);
.font-face('Main', bold, normal);
.font-face('Main', bold, italic);
.font-face('Main', normal, italic);
.font-face('Main', normal, normal);
//.font-face('Math', bold, normal);
.font-face('Math', bold, italic);
.font-face('Math', normal, italic);
//.font-face('Math', normal, normal);
.font-face('SansSerif', bold, normal);
.font-face('SansSerif', normal, italic);
.font-face('SansSerif', normal, normal);
.font-face('Script', normal, normal);
.font-face('Size1', normal, normal);
.font-face('Size2', normal, normal);
.font-face('Size3', normal, normal);
.font-face('Size4', normal, normal);
.font-face('Typewriter', normal, normal);

9
src/fonts/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
lib/blacker.mf
lib/mftrace-modified
lib/Space.otf
ff
otf
pfa
ttf
woff
woff2

139
src/fonts/Makefile Normal file
View File

@@ -0,0 +1,139 @@
#!gmake
#
# Version: Apache License 2.0
#
# Copyright (c) 2013 MathJax Project
# Copyright (c) 2013 The MathJax Consortium
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
CUSTOM=custom.cfg
-include $(CUSTOM)
MFTRACE_MODIFIED=lib/mftrace-modified
all: config fonts
$(CUSTOM):
@cp default.cfg $(CUSTOM);
$(CUSTOM).pl: $(CUSTOM)
@echo "Creating Perl config file..."
@cp $(CUSTOM) $(CUSTOM).pl
@echo >> $(CUSTOM).pl # ensure that the config file ends by a new line
@echo "MFTRACE_PATH=`$(WHICH) $(MFTRACE)`" >> $(CUSTOM).pl
@$(SED) -i "s|^\([A-Z_0-9]*\)=\(.*\)|$$\1='\2';|" $(CUSTOM).pl
@echo "1;" >> $(CUSTOM).pl
.PHONY: config
config: $(CUSTOM).pl
blacker: $(MFTRACE_MODIFIED)
$(MFTRACE_MODIFIED):
$(PERL) makeBlacker 15 # values between 10 and 30 seem best
pfa: $(MFTRACE_MODIFIED)
@echo "cmr10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify cmr10
@echo "cmmi10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --encoding $(TETEXENCODING)/aae443f0.enc --simplify cmmi10
@echo "cmsy10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --encoding $(TETEXENCODING)/10037936.enc --simplify cmsy10
@echo "cmex10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify cmex10
@echo "cmbx10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify cmbx10
@echo "cmbxti10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify cmbxti10
@echo "cmti10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify cmti10
@echo "msam10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify --encoding $(TETEXENCODING)/10037936.enc msam10
@echo "msbm10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify --encoding $(TETEXENCODING)/10037936.enc msbm10
@echo "cmmib10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --encoding $(TETEXENCODING)/aae443f0.enc --simplify cmmib10
@echo "cmbsy10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --encoding $(TETEXENCODING)/10037936.enc --simplify cmbsy10
@echo "cmtt10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify cmtt10
@echo "cmss10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify cmss10
@echo "cmssi10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify cmssi10
@echo "cmssbx10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify cmssbx10
@echo "eufm10"
cp "`$(KPSEWHICH) eufm10.pfb`" eufm10.pfb
@echo "eufb10"
cp "`$(KPSEWHICH) eufb10.pfb`" eufb10.pfb
# echo "eusm10"
# $(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify eusm10
# echo "eusb10"
# $(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify eusb10
@echo "rsfs10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify --encoding $(BASEENCODING)/tex256.enc rsfs10
mkdir -p pfa
rm -f pfa/*
mv *.pfa pfa
mv *.pfb pfa
ff: pfa
mkdir -p ff otf
rm -f ff/* otf/*
$(PERL) makeFF
.PHONY: fonts
fonts: ff
mkdir -p ttf woff woff2
rm -f ttf/* woff/* woff2/*
@for file in `ls ff/*.ff | $(SED) 's|ff/\(.*\)\.ff|\1|'`; do \
echo ""; \
echo $$file; \
$(FONTFORGE) -lang=ff -script ff/$$file.ff; \
\
echo "Hinting $$file"; \
if echo "$$file" | $(GREP) -q -e "Size[1-4]" -e "Typewriter"; then \
$(TTFAUTOHINT) --windows-compatibility --symbol ttf/$$file.ttf ttf/$$file.ttf.hinted; \
else \
$(TTFAUTOHINT) --windows-compatibility ttf/$$file.ttf ttf/$$file.ttf.hinted; \
fi; \
mv ttf/$$file.ttf.hinted ttf/$$file.ttf; \
\
echo "Generating $$file..."; \
$(PYTHON) generate_fonts.py ttf/$$file.ttf; \
done
clean:
rm -f $(CUSTOM).pl
rm -f $(MFTRACE_MODIFIED) lib/blacker.mf
rm -rf pfa ff otf ttf woff woff2

20
src/fonts/default.cfg Normal file
View File

@@ -0,0 +1,20 @@
# Note: paths should be absolute, unless they point to programs in your $PATH.
##### Standard programs #####
GREP=grep
PERL=perl
PYTHON=python
SED=sed
WHICH=which
KPSEWHICH=kpsewhich
##### Font tools #####
# Most of the tools below are standard and should be available from your package manager.
FONTFORGE=fontforge
MFTRACE=mftrace
TTX=ttx
TTFAUTOHINT=ttfautohint
##### TeXLive Encoding
TETEXENCODING=/usr/share/texlive/texmf-texlive/fonts/enc/dvips/tetex/
BASEENCODING=/usr/share/texlive/texmf-texlive/fonts/enc/dvips/base/

61
src/fonts/generate_fonts.py Executable file
View File

@@ -0,0 +1,61 @@
#!/usr/bin/env python2
import sys
import os
import json
from fontTools.ttLib import TTFont, sfnt
from fontTools.misc.timeTools import timestampNow
sfnt.USE_ZOPFLI = True
if len(sys.argv) < 2:
print "Usage: %s <font file>" % sys.argv[0]
sys.exit(1)
font_file = sys.argv[1]
font_name = os.path.splitext(os.path.basename(font_file))[0]
# now or SOURCE_DATE_EPOCH, if present
timestamp = timestampNow()
font = TTFont(font_file, recalcBBoxes=False, recalcTimestamp=False)
font['head'].created = timestamp
font['head'].modified = timestamp
# remove fontforge timestamps
if 'FFTM' in font:
del font['FFTM']
# remove redundant GDEF table
if 'GDEF' in font:
del font['GDEF']
# remove Macintosh table
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cmap.html
font['name'].names = [record for record in font['name'].names if record.platformID != 1]
font['cmap'].tables = [table for table in font['cmap'].tables if table.platformID != 1]
font['name'].setName(unicode('Version ' + str(timestamp)), 5, 3, 1, 1033)
# fix OS/2 and hhea metrics
glyf = font['glyf']
ascent = int(max(glyf[c].yMax for c in font.getGlyphOrder() if hasattr(glyf[c], "yMax")))
descent = -int(min(glyf[c].yMin for c in font.getGlyphOrder() if hasattr(glyf[c], "yMin")))
font['OS/2'].usWinAscent = ascent
font['OS/2'].usWinDescent = descent
font['hhea'].ascent = ascent
font['hhea'].descent = -descent
# save TTF
font.save(font_file, reorderTables=None)
# save WOFF
font.flavor = 'woff'
font.save(os.path.join('woff', font_name + '.woff'), reorderTables=None)
# save WOFF2
font.flavor = 'woff2'
font.save(os.path.join('woff2', font_name + '.woff2'), reorderTables=None)

BIN
src/fonts/lib/Extra.otf Normal file

Binary file not shown.

234
src/fonts/lib/Space.ttx Normal file
View File

@@ -0,0 +1,234 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="OTTO" ttLibVersion="3.28">
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
<GlyphID id="0" name=".notdef"/>
<GlyphID id="1" name="space"/>
<GlyphID id="2" name="uni00A0"/>
</GlyphOrder>
<head>
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="1.0"/>
<fontRevision value="1.0"/>
<checkSumAdjustment value="0xa98c8795"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00000011"/>
<unitsPerEm value="1000"/>
<created value="Sun Jan 24 23:04:46 2010"/>
<modified value="Sat May 14 12:26:22 2011"/>
<xMin value="50"/>
<yMin value="0"/>
<xMax value="200"/>
<yMax value="533"/>
<macStyle value="00000000 00000000"/>
<lowestRecPPEM value="8"/>
<fontDirectionHint value="2"/>
<indexToLocFormat value="0"/>
<glyphDataFormat value="0"/>
</head>
<hhea>
<tableVersion value="0x00010000"/>
<ascent value="800"/>
<descent value="-200"/>
<lineGap value="90"/>
<advanceWidthMax value="250"/>
<minLeftSideBearing value="50"/>
<minRightSideBearing value="50"/>
<xMaxExtent value="200"/>
<caretSlopeRise value="1"/>
<caretSlopeRun value="0"/>
<caretOffset value="0"/>
<reserved0 value="0"/>
<reserved1 value="0"/>
<reserved2 value="0"/>
<reserved3 value="0"/>
<metricDataFormat value="0"/>
<numberOfHMetrics value="1"/>
</hhea>
<maxp>
<tableVersion value="0x5000"/>
<numGlyphs value="3"/>
</maxp>
<OS_2>
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
will be recalculated by the compiler -->
<version value="2"/>
<xAvgCharWidth value="225"/>
<usWeightClass value="400"/>
<usWidthClass value="5"/>
<fsType value="00000000 00000000"/>
<ySubscriptXSize value="650"/>
<ySubscriptYSize value="700"/>
<ySubscriptXOffset value="0"/>
<ySubscriptYOffset value="140"/>
<ySuperscriptXSize value="650"/>
<ySuperscriptYSize value="700"/>
<ySuperscriptXOffset value="0"/>
<ySuperscriptYOffset value="480"/>
<yStrikeoutSize value="49"/>
<yStrikeoutPosition value="258"/>
<sFamilyClass value="0"/>
<panose>
<bFamilyType value="0"/>
<bSerifStyle value="0"/>
<bWeight value="0"/>
<bProportion value="0"/>
<bContrast value="0"/>
<bStrokeVariation value="0"/>
<bArmStyle value="0"/>
<bLetterForm value="0"/>
<bMidline value="0"/>
<bXHeight value="0"/>
</panose>
<ulUnicodeRange1 value="10000000 00000000 00000000 11101111"/>
<ulUnicodeRange2 value="00010000 00000000 11101100 11101101"/>
<ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
<achVendID value="PfEd"/>
<fsSelection value="00000000 01000000"/>
<usFirstCharIndex value="32"/>
<usLastCharIndex value="160"/>
<sTypoAscender value="800"/>
<sTypoDescender value="-200"/>
<sTypoLineGap value="90"/>
<usWinAscent value="533"/>
<usWinDescent value="0"/>
<ulCodePageRange1 value="00100000 00000000 00000000 10001111"/>
<ulCodePageRange2 value="01011110 00000011 00000000 00000000"/>
<sxHeight value="0"/>
<sCapHeight value="0"/>
<usDefaultChar value="32"/>
<usBreakChar value="32"/>
<usMaxContext value="1"/>
</OS_2>
<name>
<namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
Copyright (c) 2009-2010 Design Science, Inc.
Copyright (c) 2014-2018 Khan Academy
</namerecord>
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
*NAME*
</namerecord>
<namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
*WEIGHT_S*
</namerecord>
<namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
FontForge 2.0 : *NAME*-*WEIGHT*
</namerecord>
<namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
*NAME*-*WEIGHT*
</namerecord>
<namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
Version 1.1
</namerecord>
<namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
*NAME*-*WEIGHT*
</namerecord>
<namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
Copyright (c) 2009-2010, Design Science, Inc. (&lt;www.mathjax.org&gt;)
Copyright (c) 2014-2018 Khan Academy (&lt;www.khanacademy.org&gt;),
with Reserved Font Name *NAME*.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license available with a FAQ at:
http://scripts.sil.org/OFL
</namerecord>
<namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
http://scripts.sil.org/OFL
</namerecord>
</name>
<cmap>
<tableVersion version="0"/>
<cmap_format_4 platformID="0" platEncID="3" language="0">
<map code="0x20" name="space"/><!-- SPACE -->
<map code="0xa0" name="uni00A0"/><!-- NO-BREAK SPACE -->
</cmap_format_4>
<cmap_format_4 platformID="3" platEncID="1" language="0">
<map code="0x20" name="space"/><!-- SPACE -->
<map code="0xa0" name="uni00A0"/><!-- NO-BREAK SPACE -->
</cmap_format_4>
</cmap>
<post>
<formatType value="3.0"/>
<italicAngle value="0.0"/>
<underlinePosition value="-125"/>
<underlineThickness value="50"/>
<isFixedPitch value="0"/>
<minMemType42 value="0"/>
<maxMemType42 value="0"/>
<minMemType1 value="0"/>
<maxMemType1 value="0"/>
</post>
<CFF>
<major value="1"/>
<minor value="0"/>
<CFFFont name="*NAME*-*WEIGHT*">
<version value="001.001"/>
<Notice value="Copyright (c) 2009-2010 Design Science, Inc., Copyright (c) 2014-2018 Khan Academy"/>
<FullName value="*NAME*-*WEIGHT*"/>
<FamilyName value="*NAME*"/>
<Weight value="*NORMAL*"/>
<isFixedPitch value="0"/>
<ItalicAngle value="0"/>
<UnderlinePosition value="-150"/>
<UnderlineThickness value="50"/>
<PaintType value="0"/>
<CharstringType value="2"/>
<FontMatrix value="0.001 0 0 0.001 0 0"/>
<FontBBox value="50 0 200 533"/>
<StrokeWidth value="0"/>
<!-- charset is dumped separately as the 'GlyphOrder' element -->
<Encoding name="StandardEncoding"/>
<Private>
<BlueScale value="0.03963"/>
<BlueShift value="0"/>
<BlueFuzz value="1"/>
<StdHW value="50"/>
<StdVW value="50"/>
<ForceBold value="0"/>
<LanguageGroup value="0"/>
<ExpansionFactor value="0.06"/>
<initialRandomSeed value="0"/>
<defaultWidthX value="250"/>
<nominalWidthX value="193"/>
</Private>
<CharStrings>
<CharString name=".notdef">
0 50 433 50 hstem
50 50 50 50 vstem
50 hmoveto
150 533 -150 hlineto
50 -483 rmoveto
433 50 -433 vlineto
endchar
</CharString>
<CharString name="space">
endchar
</CharString>
<CharString name="uni00A0">
endchar
</CharString>
</CharStrings>
</CFFFont>
<GlobalSubrs>
<!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
</GlobalSubrs>
</CFF>
<hmtx>
<mtx name=".notdef" width="250" lsb="50"/>
<mtx name="space" width="250" lsb="0"/>
<mtx name="uni00A0" width="250" lsb="0"/>
</hmtx>
</ttFont>

49
src/fonts/makeBlacker Executable file
View File

@@ -0,0 +1,49 @@
#! /usr/bin/perl
# Creates the metafont file needed for darker copies of the TeX fonts,
# and modifies mftrace to use it.
#
# Usage: ./makeBlacker blackness
require "custom.cfg.pl";
$blacker = shift;
unless ($blacker) {
print stderr "Usage: ./makeBlacker blackness\n";
exit;
}
sub editMftrace {
my $oldMFTRACE = $MFTRACE_PATH;
$MFTRACE = "./lib/mftrace-modified";
print "Editing mftrace\n";
open(MFT,$oldMFTRACE) || die "Can't read '$oldMFTRACE': $!\n";
my $MFT = join("",<MFT>);
close(MFT);
$MFT =~ s!r"mf '\\mode:=(?:[^;]*)(; [^"]*)"!r"""mf '\\smode:="lib/blacker.mf"$1"""!;
open(MFT,">$MFTRACE") || die "Can't write '$MFTRACE': $!\n";
print MFT $MFT;
close(MFT);
chmod 0755, $MFTRACE;
}
sub makeBlackerMF {
my $blacker = shift;
print "Using blacker = $blacker\n";
open(BLACKER,">lib/blacker.mf") || die "Can't write 'lib/blacker.mf': $!\n";
print BLACKER << " END";
proofing:=0;
fontmaking:=1;
tracingtitles:=0;
pixels_per_inch:=1200;
blacker:=$blacker;
fillin:=0;
o_correction:=1;
END
close(BLACKER);
}
editMftrace();
makeBlackerMF($blacker);
1;

2003
src/fonts/makeFF Executable file

File diff suppressed because it is too large Load Diff

182
src/fonts/xbbold.mf Normal file
View File

@@ -0,0 +1,182 @@
%% filename: xbbold.mf
%% version: 2.2
%% date: 1995/01/04
%%
%% (katex-fonts) The line 69 is modified to prevent overflow
%%
%% American Mathematical Society
%% Technical Support
%% Publications Technical Group
%% 201 Charles Street
%% Providence, RI 02904
%% USA
%% tel: (401) 455-4080
%% (800) 321-4267 (USA and Canada only)
%% fax: (401) 331-3842
%% email: tech-support@ams.org
%%
%% Copyright 1995, 2009 American Mathematical Society.
%%
%% This Font Software is licensed under the SIL Open Font License,
%% Version 1.1. This license is in the accompanying file OFL.txt, and
%% is also available with a FAQ at: http://scripts.sil.org/OFL.
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Changes of minimal parameters in outlined characters for version 2.1
% done by Stefan Lindner, 18-April-1991
input xbbase;
%%mode_setup; %called by amsyb.mf; two calls confuse Metafont. NGB 15-OCT-1991
%%%%designsize:= font_size; % was 10pt#;
width#:= designsize; % was 10pt#;
unit#:= width#/18;
u#:= width#/54;
smallu#:= width#/162;
ascender#:= 37/3*unit#;
cap#:= 37/3*unit#;
number#:= 36/3*unit#;
xheight#:= 25/3*unit#;
descender#:= 12/4*unit#;
define_whole_vertical_pixels
(width,unit,u,smallu,ascender,cap,number,xheight,descender);
wpix(1.90u) (linethickness);
wpix(0.65u) (Sover_bot);
wpix(1.00u) (Aapex,Napex,Vapex,Wapex,Cover,Gover,Oover,Sover_top,Uover);
wpix(9.00u) (Uthin_bracket);
wpix(8.00u) (Kthin_diag_bracket,Xthin_diag_bracket,Ythin_diag_bracket);
wpix(7.00u) (k_thin_diag);
wpix(6.00u) (c_thin_stem_bracket);
wpix(5.00u) (c_thick_stem_bracket,c_inner_bracket,lc_thick_stem_bracket);
wpix(4.00u) (c_round_bracket);
adjpix(1.35u) (serif_thickness);
adjpix(1.30u) (Emid_tip,inbeak);
adjpix(1.50u) (Atip,Btopthin,Bmidthin,Ebot_tip,Ltip,Mapex,
Ntip,Ttip,Vtip,Wtip,Ztip,outbeak);
adjpix(1.65u) (Bbotthin,Gbotthin,Stopthin);
adjpix(1.75u) (Dtopthin,Ebotarm,Lthin,Tthin);
adjpix(1.80u) (Abar,Ctopthin,Dbotthin,Gtopthin,Jbotthin,Pmidarm,Sbotthin);
adjpix(1.90u) (Emidarm,Etoparm,Othin,Pthin,Rthin,Ydiag,Zthin);
adjpix(2.00u) (kthin,Mthin_diag,Wleftthin);
adjpix(2.10u) (Ctip);
adjpix(2.25u) (Athin,Kthin,Mthin_vert,Nthin,Uthin,Vthin,Wrightthin,Xthin);
adjpix(2.50u) (Hbar);
adjpix(2.60u) (Cbotthin);
%%%% Begin of changes for version 2.1
%(katex-fonts) Originally was pixels_per_inch*designsize < 1500:
if pixels_per_inch < 1500/designsize:
if pixels_per_inch*designsize < 1000:
if pixels_per_inch*designsize < 800:
if pixels_per_inch*designsize < 700:
minadjpix(0)(8.80u) (stem);
minadjpix(0)(6.80u) (kdiag);
minadjpix(0)(7.40u) (kstem);
minadjpix(0)(7.80u) (Jbulb,Mdiag);
minadjpix(0)(8.20u) (Kdiag);
minadjpix(0)(8.30u) (Gstem,Mstem);
minadjpix(0)(8.60u) (Lstem,Ustem,Ythick_diag);
minadjpix(0)(8.50u) (Bstem,Estem,Fstem,Ndiag,Rdiag,Xdiag,Zdiag);
minadjpix(0)(8.90u) (Btopcurve);
minadjpix(1)(9.30u) (Bbotcurve,Pcurve,Rcurve);
minadjpix(1)(9.50u) (Ccurve,Dcurve,Gcurve,Ocurve);
else:
minadjpix(1)(8.80u) (stem);
minadjpix(1)(6.80u) (kdiag);
minadjpix(1)(7.40u) (kstem);
minadjpix(1)(7.80u) (Jbulb,Mdiag);
minadjpix(1)(8.20u) (Kdiag);
minadjpix(1)(8.30u) (Gstem,Mstem);
minadjpix(1)(8.60u) (Lstem,Ustem,Ythick_diag);
minadjpix(1)(8.50u) (Bstem,Estem,Fstem,Ndiag,Rdiag,Xdiag,Zdiag);
minadjpix(1)(8.90u) (Btopcurve);
minadjpix(2)(9.30u) (Bbotcurve,Pcurve,Rcurve);
minadjpix(2)(9.50u) (Ccurve,Dcurve,Gcurve,Ocurve);
fi
else:
adjpix(3.0u) (Mapex);
minadjpix(1)(8.80u) (stem);
minadjpix(2)(6.80u) (kdiag);
minadjpix(2)(7.40u) (kstem);
minadjpix(2)(7.80u) (Jbulb);
minadjpix(1)(6.00u) (Mdiag);
minadjpix(2)(8.20u) (Kdiag);
minadjpix(2)(8.30u) (Gstem)
minadjpix(2)(8.30u) (Mstem);
minadjpix(2)(8.60u) (Lstem,Ustem,Ythick_diag);
minadjpix(2)(8.50u) (Bstem,Ndiag,Rdiag,Xdiag,Zdiag);
minadjpix(1)(8.50u) (Estem, Fstem);
minadjpix(2)(8.90u) (Btopcurve);
minadjpix(3)(9.30u) (Bbotcurve,Pcurve,Rcurve);
minadjpix(3)(9.50u) (Ccurve,Dcurve,Gcurve,Ocurve);
fi
else:
adjpix(3.0u) (Mapex);
minadjpix(2)(8.80u) (stem);
minadjpix(3)(6.80u) (kdiag);
minadjpix(3)(7.40u) (kstem);
minadjpix(3)(7.80u) (Jbulb);
minadjpix(1)(5.00u) (Mdiag);
minadjpix(3)(8.20u) (Kdiag);
minadjpix(3)(8.30u) (Gstem);
minadjpix(2)(8.30u) (Mstem);
minadjpix(3)(8.60u) (Lstem,Ustem,Ythick_diag);
minadjpix(3)(8.50u) (Estem,Fstem,Ndiag,Rdiag,Xdiag,Zdiag);
minadjpix(2)(8.50u) (Bstem);
minadjpix(3)(8.90u) (Btopcurve);
minadjpix(3)(9.30u) (Bbotcurve,Pcurve,Rcurve);
minadjpix(3)(9.50u) (Ccurve,Dcurve,Gcurve,Ocurve)
fi
else:
minadjpix(4)(8.80u) (stem);
minadjpix(4)(6.80u) (kdiag);
minadjpix(4)(7.40u) (kstem);
minadjpix(4)(7.80u) (Jbulb,Mdiag);
minadjpix(4)(8.20u) (Kdiag);
minadjpix(4)(8.30u) (Gstem,Mstem);
minadjpix(4)(8.60u) (Lstem,Ustem,Ythick_diag);
minadjpix(4)(8.50u) (Bstem,Estem,Fstem,Ndiag,Rdiag,Xdiag,Zdiag);
minadjpix(4)(8.90u) (Btopcurve);
minadjpix(5)(9.30u) (Bbotcurve,Pcurve,Rcurve);
minadjpix(5)(9.50u) (Ccurve,Dcurve,Gcurve,Ocurve)
fi;
%%%% end of changes for version 2.1
boolean lowres; lowres:=width<50;
highres_lowres(pullin) (.85)(1); % Emidarm
highres_lowres(pulleven) (1)(1.3); % Etoparm,Tarms,Zarms
highres_lowres(pullout) (1.1)(1); % Ebotarm,Lbotarm
highres_lowres(bracket0) (.0)(0); % Ntopleft
highres_lowres(bracket3) (.3)(0); % Nthinstems
highres_lowres(bracket01) (.0)(.1); % Uthin
highres_lowres(bracket32) (.3)(.2); % Vstems
highres_lowres(bracket4) (.4)(0); % P-all,R-all,I-all,F-all
highres_lowres(bracket42) (.4)(.2); % Xdiag
bool(ctrls):=false;
entasis:=inlimit(0)(0,1);
serif_constant_amt:=0pt;
join_radius:=1;
bool(softpath):=true;
c_thick_stem_bracket:=min(.5cap-eps,c_thick_stem_bracket);
rulepen:=pensquare scaled 1;
extra_beginchar:=extra_beginchar&"save t,p,ref; path p[],p[]',p[]'',ref[];";
extra_beginchar:=extra_beginchar&"pickup pencircle scaled linethickness;";
for x:="R":
wanted[byte x]:=true; endfor % test these characters
let iff=always_iff; % tests all chars in the file
font_normal_space .3width#; % TeX fontdimen 2 normal word space
font_normal_stretch .15width#; % TeX fontdimen 3 interword stretch
font_normal_shrink .1width#; % TeX fontdimen 4 interword shrink
font_x_height xheight#; % Tex fontdinem 5 for accents
font_quad width#; % TeX fontdimen 6 quad width
font_extra_space .1width#; % TeX fontdimen 7 extra space(period)
input xbcaps
bye % changed from "end" 26 Aug 93; bnb

View File

@@ -1,5 +1,5 @@
/* stylelint-disable font-family-no-missing-generic-family-keyword */
@import "../submodules/katex-fonts/fonts.less";
@import "fonts.less";
// The mu unit is defined as 1/18 em
@mu: (1em / 18);

View File

@@ -4,7 +4,7 @@
* This can be used to define some commands in terms of others.
*/
import fontMetricsData from "../submodules/katex-fonts/fontMetricsData";
import fontMetricsData from "./fontMetricsData";
import functions from "./functions";
import symbols from "./symbols";
import utils from "./utils";

1
src/metrics/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.pyc

23
src/metrics/README.md Normal file
View File

@@ -0,0 +1,23 @@
### How to generate new metrics
-------------------------------
There are several requirements for generating the metrics used by KaTeX.
- You need to have an installation of TeX which supports kpathsea. You can check
this by running `tex --version`, and seeing if it has a line that looks like
> kpathsea version 6.2.0
- You need the Perl module `JSON`. You can install this either from CPAN
(e.g. using the `cpan` command line tool: `cpan install JSON`)
or with your package manager.
- You need the Python module `fonttools`. You can install this either from PyPI
(using `easy_install` or `pip`: `pip install fonttools`)
or with your package manager.
Once you have these things, run the following command from the root directory:
sh ./docker/fonts/buildMetrics.sh
which should generate new metrics and place them into `fontMetricsData.json`.
You're done!

114
src/metrics/extract_tfms.py Executable file
View File

@@ -0,0 +1,114 @@
#!/usr/bin/env python
import collections
import json
import parse_tfm
import subprocess
import sys
def find_font_path(font_name):
try:
font_path = subprocess.check_output(['kpsewhich', font_name])
except OSError:
raise RuntimeError("Couldn't find kpsewhich program, make sure you" +
" have TeX installed")
except subprocess.CalledProcessError:
raise RuntimeError("Couldn't find font metrics: '%s'" % font_name)
return font_path.strip()
def main():
mapping = json.load(sys.stdin)
fonts = [
'cmbsy10.tfm',
'cmbx10.tfm',
'cmbxti10.tfm',
'cmex10.tfm',
'cmmi10.tfm',
'cmmib10.tfm',
'cmr10.tfm',
'cmsy10.tfm',
'cmti10.tfm',
'msam10.tfm',
'msbm10.tfm',
'eufm10.tfm',
'cmtt10.tfm',
'rsfs10.tfm',
'cmss10.tfm',
'cmssbx10.tfm',
'cmssi10.tfm',
]
# Extracted by running `\font\a=<font>` and then `\showthe\skewchar\a` in
# TeX, where `<font>` is the name of the font listed here. The skewchar
# will be printed out in the output. If it outputs `-1`, that means there
# is no skewchar, so we use `None` here.
font_skewchar = {
'cmbsy10': None,
'cmbx10': None,
'cmbxti10': None,
'cmex10': None,
'cmmi10': 127,
'cmmib10': None,
'cmr10': None,
'cmsy10': 48,
'cmti10': None,
'msam10': None,
'msbm10': None,
'eufm10': None,
'cmtt10': None,
'rsfs10': None,
'cmss10': None,
'cmssbx10': None,
'cmssi10': None,
}
font_name_to_tfm = {}
for font_name in fonts:
font_basename = font_name.split('.')[0]
font_path = find_font_path(font_name)
font_name_to_tfm[font_basename] = parse_tfm.read_tfm_file(font_path)
families = collections.defaultdict(dict)
for family, chars in mapping.iteritems():
for char, char_data in chars.iteritems():
char_num = int(char)
font = char_data['font']
tex_char_num = int(char_data['char'])
yshift = float(char_data['yshift'])
if family == "Script-Regular":
tfm_char = font_name_to_tfm[font].get_char_metrics(tex_char_num,
fix_rsfs=True)
else:
tfm_char = font_name_to_tfm[font].get_char_metrics(tex_char_num)
height = round(tfm_char.height + yshift / 1000.0, 5)
depth = round(tfm_char.depth - yshift / 1000.0, 5)
italic = round(tfm_char.italic_correction, 5)
width = round(tfm_char.width, 5)
skewkern = 0.0
if (font_skewchar[font] and
font_skewchar[font] in tfm_char.kern_table):
skewkern = round(
tfm_char.kern_table[font_skewchar[font]], 5)
families[family][char_num] = {
'height': height,
'depth': depth,
'italic': italic,
'skew': skewkern,
'width': width
}
sys.stdout.write(
json.dumps(families, separators=(',', ':'), sort_keys=True))
if __name__ == '__main__':
main()

119
src/metrics/extract_ttfs.py Executable file
View File

@@ -0,0 +1,119 @@
#!/usr/bin/env python
from fontTools.ttLib import TTFont
import sys
import json
# map of characters to extract
metrics_to_extract = {
# Font name
"AMS-Regular": {
u"\u21e2": None, # \dashrightarrow
u"\u21e0": None, # \dashleftarrow
},
"Main-Regular": {
# Skew and italic metrics can't be easily parsed from the TTF. Instead,
# we map each character to a "base character", which is a character
# from the same font with correct italic and skew metrics. A character
# maps to None if it doesn't have a base.
#u"\u2209": None, # \notin
#u"\u2260": None, # \neq
u"\u2245": None, # \cong
u"\u2026": None, # \ldots
u"\u22ef": None, # \cdots
u"\u22f1": None, # \ddots
u"\u22ee": None, # \vdots
u"\u22ee": None, # \vdots
u"\u22a8": None, # \models
u"\u22c8": None, # \bowtie
u"\u2250": None, # \doteq
u"\u23b0": None, # \lmoustache
u"\u23b1": None, # \rmoustache
u"\u27ee": None, # \lgroup
u"\u27ef": None, # \rgroup
u"\u27f5": None, # \longleftarrow
u"\u27f8": None, # \Longleftarrow
u"\u27f6": None, # \longrightarrow
u"\u27f9": None, # \Longrightarrow
u"\u27f7": None, # \longleftrightarrow
u"\u27fa": None, # \Longleftrightarrow
u"\u21a6": None, # \mapsto
u"\u27fc": None, # \longmapsto
u"\u21a9": None, # \hookleftarrow
u"\u21aa": None, # \hookrightarrow
u"\u21cc": None, # \rightleftharpoons
},
"Size1-Regular": {
u"\u222c": u"\u222b", # \iint, based on \int
u"\u222d": u"\u222b", # \iiint, based on \int
},
"Size2-Regular": {
u"\u222c": u"\u222b", # \iint, based on \int
u"\u222d": u"\u222b", # \iiint, based on \int
},
}
def main():
start_json = json.load(sys.stdin)
for font in start_json:
fontInfo = TTFont("../../fonts/KaTeX_" + font + ".ttf")
glyf = fontInfo["glyf"]
widths = fontInfo.getGlyphSet()
unitsPerEm = float(fontInfo["head"].unitsPerEm)
# We keep ALL Unicode cmaps, not just fontInfo["cmap"].getcmap(3, 1).
# This is playing it extra safe, since it reports inconsistencies.
# Platform 0 is Unicode, platform 3 is Windows. For platform 3,
# encoding 1 is UCS-2 and encoding 10 is UCS-4.
cmap = [t.cmap for t in fontInfo["cmap"].tables
if (t.platformID == 0)
or (t.platformID == 3 and t.platEncID in (1, 10))]
chars = metrics_to_extract.get(font, {})
chars[u"\u0020"] = None # space
chars[u"\u00a0"] = None # nbsp
for char, base_char in chars.iteritems():
code = ord(char)
names = set(t.get(code) for t in cmap)
if not names:
sys.stderr.write(
"Codepoint {} of font {} maps to no name\n"
.format(code, font))
continue
if len(names) != 1:
sys.stderr.write(
"Codepoint {} of font {} maps to multiple names: {}\n"
.format(code, font, ", ".join(sorted(names))))
continue
name = names.pop()
height = depth = italic = skew = width = 0
glyph = glyf[name]
if glyph.numberOfContours:
height = glyph.yMax / unitsPerEm
depth = -glyph.yMin / unitsPerEm
width = widths[name].width / unitsPerEm
if base_char:
base_char_str = str(ord(base_char))
base_metrics = start_json[font][base_char_str]
italic = base_metrics["italic"]
skew = base_metrics["skew"]
width = base_metrics["width"]
start_json[font][str(code)] = {
"height": height,
"depth": depth,
"italic": italic,
"skew": skew,
"width": width
}
sys.stdout.write(
json.dumps(start_json, separators=(',', ':'), sort_keys=True))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python
import sys
import json
props = ['depth', 'height', 'italic', 'skew']
if len(sys.argv) > 1:
if sys.argv[1] == '--width':
props.append('width')
data = json.load(sys.stdin)
sys.stdout.write(
"// This file is GENERATED by buildMetrics.sh. DO NOT MODIFY.\n")
sep = "export default {\n "
for font in sorted(data):
sys.stdout.write(sep + json.dumps(font))
sep = ": {\n "
for glyph in sorted(data[font], key=int):
sys.stdout.write(sep + json.dumps(glyph) + ": ")
values = [value if value != 0.0 else 0 for value in
[data[font][glyph][key] for key in props]]
sys.stdout.write(json.dumps(values))
sep = ",\n "
sep = ",\n },\n "
sys.stdout.write(",\n },\n};\n")

1224
src/metrics/mapping.pl Executable file

File diff suppressed because it is too large Load Diff

211
src/metrics/parse_tfm.py Normal file
View File

@@ -0,0 +1,211 @@
class CharInfoWord(object):
def __init__(self, word):
b1, b2, b3, b4 = (word >> 24,
(word & 0xff0000) >> 16,
(word & 0xff00) >> 8,
word & 0xff)
self.width_index = b1
self.height_index = b2 >> 4
self.depth_index = b2 & 0x0f
self.italic_index = (b3 & 0b11111100) >> 2
self.tag = b3 & 0b11
self.remainder = b4
def has_ligkern(self):
return self.tag == 1
def ligkern_start(self):
return self.remainder
class LigKernProgram(object):
def __init__(self, program):
self.program = program
def execute(self, start, next_char):
curr_instruction = start
while True:
instruction = self.program[curr_instruction]
(skip, inst_next_char, op, remainder) = instruction
if inst_next_char == next_char:
if op < 128:
# Don't worry about ligatures for now, we only need kerns
return None
else:
return 256 * (op - 128) + remainder
elif skip >= 128:
return None
else:
curr_instruction += 1 + skip
class TfmCharMetrics(object):
def __init__(self, width, height, depth, italic, kern_table):
self.width = width
self.height = height
self.depth = depth
self.italic_correction = italic
self.kern_table = kern_table
class TfmFile(object):
def __init__(self, start_char, end_char, char_info, width_table,
height_table, depth_table, italic_table, ligkern_table,
kern_table):
self.start_char = start_char
self.end_char = end_char
self.char_info = char_info
self.width_table = width_table
self.height_table = height_table
self.depth_table = depth_table
self.italic_table = italic_table
self.ligkern_program = LigKernProgram(ligkern_table)
self.kern_table = kern_table
def get_char_metrics(self, char_num, fix_rsfs=False):
"""Return glyph metrics for a unicode code point.
Arguments:
char_num: a unicode code point
fix_rsfs: adjust for rsfs10.tfm's different indexing system
"""
if char_num < self.start_char or char_num > self.end_char:
raise RuntimeError("Invalid character number")
if fix_rsfs:
# all of the char_nums contained start from zero in rsfs10.tfm
info = self.char_info[char_num - self.start_char]
else:
info = self.char_info[char_num + self.start_char]
char_kern_table = {}
if info.has_ligkern():
for char in range(self.start_char, self.end_char + 1):
kern = self.ligkern_program.execute(info.ligkern_start(), char)
if kern:
char_kern_table[char] = self.kern_table[kern]
return TfmCharMetrics(
self.width_table[info.width_index],
self.height_table[info.height_index],
self.depth_table[info.depth_index],
self.italic_table[info.italic_index],
char_kern_table)
class TfmReader(object):
def __init__(self, f):
self.f = f
def read_byte(self):
return ord(self.f.read(1))
def read_halfword(self):
b1 = self.read_byte()
b2 = self.read_byte()
return (b1 << 8) | b2
def read_word(self):
b1 = self.read_byte()
b2 = self.read_byte()
b3 = self.read_byte()
b4 = self.read_byte()
return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4
def read_fixword(self):
word = self.read_word()
neg = False
if word & 0x80000000:
neg = True
word = (-word & 0xffffffff)
return (-1 if neg else 1) * word / float(1 << 20)
def read_bcpl(self, length):
str_length = self.read_byte()
data = self.f.read(length - 1)
return data[:str_length]
def read_tfm_file(file_name):
with open(file_name, 'rb') as f:
reader = TfmReader(f)
# file_size
reader.read_halfword()
header_size = reader.read_halfword()
start_char = reader.read_halfword()
end_char = reader.read_halfword()
width_table_size = reader.read_halfword()
height_table_size = reader.read_halfword()
depth_table_size = reader.read_halfword()
italic_table_size = reader.read_halfword()
ligkern_table_size = reader.read_halfword()
kern_table_size = reader.read_halfword()
# extensible_table_size
reader.read_halfword()
# parameter_table_size
reader.read_halfword()
# checksum
reader.read_word()
# design_size
reader.read_fixword()
if header_size > 2:
# coding_scheme
reader.read_bcpl(40)
if header_size > 12:
# font_family
reader.read_bcpl(20)
for i in range(header_size - 17):
reader.read_word()
char_info = []
for i in range(start_char, end_char + 1):
char_info.append(CharInfoWord(reader.read_word()))
width_table = []
for i in range(width_table_size):
width_table.append(reader.read_fixword())
height_table = []
for i in range(height_table_size):
height_table.append(reader.read_fixword())
depth_table = []
for i in range(depth_table_size):
depth_table.append(reader.read_fixword())
italic_table = []
for i in range(italic_table_size):
italic_table.append(reader.read_fixword())
ligkern_table = []
for i in range(ligkern_table_size):
skip = reader.read_byte()
next_char = reader.read_byte()
op = reader.read_byte()
remainder = reader.read_byte()
ligkern_table.append((skip, next_char, op, remainder))
kern_table = []
for i in range(kern_table_size):
kern_table.append(reader.read_fixword())
# There is more information, like the ligkern, kern, extensible, and
# param table, but we don't need these for now
return TfmFile(start_char, end_char, char_info, width_table,
height_table, depth_table, italic_table,
ligkern_table, kern_table)

View File

@@ -15,7 +15,6 @@ module.exports = function(wallaby) {
"src/**/*.js",
"test/**/*.js",
"contrib/**/*.js",
"submodules/**/*.js",
"katex.js",
// These paths are excluded.