v0.4.0 - use Web Worker to generate PDF faster
This commit is contained in:
parent
3b038bf2e1
commit
ac127c7b25
5 changed files with 25730 additions and 25379 deletions
50955
dist/main.js
vendored
50955
dist/main.js
vendored
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "musescore-downloader",
|
"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 上的曲谱",
|
"description": "download sheet music from musescore.com for free, no login or Musescore Pro required | 免登录、免 Musescore Pro,免费下载 musescore.com 上的曲谱",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -18,6 +18,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-json": "^4.0.0",
|
"@rollup/plugin-json": "^4.0.0",
|
||||||
|
"@types/file-saver": "^2.0.1",
|
||||||
"@types/pdfkit": "^0.10.4",
|
"@types/pdfkit": "^0.10.4",
|
||||||
"rollup": "^1.26.3",
|
"rollup": "^1.26.3",
|
||||||
"rollup-plugin-commonjs": "^10.1.0",
|
"rollup-plugin-commonjs": "^10.1.0",
|
||||||
|
|
61
src/main.ts
61
src/main.ts
|
@ -3,17 +3,19 @@ import "./meta"
|
||||||
import { ScorePlayerData } from "./types"
|
import { ScorePlayerData } from "./types"
|
||||||
import { waitForDocumentLoaded } from "./utils"
|
import { waitForDocumentLoaded } from "./utils"
|
||||||
|
|
||||||
import PDFDocument from "pdfkit/lib/document"
|
import { PDFWorkerHelper } from "./worker-helper"
|
||||||
import saveAs from "file-saver/dist/FileSaver.js"
|
import FileSaver from "file-saver/dist/FileSaver.js"
|
||||||
|
|
||||||
|
const saveAs: typeof import("file-saver").saveAs = FileSaver.saveAs
|
||||||
|
|
||||||
let pdfBlob: Blob
|
let pdfBlob: Blob
|
||||||
|
|
||||||
const svgToPng = async (svgURL: string) => {
|
const imgToBlob = async (imgURL: string) => {
|
||||||
const imageElement = document.createElement("img")
|
const imageElement = document.createElement("img")
|
||||||
imageElement.style.display = "none"
|
imageElement.style.display = "none"
|
||||||
document.body.appendChild(imageElement)
|
document.body.appendChild(imageElement)
|
||||||
|
|
||||||
imageElement.src = svgURL
|
imageElement.src = imgURL
|
||||||
|
|
||||||
// wait until image loaded
|
// wait until image loaded
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
|
@ -34,7 +36,7 @@ const svgToPng = async (svgURL: string) => {
|
||||||
canvasContext.clearRect(0, 0, width, height)
|
canvasContext.clearRect(0, 0, width, height)
|
||||||
canvasContext.drawImage(imageElement, 0, 0)
|
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()
|
canvas.remove()
|
||||||
imageElement.remove()
|
imageElement.remove()
|
||||||
|
@ -50,32 +52,15 @@ const generatePDF = async (svgURLs: string[], name?: string) => {
|
||||||
const cachedImg = document.querySelector("img[id^=score_]") as HTMLImageElement
|
const cachedImg = document.querySelector("img[id^=score_]") as HTMLImageElement
|
||||||
const { naturalWidth: width, naturalHeight: height } = cachedImg
|
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 worker = new PDFWorkerHelper()
|
||||||
const pdf = new (PDFDocument as typeof import("pdfkit"))({
|
const pdfArrayBuffer = await worker.generatePDF(imgDataBlobList, width, height)
|
||||||
// compress: true,
|
worker.terminate()
|
||||||
size: [width, height],
|
|
||||||
autoFirstPage: false,
|
|
||||||
margin: 0,
|
|
||||||
layout: "portrait",
|
|
||||||
})
|
|
||||||
|
|
||||||
imgDataList.forEach((data) => {
|
pdfBlob = new Blob([pdfArrayBuffer])
|
||||||
pdf.addPage()
|
|
||||||
pdf.image(data, {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: webworker
|
saveAs(pdfBlob, `${name}.pdf`)
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
return pdf.getBlob().then((blob: Blob) => {
|
|
||||||
pdfBlob = blob
|
|
||||||
saveAs(blob, `${name}.pdf`)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPagesNumber = (scorePlayerData: ScorePlayerData) => {
|
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) => {
|
const getTitle = (scorePlayerData: ScorePlayerData) => {
|
||||||
try {
|
try {
|
||||||
return scorePlayerData.json.metadata.title
|
return scorePlayerData.json.metadata.title
|
||||||
|
@ -95,7 +91,7 @@ const getTitle = (scorePlayerData: ScorePlayerData) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const getScoreFileName = (scorePlayerData: ScorePlayerData) => {
|
const getScoreFileName = (scorePlayerData: ScorePlayerData) => {
|
||||||
return getTitle(scorePlayerData).replace(/\W+/g, "_")
|
return getTitle(scorePlayerData).replace(/[\s<>:"/\\|?*~\0\cA-\cZ]+/g, "_")
|
||||||
}
|
}
|
||||||
|
|
||||||
const main = () => {
|
const main = () => {
|
||||||
|
@ -121,8 +117,10 @@ const main = () => {
|
||||||
const downloadBtn = btnsDiv.querySelector("button, .button") as HTMLElement
|
const downloadBtn = btnsDiv.querySelector("button, .button") as HTMLElement
|
||||||
downloadBtn.onclick = null
|
downloadBtn.onclick = null
|
||||||
|
|
||||||
|
const imgType = getImgType() || "svg"
|
||||||
|
|
||||||
const svgURLs = Array.from({ length: getPagesNumber(scorePlayer) }).fill(null).map((_, i) => {
|
const svgURLs = Array.from({ length: getPagesNumber(scorePlayer) }).fill(null).map((_, i) => {
|
||||||
return baseURL + `score_${i}.svg`
|
return baseURL + `score_${i}.${imgType}`
|
||||||
})
|
})
|
||||||
|
|
||||||
const downloadURLs = {
|
const downloadURLs = {
|
||||||
|
@ -143,8 +141,7 @@ const main = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const textNode = [...btn.childNodes].find((x) => {
|
const textNode = [...btn.childNodes].find((x) => {
|
||||||
return x.nodeName.toLowerCase() == "#text"
|
return x.textContent.includes("Download")
|
||||||
&& x.textContent.includes("Download")
|
|
||||||
})
|
})
|
||||||
textNode.textContent = `Download ${name}`
|
textNode.textContent = `Download ${name}`
|
||||||
|
|
||||||
|
|
29
src/worker-helper.ts
Normal file
29
src/worker-helper.ts
Normal 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
61
src/worker.ts
Normal 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])
|
||||||
|
}
|
Loading…
Reference in a new issue