From f1e969402ea4a1dadba6c4c72028dc76cc6daa38 Mon Sep 17 00:00:00 2001 From: Lars-Dominik Braun Date: Sat, 2 Nov 2019 09:36:34 +0100 Subject: render: Improve generated SVG compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch to proper SVG user coordinate rendering instead of font-dependent sizes. Embed WOFF2 fonts, so images have proper fonts when embedded. Remove font blobs and submodule IBM’s plex git repo. --- .gitmodules | 3 + 3rdparty/plex | 1 + doc/fonts/IBMPlexArabic-Regular.woff2 | Bin 71160 -> 0 bytes doc/fonts/IBMPlexArabic-Thin.woff2 | Bin 72636 -> 0 bytes doc/style.css | 16 ++--- gen.sh | 8 ++- lulua/data/render-svg.css | 10 ++-- lulua/render.py | 109 ++++++++++++++++++++-------------- 8 files changed, 89 insertions(+), 58 deletions(-) create mode 160000 3rdparty/plex delete mode 100644 doc/fonts/IBMPlexArabic-Regular.woff2 delete mode 100644 doc/fonts/IBMPlexArabic-Thin.woff2 diff --git a/.gitmodules b/.gitmodules index 9ac1fdb..7a73284 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "3rdparty/wikiextractor"] path = 3rdparty/wikiextractor url = https://github.com/attardi/wikiextractor.git +[submodule "3rdparty/plex"] + path = 3rdparty/plex + url = https://github.com/IBM/plex.git diff --git a/3rdparty/plex b/3rdparty/plex new file mode 160000 index 0000000..f51d072 --- /dev/null +++ b/3rdparty/plex @@ -0,0 +1 @@ +Subproject commit f51d07200ae54caed11da4de140142ed61af1be3 diff --git a/doc/fonts/IBMPlexArabic-Regular.woff2 b/doc/fonts/IBMPlexArabic-Regular.woff2 deleted file mode 100644 index bc5e155..0000000 Binary files a/doc/fonts/IBMPlexArabic-Regular.woff2 and /dev/null differ diff --git a/doc/fonts/IBMPlexArabic-Thin.woff2 b/doc/fonts/IBMPlexArabic-Thin.woff2 deleted file mode 100644 index 685bd03..0000000 Binary files a/doc/fonts/IBMPlexArabic-Thin.woff2 and /dev/null differ diff --git a/doc/style.css b/doc/style.css index 6bddc80..4f9d63f 100644 --- a/doc/style.css +++ b/doc/style.css @@ -11,17 +11,17 @@ using http://colormind.io/bootstrap/ } @font-face { - font-family: 'IBM Plex Arabic'; - font-style: normal; - font-weight: 100; - src: local('IBM Plex Arabic Thin'), local('IBM Plex Arabic-Thin'), url('fonts/IBMPlexArabic-Thin.woff2') format('woff2'); + font-family: 'IBM Plex Arabic'; + font-style: normal; + font-weight: 100; + src: local('IBM Plex Arabic Thin'), local('IBMPlexArabic-Thin'), url('fonts/IBMPlexArabic-Thin.woff2') format('woff2'); } @font-face { - font-family: 'IBM Plex Arabic'; - font-style: normal; - font-weight: 400; - src: local('IBM Plex Arabic Regular'), local('IBM Plex Arabic-Regular'), url('fonts/IBMPlexArabic-Regular.woff2') format('woff2'); + font-family: 'IBM Plex Arabic'; + font-style: normal; + font-weight: 400; + src: local('IBM Plex Arabic Regular'), local('IBMPlexArabic-Regular'), url('fonts/IBMPlexArabic-Regular.woff2') format('woff2'); } body { diff --git a/gen.sh b/gen.sh index 6e984c3..df59e57 100755 --- a/gen.sh +++ b/gen.sh @@ -12,6 +12,7 @@ corpusdir=corpus statsdir=stats docdir=doc wikiextractor=3rdparty/wikiextractor/WikiExtractor.py +fontdir=3rdparty/plex/IBM-Plex-Arabic/fonts/complete/woff2/ optrounds=100000 # pin layers, keep hand-optimized numbers, keep top row free optpins=0;1;2;0,B*;3,* @@ -84,13 +85,14 @@ rule cp build \$docdir/_build: mkdir build \$docdir/_build/fonts: mkdir build \$docdir/_temp: mkdir - build \$docdir/_build/index.html: cpp \$docdir/index.html || \$docdir/_build build \$docdir/_build/letterfreq.json: letterfreq \$statsdir/ar-lulua/all.pickle || \$docdir/_build build \$docdir/_build/style.css: cp \$docdir/style.css || \$docdir/_build build \$docdir/_build/lulua-logo.svg: cp \$docdir/lulua-logo.svg || \$docdir/_build -build \$docdir/_build/fonts/IBMPlexArabic-Regular.woff2: cp \$docdir/fonts/IBMPlexArabic-Regular.woff2 || \$docdir/_build/fonts -build \$docdir/_build/fonts/IBMPlexArabic-Thin.woff2: cp \$docdir/fonts/IBMPlexArabic-Thin.woff2 || \$docdir/_build/fonts + + +build \$docdir/_build/fonts/IBMPlexArabic-Regular.woff2: cp \$fontdir/IBMPlexArabic-Regular.woff2 || \$docdir/_build/fonts +build \$docdir/_build/fonts/IBMPlexArabic-Thin.woff2: cp \$fontdir/IBMPlexArabic-Thin.woff2 || \$docdir/_build/fonts EOF for l in $layouts; do diff --git a/lulua/data/render-svg.css b/lulua/data/render-svg.css index 575d11c..e127625 100644 --- a/lulua/data/render-svg.css +++ b/lulua/data/render-svg.css @@ -1,6 +1,8 @@ -svg { +/* inkscape-compatibility, explicitly apply font settings to .label */ +svg, .button .label { font-family: "IBM Plex Arabic"; - font-size: 25pt; + font-size: 90px; + font-weight: 400; } .button.unused { opacity: 0.6; @@ -12,7 +14,7 @@ svg { } .button .label .layer-2, .button .label .layer-3, .button .label .layer-4 { font-size: 80%; - font-weight: 200; + font-weight: 100; } .button .label .controlchar { font-size: 40%; @@ -23,7 +25,7 @@ svg { } .button .cap.highlight { fill: #dc322f; /* red */ - filter: blur(0.5em); + filter: blur(50px); } .button.finger-little .cap.shadow { fill: #dc322f; /* red */ diff --git a/lulua/render.py b/lulua/render.py index 5834c89..acd15ab 100644 --- a/lulua/render.py +++ b/lulua/render.py @@ -18,13 +18,12 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import argparse, sys, logging, pkg_resources +import argparse, sys, logging, pkg_resources, base64 from collections import namedtuple, defaultdict from operator import attrgetter from datetime import datetime import svgwrite -from svgwrite import em import yaml from .layout import LITTLE, RING, MIDDLE, INDEX, THUMB, GenericLayout, defaultLayouts @@ -32,7 +31,7 @@ from .writer import Writer from .keyboard import defaultKeyboards from .util import first, displayText -RendererSettings = namedtuple ('RendererSetting', ['buttonMargin', 'middleGap', 'buttonWidth', 'rounded', 'shadowOffset']) +RendererSettings = namedtuple ('RendererSetting', ['buttonMargin', 'middleGap', 'buttonWidth', 'rounded', 'shadowOffset', 'markerStroke']) class Renderer: """ Keyboard to SVG renderer """ @@ -40,11 +39,12 @@ class Renderer: __slots__ = ('keyboard', 'layout', 'settings', 'cursor', 'writer', 'keyHighlight') defaultSettings = RendererSettings ( - buttonMargin=0.2, - middleGap=0.1, - buttonWidth=2, - rounded=0.1, - shadowOffset=0.05, + buttonMargin=20, + middleGap=10, + buttonWidth=200, + rounded=10, + shadowOffset=5, + markerStroke=7, ) def __init__ (self, keyboard, layout=None, writer=None, settings=None, keyHighlight=None): @@ -124,20 +124,20 @@ class Renderer: # background rect if any text if any (buttonText): b = svgwrite.shapes.Rect ( - insert=((xoff+settings.shadowOffset)*em, (yoff+settings.shadowOffset)*em), - size=(width*em, settings.buttonWidth*em), - rx=settings.rounded*em, - ry=settings.rounded*em, + insert=((xoff+settings.shadowOffset), (yoff+settings.shadowOffset)), + size=(width, settings.buttonWidth), + rx=settings.rounded, + ry=settings.rounded, class_='cap shadow') g.add (b) else: gclass.append ('unused') # main key rect b = svgwrite.shapes.Rect ( - insert=(xoff*em, yoff*em), - size=(width*em, settings.buttonWidth*em), - rx=settings.rounded*em, - ry=settings.rounded*em, + insert=(xoff, yoff), + size=(width, settings.buttonWidth), + rx=settings.rounded, + ry=settings.rounded, class_='cap') g.add (b) @@ -149,36 +149,36 @@ class Renderer: end = (xoff+width*0.7, yoff+settings.buttonWidth*0.9) # its shadow l = svgwrite.shapes.Line ( - map (lambda x: (x+settings.shadowOffset)*em, start), - map (lambda x: (x+settings.shadowOffset)*em, end), - stroke_width=0.07*em, + map (lambda x: (x+settings.shadowOffset), start), + map (lambda x: (x+settings.shadowOffset), end), + stroke_width=settings.markerStroke, class_='marker shadow') g.add (l) # the marker itself l = svgwrite.shapes.Line ( - map (em, start), - map (em, end), - stroke_width=0.07*em, + start, + end, + stroke_width=settings.markerStroke, class_='marker') g.add (l) # highlight rect highlight = self.keyHighlight.get (btn.name, 0) b = svgwrite.shapes.Rect ( - insert=(xoff*em, yoff*em), - size=(width*em, settings.buttonWidth*em), - rx=settings.rounded*em, - ry=settings.rounded*em, + insert=(xoff, yoff), + size=(width, settings.buttonWidth), + rx=settings.rounded, + ry=settings.rounded, class_='cap highlight', style=f'opacity: {highlight}') g.add (b) - # clock-wise from bottom-left to bottom-right + # clock-wise from bottom-left to bottom-right, offsets are relative to buttonWidth and button center textParam = [ - (-0.5, 0.6, 'layer-1'), - (-0.5, -1/3, 'layer-2'), - (0.5, -1/3, 'layer-3'), - (0.5, 2/3, 'layer-4'), + (-0.25, 0.3, 'layer-1'), + (-0.25, -0.15, 'layer-2'), + (0.25, -0.15, 'layer-3'), + (0.25, 0.3, 'layer-4'), ] # XXX: could probably def/use these for extraclass, morexoff, moreyoff in [(['shadow'], settings.shadowOffset, settings.shadowOffset), ([], 0, 0)]: @@ -187,12 +187,12 @@ class Renderer: for text, (txoff, tyoff, style) in zip (buttonText, textParam): if text is None: continue - txoff += morexoff - tyoff += moreyoff + txoff = txoff*settings.buttonWidth + morexoff + tyoff = tyoff*settings.buttonWidth + moreyoff # actual text must be inside tspan, so we can apply smaller font size # without affecting element position t = svgwrite.text.Text ('', - insert=((xoff+width/2+txoff)*em, (yoff+settings.buttonWidth/2+tyoff)*em), + insert=((xoff+width/2+txoff), (yoff+settings.buttonWidth/2+tyoff)), text_anchor='middle', class_=' '.join (class_)) if text.startswith ('[') and text.endswith (']'): @@ -201,14 +201,13 @@ class Renderer: class_='controlchar', direction='ltr')) g.add (svgwrite.shapes.Rect ( - insert=((xoff+width/2+txoff-0.4)*em, (yoff+settings.buttonWidth/2+tyoff-0.4)*em), - size=(0.8*em, 0.5*em), - stroke_width=0.05*em, - stroke_dasharray='5,3', + insert=((xoff+width/2+txoff-40), (yoff+settings.buttonWidth/2+tyoff-40)), + size=(80, 50), + stroke_width=2, + stroke_dasharray='15,8', class_=' '.join (controlclass_))) else: - RLI = "\u2067" # RIGHT-TO-LEFT ISOLATE (RLI) - t.add (svgwrite.text.TSpan (RLI + text, class_=style, direction='rtl')) + t.add (svgwrite.text.TSpan (text, class_=style, direction='rtl')) g.add (t) return g, width @@ -228,10 +227,34 @@ def renderSvg (args): r = Renderer (keyboard, layout=layout, writer=writer, keyHighlight=keyHeat) rendered, (w, h) = r.render () - d = svgwrite.Drawing(args.output, size=(w*em, h*em), profile='full') - d.defs.add (d.style (args.style.read ().decode ('utf-8'))) + d = svgwrite.Drawing(args.output, size=(w, h), profile='full') + + # using fonts via url() only works in stand-alone documents, not when + # embedding into a website + # see https://github.com/mozman/svgwrite/blob/master/examples/using_fonts.py#L36 + # which we cannot use since it does not support font-weight + style = '' + fonts = [ + ('IBM Plex Arabic', 100, '3rdparty/plex/IBM-Plex-Arabic/fonts/complete/woff2/IBMPlexArabic-Thin.woff2'), + ('IBM Plex Arabic', 400, '3rdparty/plex/IBM-Plex-Arabic/fonts/complete/woff2/IBMPlexArabic-Regular.woff2') + ] + for font, weight, path in fonts: + with open (path, 'rb') as fd: + data = base64.b64encode (fd.read ()).decode ('utf-8') + style += f""" + @font-face {{ + font-family: '{font}'; + font-style: normal; + font-weight: {weight}; + src: url("data:application/font-woff2;charset=utf-8;base64,{data}") format('woff2'); + }} + """ + + style += args.style.read ().decode ('utf-8') + d.defs.add (d.style (style)) + d.add (rendered) - d.save() + d.save (pretty=True) def renderXmodmap (args): keyboard = defaultKeyboards[args.keyboard] -- cgit v1.2.3