// ==UserScript== // @name 微信公众号 PDF 导出脚本 // @namespace mem.ac/weixin-print-to-pdf // @version 1.4.2 // @description 方便地导出公众号文章中以图片形式上传的试卷,让您一键开卷! // @author memset0 // @license AGPL-v3.0 // @match https://mp.weixin.qq.com/s* // @updateurl https://cdn.jsdelivr.net/gh/memset0/weixin-print-to-pdf/index.js // @downloadurl https://cdn.jsdelivr.net/gh/memset0/weixin-print-to-pdf/index.js // @run-at document-start // @downloadURL none // ==/UserScript== const CSS = ` .mem-print-container { } .mem-print-settings { margin: auto; padding: 16px; } .mem-print-settings-btn-group button { margin-right: 6px; } #mem-print-main { line-height: 0px; margin-bottom: 20px; /* padding: 16px; border: 1px solid #D9DADC; */ } #mem-print-main button { margin-right: 8px; } `; function log(...args) { console.log('[@memset0/weixin-print-to-pdf]', ...args); } function isInteger(value) { const converted = +value; return !isNaN(converted) && Number.isInteger(converted); } function applyFilter(iterable, filterPattern) { const illegalFilter = (msg) => (alert('Illegal filter: ' + String(msg)), []); const flag = []; for (const _ in iterable) { flag.push(false); } if (!filterPattern || filterPattern == '-') { for (const i in flag) { flag[+i] = true; } } else { const filters = filterPattern.split(','); for (const filter of filters) { if (filter.includes('-')) { const splited = filter.split('-'); if (splited.length > 2) { return illegalFilter('wrong interval'); } if (!splited[0]) { splited[0] = 0; } if (!splited[1]) { splited[1] = iterable.length - 1; } if (!isInteger(splited[0]) || !isInteger(splited[1])) { return illegalFilter('not a number'); } for (let i = +splited[0] - 1; i < +splited[1]; i++) { if (i < 0 || i >= flag.length) { return illegalFilter('out of range'); } flag[i] = true; } } else { if (!isInteger(filter)) { return illegalFilter('not a number'); } const x = +filter - 1; if (x < 0 || x >= flag.length) { return illegalFilter('out of range'); } flag[x] = true; } } } log('apply filter:', filter, flag); const result = []; for (const i in iterable) { if (flag[+i]) { result.push(iterable[+i]); } } return result; } function scrollTo(type) { const scrollSpeed = 50; if (type !== 'top' && type !== 'bottom') { throw new Error('type error!'); } const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight; let promiseResolve = null; let lastTimestamp = null; let scrollRecords = []; function scrollAnimated(timestamp) { const currentScroll = document.documentElement.scrollTop || document.body.scrollTop; scrollRecords.push(currentScroll); if (scrollRecords.length > 5) { scrollRecords.shift(); let finishedFlag = true; for (let i = 1; i < scrollRecords.length; i++) { if (scrollRecords[i] !== scrollRecords[i - 1]) { finishedFlag = false; break; } } if (finishedFlag) { // log('finish', scrollRecords, finishedFlag); return promiseResolve(type); } } if (lastTimestamp === null) { lastTimestamp = timestamp; } else { const deltaTimestamp = timestamp - lastTimestamp; lastTimestamp = timestamp; window.scrollTo(0, currentScroll + scrollSpeed * (type === 'top' ? -deltaTimestamp : +deltaTimestamp)); log(type, currentScroll, scrollHeight, deltaTimestamp); } window.requestAnimationFrame(scrollAnimated); } return new Promise((resolve) => { promiseResolve = resolve; window.requestAnimationFrame(scrollAnimated); }); } async function printToPdf(width, height, margin, html) { log('print to pdf', width, height, margin); // await scrollTo('top'); // await scrollTo('bottom'); const pixeledMargin = String(margin).split(' ').map((s) => (s + 'px')).join(' '); const printStyle = '' + '' + ''; html = printStyle + html; // const document = unsafeWindow.document; // seemingly needless const blob = new Blob([html], { type: 'text/html;charset=utf-8' }); const blobUrl = URL.createObjectURL(blob); log('blob url:', blobUrl); const $iframe = document.createElement('iframe'); $iframe.style.display = 'none'; $iframe.src = blobUrl; document.body.appendChild($iframe); $iframe.onload = () => { setTimeout(() => { $iframe.focus(); $iframe.contentWindow.print(); }, 1); }; } function generateHtmlFromContent(options) { let html = ''; for (const $element of applyFilter(document.getElementById('js_content').children, options.filter)) { console.log($element); html += $element.outerHTML + '\n\n'; } return html; } function generateHtmlFromPictures(options) { const minimalImageSize = 100; let html = ''; for (const $image of applyFilter(document.getElementById('js_content').querySelectorAll('img'), options.filter)) { const imageSrc = $image.getAttribute('data-src'); const imageWidth = $image.getAttribute('width'); if (!imageSrc) { continue; } if (imageWidth && imageWidth < minimalImageSize) { continue; } html += '