diff --git a/demo/fw/tools/create_age_email.py b/demo/fw/tools/create_age_email.py
new file mode 100644
index 0000000..7f448da
--- /dev/null
+++ b/demo/fw/tools/create_age_email.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+
+import os, sys
+from pathlib import Path
+import math
+import subprocess
+from email.message import EmailMessage
+from email.utils import make_msgid
+import textwrap
+import io
+import binascii
+
+import click
+import bdfparser
+from PIL import Image
+import numpy as np
+
+import tb_data_encoder
+
+
+def age_encrypt(content, keys):
+ age_binary = os.environ.get('AGE', 'age')
+ proc = subprocess.run([age_binary, '-e', *(elem for key in keys for elem in ('-r', key))],
+ check=True,
+ capture_output=True,
+ text=False,
+ input=content.encode('utf-8'))
+ return proc.stdout
+
+def pack_html(payload_img_cid, hint_img_cid):
+ return textwrap.dedent(f'''\
+
+
+
+
+
+
+
+ ''').strip()
+
+def create_hint_img(text, max_w=200, fgcolor='#e1e31b', bgcolor='#d61ca1'):
+ relative_path = (Path(__file__).parent / '../../../upstream/terminus-font-4.49.1/ter-u16b.bdf').resolve()
+ font_file = os.getenv('HINT_FONT', str(relative_path))
+ font = bdfparser.Font(font_file)
+ hex_to_np = lambda s: np.frombuffer(binascii.unhexlify(s.lstrip('#')), dtype=np.uint8)
+ colors = np.array([hex_to_np(bgcolor), hex_to_np(fgcolor)])
+
+ img = font.draw(text, linelimit=max_w)
+ return Image.fromarray(colors[img.todata(2)])
+
+# because inexplicably, pillow doesn't have this already
+def img_to_bytes(img, fmt='png'):
+ bio = io.BytesIO()
+ img.save(bio, fmt)
+ return bio.getvalue()
+
+
+@click.command()
+@click.option('-f', '--from', 'sender')
+@click.option('-t', '--to', 'recipients')
+@click.option('-c', '--cc', 'cc')
+@click.option('-b', '--bcc', 'bcc')
+@click.option('-k', '--key', 'keys', multiple=True, required=True)
+@click.option('-s', '--subject', default='tachibana/age encrypted message')
+@click.argument('content', type=click.File('r'))
+def create_age_email(content, sender, recipients, cc, bcc, keys, subject):
+
+ encrypted = age_encrypt(content.read(), keys)
+
+ msg = EmailMessage()
+ msg['Subject'] = subject
+ if sender:
+ msg['From'] = sender
+ if recipients:
+ msg['To'] = ', '.join(recipients)
+ if cc:
+ msg['Cc'] = ', '.join(cc)
+ if bcc:
+ msg['Bcc'] = ', '.join(bcc)
+
+ payload_img_cid, hint_img_cid = make_msgid(), make_msgid()
+ # remove <...> angle brackets from cids in
tags
+ msg.set_content(pack_html(payload_img_cid[1:-1], hint_img_cid[1:-1]), subtype='html')
+
+ w = max(200, math.ceil(math.sqrt(len(encrypted))))
+ h = math.ceil((len(encrypted) + 64) / w)
+ img_bytes = img_to_bytes(tb_data_encoder.data_encode(encrypted, w, h))
+ msg.add_related(img_bytes, 'image', 'png', cid=payload_img_cid)
+
+ img_bytes = img_to_bytes(create_hint_img('tachibana/age\nencrypted email', max_w=200))
+ msg.add_related(img_bytes, 'image', 'png', cid=hint_img_cid)
+
+ sys.stdout.buffer.write(bytes(msg))
+
+
+if __name__ == '__main__':
+ #print(pack_html('foobar'))
+ create_age_email()
diff --git a/demo/fw/tools/tb_data_encoder.py b/demo/fw/tools/tb_data_encoder.py
index 0390837..454aef7 100644
--- a/demo/fw/tools/tb_data_encoder.py
+++ b/demo/fw/tools/tb_data_encoder.py
@@ -21,13 +21,7 @@ WINDOW_MAGIC = np.array([
def tb_int_to_px(val):
return np.array([(val//0x10000)&0xff, (val//0x100)&0xff, val&0xff], dtype=np.uint8)
-@click.command()
-@click.option('-w', '--width', type=int, default=400)
-@click.option('-h', '--height', type=int, default=300)
-@click.argument('input_file', type=click.File('rb'))
-@click.argument('output_file', type=click.Path(dir_okay=False, writable=True))
-def tb_data_encode(width, height, input_file, output_file):
- data = input_file.read()
+def data_encode(data, width, height):
data = struct.pack('>I', len(data)) + data
data = np.frombuffer(data, dtype=np.uint8)
@@ -46,9 +40,18 @@ def tb_data_encode(width, height, input_file, output_file):
win[0, 10] = tb_int_to_px(width)
win[0, 11] = tb_int_to_px(height)
- print('out:', ' '.join(f'{x:02x}' for x in win.flatten()[:256]))
+ #print('out:', ' '.join(f'{x:02x}' for x in win.flatten()[:256]))
+ return Image.fromarray(win)
- Image.fromarray(win).save(output_file)
+@click.command()
+@click.option('-w', '--width', type=int, default=400)
+@click.option('-h', '--height', type=int, default=300)
+@click.argument('input_file', type=click.File('rb'))
+@click.argument('output_file', type=click.Path(dir_okay=False, writable=True))
+def tb_data_encode(width, height, input_file, output_file):
+ data = input_file.read()
+ img = data_encode(data, width, height)
+ img.save(output_file)
if __name__ == '__main__':
tb_data_encode()
diff --git a/demo/fpga/terminus-font-4.49.1/ter-u16b.bdf b/upstream/terminus-font-4.49.1/ter-u16b.bdf
similarity index 100%
rename from demo/fpga/terminus-font-4.49.1/ter-u16b.bdf
rename to upstream/terminus-font-4.49.1/ter-u16b.bdf
diff --git a/demo/fpga/terminus-font-4.49.1/ter-u16n.bdf b/upstream/terminus-font-4.49.1/ter-u16n.bdf
similarity index 100%
rename from demo/fpga/terminus-font-4.49.1/ter-u16n.bdf
rename to upstream/terminus-font-4.49.1/ter-u16n.bdf