Some example templates as SVG for laser cutting
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
laser-cut-templates/visual-cryptography/generate-glyph-table.py

224 lines
7.3 KiB

#!/usr/bin/env python3
"""
Visual Cryptograpy Experiment.
Create two paper cards (credit card size) which reveal the plaintext message when put over each other.
"""
import argparse
import secrets
from HersheyFonts import HersheyFonts
import secrets
import svgwrite
from svgwrite import cm, mm
from bdfparser import Font
import sys
import string
import math
import mnemonic
import xml.etree.ElementTree as ET
STROKE_WIDTH = 0.2
CARD_WIDTH = 85.6
CARD_HEIGHT = 53.98
parser = argparse.ArgumentParser()
parser.add_argument("output")
parser.add_argument("--stroke-width", type=float, default=STROKE_WIDTH)
parser.add_argument("--header-text", default="3x3 Glyph Table Page {page}")
parser.add_argument("--header-mnemonic", action="store_true")
parser.add_argument("--card-width", type=float, default=CARD_WIDTH)
parser.add_argument("--card-height", type=float, default=CARD_HEIGHT)
parser.add_argument("--font", default="3x3.bdf")
args = parser.parse_args()
STROKE_WIDTH = args.stroke_width
data = {}
dwg = svgwrite.Drawing(
args.output, ("210mm", "297mm"), profile="tiny", viewBox="0 0 210 297"
)
card_offset = 5*9 #((args.card_height + 10) // 10) * 10
y = 0
thefont = HersheyFonts()
thefont.load_default_font("futural")
thefont.normalize_rendering(5)
# do not use a word twice
used_words = set()
for page in range(1, 4):
margin_left = 10
for i in range(2):
binstr = f"{page:02b}"
r = dwg.rect(
(2 + i * 4, y+5),
(3, 3),
stroke="black",
stroke_width=STROKE_WIDTH,
fill="none" if binstr[i] == "0" else "black",
)
dwg.add(r)
thefont.normalize_rendering(2)
offsetx = 0.5
offsety = 7.8
text = args.header_text
for stroke in thefont.strokes_for_text(text.format(page=page)):
for j, point in enumerate(stroke):
stroke[j] = (margin_left + offsetx + point[0], y + offsety - point[1])
pl = dwg.polyline(stroke, stroke="black", stroke_width=STROKE_WIDTH, fill="none")
dwg.add(pl)
w = 9
h = 9
if args.header_mnemonic:
words = set()
m = mnemonic.Mnemonic("english")
for word in m.wordlist:
if len(word) <= 5:
words.add(word.upper())
for i in range(8):
choice = secrets.choice(list(words - used_words))
used_words.add(choice)
minx = 9999
maxx = 0
for stroke in thefont.strokes_for_text(choice):
for j, point in enumerate(stroke):
minx = min(minx, point[0])
maxx = max(maxx, point[0])
width = (maxx - minx) + STROKE_WIDTH
for stroke in thefont.strokes_for_text(choice):
for j, point in enumerate(stroke):
stroke[j] = (margin_left + (i*w) + ((w-width)/2) + point[0], y + offsety - point[1])
pl = dwg.polyline(stroke, stroke="black", stroke_width=STROKE_WIDTH, fill="none")
dwg.add(pl)
font = Font(args.font)
for row in range(4):
for i in range(2):
binstr = f"{row:02b}"
r = dwg.rect(
(2 + (i*4), y + 4.25 + ((row+1)*h)),
(3, 3),
stroke="black",
stroke_width=STROKE_WIDTH,
fill="none" if binstr[i] == "0" else "black",
)
dwg.add(r)
words = set()
m = mnemonic.Mnemonic("english")
for word in m.wordlist:
if len(word) <= 4:
words.add(word.upper())
choice = secrets.choice(list(words - used_words))
used_words.add(choice)
for stroke in thefont.strokes_for_text(choice):
for j, point in enumerate(stroke):
stroke[j] = (2 + point[0], y + 3.75 + ((row+1)*h) - point[1])
pl = dwg.polyline(stroke, stroke="black", stroke_width=STROKE_WIDTH, fill="none")
dwg.add(pl)
for col in range(8):
char = chr((page * 32) + ((row) * 8) + col)
glyph = font.glyph(char)
pw = 1
ph = 1
offsetx = 1
offsety = 1
tl = (margin_left + offsetx + col * w -0.5, y + offsety + ((row+1) * h) -0.5)
lr = (margin_left + offsetx + col * w + (3*pw)+0.5, y + offsety + ((row+1) * h) + (3*ph) +0.5 )
lw = 0.6
for coords in [(tl, (tl[0] + lw, tl[1])), (tl, (tl[0], tl[1] + lw)), (lr, (lr[0] - lw, lr[1])), (lr, (lr[0], lr[1]-lw)),
((lr[0], tl[1]), (lr[0]-lw, tl[1])),
((lr[0], tl[1]), (lr[0], tl[1]+lw)),
((tl[0], lr[1]), (tl[0]+lw, lr[1])),
((tl[0], lr[1]), (tl[0], lr[1]-lw)),
]:
l = dwg.line(
coords[0],
coords[1],
stroke="black",
stroke_width=STROKE_WIDTH,
)
dwg.add(l)
if glyph:
data = glyph.draw().todata()
for i, line in enumerate(data):
for j, pixel in enumerate(line):
if i <= 2 and j <= 2:
if pixel == "1":
p = dwg.rect(
(margin_left + offsetx + col * w + (pw * j), y + offsety + ((row+1) * h) + (ph * i)),
(pw, ph),
stroke="none",
fill="black" if pixel == "1" else "none",
)
dwg.add(p)
# char
thefont.normalize_rendering(4)
offsetx = 5
offsety = 3
for stroke in thefont.strokes_for_text(char):
for j, point in enumerate(stroke):
stroke[j] = (margin_left + offsetx + col * w + point[0], y + offsety + + ((row+1) * h) + h/2 - point[1])
pl = dwg.polyline(stroke, stroke="black", stroke_width=STROKE_WIDTH, fill="none")
dwg.add(pl)
# decimal value of char
thefont.normalize_rendering(2)
offsetx = 0.5
offsety = 0
for stroke in thefont.strokes_for_text(str(ord(char))):
for j, point in enumerate(stroke):
stroke[j] = (margin_left + offsetx + col * w + point[0], y + offsety + ((row+1) * h) + h - point[1])
pl = dwg.polyline(stroke, stroke="black", stroke_width=STROKE_WIDTH, fill="none")
dwg.add(pl)
# char box
r = dwg.rect(
(margin_left + col * w, y + ((row+1) * h)),
(w, h),
stroke="black",
stroke_width=STROKE_WIDTH,
fill="none",
)
dwg.add(r)
if row == 3:
for i in range(3):
binstr = f"{col:03b}"
r = dwg.rect(
(margin_left + (col*w) + 1 + (i*2.5), y + 10 + ((row+1)*h)),
(2, 2),
stroke="black",
stroke_width=STROKE_WIDTH,
fill="none" if binstr[i] == "0" else "black",
)
dwg.add(r)
y += card_offset
dwg.save()