Skip to content

Commit

Permalink
feat: add image resolution
Browse files Browse the repository at this point in the history
  • Loading branch information
jynolen committed Dec 3, 2024
1 parent 65e9231 commit 88014da
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 28 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ target/
/node_modules/
dependency-reduced-pom.xml
.idea
**/.vscode
7 changes: 4 additions & 3 deletions diagrams.net/src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// must be declared first
import { logger } from './logger.js'
import http from 'node:http'
import {TimeoutError as PuppeteerTimeoutError} from 'puppeteer'
import { TimeoutError as PuppeteerTimeoutError } from 'puppeteer'
import micro from 'micro'
import Task from './task.js'
import { create } from './browser-instance.js'
Expand All @@ -23,7 +23,8 @@ import { SyntaxError, TimeoutError, Worker } from './worker.js'
if (diagramSource) {
try {
const isPng = outputType === 'png'
const output = await worker.convert(new Task(diagramSource, isPng))
const krokiUnsafe = (process.env.KROKI_SAFE_MODE ?? 'secure').toLowerCase() === 'unsafe'
const output = await worker.convert(new Task(diagramSource, isPng, krokiUnsafe))
res.setHeader('Content-Type', isPng ? 'image/png' : 'image/svg+xml')
return micro.send(res, 200, output)
} catch (err) {
Expand Down Expand Up @@ -72,7 +73,7 @@ import { SyntaxError, TimeoutError, Worker } from './worker.js'
})
})
)
server.listen(8005)
server.listen(9000)
})().catch(err => {
logger.error({ err }, 'Unable to start the service')
process.exit(1)
Expand Down
3 changes: 2 additions & 1 deletion diagrams.net/src/task.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export default class Task {
constructor (source, isPng = false) {
constructor (source, isPng = false, isUnsafe = false) {
this.source = source
this.isPng = isPng
this.isUnsafe = isUnsafe
}
}
64 changes: 40 additions & 24 deletions diagrams.net/src/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import path from 'node:path'
import { URL, fileURLToPath } from 'node:url'
import puppeteer from 'puppeteer'

import { logger } from './logger.js'

const __dirname = fileURLToPath(new URL('.', import.meta.url))
Expand All @@ -15,8 +14,10 @@ export class TimeoutError extends Error {

export class SyntaxError extends Error {
constructor (err) {
logger.error({ err })
super(`Syntax error in graph: ${JSON.stringify(err)}`)
super('Syntax error in graph', { cause: err })
logger.error(this)
this.name = 'SyntaxError'
this.message = err.message
}
}

Expand All @@ -29,6 +30,36 @@ export class Worker {

/**
*
* @param {string} source
* @param {boolean} performResolveImage
* @returns {Promise<string|Buffer>}
*/
async browserRender (source, performResolveImage) {
const resolveImage = async function (svg) {
for (const img of await svg.querySelectorAll('image')) {
if (img.attributes['xlink:href'].value.startsWith('data:')) {
continue
}
const imgb64 = await fetch(img.attributes['xlink:href'].value).then(async (value) => {
const mimeType = value.headers.get('content-type')
const b64img = btoa(String.fromCharCode(...new Uint8Array(await value.arrayBuffer())))
return `data:${mimeType};base64,${b64img}`
})
img.setAttribute('xlink:href', imgb64)
img.removeAttribute('pointer-events')
}
return svg
}
const s = new XMLSerializer()
let svgRoot = render({ // eslint-disable-line no-undef
xml: source,
format: 'svg'
}).getSvg()
svgRoot = performResolveImage ? await resolveImage(svgRoot) : svgRoot
return s.serializeToString(svgRoot)
}

/**
* @param task
* @returns {Promise<string|Buffer>}
*/
Expand All @@ -38,36 +69,21 @@ export class Worker {
ignoreHTTPSErrors: true
})
const page = await browser.newPage()
page.on('console', msg => {
console.log(msg.text())
})
try {
await page.setViewport({ height: 800, width: 600 })
await page.goto(this.pageUrl)
const evalResult = await Promise.race([
page.evaluate((source) => {
/* global render */
try {
const svgRoot = render({
xml: source,
format: 'svg'
}).getSvg()
const s = new XMLSerializer()
return { svg: s.serializeToString(svgRoot), error: null }
} catch (err) {
logger.log({ err })
return { svg: null, error: err }
}
}, task.source),
page.evaluate(this.browserRender, task.source, task.isUnsafe).catch((err) => { throw new SyntaxError(err) }),
new Promise((resolve, reject) => setTimeout(() => reject(new TimeoutError(this.convertTimeout)), this.convertTimeout))
])

if (evalResult && evalResult.error) {
throw new SyntaxError(evalResult.error)
}

// const bounds = await page.mainFrame().$eval('#LoadingComplete', div => div.getAttribute('bounds'))
// const pageId = await page.mainFrame().$eval('#LoadingComplete', div => div.getAttribute('page-id'))
// const scale = await page.mainFrame().$eval('#LoadingComplete', div => div.getAttribute('scale'))
// const pageCount = parseInt(await page.mainFrame().$eval('#LoadingComplete', div => div.getAttribute('pageCount')))

if (task.isPng) {
await page.setContent(`<!DOCTYPE html>
<html>
Expand All @@ -76,7 +92,7 @@ export class Worker {
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
${evalResult.svg}
${evalResult}
</body>
</html>`)
const container = await page.$('svg')
Expand All @@ -85,7 +101,7 @@ ${evalResult.svg}
omitBackground: true
}))
} else {
return evalResult.svg
return evalResult
}
} finally {
try {
Expand Down

0 comments on commit 88014da

Please sign in to comment.