Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Jitai (字体): A fairly full-featured font randomizer for WaniKani.

List of supported fonts and download links

Fonts from

I would recommend taking a look at these fonts, and installing the ones you'd like. Once installed, Jitai will automatically use them. I, personally, have them all installed - it's a good mix!

Jitai will only use any given font for words where it has all the kanji in it, so don't be afraid of installing fonts that only contain a limited subset of kanji - it can't hurt. Go wild!

Default Windows fonts

  • Meiryo
  • MS Gothic
  • MS Mincho
  • Yu Gothic
  • Yu Mincho

Default OS X fonts

  • Hiragino Kaku Gothic Pro
  • Hiragino Maru Gothic Pro
  • Hiragino Mincho Pro

Common Linux fonts

EPSON families (download and run ttf30.exe)

  • EPSON 行書体M
  • EPSON 正楷書体M
  • EPSON 教科書体M
  • EPSON 太明朝体B
  • EPSON 太行書体B
  • EPSON 丸ゴシック体M

Other fonts (Typekit, etc.)

// ==UserScript==
// @name Jitai
// @version 1.3.0
// @description Display WaniKani reviews in randomized fonts, for more varied reading training.
// @author Samuel (@obskyr)
// @namespace
// @homepageURL
// @icon
// @include /^https?://(www\.)?wanikani\.com/review/session/?$/
// @grant none
// ==/UserScript==
To control which fonts to choose from, edit this list.
If you feel too many fonts of a certain type are showing
up, remove a few of those from the list. If you've got
fonts that aren't in the list that you'd like to be used,
add their names and they'll be in the rotation.
var fonts = [
// Default Windows fonts
"Meiryo, メイリオ",
"MS PGothic, MS Pゴシック, MS Gothic, MS ゴック",
"MS PMincho, MS P明朝, MS Mincho, MS 明朝",
"Yu Gothic, YuGothic",
"Yu Mincho, YuMincho",
// Default OS X fonts
"Hiragino Kaku Gothic Pro, ヒラギノ角ゴ Pro W3",
"Hiragino Maru Gothic Pro, ヒラギノ丸ゴ Pro W3",
"Hiragino Mincho Pro, ヒラギノ明朝 Pro W3",
// Common Linux fonts
"Takao Gothic, TakaoGothic",
"Takao Mincho, TakaoMincho",
"Sazanami Gothic",
"Sazanami Mincho",
"Kochi Gothic",
"Kochi Mincho",
"Dejima Mincho",
"Ume Gothic",
"Ume Mincho",
// Other Japanese fonts people use.
// You might want to try some of these!
"EPSON 行書体M",
"EPSON 正楷書体M",
"EPSON 教科書体M",
"EPSON 太明朝体B",
"EPSON 太行書体B",
"EPSON 丸ゴシック体M",
"A-OTF Shin Maru Go Pro",
"'chifont+', chifont",
"darts font",
"ArmedBanana", // This one is completely absurd. I recommend it.
"aoyagireisyosimo2, AoyagiKouzanFont2OTF",
// Add your fonts here!
"Fake font name that you can change",
"Another fake font name",
"Just add them like this!",
"Quotes around the name, comma after."
var existingFonts = [];
for (var i = 0; i < fonts.length; i++) {
var fontName = fonts[i];
if (fontExists(fontName)) {
function fontExists(fontName) {
// Approach from - thanks!
// Will return false for the browser's default monospace font, sadly.
var canvas = document.createElement('canvas');
var context = canvas.getContext("2d");
var text = "wim-—l~ツ亻".repeat(100); // Characters with widths that often vary between fonts.
context.font = "72px monospace";
var defaultWidth = context.measureText(text).width;
context.font = "72px " + fontName + ", monospace";
var testWidth = context.measureText(text).width;
return testWidth != defaultWidth;
function canRepresentGlyphs(fontName, glyphs) {
var canvas = document.createElement('canvas');
canvas.width = 50;
canvas.height = 50;
var context = canvas.getContext("2d");
context.textBaseline = 'top';
var blank = document.createElement('canvas');
blank.width = canvas.width;
blank.height = canvas.height;
var blankDataUrl = blank.toDataURL();
context.font = "24px " + fontName;
var result = true;
for (var i = 0; i < glyphs.length; i++) {
context.fillText(glyphs[i], 0, 0);
if (canvas.toDataURL() === blankDataUrl) {
result = false;
context.clearRect(0, 0, canvas.width, canvas.height);
return result;
function shuffle(arr) {
// WaniKani Reorder Ultimate overwrites Math.random(!?), so this
// next line is required for Jitai to work in conjunction with it.
var random = Math.randomB || Math.random;
for (var i = arr.length; i > 0;) {
var otherIndex = Math.floor(random() * i);
var temp = arr[i];
arr[i] = arr[otherIndex];
arr[otherIndex] = temp;
return arr;
var jitai = {
setToRandomFont: function(glyphs) {
// The font is set as a randomly shuffled list of the existing fonts
// in order to always show a random font, even if the first one chosen
// doesn't have a certain glyph being attempted to be displayed.
var randomlyOrdered = shuffle(existingFonts.slice());
// Some fonts don't contain certain radicals, for example, so it's best
// to check that the font used can represent all the glyphs. The reason
// the browser can't switch automatically is that some fonts report that
// they have a glyph, when in fact they just show up blank.
var currentFont;
if (glyphs) {
for (var i = 0; i < randomlyOrdered.length; i++) {
var fontName = randomlyOrdered[i];
if (canRepresentGlyphs(fontName, glyphs)) {
currentFont = fontName;
} else {
currentFont = randomlyOrdered.join(', ');
this.currentFont = currentFont;
this.$characterSpan.css('font-family', currentFont);
setToDefaultFont: function(fontName) {
this.$characterSpan.css('font-family', '');
setHoverFont: function(fontName) {
this.$hoverStyle.text("#character span:hover {font-family: " + fontName + " !important;}");
init: function() {
this.$characterSpan = $('#character span');
this.defaultFont = this.$characterSpan.css('font-family');
this.$hoverStyle = $('<style/>', {'type': 'text/css'});
// answerChecker.evaluate is only called when checking the answer, which
// is why we catch it, check for the "proceed to correct/incorrect display"
// condition, and set the font back to default if it's a non-stopping answer.
var oldEvaluate = answerChecker.evaluate;
answerChecker.evaluate = function(questionType, answer) {
var result = oldEvaluate.apply(this, [questionType, answer]);
if (!result.exception) {
return result;
// $.jStorage.set('currentItem') is only called right when switching to a
// new question, which is why we hook into it to randomize the font at the
// exact right time: when a new item shows up.
var oldSet = $.jStorage.set;
$.jStorage.set = function(key, value, options) {
var ret = oldSet.apply(this, [key, value, options]);
if (key === 'currentItem') {
jitai.setToRandomFont(value.kan || value.voc || value.rad);
return ret;
$(document).ready(function() {
// Make sure page doesn't jump around on hover.
var $heightStyle = $('<style/>', {'type': 'text/css'});
var heightCss = "";
// The different heights here are equal to the different line-heights.
heightCss += "#question #character {height: 1.6em;}";
heightCss += "#question #character.vocabulary {height: 3.21em;}";
heightCss += "@media (max-width: 767px) {";
heightCss += " #question #character {height: 2.4em;}";
heightCss += " #question #character.vocabulary {height: 4.85em;}";
heightCss += "}";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment