Skip to content

About

Generate Japanese dojo certificates (menkyo) and awards for rank (menjo)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

menkyo-menjo-builder – Japanese dan, kyū and menkyo certificates

Note

Vibe coding warning. Every line of code and documentation in this repository was generated by an AI model, with a human directing it and deciding what to keep. Read it as such: the code is workmanlike at best, the prose is more confident than the engineering behind it deserves, and nobody has reviewed it the way a maintained project would be reviewed.

The certificates it produces, on the other hand, are real. They were designed carefully, checked against Japanese practice and are printed and issued by an actual dōjō. If you are here for the documents rather than the software, that is the part to trust.

Generates print-ready dan-grade certificates (段位免状), kyū-grade certificates (級位認定証, for the grades below first dan) and teaching licences (免許状) for a martial-arts dōjō. The dan certificates and the licences are A3 or A4 landscape, vertical Japanese, in the manner of a traditional 賞状 (award certificate); the kyū certificate is a simpler A4 portrait sheet in horizontal lines, with the crest as a light watermark. The grade, the recipient and the date can each be printed or left blank for a calligrapher to brush in.

the three certificates, unconfigured

Three of the documents as an unconfigured build produces them.

Everything that identifies a school lives in a TOML file. Out of the box the generator names nobody: it prints 〇〇流柔術 and 〇〇道場 – the ordinary Japanese placeholder – so an unconfigured build produces a correctly typeset certificate that grants nothing.

Quick start

uv run menkyo --list                 # what a default build would write
uv run menkyo                        # write it
uv run menkyo --paper a4             # the same certificates on A4

Needs Python 3.11+ (for tomllib) and nothing else: uv run installs Pillow, NumPy, SciPy, fontTools and pypdfium2, and the fonts ship inside the package. No other program and no system font is used. Without uv, pip install -e . and then plain menkyo.

From a checkout, files land in build/ as <award>-<variant>-<size>.<ext>. The size is a3 or a4, and any suffix comes after it, so the example writes dan-overlay-a3-sample.pdf and, for its kyū sheet, kyu-overlay-a4-sample.pdf.

As a package

This repository is the generator and its menkyo command, and nothing else. The front end is dojo-portal, a separate Django application that runs the command in a subprocess.

Build the wheel from a checkout:

uv build --wheel                     # dist/menkyo-<version>-py3-none-any.whl

It holds the generator, its fonts, the example configuration and the menkyo command. The consumer installs that file, for example as a uv path source or with pip:

# from the consumer's project, next to this checkout
uv add ../menkyo-menjo-builder/dist/menkyo-<version>-py3-none-any.whl
pip install --force-reinstall ../menkyo-menjo-builder/dist/menkyo-<version>-py3-none-any.whl

The file name carries the version, so after a bump point the path source at the new wheel. A rebuilt wheel of the same version needs a nudge: with uv, run uv lock --upgrade-package menkyo && uv sync, because uv.lock pins the wheel's hash (a plain uv sync stops on a hash mismatch); with pip, --force-reinstall replaces it, which pip would otherwise skip. Or bump the version for every build the consumer takes.

The version lives in pyproject.toml and nowhere else: bump it, commit and build the wheel again. menkyo --version reports which one is installed.

An installed copy has no checkout to sit in, so it writes to ~/Documents/Menkyo unless given --out, and reads a dōjō's settings from ~/Library/Application Support/Menkyo/dojo.toml (~/.config/Menkyo/ on Linux) when no --config is given.

Nothing needs installing besides the wheel and its own dependencies: no program, no font, no system library. The text is drawn from the two fonts inside the package, and pdfium (pypdfium2) draws the PNGs.

Run it as a command, not as a library: the generator keeps its state in module globals, and an inked certificate takes a couple of hundred MB, which a separate process gives back when it ends. Write the dōjō's settings to a TOML file (menkyo --example-config prints the template), then:

