v0.4.0 - use Web Worker to generate PDF faster

This commit is contained in:
Xmader 2019-12-01 18:30:38 -05:00
parent 3b038bf2e1
commit ac127c7b25
5 changed files with 25730 additions and 25379 deletions

50955
dist/main.js vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "musescore-downloader",
"version": "0.3.4",
"version": "0.4.0",
"description": "download sheet music from musescore.com for free, no login or Musescore Pro required | 免登录、免 Musescore Pro免费下载 musescore.com 上的曲谱",
"main": "dist/main.js",
"repository": {
@ -18,6 +18,7 @@
},
"devDependencies": {
"@rollup/plugin-json": "^4.0.0",
"@types/file-saver": "^2.0.1",
"@types/pdfkit": "^0.10.4",
"rollup": "^1.26.3",
"rollup-plugin-commonjs": "^10.1.0",

View File

@ -3,17 +3,19 @@ import "./meta"
import { ScorePlayerData } from "./types"
import { waitForDocumentLoaded } from "./utils"
import PDFDocument from "pdfkit/lib/document"
import saveAs from "file-saver/dist/FileSaver.js"
import { PDFWorkerHelper } from "./worker-helper"
import FileSaver from "file-saver/dist/FileSaver.js"
const saveAs: typeof import("file-saver").saveAs = FileSaver.saveAs
let pdfBlob: Blob
const svgToPng = async (svgURL: string) => {
const imgToBlob = async (imgURL: string) => {
const imageElement = document.createElement("img")
imageElement.style.display = "none"
document.body.appendChild(imageElement)
imageElement.src = svgURL
imageElement.src = imgURL
// wait until image loaded
await new Promise((resolve) => {
@ -34,7 +36,7 @@ const svgToPng = async (svgURL: string) => {
canvasContext.clearRect(0, 0, width, height)
canvasContext.drawImage(imageElement, 0, 0)
const data = canvas.toDataURL("image/png")
const data: Blob = await new Promise(resolve => canvas.toBlob(resolve, "image/png"))
canvas.remove()
imageElement.remove()
@ -50,32 +52,15 @@ const generatePDF = async (svgURLs: string[], name?: string) => {
const cachedImg = document.querySelector("img[id^=score_]") as HTMLImageElement
const { naturalWidth: width, naturalHeight: height } = cachedImg
const imgDataList = await Promise.all(svgURLs.map(svgToPng))
const imgDataBlobList = await Promise.all(svgURLs.map(imgToBlob))
// @ts-ignore
const pdf = new (PDFDocument as typeof import("pdfkit"))({
// compress: true,
size: [width, height],
autoFirstPage: false,
margin: 0,
layout: "portrait",
})
const worker = new PDFWorkerHelper()
const pdfArrayBuffer = await worker.generatePDF(imgDataBlobList, width, height)
worker.terminate()
imgDataList.forEach((data) => {
pdf.addPage()
pdf.image(data, {
width,
height,
})
})
pdfBlob = new Blob([pdfArrayBuffer])
// TODO: webworker
// @ts-ignore
return pdf.getBlob().then((blob: Blob) => {
pdfBlob = blob
saveAs(blob, `${name}.pdf`)
})
saveAs(pdfBlob, `${name}.pdf`)
}
const getPagesNumber = (scorePlayerData: ScorePlayerData) => {
@ -86,6 +71,17 @@ const getPagesNumber = (scorePlayerData: ScorePlayerData) => {
}
}
const getImgType = (): "svg" | "png" => {
try {
const imgE: HTMLImageElement = document.querySelector("img[id^=score_]")
const { pathname } = new URL(imgE.src)
const imgtype = pathname.match(/\.(\w+)$/)[1]
return imgtype as "svg" | "png"
} catch (_) {
return null
}
}
const getTitle = (scorePlayerData: ScorePlayerData) => {
try {
return scorePlayerData.json.metadata.title
@ -95,7 +91,7 @@ const getTitle = (scorePlayerData: ScorePlayerData) => {
}
const getScoreFileName = (scorePlayerData: ScorePlayerData) => {
return getTitle(scorePlayerData).replace(/\W+/g, "_")
return getTitle(scorePlayerData).replace(/[\s<>:"/\\|?*~\0\cA-\cZ]+/g, "_")
}
const main = () => {
@ -121,8 +117,10 @@ const main = () => {
const downloadBtn = btnsDiv.querySelector("button, .button") as HTMLElement
downloadBtn.onclick = null
const imgType = getImgType() || "svg"
const svgURLs = Array.from({ length: getPagesNumber(scorePlayer) }).fill(null).map((_, i) => {
return baseURL + `score_${i}.svg`
return baseURL + `score_${i}.${imgType}`
})
const downloadURLs = {
@ -143,8 +141,7 @@ const main = () => {
}
const textNode = [...btn.childNodes].find((x) => {
return x.nodeName.toLowerCase() == "#text"
&& x.textContent.includes("Download")
return x.textContent.includes("Download")
})
textNode.textContent = `Download ${name}`

29
src/worker-helper.ts Normal file
View File

@ -0,0 +1,29 @@
import { PDFWorkerMessage } from "./worker"
import { PDFWorker } from "../dist/cache/worker"
const scriptUrlFromFunction = (fn: Function) => {
const blob = new Blob(["(" + fn.toString() + ")()"], { type: "application/javascript" })
return URL.createObjectURL(blob)
}
export class PDFWorkerHelper extends Worker {
constructor() {
const url = scriptUrlFromFunction(PDFWorker)
super(url)
}
generatePDF(imgDataBlobList: Blob[], width: number, height: number): Promise<ArrayBuffer> {
const msg: PDFWorkerMessage = [
imgDataBlobList,
width,
height,
]
this.postMessage(msg)
return new Promise((resolve) => {
this.addEventListener("message", (e) => {
resolve(e.data)
})
})
}
}

61
src/worker.ts Normal file
View File

@ -0,0 +1,61 @@
/// <reference lib="webworker" />
import PDFDocument from "pdfkit/lib/document"
const generatePDF = async (imgDataUrlList: string[], width: number, height: number): Promise<ArrayBuffer> => {
// @ts-ignore
const pdf = new (PDFDocument as typeof import("pdfkit"))({
// compress: true,
size: [width, height],
autoFirstPage: false,
margin: 0,
layout: "portrait",
})
imgDataUrlList.forEach((data) => {
pdf.addPage()
pdf.image(data, {
width,
height,
})
})
// @ts-ignore
const buf: Uint8Array = await pdf.getBuffer()
return buf.buffer
}
const getDataURL = (blob: Blob): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => {
const result = reader.result
resolve(result as string)
}
reader.onerror = reject
reader.readAsDataURL(blob)
})
}
export type PDFWorkerMessage = [Blob[], number, number];
onmessage = async (e) => {
const [
imgDataBlobList,
width,
height,
] = e.data as PDFWorkerMessage
const dataURLs = await Promise.all(imgDataBlobList.map(getDataURL))
const pdfBuf = await generatePDF(
dataURLs,
width,
height,
)
postMessage(pdfBuf, [pdfBuf])
}