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

@@ -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)