Created
February 12, 2017 15:01
-
-
Save miere43/6eca9b24765230adbd79e21696a404a5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Wanikani Critical Items Export | |
// @namespace http://tampermonkey.net/ | |
// @version 0.1 | |
// @description Export critical items from Wanikani in form of pictures | |
// @author Vladislav <miere> Vorobiev | |
// @match https://www.wanikani.com/critical-items | |
// @grant none | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
var options = { | |
// set to false to export all pictures | |
'testMode': true, | |
// your wanikani api key | |
'apiKey': '', | |
'imageWidth': 800, | |
'imageHeight': 600, | |
// maximum font size for character rendering | |
'common': { | |
'fontFamily': 'Arial', // 'NotoSansCJK', | |
// font size for drawing text. | |
// for 'character' section, font size will be reduced to fit to the image, text wrapping is enabled for other sections. | |
'fontSize': 28, | |
'textColor': '#FFFFFF', | |
// line height for 'vocabulary' and 'radical' sections. | |
'lineHeight': 20, | |
// maximum text width in percents (1.0 = 100%, 0.0 = 0%) | |
'maxWidth': 1.0, | |
// horizontal text offset in percents relative to image center | |
'offsetX': 0.5, | |
// vertical text offset | |
'offsetY': 0.5 | |
}, | |
'kanji': { | |
'backgroundColor': '#f100a1', | |
// if some property is not set, it will be inherited from 'common' section. | |
// works for 'character', 'reading' and 'meaning' sections. | |
'character': { | |
// 'character' section doesn't care about 'lineHeight' from 'common' section. | |
'maxWidth': 0.6, | |
'fontSize': 240, | |
'offsetY': 0.5 | |
}, | |
'reading': { | |
'offsetY': 0.8 | |
}, | |
'meaning': { | |
'offsetY': 0.9 | |
} | |
}, | |
'vocabulary': { | |
'backgroundColor': '#a100f1', | |
'character': { | |
'maxWidth': 0.6, | |
'fontSize': 240, | |
'offsetY': 0.5 | |
}, | |
'reading': { | |
'offsetY': 0.8 | |
}, | |
'meaning': { | |
'offsetY': 0.9 | |
} | |
}, | |
'radical': { | |
'backgroundColor': '#00a1f1', | |
'character': { | |
'maxWidth': 0.5, | |
'fontSize': 240, | |
'offsetY': 0.5 | |
}, | |
'meaning': { | |
'offsetY': 0.8, | |
'fontSize': 40, | |
} | |
}, | |
}; | |
var measureTextSize = (function() { | |
var measure = document.createElement('div'); | |
measure.style.position = 'absolute'; | |
measure.style.top = '-9999px'; | |
measure.style.left = '-9999px'; | |
//measure.id = 'test123'; | |
document.body.appendChild(measure); | |
return function(text, fontSizeInPixels, fontFamily) { | |
// @TODO add font weight? | |
measure.style.fontFamily = fontFamily; | |
measure.style.fontSize = fontSizeInPixels + 'px'; | |
// measure.style.fontWeight = ...; | |
measure.innerText = text; | |
return { 'width': measure.clientWidth, 'height': measure.clientHeight }; | |
}; | |
})(); | |
function getCriticalItems(apiKey, successCallback, errorCallback) | |
{ | |
var url = 'https://www.wanikani.com/api/user/' + apiKey + '/critical-items'; | |
var xhr = new XMLHttpRequest(); | |
var criticalItems = null; | |
xhr.addEventListener('load', function() { | |
var status = this.status; | |
if (status == 200) { | |
var response = JSON.parse(this.response); | |
if (response.error !== undefined) { | |
errorCallback(response.error); | |
} else { | |
criticalItems = response.requested_information; | |
successCallback(criticalItems); | |
} | |
} else { | |
errorCallback({ 'message': 'Wanikani request error', 'code': status }); | |
} | |
}); | |
xhr.respondType = 'json'; | |
xhr.open('GET', url, true); | |
xhr.send(); | |
} | |
var saveCanvasToDisk = (function() { | |
var saveElement = document.createElement('a'); | |
saveElement.style = 'display: none'; | |
document.body.appendChild(saveElement); | |
return function(canvas, fileName) { | |
var canvasData = canvas.toDataURL(); | |
saveElement.href = canvasData; | |
saveElement.download = fileName; | |
saveElement.click(); | |
window.URL.revokeObjectURL(canvasData); | |
}; | |
})(); | |
var canvas = document.createElement("canvas"); | |
canvas.width = options.imageWidth || 240; | |
canvas.height = options.imageHeight || 240; | |
var context = canvas.getContext('2d'); | |
if (!context) { | |
alert('Unable to get canvas context.'); | |
} | |
context.textAlign = 'center'; | |
context.textBaseline = 'middle'; | |
var criticalItemsSection = document.getElementsByClassName("low-percentage kotoba-table-list dashboard-sub-section")[0]; | |
var exportButton = document.createElement("input"); | |
exportButton.type = "button"; | |
exportButton.value = "Export as pictures"; | |
if ((options.testMode || false) === true) { | |
exportButton.value += " (test mode, only 1 picture will be exported)"; | |
} | |
function fitTextToSize(text, fontSize, fontFamily, maxWidth, maxHeight) | |
{ | |
// @Speed: this stuff is not efficient at all | |
while (fontSize > 1) { | |
var measure = measureTextSize(text, fontSize, fontFamily); | |
if (measure.width > maxWidth/* || measure.height <= maxHeight*/) { | |
fontSize -= 1; | |
continue; | |
} else { | |
break; | |
} | |
} | |
return fontSize; | |
} | |
function getContextSettingsForType(type) { | |
return options[type]; | |
} | |
function getReading(item) { | |
if (item.type == 'vocabulary') { | |
return item.kana; | |
} else if (item.type == 'kanji') { | |
return item[item.important_reading]; | |
} else { | |
return null; | |
} | |
} | |
function getMeaning(item) { | |
return item.meaning; | |
} | |
function fillTextWrapped(context, text, maxWidth, lineHeight, x, y) | |
{ | |
var words = text.split(" "); | |
var currentLine = words[0]; | |
var currentLineHeight = 0; | |
for (var i = 1; i < words.length; i++) { | |
var word = words[i]; | |
var width = context.measureText(currentLine + " " + word).width; | |
if (width < maxWidth) { | |
currentLine += " " + word; | |
} else { | |
context.fillText(currentLine, x, y + currentLineHeight); | |
currentLineHeight += lineHeight; | |
currentLine = word; | |
} | |
} | |
context.fillText(currentLine, x, y + currentLineHeight); | |
} | |
function getContextProperty(contextOptions, property, defaultValue) { | |
var value = contextOptions[property]; | |
if (value === undefined) { | |
value = options.common[property]; | |
if (value === undefined) { | |
return defaultValue; | |
} | |
} | |
return value; | |
} | |
exportButton.addEventListener('click', function() { | |
exportButton.setAttribute('disabled', true); | |
getCriticalItems(options.apiKey, function(criticalItems) { | |
var length = (options.testMode || false) === false ? criticalItems.length : 1; | |
for (var i = 0; i < length; ++i) { | |
var criticalItem = criticalItems[i]; | |
var contextOptions = null; | |
if (criticalItem.type === 'kanji' || criticalItem.type === 'vocabulary' || criticalItem.type === 'radical') { | |
contextOptions = getContextSettingsForType(criticalItem.type); | |
if (!contextOptions) { | |
alert('No options found for critical item type "' + criticalItem.type + '"'); | |
} | |
} else { | |
alert('Unknown critical item type: "' + criticalItem.type + '" at position ' + i); | |
continue; | |
} | |
// Background | |
context.fillStyle = contextOptions.backgroundColor || '#FFFFFF'; | |
context.fillRect(0, 0, canvas.width, canvas.height); | |
// Character | |
{ | |
context.fillStyle = getContextProperty(contextOptions.character, 'textColor', '#000000'); | |
var fontFamily = getContextProperty(contextOptions.character, 'fontFamily', 'Arial'); | |
var fontSize = fitTextToSize(criticalItem.character, | |
getContextProperty(contextOptions.character, 'fontSize', 24), | |
fontFamily, | |
(contextOptions.character.maxWidth || 1.0) * canvas.width, | |
canvas.height); | |
context.font = fontSize + 'px ' + fontFamily; | |
context.fillText(criticalItem.character, | |
canvas.width * getContextProperty(contextOptions.character, 'offsetX', 0.5), | |
canvas.height * getContextProperty(contextOptions.character, 'offsetY', 0.5)); | |
} | |
// Meaning | |
context.fillStyle = getContextProperty(contextOptions.meaning, 'textColor', '#000000'); | |
context.font = getContextProperty(contextOptions.meaning, 'fontSize', 10) + 'px ' + getContextProperty(contextOptions.meaning, 'fontFamily', 'Arial'); | |
fillTextWrapped(context, | |
getMeaning(criticalItem), | |
getContextProperty(contextOptions.meaning, 'maxWidth', 1.0) * canvas.width, | |
getContextProperty(contextOptions.meaning, 'lineHeight', 20), | |
canvas.width * getContextProperty(contextOptions.meaning, 'offsetX', 0.5), | |
canvas.height * getContextProperty(contextOptions.meaning, 'offsetY', 0.6)); | |
// Reading | |
if (criticalItem.type !== 'radical') { | |
context.fillStyle = getContextProperty(contextOptions.reading, 'textColor', '#000000'); | |
context.font = getContextProperty(contextOptions.reading, 'fontSize', 10) + 'px ' + getContextProperty(contextOptions.reading, 'fontFamily', 'Arial'); | |
fillTextWrapped(context, | |
getReading(criticalItem), | |
getContextProperty(contextOptions.reading, 'maxWidth', 1.0) * canvas.width, | |
getContextProperty(contextOptions.reading, 'lineHeight', 20), | |
canvas.width * getContextProperty(contextOptions.reading, 'offsetX', 0.5), | |
canvas.height * getContextProperty(contextOptions.reading, 'offsetY', 0.7)); | |
} | |
saveCanvasToDisk(canvas, criticalItem.character + '.png'); | |
} | |
}, function(error) { | |
alert('Error rethieving critical items: ' + error.message + ' (' + error.code + ')'); | |
}); | |
}); | |
criticalItemsSection.appendChild(exportButton); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment