Draw Me a Kanji
Version vom
Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greasyfork.org/scripts/11842/68906/dmak.js
/*
* Draw Me A Kanji - v0.3.1
* A funny drawer for your Japanese writings
* http://drawmeakanji.com
*
* Made by Matthieu Bilbille
* Under MIT License
*/
;(function () {
"use strict";
// Create a safe reference to the DrawMeAKanji object for use below.
var Dmak = function (text, options) {
this.text = text;
this.options = extend(Dmak.options, options);
this.strokes = [];
this.papers = [];
this.pointer = 0;
this.timeouts = {
play : [],
erasing : [],
drawing : []
};
if (!this.options.skipLoad) {
var loader = new DmakLoader(this.options.uri),
self = this;
loader.load(text, function (data) {
self.prepare(data);
// Execute custom callback "loaded" here
self.options.loaded(self.kanjis);
if (self.options.autoplay) {
self.render();
}
});
}
};
// Current version.
Dmak.VERSION = "0.2.0";
Dmak.options = {
uri: "",
skipLoad: false,
autoplay: true,
height: 109,
width: 109,
viewBox: {
x: 0,
y: 0,
w: 109,
h: 109
},
step: 0.03,
element: "draw",
stroke: {
animated : {
drawing : true,
erasing : true
},
order: {
visible: false,
attr: {
"font-size": "8",
"fill": "#999999"
}
},
attr: {
"active": "#BF0000",
// may use the keyword "random" here for random color
"stroke": "#2C2C2C",
"stroke-width": 4,
"stroke-linecap": "round",
"stroke-linejoin": "round"
}
},
grid: {
show: true,
attr: {
"stroke": "#CCCCCC",
"stroke-width": 0.5,
"stroke-dasharray": "--"
}
},
loaded: function () {
},
erased: function () {
},
drew: function () {
}
};
Dmak.fn = Dmak.prototype = {
/**
* Prepare kanjis and papers for rendering.
*/
prepare: function (data) {
this.kanjis = preprocessStrokes(data);
this.papers = giveBirthToRaphael(data.length);
if (this.options.grid.show) {
showGrid(this.papers);
}
},
/**
* Clean all strokes on papers.
*/
erase: function (end) {
// Cannot have two rendering process for the same draw. Keep it cool.
if (this.timeouts.play.length) {
return false;
}
// Don't go behind the beginning.
if (this.pointer <= 0) {
return false;
}
if (typeof end === "undefined") {
end = 0;
}
do {
this.pointer--;
eraseStroke(this.kanjis[this.pointer], this.timeouts.erasing);
// Execute custom callback "erased" here
this.options.erased(this.pointer);
}
while (this.pointer > end);
},
/**
* All the magic happens here.
*/
render: function (end) {
// Cannot have two rendering process for
// the same draw. Keep it cool.
if (this.timeouts.play.length) {
return false;
}
if (typeof end === "undefined") {
end = this.kanjis.length;
} else if (end > this.kanjis.length) {
return false;
}
var cb = function (that) {
drawStroke(that.papers[that.kanjis[that.pointer].char], that.kanjis[that.pointer], that.timeouts.drawing);
// Execute custom callback "drew" here
that.options.drew(that.pointer);
that.pointer++;
that.timeouts.play.shift();
},
delay = 0,
i;
// Before drawing clear any remaining erasing timeouts
for (i = 0; i < this.timeouts.erasing.length; i++) {
window.clearTimeout(this.timeouts.erasing[i]);
this.timeouts.erasing = [];
}
for (i = this.pointer; i < end; i++) {
if (!Dmak.options.stroke.animated.drawing || delay <= 0) {
cb(this);
} else {
this.timeouts.play.push(setTimeout(cb, delay, this));
}
delay += this.kanjis[i].duration;
}
},
/**
* Pause rendering
*/
pause: function () {
for (var i = 0; i < this.timeouts.play.length; i++) {
window.clearTimeout(this.timeouts.play[i]);
}
this.timeouts.play = [];
},
/**
* Wrap the erase function to remove the x last strokes.
*/
eraseLastStrokes: function (nbStrokes) {
this.erase(this.pointer - nbStrokes);
},
/**
* Wrap the render function to render the x next strokes.
*/
renderNextStrokes: function (nbStrokes) {
this.render(this.pointer + nbStrokes);
}
};
// HELPERS
/**
* Flattens the array of strokes ; 3D > 2D and does some preprocessing while
* looping through all the strokes:
* - Maps to a character index
* - Calculates path length
*/
function preprocessStrokes(data) {
var strokes = [],
stroke,
length,
i,
j;
for (i = 0; i < data.length; i++) {
for (j = 0; j < data[i].length; j++) {
length = Raphael.getTotalLength(data[i][j].path);
stroke = {
"char": i,
"length": length,
"duration": length * Dmak.options.step * 1000,
"path": data[i][j].path,
"groups" : data[i][j].groups,
"text": data[i][j].text,
"object": {
"path" : null,
"text": null
}
};
strokes.push(stroke);
}
}
return strokes;
}
/**
* Init Raphael paper objects
*/
function giveBirthToRaphael(nbChar) {
var papers = [],
paper,
i;
for (i = 0; i < nbChar; i++) {
paper = new Raphael(Dmak.options.element, Dmak.options.width + "px", Dmak.options.height + "px");
paper.setViewBox(Dmak.options.viewBox.x, Dmak.options.viewBox.y, Dmak.options.viewBox.w, Dmak.options.viewBox.h);
paper.canvas.setAttribute("class", "dmak-svg");
papers.push(paper);
}
return papers.reverse();
}
/**
* Draw the background grid
*/
function showGrid(papers) {
var i;
for (i = 0; i < papers.length; i++) {
papers[i].path("M" + (Dmak.options.viewBox.w / 2) + ",0 L" + (Dmak.options.viewBox.w / 2) + "," + Dmak.options.viewBox.h).attr(Dmak.options.grid.attr);
papers[i].path("M0," + (Dmak.options.viewBox.h / 2) + " L" + Dmak.options.viewBox.w + "," + (Dmak.options.viewBox.h / 2)).attr(Dmak.options.grid.attr);
}
}
/**
* Remove a single stroke ; deletion can be animated if set as so.
*/
function eraseStroke(stroke, timeouts) {
// In some cases the text object may be null:
// - Stroke order display disabled
// - Stroke already deleted
if (stroke.object.text !== null) {
stroke.object.text.remove();
}
var cb = function() {
stroke.object.path.remove();
// Finally properly prepare the object variable
stroke.object = {
"path" : null,
"text" : null
};
timeouts.shift();
};
if (Dmak.options.stroke.animated.erasing) {
stroke.object.path.node.style.stroke = Dmak.options.stroke.attr.active;
timeouts.push(animateStroke(stroke, -1, cb));
}
else {
cb();
}
}
/**
* Draw a single stroke ; drawing can be animated if set as so.
*/
function drawStroke(paper, stroke, timeouts) {
var cb = function() {
// The stroke object may have been already erased when we reach this timeout
if (stroke.object.path === null) {
return;
}
var color = Dmak.options.stroke.attr.stroke;
if(Dmak.options.stroke.attr.stroke === "random") {
color = Raphael.getColor();
}
// Revert back to the default color.
stroke.object.path.node.style.stroke = color;
stroke.object.path.node.style.transition = stroke.object.path.node.style.WebkitTransition = "stroke 400ms ease";
timeouts.shift();
};
stroke.object.path = paper.path(stroke.path);
stroke.object.path.attr(Dmak.options.stroke.attr);
if (Dmak.options.stroke.order.visible) {
showStrokeOrder(paper, stroke);
}
if (Dmak.options.stroke.animated.drawing) {
animateStroke(stroke, 1, cb);
}
else {
cb();
}
}
/**
* Draw a single next to
*/
function showStrokeOrder(paper, stroke) {
stroke.object.text = paper.text(stroke.text.x, stroke.text.y, stroke.text.value);
stroke.object.text.attr(Dmak.options.stroke.order.attr);
}
/**
* Animate stroke drawing.
* Based on the great article wrote by Jake Archibald
* http://jakearchibald.com/2013/animated-line-drawing-svg/
*/
function animateStroke(stroke, direction, callback) {
stroke.object.path.attr({"stroke": Dmak.options.stroke.attr.active});
stroke.object.path.node.style.transition = stroke.object.path.node.style.WebkitTransition = "none";
// Set up the starting positions
stroke.object.path.node.style.strokeDasharray = stroke.length + " " + stroke.length;
stroke.object.path.node.style.strokeDashoffset = (direction > 0) ? stroke.length : 0;
// Trigger a layout so styles are calculated & the browser
// picks up the starting position before animating
stroke.object.path.node.getBoundingClientRect();
stroke.object.path.node.style.transition = stroke.object.path.node.style.WebkitTransition = "stroke-dashoffset " + stroke.duration + "ms ease";
// Go!
stroke.object.path.node.style.strokeDashoffset = (direction > 0) ? "0" : stroke.length;
// Execute the callback once the animation is done
// and return the timeout id.
return setTimeout(callback, stroke.duration);
}
/**
* Simplistic helper function for extending objects
*/
function extend(defaults, replacement) {
var result = defaults,
key;
if (arguments.length !== 2) {
throw new Error("Missing arguments in extend function");
}
for (key in replacement) {
if (typeof result[key] === "object") {
result[key] = extend(result[key], replacement[key]);
} else if (result.hasOwnProperty(key)) {
result[key] = replacement[key];
}
}
return result;
}
window.Dmak = Dmak;
}());
;(function () {
"use strict";
// Create a safe reference to the DrawMeAKanji object for use below.
var DmakLoader = function (uri) {
this.uri = uri;
};
/**
* Gather SVG data information for a given set of characters.
* By default this action is done while instanciating the Word
* object, but it can be skipped, see above
*/
DmakLoader.prototype.load = function (text, callback) {
var paths = [],
nbChar = text.length,
done = 0,
i,
callbacks = {
done: function (index, data) {
paths[index] = data;
done++;
if (done === nbChar) {
callback(paths);
}
},
error: function (msg) {
console.log("Error", msg);
}
};
for (i = 0; i < nbChar; i++) {
loadSvg(this.uri, i, text.charCodeAt(i).toString(16), callbacks);
}
};
/**
* Try to load a SVG file matching the given char code.
* @thanks to the incredible work made by KanjiVG
* @see: http://kanjivg.tagaini.net
*/
function loadSvg(uri, index, charCode, callbacks) {
var xhr = new XMLHttpRequest(),
code = ("00000" + charCode).slice(-5);
// Skip space character
if(code === "00020" || code === "03000") {
callbacks.done(index, {
paths: [],
texts: []
});
return;
}
xhr.open("GET", uri + code + ".svg", true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
callbacks.done(index, parseResponse(xhr.response, code));
} else {
callbacks.error(xhr.statusText);
}
}
};
xhr.send();
}
/**
* Simple parser to extract paths and texts data.
*/
function parseResponse(response, code) {
var data = [],
dom = new DOMParser().parseFromString(response, "application/xml"),
texts = dom.querySelectorAll("text"),
groups = [],
i;
// Private recursive function to parse DOM content
function __parse(element) {
var children = element.childNodes,
i;
for(i = 0; i < children.length; i++) {
if(children[i].tagName === "g") {
groups.push(children[i].getAttribute("id"));
__parse(children[i]);
groups.splice(groups.indexOf(children[i].getAttribute("id")), 1);
}
else if(children[i].tagName === "path") {
data.push({
"path" : children[i].getAttribute("d"),
"groups" : groups.slice(0)
});
}
}
}
// Start parsing
__parse(dom.getElementById("kvg:" + code));
// And finally add order mark information
for (i = 0; i < texts.length; i++) {
data[i].text = {
"value" : texts[i].textContent,
"x" : texts[i].getAttribute("transform").split(" ")[4],
"y" : texts[i].getAttribute("transform").split(" ")[5].replace(")", "")
};
}
return data;
}
window.DmakLoader = DmakLoader;
}());