menkyo --config=dojo.toml --awards=dan --variants=overlay --formats=pdf \
       --name=ヤン・ヤンセン --rank=二段 --date=2026-09-05 --out=/tmp/xyz --json

Pass every fill-in flag, '' included, so a [sample] in the configuration can never reach a real certificate, and use the --flag=value form so a value that starts with - is not read as a flag. With --json, every line on stdout is one JSON object, written as it happens:

event fields when
progress step, total, award, variant, stage, label a slow step starts; step counts from 1
file path a file has been written
done out, files the run finished; the exit code is 0
error message, optionally detail the run failed; the exit code is 1
planned file with --list: what would be written

The command above writes, in part:

{"event": "progress", "step": 1, "total": 3, "award": "dan", "variant": "overlay", "stage": "text", "label": "dan overlay: rendering the text"}
{"event": "file", "path": "/tmp/xyz/dan-overlay-a3-二段-ヤン-ヤンセン.pdf"}
{"event": "done", "out": "/tmp/xyz", "files": ["/tmp/xyz/dan-overlay-a3-二段-ヤン-ヤンセン.pdf"]}

and a failure ends in one line such as {"event": "error", "message": "unknown award: nope …"}.

A character the fonts do not have is a failure too, found before anything is written. Only what will be drawn is checked, each string against the font it is printed in: the selected awards, the honorific only with a name, a [sample] grade only where there is a grade column, the precept only on a sheet that draws it and, where there is a crest, the art's name against both fonts, since it is also set on the crest ring. The kyū sheet is set in Noto Serif JP, so everything it prints, its body included, is checked against Noto Serif JP rather than Yuji Boku. It prints no precept and no formula, so a run that builds only kyū sheets checks neither, and with crest = false it does not check the ring either. The error event names the character, its code point and where it came from, and asks for a replacement, for example the character 𠮷 (U+20BB7) in --name is not in Yuji Boku, …. There is no fallback face: a kanji from another font among brush strokes would be visibly wrong. Ask the dōjō for a variant the font has (吉 for 𠮷).

stage is one of text, ink, pdf or png, for a caller that wants its own wording; label says the same in English. total is known from the first event, so step / total is a progress bar. A certificate takes a few seconds, most of it in ink. Without --json, the same progress goes to stderr when that is a terminal, and stdout keeps its wrote lines.

Every failure ends the output with exactly one error, a rejected flag or an unwritable --out included, and never a done. --list --json writes one planned per file and no done; its exit code says it worked. --help, --version and --example-config print plain text even with --json: they are for a person, and stop before anything runs.

Configuring it for your dōjō

Copy the example and edit it:

uv run menkyo --example-config > dojo.toml

dojo.toml in the repository root is picked up automatically and is git-ignored, so your school's identity never lands in a fork. --config PATH chooses between several. A per-user dojo.toml (see ‘As a package’) takes precedence over the repository root's when it exists, in a checkout too.

[art]
name = "〇〇流柔術"          # the art, printed and on the crest's upper arc

[dojo]
name = "〇〇道場"            # the dōjō, printed below it
crest_ring = ""              # four characters on the crest's lower arc

[signer]
title = "師範"               # the office that grants the award
name = ""                    # the signatory, in katakana

[motto]
text = ""                    # an optional school precept, in its own column
                             # (vertical sheets only)

[paper]
size = "a3"                  # or "a4"; --paper overrides it for a run
border = 22.0                # mm, the margin kept clear all round — see "Printing"
safe_inset = 38.0            # mm, where the text may begin
text_drop = 0.05              # fraction of page height — see "Where the columns start"

[[awards]]
slug = "dan"
heading = "段位免状"         # names the document
formula = "右授与ス"         # "the above is hereby awarded"
grade_column = true          # the body also states a grade: 初段, 二段 …

[[awards]]
slug = "kyu"
heading = "級位認定証"       # Kyū Grade Certificate
grade_column = "kyu"         # a grade counted in kyū: 五級, 準五級 …
body = [                     # the text below the grade, one string per line
  "上記の者は本会所定の審査に合格したので",
  "ここに認定します",
]
watermark = 0.08             # the crest's grey behind the text, 8 % of black

