diff options
author | Behdad Esfahbod <behdad@behdad.org> | 2016-05-10 07:27:55 +0200 |
---|---|---|
committer | Behdad Esfahbod <behdad@behdad.org> | 2016-05-10 07:27:55 +0200 |
commit | e78d7545be885a0d3b82f27ede8019818699f7ee (patch) | |
tree | e4a4d886dc296302f6c6a358e18fdf4696176b1b | |
parent | 1ce04a6baee328a720579f4a4c642bce1e26f914 (diff) |
Start adding opentype-gx presentation
-rw-r--r-- | opentype-gx/Makefile | 13 | ||||
-rwxr-xr-x | opentype-gx/opentypegx_slides.py | 464 | ||||
-rw-r--r-- | opentype-gx/opentypegx_theme.py | 142 | ||||
l--------- | opentype-gx/pangopygments.py | 1 | ||||
l--------- | opentype-gx/slippy.py | 1 |
5 files changed, 621 insertions, 0 deletions
diff --git a/opentype-gx/Makefile b/opentype-gx/Makefile new file mode 100644 index 0000000..535070a --- /dev/null +++ b/opentype-gx/Makefile @@ -0,0 +1,13 @@ +name = opentypegx +ARGS = --geometry 1920x1200 + +all: $(name)_slides.pdf + +view: + python slippy.py $(name)_slides.py -t $(name)_theme.py $(ARGS) + +%_slides.pdf: slippy.py %_slides.py %_theme.py + python slippy.py $*_slides.py -t $*_theme.py -o $@ $(ARGS) + +clean: + $(RM) $(name)_slides.pdf *.pyc diff --git a/opentype-gx/opentypegx_slides.py b/opentype-gx/opentypegx_slides.py new file mode 100755 index 0000000..4a10aeb --- /dev/null +++ b/opentype-gx/opentypegx_slides.py @@ -0,0 +1,464 @@ +#!/usr/bin/python +# -*- coding:utf8 -*- + +# Copyright 2016 Behdad Esfahbod <behdad@google.com> + +# A slides file should populate the variable slides with +# a list of tuples. Each tuple should have: +# +# - Slide content +# - User data +# - Canvas width +# - Canvas height +# +# Slide content can be a string, a list of strings, +# a function returning one of those, or a generator +# yielding strings. The user data should be a dictionary or +# None, and is both used to communicate options to the +# renderer and to pass extra options to the theme functions. +# +# A function-based slide content will be passed a renderer object. +# Renderer is an object similar to a cairo.Context and +# pangocairo.CairoContext but has its own methods too. +# The more useful of them here are put_text, put_image, and +# set_allocation. See their pydocs. + +title_font="Impact" +head_font="Bold" +body_font="Roboto" + +slides = [] +def slide_add(f, data=None, width=1920, height=1200): + if data is None: + data = {} + if "desc" not in data: + data['desc'] = body_font + slides.append ((f, data, width, height)) + return f + +import pango, pangocairo, cairo, os, signal +from pangopygments import highlight + +# We use slide data to tell the theme who's speaking. +# That is, which side the bubble should point to. +behdad = -1 +whois = 0 +def who(name): + global whois + whois = name + +# And convenience functions to add a slide. Can be +# used as a function decorator, or called directly. +def slide_who(f, who, data=None): + if data: + data = dict (data) + else: + data = {} + data['who'] = who + return slide_add (f, data) + +def slide(f, data=None): + return slide_who (f, whois, data=data) + +def slide_heading(f, data=None): + if data is None: + data = {} + if 'desc' not in data: + data['desc'] = head_font + if data and 'who' in data: + return slide_who (f, data['who'], data=data) + else: + return slide_who (f, 0, data=data) + +def bullet_list_slide (title, items): + def s (r): + ts = "<span font_desc='"+head_font+"'>"+title+"</span>\n" + return ts + '\n'.join ("• " + item for item in items) + s.__name__ = title + data={'desc': body_font, 'align': pango.ALIGN_LEFT} + slide (s, data) + +def image_slide (f, width=600, height=600, imgwidth=0, imgheight=0, xoffset=0, yoffset=0, data=None): + def s (r): + r.move_to (400+xoffset, 300+yoffset) + r.put_image (f, width=imgwidth, height=imgheight) + x = (800 - width) * .5 + y = (600 - height) * .5 + r.set_allocation (x, y, width, height) + s.__name__ = f + slide (s, data) + return s + +def draw_image (r, f, width=600, height=600, imgwidth=0, imgheight=0, xoffset=0, yoffset=0, data=None): + r.move_to (400+xoffset, 300+yoffset) + r.put_image (f, width=imgwidth, height=imgheight) + x = (800 - width) * .5 + y = (600 - height) * .5 + r.set_allocation (x, y, width, height) + +def source_slide(s, lang): + s = highlight(s, lang) + s = "<span font_desc='monospace'>" + s + "</span>" + slide_heading (s, data={'align': pango.ALIGN_LEFT}) + +def python_slide(s): + source_slide(s, lang="py") + +def xml_slide(s): + source_slide(s, lang="xml") + +def patch_slide(s): + lines = s.split ("\n") + new_lines = [] + for s in lines: + s = s.replace("&", "&").replace("<", "<") + if not s: s = ' ' + if s[0] == '-': + s = "<span fgcolor='#d00'>%s</span>" % s + elif s[0] == '+': + s = "<span fgcolor='#0a0'>%s</span>" % s + elif s[0] != ' ': + s = "<b>%s</b>" % s + + new_lines.append (s) + + s = '\n'.join (new_lines) + s = "<span font_desc='monospace'>" + s + "</span>" + slide_heading (s, data={'align': pango.ALIGN_LEFT}) + +def commit_slide(s, who=None): + lines = s.split ("\n") + new_lines = [] + for s in lines: + s = s.replace("&", "&").replace("<", "<") + if not s: s = ' ' + if s[0] != ' ': + s = "<b>%s</b>" % s + + new_lines.append (s) + + s = '\n'.join (new_lines) + s = "<span font_desc='monospace'>" + s + "</span>" + if who: + slide_heading (s, data={'align': pango.ALIGN_LEFT, 'who': who}) + else: + slide_heading (s, data={'align': pango.ALIGN_LEFT}) + + +# +# Slides start here +# + +who (behdad) + +@slide +def title_slide (r): + r.move_to (400, 250) + r.put_text ("<span font_desc='"+title_font+"'>FontTools</span>\n"+ + "<span font_desc='"+head_font+" 30'>"+ + "reviving an Open Source project,\n"+ + "<span strikethrough='true'>re</span>building a thriving community"+ + "</span>", valign=0, halign=0, desc=title_font+" 100") + + r.move_to (400, 550) + r.put_text ("""Behdad Esfahbod""", desc=body_font+" 20", halign=0, valign=-1) + +bullet_list_slide("History", [ + "Started in 1999ish", + "by Just van Rossum", + "of LettError fame", + "Slowed down by 2004", + "I picked it up last year"]) +bullet_list_slide("What is it?", [ + "Two things: TTX and fontTools", + "TTX converts fonts to XML and back", + "fontTools is a Python library", + "Font engineer's calculator", + "Closely reflecting OpenType tables", + "Minimum abstractions"]) + +slide_heading("TTX") +xml_slide(""" +<?xml version="1.0" encoding="utf-8"?> +<ttFont sfntVersion="\\x00\\x01\\x00\\x00" ttLibVersion="2.5"> + + <GlyphOrder> + <!-- The 'id' attribute is only for humans; it is ignored when parsed. --> + <GlyphID id="0" name=".notdef"/> + <GlyphID id="1" name="T"/> + <GlyphID id="2" name="X"/> + </GlyphOrder> + + <head> + <!-- Most of this table will be recalculated by the compiler --> + <tableVersion value="1.0"/> + <fontRevision value="1.001"/> + <checkSumAdjustment value="0x22ff1f02"/> + <magicNumber value="0x5f0f3cf5"/> + <flags value="00000000 00001011"/> + <unitsPerEm value="1000"/> + ... + </head> + + <hhea> + <tableVersion value="1.0"/> + <ascent value="863"/> + <descent value="-228"/> + <lineGap value="0"/> + ... + </hhea> +""") +xml_slide(""" + <maxp> + <!-- Most of this table will be recalculated by the compiler --> + <tableVersion value="0x10000"/> + <numGlyphs value="3"/> + <maxPoints value="68"/> + <maxContours value="7"/> + ... + </maxp> + + <OS_2> + <version value="3"/> + <xAvgCharWidth value="560"/> + <usWeightClass value="400"/> + <usWidthClass value="5"/> + <fsType value="00000000 00000000"/> + ... + </OS_2> +""") +xml_slide(""" + <hmtx> + <mtx name=".notdef" width="260" lsb="0"/> + <mtx name="T" width="576" lsb="22"/> + <mtx name="X" width="584" lsb="26"/> + </hmtx> + + <cmap> + <tableVersion version="0"/> + <cmap_format_4 platformID="3" platEncID="1" language="0"> + <map code="0x54" name="T"/><!-- LATIN CAPITAL LETTER T --> + <map code="0x58" name="X"/><!-- LATIN CAPITAL LETTER X --> + </cmap_format_4> + </cmap> + + <loca> + <!-- The 'loca' table will be calculated by the compiler --> + </loca> +""") +xml_slide(""" + <glyf> + + <!-- The xMin, yMin, xMax and yMax values + will be recalculated by the compiler. --> + + <TTGlyph name=".notdef"/><!-- contains no outline data --> + + <TTGlyph name="T" xMin="22" yMin="0" xMax="554" yMax="715"> + <contour> + <pt x="264" y="673" on="1"/> + <pt x="22" y="673" on="1"/> + <pt x="22" y="715" on="1"/> + <pt x="554" y="715" on="1"/> + <pt x="554" y="673" on="1"/> + <pt x="312" y="673" on="1"/> + <pt x="312" y="0" on="1"/> + <pt x="264" y="0" on="1"/> + </contour> + <instructions><assembly> + </assembly></instructions> + </TTGlyph> + + <TTGlyph name="X" xMin="26" yMin="0" xMax="558" yMax="715"> + ... + </TTGlyph> + + </glyf> +""") +xml_slide(""" + <name> + <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409"> + Julius Sans One + </namerecord> + <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409"> + Regular + </namerecord> + </name> + + <post> + <formatType value="3.0"/> + <italicAngle value="0.0"/> + <underlinePosition value="-75"/> + <underlineThickness value="50"/> + <isFixedPitch value="0"/> + <minMemType42 value="0"/> + <maxMemType42 value="0"/> + <minMemType1 value="0"/> + <maxMemType1 value="0"/> + </post> + +</ttFont> +""") + +slide_heading("fontTools") +bullet_list_slide("<span font_desc='monospace' foreground='#080'>from <span foreground='#00f'><b>fontTools</b></span> import</span>", [ + "afmLib", + "cffLib", + "<span strikethrough='true'>fondLib</span>", + "<span strikethrough='true'>nfntLib</span>", + "t1Lib", + "<b>ttLib</b>", + ]) +python_slide(open("snippets/unencoded.py").read()) +python_slide(""" +set(['A.salt', + 'B.salt', + 'E.salt', + 'E_x', + 'F_i', + 'N.salt', + 'NULL', + 'T_h', + 'T_i', + 'a.end', + ... + 'y_z']) +""") + +bullet_list_slide("Framework", [ + "Compiler / decompiler", + "XML serialization", + "Optimizer", + "Library", + "A tool to build products with", + "Not an end product", +]) +bullet_list_slide("Philosophy", [ + "Minimal abstraction", + "Hide byte layout, redundancy, etc", + "Platform to build on", +]) +bullet_list_slide("Why?", [ + "Free Software", + "Python (2.7+ & 3.x)", + "Portable", + "No dependencies", + "Non-destructive", + "Batch-friendly", +]) +bullet_list_slide("What for?", [ + "Figuring out what's inside a font", + "Diff'ing font versions", + "Final steps of producing fonts", + "Hotfixing existing fonts", + "Reporting and analysis over <i>many</i> fonts", + "Server-side reporting and manipulation", + "Readable VCS-friendly font source code", + "Subsetting and merging fonts", +]) +bullet_list_slide("Hotfixing", [ + "XML and back", + "Python recipes", + "Interactive Python", +]) +python_slide(open("snippets/drop_glyphnames.py").read()) +python_slide(open("snippets/rename_glyphs.py").read()) + +bullet_list_slide("New work", [ + "Bitmap tables, color tables, misc tables", + "WOFF1 read and write", + "More stable XML format", + "More optimized font files", + "Bug fixes; many of them", + "Major speed-up", + "New tools", +]) + +bullet_list_slide("Tools", [ + "pyftsubset", + "pyftmerge", + "pyftinspect", +]) +bullet_list_slide("pyftsubset", [ + "OpenType font subsetter & optimizer", + "TrueType and CFF flavored", + "SFNT and WOFF1", + "Full OpenType Layout support", + "File-size optimizations", + "Designed for webfont productions", + "Used for sub-family instantiation", +]) +source_slide(open("snippets/subsetting.txt").read(), "text") +bullet_list_slide("pyftmerge", [ + "Font merging tool", + "Very early prototype", + "Fonts having same format, upem", + "TrueType-flavored only", + "Retains hinting from one font", + "Handles conflicting characters", + "Designed for Noto", + "Used for merging Latin and non-Latin", +]) +source_slide(open("snippets/merging.txt").read(), "text") + +# 1. Feel free to mention in your talk Yannis Haralambous' book "Fonts & Encodings" [1], simply because it can be seen as a "user manual for TTX". It talks extensively about the various SFNT tables, and illustrates them using TTX XML structures. So, while fontTools itself has very lacking documentation*, the book is actually a great companion. When I read the book and I look at the .ttx XML representation of a font, I have good chances to actually understand how it all works. + +slide_heading("FontTools") +bullet_list_slide("Strengths", [ + "Supports wide array of tables", + "Low-level, non-destructive", + "Python", + "Self-documenting", + "Optimized output", + "Extensible", + "Supports all color tables", +]) +bullet_list_slide("Weaknesses", [ + "Supports wide array of tables", + "Low-level, non-destructive", + "Python", + "Undocumented", + "Always optimizes output", + "Not well-extensible", + "Supports many legacy tables", +]) +bullet_list_slide("Users", [ + "Google Fonts; misc foundries", + "Font Bakery, AFDKO, etc", + "Adam Twardoch; others", + "...", +]) +bullet_list_slide("Community", [ + "https://github.com/behdad/fonttools/", + "https://groups.google.com/forum/#!forum/fonttools", + "http://sourceforge.net/projects/fonttools/", +]) + +bullet_list_slide("Future work", [ + "More font optimization", + "Better input: .fea / UFO", + "ttx2ufo", + "Lint tool", + "TrueType / CFF conversion", + "Subsetting more tables", + "Better merging", +]) +bullet_list_slide("Future work: optimization", [ + "Optimal packing: glyf flags / PUSH / etc", + "CFF outline operation specializer", + "CFF subroutinizer", + "TrueType outline optimizer? (fontcrunch)", + "TrueType bytecode analysis", + "UPEM reduction", +]) + +slide("<b>Q?</b>", data={"desc":title_font}) +python_slide('') + +python_slide(open("snippets/drop_glyphnames_cff.py").read()) +xml_slide(open("snippets/tofu.ttx").read()) + +if __name__ == "__main__": + import slippy + import opentypegx_theme + slippy.main (slides, opentypegx_theme, args=['--geometry', '1920x1200']) diff --git a/opentype-gx/opentypegx_theme.py b/opentype-gx/opentypegx_theme.py new file mode 100644 index 0000000..2198ee5 --- /dev/null +++ b/opentype-gx/opentypegx_theme.py @@ -0,0 +1,142 @@ +# vim: set fileencoding=utf-8 : +# Written by Behdad Esfahbod, 2014 +# Not copyrighted, in public domain. + +# A theme file should define two functions: +# +# - prepare_page(renderer): should draw any background and return a tuple of +# x,y,w,h that is the area to use for slide canvas. +# +# - draw_bubble(renderer, x, y, w, h, data=None): should setup canvas for the +# slide to run. Can draw a speaking-bubble for example. x,y,w,h is the +# actual extents that the slide will consume. Data will be the user-data +# dictionary from the slide. +# +# Renderer is an object similar to a cairo.Context and pangocairo.CairoContext +# but has its own methods too. The more useful of them here are put_text and +# put_image. See their pydocs. + +import cairo + +avatar_margin = .10 +logo_margin = .01 +footer_margin = .05 +padding = .000 +bubble_rad = .25 + +def bubble (cr, x0, y0, x, y, w, h): + + r = min (w, h) * (bubble_rad / (1 - 2./8*bubble_rad)) + + p = r / 7. + x, y, w, h, r = x - p, y - p, w + 2*p, h + 2*p, r + p + + x1, y1, x2, y2 = x, y, x + w, y + h + + cr.move_to (x1+r, y1) + cr.line_to (x2-r, y1) + cr.curve_to (x2, y1, x2, y1, x2, y1+r) + cr.line_to (x2, y2-r) + cr.curve_to (x2, y2, x2, y2, x2-r, y2) + cr.line_to (x1+r, y2) + cr.curve_to (x1, y2, x1, y2, x1, y2-r) + cr.line_to (x1, y1+r) + cr.curve_to (x1, y1, x1, y1, x1+r, y1) + cr.close_path () + + xc, yc = .5 * (x1 + x2), .5 * (y1 + y2) + cr.move_to (xc+r, yc) + cr.curve_to (xc+r, y0, .5 * (xc+r+x0), (yc+y0*2)/3, x0, y0) + cr.curve_to (.5 * (xc-r+x0), (yc+y0*2)/3, xc-r, y0, xc-r, yc) + + +def prepare_page (renderer): + cr = renderer.cr + width = renderer.width + height = renderer.height + + a = avatar_margin * width + s = (logo_margin + footer_margin) * .5 * width + l = logo_margin * height + f = footer_margin * height + p = padding * min (width, height) + p2 = 2 * p + + cr.paint () + sky = cairo.LinearGradient (0, 0, 0, height) + sky.add_color_stop_rgba (0, 0x02/255., 0x23/255., 0x80/255., 1.) + sky.add_color_stop_rgba (1, 0x9b/255., 0x84/255., 0x66/255., 1.) + cr.set_source (sky) + cr.paint () + + # Background image + #cr.move_to (width / 2., height / 2.) + #renderer.put_image ("pillars.jpg", height = height, width = width) + + cr.move_to (.5 * width, height-p2) + b = 150. + cr.set_source_rgb (0x9b/b, 0x84/b, 0x66/b) + renderer.put_text ("TYPO Labs, 10 & 11 May, 2016, Berlin", height=f-p2, valign=-1, desc="") + + # Cartoon icons for speakers + who = renderer.data.get ('who', None) + if who: + if who < 0: + cr.move_to (p, height-p) + renderer.put_image ("behdad.png", width = a-p2, valign=-1, halign=+1) + else: + cr.move_to (width-p, height-p) + renderer.put_image (who, width = a-p2, valign=-1, halign=-1) + + # Compute rectangle available for slide content + w = width - s - s - p * 2 + x = s + p + h = height - l - f - p * 2 + y = l + p + + # Adjust for bubble padding. the 8 comes from bezier calculations + d = min (w, h) * bubble_rad / 8. + x, y, w, h = x + d, y + d, w - d*2, h - d*2 + + return x, y, w, h + +def draw_bubble (renderer, x, y, w, h, data={}): + # Fancy speech bubble! + cr = renderer.cr + width = renderer.width + height = renderer.height + + s = avatar_margin * width + p = padding * min (width, height) + + cr.save() + x, y = cr.user_to_device (x, y) + w, h = cr.user_to_device_distance (w, h) + cr.identity_matrix () + + who = data.get ('who', None) + if not who: + xc, yc = x + w*.5, y + h*.5 + elif who < 0: + xc, yc = s * .9, height - .7 * s + else: + xc, yc = width - s * .9, height - .7 * s + + bubble (cr, xc, yc, x, y, w, h) + cr.rectangle (width, 0, -width, height) + cr.clip () + + bubble (cr, xc, yc, x, y, w, h) + #cr.set_source_rgb (0, 0, 0) + #cr.set_line_width (p) + #cr.set_miter_limit (20) + #cr.stroke_preserve () + + cr.restore () + + cr.clip () + cr.set_source_rgba (1, 1, 1, .94) + cr.paint () + + b = 700. + cr.set_source_rgb (0xbe/b, 0xa3/b, 0x89/b) diff --git a/opentype-gx/pangopygments.py b/opentype-gx/pangopygments.py new file mode 120000 index 0000000..3a5bcdd --- /dev/null +++ b/opentype-gx/pangopygments.py @@ -0,0 +1 @@ +../pangopygments.py
\ No newline at end of file diff --git a/opentype-gx/slippy.py b/opentype-gx/slippy.py new file mode 120000 index 0000000..8eb5363 --- /dev/null +++ b/opentype-gx/slippy.py @@ -0,0 +1 @@ +../slippy.py
\ No newline at end of file |