clone

a browser-first diagram editor with live svg previews, local drafts and encrypted, accountless sharing

main
diagramzip / renderers / edge / artifacts / graphviz / graphviz-wrapper.ts
graphviz-wrapper.ts 9.1K
1// @ts-nocheck2import { parseStderrMessages, parseAgerrMessages } from "./graphviz-errors";34export function getGraphvizVersion(module) {5 const resultPointer = module.ccall("viz_get_graphviz_version", "number", [], []);6 return module.UTF8ToString(resultPointer);7}89export function getPluginList(module, kind) {10 const resultPointer = module.ccall("viz_get_plugin_list", "number", ["string"], [kind]);1112 if (resultPointer == 0) {13 throw new Error(`couldn't get plugin list: ${kind}`);14 }1516 const list = [];17 let itemPointer = resultPointer;18 let stringPointer;1920 while (stringPointer = module.getValue(itemPointer, "*")) {21 list.push(module.UTF8ToString(stringPointer));22 module.ccall("free", "number", ["number"], [stringPointer]);23 itemPointer += 4;24 }2526 module.ccall("free", "number", ["number"], [resultPointer]);2728 return list;29}3031export function renderInput(module, input, formats, options) {32 let graphPointer, contextPointer, resultPointer, imageFilePaths;3334 try {35 module["agerrMessages"] = [];36 module["stderrMessages"] = [];3738 imageFilePaths = createImageFiles(module, options.images);3940 if (typeof input === "string") {41 graphPointer = readStringInput(module, input, options);42 } else if (typeof input === "object") {43 graphPointer = readObjectInput(module, input, options);44 } else {45 throw new Error("input must be a string or object");46 }4748 if (graphPointer === 0) {49 return {50 status: "failure",51 output: undefined,52 errors: parseErrorMessages(module)53 };54 }5556 setDefaultAttributes(module, graphPointer, options);5758 module.ccall("viz_set_y_invert", "number", ["number"], [options.yInvert ? 1 : 0]);59 module.ccall("viz_set_reduce", "number", ["number"], [options.reduce ? 1 : 0]);6061 contextPointer = module.ccall("viz_create_context");6263 module.ccall("viz_reset_errors");6465 let layoutError = module.ccall("viz_layout", "number", ["number", "number", "string"], [contextPointer, graphPointer, options.engine]);6667 if (layoutError !== 0) {68 return {69 status: "failure",70 output: undefined,71 errors: parseErrorMessages(module)72 };73 }7475 let output = {};7677 for (let format of formats) {78 resultPointer = module.ccall("viz_render", "number", ["number", "number", "string"], [contextPointer, graphPointer, format]);7980 if (resultPointer === 0) {81 return {82 status: "failure",83 output: undefined,84 errors: parseErrorMessages(module)85 };86 } else {87 output[format] = module.UTF8ToString(resultPointer);8889 module.ccall("free", "number", ["number"], [resultPointer]);90 resultPointer = 0;91 }92 }9394 return {95 status: "success",96 output: output,97 errors: parseErrorMessages(module)98 };99 } catch (error) {100 if (/^exit\(\d+\)/.test(error)) {101 return {102 status: "failure",103 output: undefined,104 errors: parseErrorMessages(module)105 };106 } else {107 throw error;108 }109 } finally {110 if (contextPointer && graphPointer) {111 module.ccall("viz_free_layout", "number", ["number"], [contextPointer, graphPointer]);112 }113114 if (graphPointer) {115 module.ccall("viz_free_graph", "number", ["number"], [graphPointer]);116 }117118 if (contextPointer) {119 module.ccall("viz_free_context", "number", ["number"], [contextPointer]);120 }121122 if (resultPointer) {123 module.ccall("free", "number", ["number"], [resultPointer]);124 }125126 if (imageFilePaths) {127 removeImageFiles(module, imageFilePaths);128 }129 }130}131132function parseErrorMessages(module) {133 return parseAgerrMessages(module["agerrMessages"]).concat(parseStderrMessages(module["stderrMessages"]));134}135136function createImageFiles(module, images) {137 if (!images) {138 return [];139 }140141 for (const image of images) {142 if (typeof image.name !== "string") {143 throw new Error("image name must be a string");144 } else if (typeof image.width !== "number" && typeof image.width !== "string") {145 throw new Error("image width must be a number or string");146 } else if (typeof image.height !== "number" && typeof image.height !== "string") {147 throw new Error("image height must be a number or string");148 }149 }150151 return images.map(image => {152 const path = module.PATH.join("/", image.name);153 const data = `<?xml version="1.0" encoding="UTF-8"?>154<svg xmlns="http://www.w3.org/2000/svg" width="${image.width}" height="${image.height}"></svg>155`;156157 module.FS.createPath("/", module.PATH.dirname(path));158 module.FS.writeFile(path, data);159160 return path;161 });162}163164function removeImageFiles(module, imageFilePaths) {165 for (const path of imageFilePaths) {166 if (module.FS.analyzePath(path).exists) {167 module.FS.unlink(path);168 }169 }170}171172function readStringInput(module, src, options) {173 let srcPointer;174175 try {176 const srcLength = module.lengthBytesUTF8(src);177 srcPointer = module.ccall("malloc", "number", ["number"], [srcLength + 1]);178 module.stringToUTF8(src, srcPointer, srcLength + 1);179180 return module.ccall("viz_read_one_graph", "number", ["number"], [srcPointer]);181 } finally {182 if (srcPointer) {183 module.ccall("free", "number", ["number"], [srcPointer]);184 }185 }186}187188function readObjectInput(module, object, options) {189 const graphPointer = module.ccall("viz_create_graph", "number", ["string", "number", "number"], [object.name, typeof object.directed !== "undefined" ? object.directed : true, typeof object.strict !== "undefined" ? object.strict : false]);190191 readGraph(module, graphPointer, object);192193 return graphPointer;194}195196function readGraph(module, graphPointer, graphData) {197 if (graphData.nodes) {198 graphData.nodes.forEach(nodeData => {199 if (typeof nodeData.name === "undefined") {200 throw new Error("nodes must have a name");201 }202203 const nodePointer = module.ccall("viz_add_node", "number", ["number", "string"], [graphPointer, String(nodeData.name)]);204205 if (nodeData.attributes) {206 setAttributes(module, graphPointer, nodePointer, nodeData.attributes);207 }208 });209 }210211 if (graphData.edges) {212 graphData.edges.forEach(edgeData => {213 if (typeof edgeData.tail === "undefined") {214 throw new Error("edges must have a tail");215 }216217 if (typeof edgeData.head === "undefined") {218 throw new Error("edges must have a head");219 }220221 const edgePointer = module.ccall("viz_add_edge", "number", ["number", "string", "string"], [graphPointer, String(edgeData.tail), String(edgeData.head)]);222223 if (edgeData.attributes) {224 setAttributes(module, graphPointer, edgePointer, edgeData.attributes);225 }226 });227 }228229 if (graphData.subgraphs) {230 graphData.subgraphs.forEach(subgraphData => {231 const subgraphPointer = module.ccall("viz_add_subgraph", "number", ["number", "string"], [graphPointer, typeof subgraphData.name !== "undefined" ? String(subgraphData.name) : 0]);232233 readGraph(module, subgraphPointer, subgraphData);234 });235 }236237 setDefaultAttributes(module, graphPointer, graphData);238}239240function setDefaultAttributes(module, graphPointer, data) {241 if (data.graphAttributes) {242 for (const [name, value] of Object.entries(data.graphAttributes)) {243 withStringPointer(module, graphPointer, value, stringPointer => {244 module.ccall("viz_set_default_graph_attribute", "number", ["number", "string", "number"], [graphPointer, name, stringPointer]);245 });246 }247 }248249 if (data.nodeAttributes) {250 for (const [name, value] of Object.entries(data.nodeAttributes)) {251 withStringPointer(module, graphPointer, value, stringPointer => {252 module.ccall("viz_set_default_node_attribute", "number", ["number", "string", "number"], [graphPointer, name, stringPointer]);253 });254 }255 }256257 if (data.edgeAttributes) {258 for (const [name, value] of Object.entries(data.edgeAttributes)) {259 withStringPointer(module, graphPointer, value, stringPointer => {260 module.ccall("viz_set_default_edge_attribute", "number", ["number", "string", "number"], [graphPointer, name, stringPointer]);261 });262 }263 }264}265266function setAttributes(module, graphPointer, objectPointer, attributes) {267 for (const [key, value] of Object.entries(attributes)) {268 withStringPointer(module, graphPointer, value, stringPointer => {269 module.ccall("viz_set_attribute", "number", ["number", "string", "number"], [objectPointer, key, stringPointer]);270 });271 }272}273274function withStringPointer(module, graphPointer, value, callbackFn) {275 let stringPointer;276277 if (typeof value === "object" && "html" in value) {278 stringPointer = module.ccall("viz_string_dup_html", "number", ["number", "string"], [graphPointer, String(value.html)]);279 } else {280 stringPointer = module.ccall("viz_string_dup", "number", ["number", "string"], [graphPointer, String(value)]);281 }282283 if (stringPointer == 0) {284 throw new Error("couldn't dup string");285 }286287 callbackFn(stringPointer);288289 if (typeof value === "object" && "html" in value) {290 module.ccall("viz_string_free_html", "number", ["number", "number"], [graphPointer, stringPointer]);291 } else {292 module.ccall("viz_string_free", "number", ["number", "number"], [graphPointer, stringPointer]);293 }294}