Each [[awards]] block becomes one --awards slug, so a school with a different set of documents adds, removes or renames them here and nowhere else. grade_column is the one subtle key: a dan certificate states a grade in the body as well as in the heading, whereas a licence has already named its award in the heading and would otherwise leave an empty column behind the recipient's name. Setting it false closes the layout up. It also says what --rank counts in: true (or "dan") in dan, "kyu" in kyū, so -r 5 is 五段 on the one and 五級 on the other. An explicit --rank on a licence counts in dan.

The unit also decides the sheet. An award counting in kyū gets the kyū sheet, a simpler one for the grades below shodan (first dan): A4 portrait, read in horizontal lines, set in Noto Serif JP without the ink grading, with the crest as a light watermark behind the text and no precept.

Where a vertical sheet prints its formula, the kyū sheet prints body: one string per printed line, one to three lines. Japanese has no spaces to break a line at, so you choose the breaks. Without the key the sheet prints the two lines above. formula is not needed on a kyū award and is ignored there.

watermark is the crest's grey as a fraction of black, above 0 and up to 1. The default, 0.08, is a light grey. At about 0.93 the crest is as dark as the text, and above that darker. body and watermark are checked on every award but used only on the kyū sheet.

crest, true unless set, says whether the sheet draws the crest: false leaves it off a vertical sheet and leaves the watermark off the kyū sheet. The precept prints on every vertical sheet when [motto] text has one.

The per-award motto key of 0.5.0 is gone. A configuration that still sets it is refused with a message naming the file, the award and the key, so a 0.5.0 kyū block is refused until you delete its motto line. Delete its crest = false as well if you want the watermark; its formula = "右認定ス" can stay.

A colour key, which earlier versions read for a drawn frame, is still accepted and ignored: the certificates print in black on plain white paper.

Where the columns start

text_drop shifts every printed column's starting point down by that fraction of the page height, giving the reading order more air above it without resizing or re-tuning anything. 0.05 is the shipped taste; 0 reproduces the original layout, columns starting right under the crest.

It moves every column by the same amount, so a large value can push a tall column past safe_inset. The one that matters is the date left blank: its fourteen slots are the tallest column on the sheet, and at 0.05 it still clears safe_inset by a few mm. Check with menkyo -v proof -d '' before raising it further.

The kyū sheet has no columns, and text_drop does not move it: its lines keep their own places.

An optional traced crest

The crest ring is set in a font by default, which is fine at 5 mm. If you have your own crest and want its actual lettering, potrace each character to SVG, drop them in a crest/ folder beside your dojo.toml, named 安.svg, 心.svg and so on, and set traced_glyphs = true under [dojo].

Compare the two before you commit to it. Display lettering is usually drawn already distorted to fit its arc, and tracing it puts a second arc on top of the first – which is why this is off by default. crest/ is git-ignored for the same reason dojo.toml is.

The default crest (mon) is the yotsume-bishi, four lozenges in a diamond, a traditional family crest (kamon); its proportions are constants at the top of src/menkyo/build_diploma.py.

Options

flag meaning
-c, --config PATH which configuration to use (default: dojo.toml, else the shipped example)
--example-config print the shipped example configuration and stop
-a, --awards LIST which awards, or all (default: all)
-v, --variants LIST which variants, or all (default: overlay)
-f, --formats LIST pdf, png, svg (default: pdf)
--no-ink solid black instead of graded sumi (brush ink)
-n, --name KATAKANA print the recipient's name instead of leaving it blank
-r, --rank GRADE print the grade: 2 or nidan → 二段, 1 → 初段 on a dan award; 5, gokyu or yellow → 五級 on a kyū award; or literal text
-d, --date YYYY-MM-DD print the award date in kanji; today also works
--honorific TEXT appended to a printed name (default from the config; '' to omit)
--suffix TEXT tag for output filenames (defaults to the name)
--paper SIZE the sheet, a3 or a4: landscape (default: [paper] size, else a3), and portrait for the kyū sheet (default: a4); given, it applies to every award
--dpi N resolution for PNG output, 1–2400 (default: 300)
--all shorthand for --awards all --variants all
--clean empty build/ first
--list list what would be written, then stop
--json one JSON object per line on stdout, for programs (see above)
--version print the version and stop
menkyo -a dan -r 5 -n 'ヤン・ヤンセン' -d 2024-07-01
menkyo -a dan -v proof -f png      # one proof sheet
menkyo --all -f pdf,png            # every variant, PDF and PNG

