您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

createSVG.js 4.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. const fs = require("./fs_promises.js"),
  2. path = require("path"),
  3. wboPencilPoint = require("../client-data/tools/pencil/wbo_pencil_point.js").wboPencilPoint;
  4. function htmlspecialchars(str) {
  5. if (typeof str !== "string") return "";
  6. return str.replace(/[<>&"']/g, function (c) {
  7. switch (c) {
  8. case '<': return '&lt;';
  9. case '>': return '&gt;';
  10. case '&': return '&amp;';
  11. case '"': return '&quot;';
  12. case "'": return '&#39;';
  13. }
  14. });
  15. }
  16. function renderPath(el, pathstring) {
  17. return '<path ' +
  18. (el.id ?
  19. ('id="' + htmlspecialchars(el.id) + '" ') : '') +
  20. 'stroke-width="' + (el.size | 0) + '" ' +
  21. (el.opacity ?
  22. ('opacity="' + parseFloat(el.opacity) + '" ') : '') +
  23. 'stroke="' + htmlspecialchars(el.color) + '" ' +
  24. 'd="' + pathstring + '" ' +
  25. (el.deltax || el.deltay ?
  26. ('transform="translate(' + (+el.deltax) + ',' + (+el.deltay) + ')"') : '') +
  27. '/>';
  28. }
  29. const Tools = {
  30. /**
  31. * @return {string}
  32. */
  33. "Text": function (el) {
  34. return '<text ' +
  35. 'id="' + htmlspecialchars(el.id || "t") + '" ' +
  36. 'x="' + (el.x | 0) + '" ' +
  37. 'y="' + (el.y | 0) + '" ' +
  38. 'font-size="' + (el.size | 0) + '" ' +
  39. 'fill="' + htmlspecialchars(el.color || "#000") + '" ' +
  40. (el.deltax || el.deltay ? ('transform="translate(' + (el.deltax || 0) + ',' + (el.deltay || 0) + ')"') : '') +
  41. '>' + htmlspecialchars(el.txt || "") + '</text>';
  42. },
  43. /**
  44. * @return {string}
  45. */
  46. "Pencil": function (el) {
  47. if (!el._children) return "";
  48. let pts = el._children.reduce(function (pts, point) {
  49. return wboPencilPoint(pts, point.x, point.y);
  50. }, []);
  51. const pathstring = pts.map(function (op) {
  52. return op.type + ' ' + op.values.join(' ')
  53. }).join(' ');
  54. return renderPath(el, pathstring);
  55. },
  56. /**
  57. * @return {string}
  58. */
  59. "Rectangle": function (el) {
  60. return '<rect ' +
  61. (el.id ?
  62. ('id="' + htmlspecialchars(el.id) + '" ') : '') +
  63. 'x="' + (el.x || 0) + '" ' +
  64. 'y="' + (el.y || 0) + '" ' +
  65. 'width="' + (el.x2 - el.x) + '" ' +
  66. 'height="' + (el.y2 - el.y) + '" ' +
  67. 'stroke="' + htmlspecialchars(el.color) + '" ' +
  68. 'stroke-width="' + (el.size | 0) + '" ' +
  69. (el.deltax || el.deltay ? ('transform="translate(' + (el.deltax || 0) + ',' + (el.deltay || 0) + ')"') : '') +
  70. '/>';
  71. },
  72. /**
  73. * @return {string}
  74. */
  75. "Ellipse": function (el) {
  76. const cx = Math.round((el.x2 + el.x) / 2);
  77. const cy = Math.round((el.y2 + el.y) / 2);
  78. const rx = Math.abs(el.x2 - el.x) / 2;
  79. const ry = Math.abs(el.y2 - el.y) / 2;
  80. const pathstring =
  81. "M" + (cx - rx) + " " + cy +
  82. "a" + rx + "," + ry + " 0 1,0 " + (rx * 2) + ",0" +
  83. "a" + rx + "," + ry + " 0 1,0 " + (rx * -2) + ",0";
  84. return renderPath(el, pathstring);
  85. },
  86. /**
  87. * @return {string}
  88. */
  89. "Straight line": function (el) {
  90. const pathstring = "M" + el.x + " " + el.y + "L" + el.x2 + " " + el.y2;
  91. return renderPath(el, pathstring);
  92. }
  93. };
  94. /**
  95. * Writes the given board as an svg to the given writeable stream
  96. * @param {Object[string, BoardElem]} obj
  97. * @param {WritableStream} writeable
  98. */
  99. async function toSVG(obj, writeable) {
  100. const margin = 400;
  101. const elems = Object.values(obj);
  102. const dim = elems.reduce(function (dim, elem) {
  103. if (elem._children) elem = elem._children[0];
  104. return [
  105. Math.max(elem.x + margin + elem.deltax | 0, dim[0]),
  106. Math.max(elem.y + margin + elem.deltay | 0, dim[1]),
  107. ]
  108. }, [margin, margin]);
  109. writeable.write(
  110. '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" ' +
  111. 'width="' + dim[0] + '" height="' + dim[1] + '">' +
  112. '<defs><style type="text/css"><![CDATA[' +
  113. 'text {font-family:"Arial"}' +
  114. 'path {fill:none;stroke-linecap:round;stroke-linejoin:round;}' +
  115. 'rect {fill:none}' +
  116. ']]></style></defs>'
  117. );
  118. await Promise.all(elems.map(async function (elem) {
  119. await Promise.resolve(); // Do not block the event loop
  120. const renderFun = Tools[elem.tool];
  121. if (renderFun) writeable.write(renderFun(elem));
  122. else console.warn("Missing render function for tool", elem.tool);
  123. }));
  124. writeable.write('</svg>');
  125. }
  126. async function renderBoard(file, stream) {
  127. const data = await fs.promises.readFile(file);
  128. var board = JSON.parse(data);
  129. return toSVG(board, stream);
  130. }
  131. if (require.main === module) {
  132. const config = require("./configuration.js");
  133. const HISTORY_FILE = process.argv[2] || path.join(config.HISTORY_DIR, "board-anonymous.json");
  134. renderBoard(HISTORY_FILE, process.stdout)
  135. .catch(console.error.bind(console));
  136. } else {
  137. module.exports = { 'renderBoard': renderBoard };
  138. }