123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- 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 };
- }
|