Skip to content

Commit

Permalink
Improve compatibility & performance for svg in spx v1
Browse files Browse the repository at this point in the history
  • Loading branch information
nighca committed Jan 10, 2025
1 parent d8156a5 commit fc62030
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 26 deletions.
25 changes: 23 additions & 2 deletions spx-gui/src/components/project/runner/v1/ProjectRunnerV1.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@

<script lang="ts" setup>
import { onMounted, onUnmounted, ref } from 'vue'
import JSZip from 'jszip'
import { registerPlayer } from '@/utils/player-registry'
import { until } from '@/utils/utils'
import { useFileUrl } from '@/utils/file'
import { toPng } from '@/utils/img'
import { toNativeFile } from '@/models/common/file'
import { Project } from '@/models/project'
import { UIImg, UILoading } from '@/components/ui'
import IframeDisplay, { preload } from './IframeDisplay.vue'
Expand Down Expand Up @@ -44,12 +47,30 @@ onUnmounted(() => {
registered.onStopped()
})
async function getProjectZipData() {
const zip = new JSZip()
const [, files] = await props.project.export()
Object.entries(files).forEach(([path, file]) => {
if (file == null) return
zip.file(
path,
toNativeFile(file).then((nf) => {
// For svg files, we convert them to png before sending to spx (v1):
// 1. Compatibility: Many SVG features are not supported in spx v1
// 2. Improve performance: SVG rendering is slow in spx v1
if (nf.type === 'image/svg+xml') return toPng(nf)
return nf
})
)
})
return zip.generateAsync({ type: 'arraybuffer' })
}
defineExpose({
async run() {
loading.value = true
registered.onStart()
const gbpFile = await props.project.exportGbpFile()
zipData.value = await gbpFile.arrayBuffer()
zipData.value = await getProjectZipData()
await until(() => !loading.value)
},
stop() {
Expand Down
3 changes: 2 additions & 1 deletion spx-gui/src/components/project/runner/v2/ProjectRunnerV2.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { untilNotNull } from '@/utils/utils'
import { useFileUrl } from '@/utils/file'
import { registerPlayer } from '@/utils/player-registry'
import { addPrefetchLink } from '@/utils/dom'
import { toNativeFile } from '@/models/common/file'
import type { Project } from '@/models/project'
import { UIImg, UILoading } from '@/components/ui'
Expand Down Expand Up @@ -57,7 +58,7 @@ async function getProjectData() {
const zip = new JSZip()
const [, files] = await props.project.export()
Object.entries(files).forEach(([path, file]) => {
if (file != null) zip.file(path, file.arrayBuffer())
if (file != null) zip.file(path, toNativeFile(file))
})
return zip.generateAsync({ type: 'arraybuffer' })
}
Expand Down
20 changes: 5 additions & 15 deletions spx-gui/src/models/common/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { markRaw } from 'vue'
import { getMimeFromExt } from '@/utils/file'
import { getSVGSize } from '@/utils/img'
import { extname } from '@/utils/path'
import { Disposable, type Disposer } from '@/utils/disposable'
import { Cancelled } from '@/utils/exception'
Expand Down Expand Up @@ -122,20 +123,6 @@ export async function toConfig(file: File) {
return JSON.parse(text) as unknown
}

async function getSVGImageSize(svgFile: File) {
const svgText = await toText(svgFile)
const parser = new DOMParser()
const svg = parser.parseFromString(svgText, 'image/svg+xml').documentElement
if (!(svg instanceof SVGSVGElement)) throw new Error(`invalid svg: ${svgFile.name}`)
// Keep consistent with spx, for details see:
// * https://github.com/goplus/spx/blob/15b2e572746f3aaea519c2d9c0027188b50b62c8/internal/svgr/svg.go#L39
// * https://github.com/qiniu/oksvg/blob/917f53935572252ba3da8909ca4fbedec418bde1/svgd.go#L1015-L1049
let { width, height } = svg.viewBox.baseVal
if (width === 0) width = svg.width.baseVal.value
if (height === 0) height = svg.height.baseVal.value
return { width, height }
}

async function getBitmapImageSize(bitmapImgFile: File) {
const d = new Disposable()
const imgUrl = await bitmapImgFile.url((fn) => d.addDisposer(fn))
Expand All @@ -157,7 +144,10 @@ async function getBitmapImageSize(bitmapImgFile: File) {
}

export async function getImageSize(file: File) {
if (file.type === 'image/svg+xml') return getSVGImageSize(file)
if (file.type === 'image/svg+xml') {
const svgText = await toText(file)
return getSVGSize(svgText)
}
return getBitmapImageSize(file)
}

Expand Down
52 changes: 44 additions & 8 deletions spx-gui/src/utils/img.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,64 @@
import { Disposable } from '@/utils/disposable'

/** Convert arbitrary-type (supported by current browser) image content to type-`image/jpeg` content. */
export async function toJpeg(blob: Blob) {
/** Convert arbitrary-type (supported by current browser) image content to another type. */
export function convertImg(
/** Input image */
input: Blob,
/** Mime type of the output image, see details in https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#type */
type: string
) {
const d = new Disposable()
return new Promise<Blob>((resolve, reject) => {
const img = new Image()
img.onload = () => {
img.onload = async () => {
const canvas = document.createElement('canvas')
canvas.width = img.naturalWidth
canvas.height = img.naturalHeight
canvas.getContext('2d')?.drawImage(img, 0, 0)
if (input.type === 'image/svg+xml') {
const svgText = await input.text()
const { width, height } = await getSVGSize(svgText)
canvas.width = width
canvas.height = height
} else {
canvas.width = img.naturalWidth
canvas.height = img.naturalHeight
}
canvas.getContext('2d')?.drawImage(img, 0, 0, canvas.width, canvas.height)
canvas.toBlob((newBlob) => {
if (newBlob == null) {
reject(new Error('toBlob failed'))
return
}
resolve(newBlob)
}, 'image/jpeg')
}, type)
}
img.onerror = (e) => reject(new Error(`load image failed: ${e.toString()}`))
const url = URL.createObjectURL(blob)
const url = URL.createObjectURL(input)
d.addDisposer(() => URL.revokeObjectURL(url))
img.src = url
}).finally(() => {
d.dispose()
})
}

/** Convert arbitrary-type (supported by current browser) image content to type-`image/jpeg` content. */
export function toJpeg(blob: Blob) {
return convertImg(blob, 'image/jpeg')
}

/** Convert arbitrary-type (supported by current browser) image content to type-`image/png` content. */
export async function toPng(blob: Blob) {
return convertImg(blob, 'image/png')
}

/** Get the size of the SVG image, keeping consistent with spx. */
export async function getSVGSize(svgText: string) {
const parser = new DOMParser()
const svg = parser.parseFromString(svgText, 'image/svg+xml').documentElement
if (!(svg instanceof SVGSVGElement)) throw new Error('invalid svg')
// Keep consistent with spx, for details see:
// * https://github.com/goplus/spx/blob/15b2e572746f3aaea519c2d9c0027188b50b62c8/internal/svgr/svg.go#L39
// * https://github.com/qiniu/oksvg/blob/917f53935572252ba3da8909ca4fbedec418bde1/svgd.go#L1015-L1049
let { width, height } = svg.viewBox.baseVal
if (width === 0) width = svg.width.baseVal.value
if (height === 0) height = svg.height.baseVal.value
return { width, height }
}

0 comments on commit fc62030

Please sign in to comment.