|
// ==UserScript== |
|
// @name Jitai |
|
// @version 1.3.2 |
|
// @description Display WaniKani reviews in randomized fonts, for more varied reading training. |
|
// @author Samuel (@obskyr) |
|
// @copyright 2016-2018, obskyr |
|
// @license MIT |
|
// @namespace http://obskyr.io/ |
|
// @homepageURL https://gist.github.com/obskyr/9f3c77cf6bf663792c6e |
|
// @icon http://i.imgur.com/qyuR9bD.png |
|
// @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", |
|
"cinecaption", |
|
"nagayama_kai", |
|
"A-OTF Shin Maru Go Pro", |
|
"Hosofuwafont", |
|
"ChihayaGothic", |
|
"'chifont+', chifont", |
|
"darts font", |
|
"santyoume-font", |
|
"FC-Flower", |
|
"ArmedBanana", // This one is completely absurd. I recommend it. |
|
"HakusyuKaisyoExtraBold_kk", |
|
"aoyagireisyosimo2, AoyagiKouzanFont2OTF", |
|
"aquafont", |
|
|
|
// 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)) { |
|
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 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; |
|
break; |
|
} |
|
context.clearRect(0, 0, canvas.width, canvas.height); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
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 = this.getShuffledFonts(); |
|
|
|
// 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; |
|
break; |
|
} |
|
} |
|
} else { |
|
currentFont = randomlyOrdered.join(', '); |
|
} |
|
|
|
this.currentFont = currentFont; |
|
|
|
jitai.setHoverFont(jitai.defaultFont); |
|
this.$characterSpan.css('font-family', currentFont); |
|
}, |
|
|
|
setToDefaultFont: function() { |
|
jitai.setHoverFont(jitai.currentFont); |
|
this.$characterSpan.css('font-family', ''); |
|
}, |
|
|
|
setHoverFont: function(fontName) { |
|
this.$hoverStyle.text("#character span:hover {font-family: " + fontName + " !important;}"); |
|
}, |
|
|
|
getShuffledFonts: function() { |
|
// This shouldn't have to be part of the Jitai object, |
|
// but it uses Jitai's local copy of Math.random, so |
|
// this is pretty much the most reasonable way to do it. |
|
var fonts = existingFonts.slice(); |
|
for (var i = fonts.length; i > 0;) { |
|
var otherIndex = Math.floor(this.random() * i); |
|
i--; |
|
|
|
var temp = fonts[i]; |
|
fonts[i] = fonts[otherIndex]; |
|
fonts[otherIndex] = temp; |
|
} |
|
return fonts; |
|
}, |
|
|
|
init: function() { |
|
// Reorder scripts seem to like overwriting Math.random(!?), so this |
|
// workaround is required for Jitai to work in conjunction with them. |
|
var iframe = document.createElement('iframe'); |
|
iframe.className = 'jitai-workaround-for-reorder-script-compatibility'; |
|
iframe.style.display = 'none'; |
|
document.body.appendChild(iframe); |
|
this.random = iframe.contentWindow.Math.random; |
|
|
|
this.$characterSpan = $('#character span'); |
|
this.defaultFont = this.$characterSpan.css('font-family'); |
|
|
|
this.$hoverStyle = $('<style/>', {'type': 'text/css'}); |
|
$('head').append(this.$hoverStyle); |
|
|
|
// 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) { |
|
jitai.setToDefaultFont(); |
|
} |
|
|
|
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() { |
|
jitai.init(); |
|
|
|
// 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 += "}"; |
|
|
|
$heightStyle.text(heightCss); |
|
$('head').append($heightStyle); |
|
}); |