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
349
dist/main.js
vendored
349
dist/main.js
vendored
|
@ -3,7 +3,7 @@
|
||||||
// @namespace https://www.xmader.com/
|
// @namespace https://www.xmader.com/
|
||||||
// @homepageURL https://github.com/Xmader/musescore-downloader/
|
// @homepageURL https://github.com/Xmader/musescore-downloader/
|
||||||
// @supportURL https://github.com/Xmader/musescore-downloader/issues
|
// @supportURL https://github.com/Xmader/musescore-downloader/issues
|
||||||
// @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 上的曲谱
|
||||||
// @author Xmader
|
// @author Xmader
|
||||||
// @match https://musescore.com/*/*
|
// @match https://musescore.com/*/*
|
||||||
|
@ -43,6 +43,22 @@
|
||||||
typeof self !== "undefined" ? self :
|
typeof self !== "undefined" ? self :
|
||||||
typeof window !== "undefined" ? window : {});
|
typeof window !== "undefined" ? window : {});
|
||||||
|
|
||||||
|
const PDFWorker = function () {
|
||||||
|
(function () {
|
||||||
|
|
||||||
|
function __awaiter(thisArg, _arguments, P, generator) {
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var global$1$1 = (typeof global$1 !== "undefined" ? global$1 :
|
||||||
|
typeof self !== "undefined" ? self :
|
||||||
|
typeof window !== "undefined" ? window : {});
|
||||||
|
|
||||||
var lookup = [];
|
var lookup = [];
|
||||||
var revLookup = [];
|
var revLookup = [];
|
||||||
var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array;
|
var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array;
|
||||||
|
@ -270,8 +286,8 @@
|
||||||
* We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they
|
* We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they
|
||||||
* get the Object implementation, which is slower but behaves correctly.
|
* get the Object implementation, which is slower but behaves correctly.
|
||||||
*/
|
*/
|
||||||
Buffer.TYPED_ARRAY_SUPPORT = global$1.TYPED_ARRAY_SUPPORT !== undefined
|
Buffer.TYPED_ARRAY_SUPPORT = global$1$1.TYPED_ARRAY_SUPPORT !== undefined
|
||||||
? global$1.TYPED_ARRAY_SUPPORT
|
? global$1$1.TYPED_ARRAY_SUPPORT
|
||||||
: true;
|
: true;
|
||||||
|
|
||||||
function kMaxLength () {
|
function kMaxLength () {
|
||||||
|
@ -2481,10 +2497,10 @@
|
||||||
}
|
}
|
||||||
var cachedSetTimeout = defaultSetTimout;
|
var cachedSetTimeout = defaultSetTimout;
|
||||||
var cachedClearTimeout = defaultClearTimeout;
|
var cachedClearTimeout = defaultClearTimeout;
|
||||||
if (typeof global$1.setTimeout === 'function') {
|
if (typeof global$1$1.setTimeout === 'function') {
|
||||||
cachedSetTimeout = setTimeout;
|
cachedSetTimeout = setTimeout;
|
||||||
}
|
}
|
||||||
if (typeof global$1.clearTimeout === 'function') {
|
if (typeof global$1$1.clearTimeout === 'function') {
|
||||||
cachedClearTimeout = clearTimeout;
|
cachedClearTimeout = clearTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2605,7 +2621,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
// from https://github.com/kumavis/browser-process-hrtime/blob/master/index.js
|
// from https://github.com/kumavis/browser-process-hrtime/blob/master/index.js
|
||||||
var performance = global$1.performance || {};
|
var performance = global$1$1.performance || {};
|
||||||
var performanceNow =
|
var performanceNow =
|
||||||
performance.now ||
|
performance.now ||
|
||||||
performance.mozNow ||
|
performance.mozNow ||
|
||||||
|
@ -2683,7 +2699,7 @@
|
||||||
// If --no-deprecation is set, then it is a no-op.
|
// If --no-deprecation is set, then it is a no-op.
|
||||||
function deprecate(fn, msg) {
|
function deprecate(fn, msg) {
|
||||||
// Allow for deprecating things in the process of starting up.
|
// Allow for deprecating things in the process of starting up.
|
||||||
if (isUndefined(global$1.process)) {
|
if (isUndefined(global$1$1.process)) {
|
||||||
return function() {
|
return function() {
|
||||||
return deprecate(fn, msg).apply(this, arguments);
|
return deprecate(fn, msg).apply(this, arguments);
|
||||||
};
|
};
|
||||||
|
@ -3142,8 +3158,8 @@
|
||||||
* We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they
|
* We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they
|
||||||
* get the Object implementation, which is slower but behaves correctly.
|
* get the Object implementation, which is slower but behaves correctly.
|
||||||
*/
|
*/
|
||||||
Buffer$1.TYPED_ARRAY_SUPPORT = global$1.TYPED_ARRAY_SUPPORT !== undefined
|
Buffer$1.TYPED_ARRAY_SUPPORT = global$1$1.TYPED_ARRAY_SUPPORT !== undefined
|
||||||
? global$1.TYPED_ARRAY_SUPPORT
|
? global$1$1.TYPED_ARRAY_SUPPORT
|
||||||
: true;
|
: true;
|
||||||
|
|
||||||
function kMaxLength$1 () {
|
function kMaxLength$1 () {
|
||||||
|
@ -6948,10 +6964,10 @@
|
||||||
}
|
}
|
||||||
var cachedSetTimeout$1 = defaultSetTimout$1;
|
var cachedSetTimeout$1 = defaultSetTimout$1;
|
||||||
var cachedClearTimeout$1 = defaultClearTimeout$1;
|
var cachedClearTimeout$1 = defaultClearTimeout$1;
|
||||||
if (typeof global$1.setTimeout === 'function') {
|
if (typeof global$1$1.setTimeout === 'function') {
|
||||||
cachedSetTimeout$1 = setTimeout;
|
cachedSetTimeout$1 = setTimeout;
|
||||||
}
|
}
|
||||||
if (typeof global$1.clearTimeout === 'function') {
|
if (typeof global$1$1.clearTimeout === 'function') {
|
||||||
cachedClearTimeout$1 = clearTimeout;
|
cachedClearTimeout$1 = clearTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7072,7 +7088,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
// from https://github.com/kumavis/browser-process-hrtime/blob/master/index.js
|
// from https://github.com/kumavis/browser-process-hrtime/blob/master/index.js
|
||||||
var performance$1 = global$1.performance || {};
|
var performance$1 = global$1$1.performance || {};
|
||||||
var performanceNow$1 =
|
var performanceNow$1 =
|
||||||
performance$1.now ||
|
performance$1.now ||
|
||||||
performance$1.mozNow ||
|
performance$1.mozNow ||
|
||||||
|
@ -13528,7 +13544,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global$1 !== 'undefined' ? global$1 : typeof self !== 'undefined' ? self : {};
|
||||||
|
|
||||||
function createCommonjsModule(fn, module) {
|
function createCommonjsModule(fn, module) {
|
||||||
return module = { exports: {} }, fn(module, module.exports), module.exports;
|
return module = { exports: {} }, fn(module, module.exports), module.exports;
|
||||||
|
@ -25982,12 +25998,264 @@ Please pipe the document into a Node stream.\
|
||||||
mixin(ImagesMixin);
|
mixin(ImagesMixin);
|
||||||
mixin(OutputDocumentBrowser);
|
mixin(OutputDocumentBrowser);
|
||||||
|
|
||||||
|
/// <reference lib="webworker" />
|
||||||
|
const generatePDF = (imgDataUrlList, width, height) => __awaiter(void 0, void 0, void 0, function* () {
|
||||||
|
// @ts-ignore
|
||||||
|
const pdf = new PDFDocument({
|
||||||
|
// 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 = yield pdf.getBuffer();
|
||||||
|
return buf.buffer;
|
||||||
|
});
|
||||||
|
const getDataURL = (blob) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => {
|
||||||
|
const result = reader.result;
|
||||||
|
resolve(result);
|
||||||
|
};
|
||||||
|
reader.onerror = reject;
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
onmessage = (e) => __awaiter(void 0, void 0, void 0, function* () {
|
||||||
|
const [imgDataBlobList, width, height,] = e.data;
|
||||||
|
const dataURLs = yield Promise.all(imgDataBlobList.map(getDataURL));
|
||||||
|
const pdfBuf = yield generatePDF(dataURLs, width, height);
|
||||||
|
postMessage(pdfBuf, [pdfBuf]);
|
||||||
|
});
|
||||||
|
|
||||||
|
}());
|
||||||
|
return Worker
|
||||||
|
};
|
||||||
|
|
||||||
|
const scriptUrlFromFunction = (fn) => {
|
||||||
|
const blob = new Blob(["(" + fn.toString() + ")()"], { type: "application/javascript" });
|
||||||
|
return URL.createObjectURL(blob);
|
||||||
|
};
|
||||||
|
class PDFWorkerHelper extends Worker {
|
||||||
|
constructor() {
|
||||||
|
const url = scriptUrlFromFunction(PDFWorker);
|
||||||
|
super(url);
|
||||||
|
}
|
||||||
|
generatePDF(imgDataBlobList, width, height) {
|
||||||
|
const msg = [
|
||||||
|
imgDataBlobList,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
];
|
||||||
|
this.postMessage(msg);
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.addEventListener("message", (e) => {
|
||||||
|
resolve(e.data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
||||||
|
|
||||||
|
function createCommonjsModule(fn, module) {
|
||||||
|
return module = { exports: {} }, fn(module, module.exports), module.exports;
|
||||||
|
}
|
||||||
|
|
||||||
|
var FileSaver = createCommonjsModule(function (module, exports) {
|
||||||
|
(function (global, factory) {
|
||||||
|
{
|
||||||
|
factory();
|
||||||
|
}
|
||||||
|
})(commonjsGlobal, function () {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FileSaver.js
|
||||||
|
* A saveAs() FileSaver implementation.
|
||||||
|
*
|
||||||
|
* By Eli Grey, http://eligrey.com
|
||||||
|
*
|
||||||
|
* License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)
|
||||||
|
* source : http://purl.eligrey.com/github/FileSaver.js
|
||||||
|
*/
|
||||||
|
// The one and only way of getting global scope in all environments
|
||||||
|
// https://stackoverflow.com/q/3277182/1008999
|
||||||
|
var _global = typeof window === 'object' && window.window === window ? window : typeof self === 'object' && self.self === self ? self : typeof commonjsGlobal === 'object' && commonjsGlobal.global === commonjsGlobal ? commonjsGlobal : void 0;
|
||||||
|
|
||||||
|
function bom(blob, opts) {
|
||||||
|
if (typeof opts === 'undefined') opts = {
|
||||||
|
autoBom: false
|
||||||
|
};else if (typeof opts !== 'object') {
|
||||||
|
console.warn('Deprecated: Expected third argument to be a object');
|
||||||
|
opts = {
|
||||||
|
autoBom: !opts
|
||||||
|
};
|
||||||
|
} // prepend BOM for UTF-8 XML and text/* types (including HTML)
|
||||||
|
// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
|
||||||
|
|
||||||
|
if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
|
||||||
|
return new Blob([String.fromCharCode(0xFEFF), blob], {
|
||||||
|
type: blob.type
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
function download(url, name, opts) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', url);
|
||||||
|
xhr.responseType = 'blob';
|
||||||
|
|
||||||
|
xhr.onload = function () {
|
||||||
|
saveAs(xhr.response, name, opts);
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onerror = function () {
|
||||||
|
console.error('could not download file');
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function corsEnabled(url) {
|
||||||
|
var xhr = new XMLHttpRequest(); // use sync to avoid popup blocker
|
||||||
|
|
||||||
|
xhr.open('HEAD', url, false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
xhr.send();
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
return xhr.status >= 200 && xhr.status <= 299;
|
||||||
|
} // `a.click()` doesn't work for all browsers (#465)
|
||||||
|
|
||||||
|
|
||||||
|
function click(node) {
|
||||||
|
try {
|
||||||
|
node.dispatchEvent(new MouseEvent('click'));
|
||||||
|
} catch (e) {
|
||||||
|
var evt = document.createEvent('MouseEvents');
|
||||||
|
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null);
|
||||||
|
node.dispatchEvent(evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var saveAs = _global.saveAs || ( // probably in some web worker
|
||||||
|
typeof window !== 'object' || window !== _global ? function saveAs() {}
|
||||||
|
/* noop */
|
||||||
|
// Use download attribute first if possible (#193 Lumia mobile)
|
||||||
|
: 'download' in HTMLAnchorElement.prototype ? function saveAs(blob, name, opts) {
|
||||||
|
var URL = _global.URL || _global.webkitURL;
|
||||||
|
var a = document.createElement('a');
|
||||||
|
name = name || blob.name || 'download';
|
||||||
|
a.download = name;
|
||||||
|
a.rel = 'noopener'; // tabnabbing
|
||||||
|
// TODO: detect chrome extensions & packaged apps
|
||||||
|
// a.target = '_blank'
|
||||||
|
|
||||||
|
if (typeof blob === 'string') {
|
||||||
|
// Support regular links
|
||||||
|
a.href = blob;
|
||||||
|
|
||||||
|
if (a.origin !== location.origin) {
|
||||||
|
corsEnabled(a.href) ? download(blob, name, opts) : click(a, a.target = '_blank');
|
||||||
|
} else {
|
||||||
|
click(a);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Support blobs
|
||||||
|
a.href = URL.createObjectURL(blob);
|
||||||
|
setTimeout(function () {
|
||||||
|
URL.revokeObjectURL(a.href);
|
||||||
|
}, 4E4); // 40s
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
click(a);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
} // Use msSaveOrOpenBlob as a second approach
|
||||||
|
: 'msSaveOrOpenBlob' in navigator ? function saveAs(blob, name, opts) {
|
||||||
|
name = name || blob.name || 'download';
|
||||||
|
|
||||||
|
if (typeof blob === 'string') {
|
||||||
|
if (corsEnabled(blob)) {
|
||||||
|
download(blob, name, opts);
|
||||||
|
} else {
|
||||||
|
var a = document.createElement('a');
|
||||||
|
a.href = blob;
|
||||||
|
a.target = '_blank';
|
||||||
|
setTimeout(function () {
|
||||||
|
click(a);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
navigator.msSaveOrOpenBlob(bom(blob, opts), name);
|
||||||
|
}
|
||||||
|
} // Fallback to using FileReader and a popup
|
||||||
|
: function saveAs(blob, name, opts, popup) {
|
||||||
|
// Open a popup immediately do go around popup blocker
|
||||||
|
// Mostly only available on user interaction and the fileReader is async so...
|
||||||
|
popup = popup || open('', '_blank');
|
||||||
|
|
||||||
|
if (popup) {
|
||||||
|
popup.document.title = popup.document.body.innerText = 'downloading...';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof blob === 'string') return download(blob, name, opts);
|
||||||
|
var force = blob.type === 'application/octet-stream';
|
||||||
|
|
||||||
|
var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari;
|
||||||
|
|
||||||
|
var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);
|
||||||
|
|
||||||
|
if ((isChromeIOS || force && isSafari) && typeof FileReader === 'object') {
|
||||||
|
// Safari doesn't allow downloading of blob URLs
|
||||||
|
var reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onloadend = function () {
|
||||||
|
var url = reader.result;
|
||||||
|
url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;');
|
||||||
|
if (popup) popup.location.href = url;else location = url;
|
||||||
|
popup = null; // reverse-tabnabbing #460
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
|
} else {
|
||||||
|
var URL = _global.URL || _global.webkitURL;
|
||||||
|
var url = URL.createObjectURL(blob);
|
||||||
|
if (popup) popup.location = url;else location.href = url;
|
||||||
|
popup = null; // reverse-tabnabbing #460
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}, 4E4); // 40s
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_global.saveAs = saveAs.saveAs = saveAs;
|
||||||
|
|
||||||
|
{
|
||||||
|
module.exports = saveAs;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const saveAs = FileSaver.saveAs;
|
||||||
let pdfBlob;
|
let pdfBlob;
|
||||||
const svgToPng = (svgURL) => __awaiter(void 0, void 0, void 0, function* () {
|
const imgToBlob = (imgURL) => __awaiter(void 0, void 0, void 0, function* () {
|
||||||
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
|
||||||
yield new Promise((resolve) => {
|
yield new Promise((resolve) => {
|
||||||
imageElement.onload = () => resolve();
|
imageElement.onload = () => resolve();
|
||||||
|
@ -26001,39 +26269,23 @@ Please pipe the document into a Node stream.\
|
||||||
document.body.appendChild(canvas);
|
document.body.appendChild(canvas);
|
||||||
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 = yield new Promise(resolve => canvas.toBlob(resolve, "image/png"));
|
||||||
canvas.remove();
|
canvas.remove();
|
||||||
imageElement.remove();
|
imageElement.remove();
|
||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
const generatePDF = (svgURLs, name) => __awaiter(void 0, void 0, void 0, function* () {
|
const generatePDF = (svgURLs, name) => __awaiter(void 0, void 0, void 0, function* () {
|
||||||
if (pdfBlob) {
|
if (pdfBlob) {
|
||||||
return FileSaver(pdfBlob, `${name}.pdf`);
|
return saveAs(pdfBlob, `${name}.pdf`);
|
||||||
}
|
}
|
||||||
const cachedImg = document.querySelector("img[id^=score_]");
|
const cachedImg = document.querySelector("img[id^=score_]");
|
||||||
const { naturalWidth: width, naturalHeight: height } = cachedImg;
|
const { naturalWidth: width, naturalHeight: height } = cachedImg;
|
||||||
const imgDataList = yield Promise.all(svgURLs.map(svgToPng));
|
const imgDataBlobList = yield Promise.all(svgURLs.map(imgToBlob));
|
||||||
// @ts-ignore
|
const worker = new PDFWorkerHelper();
|
||||||
const pdf = new PDFDocument({
|
const pdfArrayBuffer = yield worker.generatePDF(imgDataBlobList, width, height);
|
||||||
// compress: true,
|
worker.terminate();
|
||||||
size: [width, height],
|
pdfBlob = new Blob([pdfArrayBuffer]);
|
||||||
autoFirstPage: false,
|
saveAs(pdfBlob, `${name}.pdf`);
|
||||||
margin: 0,
|
|
||||||
layout: "portrait",
|
|
||||||
});
|
|
||||||
imgDataList.forEach((data) => {
|
|
||||||
pdf.addPage();
|
|
||||||
pdf.image(data, {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// TODO: webworker
|
|
||||||
// @ts-ignore
|
|
||||||
return pdf.getBlob().then((blob) => {
|
|
||||||
pdfBlob = blob;
|
|
||||||
FileSaver(blob, `${name}.pdf`);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
const getPagesNumber = (scorePlayerData) => {
|
const getPagesNumber = (scorePlayerData) => {
|
||||||
try {
|
try {
|
||||||
|
@ -26043,6 +26295,17 @@ Please pipe the document into a Node stream.\
|
||||||
return document.querySelectorAll("img[id^=score_]").length;
|
return document.querySelectorAll("img[id^=score_]").length;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const getImgType = () => {
|
||||||
|
try {
|
||||||
|
const imgE = document.querySelector("img[id^=score_]");
|
||||||
|
const { pathname } = new URL(imgE.src);
|
||||||
|
const imgtype = pathname.match(/\.(\w+)$/)[1];
|
||||||
|
return imgtype;
|
||||||
|
}
|
||||||
|
catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
const getTitle = (scorePlayerData) => {
|
const getTitle = (scorePlayerData) => {
|
||||||
try {
|
try {
|
||||||
return scorePlayerData.json.metadata.title;
|
return scorePlayerData.json.metadata.title;
|
||||||
|
@ -26052,7 +26315,7 @@ Please pipe the document into a Node stream.\
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const getScoreFileName = (scorePlayerData) => {
|
const getScoreFileName = (scorePlayerData) => {
|
||||||
return getTitle(scorePlayerData).replace(/\W+/g, "_");
|
return getTitle(scorePlayerData).replace(/[\s<>:"/\\|?*~\0\cA-\cZ]+/g, "_");
|
||||||
};
|
};
|
||||||
const main = () => {
|
const main = () => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -26071,8 +26334,9 @@ Please pipe the document into a Node stream.\
|
||||||
const btnsDiv = document.querySelector(".score-right .buttons-wrapper") || document.querySelectorAll("aside section > div")[3];
|
const btnsDiv = document.querySelector(".score-right .buttons-wrapper") || document.querySelectorAll("aside section > div")[3];
|
||||||
const downloadBtn = btnsDiv.querySelector("button, .button");
|
const downloadBtn = btnsDiv.querySelector("button, .button");
|
||||||
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 = {
|
||||||
"MSCZ": msczURL,
|
"MSCZ": msczURL,
|
||||||
|
@ -26090,8 +26354,7 @@ Please pipe the document into a Node stream.\
|
||||||
btn.dataset.target = "";
|
btn.dataset.target = "";
|
||||||
}
|
}
|
||||||
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}`;
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -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