mirror of
https://github.com/lovasoa/whitebophir
synced 2024-11-14 00:07:07 +00:00
224 lines
5.1 KiB
JavaScript
224 lines
5.1 KiB
JavaScript
const fs = require("./fs_promises.js"),
|
|
path = require("path"),
|
|
wboPencilPoint = require("../client-data/tools/pencil/wbo_pencil_point.js")
|
|
.wboPencilPoint;
|
|
|
|
function htmlspecialchars(str) {
|
|
if (typeof str !== "string") return "";
|
|
|
|
return str.replace(/[<>&"']/g, function (c) {
|
|
switch (c) {
|
|
case "<":
|
|
return "<";
|
|
case ">":
|
|
return ">";
|
|
case "&":
|
|
return "&";
|
|
case '"':
|
|
return """;
|
|
case "'":
|
|
return "'";
|
|
}
|
|
});
|
|
}
|
|
|
|
function renderPath(el, pathstring) {
|
|
return (
|
|
"<path " +
|
|
(el.id ? 'id="' + htmlspecialchars(el.id) + '" ' : "") +
|
|
'stroke-width="' +
|
|
(el.size | 0) +
|
|
'" ' +
|
|
(el.opacity ? 'opacity="' + parseFloat(el.opacity) + '" ' : "") +
|
|
'stroke="' +
|
|
htmlspecialchars(el.color) +
|
|
'" ' +
|
|
'd="' +
|
|
pathstring +
|
|
'" ' +
|
|
(el.deltax || el.deltay
|
|
? 'transform="translate(' + +el.deltax + "," + +el.deltay + ')"'
|
|
: "") +
|
|
"/>"
|
|
);
|
|
}
|
|
|
|
const Tools = {
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
Text: function (el) {
|
|
return (
|
|
"<text " +
|
|
'id="' +
|
|
htmlspecialchars(el.id || "t") +
|
|
'" ' +
|
|
'x="' +
|
|
(el.x | 0) +
|
|
'" ' +
|
|
'y="' +
|
|
(el.y | 0) +
|
|
'" ' +
|
|
'font-size="' +
|
|
(el.size | 0) +
|
|
'" ' +
|
|
'fill="' +
|
|
htmlspecialchars(el.color || "#000") +
|
|
'" ' +
|
|
(el.deltax || el.deltay
|
|
? 'transform="translate(' +
|
|
(el.deltax || 0) +
|
|
"," +
|
|
(el.deltay || 0) +
|
|
')"'
|
|
: "") +
|
|
">" +
|
|
htmlspecialchars(el.txt || "") +
|
|
"</text>"
|
|
);
|
|
},
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
Pencil: function (el) {
|
|
if (!el._children) return "";
|
|
let pts = el._children.reduce(function (pts, point) {
|
|
return wboPencilPoint(pts, point.x, point.y);
|
|
}, []);
|
|
const pathstring = pts
|
|
.map(function (op) {
|
|
return op.type + " " + op.values.join(" ");
|
|
})
|
|
.join(" ");
|
|
return renderPath(el, pathstring);
|
|
},
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
Rectangle: function (el) {
|
|
return (
|
|
"<rect " +
|
|
(el.id ? 'id="' + htmlspecialchars(el.id) + '" ' : "") +
|
|
'x="' +
|
|
(el.x || 0) +
|
|
'" ' +
|
|
'y="' +
|
|
(el.y || 0) +
|
|
'" ' +
|
|
'width="' +
|
|
(el.x2 - el.x) +
|
|
'" ' +
|
|
'height="' +
|
|
(el.y2 - el.y) +
|
|
'" ' +
|
|
'stroke="' +
|
|
htmlspecialchars(el.color) +
|
|
'" ' +
|
|
'stroke-width="' +
|
|
(el.size | 0) +
|
|
'" ' +
|
|
(el.deltax || el.deltay
|
|
? 'transform="translate(' +
|
|
(el.deltax || 0) +
|
|
"," +
|
|
(el.deltay || 0) +
|
|
')"'
|
|
: "") +
|
|
"/>"
|
|
);
|
|
},
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
Ellipse: function (el) {
|
|
const cx = Math.round((el.x2 + el.x) / 2);
|
|
const cy = Math.round((el.y2 + el.y) / 2);
|
|
const rx = Math.abs(el.x2 - el.x) / 2;
|
|
const ry = Math.abs(el.y2 - el.y) / 2;
|
|
const pathstring =
|
|
"M" +
|
|
(cx - rx) +
|
|
" " +
|
|
cy +
|
|
"a" +
|
|
rx +
|
|
"," +
|
|
ry +
|
|
" 0 1,0 " +
|
|
rx * 2 +
|
|
",0" +
|
|
"a" +
|
|
rx +
|
|
"," +
|
|
ry +
|
|
" 0 1,0 " +
|
|
rx * -2 +
|
|
",0";
|
|
return renderPath(el, pathstring);
|
|
},
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
"Straight line": function (el) {
|
|
const pathstring = "M" + el.x + " " + el.y + "L" + el.x2 + " " + el.y2;
|
|
return renderPath(el, pathstring);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Writes the given board as an svg to the given writeable stream
|
|
* @param {Object[string, BoardElem]} obj
|
|
* @param {WritableStream} writeable
|
|
*/
|
|
async function toSVG(obj, writeable) {
|
|
const margin = 400;
|
|
const elems = Object.values(obj);
|
|
const dim = elems.reduce(
|
|
function (dim, elem) {
|
|
if (elem._children && elem._children.length) elem = elem._children[0];
|
|
return [
|
|
Math.max((elem.x + margin + (elem.deltax | 0)) | 0, dim[0]),
|
|
Math.max((elem.y + margin + (elem.deltay | 0)) | 0, dim[1]),
|
|
];
|
|
},
|
|
[margin, margin]
|
|
);
|
|
writeable.write(
|
|
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" ' +
|
|
'width="' +
|
|
dim[0] +
|
|
'" height="' +
|
|
dim[1] +
|
|
'">' +
|
|
'<defs><style type="text/css"><![CDATA[' +
|
|
'text {font-family:"Arial"}' +
|
|
"path {fill:none;stroke-linecap:round;stroke-linejoin:round;}" +
|
|
"rect {fill:none}" +
|
|
"]]></style></defs>"
|
|
);
|
|
await Promise.all(
|
|
elems.map(async function (elem) {
|
|
await Promise.resolve(); // Do not block the event loop
|
|
const renderFun = Tools[elem.tool];
|
|
if (renderFun) writeable.write(renderFun(elem));
|
|
else console.warn("Missing render function for tool", elem.tool);
|
|
})
|
|
);
|
|
writeable.write("</svg>");
|
|
}
|
|
|
|
async function renderBoard(file, stream) {
|
|
const data = await fs.promises.readFile(file);
|
|
var board = JSON.parse(data);
|
|
return toSVG(board, stream);
|
|
}
|
|
|
|
if (require.main === module) {
|
|
const config = require("./configuration.js");
|
|
const HISTORY_FILE =
|
|
process.argv[2] || path.join(config.HISTORY_DIR, "board-anonymous.json");
|
|
|
|
renderBoard(HISTORY_FILE, process.stdout).catch(console.error.bind(console));
|
|
} else {
|
|
module.exports = { renderBoard: renderBoard };
|
|
}
|