Variants

variant what it is
overlay the certificate, its text graded like sumi – the one to print
overlay-flat solid black instead of graded sumi, pure vector
proof safe areas and the zones the calligrapher fills

Only overlay is built by default. All three are the same layout on plain white paper. overlay and overlay-flat are black text and crest, with no frame and no background; proof adds pale guides.

The kyū sheet is set in type and never graded, so its overlay is the certificate as it prints, text in black over the pale watermark, and overlay-flat is the same drawing, byte for byte; --no-ink changes nothing on it. Its proof adds the margins, the zones for a blank name, grade or date, and a circle for a seal right of the signatory's name.

Filling the document in

Three slots are normally left for the calligrapher – the grade, the recipient and the date – and any of them can be printed instead.

--rank takes a number or romaji (Japanese in Latin letters) and writes the kanji: 2 or nidan becomes 二段, and 1 becomes 初段 – first dan has its own word, never 一段. Plain numerals are used rather than the formal 弐段; pass the literal text if you want the formal forms (--rank 弐段). Anything that is not a dan grade passes through untouched, so a licence can use the same flag: --rank 師範. The exception is a kyū form (below): on a dan award it is refused rather than printed in Latin letters. Either way, ū or uu reads as u (jūdan → 十段).

The award decides the unit. On a kyū award (grade_column = "kyu") the same flag counts in kyū (級), from 10 kyū, the first grade, up to 1 kyū, just below shodan:

  • 1 to 10, or the romaji ikkyu … gokyu … jukkyu: 5 and gokyu are 五級;
  • the belt: white 六級, yellow 五級, orange 四級, green 三級, blue 二級, brown 一級;
  • the children's steps between two belts, written with 準 (associate): white-yellow or jun5 準五級, yellow-orange or jun4 準四級, orange-green or jun3 準三級;
  • kanji as they should print: --rank 準五級, --rank 三級.

Anything else is an error that lists these forms, a dan form such as nidan included. On a vertical sheet a grade of more than two characters (準五級) is set smaller to fit the space two take; on the kyū sheet there is room across the page, and it keeps its size.

--date takes an ISO date, or today, and writes 西暦二〇二六年九月五日: the year digit by digit as 西暦 (the Western calendar) requires, month and day as spoken numbers (十二月, 三十一日). Leave it off and the column keeps an empty grid to brush into – worth considering, since a sheet with the date already on it is one you cannot hold back if a grading slips.

--name prints the recipient in katakana, in its own column between the heading and the award, and moves the brushed rank slot left to make room; on the kyū sheet, on its own line below the heading. The filename picks the name up so one recipient's files never overwrite another's. Long names shrink to fit.

How a Western name should be written in katakana is a judgement call. Dutch Geert could be ヘールト, ゲールト or フェールト – the hard G has no Japanese equivalent and every choice is a compromise. Agree it with the recipient before printing: it is the one word on the sheet they will look at hardest.

Printing

  1. Plain white paper, A3 or A4.
  2. Print <award>-overlay-<size>.pdf at 100 % – no ‘fit to page’, no scaling. An A3 page is exactly 420 × 297 mm, an A4 page 297 × 210 mm; check with a ruler on the first sheet.
  3. border and safe_inset under [paper] are margins on the A3 sheet: border is kept clear all round (22 mm by default), safe_inset is where the text may begin (38 mm). Nothing prints them; -v proof draws both, so you can check a layout against your printer's own unprintable edge.

