From 583095d1ec32943d9a037726c8ecf9d4171e6ddd Mon Sep 17 00:00:00 2001 From: Andrew Lovett-Barron Date: Thu, 4 Jul 2024 12:38:54 +0200 Subject: [PATCH] Mostly got this set up, but trying to figure out ts errors --- package-lock.json | 5 +- package.json | 2 + src/embed.ts | 97 +++++++++++---------------------- src/jsoncanvas.ts | 135 +++++++++++++++++----------------------------- 4 files changed, 84 insertions(+), 155 deletions(-) diff --git a/package-lock.json b/package-lock.json index 60dfbf6..2d23b67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,8 @@ "@types/hast-format": "^2.3.0", "hast-util-to-html": "^9.0.1", "hastscript": "^9.0.0", + "mdast-util-from-markdown": "^2.0.1", + "mdast-util-to-hast": "^13.2.0", "rehype-stringify": "^10.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.0", @@ -6140,7 +6142,6 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.1.tgz", "integrity": "sha512-hZOofyZANbyWo+9RP75xIDV/gq+OUKx+T46IlwERnKmfpwp81XBFbT9mi26ws+SJchA4RVUQwIBJpqEOBhMzEQ==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", @@ -7522,7 +7523,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz", "integrity": "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==", - "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", @@ -7546,7 +7546,6 @@ "version": "13.2.0", "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", diff --git a/package.json b/package.json index 941f012..19e24d5 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,8 @@ "@types/hast-format": "^2.3.0", "hast-util-to-html": "^9.0.1", "hastscript": "^9.0.0", + "mdast-util-from-markdown": "^2.0.1", + "mdast-util-to-hast": "^13.2.0", "rehype-stringify": "^10.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.0", diff --git a/src/embed.ts b/src/embed.ts index 644af31..f8ea8c6 100644 --- a/src/embed.ts +++ b/src/embed.ts @@ -1,14 +1,9 @@ -import { unified } from "unified"; -import parse from "remark-parse"; -import remark2rehype from "remark-rehype"; -import rehypeStringify from "rehype-stringify"; +import { s } from "hastscript"; +import { Element } from "hast"; -import * as path from "path"; -import { Processor, Transformer } from "unified"; -import { Node, Parent } from "unist"; -import { VFile } from "vfile"; -import { h, s } from "hastscript"; -import { Element as SvgElement } from "hast-format"; +import { toHtml } from "hast-util-to-html"; +import { fromMarkdown } from "mdast-util-from-markdown"; +import { toHast } from "mdast-util-to-hast"; import { GenericNode } from "@trbn/jsoncanvas"; // import { applyDefaults, Options } from "./options"; @@ -25,75 +20,43 @@ export function checkImagesLoaded(callback: Function) { } // This renders out the images -export async function drawEmbedded(svg: SvgElement, node: GenericNode | any) { - if (node.type === "file" && canvas) { +export async function drawEmbedded(svg: Element, node: GenericNode | any) { + if (node.type === "file" && svg) { if (node.file.match(/\.(jpg|jpeg|png|gif)$/i)) { - const drawImg = new Image() as any; - const img = await loadImage(node.file); - - drawImg.onload = () => { - ctx.drawImage( - drawImg, - node.x + canvas.width / 2, - node.y + canvas.height / 2, - node.width, - node.height - ); - }; - drawImg.src = img.src; - imagesLoaded.push(drawImg); + const image = s("image", { + x: node.x, + y: node.y, + width: node.width, + height: node.height, + "xlink:href": node.file, + }); + + svg.children.push(image); } } } // This renders out the images -export async function drawMarkdownEmbed( - canvas: Canvas, - ctx: CanvasRenderingContext2D, - node: GenericNode | any -) { - if (node.type === "file" && canvas) { +export async function drawMarkdownEmbed(svg: Element, node: GenericNode | any) { + if (node.type === "file" && svg) { if (node.file.match(/\.(md|mdx)$/i)) { const resp = await fetch(node.file); const mdFile = await resp.text(); - const renderedMarkdown = await unified() - .use(parse) - .use(remark2rehype) // Convert Markdown to HTML - .use(rehypeStringify) - .process(mdFile); - - const htmlString = String(renderedMarkdown); - const div = document.createElement("div"); - div.innerHTML = htmlString; - - div.style.width = `${node.width}px`; - div.style.height = `${node.height}px`; - div.style.color = "black"; - div.style.backgroundColor = "red"; - div.style.position = "absolute"; - // div.style.left = "-9999px"; - document.body.appendChild(div); - - // Use html2canvas to render the div to an image - const canvasElement = await html2canvas(div); - const img = new Image(node.width, node.height) as any; - img.onload = async () => { - await ctx.drawImage( - img, - node.x + canvas.width / 2, - node.y + canvas.height / 2, - node.width, - node.height - ); + const mdast = fromMarkdown(mdFile); + const hast = toHast(mdast); + const html = toHtml(hast); - console.log("Markdown", img); + // Ref: https://stackoverflow.com/questions/45518545/svg-foreignobject-not-showing-on-any-browser-why + const embed = s("foreignObject", { + x: node.x, + y: node.y, + width: node.width, + height: node.height, + }); + embed.children.push(hast as Element); // If this breaks, this is probably the spot it breaks - // Cleanup - document.body.removeChild(div); - }; - img.src = canvasElement.toDataURL(); - imagesLoaded.push(img); + svg.children.push(embed); } } } diff --git a/src/jsoncanvas.ts b/src/jsoncanvas.ts index e741bb0..ca8bb1b 100644 --- a/src/jsoncanvas.ts +++ b/src/jsoncanvas.ts @@ -3,7 +3,7 @@ import { Processor, Transformer } from "unified"; import { Node, Parent } from "unist"; import { VFile } from "vfile"; import { h, s } from "hastscript"; -import { Element as SvgElement } from "hast-format"; +import { Element } from "hast"; import { JSONCanvas, Edge, GenericNode } from "@trbn/jsoncanvas"; @@ -46,7 +46,7 @@ export function render( calculateMinimumCanvasSize(jsc); // Init Canvas objects - const svg = initRender("jsc", canvasWidth + offsetX, canvasHeight + offsetY); + const svg = initRender(canvasWidth + offsetX, canvasHeight + offsetY); if (svg === null) return null; @@ -63,12 +63,12 @@ export function render( drawEdge(svg, toNode, fromNode, edge, options); }); - return checkImagesLoaded(() => renderToBuffer(canvas)); + return checkImagesLoaded(() => renderToBuffer(svg)); } -function renderToBuffer(config?: Partial) { +function renderToBuffer(svg: Element, config?: Partial) { const options = applyDefaults(config); - console.log; + console.log("Rendering", svg, options); return null; } @@ -76,7 +76,7 @@ function initRender( width: number, height: number, config?: Partial -): SvgElement { +): Element { const options = applyDefaults(config); const BASE_SVG_PROPS = { @@ -100,12 +100,11 @@ function initRender( const svg = s("svg", props); - return; - svg; + return svg; } async function drawNode( - svg: SvgElement, + svg: Element, node: GenericNode | any, config?: Partial ) { @@ -134,93 +133,88 @@ async function drawNode( strokeStyle = "rgba(100,10,100,1)"; } + const group = s("g"); + const rect = s("rect", { - x: node.x + svg.properties?.width / 2, - y: node.y + svg.properties?.height / 2, + x: node.x + svg.properties.width / 2, + y: node.y + svg.properties.height / 2, width: node.width, height: node.height, rx: 5, ry: 5, + stroke: strokeStyle, + fill: fillStyle, + "stroke-width": options.lineStrokeWidth, }); + group.children.push(rect); + drawEmbedded(svg, node); drawMarkdownEmbed(svg, node); - ctx.lineWidth = options.nodeStrokeWidth; - ctx.stroke(); - ctx.fill(); - ctx.closePath(); - - ctx.fillStyle = "rgba(0, 0, 0, 1)"; + // ctx.fillStyle = "rgba(0, 0, 0, 1)"; if (node.label) { - ctx.fillText( - node.label, - node.x + 5 + canvas.width / 2, - node.y + 20 + canvas.height / 2 - ); + // ctx.fillText( + // node.label, + // node.x + 5 + canvas.width / 2, + // node.y + 20 + canvas.height / 2 + // ); } if (node.type === "text" && node.text) { - ctx.fillText( - node.text, - node.x + 5 + canvas.width / 2, - node.y + 40 + canvas.height / 2 - ); + // ctx.fillText( + // node.text, + // node.x + 5 + canvas.width / 2, + // node.y + 40 + canvas.height / 2 + // ); } - svg.children.push(rect); + svg.children.push(group); } function drawEdge( - svg: SvgElement, + svg: Element, toNode: GenericNode, fromNode: GenericNode, edge: Edge | any, config?: Partial ) { const options = applyDefaults(config); - - ctx.lineWidth = options.lineStrokeWidth; - ctx.strokeStyle = "rgba(0,0,0,1)"; - - if (fromNode && toNode) { + if (svg === null || svg == undefined) return null; + else if (fromNode && toNode) { let startX = fromNode.x + (edge.fromSide == "top" || edge.fromSide == "bottom" ? fromNode.width / 2 : fromNode.width) + - canvas.width / 2; - let startY = fromNode.y + fromNode.height / 2 + canvas.height / 2; + svg.properties!.width / 2; + let startY = fromNode.y + fromNode.height / 2 + svg.properties.height / 2; let endX = toNode.x + (edge.toSide == "top" || edge.toSide == "bottom" ? toNode.width / 2 : toNode.width) + - canvas.width / 2; - let endY = toNode.y + toNode.height / 2 + canvas.height / 2; + svg.properties.width / 2; + let endY = toNode.y + toNode.height / 2 + svg.properties.height / 2; if (edge.fromSide === "left") { - startX = fromNode.x + canvas.width / 2; + startX = fromNode.x + svg.properties.width / 2; } else if (edge.fromSide === "top") { - startY = fromNode.y + canvas.height / 2; + startY = fromNode.y + svg.properties.height / 2; } else if (edge.fromSide === "bottom") { - startY = fromNode.y + fromNode.height + canvas.height / 2; + startY = fromNode.y + fromNode.height + svg.properties.height / 2; } if (edge.toSide === "right") { - endX = toNode.x + toNode.width + canvas.width / 2; + endX = toNode.x + toNode.width + svg.properties.width / 2; } else if (edge.toSide === "top") { - endY = toNode.y + canvas.height / 2; + endY = toNode.y + svg.properties.height / 2; } else if (edge.toSide === "bottom") { - endY = toNode.y + toNode.height + canvas.height / 2; + endY = toNode.y + toNode.height + svg.properties.height / 2; } else if (edge.toSide === "left") { - endX = toNode.x + canvas.width / 2; + endX = toNode.x + svg.properties.width / 2; } - ctx.beginPath(); - ctx.moveTo(startX, startY); - // ctx.lineTo(endX, endY); - // Change the control point logic based on fromSide/toSide const cp1 = { x: startX, @@ -232,41 +226,12 @@ function drawEdge( y: startY, }; - ctx.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, endX, endY); - ctx.stroke(); - ctx.closePath(); - - const t = 1.0; // At the end of the curve - const dx = - 3 * (1 - t) * (1 - t) * (cp1.x - startX) + - 6 * (1 - t) * t * (cp2.x - cp1.x) + - 3 * t * t * (endX - cp2.x); - const dy = - 3 * (1 - t) * (1 - t) * (cp1.y - startY) + - 6 * (1 - t) * t * (cp2.y - cp1.y) + - 3 * t * t * (endY - cp2.y); - const angle = Math.atan2(dy, dx); - - // Draw arrowhead - const headlen = 20; // length of head in pixels - ctx.beginPath(); - ctx.moveTo(endX, endY); - ctx.lineTo( - endX - headlen * Math.cos(angle - Math.PI / 6), - endY - headlen * Math.sin(angle - Math.PI / 6) - ); - ctx.lineTo( - endX - headlen * Math.cos(angle + Math.PI / 6), - endY - headlen * Math.sin(angle + Math.PI / 6) - ); - ctx.lineTo(endX, endY); - ctx.lineTo( - endX - headlen * Math.cos(angle - Math.PI / 6), - endY - headlen * Math.sin(angle - Math.PI / 6) - ); - ctx.stroke(); - ctx.fill(); + const line = s("path", { + d: `M ${startX} ${startY} C ${cp1.x} ${cp1.y}, ${cp2.x} ${cp2.y}, ${endX} ${endY}`, + stroke: "black", + "stroke-width": options.lineStrokeWidth, + fill: "none", + }); + svg.children.push(line); } - - svg.children.push(arrow); }