[Userscript] Jitai v2

Since the new WaniKani UI update this Jitai script is broken. I quickly completely rewrote the logic to make it work with the current version, see code below.

I will provide minimum to no support or help, I just thought it’d be nice to share a quick solution for those that need it.

// ==UserScript==
// @name        Jitai
// @version     2.0
// @description Display WaniKani reviews in randomized fonts, for more varied reading training.
// @author      rogerxiii (original by obskyr)
// @license     MIT
// @icon        http://i.imgur.com/qyuR9bD.png
// @include     /^https?://(www\.)?wanikani\.com/subjects/review/?$/
// @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)
    const fontName = fonts[i];
    if (fontExists(fontName)) existingFonts.push(fontName);

function fontExists(fontName) {
    // Approach from kirupa.com/html5/detect_whether_font_is_installed.htm - 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;

    // Microsoft Edge raises an error when a context's font is set to a string
    // containing certain special characters... so that needs to be handled.
    try {
        context.font = "72px " + fontName + ", monospace";
    } catch (e) {
        return false;
    var testWidth = context.measureText(text).width;

    return testWidth != defaultWidth;

function isCanvasBlank(canvas) {
    return !canvas.getContext('2d')
        .getImageData(0, 0, canvas.width, canvas.height).data
        .some(channel => channel !== 0);

function canRepresentGlyphs(fontName, glyphs) {
    var canvas = document.createElement('canvas');
    canvas.width = 50;
    canvas.height = 50;
    var context = canvas.getContext("2d");
    context.textBaseline = 'top';

    context.font = "24px " + fontName;

    var result = true;
    for (var i = 0; i < glyphs.length; i++) {
        context.fillText(glyphs[i], 0, 0);
        if (isCanvasBlank(canvas)) {
            result = false;
        context.clearRect(0, 0, canvas.width, canvas.height);

    return result;

// -----------------------------------------------------------------------------------------------------

const ele = document.getElementsByClassName("character-header__characters")[0];
const default_font = getComputedStyle(ele).fontFamily;
let current_font = default_font;

let style = document.createElement("style");
style.appendChild(document.createTextNode(".character-header__characters:hover { font-family: var(--font-family-japanese-hover); }"));
ele.style.setProperty("--font-family-japanese-hover", current_font);

// On Submit
window.addEventListener("didAnswerQuestion", function()
    ele.style.setProperty("--font-family-japanese", default_font);
    ele.style.setProperty("--font-family-japanese-hover", current_font);

// On Next
window.addEventListener("willShowNextQuestion", function(event)
    const characters = event.detail.subject.characters;
    if (typeof(characters) == "string")
        do current_font = existingFonts[Math.floor(Math.random() * existingFonts.length)];
        while (!canRepresentGlyphs(current_font, characters));

        ele.style.setProperty("--font-family-japanese", current_font);
        ele.style.setProperty("--font-family-japanese-hover", default_font);


Thank you so much! This is fantastic!

1 Like

Thanks. I.will try it later :slight_smile:

Thanks a lot rewriting the script. :slight_smile:
I think in the old one there was something to prevent a font being used when a character wasn’t covered by it. Do you happen to know which part that was? I’m really not that experienced with coding.

I copied that logic from the previous script, so it should be identical!
If you want to look at the code you can see the function canRepresentGlyphs does that.

1 Like

Great work. I had a custom addition, do you have a quick solution how to integrate that, too? I used the KanjiStrokeOrders font from another script to show stroke orders on CTRL key down:

init: function() {
jitai.$characterSpan.css(‘font-family’, ‘KanjiStrokeOrders’);});
jitai.$characterSpan.css(‘font-family’, ‘Ackaisyo’);});

    //CTRL keydown sets to default font
    $(document).keydown(function(event) {
        if(event.which === 17) {
            jitai.$characterSpan.css('font-family', 'KanjiStrokeOrders');
    //CTRL keyup sets to random font again
    $(document).keyup(function(event) {
        if(event.which === 17) {
            jitai.$characterSpan.css('font-family', 'Ackaisyo');

Hi, sorry, but could someone explain how to implement this fix? Do I just go in and completely replace the old code with the new code? Thanks in advance. :pray:

Indeed, copy that whole code to either a new script or replace the old one!

1 Like

Thanks so much! Looks like it’s working! :grin:

1 Like

you’re a hero, thank you!

This version crashes my reviews, looks like every time a ~ item comes up:

Loading these reviews freezes and timeouts my tab. Any ideas why?

I just had one in my reviews today and it worked fine. If you have other scripts running then one of them might be interfering, you could try turning some off and see which causes the crash.
Alternatively, when it happens again please show me what error shows up in the console.

As a side question, do you have enough fonts installed? I think there’s a bug that I haven’t fixed yet for when there’s no installed fonts available for an item, it doesn’t default back to the normal wanikani font. That could be your issue as well.

1 Like

I don’t know if this is due to my slow internet connection, but it only applies the styling after a second for the very first item to be reviewed. Is the event "willShowNextQuestion" released only after a delay? If so, wouldn’t it be better to set current_font to a starting random font and then replace the do while block with a while one?

I posted an update on Jitai over on the original thread.


This is champion status. Used Jitai back in the day before I reset, and was really bummed to see it wasn’t working now that I’m going through WK again. Thank you for reviving such a useful tool!

The font update today broke this :frowning:

Someone posted a fix here