COVID certificate and other large NDEFs

If anyone’s interested, here’s my latest effort to shrink the EU COVID vaccination certificate sample:

original_covid_certificate_template.pdf (178.0 KB)
compressed_covid_certificate_template.pdf (6.2 KB)

If you print both and superimpose them, you can’t tell them apart.

6.2 KB is well within the storage capability of a DESFire. But I think there’s still room for improvement with the encoding of the QR code. In the end, I ended up writing a script that generates the PDF directly with raw PDF directives. Not fun…

EDIT: here’s the script if you want to play with it - and the QR code GIF. You’ll need Python3 and the Reportlab library:

#!/usr/bin/python3

from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.pdfgen.canvas import Canvas

from reportlab import rl_config
rl_config.invariant = 1


blue = (0, 0.2, 0.6)	# RGB
yellow = (1, 0.8, 0)	# RGB
white = (1, 1, 1)	# RGB
black = (0, 0, 0)	# RGB

canvas = Canvas("compressed_covid_certificate_template.pdf", pagesize = A4, pageCompression = 1)

canvas.setTitle("Mallitodistus EU:n koronarokotustodistuksesta")
canvas.setCreator("OpenPDF 1.3.24")
canvas.setProducer("OpenPDF 1.3.24")
canvas.setSubject("EU Digital COVID Certificate")

canvas.setFillColorRGB(*blue)

# Header
canvas.setFont("Helvetica-Bold", 22)
canvas.drawCentredString(53 * mm, 259 * mm, "EU Digital COVID")
canvas.drawCentredString(53 * mm, 247 * mm, "Certificate")
canvas.drawCentredString(53 * mm, 227 * mm, "EU:n koronatodistus")
canvas.drawCentredString(53 * mm, 215 * mm, "EU:s coronaintyg")

# EU flag background
canvas.rect(31.5 * mm, 175 * mm, 43 * mm, 32 * mm, stroke = 0, fill = 1)

# Blue text and links
canvas.setFont("Helvetica", 13)
canvas.drawString(6 * mm, 135.5 * mm, "VACCINATION CERTIFICATE / "
					"ROKOTUSTODISTUS / VACCINATIONSINTYG")
canvas.setFontSize(11)
canvas.drawString(107.5 * mm, 206 * mm, "Testaaja, Matti Kari")
canvas.drawString(107.5 * mm, 192 * mm, "1995-05-20")
canvas.drawString(107.5 * mm, 173 * mm, "URN:UVCI:01:FI:"
					"41EPCEEXEDR3TZV5RW9XQIR15#2")

canvas.setFontSize(9)
canvas.drawString(111.6 * mm, 10.9 * mm, "https://reopen.europa.eu/en.")
canvas.linkURL("https://reopen.europa.eu/en",
		(111.6 * mm, 10.9 * mm, 151 * mm, 13 * mm), relative = 1)
canvas.drawString(6 * mm, 6.2 * mm, "https://www.kanta.fi/koronatodistus.")
canvas.linkURL("https://www.kanta.fi/koronatodistus",
		(6 * mm, 6.2 * mm, 55.5 * mm, 8.3 * mm), relative = 1)
canvas.drawString(95 * mm, 6.2 * mm, "https://www.kanta.fi/sv/coronaintyg")
canvas.linkURL("https://www.kanta.fi/sv/coronaintyg",
		(95 * mm, 6.2 * mm, 144 * mm, 8.3 * mm), relative = 1)

canvas.setFillColorRGB(*yellow)

# Yellow dividers
canvas.rect(10.5 * mm, 237 * mm, 84 * mm, 1.7 * mm, stroke = 0, fill = 1)
canvas.rect(106 * mm, 222 * mm, 94.5 * mm, 1.7 * mm, stroke = 0, fill = 1)

# EU flag stars
canvas.setFontSize(15)
canvas.drawCentredString(52.75 * mm, 201 * mm, "\u2605")
canvas.drawCentredString(46.75 * mm, 199.3 * mm, "\u2605")
canvas.drawCentredString(58.75 * mm, 199.3 * mm, "\u2605")
canvas.drawCentredString(42.5 * mm, 194.9 * mm, "\u2605")
canvas.drawCentredString(63 * mm, 194.9 * mm, "\u2605")
canvas.drawCentredString(40.85 * mm, 189 * mm, "\u2605")
canvas.drawCentredString(64.65 * mm, 189 * mm, "\u2605")
canvas.drawCentredString(42.5 * mm, 183.1 * mm, "\u2605")
canvas.drawCentredString(63 * mm, 183.1 * mm, "\u2605")
canvas.drawCentredString(46.75 * mm, 178.7 * mm, "\u2605")
canvas.drawCentredString(58.75 * mm, 178.7 * mm, "\u2605")
canvas.drawCentredString(52.75 * mm, 177.2 * mm, "\u2605")

# EU flag "FI" marking
canvas.setFillColorRGB(*white)
canvas.setFontSize(23)
canvas.drawCentredString(53 * mm, 188 * mm, "FI")

# QR code
canvas.drawInlineImage("qr_code.gif", 133 * mm, 233.5 * mm,
			width = 48.5 * mm, height = 48.5 * mm)

# Black bold text
canvas.setFont("Helvetica-Bold", 11)
canvas.setFillColorRGB(*black)
canvas.drawString(107.5 * mm, 214.5 * mm, "Surname(s) and forename(s):")
canvas.drawString(107.5 * mm, 200 * mm, "Date of birth:")
canvas.drawString(107.5 * mm, 186 * mm, "Unique certificate identifier:")

canvas.setFontSize(10)
canvas.drawString(6 * mm, 125.2 * mm, "Disease or agent targeted:")
canvas.drawString(6 * mm, 115.4 * mm, "Vaccine:")
canvas.drawString(6 * mm, 105.4 * mm, "Vaccine medicinal product:")
canvas.drawString(6 * mm, 95.5 * mm, "Vaccine marketing authorisation holder:")
canvas.drawString(6 * mm, 85.7 * mm, "Number in a series of vaccinations/doses "
					"and the overall")
canvas.drawString(6 * mm, 81.7 * mm, "number of doses in the series:")
canvas.drawString(6 * mm, 63.4 * mm, "Date of vaccination, indicating the date "
					"of the latest dose")
canvas.drawString(6 * mm, 59.2 * mm, "received:")
canvas.drawString(6 * mm, 44.7 * mm, "Member state of vaccination:")
canvas.drawString(6 * mm, 30.7 * mm, "Certificate issuer:")

# Black text, certificate
canvas.setFont("Helvetica", 11)
canvas.drawString(107.5 * mm, 210 * mm, "Nimi/Namm:")
canvas.drawString(107.5 * mm, 195.8 * mm, "Syntymäaika/Födelsedatum:")
canvas.drawString(107.5 * mm, 182 * mm, "Todistuksen yksilöllinen tunniste / "
					"En unik indetifierare för")
canvas.drawString(107.5 * mm, 177.7 * mm, "intyget:")

canvas.setFontSize(10)
canvas.drawString(6 * mm, 121 * mm, "Tauti tai taudinaiheuttaja / "
					"Sjukdom eller smittämne:")
canvas.drawString(6 * mm, 111.4 * mm, "Rokote/Vaccin:")
canvas.drawString(6 * mm, 101.4 * mm, "Rokotevalmisteen kauppanimi / "
					"Vaccinets handelsnamn:")
canvas.drawString(6 * mm, 91.5 * mm, "Myyntiluvan haltija / "
					"Innehavare av försäljningstillstånd:")
canvas.drawString(6 * mm, 77.7 * mm, "Saadut rokoteannokset ja "
					"tarvittavien annosten")
canvas.drawString(6 * mm, 73.4 * mm, "kokonaismäärä / Givna vaccindoser "
					"och det totala antalet")
canvas.drawString(6 * mm, 69.2 * mm, "doser som behövs:")
canvas.drawString(6 * mm, 55.0 * mm, "Viimeksi saadun rokotteen antopäivä / "
					"Datum för den senaste")
canvas.drawString(6 * mm, 50.7 * mm, "vaccinationen:")
canvas.drawString(6 * mm, 40.7 * mm, "Jäsenvaltio, jossa rokotus on saatu / "
					"Medlemsstaten där")
canvas.drawString(6 * mm, 36.7 * mm, "vaccineringhar getts:")
canvas.drawString(6 * mm, 26.7 * mm, "Todistuksen antaja / "
					"Utfärdare av intyget:")

canvas.drawString(110.2 * mm, 125.2 * mm, "COVID-19")
canvas.drawString(110.2 * mm, 115.4 * mm, "COVID-19 vaccines")
canvas.drawString(110.2 * mm, 111.4 * mm, "Covid-19-rokotteet / "
						"Vaccin mot covid-19")
canvas.drawString(110.2 * mm, 105.4 * mm, "Comirnaty")
canvas.drawString(110.2 * mm, 95.5 * mm, "BioNTech Manufacturing GmbH")
canvas.drawString(110.2 * mm, 85.7 * mm, "2 / 2")
canvas.drawString(110.2 * mm, 63.4 * mm, "2021-03-05")
canvas.drawString(110.2 * mm, 44.7 * mm, "Finland")
canvas.drawString(110.2 * mm, 40.7 * mm, "Suomi")
canvas.drawString(110.2 * mm, 30.7 * mm, "The Social Insurance Institution "
						"of Finland")
canvas.drawString(110.2 * mm, 26.7 * mm, "Kela/Fpa")

# Black text, footer
canvas.setFontSize(9)

canvas.drawString(6 * mm, 20.6 * mm, "This certificate is not a travel "
					"document. The scientific evidence on "
					"COVID-19 vaccination, testing and "
					"recovery continues to evolve, also in")
canvas.drawString(6 * mm, 15.6 * mm, "view of new variants of concern of the "
					"virus. Before traveling, please check "
					"the applicable public health measures "
					"and related restrictions")
canvas.drawString(6 * mm, 10.9 * mm, "applied at the point of destination. "
					"Relevant information can be found "
					"here:")
canvas.drawString(152.5 * mm, 10.9 * mm, "Lue lisää koronatodistuksesta:")
canvas.drawString(56.6 * mm, 6.2 * mm, "Läs mer om coronaintyget:")

canvas.save()

qr_code.zip (963 Bytes)

9 Likes