A4 is the A3 certificate scaled by 1/√2 (about 0.71): the same layout, crest and margins, all proportionally smaller, so border is 15.6 mm on A4 and the 12 mm columns are 8.5 mm. Choose it with --paper a4, or with size = "a4" under [paper] as the dōjō's default.

The kyū sheet is A4 portrait, 210 × 297 mm, whatever [paper] size says; --paper a3 makes it A3 portrait, 297 × 420 mm, the same layout scaled up by √2. Its margins are those of the landscape A4: border and safe_inset scaled by 1/√2.

Make a test print of the kyū sheet's watermark. The crest behind the text is a light grey, 8 % of black by default (watermark = 0.08). A home printer may print a grey that light too faintly to see, or in visible bands or speckles. Print the overlay once at 100 % and look: if the crest is hard to see, raise watermark a little (0.12); if it competes with the text, lower it.

Print a -flat variant alongside your first real print. The graded text is a greyscale raster; a home laser halftones grey, and some printers mishandle its transparency. If the columns come out mottled, the -flat files are the same layout in solid black, pure vector. Nothing else differs.

The PDFs are print files: the text is drawn as glyph outlines, so it cannot be searched or copied, and no font is embedded. The overlays are pure grey (DeviceGray), with no colour at all.

How it looks the way it does

The printed text is set in Yuji Boku (佑字 木), a genuinely brush-written kaisho (regular script), then graded by src/menkyo/ink.py so the ink thins towards grey where the brush lifts – a real brush never lays down flat black. The crest ring is set in Noto Serif JP SemiBold, a mincho (the printed serif style), since a mon is a carved seal rather than brushwork. The kyū sheet is set entirely in Noto Serif JP, in solid black, with no grading: a plainer certificate, distinct from the brushed dan and licence sheets. Both fonts ship in src/menkyo/fonts/ and are read from there by file, never looked up by name, so nothing needs installing and nothing on your machine changes or is used.

DECISIONS.md records why each line reads as it does, and how the ink was arrived at.

Layout

src/menkyo/             the package: everything the wheel ships
  build_diploma.py      generator and CLI; typography constants at the top
  render.py             the SVG as PDF (glyph outlines) and PNG (pdfium)
  png.py                PNGs written and read a band of rows at a time
  config.py             the TOML configuration
  paths.py              where things live: checkout or installed
  ink.py                the sumi grading
  example.toml          placeholder configuration, the shipped default
  fonts/                Yuji Boku and Noto Serif JP SemiBold + their OFL licences
tests/                  the suite; tests/docker/ runs it on Linux
docs/                   templates explaining each document to the person
                        who receives it, plus the image above
build/                  generated; git-ignored

Writing for the recipient

A certificate in a language its recipient cannot read is worth accompanying with a page that says what it says. docs/ holds one such page per document, written for exactly that reader.

They explain in full everything the generator prints on its own – the headings, the award and licence formulas, the date, 殿 (the honorific after the name), the reading order, the default crest. The four things that are configuration are described only as far as the form goes, each marked with a block beginning ‘If you are adapting this document’. Replace those with your own account: what your crest means, what your school's name means, who signs. Nobody else can write that part.

A note on what this makes

This produces credentials. It is a typesetting tool and it will cheerfully set any school's name in any grade – the placeholders exist so that it does not do so by default, but they are placeholders and not a lock.

Awarding rank in a Japanese martial art is somebody's prerogative, and usually not the prerogative of whoever has a printer. Configure it for the documents you are actually entitled to issue.

Licence

MIT – see LICENSE. Two things are not covered by it: the bundled fonts, which are under the SIL Open Font License, and crests and school names, which are nobody's to grant. See NOTICE.

About

Generate Japanese dojo certificates (menkyo) and awards for rank (menjo)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages