summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xMakefile28
-rw-r--r--out/RobotoCondensedTTF/RobotoCondensed-Bold.ttfbin0 -> 127336 bytes
-rw-r--r--out/RobotoCondensedTTF/RobotoCondensed-BoldItalic.ttfbin0 -> 135512 bytes
-rw-r--r--out/RobotoCondensedTTF/RobotoCondensed-Italic.ttfbin0 -> 133916 bytes
-rw-r--r--out/RobotoCondensedTTF/RobotoCondensed-Light.ttfbin0 -> 126172 bytes
-rw-r--r--out/RobotoCondensedTTF/RobotoCondensed-LightItalic.ttfbin0 -> 134668 bytes
-rw-r--r--out/RobotoCondensedTTF/RobotoCondensed-Regular.ttfbin0 -> 125332 bytes
-rw-r--r--out/RobotoTTF/Roboto-Black.ttfbin0 -> 127944 bytes
-rw-r--r--out/RobotoTTF/Roboto-BlackItalic.ttfbin0 -> 134724 bytes
-rw-r--r--out/RobotoTTF/Roboto-Bold.ttfbin0 -> 127740 bytes
-rw-r--r--out/RobotoTTF/Roboto-BoldItalic.ttfbin0 -> 134568 bytes
-rw-r--r--out/RobotoTTF/Roboto-Italic.ttfbin0 -> 132448 bytes
-rw-r--r--out/RobotoTTF/Roboto-Light.ttfbin0 -> 126796 bytes
-rw-r--r--out/RobotoTTF/Roboto-LightItalic.ttfbin0 -> 133188 bytes
-rw-r--r--out/RobotoTTF/Roboto-Medium.ttfbin0 -> 127488 bytes
-rw-r--r--out/RobotoTTF/Roboto-MediumItalic.ttfbin0 -> 134312 bytes
-rw-r--r--out/RobotoTTF/Roboto-Regular.ttfbin0 -> 126072 bytes
-rw-r--r--out/RobotoTTF/Roboto-Thin.ttfbin0 -> 127584 bytes
-rw-r--r--out/RobotoTTF/Roboto-ThinItalic.ttfbin0 -> 132868 bytes
-rwxr-xr-xres/buildnumber.txt1
-rwxr-xr-xres/diacritics.txt579
-rwxr-xr-xres/ot_classes.txt9
-rwxr-xr-xres/ot_features.txt27
-rwxr-xr-xres/ot_kerningclasses.txt75
-rwxr-xr-xres/roboto.cfg49
-rwxr-xr-xscripts/build-v2.py148
-rwxr-xr-xscripts/lib/fontbuild/Build.py227
-rwxr-xr-xscripts/lib/fontbuild/__init__.py6
-rw-r--r--scripts/lib/fontbuild/alignpoints.py148
-rwxr-xr-xscripts/lib/fontbuild/anchors.py44
-rwxr-xr-xscripts/lib/fontbuild/convertCurves.py90
-rw-r--r--scripts/lib/fontbuild/curveFitPen.py402
-rwxr-xr-xscripts/lib/fontbuild/generateGlyph.py48
-rwxr-xr-xscripts/lib/fontbuild/instanceNames.py184
-rw-r--r--scripts/lib/fontbuild/italics.py267
-rw-r--r--scripts/lib/fontbuild/italics2.py148
-rwxr-xr-xscripts/lib/fontbuild/kerning.py26
-rwxr-xr-xscripts/lib/fontbuild/mitreGlyph.py121
-rwxr-xr-xscripts/lib/fontbuild/mix.py334
-rw-r--r--src/v2/Roboto_Bold.vfbbin0 -> 149672 bytes
-rw-r--r--src/v2/Roboto_Regular.vfbbin0 -> 137839 bytes
-rw-r--r--src/v2/Roboto_Thin.vfbbin0 -> 144007 bytes
42 files changed, 2961 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100755
index 0000000..c145e46
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,28 @@
+
+all: sans slab mono
+
+sans:
+ echo "BASEDIR=\"$(CURDIR)\"" > /tmp/makefonts.flw
+ cat "scripts/build.py" >> /tmp/makefonts.flw
+ open -nWa "$(FONTLAB)" /tmp/makefonts.flw
+
+v2:
+ echo "BASEDIR=\"$(CURDIR)\"" > /tmp/makefontsB.flw
+ cat "scripts/build-v2.py" >> /tmp/makefontsB.flw
+ open -nWa "$(FONTLAB)" /tmp/makefontsB.flw
+
+
+slab:
+ echo "BASEDIR=\"$(CURDIR)\"" > /tmp/makefonts.flw
+ cat "scripts/build-slab.py" >> /tmp/makefonts.flw
+ open -nWa "$(FONTLAB)" /tmp/makefonts.flw
+
+slabitalic:
+ echo "BASEDIR=\"$(CURDIR)\"" > /tmp/makefonts.flw
+ cat "scripts/build-slabitalic.py" >> /tmp/makefonts.flw
+ open -nWa "$(FONTLAB)" /tmp/makefonts.flw
+
+mono:
+ echo "BASEDIR=\"$(CURDIR)\"" > /tmp/makefonts.flw
+ cat "scripts/build-monoV2.py" >> /tmp/makefonts.flw
+ open -nWa "$(FONTLAB)" /tmp/makefonts.flw
diff --git a/out/RobotoCondensedTTF/RobotoCondensed-Bold.ttf b/out/RobotoCondensedTTF/RobotoCondensed-Bold.ttf
new file mode 100644
index 0000000..fe52e7d
--- /dev/null
+++ b/out/RobotoCondensedTTF/RobotoCondensed-Bold.ttf
Binary files differ
diff --git a/out/RobotoCondensedTTF/RobotoCondensed-BoldItalic.ttf b/out/RobotoCondensedTTF/RobotoCondensed-BoldItalic.ttf
new file mode 100644
index 0000000..2d29ffb
--- /dev/null
+++ b/out/RobotoCondensedTTF/RobotoCondensed-BoldItalic.ttf
Binary files differ
diff --git a/out/RobotoCondensedTTF/RobotoCondensed-Italic.ttf b/out/RobotoCondensedTTF/RobotoCondensed-Italic.ttf
new file mode 100644
index 0000000..1d7eb08
--- /dev/null
+++ b/out/RobotoCondensedTTF/RobotoCondensed-Italic.ttf
Binary files differ
diff --git a/out/RobotoCondensedTTF/RobotoCondensed-Light.ttf b/out/RobotoCondensedTTF/RobotoCondensed-Light.ttf
new file mode 100644
index 0000000..1209024
--- /dev/null
+++ b/out/RobotoCondensedTTF/RobotoCondensed-Light.ttf
Binary files differ
diff --git a/out/RobotoCondensedTTF/RobotoCondensed-LightItalic.ttf b/out/RobotoCondensedTTF/RobotoCondensed-LightItalic.ttf
new file mode 100644
index 0000000..ed1ccea
--- /dev/null
+++ b/out/RobotoCondensedTTF/RobotoCondensed-LightItalic.ttf
Binary files differ
diff --git a/out/RobotoCondensedTTF/RobotoCondensed-Regular.ttf b/out/RobotoCondensedTTF/RobotoCondensed-Regular.ttf
new file mode 100644
index 0000000..4a9a6f9
--- /dev/null
+++ b/out/RobotoCondensedTTF/RobotoCondensed-Regular.ttf
Binary files differ
diff --git a/out/RobotoTTF/Roboto-Black.ttf b/out/RobotoTTF/Roboto-Black.ttf
new file mode 100644
index 0000000..c7d3aec
--- /dev/null
+++ b/out/RobotoTTF/Roboto-Black.ttf
Binary files differ
diff --git a/out/RobotoTTF/Roboto-BlackItalic.ttf b/out/RobotoTTF/Roboto-BlackItalic.ttf
new file mode 100644
index 0000000..8345a10
--- /dev/null
+++ b/out/RobotoTTF/Roboto-BlackItalic.ttf
Binary files differ
diff --git a/out/RobotoTTF/Roboto-Bold.ttf b/out/RobotoTTF/Roboto-Bold.ttf
new file mode 100644
index 0000000..c025885
--- /dev/null
+++ b/out/RobotoTTF/Roboto-Bold.ttf
Binary files differ
diff --git a/out/RobotoTTF/Roboto-BoldItalic.ttf b/out/RobotoTTF/Roboto-BoldItalic.ttf
new file mode 100644
index 0000000..73ea78f
--- /dev/null
+++ b/out/RobotoTTF/Roboto-BoldItalic.ttf
Binary files differ
diff --git a/out/RobotoTTF/Roboto-Italic.ttf b/out/RobotoTTF/Roboto-Italic.ttf
new file mode 100644
index 0000000..58ffced
--- /dev/null
+++ b/out/RobotoTTF/Roboto-Italic.ttf
Binary files differ
diff --git a/out/RobotoTTF/Roboto-Light.ttf b/out/RobotoTTF/Roboto-Light.ttf
new file mode 100644
index 0000000..e2749cf
--- /dev/null
+++ b/out/RobotoTTF/Roboto-Light.ttf
Binary files differ
diff --git a/out/RobotoTTF/Roboto-LightItalic.ttf b/out/RobotoTTF/Roboto-LightItalic.ttf
new file mode 100644
index 0000000..59a6096
--- /dev/null
+++ b/out/RobotoTTF/Roboto-LightItalic.ttf
Binary files differ
diff --git a/out/RobotoTTF/Roboto-Medium.ttf b/out/RobotoTTF/Roboto-Medium.ttf
new file mode 100644
index 0000000..db1f5c8
--- /dev/null
+++ b/out/RobotoTTF/Roboto-Medium.ttf
Binary files differ
diff --git a/out/RobotoTTF/Roboto-MediumItalic.ttf b/out/RobotoTTF/Roboto-MediumItalic.ttf
new file mode 100644
index 0000000..6abd84a
--- /dev/null
+++ b/out/RobotoTTF/Roboto-MediumItalic.ttf
Binary files differ
diff --git a/out/RobotoTTF/Roboto-Regular.ttf b/out/RobotoTTF/Roboto-Regular.ttf
new file mode 100644
index 0000000..dd38dd9
--- /dev/null
+++ b/out/RobotoTTF/Roboto-Regular.ttf
Binary files differ
diff --git a/out/RobotoTTF/Roboto-Thin.ttf b/out/RobotoTTF/Roboto-Thin.ttf
new file mode 100644
index 0000000..4417578
--- /dev/null
+++ b/out/RobotoTTF/Roboto-Thin.ttf
Binary files differ
diff --git a/out/RobotoTTF/Roboto-ThinItalic.ttf b/out/RobotoTTF/Roboto-ThinItalic.ttf
new file mode 100644
index 0000000..812ab89
--- /dev/null
+++ b/out/RobotoTTF/Roboto-ThinItalic.ttf
Binary files differ
diff --git a/res/buildnumber.txt b/res/buildnumber.txt
new file mode 100755
index 0000000..bbd2810
--- /dev/null
+++ b/res/buildnumber.txt
@@ -0,0 +1 @@
+00975 \ No newline at end of file
diff --git a/res/diacritics.txt b/res/diacritics.txt
new file mode 100755
index 0000000..5c12d4a
--- /dev/null
+++ b/res/diacritics.txt
@@ -0,0 +1,579 @@
+breve=cyrillicbreve
+space=nbspace
+hyphen=uni00AD
+D+crossbar:cross=Dcroat/30,0
+D+crossbar:cross=Eth/30,0
+h+crossbar:cross=hbar/30,0
+T+crossbar:cross=Tbar
+t+crossbar:cross=tbar
+A+grave:top=Agrave
+A+acute:top=Aacute
+A+circumflex:top=Acircumflex
+A+tilde:top=Atilde
+A+dieresis:top=Adieresis
+A+ring:top=Aring
+A+ringacute:top=Aringacute
+C+cedilla:bottom=Ccedilla
+E+grave:top=Egrave
+E+acute:top=Eacute
+E+circumflex:top=Ecircumflex
+E+dieresis:top=Edieresis
+I+grave:top=Igrave
+I+acute:top=Iacute
+I+circumflex:top=Icircumflex
+I+dieresis:top=Idieresis
+N+tilde:top=Ntilde
+O+grave:top=Ograve
+O+acute:top=Oacute
+O+circumflex:top=Ocircumflex
+O+tilde:top=Otilde
+O+dieresis:top=Odieresis
+U+grave:top=Ugrave
+U+acute:top=Uacute
+U+circumflex:top=Ucircumflex
+U+dieresis:top=Udieresis
+Y+acute:top=Yacute
+a+grave:top=agrave
+a+acute:top=aacute
+a+circumflex:top=acircumflex
+a+tilde:top=atilde
+a+dieresis:top=adieresis
+a+ring:top=aring
+a+ringacute:top=aringacute
+c+cedilla:bottom=ccedilla
+e+grave:top=egrave
+e+acute:top=eacute
+e+circumflex:top=ecircumflex
+e+dieresis:top=edieresis
+dotlessi+grave:top=igrave
+dotlessi+acute:top=iacute
+dotlessi+circumflex:top=icircumflex
+dotlessi+dieresis:top=idieresis
+n+tilde:top=ntilde
+o+grave:top=ograve
+o+acute:top=oacute
+o+circumflex:top=ocircumflex
+o+tilde:top=otilde
+o+dieresis:top=odieresis
+u+grave:top=ugrave
+u+acute:top=uacute
+u+circumflex:top=ucircumflex
+u+dieresis:top=udieresis
+y+acute:top=yacute
+y+dieresis:top=ydieresis
+A+macron:top=Amacron
+a+macron:top=amacron
+A+breve:top=Abreve
+a+breve:top=abreve
+A+ogonek:ogonek=Aogonek
+a+ogonek:ogonek=aogonek
+C+acute:top=Cacute
+c+acute:top=cacute
+C+circumflex:top=Ccircumflex
+c+circumflex:top=ccircumflex
+C+dotaccent:top=uni010A
+c+dotaccent:top=uni010B
+C+caron:top=Ccaron
+c+caron:top=ccaron
+D+caron:top=Dcaron
+d+commaaccent:caron=dcaron/0,150
+E+macron:top=Emacron
+e+macron:top=emacron
+E+breve:top=Ebreve
+e+breve:top=ebreve
+E+dotaccent:top=Edotaccent
+e+dotaccent:top=edotaccent
+E+ogonek:ogonek=Eogonek
+e+ogonek:ogonek=eogonek
+E+caron:top=Ecaron
+e+caron:top=ecaron
+G+circumflex:top=Gcircumflex
+g+circumflex:top=gcircumflex
+G+breve:top=Gbreve
+g+breve:top=gbreve
+G+dotaccent:top=uni0120
+g+dotaccent:top=uni0121
+G+commaaccent:bottom=Gcommaaccent
+g+commaaccentrotate:top=gcommaaccent
+H+circumflex:top=Hcircumflex
+h+circumflex:top=hcircumflex
+I+tilde:top=Itilde
+dotlessi+tilde:top=itilde
+I+macron:top=Imacron
+dotlessi+macron:top=imacron
+I+breve:top=Ibreve
+dotlessi+breve:top=ibreve
+I+ogonek:ogonek=Iogonek
+i+ogonek:ogonek=iogonek
+I+dotaccent:top=Idotaccent
+I_J=IJ
+i_j=ij
+J+circumflex:top=Jcircumflex
+uni0237+circumflex:top=jcircumflex
+K+commaaccent:bottom=Kcommaaccent
+k+commaaccent:bottom=kcommaaccent
+L+acute:top=Lacute
+l+acute:top=lacute
+L+commaaccent:bottom=Lcommaaccent
+l+commaaccent:bottom=lcommaaccent
+L+commaaccent:caron=Lcaron
+l+commaaccent:caron=lcaron/0,150
+L+dotaccent:dot=Ldot
+l+dotaccent:dot=ldot/0,220
+N+acute:top=Nacute
+n+acute:top=nacute
+N+commaaccent:bottom=Ncommaaccent
+n+commaaccent:bottom=ncommaaccent
+N+caron:top=Ncaron
+n+caron:top=ncaron
+n+commaaccent:caron=napostrophe
+O+macron:top=Omacron
+o+macron:top=omacron
+O+breve:top=Obreve
+o+breve:top=obreve
+O+hungarumlaut:top=Ohungarumlaut
+o+hungarumlaut:top=ohungarumlaut
+R+acute:top=Racute
+r+acute:top=racute
+R+commaaccent:bottom=Rcommaaccent
+r+commaaccent:bottom=rcommaaccent
+R+caron:top=Rcaron
+r+caron:top=rcaron
+S+acute:top=Sacute
+s+acute:top=sacute
+S+circumflex:top=Scircumflex
+s+circumflex:top=scircumflex
+S+cedilla:bottom=Scedilla
+s+cedilla:bottom=scedilla
+S+commaaccent:bottom=uni0218
+s+commaaccent:bottom=uni0219
+S+caron:top=Scaron
+s+caron:top=scaron
+T+commaaccent:bottom=uni021A
+t+commaaccent:bottom=uni021B
+T+cedilla:bottom=uni0162
+t+cedilla:bottom=uni0163
+T+caron:top=Tcaron
+t+commaaccent:caron=tcaron/0,40
+U+tilde:top=Utilde
+u+tilde:top=utilde
+U+macron:top=Umacron
+u+macron:top=umacron
+U+breve:top=Ubreve
+u+breve:top=ubreve
+U+ring:top=Uring
+u+ring:top=uring
+U+hungarumlaut:top=Uhungarumlaut
+u+hungarumlaut:top=uhungarumlaut
+U+ogonek:ogonek=Uogonek
+u+ogonek:ogonek=uogonek
+W+circumflex:top=Wcircumflex
+w+circumflex:top=wcircumflex
+Y+circumflex:top=Ycircumflex
+y+circumflex:top=ycircumflex
+Y+dieresis:top=Ydieresis
+Z+acute:top=Zacute
+z+acute:top=zacute
+Z+dotaccent:top=Zdotaccent
+z+dotaccent:top=zdotaccent
+Z+caron:top=Zcaron
+z+caron:top=zcaron
+AE+acute:top=AEacute
+ae+acute:top=aeacute
+Oslash+acute:top=Oslashacute
+oslash+acute:top=oslashacute
+z+caron:top=zcaron
+#
+# Smallcaps
+D.smcp+crossbar:cross=Dcroat.smcp
+D.smcp+crossbar:cross=Eth.smcp
+T.smcp+crossbar:cross=Tbar.smcp
+A.smcp+grave:top=Agrave.smcp
+A.smcp+acute:top=Aacute.smcp
+A.smcp+circumflex:top=Acircumflex.smcp
+A.smcp+tilde:top=Atilde.smcp
+A.smcp+dieresis:top=Adieresis.smcp
+A.smcp+ring:top=Aring.smcp
+A.smcp+ringacute:top=Aringacute.smcp
+C.smcp+cedilla:bottom=Ccedilla.smcp
+E.smcp+grave:top=Egrave.smcp
+E.smcp+acute:top=Eacute.smcp
+E.smcp+circumflex:top=Ecircumflex.smcp
+E.smcp+dieresis:top=Edieresis.smcp
+I.smcp+grave:top=Igrave.smcp
+I.smcp+acute:top=Iacute.smcp
+I.smcp+circumflex:top=Icircumflex.smcp
+I.smcp+dieresis:top=Idieresis.smcp
+N.smcp+tilde:top=Ntilde.smcp
+O.smcp+grave:top=Ograve.smcp
+O.smcp+acute:top=Oacute.smcp
+O.smcp+circumflex:top=Ocircumflex.smcp
+O.smcp+tilde:top=Otilde.smcp
+O.smcp+dieresis:top=Odieresis.smcp
+U.smcp+grave:top=Ugrave.smcp
+U.smcp+acute:top=Uacute.smcp
+U.smcp+circumflex:top=Ucircumflex.smcp
+U.smcp+dieresis:top=Udieresis.smcp
+Y.smcp+acute:top=Yacute.smcp
+A.smcp+macron:top=Amacron.smcp
+A.smcp+breve:top=Abreve.smcp
+A.smcp+ogonek:ogonek=Aogonek.smcp
+C.smcp+acute:top=Cacute.smcp
+C.smcp+circumflex:top=Ccircumflex.smcp
+C.smcp+dotaccent:top=uni010A.smcp
+C.smcp+caron:top=Ccaron.smcp
+D.smcp+caron:top=Dcaron.smcp
+E.smcp+macron:top=Emacron.smcp
+E.smcp+breve:top=Ebreve.smcp
+E.smcp+dotaccent:top=Edotaccent.smcp
+E.smcp+ogonek:ogonek=Eogonek.smcp
+E.smcp+caron:top=Ecaron.smcp
+G.smcp+circumflex:top=Gcircumflex.smcp
+G.smcp+breve:top=Gbreve.smcp
+G.smcp+dotaccent:top=uni0120.smcp
+G.smcp+commaaccent:bottom=Gcommaaccent.smcp
+H.smcp+circumflex:top=Hcircumflex.smcp
+I.smcp+tilde:top=Itilde.smcp
+I.smcp+macron:top=Imacron.smcp
+I.smcp+breve:top=Ibreve.smcp
+I.smcp+ogonek:ogonek=Iogonek.smcp
+I.smcp+dotaccent:top=Idotaccent.smcp
+J.smcp+circumflex:top=Jcircumflex.smcp
+K.smcp+commaaccent:bottom=Kcommaaccent.smcp
+L.smcp+acute:top=Lacute.smcp
+L.smcp+commaaccent:bottom=Lcommaaccent.smcp
+L.smcp+commaaccent:caron=Lcaron.smcp
+L.smcp+dotaccent:dot=Ldot.smcp
+N.smcp+acute:top=Nacute.smcp
+N.smcp+commaaccent:bottom=Ncommaaccent.smcp
+N.smcp+caron:top=Ncaron.smcp
+O.smcp+macron:top=Omacron.smcp
+O.smcp+breve:top=Obreve.smcp
+O.smcp+hungarumlaut:top=Ohungarumlaut.smcp
+R.smcp+acute:top=Racute.smcp
+R.smcp+commaaccent:bottom=Rcommaaccent.smcp
+R.smcp+caron:top=Rcaron.smcp
+S.smcp+acute:top=Sacute.smcp
+S.smcp+circumflex:top=Scircumflex.smcp
+S.smcp+cedilla:bottom=Scedilla.smcp
+S.smcp+caron:top=Scaron.smcp
+T.smcp+commaaccent:bottom=Tcommaaccent.smcp
+T.smcp+caron:top=Tcaron.smcp
+U.smcp+tilde:top=Utilde.smcp
+U.smcp+macron:top=Umacron.smcp
+U.smcp+breve:top=Ubreve.smcp
+U.smcp+ring:top=Uring.smcp
+U.smcp+hungarumlaut:top=Uhungarumlaut.smcp
+U.smcp+ogonek:ogonek=Uogonek.smcp
+W.smcp+circumflex:top=Wcircumflex.smcp
+Y.smcp+circumflex:top=Ycircumflex.smcp
+Y.smcp+dieresis:top=Ydieresis.smcp
+Z.smcp+acute:top=Zacute.smcp
+Z.smcp+dotaccent:top=Zdotaccent.smcp
+Z.smcp+caron:top=Zcaron.smcp
+S.smcp_S.smcp=germandbls.smcp
+#
+A+tonos:tonos=Alphatonos/0,0
+E+tonos:tonos=Epsilontonos/100,0
+H+tonos:tonos=Etatonos/100,0
+I+tonos:tonos=Iotatonos/100,0
+O+tonos:tonos=Omicrontonos/20,0
+Y+tonos:tonos=Upsilontonos/100,0
+Omega+tonos:tonos=Omegatonos/20,0
+iota+dieresistonos:top=iotadieresistonos
+A=Alpha
+B=Beta
+E=Epsilon
+Z=Zeta
+H=Eta
+I=Iota
+K=Kappa
+M=Mu
+N=Nu
+O=Omicron
+P=Rho
+T=Tau
+Y=Upsilon
+X=Chi
+I+dieresis:top=Iotadieresis
+Y+dieresis:top=Upsilondieresis
+alpha+tonos:top=alphatonos
+epsilon+tonos:top=epsilontonos
+eta+tonos:top=etatonos
+iota+tonos:top=iotatonos
+upsilon+dieresistonos:top=upsilondieresistonos
+kgreenlandic=kappa
+o=omicron
+mu=uni03BC
+v=nu
+x=chi
+iota+dieresis:top=iotadieresis
+upsilon+dieresis:top=upsilondieresis
+o+tonos:top=omicrontonos
+upsilon+tonos:top=upsilontonos
+omega+tonos:top=omegatonos
+E+dieresis:top=uni0401
+Gamma+acute:top=uni0403
+S=uni0405
+I=uni0406
+I+dieresis:top=uni0407
+J=uni0408
+K.alt=uni041A
+K+acute:top=uni040C
+uni0423+breve:top=uni040E
+#
+A=uni0410
+B=uni0412
+Gamma=uni0413
+E=uni0415
+uni0418+breve:top=uni0419
+M=uni041C
+H=uni041D
+O=uni041E
+Pi=uni041F
+P=uni0420
+C=uni0421
+T=uni0422
+Phi=uni0424
+X=uni0425
+a=uni0430
+e=uni0435
+uni0438+breve:top=uni0439
+o=uni043E
+p=uni0440
+c=uni0441
+y=uni0443
+x=uni0445
+e+dieresis:top=uni0451
+uni0433+acute:top=uni0453
+s=uni0455
+i=uni0456
+dotlessi+dieresis:top=uni0457
+j=uni0458
+uni043A+acute:top=uni045C
+y+breve:top=uni045E
+W+grave:top=Wgrave
+w+grave:top=wgrave
+W+acute:top=Wacute
+w+acute:top=wacute
+W+dieresis:top=Wdieresis
+w+dieresis:top=wdieresis
+Y+grave:top=Ygrave
+y+grave:top=ygrave
+quotesingle=minute
+quotedbl=second
+exclam_exclam=exclamdbl
+f_l=uniFB02
+uni0237+caron:top=uni01F0
+quoteright=uni02BC
+M+acute:top=uni1E3E
+m+acute:top=uni1E3F
+A+uni02F3:top=uni1E00
+a+uni02F3:top=uni1E01
+O+dasiaoxia:tonos=uni1F4D
+f_fi=uniFB03
+f_f_l=uniFB04
+E+grave:top=uni0400
+uni0418+grave:top=uni040D
+e+grave:top=uni0450
+uni0438+grave:top=uni045D
+Psi=uni0470
+psi=uni0471
+uni0474+uni030F:top=uni0476
+uni0475+uni030F:top=uni0477
+o_y=uni0479
+O_y=uni0478
+uni0417+cyrillictic:bottom=uni0498
+uni0437+cyrillictic:bottom=uni0499
+C+cyrillictic:bottom=uni04AA
+c+cyrillictic:bottom=uni04AB
+Y=uni04AE
+gamma=uni04AF
+I=uni04C0
+uni0416+breve:top=uni04C1
+uni0436+breve:top=uni04C2
+I=uni04CF
+A+breve:top=uni04D0
+a+breve:top=uni04D1
+A+dieresis:top=uni04D2
+a+dieresis:top=uni04D3
+AE=uni04D4
+ae=uni04D5
+E+breve:top=uni04D6
+e+breve:top=uni04D7
+uni04D8+dieresis:top=uni04DA
+schwa=uni04D9
+schwa+dieresis:top=uni04DB
+uni0416+dieresis:top=uni04DC
+uni0436+dieresis:top=uni04DD
+uni0417+dieresis:top=uni04DE
+uni0437+dieresis:top=uni04DF
+uni0418+macron:top=uni04E2
+uni0438+macron:top=uni04E3
+uni0418+dieresis:top=uni04E4
+uni0438+dieresis:top=uni04E5
+O+dieresis:top=uni04E6
+o+dieresis:top=uni04E7
+uni0472=uni04E8
+uni0473=uni04E9
+uni0472+dieresis:top=uni04EA
+uni0473+dieresis:top=uni04EB
+uni042D+dieresis:top=uni04EC
+uni044D+dieresis:top=uni04ED
+uni0423+macron:top=uni04EE
+y+macron:top=uni04EF
+uni0423+dieresis:top=uni04F0
+y+dieresis:top=uni04F1
+uni0423+hungarumlaut:top=uni04F2
+y+hungarumlaut:top=uni04F3
+uni0427+dieresis:top=uni04F4
+uni0447+dieresis:top=uni04F5
+uni042B+dieresis:top=uni04F8
+uni044B+dieresis:top=uni04F9
+X+cyrillichook:right=uni04FC
+x+cyrillichook:right=uni04FD
+d=uni0501
+uni041B+cyrillichook:right=uni0512
+uni043B+cyrillichook:right=uni0513
+A+dotbelow:bottom=uni1EA0
+a+dotbelow:bottom=uni1EA1
+A+hook:top=uni1EA2
+a+hook:top=uni1EA3
+A+circumflexacutecomb:top=uni1EA4
+a+circumflexacutecomb:top=uni1EA5
+A+circumflexgravecomb:top=uni1EA6
+a+circumflexgravecomb:top=uni1EA7
+A+circumflexhookcomb:top=uni1EA8
+a+circumflexhookcomb:top=uni1EA9
+A+circumflextildecomb:top=uni1EAA
+a+circumflextildecomb:top=uni1EAB
+A+circumflex:top+dotbelow:bottom=uni1EAC
+a+circumflex:top+dotbelow:bottom=uni1EAD
+A+breveacutecomb:top=uni1EAE
+a+breveacutecomb:top=uni1EAF
+A+brevegravecomb:top=uni1EB0
+a+brevegravecomb:top=uni1EB1
+A+brevehookcomb:top=uni1EB2
+a+brevehookcomb:top=uni1EB3
+A+brevetildecomb:top=uni1EB4
+a+brevetildecomb:top=uni1EB5
+A+breve:top+dotbelow:bottom=uni1EB6
+a+breve:top+dotbelow:bottom=uni1EB7
+E+dotbelow:bottom=uni1EB8
+e+dotbelow:bottom=uni1EB9
+E+hook:top=uni1EBA
+e+hook:top=uni1EBB
+E+tilde:top=uni1EBC
+e+tilde:top=uni1EBD
+E+circumflexacutecomb:top=uni1EBE
+e+circumflexacutecomb:top=uni1EBF
+E+circumflexgravecomb:top=uni1EC0
+e+circumflexgravecomb:top=uni1EC1
+E+circumflexhookcomb:top=uni1EC2
+e+circumflexhookcomb:top=uni1EC3
+E+circumflextildecomb:top=uni1EC4
+e+circumflextildecomb:top=uni1EC5
+E+circumflex:top+dotbelow:bottom=uni1EC6
+e+circumflex:top+dotbelow:bottom=uni1EC7
+I+hook:top=uni1EC8
+dotlessi+hook:top=uni1EC9
+I+dotbelow:bottom=uni1ECA
+i+dotbelow:bottom=uni1ECB
+O+dotbelow:bottom=uni1ECC
+o+dotbelow:bottom=uni1ECD
+O+hook:top=uni1ECE
+o+hook:top=uni1ECF
+O+circumflexacutecomb:top=uni1ED0
+o+circumflexacutecomb:top=uni1ED1
+O+circumflexgravecomb:top=uni1ED2
+o+circumflexgravecomb:top=uni1ED3
+O+circumflexhookcomb:top=uni1ED4
+o+circumflexhookcomb:top=uni1ED5
+O+circumflextildecomb:top=uni1ED6
+o+circumflextildecomb:top=uni1ED7
+O+circumflex:top+dotbelow:bottom=uni1ED8
+o+circumflex:top+dotbelow:bottom=uni1ED9
+Ohorn+acute:top=uni1EDA
+ohorn+acute:top=uni1EDB
+Ohorn+grave:top=uni1EDC
+ohorn+grave:top=uni1EDD
+Ohorn+hook:top=uni1EDE
+ohorn+hook:top=uni1EDF
+Ohorn+tilde:top=uni1EE0
+ohorn+tilde:top=uni1EE1
+Ohorn+dotbelow:bottom=uni1EE2
+ohorn+dotbelow:bottom=uni1EE3
+U+dotbelow:bottom=uni1EE4
+u+dotbelow:bottom=uni1EE5
+U+hook:top=uni1EE6
+u+hook:top=uni1EE7
+Uhorn+acute:top=uni1EE8
+uhorn+acute:top=uni1EE9
+Uhorn+grave:top=uni1EEA
+uhorn+grave:top=uni1EEB
+Uhorn+hook:top=uni1EEC
+uhorn+hook:top=uni1EED
+Uhorn+tilde:top=uni1EEE
+uhorn+tilde:top=uni1EEF
+Uhorn+dotbelow:bottom=uni1EF0
+uhorn+dotbelow:bottom=uni1EF1
+Y+dotbelow:bottom=uni1EF4
+y+dotbelow:bottom=uni1EF5
+Y+hook:top=uni1EF6
+y+hook:top=uni1EF7
+Y+tilde:top=uni1EF8
+y+tilde:top=uni1EF9
+d+crossbar:cross=dcroat/0,30
+d+crossbar:cross+underscore:bottom=uni20AB/0,30
+#
+K.alt+cyrillictic:right=uni049A
+uni043A+cyrillictic:right=uni049B
+H+cyrillictic:right=uni04A2
+uni043D+cyrillictic:right=uni04A3
+T+cyrillictic:right=uni04AC
+uni0442+cyrillictic:right=uni04AD
+X+cyrillictic:right=uni04B2
+x+cyrillictic:right=uni04B3
+uni0427+cyrillictic:right=uni04B6
+uni0447+cyrillictic:right=uni04B7
+uni0427+cyrillictic:left=uni04CB
+uni0447+cyrillictic:left=uni04CC
+Gamma+cyrillictic:right=uni04F6
+uni0433+cyrillictic:right=uni04F7
+uni0416+cyrillictic:right=uni0496
+uni0436+cyrillictic:right=uni0497
+uni04BC+cyrillictic:bottom=uni04BE
+uni04BD+cyrillictic:bottom=uni04BF
+h=uni04BB
+uni044C+crossbar:cross=uni048D
+uni042C+crossbar:cross=uni048C
+uni044C+crossbar:cross=uni0463
+uni042C+crossbar:cross=uni0462
+Gamma+crossbar:cross=uni0492
+uni0433+crossbar:cross=uni0493
+K.alt+crossbar:cross=uni049E/20,0
+k+crossbar:cross=uni049F/20,0
+uni0418+breve:top+comma:right=uni048A
+uni0438+breve:top+comma:right=uni048B
+H+comma:right=uni04C9
+uni043D+comma:right=uni04CA
+M+comma:right=uni04CD
+uni043C+comma:right=uni04CE
+uni041B+comma:right=uni04C5
+uni043B+comma:right=uni04C6
+Y+crossbar:cross=uni04B0
+gamma+crossbar:cross=uni04B1
+X+crossbar:cross=uni04FE
+x+crossbar:cross=uni04FF
+epsilon=uni0511
+F+crossbar:cross=franc
+emdash=uni2015
+#Lining numbers
+two.lnum=two
+three.lnum=three
+four.lnum=four
+five.lnum=five
+six.lnum=six/20,0
+eight.lnum=eight/20,20
+nine.lnum=nine/0,20
+zero.lnum=zero/20,20 \ No newline at end of file
diff --git a/res/ot_classes.txt b/res/ot_classes.txt
new file mode 100755
index 0000000..745bc96
--- /dev/null
+++ b/res/ot_classes.txt
@@ -0,0 +1,9 @@
+languagesystem DFLT dflt;
+
+# classes
+
+@UC_ROMAN = [ A - Z Scedilla Udieresis Zcaron Zdotaccent Zacute Ydieresis Ycircumflex Wcircumflex Uogonek Uhungarumlaut Uring Ubreve Umacron Utilde Tcaron uni0162 Scaron Scircumflex Sacute Rcaron Rcommaaccent Racute Ohungarumlaut Obreve Omacron Ncaron Ncommaaccent Nacute Ldot Lcaron Lcommaaccent Lacute Kcommaaccent Jcircumflex Iogonek Ibreve Imacron Itilde Hcircumflex Gcommaaccent Gbreve Gcircumflex Ecaron Eogonek Edotaccent Ebreve Emacron Dcaron Ccaron Ccircumflex Cacute Aogonek Abreve Amacron Yacute Ucircumflex Uacute Ugrave Odieresis Otilde Ocircumflex Oacute Ograve Ntilde Idieresis Icircumflex Iacute Igrave Edieresis Ecircumflex Eacute Egrave Ccedilla Aringacute Aring Adieresis Atilde Acircumflex Aacute Agrave Tbar Eth Dcroat ];
+@LC_ROMAN = [ a - z scedilla udieresis zcaron zdotaccent zacute ydieresis ycircumflex wcircumflex uogonek uhungarumlaut uring ubreve umacron utilde tcaron uni0163 scaron scircumflex sacute rcaron rcommaaccent racute ohungarumlaut obreve omacron ncaron ncommaaccent nacute ldot lcaron lcommaaccent lacute kcommaaccent jcircumflex iogonek ibreve imacron itilde hcircumflex gcommaaccent gbreve gcircumflex ecaron eogonek edotaccent ebreve emacron dcaron ccaron ccircumflex cacute aogonek abreve amacron yacute ucircumflex uacute ugrave odieresis otilde ocircumflex oacute ograve ntilde idieresis icircumflex iacute igrave edieresis ecircumflex eacute egrave ccedilla aringacute aring adieresis atilde acircumflex aacute agrave tbar eth dcroat ];
+@SC_ROMAN = [ A.smcp B.smcp C.smcp D.smcp E.smcp F.smcp G.smcp H.smcp I.smcp J.smcp K.smcp L.smcp M.smcp N.smcp O.smcp P.smcp Q.smcp R.smcp S.smcp T.smcp U.smcp V.smcp W.smcp X.smcp Y.smcp Z.smcp Scedilla.smcp Udieresis.smcp Zcaron.smcp Zdotaccent.smcp Zacute.smcp Ydieresis.smcp Ycircumflex.smcp Wcircumflex.smcp Uogonek.smcp Uhungarumlaut.smcp Uring.smcp Ubreve.smcp Umacron.smcp Utilde.smcp Tcaron.smcp uni0162.smcp Scaron.smcp Scircumflex.smcp Sacute.smcp Rcaron.smcp Rcommaaccent.smcp Racute.smcp Ohungarumlaut.smcp Obreve.smcp Omacron.smcp Ncaron.smcp Ncommaaccent.smcp Nacute.smcp Ldot.smcp Lcaron.smcp Lcommaaccent.smcp Lacute.smcp Kcommaaccent.smcp Jcircumflex.smcp Iogonek.smcp Ibreve.smcp Imacron.smcp Itilde.smcp Hcircumflex.smcp Gcommaaccent.smcp Gbreve.smcp Gcircumflex.smcp Ecaron.smcp Eogonek.smcp Edotaccent.smcp Ebreve.smcp Emacron.smcp Dcaron.smcp Ccaron.smcp Ccircumflex.smcp Cacute.smcp Aogonek.smcp Abreve.smcp Amacron.smcp Yacute.smcp Ucircumflex.smcp Uacute.smcp Ugrave.smcp Odieresis.smcp Otilde.smcp Ocircumflex.smcp Oacute.smcp Ograve.smcp Ntilde.smcp Idieresis.smcp Icircumflex.smcp Iacute.smcp Igrave.smcp Edieresis.smcp Ecircumflex.smcp Eacute.smcp Egrave.smcp Ccedilla.smcp Aringacute.smcp Aring.smcp Adieresis.smcp Atilde.smcp Acircumflex.smcp Aacute.smcp Agrave.smcp Tbar.smcp Eth.smcp Dcroat.smcp ];
+@TNUM = [zero one two three four five six seven eight nine];
+@LNUM = [zero.lnum one.lnum two.lnum three.lnum four.lnum five.lnum six.lnum seven.lnum eight.lnum nine.lnum]; \ No newline at end of file
diff --git a/res/ot_features.txt b/res/ot_features.txt
new file mode 100755
index 0000000..ac707e1
--- /dev/null
+++ b/res/ot_features.txt
@@ -0,0 +1,27 @@
+feature smcp {
+ sub @UC_ROMAN by @SC_ROMAN;
+ sub @LC_ROMAN by @SC_ROMAN;
+ sub Idotaccent by Idotaccent.smcp;
+ sub germandbls by germandbls.smcp;
+} smcp;
+
+feature liga {
+ sub f i by fi;
+ sub f f i by ffi;
+} liga;
+
+feature lnum {
+ sub @TNUM by @LNUM;
+}
+
+feature ss01 {
+ sub alpha by alpha.alt;
+}
+
+feature ss02 {
+ sub g by g.alt;
+}
+
+feature ss03 {
+ sub R by R.alt;
+} \ No newline at end of file
diff --git a/res/ot_kerningclasses.txt b/res/ot_kerningclasses.txt
new file mode 100755
index 0000000..0b711d5
--- /dev/null
+++ b/res/ot_kerningclasses.txt
@@ -0,0 +1,75 @@
+@_A_L = [ A Aacute uni1EB6 Abreve uni04D0 uni1EAE uni1EB0 uni1EB2 uni1EB4 uni1EAC Acircumflex uni1EA4 uni1EA6 uni1EA8 uni1EAA Adieresis uni04D2 uni1EA0 Agrave uni1EA2 Amacron Aogonek Aring Aringacute Atilde Alphatonos uni1E00 uni0410 Lambda Alpha Delta uni0466 ];
+@_A_R = [ A Aacute uni1EB6 Abreve uni04D0 uni1EAE uni1EB0 uni1EB2 uni1EB4 uni1EAC Acircumflex uni1EA4 uni1EA6 uni1EA8 uni1EAA Adieresis uni04D2 uni1EA0 Agrave uni1EA2 Amacron Aogonek Aring Aringacute Atilde Alphatonos uni1E00 uni0410 Lambda Alpha Delta uni0466 ];
+@_Asm_L = [ A.smcp Aacute.smcp Abreve.smcp Acircumflex.smcp Adieresis.smcp Agrave.smcp Amacron.smcp Aogonek.smcp Aring.smcp Aringacute.smcp Atilde.smcp ];
+@_Asm_R = [ A.smcp Aacute.smcp Abreve.smcp Acircumflex.smcp Adieresis.smcp Agrave.smcp Amacron.smcp Aogonek.smcp Aring.smcp Aringacute.smcp Atilde.smcp ];
+@_B_L = [ B Beta uni0412 ];
+#@_H_R = [ H B D E F I K L M N P R Beta uni0412 Eacute Ebreve uni04D6 Ecaron uni1EC6 Ecircumflex uni1EBE uni1EC0 uni1EC2 uni1EC4 Edieresis uni0401 Edotaccent uni1EB8 Egrave Thorn uni0400 uni1EBA Emacron Eogonek uni1EBC Epsilontonos Epsilon Lacute Lcommaaccent Lcaron Ldot Kappa uni041A uni040C Kcommaaccent uni049E uni049A Iota uni0406 uni04C0 uni04CF Eta uni041D Iacute Ibreve Icircumflex Idieresis Iotadieresis uni0407 Idotaccent uni1ECA Igrave uni1EC8 Imacron Iogonek Itilde Iotatonos Hcircumflex uni04C9 uni04A2 Etatonos uni1E3E uni04CD Mu uni041C Nacute Ncaron Ncommaaccent Ntilde Nu Rho uni0420 Gamma uni0403 uni0492 uni04F6 uni0413 ];
+#@_H_L = [ H Hcircumflex uni04C9 uni04A2 Etatonos Eta uni041D Iacute Ibreve Icircumflex Idieresis Iotadieresis uni0407 Idotaccent uni1ECA Igrave uni1EC8 Imacron Iogonek Itilde Iotatonos I Iota uni0406 uni04C0 uni04CF uni1E3E uni04CD M Mu uni041C Nacute Ncaron Ncommaaccent Ntilde N Nu uni040F uni0418 uni041B uni0426 uni0428 ];
+@_O_R = [ O O.ss06 C.ss06 G.ss06 Q.ss06 C Cacute Ccaron Ccedilla Ccircumflex uni04AA uni010A uni0421 OE G Gbreve Gcircumflex Gcommaaccent uni0120 Oacute Obreve uni1ED8 Ocircumflex uni1ED0 uni1ED2 uni1ED4 uni1ED6 uni1F4D Odieresis uni04E6 uni1ECC Ograve uni1ECE Ohungarumlaut Omacron Otilde Omicrontonos Omicron Q Theta uni041E uni0478 Ohorn uni1EDA uni1EE2 uni1EDC uni1EDE uni1EE0 Oslash Oslashacute uni0404 uni0460 uni0472 uni04E8 uni04EA uni047A uni047C uni047E uni0480 uni04A8 uni050C ];
+@_O_L = [ O O.ss06 D.ss06 D Dcaron Dcroat Eth Oacute Obreve uni1ED8 Ocircumflex uni1ED0 uni1ED2 uni1ED4 uni1ED6 uni1F4D Odieresis uni04E6 uni1ECC Ograve uni1ECE Ohungarumlaut Omacron Otilde Omicrontonos Omicron Theta uni041E ];
+@_C_L = [ C C.ss06 Cacute Ccaron Ccedilla Ccircumflex uni04AA uni010A uni0421 ];
+@_E_L = [ E Eacute Ebreve uni04D6 Ecaron uni1EC6 Ecircumflex uni1EBE uni1EC0 uni1EC2 uni1EC4 Edieresis uni0401 Edotaccent uni1EB8 Egrave uni0400 uni1EBA Emacron Eogonek uni1EBC Epsilontonos Epsilon uni0415 ];
+@_T_L = [ T Tcaron uni0162 uni021A Tbar uni04AC Tau uni0422 ];
+@_Gamma_L = [ Gamma uni0403 uni0492 uni04F6 uni0413 uni0490 ];
+@_uni0433_L = [ uni0433 uni04FB uni0453 uni04F7 uni0491 ];
+@_T_R = [ T uni0162 uni021A Tcaron uni0402 uni040B uni0422 uni04A0 uni04AC uni04B4 ];
+@_J_R = [ J Jcircumflex uni0408 ];
+@_K_L = [ K uni040C Kcommaaccent uni049E uni049A Kappa uni041A uni049C uni04A0 ];
+@_L_L = [ L Lacute Lcommaaccent Lcaron Ldot ];
+@_P_L = [ P Rho uni0420 ];
+@_S_L = [ S Sacute Scircumflex Scedilla Scaron uni0218 ];
+@_S_R = [ S Sacute Scircumflex Scedilla Scaron uni0218 ];
+@_U_L = [ U J Jcircumflex uni0408 Uacute Ubreve Ucircumflex Udieresis uni1EE4 Ugrave uni1EE6 Uhungarumlaut Umacron Uogonek Uring Utilde ];
+@_U_R = [ U Uacute Ubreve Ucircumflex Udieresis uni1EE4 Ugrave uni1EE6 Uhungarumlaut Umacron Uogonek Uring Utilde ];
+@_V_L = [ V uni0474 uni0476 ];
+@_V_R = [ V uni0474 uni0476 ];
+@_X_L = [ X Chi uni0425 uni04FC uni04B2 uni0416 uni04C1 uni04DC uni0496 ];
+@_X_R = [ X Chi uni0425 uni04FC uni04B2 uni0416 uni04C1 uni04DC uni0496 ];
+@_Y_L = [ Y Yacute Ycircumflex uni04B0 Upsilondieresis Ydieresis uni1EF4 Ygrave uni1EF6 uni1EF8 Upsilontonos Upsilon uni04AE ];
+@_Y_R = [ Y Yacute Ycircumflex uni04B0 Upsilondieresis Ydieresis uni1EF4 Ygrave uni1EF6 uni1EF8 Upsilontonos Upsilon uni04AE ];
+@_W_L = [ W Wacute Wcircumflex Wdieresis Wgrave ];
+@_W_R = [ W Wacute Wcircumflex Wdieresis Wgrave ];
+@_Z_L = [ Z Zacute Zdotaccent Zcaron Zeta ];
+@_Z_R = [ Z Zacute Zdotaccent Zcaron Zeta ];
+@_uni0423_L = [ uni0423 uni040E uni04EE uni04F0 uni04F2 ];
+@_uni0423_R = [ uni0423 uni040E uni04EE uni04F0 uni04F2 ];
+
+@_a_L = [ a aacute abreve acircumflex adieresis agrave amacron aogonek aring aringacute atilde uni0430 uni04D1 uni04D3 uni1E01 uni1EA1 uni1EA3 uni1EA5 uni1EA7 uni1EA9 uni1EAB uni1EAD uni1EAF uni1EB1 uni1EB3 uni1EB5 uni1EB7 ];
+@_a_R = [ a aacute abreve acircumflex adieresis agrave amacron aogonek aring aringacute atilde uni0430 uni04D1 uni04D3 uni1E01 uni1EA1 uni1EA3 uni1EA5 uni1EA7 uni1EA9 uni1EAB uni1EAD uni1EAF uni1EB1 uni1EB3 uni1EB5 uni1EB7 ];
+@_c_L = [ c cacute ccaron ccedilla ccircumflex uni010B uni0441 uni04AB ];
+@_c_R = [ c ccedilla cacute ccircumflex uni010B ccaron uni04AB oe d dcaron uni0501 e egrave eacute ecircumflex edieresis uni04D9 emacron ebreve edotaccent eogonek ecaron sigma1 uni0435 uni0451 uni0450 uni04D7 uni04DB uni1EB9 uni1EBB uni1EBD uni1EBF uni1EC1 uni1EC3 uni1EC5 uni1EC7 g gcircumflex gbreve uni0121 gcommaaccent q alpha alphatonos sigma uni0444 uni0441 uni0454 uni047D uni04A9 uni0502 uni0503 uni04E9 ohorn uni1EDB uni1EDD uni1EDF uni1EE3 uni0479 uni0481 uni050D ];
+@_b_L = [ b p uni0440 rho thorn uni044D uni04ED uni048F uni0444 ];
+@_e_L = [ e egrave eacute ecircumflex edieresis uni04D9 emacron ebreve edotaccent eogonek ecaron uni0435 uni0451 uni0450 uni04D7 uni04DB uni1EB9 uni1EBB uni1EBD uni1EBF uni1EC1 uni1EC3 uni1EC5 uni1EC7 ];
+@_l_R = [ l h k ];
+@_n_R = [ n nacute uni0448 uni0446 uni0440 uni04A5 uni04A3 uni04E3 uni049B uni04E5 uni04FB uni04C8 uni043D uni043F uni043A uni043C ntilde uni045C uni0449 uni045A uni045D etatonos uni0453 uni048B uni0438 uni0439 ncaron uni04CE uni04F9 uni04CA uni0433 napostrophe m uni1E3F p eta ncommaaccent uni044E uni04F7 uni044C ];
+@_n_L = [ n napostrophe nacute h m uni1E3F ntilde eta ncommaaccent hcircumflex ncaron uni04BB etatonos ];
+@_o_L = [ o ograve ocircumflex uni1ED3 uni1ED1 uni1ED7 uni1ED5 obreve uni1ED9 uni04E7 uni1ECF uni1ECD uni043E uni0473 otilde omicrontonos odieresis ohungarumlaut omicron uni1EE1 omacron oacute uni04EB ];
+@_o_R = [ o ograve ocircumflex uni1ED3 uni1ED1 uni1ED7 uni1ED5 obreve uni1ED9 uni04E7 uni1ECF uni1ECD uni043E uni0473 otilde omicrontonos odieresis ohungarumlaut omicron uni1EE1 omacron oacute uni04EB ];
+@_v_L = [ v ycircumflex gamma uni04AF uni0475 uni0477 uni04EF ydieresis uni0443 uni04F3 uni1EF5 uni04F1 uni1EF9 yacute y uni1EF7 nu ygrave uni045E ];
+@_v_R = [ v ycircumflex gamma uni04AF uni0475 uni0477 uni04EF ydieresis uni0443 uni04F3 uni1EF5 uni04F1 uni1EF9 yacute y uni1EF7 nu ygrave uni045E ];
+@_r_L = [ r racute rcommaaccent rcaron ];
+@_s_L = [ s sacute scircumflex scedilla scaron uni0219 uni0455 ];
+@_s_R = [ s sacute scircumflex scedilla scaron uni0219 uni0455 ];
+@_u_R = [ u ugrave uacute ucircumflex udieresis utilde umacron ubreve uring uhungarumlaut uogonek uni1EE5 uni1EE7 uhorn uni1EE9 uni1EEB uni1EED uni1EEF uni1EF1 upsilon upsilondieresistonos upsilondieresis upsilontonos ];
+@_u_L = [ u ugrave uacute ucircumflex udieresis utilde umacron ubreve uring uhungarumlaut uogonek uni1EE5 uni1EE7 ];
+@_x_L = [ x uni0445 uni04FD uni04B3 uni0436 uni04C2 uni04DD uni0497 ];
+@_x_R = [ x uni0445 uni04FD uni04B3 uni0436 uni04C2 uni04DD uni0497 ];
+@_z_L = [ z zacute zdotaccent zcaron ];
+@_z_R = [ z zacute zdotaccent zcaron ];
+
+@_quote_L = [ quotesingle quotedbl second quotedblleft quotedblright quoteleft quoteright quotereversed uni02BC minute ];
+@_quote_R = [ quotesingle quotedbl second quotedblleft quotedblright quoteleft quoteright quotereversed uni02BC minute ];
+@_hyphen_L = [ hyphen emdash endash uni00AD uni2015 ];
+@_hyphen_R = [ hyphen emdash endash uni00AD uni2015 ];
+@_period_L = [ period comma ellipsis uni2025 quotedblbase quotesinglbase ];
+@_period_R = [ period comma ellipsis uni2025 quotedblbase quotesinglbase ];
+
+@_uni042C_L = [ uni042C uni0409 uni040A uni042A uni0462 ];
+@_uni044C_L = [ uni044C uni044A uni0459 uni045A uni0463 ];
+@_uni0427_R = [ uni0427 uni04F4 uni04B6 uni04CB ];
+@_uni0447_R = [ uni0447 uni04F5 uni04B7 uni04CC ];
+@_uni041B_R = [ uni041B uni0409 uni0512 uni04C5 uni0508 ];
+@_uni043B_R = [ uni043B uni0459 uni0513 uni04C6 ];
+
+@uni0442_L = [ uni0442 tau ];
+@uni0442_R = [ uni0442 tau ]; \ No newline at end of file
diff --git a/res/roboto.cfg b/res/roboto.cfg
new file mode 100755
index 0000000..2737eac
--- /dev/null
+++ b/res/roboto.cfg
@@ -0,0 +1,49 @@
+[main]
+
+builddir: out
+foundry: Google
+version: 2.0
+buildnumberfile: res/buildnumber.txt
+
+[res]
+
+diacriticfile: res/diacritics.txt
+ot_classesfile: res/ot_classes.txt
+ot_kerningclassesfile: res/ot_kerningclasses.txt
+ot_featuresfile: res/ot_features.txt
+
+[glyphs]
+
+decompose: integral product florin Tbar tbar Hbar hbar Eng eng
+ notequal mu asterisk asciicircum at cent Thorn thorn ampersand
+ numbersign Eth eth Lslash lslash onesuperior twosuperior threesuperior
+ foursuperior OE AE ae oe Euro yen notequal plus multiply A B C D E F G H
+ K M N P Q R T U V W X Y Z fi fl a b c d e f g h k l m n p q r t u v w x
+ y dcroat notequal florin Oslash
+ oslash dollar Dcroat uni0468 uni0469 uni0490 uni0491 uni0402 uni0404
+ uni0405 uni0409 uni040A uni040B uni040F uni0411 uni0414 uni0416 uni0417
+ uni041B uni0426 uni0427 uni0428 uni0429 uni042A uni042B uni042D uni042E
+ uni0431 uni0432 uni0433 uni0434 uni0436 uni0437 uni0438 uni043A uni043B
+ uni043C uni043D uni043F uni0441 uni0442 uni0444 uni0446 uni0447 uni0448
+ uni0449 uni044A uni044B uni044C uni044D uni044E uni044F uni0452 uni0454
+ uni0459 uni045A uni045B uni045F uni0462 uni0463 uni0464 uni0465 uni0472
+ uni0473 uni048C uni048D uni0492 uni0493 uni0494 uni0495 uni049E uni049F
+ uni04A0 uni04A1 uni04A4 uni04A5 uni04A6 uni04A7 uni04B0 uni04B1 uni04B4
+ uni04B5 uni04BA uni04C3 uni04C4 uni04FE uni04FF Ohorn Uhorn alpha beta
+ delta epsilon eta gamma iota lambda ohorn omega phi psi rho sigma sigma1
+ tau theta uhorn upsilon xi zeta Aogonek aogonek Eogonek eogonek Uogonek
+ uogonek Iogonek iogonek Uogonek.smcp Aogonek.smcp Eogonek.smcp Uogonek.smcp
+ Iogonek.smcp
+
+predecompose: uni04B4 uni04B5 dcroat uni040F uni045F uni0490 uni0491 OE
+ oe Oslash oslash uni04A6 uni04A7 uni0492 uni0493 uni04BC uni04BD gamma Ohorn
+ ohorn Uhorn uhorn uni0472 uni0473 pi uni046C uni046D
+
+lessitalic: dagger daggerdbl plusminus dollar florin quotesingle quotedbl
+ tilde asciitilde braceleft braceright parenleft parenright bracketleft
+ bracketright ampersand acute grave dieresis macron breve bar sum integral product
+ s S v x V X W Z z Z.smcp S.smcp copyright registered ogonek asterisk
+ two seven six nine uni0431
+
+delete: S.it K.it Z.it O.it C.it D.it b.it d.it g.it p.it q.it s.cn O.cn G.cn
+ C.cn U.cn b.cn c.cn d.cn e.cn g.cn o.cn p.cn q.cn \ No newline at end of file
diff --git a/scripts/build-v2.py b/scripts/build-v2.py
new file mode 100755
index 0000000..f437fd0
--- /dev/null
+++ b/scripts/build-v2.py
@@ -0,0 +1,148 @@
+
+import sys
+# BASEDIR="/Users/robertsonc/GoogleDrive/Fonts/Roboto_src"
+sys.path.insert(0,"%s/scripts/lib"%BASEDIR)
+
+from robofab.world import RFont
+from fontTools.misc.transform import Transform
+from fontbuild.Build import FontProject,swapGlyphs,transformGlyphMembers
+from fontbuild.mix import Mix,Master
+from fontbuild.italics import condenseGlyph, transformFLGlyphMembers
+
+# Masters
+
+rg = Master("%s/src/v2/Roboto_Regular.vfb"%BASEDIR)
+bd = Master("%s/src/v2/Roboto_Bold.vfb"%BASEDIR)
+th = Master("%s/src/v2/Roboto_Thin.vfb"%BASEDIR)
+
+# build condensed masters
+
+condensed = Font(th.font)
+
+lessCondensed = "plusminus \
+bracketleft bracketright dieresis \
+macron percent \
+multiply degree at i j zero one two \
+three four five six seven eight nine braceright braceleft".split()
+uncondensed = "tonos breve acute grave quotesingle quotedbl asterisk \
+period currency registered copyright bullet ring degree dieresis comma bar brokenbar dotaccent \
+dotbelow colon semicolon uniFFFC uniFFFD uni0488 uni0489 ringbelow estimated".split()
+moreCondensed = "z Z M W A V".split()
+
+
+def condenseFont(font, scale=.8, stemWidth=185):
+ f = RFont(font)
+
+ xscale = scale
+ CAPS = "A B C.cn D.cn E F G.cn H I J K L M N O.cn P Q.cn R S T U.cn V W X Y Z one two three four five six seven eight nine zero".split()
+ LC = "a.cn b.cn c.cn d.cn e.cn f g.cn h i j k l m n o.cn p.cn q.cn r s t u v w x y z".split()
+ # for g in [f[name] for name in LC]:
+ for g in f:
+ if (len(g) > 0):
+ # print g.name
+ if g.name in lessCondensed:
+ scale = xscale * 1.1
+ if g.name in uncondensed:
+ continue
+ if g.name in moreCondensed:
+ scale = xscale * .90
+ # g2 = condenseGlyph(g, xscale)
+ # g.clear()
+ # g2.drawPoints(g.getPointPen())
+ m = Transform(xscale, 0, 0, 1, 20, 0)
+ g.transform(m)
+ transformFLGlyphMembers(g,m,transformAnchors=False)
+ g.width += 40
+ return f
+
+
+proj = FontProject(rg.font, BASEDIR, "res/roboto.cfg", th.ffont)
+proj.incrementBuildNumber()
+
+# FAMILYNAME = "Roboto 2 DRAFT"
+# FAMILYNAME = "Roboto2"
+FAMILYNAME = "Roboto"
+
+proj.generateFont(th.font,"%s/Thin/Regular/Th"%FAMILYNAME)
+proj.generateFont(th.font,"%s/Thin Italic/Italic/Th"%FAMILYNAME, italic=True, stemWidth=80)
+
+proj.generateFont(Mix([th,rg], 0.45),"%s/Light/Regular/Lt"%FAMILYNAME)
+proj.generateFont(Mix([th,rg], 0.45),"%s/Light Italic/Italic/Lt"%FAMILYNAME, italic=True, stemWidth=120)
+
+proj.generateFont(Mix([th,rg], Point(0.90, 0.92)),"%s/Regular/Regular/Rg"%FAMILYNAME)
+proj.generateFont(Mix([th,rg], Point(0.90, 0.92)),"%s/Italic/Italic/Rg"%FAMILYNAME, italic=True, stemWidth=185)
+
+proj.generateFont(Mix([rg,bd], 0.35),"%s/Medium/Regular/Lt"%FAMILYNAME)
+proj.generateFont(Mix([rg,bd], 0.35),"%s/Medium Italic/Bold Italic/Lt"%FAMILYNAME, italic=True, stemWidth=230)
+
+proj.generateFont(Mix([rg,bd], Point(0.73, 0.73)),"%s/Bold/Bold/Rg"%FAMILYNAME)
+proj.generateFont(Mix([rg,bd], Point(0.73, 0.73)),"%s/Bold Italic/Bold Italic/Rg"%FAMILYNAME, italic=True, stemWidth=290)
+
+proj.generateFont(Mix([rg,bd], Point(1.125, 1.0)),"%s/Black/Bold/Bk"%FAMILYNAME)
+proj.generateFont(Mix([rg,bd], Point(1.125, 1.0)),"%s/Black Italic/Bold Italic/Bk"%FAMILYNAME, italic=True, stemWidth=290)
+
+thcn1 = Master(condenseFont(Font(th.font), .84, 40).naked())
+cn1 = Master( rg.ffont.addDiff(thcn1.ffont, th.ffont))
+bdcn1 = Master( bd.ffont.addDiff(thcn1.ffont, th.ffont))
+
+proj.generateFont(Mix([thcn1,cn1], Point(0.45, 0.47)), "%s Condensed/Light/Regular/Lt"%FAMILYNAME, swapSuffixes=[".cn"])
+proj.generateFont(Mix([thcn1,cn1], Point(0.9, 0.92)), "%s Condensed/Regular/Regular/Rg"%FAMILYNAME, swapSuffixes=[".cn"])
+proj.generateFont(Mix([cn1,bdcn1], Point(0.75, 0.75)), "%s Condensed/Bold/Bold/Rg"%FAMILYNAME, swapSuffixes=[".cn"])
+
+proj.generateFont(Mix([thcn1,cn1], Point(0.40, 0.42)), "%s Condensed/Light Italic/Italic/Lt"%FAMILYNAME, italic=True, swapSuffixes=[".cn"], stemWidth=120)
+proj.generateFont(Mix([thcn1,cn1], Point(0.9, 0.92)), "%s Condensed/Italic/Italic/Rg"%FAMILYNAME, italic=True, swapSuffixes=[".cn"], stemWidth=185)
+proj.generateFont(Mix([cn1,bdcn1], Point(0.75, 0.75)), "%s Condensed/Bold Italic/Bold Italic/Rg"%FAMILYNAME, italic=True, swapSuffixes=[".cn"], stemWidth=240)
+
+for i in range(len(fl)):
+ fl.Close(0)
+
+sys.exit(0)
+
+
+
+## Old stuff
+
+# for g in condensed.glyphs:
+# scaleX = .78
+# marginX = 25
+# if g.name in lessCondensed:
+# transformGlyphMembers(g, Matrix(scaleX * 1.15,0,0,1,marginX,0))
+# elif g.name in moreCondensed:
+# transformGlyphMembers(g, Matrix(scaleX * .95,0,0,1,marginX,0))
+# else:
+# transformGlyphMembers(g, Matrix(scaleX,0,0,1,marginX,0))
+# g.width += marginX * 2
+# cn = Master(condensed)
+#
+#
+# bdcn = Master( bd.ffont.addDiff(cn.ffont, th.ffont))
+# rgcn = Master( rg.ffont.addDiff(cn.ffont, th.ffont))
+# cn1 = Master(Mix([th, rgcn], Point(.7,.7)).generateFFont())
+# bdcn1 = Master(Mix([bd, bdcn], Point(.7,.7)).generateFFont())
+
+# cn1 = Master(condenseFont(Font(rg.font), .82, 180).naked())
+# bdcn1 = Master(condenseFont(Font(bd.font), .82, 320).naked())
+
+# proj.generateFont(Mix([cn1, bdcn1], Point(0.22, 0.2)), "%s Condensed/Regular/Regular/Rg"%FAMILYNAME, swapSuffixes=[".cnn"])
+# proj.generateFont(Mix([cn1, bdcn1], Point(0.22, 0.2)), "%s Condensed/Italic/Italic/Rg"%FAMILYNAME, italic=True, swapSuffixes=[".cn",".it"])
+#
+# proj.generateFont(Mix([cn1, bdcn1], Point(.80, .75)), "%s Condensed/Bold/Bold/Rg"%FAMILYNAME, swapSuffixes=[".cnn"])
+# proj.generateFont(Mix([cn1, bdcn1], Point(.80, .75)), "%s Condensed/Bold Italic/Bold Italic/Rg"%FAMILYNAME, italic=True, swapSuffixes=[".cn",".it"])
+#
+# proj.generateFont(Mix([cn, cn1], Point(.74, .72)), "%s Condensed/Light/Regular/Lt"%FAMILYNAME, swapSuffixes=[".cnn"])
+# proj.generateFont(Mix([cn, cn1], Point(.74, .72)), "%s Condensed/Light Italic/Italic/Lt"%FAMILYNAME, italic=True, swapSuffixes=[".cn",".it"])
+
+# wide = Mix([
+# Master(Mix([cn1, bdcn1], Point(0.22, 0.2)).generateFFont()),
+# Master(Mix([th,rg], Point(0.90, 0.92)).generateFFont())],
+# 1.15)
+# widebd = Mix([
+# Master(Mix([cn1, bdcn1], Point(.80, .75)).generateFFont()),
+# Master(Mix([rg,bd], Point(0.75, 0.7)).generateFFont())],
+# 1.15)
+# proj.generateFont(wide, "%s Wide/Regular/Regular/Rg" %FAMILYNAME , swapSuffixes=[".ss06"])
+# proj.generateFont(wide,"%s Wide/Italic/Italic/Rg" %FAMILYNAME, italic=True, swapSuffixes=[".it", ".ss06"])
+# proj.generateFont(widebd,"%s Wide/Bold/Bold/Rg"%FAMILYNAME, swapSuffixes=[".ss06"])
+# proj.generateFont(widebd,"%s Wide/Bold Italic/Bold Italic/Rg"%FAMILYNAME, italic=True, swapSuffixes=[".it", ".ss06"])
+
+
diff --git a/scripts/lib/fontbuild/Build.py b/scripts/lib/fontbuild/Build.py
new file mode 100755
index 0000000..d10cc9d
--- /dev/null
+++ b/scripts/lib/fontbuild/Build.py
@@ -0,0 +1,227 @@
+from FL import *
+from fontbuild.mix import Mix,Master,narrowFLGlyph
+from fontbuild.instanceNames import setNames
+from fontbuild.italics import italicizeGlyph
+from fontbuild.convertCurves import glyphCurvesToQuadratic
+from fontbuild.mitreGlyph import mitreGlyph
+from fontbuild.generateGlyph import generateGlyph
+from fontTools.misc.transform import Transform
+from fontbuild.kerning import generateFLKernClassesFromOTString
+import ConfigParser
+import os
+
+
+class FontProject:
+
+ def __init__(self, basefont, basedir, configfile, thinfont = None):
+ self.basefont = basefont
+ self.thinfont = thinfont
+ self.basedir = basedir
+ self.config = ConfigParser.RawConfigParser()
+ self.configfile = self.basedir+"/"+configfile
+ self.config.read(self.configfile)
+
+ diacriticList = open(self.basedir + "/" + self.config.get("res","diacriticfile")).readlines()
+ self.diacriticList = [line.strip() for line in diacriticList if not line.startswith("#")]
+ self.ot_classes = open(self.basedir + "/" + self.config.get("res","ot_classesfile")).read()
+ self.ot_kerningclasses = open(self.basedir + "/" + self.config.get("res","ot_kerningclassesfile")).read()
+ self.ot_features = open(self.basedir + "/" + self.config.get("res","ot_featuresfile")).read()
+
+ self.builddir = "out"
+ self.decompose = self.config.get("glyphs","decompose").split()
+ self.predecompose = self.config.get("glyphs","predecompose").split()
+ self.lessItalic = self.config.get("glyphs","lessitalic").split()
+ self.deleteList = self.config.get("glyphs","delete").split()
+ self.buildnumber = self.loadBuildNumber()
+
+
+ def loadBuildNumber(self):
+ versionFile = open(self.basedir + "/" + self.config.get("main","buildnumberfile"), "r+")
+ buildnumber = int(versionFile.read().strip())
+ buildnumber = "%05d" %(int(buildnumber) + 1)
+ print "BuildNumber: %s" %(buildnumber)
+ versionFile.close()
+ return buildnumber
+
+ def incrementBuildNumber(self):
+ if len(self.buildnumber) > 0:
+ versionFile = open(self.basedir + "/" + self.config.get("main","buildnumberfile"), "r+")
+ versionFile.seek(0)
+ versionFile.write(self.buildnumber)
+ versionFile.truncate()
+ versionFile.close()
+ else:
+ raise Exception("Empty build number")
+
+
+ def generateFont(self, mix, names, italic=False, swapSuffixes=None, stemWidth=185, kern=True):
+
+ n = names.split("/")
+ log("---------------------\n%s %s\n----------------------" %(n[0],n[1]))
+ log(">> Mixing masters")
+ if isinstance( mix, Mix):
+ f = mix.generateFont(self.basefont)
+ else:
+ f = Font(mix)
+ fl.Add(f)
+ index = fl.ifont
+ fl.CallCommand(33239) # Sort glyphs by unicode
+ if italic == True:
+ log(">> Italicizing")
+ fl.UpdateFont(fl.ifont)
+ tweakAmmount = .085
+ narrowAmmount = .93
+ if names.find("Thin") != -1:
+ tweakAmmount = .05
+ if names.find("Condensed") != -1:
+ narrowAmmount = .96
+ i = 0
+ for g in f.glyphs:
+ i += 1
+ if i % 10 == 0: print g.name
+
+ # if i < 24:
+ # continue
+ # if i > 86:
+ # for i,g in enumerate(fl.font.glyphs):
+ # fl.UpdateGlyph(i)
+ # # break
+ # assert False
+
+ # print g.name
+ # if self.thinfont != None:
+ # narrowFLGlyph(g,self.thinfont.getGlyph(g.name),factor=narrowAmmount)
+
+ if g.name != "eight" or g.name != "Q":
+ g.RemoveOverlap()
+
+ # not sure why FontLab sometimes refuses, seems to work if called twice
+
+ if (g.name in self.lessItalic):
+ italicizeGlyph(g, 9, stemWidth=stemWidth)
+ else:
+ italicizeGlyph(g, 10, stemWidth=stemWidth)
+ g.RemoveOverlap()
+ g.width += 10
+ fl.UpdateGlyph(i-1)
+
+ if swapSuffixes != None:
+ for swap in swapSuffixes:
+ swapList = [g.name for g in f.glyphs if g.name.endswith(swap)]
+ for gname in swapList:
+ print gname
+ swapGlyphs(f, gname.replace(swap,""), gname)
+ for gname in self.predecompose:
+ g = f[f.FindGlyph(gname)]
+ if g != None:
+ g.Decompose()
+
+ log(">> Generating glyphs")
+ generateGlyphs(f, self.diacriticList)
+ log(">> Copying features")
+ f.ot_classes = self.ot_classes
+ copyFeatures(self.basefont,f)
+ fl.UpdateFont(index)
+ log(">> Decomposing")
+ for gname in self.decompose:
+ g = f[f.FindGlyph(gname)]
+ if g != None:
+ g.Decompose()
+ g.Decompose()
+
+ setNames(f, n, foundry=self.config.get('main','foundry'),
+ version=self.config.get('main','version'),
+ build=self.buildnumber)
+ cleanCurves(f)
+ deleteGlyphs(f,self.deleteList)
+ if kern:
+ generateFLKernClassesFromOTString(f,self.ot_kerningclasses)
+ log(">> Generating font files")
+ directoryName = n[0].replace(" ","")
+ directoryPath = "%s/%s/%sTTF"%(self.basedir,self.builddir,directoryName)
+ if not os.path.exists(directoryPath):
+ os.makedirs(directoryPath)
+ ttfName = "%s/%s.ttf"%(directoryPath,f.font_name)
+ fl.GenerateFont(fl.ifont,ftTRUETYPE,ttfName)
+ f.modified = 0
+ fl.Close(index)
+
+def transformGlyphMembers(g, m):
+ g.width = int(g.width * m.a)
+ g.Transform(m)
+ for a in g.anchors:
+ p = Point(a.p)
+ p.Transform(m)
+ a.p = p
+ for c in g.components:
+ # Assumes that components have also been individually transformed
+ p = Point(0,0)
+ d = Point(c.deltas[0])
+ d.Transform(m)
+ p.Transform(m)
+ d1 = d - p
+ c.deltas[0].x = d1.x
+ c.deltas[0].y = d1.y
+ s = Point(c.scale)
+ s.Transform(m)
+ #c.scale = s
+
+def swapGlyphs(f,gName1,gName2):
+ try:
+ g1 = f.glyphs[f.FindGlyph(gName1)]
+ g2 = f.glyphs[f.FindGlyph(gName2)]
+ except IndexError:
+ log("swapGlyphs failed for %s %s"%(gName1, gName2))
+ return
+ g3 = Glyph(g1)
+
+ g1.Clear()
+ g1.Insert(g2)
+ g1.SetMetrics(g2.GetMetrics())
+
+ g2.Clear()
+ g2.Insert(g3)
+ g2.SetMetrics(g3.GetMetrics())
+
+def log(msg):
+ print msg
+
+# def addOTFeatures(f):
+# f.ot_classes = ot_classes
+
+def copyFeatures(f1, f2):
+ for ft in f1.features:
+ t = Feature(ft.tag, ft.value)
+ f2.features.append(t)
+ #f2.ot_classes = f1.ot_classes
+ f2.classes = []
+ f2.classes = f1.classes
+
+def generateGlyphs(f, glyphNames):
+ log(">> Generating diacritics")
+ glyphnames = [gname for gname in glyphNames if not gname.startswith("#") and gname != ""]
+
+ for glyphName in glyphNames:
+ generateGlyph(f, glyphName)
+
+def cleanCurves(f):
+ log(">> Removing overlaps")
+ for g in f.glyphs:
+ g.UnselectAll()
+ g.RemoveOverlap()
+
+ log(">> Mitring sharp corners")
+ # for g in f.glyphs:
+ # mitreGlyph(g, 3., .7)
+
+ log(">> Converting curves to quadratic")
+ # for g in f.glyphs:
+ # glyphCurvesToQuadratic(g)
+
+def deleteGlyphs(f,deleteList):
+ fl.Unselect()
+ for name in deleteList:
+ glyphIndex = f.FindGlyph(name)
+ if glyphIndex != -1:
+ del f.glyphs[glyphIndex]
+ fl.UpdateFont()
diff --git a/scripts/lib/fontbuild/__init__.py b/scripts/lib/fontbuild/__init__.py
new file mode 100755
index 0000000..4ed7203
--- /dev/null
+++ b/scripts/lib/fontbuild/__init__.py
@@ -0,0 +1,6 @@
+"""
+fontbuild
+
+A collection of font production tools written for FontLab
+"""
+version = "0.1" \ No newline at end of file
diff --git a/scripts/lib/fontbuild/alignpoints.py b/scripts/lib/fontbuild/alignpoints.py
new file mode 100644
index 0000000..ed502ed
--- /dev/null
+++ b/scripts/lib/fontbuild/alignpoints.py
@@ -0,0 +1,148 @@
+
+
+import numpy as np
+from numpy.linalg import lstsq
+import math
+
+def alignCorners(glyph, va, subsegments):
+ out = va.copy()
+ # for i,c in enumerate(subsegments):
+ # segmentCount = len(glyph.contours[i].segments) - 1
+ # n = len(c)
+ # for j,s in enumerate(c):
+ # if j < segmentCount:
+ # seg = glyph.contours[i].segments[j]
+ # if seg.type == "line":
+ # subIndex = subsegmentIndex(i,j,subsegments)
+ # out[subIndex] = alignPoints(va[subIndex])
+
+ for i,c in enumerate(subsegments):
+ segmentCount = len(glyph.contours[i].segments)
+ n = len(c)
+ for j,s in enumerate(c):
+ if j < segmentCount - 1:
+ segType = glyph.contours[i].segments[j].type
+ segnextType = glyph.contours[i].segments[j+1].type
+ next = j+1
+ elif j == segmentCount -1 and s[1] > 3:
+ segType = glyph.contours[i].segments[j].type
+ segNextType = "line"
+ next = j+1
+ elif j == segmentCount:
+ segType = "line"
+ segnextType = glyph.contours[i].segments[1].type
+ if glyph.name == "J":
+ print s[1]
+ print segnextType
+ next = 1
+ else:
+ break
+ if segType == "line" and segnextType == "line":
+ subIndex = subsegmentIndex(i,j,subsegments)
+ pts = va[subIndex]
+ ptsnext = va[subsegmentIndex(i,next,subsegments)]
+ # out[subIndex[-1]] = (out[subIndex[-1]] - 500) * 3 + 500 #findCorner(pts, ptsnext)
+ # print subIndex[-1], subIndex, subsegmentIndex(i,next,subsegments)
+ try:
+ out[subIndex[-1]] = findCorner(pts, ptsnext)
+ except:
+ pass
+ # print glyph.name, "Can't find corner: parallel lines"
+ return out
+
+
+def subsegmentIndex(contourIndex, segmentIndex, subsegments):
+ # This whole thing is so dumb. Need a better data model for subsegments
+
+ contourOffset = 0
+ for i,c in enumerate(subsegments):
+ if i == contourIndex:
+ break
+ contourOffset += c[-1][0]
+ n = subsegments[contourIndex][-1][0]
+ # print contourIndex, contourOffset, n
+ startIndex = subsegments[contourIndex][segmentIndex-1][0]
+ segmentCount = subsegments[contourIndex][segmentIndex][1]
+ endIndex = (startIndex + segmentCount + 1) % (n)
+
+ indices = np.array([(startIndex + i) % (n) + contourOffset for i in range(segmentCount + 1)])
+ return indices
+
+def alignPoints(pts, start=None, end=None):
+ if start == None or end == None:
+ start, end = fitLine(pts)
+ out = pts.copy()
+ for i,p in enumerate(pts):
+ out[i] = nearestPoint(start, end, p)
+ return out
+
+def findCorner(pp, nn):
+ if len(pp) < 4 or len(nn) < 4:
+ assert 0, "line too short to fit"
+ pStart,pEnd = fitLine(pp)
+ nStart,nEnd = fitLine(nn)
+ prev = pEnd - pStart
+ next = nEnd - nStart
+ # print int(np.arctan2(prev[1],prev[0]) / math.pi * 180),
+ # print int(np.arctan2(next[1],next[0]) / math.pi * 180)
+ # if lines are parallel, return simple average of end and start points
+ if np.dot(prev / np.linalg.norm(prev),
+ next / np.linalg.norm(next)) > .999999:
+ # print "parallel lines", np.arctan2(prev[1],prev[0]), np.arctan2(next[1],next[0])
+ # print prev, next
+ assert 0, "parallel lines"
+ return lineIntersect(pStart, pEnd, nStart, nEnd)
+
+def lineIntersect((x1,y1),(x2,y2),(x3,y3),(x4,y4)):
+ x12 = x1 - x2
+ x34 = x3 - x4
+ y12 = y1 - y2
+ y34 = y3 - y4
+
+ det = x12 * y34 - y12 * x34
+ if det == 0:
+ print "parallel!"
+
+ a = x1 * y2 - y1 * x2
+ b = x3 * y4 - y3 * x4
+
+ x = (a * x34 - b * x12) / det
+ y = (a * y34 - b * y12) / det
+
+ return (x,y)
+
+def fitLineLSQ(pts):
+ "returns a line fit with least squares. Fails for vertical lines"
+ n = len(pts)
+ a = np.ones((n,2))
+ for i in range(n):
+ a[i,0] = pts[i,0]
+ line = lstsq(a,pts[:,1])[0]
+ return line
+
+def fitLine(pts):
+ """returns a start vector and direction vector
+ Assumes points segments that already form a somewhat smooth line
+ """
+ n = len(pts)
+ if n < 1:
+ return (0,0),(0,0)
+ a = np.zeros((n-1,2))
+ for i in range(n-1):
+ v = pts[i] - pts[i+1]
+ a[i] = v / np.linalg.norm(v)
+ direction = np.mean(a[1:-1], axis=0)
+ start = np.mean(pts[1:-1], axis=0)
+ return start, start+direction
+
+def nearestPoint(a,b,c):
+ "nearest point to point c on line a_b"
+ magnitude = np.linalg.norm(b-a)
+ if magnitude == 0:
+ raise Exception, "Line segment cannot be 0 length"
+ return (b-a) * np.dot((c-a) / magnitude, (b-a) / magnitude) + a
+
+# pts = np.array([[1,1],[2,2],[3,3],[4,4]])
+# pts2 = np.array([[1,0],[2,0],[3,0],[4,0]])
+# print alignPoints(pts2, start = pts[0], end = pts[0]+pts[0])
+# # print findCorner(pts,pts2) \ No newline at end of file
diff --git a/scripts/lib/fontbuild/anchors.py b/scripts/lib/fontbuild/anchors.py
new file mode 100755
index 0000000..7cc3869
--- /dev/null
+++ b/scripts/lib/fontbuild/anchors.py
@@ -0,0 +1,44 @@
+#import numpy as np
+from FL import *
+
+
+def getGlyph(gname,font):
+ index = font.FindGlyph(gname)
+ if index != -1:
+ return font.glyphs[index]
+ else:
+ return None
+
+def getComponentByName(f,g,componentName):
+ componentIndex = f.FindGlyph(componentName)
+ for c in g.components:
+ if c.index == componentIndex:
+ return c
+
+def getAnchorByName(g,anchorName):
+ for a in g.anchors:
+ if a.name == anchorName:
+ return a
+
+
+def alignComponentToAnchor(f,glyphName,baseName,accentName,anchorName):
+ g = getGlyph(glyphName,f)
+ base = getGlyph(baseName,f)
+ accent = getGlyph(accentName,f)
+ if g == None or base == None or accent == None:
+ return
+ a1 = getAnchorByName(base,anchorName)
+ a2 = getAnchorByName(accent,"_" + anchorName)
+ if a1 == None or a2 == None:
+ return
+ offset = a1.p - a2.p
+ c = getComponentByName(f,g,accentName)
+ c.deltas[0].x = offset.x
+ c.deltas[0].y = offset.y
+
+def alignComponentsToAnchors(f,glyphName,baseName,accentNames):
+ for a in accentNames:
+ if len(a) == 1:
+ continue
+ alignComponentToAnchor(f,glyphName,baseName,a[0],a[1])
+
diff --git a/scripts/lib/fontbuild/convertCurves.py b/scripts/lib/fontbuild/convertCurves.py
new file mode 100755
index 0000000..e900a73
--- /dev/null
+++ b/scripts/lib/fontbuild/convertCurves.py
@@ -0,0 +1,90 @@
+#! /usr/bin/env python
+
+"""
+Converts a cubic bezier curve to a quadratic spline with
+exactly two off curve points.
+
+"""
+
+import numpy
+from numpy import array,cross,dot
+from fontTools.misc import bezierTools
+from FL import *
+
+def calcIntersect(a,b,c,d):
+ numpy.seterr(all='raise')
+ e = b-a
+ f = d-c
+ p = array([-e[1], e[0]])
+ try:
+ h = dot((a-c),p) / dot(f,p)
+ except:
+ print a,b,c,d
+ raise
+ return c + dot(f,h)
+
+def simpleConvertToQuadratic(p0,p1,p2,p3):
+ p = [array(i.x,i.y) for i in [p0,p1,p2,p3]]
+ off = calcIntersect(p[0],p[1],p[2],p[3])
+
+# OFFCURVE_VECTOR_CORRECTION = -.015
+OFFCURVE_VECTOR_CORRECTION = 0
+
+def convertToQuadratic(p0,p1,p2,p3):
+ # TODO: test for accuracy and subdivide further if needed
+ p = [(i.x,i.y) for i in [p0,p1,p2,p3]]
+ # if p[0][0] == p[1][0] and p[0][0] == p[2][0] and p[0][0] == p[2][0] and p[0][0] == p[3][0]:
+ # return (p[0],p[1],p[2],p[3])
+ # if p[0][1] == p[1][1] and p[0][1] == p[2][1] and p[0][1] == p[2][1] and p[0][1] == p[3][1]:
+ # return (p[0],p[1],p[2],p[3])
+ seg1,seg2 = bezierTools.splitCubicAtT(p[0], p[1], p[2], p[3], .5)
+ pts1 = [array([i[0], i[1]]) for i in seg1]
+ pts2 = [array([i[0], i[1]]) for i in seg2]
+ on1 = seg1[0]
+ on2 = seg2[3]
+ try:
+ off1 = calcIntersect(pts1[0], pts1[1], pts1[2], pts1[3])
+ off2 = calcIntersect(pts2[0], pts2[1], pts2[2], pts2[3])
+ except:
+ return (p[0],p[1],p[2],p[3])
+ off1 = (on1 - off1) * OFFCURVE_VECTOR_CORRECTION + off1
+ off2 = (on2 - off2) * OFFCURVE_VECTOR_CORRECTION + off2
+ return (on1,off1,off2,on2)
+
+def cubicNodeToQuadratic(g,nid):
+
+ node = g.nodes[nid]
+ if (node.type != nCURVE):
+ print "Node type not curve"
+ return
+
+ #pNode,junk = getPrevAnchor(g,nid)
+ pNode = g.nodes[nid-1] #assumes that a nCURVE type will always be proceeded by another point on the same contour
+ points = convertToQuadratic(pNode[0],node[1],node[2],node[0])
+ points = [Point(p[0],p[1]) for p in points]
+ curve = [
+ Node(nOFF, points[1]),
+ Node(nOFF, points[2]),
+ Node(nLINE,points[3]) ]
+ return curve
+
+def glyphCurvesToQuadratic(g):
+
+ nodes = []
+ for i in range(len(g.nodes)):
+ n = g.nodes[i]
+ if n.type == nCURVE:
+ try:
+ newNodes = cubicNodeToQuadratic(g, i)
+ nodes = nodes + newNodes
+ except Exception:
+ print g.name, i
+ raise
+ else:
+ nodes.append(Node(g.nodes[i]))
+ g.Clear()
+ g.Insert(nodes)
+
+
+
+
diff --git a/scripts/lib/fontbuild/curveFitPen.py b/scripts/lib/fontbuild/curveFitPen.py
new file mode 100644
index 0000000..d050479
--- /dev/null
+++ b/scripts/lib/fontbuild/curveFitPen.py
@@ -0,0 +1,402 @@
+#! /opt/local/bin/pythonw2.7
+
+__all__ = ["SubsegmentPen","SubsegmentsToCurvesPen", "segmentGlyph", "fitGlyph"]
+
+from fontTools.pens.basePen import BasePen
+from fontTools.misc import bezierTools
+from robofab.pens.pointPen import AbstractPointPen
+from robofab.pens.adapterPens import PointToSegmentPen, GuessSmoothPointPen
+import numpy as np
+from numpy.linalg import norm
+from numpy import array as v
+from random import random
+
+from robofab.pens.pointPen import BasePointToSegmentPen
+class SubsegmentsToCurvesPointPen(BasePointToSegmentPen):
+ def __init__(self, glyph, subsegmentGlyph, subsegments):
+ BasePointToSegmentPen.__init__(self)
+ self.glyph = glyph
+ self.subPen = SubsegmentsToCurvesPen(None, glyph.getPen(), subsegmentGlyph, subsegments)
+
+ def setMatchTangents(self, b):
+ self.subPen.matchTangents = b
+
+ def _flushContour(self, segments):
+ #
+ # adapted from robofab.pens.adapterPens.rfUFOPointPen
+ #
+ assert len(segments) >= 1
+ # if we only have one point and it has a name, we must have an anchor
+ first = segments[0]
+ segmentType, points = first
+ pt, smooth, name, kwargs = points[0]
+ if len(segments) == 1 and name != None:
+ self.glyph.appendAnchor(name, pt)
+ return
+ else:
+ segmentType, points = segments[-1]
+ movePt, smooth, name, kwargs = points[-1]
+ if smooth:
+ # last point is smooth, set pen to start smooth
+ self.subPen.setLastSmooth(True)
+ if segmentType == 'line':
+ del segments[-1]
+
+ self.subPen.moveTo(movePt)
+
+ # do the rest of the segments
+ for segmentType, points in segments:
+ isSmooth = True in [smooth for pt, smooth, name, kwargs in points]
+ pp = [pt for pt, smooth, name, kwargs in points]
+ if segmentType == "line":
+ assert len(pp) == 1
+ if isSmooth:
+ self.subPen.smoothLineTo(pp[0])
+ else:
+ self.subPen.lineTo(pp[0])
+ elif segmentType == "curve":
+ assert len(pp) == 3
+ if isSmooth:
+ self.subPen.smoothCurveTo(*pp)
+ else:
+ self.subPen.curveTo(*pp)
+ elif segmentType == "qcurve":
+ assert 0, "qcurve not supported"
+ else:
+ assert 0, "illegal segmentType: %s" % segmentType
+ self.subPen.closePath()
+
+ def addComponent(self, glyphName, transform):
+ self.subPen.addComponent(glyphName, transform)
+
+class SubsegmentsToCurvesPen(BasePen):
+ def __init__(self, glyphSet, otherPen, subsegmentGlyph, subsegments):
+ BasePen.__init__(self, None)
+ self.otherPen = otherPen
+ self.ssglyph = subsegmentGlyph
+ self.subsegments = subsegments
+ self.contourIndex = -1
+ self.segmentIndex = -1
+ self.lastPoint = (0,0)
+ self.lastSmooth = False
+ self.nextSmooth = False
+
+ def setLastSmooth(self, b):
+ self.lastSmooth = b
+
+ def _moveTo(self, (x, y)):
+ self.contourIndex += 1
+ self.segmentIndex = 0
+ self.startPoint = (x,y)
+ p = self.ssglyph.contours[self.contourIndex][0].points[0]
+ self.otherPen.moveTo((p.x, p.y))
+ self.lastPoint = (x,y)
+
+ def _lineTo(self, (x, y)):
+ self.segmentIndex += 1
+ index = self.subsegments[self.contourIndex][self.segmentIndex][0]
+ p = self.ssglyph.contours[self.contourIndex][index].points[0]
+ self.otherPen.lineTo((p.x, p.y))
+ self.lastPoint = (x,y)
+ self.lastSmooth = False
+
+ def smoothLineTo(self, (x, y)):
+ self.lineTo((x,y))
+ self.lastSmooth = True
+
+ def smoothCurveTo(self, (x1, y1), (x2, y2), (x3, y3)):
+ self.nextSmooth = True
+ self.curveTo((x1, y1), (x2, y2), (x3, y3))
+ self.nextSmooth = False
+ self.lastSmooth = True
+
+ def _curveToOne(self, (x1, y1), (x2, y2), (x3, y3)):
+ self.segmentIndex += 1
+ c = self.ssglyph.contours[self.contourIndex]
+ n = len(c)
+ startIndex = (self.subsegments[self.contourIndex][self.segmentIndex-1][0])
+ segmentCount = (self.subsegments[self.contourIndex][self.segmentIndex][1])
+ endIndex = (startIndex + segmentCount + 1) % (n)
+
+ indices = [(startIndex + i) % (n) for i in range(segmentCount + 1)]
+ points = np.array([(c[i].points[0].x, c[i].points[0].y) for i in indices])
+ prevPoint = (c[(startIndex - 1)].points[0].x, c[(startIndex - 1)].points[0].y)
+ nextPoint = (c[(endIndex) % n].points[0].x, c[(endIndex) % n].points[0].y)
+ prevTangent = prevPoint - points[0]
+ nextTangent = nextPoint - points[-1]
+
+ tangent1 = points[1] - points[0]
+ tangent3 = points[-2] - points[-1]
+ prevTangent /= np.linalg.norm(prevTangent)
+ nextTangent /= np.linalg.norm(nextTangent)
+ tangent1 /= np.linalg.norm(tangent1)
+ tangent3 /= np.linalg.norm(tangent3)
+
+ tangent1, junk = self.smoothTangents(tangent1, prevTangent, self.lastSmooth)
+ tangent3, junk = self.smoothTangents(tangent3, nextTangent, self.nextSmooth)
+ if self.matchTangents == True:
+ cp = fitBezier(points, tangent1, tangent3)
+ cp[1] = norm(cp[1] - cp[0]) * tangent1 / norm(tangent1) + cp[0]
+ cp[2] = norm(cp[2] - cp[3]) * tangent3 / norm(tangent3) + cp[3]
+ else:
+ cp = fitBezier(points)
+ # if self.ssglyph.name == 'r':
+ # print "-----------"
+ # print self.lastSmooth, self.nextSmooth
+ # print "%i %i : %i %i \n %i %i : %i %i \n %i %i : %i %i"%(x1,y1, cp[1,0], cp[1,1], x2,y2, cp[2,0], cp[2,1], x3,y3, cp[3,0], cp[3,1])
+ self.otherPen.curveTo((cp[1,0], cp[1,1]), (cp[2,0], cp[2,1]), (cp[3,0], cp[3,1]))
+ self.lastPoint = (x3, y3)
+ self.lastSmooth = False
+
+ def smoothTangents(self,t1,t2,forceSmooth = False):
+ if forceSmooth or (abs(t1.dot(t2)) > .95 and norm(t1-t2) > 1):
+ # print t1,t2,
+ t1 = (t1 - t2) / 2
+ t2 = -t1
+ # print t1,t2
+ return t1 / norm(t1), t2 / norm(t2)
+
+
+ def _closePath(self):
+ self.otherPen.closePath()
+
+ def _endPath(self):
+ self.otherPen.endPath()
+
+
+ def addComponent(self, glyphName, transformation):
+ self.otherPen.addComponent(glyphName, transformation)
+
+
+class SubsegmentPointPen(BasePointToSegmentPen):
+ def __init__(self, glyph, resolution):
+ BasePointToSegmentPen.__init__(self)
+ self.glyph = glyph
+ self.resolution = resolution
+ self.subPen = SubsegmentPen(None, glyph.getPen())
+
+ def getSubsegments(self):
+ return self.subPen.subsegments[:]
+
+ def _flushContour(self, segments):
+ #
+ # adapted from robofab.pens.adapterPens.rfUFOPointPen
+ #
+ assert len(segments) >= 1
+ # if we only have one point and it has a name, we must have an anchor
+ first = segments[0]
+ segmentType, points = first
+ pt, smooth, name, kwargs = points[0]
+ if len(segments) == 1 and name != None:
+ self.glyph.appendAnchor(name, pt)
+ return
+ else:
+ segmentType, points = segments[-1]
+ movePt, smooth, name, kwargs = points[-1]
+ if segmentType == 'line':
+ del segments[-1]
+
+ self.subPen.moveTo(movePt)
+
+ # do the rest of the segments
+ for segmentType, points in segments:
+ points = [pt for pt, smooth, name, kwargs in points]
+ if segmentType == "line":
+ assert len(points) == 1
+ self.subPen.lineTo(points[0])
+ elif segmentType == "curve":
+ assert len(points) == 3
+ self.subPen.curveTo(*points)
+ elif segmentType == "qcurve":
+ assert 0, "qcurve not supported"
+ else:
+ assert 0, "illegal segmentType: %s" % segmentType
+ self.subPen.closePath()
+
+ def addComponent(self, glyphName, transform):
+ self.subPen.addComponent(glyphName, transform)
+
+class SubsegmentPen(BasePen):
+
+ def __init__(self, glyphSet, otherPen, resolution=25):
+ BasePen.__init__(self,glyphSet)
+ self.resolution = resolution
+ self.otherPen = otherPen
+ self.subsegments = []
+ self.startContour = (0,0)
+ self.contourIndex = -1
+
+ def _moveTo(self, (x, y)):
+ self.contourIndex += 1
+ self.segmentIndex = 0
+ self.subsegments.append([])
+ self.subsegmentCount = 0
+ self.subsegments[self.contourIndex].append([self.subsegmentCount, 0])
+ self.startContour = (x,y)
+ self.lastPoint = (x,y)
+ self.otherPen.moveTo((x,y))
+
+ def _lineTo(self, (x, y)):
+ count = self.stepsForSegment((x,y),self.lastPoint)
+ if count < 1:
+ count = 1
+ self.subsegmentCount += count
+ self.subsegments[self.contourIndex].append([self.subsegmentCount, count])
+ for i in range(1,count+1):
+ x1 = self.lastPoint[0] + (x - self.lastPoint[0]) * i/float(count)
+ y1 = self.lastPoint[1] + (y - self.lastPoint[1]) * i/float(count)
+ self.otherPen.lineTo((x1,y1))
+ self.lastPoint = (x,y)
+
+ def _curveToOne(self, (x1, y1), (x2, y2), (x3, y3)):
+ count = self.stepsForSegment((x3,y3),self.lastPoint)
+ if count < 2:
+ count = 2
+ self.subsegmentCount += count
+ self.subsegments[self.contourIndex].append([self.subsegmentCount,count])
+ x = self.renderCurve((self.lastPoint[0],x1,x2,x3),count)
+ y = self.renderCurve((self.lastPoint[1],y1,y2,y3),count)
+ assert len(x) == count
+ if (x3 == self.startContour[0] and y3 == self.startContour[1]):
+ count -= 1
+ for i in range(count):
+ self.otherPen.lineTo((x[i],y[i]))
+ self.lastPoint = (x3,y3)
+
+ def _closePath(self):
+ if not (self.lastPoint[0] == self.startContour[0] and self.lastPoint[1] == self.startContour[1]):
+ self._lineTo(self.startContour)
+ self.otherPen.closePath()
+
+ def _endPath(self):
+ self.otherPen.endPath()
+
+ def addComponent(self, glyphName, transformation):
+ self.otherPen.addComponent(glyphName, transformation)
+
+ def stepsForSegment(self, p1, p2):
+ dist = np.linalg.norm(v(p1) - v(p2))
+ out = int(dist / self.resolution)
+ return out
+
+ def renderCurve(self,p,count):
+ curvePoints = []
+ t = 1.0 / float(count)
+ temp = t * t
+
+ f = p[0]
+ fd = 3 * (p[1] - p[0]) * t
+ fdd_per_2 = 3 * (p[0] - 2 * p[1] + p[2]) * temp
+ fddd_per_2 = 3 * (3 * (p[1] - p[2]) + p[3] - p[0]) * temp * t
+
+ fddd = fddd_per_2 + fddd_per_2
+ fdd = fdd_per_2 + fdd_per_2
+ fddd_per_6 = fddd_per_2 * (1.0 / 3)
+
+ for i in range(count):
+ f = f + fd + fdd_per_2 + fddd_per_6
+ fd = fd + fdd + fddd_per_2
+ fdd = fdd + fddd
+ fdd_per_2 = fdd_per_2 + fddd_per_2
+ curvePoints.append(f)
+
+ return curvePoints
+
+
+
+def fitBezierSimple(pts):
+ T = [np.linalg.norm(pts[i]-pts[i-1]) for i in range(1,len(pts))]
+ tsum = np.sum(T)
+ T = [0] + T
+ T = [np.sum(T[0:i+1])/tsum for i in range(len(pts))]
+ T = [[t**3, t**2, t, 1] for t in T]
+ T = np.array(T)
+ M = np.array([[-1, 3, -3, 1],
+ [ 3, -6, 3, 0],
+ [-3, 3, 0, 0],
+ [ 1, 0, 0, 0]])
+ T = T.dot(M)
+ T = np.concatenate((T, np.array([[100,0,0,0], [0,0,0,100]])))
+ # pts = np.vstack((pts, pts[0] * 100, pts[-1] * 100))
+ C = np.linalg.lstsq(T, pts)
+ return C[0]
+
+
+def subdivideLineSegment(pts):
+ out = [pts[0]]
+ for i in range(1, len(pts)):
+ out.append(pts[i-1] + (pts[i] - pts[i-1]) * .5)
+ out.append(pts[i])
+ return np.array(out)
+
+
+def fitBezier(pts,tangent0=None,tangent3=None):
+ if len(pts < 4):
+ pts = subdivideLineSegment(pts)
+ T = [np.linalg.norm(pts[i]-pts[i-1]) for i in range(1,len(pts))]
+ tsum = np.sum(T)
+ T = [0] + T
+ T = [np.sum(T[0:i+1])/tsum for i in range(len(pts))]
+ T = [[t**3, t**2, t, 1] for t in T]
+ T = np.array(T)
+ M = np.array([[-1, 3, -3, 1],
+ [ 3, -6, 3, 0],
+ [-3, 3, 0, 0],
+ [ 1, 0, 0, 0]])
+ T = T.dot(M)
+ n = len(pts)
+ pout = pts.copy()
+ pout[:,0] -= (T[:,0] * pts[0,0]) + (T[:,3] * pts[-1,0])
+ pout[:,1] -= (T[:,0] * pts[0,1]) + (T[:,3] * pts[-1,1])
+
+ TT = np.zeros((n*2,4))
+ for i in range(n):
+ for j in range(2):
+ TT[i*2,j*2] = T[i,j+1]
+ TT[i*2+1,j*2+1] = T[i,j+1]
+ pout = pout.reshape((n*2,1),order="C")
+
+ if tangent0 != None and tangent3 != None:
+ tangentConstraintsT = np.array([
+ [tangent0[1], -tangent0[0], 0, 0],
+ [0, 0, tangent3[1], -tangent3[0]]
+ ])
+ tangentConstraintsP = np.array([
+ [pts[0][1] * -tangent0[0] + pts[0][0] * tangent0[1]],
+ [pts[-1][1] * -tangent3[0] + pts[-1][0] * tangent3[1]]
+ ])
+ TT = np.concatenate((TT, tangentConstraintsT * 1000))
+ pout = np.concatenate((pout, tangentConstraintsP * 1000))
+ C = np.linalg.lstsq(TT,pout)[0].reshape((2,2))
+ return np.array([pts[0], C[0], C[1], pts[-1]])
+
+
+def segmentGlyph(glyph,resolution=50):
+ g1 = glyph.copy()
+ g1.clear()
+ dp = SubsegmentPointPen(g1, resolution)
+ glyph.drawPoints(dp)
+ return g1, dp.getSubsegments()
+
+
+def fitGlyph(glyph, subsegmentGlyph, subsegmentIndices, matchTangents=True):
+ outGlyph = glyph.copy()
+ outGlyph.clear()
+ fitPen = SubsegmentsToCurvesPointPen(outGlyph, subsegmentGlyph, subsegmentIndices)
+ fitPen.setMatchTangents(matchTangents)
+ # smoothPen = GuessSmoothPointPen(fitPen)
+ glyph.drawPoints(fitPen)
+ outGlyph.width = subsegmentGlyph.width
+ return outGlyph
+
+
+if __name__ == '__main__':
+ p = SubsegmentPen(None, None)
+ pts = np.array([
+ [0,0],
+ [.5,.5],
+ [.5,.5],
+ [1,1]
+ ])
+ print np.array(p.renderCurve(pts,10)) * 10
+ \ No newline at end of file
diff --git a/scripts/lib/fontbuild/generateGlyph.py b/scripts/lib/fontbuild/generateGlyph.py
new file mode 100755
index 0000000..f2214f0
--- /dev/null
+++ b/scripts/lib/fontbuild/generateGlyph.py
@@ -0,0 +1,48 @@
+from anchors import alignComponentsToAnchors
+from FL import *
+
+def parseComposite(composite):
+ c = composite.split("=")
+ d = c[1].split("/")
+ glyphName = d[0]
+ if len(d) == 1:
+ offset = [0,0]
+ else:
+ offset = [int(i) for i in d[1].split(",")]
+ accentString = c[0]
+ accents = accentString.split("+")
+ baseName = accents.pop(0)
+ accentNames = [i.split(":") for i in accents ]
+ return (glyphName, baseName, accentNames, offset)
+
+def shiftGlyphMembers(g, x):
+ g.Shift(Point(x,0))
+ for c in g.components:
+ c.deltas[0].x = c.deltas[0].x + x
+
+def generateGlyph(f,gname):
+ if gname.find("_") != -1:
+ generateString = gname
+ g = f.GenerateGlyph(generateString)
+ if f.FindGlyph(g.name) == -1:
+ f.glyphs.append(g)
+ return g
+ else:
+ glyphName, baseName, accentNames, offset = parseComposite(gname)
+ components = [baseName] + [i[0] for i in accentNames]
+ if len(components) == 1:
+ components.append("NONE")
+ generateString = "%s=%s" %("+".join(components), glyphName)
+ g = f.GenerateGlyph(generateString)
+ if f.FindGlyph(g.name) == -1:
+ f.glyphs.append(g)
+ g1 = f.glyphs[f.FindGlyph(g.name)]
+ if len(accentNames) > 0:
+ alignComponentsToAnchors(f,glyphName,baseName,accentNames)
+ if (offset[0] != 0 or offset[1] != 0):
+ g1.width += offset[1] + offset[0]
+ shiftGlyphMembers(g1,offset[0])
+ return g
+
+# generateGlyph(fl.font,"A+ogonek=Aogonek")
+# fl.UpdateFont() \ No newline at end of file
diff --git a/scripts/lib/fontbuild/instanceNames.py b/scripts/lib/fontbuild/instanceNames.py
new file mode 100755
index 0000000..96827c8
--- /dev/null
+++ b/scripts/lib/fontbuild/instanceNames.py
@@ -0,0 +1,184 @@
+from datetime import date
+import re
+from random import randint
+import string
+
+class InstanceNames:
+ "Class that allows easy setting of FontLab name fields. TODO: Add proper italic flags"
+
+ foundry = ""
+ build = "0000"
+ version = "1.0"
+ year = date.today().year
+ designer = "Christian Robertson"
+ license = "Licensed under the Apache License, Version 2.0"
+ licenseURL = "http://www.apache.org/licenses/LICENSE-2.0"
+
+ def __init__(self,names):
+ if type(names) == type(" "):
+ names = names.split("/")
+
+ self.longfamily = names[0]
+ self.longstyle = names[1]
+ self.shortstyle = names[2]
+ self.subfamilyAbbrev = names[3]
+
+ self.width = self._getWidth()
+ self.italic = self._getItalic()
+ self.weight = self._getWeight()
+ self.fullname = "%s %s" %(self.longfamily, self.longstyle)
+ self.postscript = re.sub(' ','', self.longfamily) + "-" + re.sub(' ','',self.longstyle)
+
+ # if self.subfamilyAbbrev != "" and self.subfamilyAbbrev != None and self.subfamilyAbbrev != "Rg":
+ # self.shortfamily = "%s %s" %(self.longfamily, self.subfamilyAbbrev)
+ # else:
+ # self.shortfamily = self.longfamily
+ self.shortfamily = self.longfamily
+
+ def setRFNames(self,f, version=1, versionMinor=0):
+ f.info.familyName = self.longfamily
+ f.info.styleName = self.longstyle
+ f.info.styleMapFamilyName = self.shortfamily
+ f.info.styleMapStyleName = self.shortstyle.lower()
+ f.info.versionMajor = version
+ f.info.versionMinor = versionMinor
+ f.info.year = self.year
+ f.info.copyright = "Font data copyright %s %s" %(self.foundry, self.year)
+ f.info.trademark = "%s is a trademark of %s." %(self.longfamily, self.foundry)
+
+ f.info.openTypeNameDesigner = "Christian Robertson"
+ f.info.openTypeNameDesignerURL = self.foundry + ".com"
+ f.info.openTypeNameManufacturer = self.foundry
+ f.info.openTypeNameManufacturerURL = self.foundry + ".com"
+ f.info.openTypeNameLicense = self.license
+ f.info.openTypeNameLicenseURL = self.licenseURL
+ f.info.openTypeNameVersion = "%i.%i" %(version,versionMinor)
+ f.info.openTypeNameUniqueID = "%s:%s:%s" %(self.foundry, self.longfamily, self.year)
+ # f.info.openTypeNameDescription = ""
+ # f.info.openTypeNameCompatibleFullName = ""
+ # f.info.openTypeNameSampleText = ""
+ if (self.subfamilyAbbrev != "Rg"):
+ f.info.openTypeNamePreferredFamilyName = self.longfamily
+ f.info.openTypeNamePreferredSubfamilyName = self.longstyle
+
+ f.info.macintoshFONDName = re.sub(' ','',self.longfamily) + " " + re.sub(' ','',self.longstyle)
+
+
+ def setFLNames(self,flFont):
+
+ from FL import NameRecord
+
+ flFont.family_name = self.shortfamily
+ flFont.mac_compatible = self.fullname
+ flFont.style_name = self.longstyle
+ flFont.full_name = self.fullname
+ flFont.font_name = self.postscript
+ flFont.font_style = self._getStyleCode()
+ flFont.menu_name = self.shortfamily
+ flFont.apple_name = re.sub(' ','',self.longfamily) + " " + re.sub(' ','',self.longstyle)
+ flFont.fond_id = randint(1000,9999)
+ flFont.pref_family_name = self.longfamily
+ flFont.pref_style_name = self.longstyle
+ flFont.weight = self.weight
+ flFont.weight_code = self._getWeightCode(self.weight)
+ flFont.width = self.width
+
+ fn = flFont.fontnames
+ fn.clean()
+ fn.append(NameRecord(0,1,0,0, "Font data copyright %s %s" %(self.foundry, self.year) ))
+ fn.append(NameRecord(0,3,1,1033, "Font data copyright %s %s" %(self.foundry, self.year) ))
+ fn.append(NameRecord(1,1,0,0, self.longfamily ))
+ fn.append(NameRecord(1,3,1,1033, self.shortfamily ))
+ fn.append(NameRecord(2,1,0,0, self.longstyle ))
+ fn.append(NameRecord(2,3,1,1033, self.longstyle ))
+ fn.append(NameRecord(3,1,0,0, "%s:%s:%s" %(self.foundry, self.longfamily, self.year) ))
+ fn.append(NameRecord(3,3,1,1033, "%s:%s:%s" %(self.foundry, self.longfamily, self.year) ))
+ fn.append(NameRecord(4,1,0,0, self.fullname ))
+ fn.append(NameRecord(4,3,1,1033, self.fullname ))
+ fn.append(NameRecord(5,1,0,0, "Version %s%s; %s" %(self.version, self.build, self.year) ))
+ fn.append(NameRecord(5,3,1,1033, "Version %s%s; %s" %(self.version, self.build, self.year) ))
+ fn.append(NameRecord(6,1,0,0, self.postscript ))
+ fn.append(NameRecord(6,3,1,1033, self.postscript ))
+ fn.append(NameRecord(7,1,0,0, "%s is a trademark of %s." %(self.longfamily, self.foundry) ))
+ fn.append(NameRecord(7,3,1,1033, "%s is a trademark of %s." %(self.longfamily, self.foundry) ))
+ fn.append(NameRecord(9,1,0,0, self.foundry ))
+ fn.append(NameRecord(9,3,1,1033, self.foundry ))
+ fn.append(NameRecord(11,1,0,0, self.foundry + ".com" ))
+ fn.append(NameRecord(11,3,1,1033, self.foundry + ".com" ))
+ fn.append(NameRecord(12,1,0,0, self.designer ))
+ fn.append(NameRecord(12,3,1,1033, self.designer ))
+ fn.append(NameRecord(13,1,0,0, self.license ))
+ fn.append(NameRecord(13,3,1,1033, self.license ))
+ fn.append(NameRecord(14,1,0,0, self.licenseURL ))
+ fn.append(NameRecord(14,3,1,1033, self.licenseURL ))
+ if (self.subfamilyAbbrev != "Rg"):
+ fn.append(NameRecord(16,3,1,1033, self.longfamily ))
+ fn.append(NameRecord(17,3,1,1033, self.longstyle))
+ #else:
+ #fn.append(NameRecord(17,3,1,1033,""))
+ #fn.append(NameRecord(18,1,0,0, re.sub("Italic","It", self.fullname)))
+
+ def _getSubstyle(self, regex):
+ substyle = re.findall(regex, self.longstyle)
+ if len(substyle) > 0:
+ return substyle[0]
+ else:
+ return ""
+
+ def _getItalic(self):
+ return self._getSubstyle(r"Italic|Oblique|Obliq")
+
+ def _getWeight(self):
+ w = self._getSubstyle(r"Extrabold|Superbold|Super|Fat|Bold|Semibold|Demibold|Medium|Light|Thin")
+ if w == "":
+ w = "Regular"
+ return w
+
+ def _getWidth(self):
+ w = self._getSubstyle(r"Condensed|Extended|Narrow|Wide")
+ if w == "":
+ w = "Normal"
+ return w
+
+ def _getStyleCode(self):
+ styleCode = 0
+ if self.shortstyle == "Bold":
+ styleCode = 32
+ if self.shortstyle == "Italic":
+ styleCode = 1
+ if self.shortstyle == "Bold Italic":
+ styleCode = 33
+ if self.longstyle == "Regular":
+ styleCode = 64
+ return styleCode
+
+ def _getWeightCode(self,weight):
+ if weight == "Thin":
+ return 250
+ elif weight == "Light":
+ return 300
+ elif weight == "Bold":
+ return 700
+ elif weight == "Medium":
+ return 500
+ elif weight == "Semibold":
+ return 600
+ elif weight == "Black":
+ return 800
+ elif weight == "Fat":
+ return 900
+
+ return 400
+
+def setNames(f,names,foundry="",version="1.0",build="0000"):
+ InstanceNames.foundry = foundry
+ InstanceNames.version = version
+ InstanceNames.build = build
+ i = InstanceNames(names)
+ i.setFLNames(f)
+
+def setNamesRF(f,names,foundry=""):
+ InstanceNames.foundry = foundry
+ i = InstanceNames(names)
+ i.setRFNames(f)
+ \ No newline at end of file
diff --git a/scripts/lib/fontbuild/italics.py b/scripts/lib/fontbuild/italics.py
new file mode 100644
index 0000000..72178b4
--- /dev/null
+++ b/scripts/lib/fontbuild/italics.py
@@ -0,0 +1,267 @@
+from fontTools.misc.transform import Transform
+from robofab.world import CurrentFont
+from robofab.world import RFont
+from time import clock
+import numpy as np
+import math
+from alignpoints import alignCorners
+
+def italicizeGlyph(g, angle=10, stemWidth=185):
+ f = CurrentFont()
+ glyph = f[g.name]
+ slope = np.tanh([math.pi * angle / 180])
+
+ # determine how far on the x axis the glyph should slide
+ # to compensate for the slant. -600 is a magic number
+ # that assumes a 2048 unit em square
+ MEAN_YCENTER = -600
+ m = Transform(1, 0, slope, 1, 0, 0)
+ xoffset, junk = m.transformPoint((0, MEAN_YCENTER))
+ m = Transform(.97, 0, slope, 1, xoffset, 0)
+
+ if len(glyph) > 0:
+ g2 = italicize(f[g.name], angle, xoffset=xoffset, stemWidth=stemWidth)
+ f.insertGlyph(g2, g.name)
+
+ transformFLGlyphMembers(f[g.name], m)
+
+
+def italicize(glyph, angle=12, stemWidth=180, xoffset=-50):
+ CURVE_CORRECTION_WEIGHT = .03
+ CORNER_WEIGHT = 10
+ ga,subsegments = segmentGlyph(glyph,25)
+ va, e = glyphToMesh(ga)
+ n = len(va)
+ grad = mapEdges(lambda a,(p,n): normalize(p-a), va, e)
+ cornerWeights = mapEdges(lambda a,(p,n): normalize(p-a).dot(normalize(a-n)), grad, e)[:,0].reshape((-1,1))
+ smooth = np.ones((n,1)) * CURVE_CORRECTION_WEIGHT
+
+ controlPoints = findControlPointsInMesh(glyph, va, subsegments)
+ smooth[controlPoints > 0] = 1
+ smooth[cornerWeights < .6] = CORNER_WEIGHT
+ # smooth[cornerWeights >= .9999] = 1
+
+ out = va.copy()
+ hascurves = False
+ for c in glyph.contours:
+ for s in c.segments:
+ if s.type == "curve":
+ hascurves = True
+ break
+ if hascurves:
+ break
+ if stemWidth > 100:
+ outCorrected = skewMesh(recompose(skewMesh(out, angle * 1.6), grad, e, smooth=smooth), -angle * 1.6)
+ # out = copyMeshDetails(va, out, e, 6)
+ else:
+ outCorrected = out
+ normals = edgeNormals(out, e)
+ center = va + normals * stemWidth * .4
+ if stemWidth > 130:
+ center[:, 0] = va[:, 0] * .7 + center[:,0] * .3
+ centerSkew = skewMesh(center.dot(np.array([[.97,0],[0,1]])), angle * .9)
+ out = outCorrected + (centerSkew - center)
+ out[:,1] = outCorrected[:,1]
+
+ smooth = np.ones((n,1)) * .1
+ out = alignCorners(glyph, out, subsegments)
+ out = copyMeshDetails(skewMesh(va, angle), out, e, 7, smooth=smooth)
+ # grad = mapEdges(lambda a,(p,n): normalize(p-a), skewMesh(outCorrected, angle*.9), e)
+ # out = recompose(out, grad, e, smooth=smooth)
+
+ out = skewMesh(out, angle * .1)
+ out[:,0] += xoffset
+ # out[:,1] = outCorrected[:,1]
+ out[va[:,1] == 0, 1] = 0
+ gOut = meshToGlyph(out, ga)
+ # gOut.width *= .97
+ # gOut.width += 10
+ # return gOut
+ return fitGlyph(glyph, gOut, subsegments)
+
+def condenseGlyph(glyph, scale=.8, stemWidth=185):
+ ga, subsegments = segmentGlyph(glyph, 25)
+ va, e = glyphToMesh(ga)
+ n = len(va)
+
+ normals = edgeNormals(va,e)
+ cn = va.dot(np.array([[scale, 0],[0,1]]))
+ grad = mapEdges(lambda a,(p,n): normalize(p-a), cn, e)
+ # ograd = mapEdges(lambda a,(p,n): normalize(p-a), va, e)
+
+ cn[:,0] -= normals[:,0] * stemWidth * .5 * (1 - scale)
+ out = recompose(cn, grad, e, smooth=.5)
+ # out = recompose(out, grad, e, smooth=.1)
+ out = recompose(out, grad, e, smooth=.01)
+
+ # cornerWeights = mapEdges(lambda a,(p,n): normalize(p-a).dot(normalize(a-n)), grad, e)[:,0].reshape((-1,1))
+ # smooth = np.ones((n,1)) * .1
+ # smooth[cornerWeights < .6] = 10
+ #
+ # grad2 = quantizeGradient(grad).astype(float)
+ # grad2 = copyGradDetails(grad, grad2, e, scale=10)
+ # grad2 = mapEdges(lambda a,e: normalize(a), grad2, e)
+ # out = recompose(out, grad2, e, smooth=smooth)
+ out[:,0] += 15
+ out[:,1] = va[:,1]
+ # out = recompose(out, grad, e, smooth=.5)
+ gOut = meshToGlyph(out, ga)
+ gOut = fitGlyph(glyph, gOut, subsegments)
+ for i,seg in enumerate(gOut):
+ gOut[i].points[0].y = glyph[i].points[0].y
+ return gOut
+
+
+def transformFLGlyphMembers(g, m, transformAnchors = True):
+ # g.transform(m)
+ g.width = g.width * m[0]
+ p = m.transformPoint((0,0))
+ for c in g.components:
+ d = m.transformPoint(c.offset)
+ c.offset = (d[0] - p[0], d[1] - p[1])
+ if transformAnchors:
+ for a in g.anchors:
+ aa = m.transformPoint((a.x,a.y))
+ a.x = aa[0]
+ # a.x,a.y = (aa[0] - p[0], aa[1] - p[1])
+ # a.x = a.x - m[4]
+
+
+from curveFitPen import fitGlyph,segmentGlyph
+from numpy.linalg import norm
+from scipy.sparse.linalg import cg
+from scipy.ndimage.filters import gaussian_filter1d as gaussian
+from scipy.cluster.vq import vq, kmeans2, whiten
+
+def glyphToMesh(g):
+ points = []
+ edges = {}
+ offset = 0
+ for c in g.contours:
+ if len(c) < 2:
+ continue
+ for i,prev,next in rangePrevNext(len(c)):
+ points.append((c[i].points[0].x, c[i].points[0].y))
+ edges[i + offset] = np.array([prev + offset, next + offset], dtype=int)
+ offset += len(c)
+ return np.array(points), edges
+
+def meshToGlyph(points, g):
+ g1 = g.copy()
+ j = 0
+ for c in g1.contours:
+ if len(c) < 2:
+ continue
+ for i in range(len(c)):
+ c[i].points[0].x = points[j][0]
+ c[i].points[0].y = points[j][1]
+ j += 1
+ return g1
+
+def quantizeGradient(grad, book=None):
+ if book == None:
+ book = np.array([(1,0),(0,1),(0,-1),(-1,0)])
+ indexArray = vq(whiten(grad), book)[0]
+ out = book[indexArray]
+ for i,v in enumerate(out):
+ out[i] = normalize(v)
+ return out
+
+def findControlPointsInMesh(glyph, va, subsegments):
+ controlPointIndices = np.zeros((len(va),1))
+ index = 0
+ for i,c in enumerate(subsegments):
+ segmentCount = len(glyph.contours[i].segments) - 1
+ for j,s in enumerate(c):
+ if j < segmentCount:
+ if glyph.contours[i].segments[j].type == "line":
+ controlPointIndices[index] = 1
+ index += s[1]
+ return controlPointIndices
+
+
+
+def recompose(v, grad, e, smooth=1, P=None, distance=None):
+ n = len(v)
+ if distance == None:
+ distance = mapEdges(lambda a,(p,n): norm(p - a), v, e)
+ if (P == None):
+ P = mP(v,e)
+ P += np.identity(n) * smooth
+ f = v.copy()
+ for i,(prev,next) in e.iteritems():
+ f[i] = (grad[next] * distance[next] - grad[i] * distance[i])
+ out = v.copy()
+ f += v * smooth
+ for i in range(len(out[0,:])):
+ out[:,i] = cg(P, f[:,i])[0]
+ return out
+
+def mP(v,e):
+ n = len(v)
+ M = np.zeros((n,n))
+ for i, edges in e.iteritems():
+ w = -2 / float(len(edges))
+ for index in edges:
+ M[i,index] = w
+ M[i,i] = 2
+ return M
+
+def normalize(v):
+ n = np.linalg.norm(v)
+ if n == 0:
+ return v
+ return v/n
+
+def mapEdges(func,v,e,*args):
+ b = v.copy()
+ for i, edges in e.iteritems():
+ b[i] = func(v[i], [v[j] for j in edges], *args)
+ return b
+
+def getNormal(a,b,c):
+ "Assumes TT winding direction"
+ p = np.roll(normalize(b - a), 1)
+ n = -np.roll(normalize(c - a), 1)
+ p[1] *= -1
+ n[1] *= -1
+ # print p, n, normalize((p + n) * .5)
+ return normalize((p + n) * .5)
+
+def edgeNormals(v,e):
+ "Assumes a mesh where each vertex has exactly least two edges"
+ return mapEdges(lambda a,(p,n) : getNormal(a,p,n),v,e)
+
+def rangePrevNext(count):
+ c = np.arange(count,dtype=int)
+ r = np.vstack((c, np.roll(c, 1), np.roll(c, -1)))
+ return r.T
+
+def skewMesh(v,angle):
+ slope = np.tanh([math.pi * angle / 180])
+ return v.dot(np.array([[1,0],[slope,1]]))
+
+def labelConnected(e):
+ label = 0
+ labels = np.zeros((len(e),1))
+ for i,(prev,next) in e.iteritems():
+ labels[i] = label
+ if next <= i:
+ label += 1
+ return labels
+
+def copyGradDetails(a,b,e,scale=15):
+ n = len(a)
+ labels = labelConnected(e)
+ out = a.astype(float).copy()
+ for i in range(labels[-1]+1):
+ mask = (labels==i).flatten()
+ out[mask,:] = gaussian(b[mask,:], scale, mode="wrap", axis=0) + a[mask,:] - gaussian(a[mask,:], scale, mode="wrap", axis=0)
+ return out
+
+def copyMeshDetails(va,vb,e,scale=5,smooth=.01):
+ gradA = mapEdges(lambda a,(p,n): normalize(p-a), va, e)
+ gradB = mapEdges(lambda a,(p,n): normalize(p-a), vb, e)
+ grad = copyGradDetails(gradA, gradB, e, scale)
+ grad = mapEdges(lambda a,(p,n): normalize(a), grad, e)
+ return recompose(vb, grad, e, smooth=smooth) \ No newline at end of file
diff --git a/scripts/lib/fontbuild/italics2.py b/scripts/lib/fontbuild/italics2.py
new file mode 100644
index 0000000..7eee90b
--- /dev/null
+++ b/scripts/lib/fontbuild/italics2.py
@@ -0,0 +1,148 @@
+from curveFitPen import fitGlyph,segmentGlyph
+import numpy as np
+from numpy.linalg import norm
+import math
+from scipy.sparse.linalg import cg
+
+def glyphToMesh(g):
+ points = []
+ edges = {}
+ offset = 0
+ for c in g.contours:
+ if len(c) < 2:
+ continue
+ for i,prev,next in rangePrevNext(len(c)):
+ points.append((c[i].points[0].x, c[i].points[0].y))
+ edges[i + offset] = np.array([prev + offset, next + offset], dtype=int)
+ offset += len(c)
+ return np.array(points), edges
+
+def meshToGlyph(points, g):
+ g1 = g.copy()
+ j = 0
+ for c in g1.contours:
+ if len(c) < 2:
+ continue
+ for i in range(len(c)):
+ c[i].points[0].x = points[j][0]
+ c[i].points[0].y = points[j][1]
+ j += 1
+ return g1
+
+def italicize(glyph, angle=12, stemWidth=180, xoffset=-50):
+ ga,subsegments = segmentGlyph(glyph,25)
+ va, e = glyphToMesh(ga)
+ n = len(va)
+ grad = mapEdges(lambda a,(p,n): normalize(p-a), va, e)
+ cornerWeights = mapEdges(lambda a,(p,n): normalize(p-a).dot(normalize(a-n)), grad, e)[:,0].reshape((-1,1))
+ smooth = np.ones((n,1)) * .02
+ smooth[cornerWeights < .6] = 5
+ # smooth[cornerWeights >= .9999] = 2
+ out = va.copy()
+ if stemWidth > 100:
+ out = skewMesh(poisson(skewMesh(out, angle * 2), grad, e, smooth=smooth), -angle * 2)
+ out = copyMeshDetails(va, out, e, 6)
+ # return meshToGlyph(out,ga)
+ normals = edgeNormals(out, e)
+ center = va + normals * stemWidth * .4
+ if stemWidth > 100:
+ center[:, 0] = va[:, 0]
+ centerSkew = skewMesh(center.dot(np.array([[.97,0],[0,1]])), angle * .7)
+ # centerSkew = skewMesh(center, angle * .7)
+ out = out + (centerSkew - center)
+ out = copyMeshDetails(skewMesh(va, angle * .7), out, e, 12)
+ out = skewMesh(out, angle * .3)
+ out[:,0] += xoffset
+ # out[:,1] = va[:,1]
+ gOut = meshToGlyph(out, ga)
+ # gOut.width *= .97
+ gOut.width += 10
+ # return gOut
+ return fitGlyph(glyph, gOut, subsegments)
+
+def poisson(v, grad, e, smooth=1, P=None, distance=None):
+ n = len(v)
+ if distance == None:
+ distance = mapEdges(lambda a,(p,n): norm(p - a), v, e)
+ if (P == None):
+ P = mP(v,e)
+ P += np.identity(n) * smooth
+ f = v.copy()
+ for i,(prev,next) in e.iteritems():
+ f[i] = (grad[next] * distance[next] - grad[i] * distance[i])
+ out = v.copy()
+ f += v * smooth
+ for i in range(len(out[0,:])):
+ out[:,i] = cg(P, f[:,i])[0]
+ return out
+
+def mP(v,e):
+ n = len(v)
+ M = np.zeros((n,n))
+ for i, edges in e.iteritems():
+ w = -2 / float(len(edges))
+ for index in edges:
+ M[i,index] = w
+ M[i,i] = 2
+ return M
+
+def normalize(v):
+ n = np.linalg.norm(v)
+ if n == 0:
+ return v
+ return v/n
+
+def mapEdges(func,v,e,*args):
+ b = v.copy()
+ for i, edges in e.iteritems():
+ b[i] = func(v[i], [v[j] for j in edges], *args)
+ return b
+
+def getNormal(a,b,c):
+ "Assumes TT winding direction"
+ p = np.roll(normalize(b - a), 1)
+ n = -np.roll(normalize(c - a), 1)
+ p[1] *= -1
+ n[1] *= -1
+ # print p, n, normalize((p + n) * .5)
+ return normalize((p + n) * .5)
+
+def edgeNormals(v,e):
+ "Assumes a mesh where each vertex has exactly least two edges"
+ return mapEdges(lambda a,(p,n) : getNormal(a,p,n),v,e)
+
+def rangePrevNext(count):
+ c = np.arange(count,dtype=int)
+ r = np.vstack((c, np.roll(c, 1), np.roll(c, -1)))
+ return r.T
+
+def skewMesh(v,angle):
+ slope = np.tanh([math.pi * angle / 180])
+ return v.dot(np.array([[1,0],[slope,1]]))
+
+
+from scipy.ndimage.filters import gaussian_filter1d as gaussian
+
+def labelConnected(e):
+ label = 0
+ labels = np.zeros((len(e),1))
+ for i,(prev,next) in e.iteritems():
+ labels[i] = label
+ if next <= i:
+ label += 1
+ return labels
+
+def copyGradDetails(a,b,e,scale=15):
+ n = len(a)
+ labels = labelConnected(e)
+ out = a.astype(float).copy()
+ for i in range(labels[-1]+1):
+ mask = (labels==i).flatten()
+ out[mask,:] = gaussian(b[mask,:], scale, mode="wrap", axis=0) + a[mask,:] - gaussian(a[mask,:], scale, mode="wrap", axis=0)
+ return out
+
+def copyMeshDetails(va,vb,e,scale=5,smooth=.01):
+ gradA = mapEdges(lambda a,(p,n): normalize(p-a), va, e)
+ gradB = mapEdges(lambda a,(p,n): normalize(p-a), vb, e)
+ grad = copyGradDetails(gradA, gradB, e, scale)
+ return poisson(vb, grad, e, smooth=smooth) \ No newline at end of file
diff --git a/scripts/lib/fontbuild/kerning.py b/scripts/lib/fontbuild/kerning.py
new file mode 100755
index 0000000..cdece40
--- /dev/null
+++ b/scripts/lib/fontbuild/kerning.py
@@ -0,0 +1,26 @@
+import re
+from FL import *
+
+def markKernClassesLR(f):
+ for i in range(len(f.classes)):
+ classname = f.classes[i].split(':', 1).pop(0).strip()
+ if classname.startswith('_'):
+ l = 0
+ r = 0
+ if classname.endswith('_L'):
+ l = 1
+ elif classname.endswith('_R'):
+ r = 1
+ elif classname.endswith('_LR'):
+ l = 1
+ r = 1
+ f.SetClassFlags(i, l, r)
+ fl.UpdateFont()
+
+def generateFLKernClassesFromOTString(f,classString):
+ classString.replace("\r","\n")
+ rx = re.compile(r"@(_[\w]+)\s*=\s*\[\s*(\w+?)\s+(.*?)\]\s*;")
+ classes = ["%s : %s' %s" %(m[0],m[1],m[2]) for m in rx.findall(classString)]
+ f.classes = classes
+ markKernClassesLR(f)
+
diff --git a/scripts/lib/fontbuild/mitreGlyph.py b/scripts/lib/fontbuild/mitreGlyph.py
new file mode 100755
index 0000000..ab68e4e
--- /dev/null
+++ b/scripts/lib/fontbuild/mitreGlyph.py
@@ -0,0 +1,121 @@
+"""Mitre Glyph:
+
+mitreSize : Length of the segment created by the mitre. The default is 4.
+maxAngle : Maximum angle in radians at which nodes will be mitred. The default is .9 (about 50 degrees).
+ Works for both inside and outside angles
+
+"""
+
+from FL import *
+import math
+
+def getContours(g):
+ nLength = len(g.nodes)
+ contours = []
+ cid = -1
+ for i in range(nLength):
+ n = g.nodes[i]
+ if n.type == nMOVE:
+ cid += 1
+ contours.append([])
+ contours[cid].append(n)
+ return contours
+
+def getTangents(contours):
+ tmap = []
+ for c in contours:
+ clen = len(c)
+ for i in range(clen):
+ n = c[i]
+ p = Point(n.x, n.y)
+ nn = c[(i + 1) % clen]
+ pn = c[(clen + i - 1) % clen]
+ if nn.type == nCURVE:
+ np = Point(nn[1].x,nn[1].y)
+ else:
+ np = Point(nn.x,nn.y)
+ if n.type == nCURVE:
+ pp = Point(n[2].x,n[2].y)
+ else:
+ pp = Point(pn.x,pn.y)
+ nVect = Point(-p.x + np.x, -p.y + np.y)
+ pVect = Point(-p.x + pp.x, -p.y + pp.y)
+ tmap.append((pVect,nVect))
+ return tmap
+
+def normalizeVector(p):
+ m = getMagnitude(p);
+ if m != 0:
+ return p*(1/m)
+ else:
+ return Point(0,0)
+
+def getMagnitude(p):
+ return math.sqrt(p.x*p.x + p.y*p.y)
+
+def getDistance(v1,v2):
+ return getMagnitude(Point(v1.x - v2.x, v1.y - v2.y))
+
+def getAngle(v1,v2):
+ angle = math.atan2(v1.y,v1.x) - math.atan2(v2.y,v2.x)
+ return (angle + (2*math.pi)) % (2*math.pi)
+
+def angleDiff(a,b):
+ return math.pi - abs((abs(a - b) % (math.pi*2)) - math.pi)
+
+def getAngle2(v1,v2):
+ return abs(angleDiff(math.atan2(v1.y, v1.x), math.atan2(v2.y, v2.x)))
+
+def getMitreOffset(n,v1,v2,mitreSize=4,maxAngle=.9):
+
+ # dont mitre if segment is too short
+ if abs(getMagnitude(v1)) < mitreSize * 2 or abs(getMagnitude(v2)) < mitreSize * 2:
+ return
+ angle = getAngle2(v2,v1)
+ v1 = normalizeVector(v1)
+ v2 = normalizeVector(v2)
+ if v1.x == v2.x and v1.y == v2.y:
+ return
+
+
+ # only mitre corners sharper than maxAngle
+ if angle > maxAngle:
+ return
+
+ radius = mitreSize / abs(getDistance(v1,v2))
+ offset1 = Point(round(v1.x * radius), round(v1.y * radius))
+ offset2 = Point(round(v2.x * radius), round(v2.y * radius))
+ return offset1, offset2
+
+def mitreGlyph(g,mitreSize,maxAngle):
+ if g == None:
+ return
+
+ contours = getContours(g)
+ tangents = getTangents(contours)
+ nodes = []
+ needsMitring = False
+ nid = -1
+ for c in contours:
+ for n in c:
+ nid += 1
+ v1, v2 = tangents[nid]
+ off = getMitreOffset(n,v1,v2,mitreSize,maxAngle)
+ n1 = Node(n)
+ if off != None:
+ offset1, offset2 = off
+ n2 = Node(nLINE, Point(n.x + offset2.x, n.y + offset2.y))
+ n1[0].x += offset1.x
+ n1[0].y += offset1.y
+ nodes.append(n1)
+ nodes.append(n2)
+ needsMitring = True
+ else:
+ nodes.append(n1)
+ if needsMitring:
+ g.Clear()
+ g.Insert(nodes)
+
+fl.SetUndo()
+mitreGlyph(fl.glyph,8.,.9)
+fl.UpdateGlyph() \ No newline at end of file
diff --git a/scripts/lib/fontbuild/mix.py b/scripts/lib/fontbuild/mix.py
new file mode 100755
index 0000000..86bf4ed
--- /dev/null
+++ b/scripts/lib/fontbuild/mix.py
@@ -0,0 +1,334 @@
+from FL import *
+from numpy import array, append
+import copy
+
+class FFont:
+ "Font wrapper for floating point operations"
+
+ def __init__(self,f=None):
+ self.glyphs = {}
+ self.hstems = []
+ self.vstems = []
+ if isinstance(f,FFont):
+ #self.glyphs = [g.copy() for g in f.glyphs]
+ for key,g in f.glyphs.iteritems():
+ self.glyphs[key] = g.copy()
+ self.hstems = list(f.hstems)
+ self.vstems = list(f.vstems)
+ elif f != None:
+ self.copyFromFont(f)
+
+ def copyFromFont(self,f):
+ for g in f.glyphs:
+ self.glyphs[g.name] = FGlyph(g)
+ self.hstems = [s for s in f.stem_snap_h[0]]
+ self.vstems = [s for s in f.stem_snap_v[0]]
+
+
+ def copyToFont(self,f):
+ for g in f.glyphs:
+ try:
+ gF = self.glyphs[g.name]
+ gF.copyToGlyph(g)
+ except:
+ print "Copy to glyph failed for" + g.name
+ f.stem_snap_h[0] = self.hstems
+ f.stem_snap_v[0] = self.vstems
+
+ def getGlyph(self, gname):
+ try:
+ return self.glyphs[gname]
+ except:
+ return None
+
+ def setGlyph(self, gname, glyph):
+ self.glyphs[gname] = glyph
+
+ def addDiff(self,b,c):
+ newFont = FFont(self)
+ for key,g in newFont.glyphs.iteritems():
+ gB = b.getGlyph(key)
+ gC = c.getGlyph(key)
+ try:
+ newFont.glyphs[key] = g.addDiff(gB,gC)
+ except:
+ print "Add diff failed for '%s'" %key
+ return newFont
+
+class FGlyph:
+ "provides a temporary floating point compatible glyph data structure"
+
+ def __init__(self, g=None):
+ self.nodes = []
+ self.width = 0.
+ self.components = []
+ self.kerning = []
+ self.anchors = []
+ if g != None:
+ self.copyFromGlyph(g)
+
+ def copyFromGlyph(self,g):
+ self.name = g.name
+ valuesX = []
+ valuesY = []
+ self.width = len(valuesX)
+ valuesX.append(g.width)
+ for c in g.components:
+ self.components.append((len(valuesX),len(valuesY)))
+ valuesX.append(c.scale.x)
+ valuesY.append(c.scale.y)
+ valuesX.append(c.delta.x)
+ valuesY.append(c.delta.y)
+
+ for a in g.anchors:
+ self.anchors.append((len(valuesX), len(valuesY)))
+ valuesX.append(a.x)
+ valuesY.append(a.y)
+
+ for i in range(len(g.nodes)):
+ self.nodes.append([])
+ for j in range (len(g.nodes[i])):
+ self.nodes[i].append( (len(valuesX), len(valuesY)) )
+ valuesX.append(g.nodes[i][j].x)
+ valuesY.append(g.nodes[i][j].y)
+
+ for k in g.kerning:
+ self.kerning.append(KerningPair(k))
+
+ self.dataX = array(valuesX)
+ self.dataY = array(valuesY)
+
+ def copyToGlyph(self,g):
+ g.width = self._derefX(self.width)
+ if len(g.components) == len(self.components):
+ for i in range(len(self.components)):
+ g.components[i].scale.x = self._derefX( self.components[i][0] + 0)
+ g.components[i].scale.y = self._derefY( self.components[i][1] + 0)
+ g.components[i].deltas[0].x = self._derefX( self.components[i][0] + 1)
+ g.components[i].deltas[0].y = self._derefY( self.components[i][1] + 1)
+ g.kerning = []
+ if len(g.anchors) == len(self.anchors):
+ for i in range(len(self.anchors)):
+ g.anchors[i].x = self._derefX( self.anchors[i][0])
+ g.anchors[i].y = self._derefY( self.anchors[i][1])
+ for k in self.kerning:
+ g.kerning.append(KerningPair(k))
+ for i in range( len(g.nodes)) :
+ for j in range (len(g.nodes[i])):
+ g.nodes[i][j].x = self._derefX( self.nodes[i][j][0] )
+ g.nodes[i][j].y = self._derefY( self.nodes[i][j][1] )
+
+ def isCompatible(self,g):
+ return len(self.dataX) == len(g.dataX) and len(self.dataY) == len(g.dataY) and len(g.nodes) == len(self.nodes)
+
+ def __add__(self,g):
+ if self.isCompatible(g):
+ newGlyph = self.copy()
+ newGlyph.dataX = self.dataX + g.dataX
+ newGlyph.dataY = self.dataY + g.dataY
+ return newGlyph
+ else:
+ print "Add failed for '%s'" %(self.name)
+ raise Exception
+
+ def __sub__(self,g):
+ if self.isCompatible(g):
+ newGlyph = self.copy()
+ newGlyph.dataX = self.dataX - g.dataX
+ newGlyph.dataY = self.dataY - g.dataY
+ return newGlyph
+ else:
+ print "Subtract failed for '%s'" %(self.name)
+ raise Exception
+
+ def __mul__(self,scalar):
+ newGlyph = self.copy()
+ newGlyph.dataX = self.dataX * scalar
+ newGlyph.dataY = self.dataY * scalar
+ return newGlyph
+
+ def scaleX(self,scalar):
+ newGlyph = self.copy()
+ if len(self.dataX) > 0:
+ newGlyph.dataX = self.dataX * scalar
+ for i in range(len(newGlyph.components)):
+ newGlyph.dataX[newGlyph.components[i][0]] = self.dataX[newGlyph.components[i][0]]
+ return newGlyph
+
+ def shift(self,ammount):
+ newGlyph = self.copy()
+ newGlyph.dataX = self.dataX + ammount
+ for i in range(len(newGlyph.components)):
+ newGlyph.dataX[newGlyph.components[i][0]] = self.dataX[newGlyph.components[i][0]]
+ return newGlyph
+
+ def interp(self, g, v):
+ gF = self.copy()
+ if not self.isCompatible(g):
+ print "Interpolate failed for '%s'; outlines incompatible" %(self.name)
+ raise Exception
+
+ gF.dataX += (g.dataX - gF.dataX) * v.x
+ gF.dataY += (g.dataY - gF.dataY) * v.y
+ gF.kerning = interpolateKerns(self,g,v)
+ return gF
+
+ def copy(self):
+ ng = FGlyph()
+ ng.nodes = list(self.nodes)
+ ng.width = self.width
+ ng.components = list(self.components)
+ ng.kerning = list(self.kerning)
+ ng.anchors = list(self.anchors)
+ ng.dataX = self.dataX.copy()
+ ng.dataY = self.dataY.copy()
+ ng.name = self.name
+ return ng
+
+ def _derefX(self,id):
+ return int(round(self.dataX[id]))
+
+ def _derefY(self,id):
+ return int(round(self.dataY[id]))
+
+ def addDiff(self,gB,gC):
+ newGlyph = self + (gB - gC)
+ return newGlyph
+
+
+
+class Master:
+
+
+ def __init__(self,font=None,v=0,ifont=None, kernlist=None, overlay=None):
+ if isinstance(font,FFont):
+ self.font = None
+ self.ffont = font
+ elif isinstance(font,str):
+ self.openFont(font,overlay)
+ elif isinstance(font,Mix):
+ self.font = font
+ else:
+ self.font = font
+ self.ifont = ifont
+ self.ffont = FFont(font)
+ if isinstance(v,float) or isinstance(v,int):
+ self.v = Point(v,v)
+ else:
+ self.v = v
+ if kernlist != None:
+ kerns = [i.strip().split() for i in open(kernlist).readlines()]
+
+ self.kernlist = [{'left':k[0], 'right':k[1], 'value': k[2]}
+ for k in kerns
+ if not k[0].startswith("#")
+ and not k[0] == ""]
+ #TODO implement class based kerning / external kerning file
+
+ def openFont(self, path, overlayPath=None):
+ fl.Open(path,True)
+ self.ifont = fl.ifont
+ self.font = fl.font
+ if overlayPath != None:
+ fl.Open(overlayPath,True)
+ ifont = self.ifont
+ font = self.font
+ overlayIfont = fl.ifont
+ overlayFont = fl.font
+
+ for overlayGlyph in overlayFont.glyphs:
+ glyphIndex = font.FindGlyph(overlayGlyph.name)
+ if glyphIndex != -1:
+ oldGlyph = Glyph(font.glyphs[glyphIndex])
+ kernlist = [KerningPair(k) for k in oldGlyph.kerning]
+ font.glyphs[glyphIndex] = Glyph(overlayGlyph)
+ font.glyphs[glyphIndex].kerning = kernlist
+ else:
+ font.glyphs.append(overlayGlyph)
+ fl.UpdateFont(ifont)
+ fl.Close(overlayIfont)
+ self.ffont = FFont(self.font)
+
+
+class Mix:
+ def __init__(self,masters,v):
+ self.masters = masters
+ if isinstance(v,float) or isinstance(v,int):
+ self.v = Point(v,v)
+ else:
+ self.v = v
+
+ def getFGlyph(self, master, gname):
+ if isinstance(master.font, Mix):
+ return font.mixGlyphs(gname)
+ return master.ffont.getGlyph(gname)
+
+ def getGlyphMasters(self,gname):
+ masters = self.masters
+ if len(masters) <= 2:
+ return self.getFGlyph(masters[0], gname), self.getFGlyph(masters[-1], gname)
+
+ def generateFFont(self):
+ ffont = FFont(self.masters[0].ffont)
+ for key,g in ffont.glyphs.iteritems():
+ ffont.glyphs[key] = self.mixGlyphs(key)
+ return ffont
+
+ def generateFont(self, baseFont):
+ newFont = Font(baseFont)
+ #self.mixStems(newFont) todo _ fix stems code
+ for g in newFont.glyphs:
+ gF = self.mixGlyphs(g.name)
+ if gF == None:
+ g.mark = True
+ else:
+ # FIX THIS #print gF.name, g.name, len(gF.nodes),len(g.nodes),len(gF.components),len(g.components)
+ try:
+ gF.copyToGlyph(g)
+ except:
+ "Nodes incompatible"
+ return newFont
+
+ def mixGlyphs(self,gname):
+ gA,gB = self.getGlyphMasters(gname)
+ try:
+ return gA.interp(gB,self.v)
+ except:
+ print "mixglyph failed for %s" %(gname)
+ if gA != None:
+ return gA.copy()
+
+def narrowFLGlyph(g, gThin, factor=.75):
+ gF = FGlyph(g)
+ if not isinstance(gThin,FGlyph):
+ gThin = FGlyph(gThin)
+ gCondensed = gThin.scaleX(factor)
+ try:
+ gNarrow = gF + (gCondensed - gThin)
+ gNarrow.copyToGlyph(g)
+ except:
+ print "No dice for: " + g.name
+
+def interpolate(a,b,v,e=0):
+ if e == 0:
+ return a+(b-a)*v
+ qe = (b-a)*v*v*v + a #cubic easing
+ le = a+(b-a)*v # linear easing
+ return le + (qe-le) * e
+
+def interpolateKerns(gA,gB,v):
+ kerns = []
+ for kA in gA.kerning:
+ key = kA.key
+ matchedKern = None
+ for kB in gA.kerning:
+ if key == kB.key:
+ matchedKern = kB
+ break
+ # if matchedkern == None:
+ # matchedkern = Kern(kA)
+ # matchedkern.value = 0
+ if matchedKern != None:
+ kernValue = interpolate(kA.value, matchedKern.value, v.x)
+ kerns.append(KerningPair(kA.key,kernValue))
+ return kerns \ No newline at end of file
diff --git a/src/v2/Roboto_Bold.vfb b/src/v2/Roboto_Bold.vfb
new file mode 100644
index 0000000..da32f52
--- /dev/null
+++ b/src/v2/Roboto_Bold.vfb
Binary files differ
diff --git a/src/v2/Roboto_Regular.vfb b/src/v2/Roboto_Regular.vfb
new file mode 100644
index 0000000..1d412af
--- /dev/null
+++ b/src/v2/Roboto_Regular.vfb
Binary files differ
diff --git a/src/v2/Roboto_Thin.vfb b/src/v2/Roboto_Thin.vfb
new file mode 100644
index 0000000..f7cb560
--- /dev/null
+++ b/src/v2/Roboto_Thin.vfb
Binary files differ