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", | ||||
|   "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", | ||||
|  |  | |||
							
								
								
									
										61
									
								
								src/main.ts
									
										
									
									
									
								
							
							
						
						
									
										61
									
								
								src/main.ts
									
										
									
									
									
								
							|  | @ -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
									
								
							
							
						
						
									
										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…
	
	Add table
		Add a link
		
	
		Reference in a new issue