├── .tool-versions ├── release └── app │ ├── .gitignore │ ├── package.json │ └── yarn.lock ├── .erb ├── scripts │ ├── DoNothing.js │ ├── .eslintrc │ ├── delete-source-maps.js │ ├── clean.js │ ├── link-modules.ts │ ├── check-node-env.js │ ├── check-port-in-use.js │ ├── electron-rebuild.js │ ├── check-build-exists.ts │ ├── Notarize.js │ └── check-native-dep.js ├── mocks │ └── fileMock.js ├── img │ └── demo.png └── configs │ ├── .eslintrc │ ├── webpack.config.eslint.ts │ ├── webpack.paths.ts │ ├── webpack.config.base.ts │ ├── webpack.config.renderer.dev.dll.ts │ ├── webpack.config.main.prod.ts │ ├── webpack.config.preload.dev.ts │ ├── webpack.config.renderer.prod.ts │ └── webpack.config.renderer.dev.ts ├── .github ├── FUNDING.yml ├── config.yml ├── ISSUE_TEMPLATE │ ├── 3-Feature_request.md │ ├── 2-Question.md │ └── 1-Bug_report.md ├── stale.yml └── workflows │ ├── test.yml │ └── publish.yml ├── assets ├── icon.png ├── icon.icns ├── embedded.provisionprofile ├── assets.d.ts ├── entitlements.mac.plist ├── entitlements.mas.plist └── entitlements.mas.inherit.plist ├── .husky └── pre-commit ├── .vscode ├── extensions.json ├── settings.json └── launch.json ├── postcss.config.js ├── src ├── renderer │ ├── index.tsx │ ├── index.ejs │ ├── preload.d.ts │ ├── App.css │ ├── App.tsx │ └── components │ │ └── printer │ │ ├── Screen.tsx │ │ └── Main.tsx └── main │ ├── util.ts │ ├── preload.ts │ ├── menu.ts │ └── main.ts ├── .editorconfig ├── .gitattributes ├── tailwind.config.js ├── .gitignore ├── .eslintignore ├── tsconfig.json ├── README.md ├── .eslintrc.js ├── package.json └── LICENSE /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 16.16.0 2 | -------------------------------------------------------------------------------- /release/app/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | -------------------------------------------------------------------------------- /.erb/scripts/DoNothing.js: -------------------------------------------------------------------------------- 1 | exports.default = () => {}; 2 | -------------------------------------------------------------------------------- /.erb/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://paypal.me/vomanhtai'] 2 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plainlab/plainprinter/HEAD/assets/icon.png -------------------------------------------------------------------------------- /.erb/img/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plainlab/plainprinter/HEAD/.erb/img/demo.png -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /assets/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plainlab/plainprinter/HEAD/assets/icon.icns -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "EditorConfig.EditorConfig"] 3 | } 4 | -------------------------------------------------------------------------------- /assets/embedded.provisionprofile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plainlab/plainprinter/HEAD/assets/embedded.provisionprofile -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | requiredHeaders: 2 | - Prerequisites 3 | - Expected Behavior 4 | - Current Behavior 5 | - Possible Solution 6 | - Your Environment 7 | -------------------------------------------------------------------------------- /.erb/configs/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3-Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: You want something added to Screen Printer. 🎉 4 | labels: 'enhancement' 5 | --- 6 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.eslint.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/no-unresolved: off, import/no-self-import: off */ 2 | 3 | module.exports = require('./webpack.config.renderer.dev').default; 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-Question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question.❓ 4 | labels: 'question' 5 | --- 6 | 7 | ## Summary 8 | 9 | 10 | -------------------------------------------------------------------------------- /.erb/scripts/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off", 6 | "import/no-extraneous-dependencies": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/renderer/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | import App from './App'; 3 | 4 | const container = document.getElementById('root')!; 5 | const root = createRoot(container); 6 | root.render(); 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.exe binary 3 | *.png binary 4 | *.jpg binary 5 | *.jpeg binary 6 | *.ico binary 7 | *.icns binary 8 | *.eot binary 9 | *.otf binary 10 | *.ttf binary 11 | *.woff binary 12 | *.woff2 binary 13 | -------------------------------------------------------------------------------- /assets/assets.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const content: any; 3 | export default content; 4 | } 5 | 6 | declare module '*.png' { 7 | const content: any; 8 | export default content; 9 | } 10 | 11 | declare module '*.jpg' { 12 | const content: any; 13 | export default content; 14 | } 15 | -------------------------------------------------------------------------------- /.erb/scripts/delete-source-maps.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import rimraf from 'rimraf'; 3 | import webpackPaths from '../configs/webpack.paths'; 4 | 5 | export default function deleteSourceMaps() { 6 | rimraf.sync(path.join(webpackPaths.distMainPath, '*.js.map')); 7 | rimraf.sync(path.join(webpackPaths.distRendererPath, '*.js.map')); 8 | } 9 | -------------------------------------------------------------------------------- /.erb/scripts/clean.js: -------------------------------------------------------------------------------- 1 | import rimraf from 'rimraf'; 2 | import process from 'process'; 3 | import webpackPaths from '../configs/webpack.paths'; 4 | 5 | const foldersToRemove = [ 6 | webpackPaths.distPath, 7 | webpackPaths.buildPath, 8 | webpackPaths.dllPath, 9 | ]; 10 | 11 | foldersToRemove.forEach((folder) => { 12 | rimraf.sync(folder); 13 | }); 14 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | theme: {}, 3 | purge: { 4 | content: ['./src/**/*.{ts,tsx,html}'], 5 | }, 6 | variants: { 7 | extend: { 8 | backgroundColor: ['active', 'disabled'], 9 | textColor: ['active'], 10 | opacity: ['disabled'], 11 | outline: ['focus', 'active'], 12 | }, 13 | }, 14 | plugins: [], 15 | }; 16 | -------------------------------------------------------------------------------- /.erb/scripts/link-modules.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import webpackPaths from '../configs/webpack.paths'; 3 | 4 | const { srcNodeModulesPath } = webpackPaths; 5 | const { appNodeModulesPath } = webpackPaths; 6 | 7 | if (!fs.existsSync(srcNodeModulesPath) && fs.existsSync(appNodeModulesPath)) { 8 | fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction'); 9 | } 10 | -------------------------------------------------------------------------------- /src/renderer/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | Screen Printer · Auto PDF screen printer 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /.erb/scripts/check-node-env.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | export default function checkNodeEnv(expectedEnv) { 4 | if (!expectedEnv) { 5 | throw new Error('"expectedEnv" not set'); 6 | } 7 | 8 | if (process.env.NODE_ENV !== expectedEnv) { 9 | console.log( 10 | chalk.whiteBright.bgRed.bold( 11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config` 12 | ) 13 | ); 14 | process.exit(2); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /assets/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.cs.disable-library-validation 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/util.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/prefer-default-export: off */ 2 | import { URL } from 'url'; 3 | import path from 'path'; 4 | 5 | export function resolveHtmlPath(htmlFileName: string) { 6 | if (process.env.NODE_ENV === 'development') { 7 | const port = process.env.PORT || 1212; 8 | const url = new URL(`http://localhost:${port}`); 9 | url.pathname = htmlFileName; 10 | return url.href; 11 | } 12 | return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}`; 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/preload.d.ts: -------------------------------------------------------------------------------- 1 | import { Channels } from 'main/preload'; 2 | 3 | declare global { 4 | interface Window { 5 | electron: { 6 | ipcRenderer: { 7 | sendMessage(channel: Channels, ...args: any[]): void; 8 | on(channel: string, func: (_event: any, ...args: any[]) => void): any; 9 | once(channel: string, func: (...args: any[]) => void): void; 10 | invoke(channel: Channels, ...args: any[]): any; 11 | }; 12 | }; 13 | } 14 | } 15 | 16 | export {}; 17 | -------------------------------------------------------------------------------- /.erb/scripts/check-port-in-use.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import detectPort from 'detect-port'; 3 | 4 | const port = process.env.PORT || '1212'; 5 | 6 | detectPort(port, (err, availablePort) => { 7 | if (port !== String(availablePort)) { 8 | throw new Error( 9 | chalk.whiteBright.bgRed.bold( 10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start` 11 | ) 12 | ); 13 | } else { 14 | process.exit(0); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | *.p12 10 | 11 | # Coverage directory used by tools like istanbul 12 | coverage 13 | .eslintcache 14 | 15 | # Dependency directory 16 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 17 | node_modules 18 | 19 | # OSX 20 | .DS_Store 21 | 22 | release/app/dist 23 | release/build 24 | .erb/dll 25 | 26 | .idea 27 | npm-debug.log.* 28 | *.css.d.ts 29 | *.sass.d.ts 30 | *.scss.d.ts 31 | .gitconfig 32 | -------------------------------------------------------------------------------- /assets/entitlements.mas.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.cs.allow-unsigned-executable-memory 10 | 11 | com.apple.security.cs.disable-library-validation 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /assets/entitlements.mas.inherit.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.inherit 8 | 9 | com.apple.security.cs.allow-unsigned-executable-memory 10 | 11 | com.apple.security.cs.disable-library-validation 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | 31 | # eslint ignores hidden directories by default: 32 | # https://github.com/eslint/eslint/issues/8429 33 | !.erb 34 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 40 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pr 8 | - discussion 9 | - e2e 10 | - enhancement 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.erb/scripts/electron-rebuild.js: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import fs from 'fs'; 3 | import { dependencies } from '../../release/app/package.json'; 4 | import webpackPaths from '../configs/webpack.paths'; 5 | 6 | if ( 7 | Object.keys(dependencies || {}).length > 0 && 8 | fs.existsSync(webpackPaths.appNodeModulesPath) 9 | ) { 10 | const electronRebuildCmd = 11 | '../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir .'; 12 | const cmd = 13 | process.platform === 'win32' 14 | ? electronRebuildCmd.replace(/\//g, '\\') 15 | : electronRebuildCmd; 16 | execSync(cmd, { 17 | cwd: webpackPaths.appPath, 18 | stdio: 'inherit', 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | ".eslintrc": "jsonc", 4 | ".prettierrc": "jsonc", 5 | ".eslintignore": "ignore" 6 | }, 7 | 8 | "eslint.validate": [ 9 | "javascript", 10 | "javascriptreact", 11 | "html", 12 | "typescriptreact" 13 | ], 14 | 15 | "javascript.validate.enable": false, 16 | "javascript.format.enable": false, 17 | "typescript.format.enable": false, 18 | 19 | "search.exclude": { 20 | ".git": true, 21 | ".eslintcache": true, 22 | ".erb/dll": true, 23 | "release/{build,app/dist}": true, 24 | "node_modules": true, 25 | "npm-debug.log.*": true, 26 | "test/**/__snapshots__": true, 27 | "package-lock.json": true, 28 | "*.{css,sass,scss}.d.ts": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/renderer/App.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer components { 6 | .btn { 7 | @apply rounded-md bg-gray-50 px-2 py-0.5 outline-none text-xs border shadow-sm active:bg-blue-500 active:text-white disabled:opacity-50; 8 | } 9 | 10 | .btn-link { 11 | @apply outline-none hover:opacity-80 active:text-gray-300 active:outline-none focus:outline-none; 12 | } 13 | 14 | input[type='number'] { 15 | @apply px-1 py-0.5 rounded focus:ring-blue-500 focus:outline-none focus:ring-2 focus:ring-inset; 16 | } 17 | 18 | input[type='radio'] { 19 | @apply focus:outline-none; 20 | } 21 | 22 | textarea { 23 | @apply rounded focus:ring-blue-500 focus:outline-none focus:ring-2 focus:ring-inset; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Electron: Main", 6 | "type": "node", 7 | "request": "launch", 8 | "protocol": "inspector", 9 | "runtimeExecutable": "npm", 10 | "runtimeArgs": ["run", "start"], 11 | "env": { 12 | "MAIN_ARGS": "--inspect=5858 --remote-debugging-port=9223" 13 | } 14 | }, 15 | { 16 | "name": "Electron: Renderer", 17 | "type": "chrome", 18 | "request": "attach", 19 | "port": 9223, 20 | "webRoot": "${workspaceFolder}", 21 | "timeout": 15000 22 | } 23 | ], 24 | "compounds": [ 25 | { 26 | "name": "Electron: All", 27 | "configurations": ["Electron: Main", "Electron: Renderer"] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/main/preload.ts: -------------------------------------------------------------------------------- 1 | import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'; 2 | 3 | export type Channels = string; 4 | 5 | contextBridge.exposeInMainWorld('electron', { 6 | ipcRenderer: { 7 | sendMessage(channel: Channels, args: unknown[]) { 8 | ipcRenderer.send(channel, args); 9 | }, 10 | invoke(channel: Channels, ...args: unknown[]) { 11 | return ipcRenderer.invoke(channel, ...args); 12 | }, 13 | on( 14 | channel: Channels, 15 | func: (_event: IpcRendererEvent, ...args: unknown[]) => void 16 | ) { 17 | return ipcRenderer.on(channel, func); 18 | }, 19 | once(channel: Channels, func: (...args: unknown[]) => void) { 20 | ipcRenderer.once(channel, (_event, ...args) => func(...args)); 21 | }, 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | release: 7 | runs-on: ${{ matrix.os }} 8 | 9 | strategy: 10 | matrix: 11 | os: [macos-latest] 12 | 13 | steps: 14 | - name: Check out Git repository 15 | uses: actions/checkout@v1 16 | 17 | - name: Install Node.js, NPM and Yarn 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: 16 21 | 22 | - name: yarn install 23 | run: | 24 | yarn install --frozen-lockfile --network-timeout 300000 25 | 26 | - name: yarn test 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | run: | 30 | yarn package 31 | yarn lint 32 | yarn tsc 33 | yarn test 34 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "es2021", 5 | "module": "commonjs", 6 | "lib": ["dom", "es2021"], 7 | "declaration": true, 8 | "declarationMap": true, 9 | "jsx": "react-jsx", 10 | "strict": true, 11 | "pretty": true, 12 | "sourceMap": true, 13 | "baseUrl": "./src", 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "moduleResolution": "node", 19 | "esModuleInterop": true, 20 | "allowSyntheticDefaultImports": true, 21 | "resolveJsonModule": true, 22 | "allowJs": true, 23 | "outDir": "release/app/dist" 24 | }, 25 | "exclude": ["test", "release/build", "release/app/dist", ".erb/dll"] 26 | } 27 | -------------------------------------------------------------------------------- /src/renderer/App.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | BrowserRouter as Router, 3 | Routes, 4 | Route, 5 | useLocation, 6 | } from 'react-router-dom'; 7 | import qs from 'qs'; 8 | 9 | import './App.css'; 10 | import Main from './components/printer/Main'; 11 | import Screen from './components/printer/Screen'; 12 | 13 | function MyApp() { 14 | const location = useLocation(); 15 | const { search } = location; 16 | const so = qs.parse(search.slice(search.lastIndexOf('?') + 1)); 17 | return so.page === 'screen' ? ( 18 | 19 | ) : ( 20 |
21 | ); 22 | } 23 | 24 | export default function App() { 25 | return ( 26 | 27 | 28 | } /> 29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /.erb/scripts/check-build-exists.ts: -------------------------------------------------------------------------------- 1 | // Check if the renderer and main bundles are built 2 | import path from 'path'; 3 | import chalk from 'chalk'; 4 | import fs from 'fs'; 5 | import webpackPaths from '../configs/webpack.paths'; 6 | 7 | const mainPath = path.join(webpackPaths.distMainPath, 'main.js'); 8 | const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js'); 9 | 10 | if (!fs.existsSync(mainPath)) { 11 | throw new Error( 12 | chalk.whiteBright.bgRed.bold( 13 | 'The main process is not built yet. Build it by running "npm run build:main"' 14 | ) 15 | ); 16 | } 17 | 18 | if (!fs.existsSync(rendererPath)) { 19 | throw new Error( 20 | chalk.whiteBright.bgRed.bold( 21 | 'The renderer process is not built yet. Build it by running "npm run build:renderer"' 22 | ) 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /.erb/scripts/Notarize.js: -------------------------------------------------------------------------------- 1 | const { notarize } = require('electron-notarize'); 2 | const { build } = require('../../package.json'); 3 | 4 | exports.default = async function notarizeMacos(context) { 5 | const { electronPlatformName, appOutDir } = context; 6 | if (electronPlatformName !== 'darwin') { 7 | return; 8 | } 9 | 10 | if (!('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env)) { 11 | console.warn( 12 | 'Skipping notarizing step. APPLE_ID and APPLE_ID_PASS env variables must be set' 13 | ); 14 | return; 15 | } 16 | 17 | const appName = context.packager.appInfo.productFilename; 18 | 19 | await notarize({ 20 | appBundleId: build.appId, 21 | appPath: `${appOutDir}/${appName}.app`, 22 | appleId: process.env.APPLE_ID, 23 | appleIdPassword: process.env.APPLE_ID_PASS, 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /release/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plainprinter", 3 | "productName": "Screen Printer", 4 | "version": "1.0.2", 5 | "description": "Auto PDF screen printer", 6 | "main": "./dist/main/main.js", 7 | "author": { 8 | "name": "Tai Vo", 9 | "email": "screenprinter@manhtai.com", 10 | "url": "https://screenprinter.manhtai.com" 11 | }, 12 | "scripts": { 13 | "electron-rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js", 14 | "postinstall": "npm run electron-rebuild && npm run link-modules", 15 | "link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.ts" 16 | }, 17 | "license": "GPL-3.0-only", 18 | "dependencies": { 19 | "pdfkit": "^0.12.3", 20 | "screenshot-desktop": "^1.14.1", 21 | "@jitsi/robotjs": "^0.6.13" 22 | }, 23 | "devDependencies": {} 24 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Publish](https://github.com/plainlab/plainprinter/actions/workflows/publish.yml/badge.svg)](https://github.com/plainlab/plainprinter/actions/workflows/publish.yml) 2 | [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/plainlab/plainprinter)](https://github.com/plainlab/plainprinter/releases/latest) 3 | 4 | # Screen Printer 5 | 6 | > Take multiple screenshots and convert them into a PDF file 7 | 8 | ![Demo](.erb/img/demo.png) 9 | 10 | ## Features 11 | 12 | - Choose an area to take screenshots 13 | - Convert screenshots into a PDF 14 | - Auto click on the Next button 15 | 16 | ## Installation 17 | 18 | For macOS & Windows: https://github.com/plainlab/plainprinter/releases 19 | 20 | - macOS: Get `.dmg` file, open it and drag the app into Applications folder, for M1 mac: get `arm64.dmg` file instead. 21 | - Windows: Get `.exe` file and open it to install. 22 | 23 | ## CLI version 24 | 25 | https://github.com/manhtai/vitalsource-printer 26 | 27 | ## Buy me a coffee 28 | 29 | [Gumroad](https://gum.co/plainprinter) 30 | 31 | --- 32 | 33 | © 2023 Tai Vo 34 | -------------------------------------------------------------------------------- /.erb/configs/webpack.paths.ts: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const rootPath = path.join(__dirname, '../..'); 4 | 5 | const dllPath = path.join(__dirname, '../dll'); 6 | 7 | const srcPath = path.join(rootPath, 'src'); 8 | const srcMainPath = path.join(srcPath, 'main'); 9 | const srcRendererPath = path.join(srcPath, 'renderer'); 10 | 11 | const releasePath = path.join(rootPath, 'release'); 12 | const appPath = path.join(releasePath, 'app'); 13 | const appPackagePath = path.join(appPath, 'package.json'); 14 | const appNodeModulesPath = path.join(appPath, 'node_modules'); 15 | const srcNodeModulesPath = path.join(srcPath, 'node_modules'); 16 | 17 | const distPath = path.join(appPath, 'dist'); 18 | const distMainPath = path.join(distPath, 'main'); 19 | const distRendererPath = path.join(distPath, 'renderer'); 20 | 21 | const buildPath = path.join(releasePath, 'build'); 22 | 23 | export default { 24 | rootPath, 25 | dllPath, 26 | srcPath, 27 | srcMainPath, 28 | srcRendererPath, 29 | releasePath, 30 | appPath, 31 | appPackagePath, 32 | appNodeModulesPath, 33 | srcNodeModulesPath, 34 | distPath, 35 | distMainPath, 36 | distRendererPath, 37 | buildPath, 38 | }; 39 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'erb', 3 | rules: { 4 | // A temporary hack related to IDE not resolving correct package.json 5 | 'import/no-extraneous-dependencies': 'off', 6 | 'import/no-unresolved': 'error', 7 | // Since React 17 and typescript 4.1 you can safely disable the rule 8 | 'react/react-in-jsx-scope': 'off', 9 | 'import/extensions': [ 10 | 'error', 11 | 'ignorePackages', 12 | { 13 | js: 'off', 14 | jsx: 'off', 15 | ts: 'off', 16 | tsx: 'off', 17 | }, 18 | ], 19 | }, 20 | parserOptions: { 21 | ecmaVersion: 2020, 22 | sourceType: 'module', 23 | project: './tsconfig.json', 24 | tsconfigRootDir: __dirname, 25 | createDefaultProgram: true, 26 | }, 27 | settings: { 28 | 'import/resolver': { 29 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below 30 | node: {}, 31 | webpack: { 32 | config: require.resolve('./.erb/configs/webpack.config.eslint.ts'), 33 | }, 34 | typescript: {}, 35 | }, 36 | 'import/parsers': { 37 | '@typescript-eslint/parser': ['.ts', '.tsx'], 38 | }, 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.base.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Base webpack config used across other specific configs 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import webpackPaths from './webpack.paths'; 7 | import { dependencies as externals } from '../../release/app/package.json'; 8 | 9 | const configuration: webpack.Configuration = { 10 | externals: [...Object.keys(externals || {})], 11 | 12 | stats: 'errors-only', 13 | 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.[jt]sx?$/, 18 | exclude: /node_modules/, 19 | use: { 20 | loader: 'ts-loader', 21 | options: { 22 | // Remove this line to enable type checking in webpack builds 23 | transpileOnly: true, 24 | }, 25 | }, 26 | }, 27 | ], 28 | }, 29 | 30 | output: { 31 | path: webpackPaths.srcPath, 32 | // https://github.com/webpack/webpack/issues/1114 33 | library: { 34 | type: 'commonjs2', 35 | }, 36 | }, 37 | 38 | /** 39 | * Determine the array of extensions that should be used to resolve modules. 40 | */ 41 | resolve: { 42 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], 43 | modules: [webpackPaths.srcPath, 'node_modules'], 44 | }, 45 | 46 | plugins: [ 47 | new webpack.EnvironmentPlugin({ 48 | NODE_ENV: 'production', 49 | }), 50 | ], 51 | }; 52 | 53 | export default configuration; 54 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | publish: 10 | runs-on: ${{ matrix.os }} 11 | 12 | strategy: 13 | matrix: 14 | os: [macos-latest, windows-latest] 15 | 16 | steps: 17 | - name: Checkout git repo 18 | uses: actions/checkout@v1 19 | 20 | - name: Install Node, NPM and Yarn 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: 16 24 | 25 | - name: Get yarn cache directory path 26 | id: yarn-cache-dir-path 27 | run: echo "::set-output name=dir::$(yarn cache dir)" 28 | 29 | - uses: actions/cache@v1 30 | id: yarn-cache 31 | with: 32 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 33 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 34 | restore-keys: | 35 | ${{ runner.os }}-yarn- 36 | 37 | - name: Install dependencies 38 | run: | 39 | yarn install --prefer-offline 40 | 41 | - name: Install dependencies 42 | run: | 43 | yarn install 44 | 45 | - name: Publish releases 46 | env: 47 | # These values are used for auto updates signing 48 | APPLE_ID: ${{ secrets.APPLE_ID }} 49 | APPLE_ID_PASS: ${{ secrets.APPLE_ID_PASS }} 50 | CSC_LINK: ${{ secrets.CSC_LINK }} 51 | CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} 52 | WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }} 53 | WIN_CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} 54 | # This is used for uploading release assets to github 55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 56 | run: | 57 | yarn postinstall && yarn build && yarn electron-builder --publish always --x64 --arm64 58 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: You're having technical issues. 🐞 4 | labels: 'bug' 5 | --- 6 | 7 | 8 | 9 | ## Prerequisites 10 | 11 | 12 | 13 | - [ ] Using yarn 14 | - [ ] Using an up-to-date [`master` branch](https://github.com/electron-react-boilerplate/electron-react-boilerplate/tree/master) 15 | - [ ] Using latest version of devtools. [Check the docs for how to update](https://electron-react-boilerplate.js.org/docs/dev-tools/) 16 | - [ ] Tried solutions mentioned in [#400](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/400) 17 | - [ ] For issue in production release, add devtools output of `DEBUG_PROD=true yarn build && yarn start` 18 | 19 | ## Expected Behavior 20 | 21 | 22 | 23 | ## Current Behavior 24 | 25 | 26 | 27 | ## Steps to Reproduce 28 | 29 | 30 | 31 | 32 | 1. 33 | 34 | 2. 35 | 36 | 3. 37 | 38 | 4. 39 | 40 | ## Possible Solution (Not obligatory) 41 | 42 | 43 | 44 | ## Context 45 | 46 | 47 | 48 | 49 | 50 | ## Your Environment 51 | 52 | 53 | 54 | - Node version: 55 | - Screen Printer version or branch: 56 | - Operating System and version: 57 | - Link to your project: 58 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.dev.dll.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds the DLL for development electron renderer process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import path from 'path'; 7 | import { merge } from 'webpack-merge'; 8 | import baseConfig from './webpack.config.base'; 9 | import webpackPaths from './webpack.paths'; 10 | import { dependencies } from '../../package.json'; 11 | import checkNodeEnv from '../scripts/check-node-env'; 12 | 13 | checkNodeEnv('development'); 14 | 15 | const dist = webpackPaths.dllPath; 16 | 17 | const configuration: webpack.Configuration = { 18 | context: webpackPaths.rootPath, 19 | 20 | devtool: 'eval', 21 | 22 | mode: 'development', 23 | 24 | target: 'electron-renderer', 25 | 26 | externals: ['fsevents', 'crypto-browserify'], 27 | 28 | /** 29 | * Use `module` from `webpack.config.renderer.dev.js` 30 | */ 31 | module: require('./webpack.config.renderer.dev').default.module, 32 | 33 | entry: { 34 | renderer: Object.keys(dependencies || {}), 35 | }, 36 | 37 | output: { 38 | path: dist, 39 | filename: '[name].dev.dll.js', 40 | library: { 41 | name: 'renderer', 42 | type: 'var', 43 | }, 44 | }, 45 | 46 | plugins: [ 47 | new webpack.DllPlugin({ 48 | path: path.join(dist, '[name].json'), 49 | name: '[name]', 50 | }), 51 | 52 | /** 53 | * Create global constants which can be configured at compile time. 54 | * 55 | * Useful for allowing different behaviour between development builds and 56 | * release builds 57 | * 58 | * NODE_ENV should be production so that modules do not perform certain 59 | * development checks 60 | */ 61 | new webpack.EnvironmentPlugin({ 62 | NODE_ENV: 'development', 63 | }), 64 | 65 | new webpack.LoaderOptionsPlugin({ 66 | debug: true, 67 | options: { 68 | context: webpackPaths.srcPath, 69 | output: { 70 | path: webpackPaths.dllPath, 71 | }, 72 | }, 73 | }), 74 | ], 75 | }; 76 | 77 | export default merge(baseConfig, configuration); 78 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.main.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for production electron main process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import { merge } from 'webpack-merge'; 8 | import TerserPlugin from 'terser-webpack-plugin'; 9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 10 | import baseConfig from './webpack.config.base'; 11 | import webpackPaths from './webpack.paths'; 12 | import checkNodeEnv from '../scripts/check-node-env'; 13 | import deleteSourceMaps from '../scripts/delete-source-maps'; 14 | 15 | checkNodeEnv('production'); 16 | deleteSourceMaps(); 17 | 18 | const configuration: webpack.Configuration = { 19 | devtool: 'source-map', 20 | 21 | mode: 'production', 22 | 23 | target: 'electron-main', 24 | 25 | entry: { 26 | main: path.join(webpackPaths.srcMainPath, 'main.ts'), 27 | preload: path.join(webpackPaths.srcMainPath, 'preload.ts'), 28 | }, 29 | 30 | output: { 31 | path: webpackPaths.distMainPath, 32 | filename: '[name].js', 33 | }, 34 | 35 | optimization: { 36 | minimizer: [ 37 | new TerserPlugin({ 38 | parallel: true, 39 | }), 40 | ], 41 | }, 42 | 43 | plugins: [ 44 | new BundleAnalyzerPlugin({ 45 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 46 | }), 47 | 48 | /** 49 | * Create global constants which can be configured at compile time. 50 | * 51 | * Useful for allowing different behaviour between development builds and 52 | * release builds 53 | * 54 | * NODE_ENV should be production so that modules do not perform certain 55 | * development checks 56 | */ 57 | new webpack.EnvironmentPlugin({ 58 | NODE_ENV: 'production', 59 | DEBUG_PROD: false, 60 | START_MINIMIZED: false, 61 | }), 62 | ], 63 | 64 | /** 65 | * Disables webpack processing of __dirname and __filename. 66 | * If you run the bundle in node.js it falls back to these values of node.js. 67 | * https://github.com/webpack/webpack/issues/2010 68 | */ 69 | node: { 70 | __dirname: false, 71 | __filename: false, 72 | }, 73 | }; 74 | 75 | export default merge(baseConfig, configuration); 76 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.preload.dev.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import { merge } from 'webpack-merge'; 4 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 5 | import baseConfig from './webpack.config.base'; 6 | import webpackPaths from './webpack.paths'; 7 | import checkNodeEnv from '../scripts/check-node-env'; 8 | 9 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's 10 | // at the dev webpack config is not accidentally run in a production environment 11 | if (process.env.NODE_ENV === 'production') { 12 | checkNodeEnv('development'); 13 | } 14 | 15 | const configuration: webpack.Configuration = { 16 | devtool: 'inline-source-map', 17 | 18 | mode: 'development', 19 | 20 | target: 'electron-preload', 21 | 22 | entry: path.join(webpackPaths.srcMainPath, 'preload.ts'), 23 | 24 | output: { 25 | path: webpackPaths.dllPath, 26 | filename: 'preload.js', 27 | }, 28 | 29 | plugins: [ 30 | new BundleAnalyzerPlugin({ 31 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 32 | }), 33 | 34 | /** 35 | * Create global constants which can be configured at compile time. 36 | * 37 | * Useful for allowing different behaviour between development builds and 38 | * release builds 39 | * 40 | * NODE_ENV should be production so that modules do not perform certain 41 | * development checks 42 | * 43 | * By default, use 'development' as NODE_ENV. This can be overriden with 44 | * 'staging', for example, by changing the ENV variables in the npm scripts 45 | */ 46 | new webpack.EnvironmentPlugin({ 47 | NODE_ENV: 'development', 48 | }), 49 | 50 | new webpack.LoaderOptionsPlugin({ 51 | debug: true, 52 | }), 53 | ], 54 | 55 | /** 56 | * Disables webpack processing of __dirname and __filename. 57 | * If you run the bundle in node.js it falls back to these values of node.js. 58 | * https://github.com/webpack/webpack/issues/2010 59 | */ 60 | node: { 61 | __dirname: false, 62 | __filename: false, 63 | }, 64 | 65 | watch: true, 66 | }; 67 | 68 | export default merge(baseConfig, configuration); 69 | -------------------------------------------------------------------------------- /.erb/scripts/check-native-dep.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import chalk from 'chalk'; 3 | import { execSync } from 'child_process'; 4 | import { dependencies } from '../../package.json'; 5 | 6 | if (dependencies) { 7 | const dependenciesKeys = Object.keys(dependencies); 8 | const nativeDeps = fs 9 | .readdirSync('node_modules') 10 | .filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`)); 11 | if (nativeDeps.length === 0) { 12 | process.exit(0); 13 | } 14 | try { 15 | // Find the reason for why the dependency is installed. If it is installed 16 | // because of a devDependency then that is okay. Warn when it is installed 17 | // because of a dependency 18 | const { dependencies: dependenciesObject } = JSON.parse( 19 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString() 20 | ); 21 | const rootDependencies = Object.keys(dependenciesObject); 22 | const filteredRootDependencies = rootDependencies.filter((rootDependency) => 23 | dependenciesKeys.includes(rootDependency) 24 | ); 25 | if (filteredRootDependencies.length > 0) { 26 | const plural = filteredRootDependencies.length > 1; 27 | console.log(` 28 | ${chalk.whiteBright.bgYellow.bold( 29 | 'Webpack does not work with native dependencies.' 30 | )} 31 | ${chalk.bold(filteredRootDependencies.join(', '))} ${ 32 | plural ? 'are native dependencies' : 'is a native dependency' 33 | } and should be installed inside of the "./release/app" folder. 34 | First, uninstall the packages from "./package.json": 35 | ${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')} 36 | ${chalk.bold( 37 | 'Then, instead of installing the package to the root "./package.json":' 38 | )} 39 | ${chalk.whiteBright.bgRed.bold('npm install your-package')} 40 | ${chalk.bold('Install the package to "./release/app/package.json"')} 41 | ${chalk.whiteBright.bgGreen.bold( 42 | 'cd ./release/app && npm install your-package' 43 | )} 44 | Read more about native dependencies at: 45 | ${chalk.bold( 46 | 'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure' 47 | )} 48 | `); 49 | process.exit(1); 50 | } 51 | } catch (e) { 52 | console.log('Native dependencies could not be checked'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/renderer/components/printer/Screen.tsx: -------------------------------------------------------------------------------- 1 | import { MouseEvent, useRef, useState, useEffect } from 'react'; 2 | 3 | const Screen = ({ select }: { select: string }) => { 4 | const parentRef = useRef(null); 5 | const canvasRef = useRef(null); 6 | 7 | const [mouse, setMouse] = useState({ 8 | select, 9 | startX: 0, 10 | startY: 0, 11 | x0: 0, 12 | y0: 0, 13 | x1: 0, 14 | y1: 0, 15 | }); 16 | const [draging, setDraging] = useState(false); 17 | 18 | const closeScreen = () => { 19 | window.electron?.ipcRenderer.invoke('close-screen', { ...mouse }); 20 | }; 21 | 22 | const setCanvasSize = () => { 23 | const ctx = canvasRef.current; 24 | if (ctx) { 25 | ctx.width = window.innerWidth || 1000; 26 | ctx.height = window.innerHeight || 1000; 27 | } 28 | }; 29 | 30 | const handleMouseDown = (ev: MouseEvent) => { 31 | setMouse({ 32 | ...mouse, 33 | startX: ev.pageX - (canvasRef.current?.getBoundingClientRect().left || 0), 34 | startY: ev.pageY - (canvasRef.current?.getBoundingClientRect().top || 0), 35 | x0: ev.screenX, 36 | y0: ev.screenY, 37 | }); 38 | 39 | setCanvasSize(); 40 | setDraging(true); 41 | }; 42 | 43 | const handleMouseMove = (ev: MouseEvent) => { 44 | if (!draging) { 45 | return; 46 | } 47 | 48 | const ctx = canvasRef.current?.getContext('2d'); 49 | 50 | if (ctx) { 51 | const width = 52 | ev.pageX - 53 | (canvasRef.current?.getBoundingClientRect().left || 0) - 54 | mouse.startX; 55 | const height = 56 | ev.pageY - 57 | (canvasRef.current?.getBoundingClientRect().top || 0) - 58 | mouse.startY; 59 | 60 | setMouse({ 61 | ...mouse, 62 | x1: ev.screenX, 63 | y1: ev.screenY, 64 | }); 65 | 66 | ctx.clearRect( 67 | 0, 68 | 0, 69 | parentRef.current?.clientWidth || 1000, 70 | parentRef.current?.clientHeight || 1000 71 | ); 72 | ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; 73 | ctx.fillRect(mouse.startX, mouse.startY, width, height); 74 | } 75 | }; 76 | 77 | const handleMouseUp = () => { 78 | setDraging(false); 79 | closeScreen(); 80 | }; 81 | 82 | useEffect(() => { 83 | window.onresize = setCanvasSize; 84 | }, []); 85 | 86 | window.electron?.ipcRenderer.on('screen-show', () => { 87 | setCanvasSize(); 88 | }); 89 | 90 | return ( 91 |
96 | 103 |
104 | ); 105 | }; 106 | 107 | export default Screen; 108 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Build config for electron renderer process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 8 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 10 | import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'; 11 | import { merge } from 'webpack-merge'; 12 | import TerserPlugin from 'terser-webpack-plugin'; 13 | import baseConfig from './webpack.config.base'; 14 | import webpackPaths from './webpack.paths'; 15 | import checkNodeEnv from '../scripts/check-node-env'; 16 | import deleteSourceMaps from '../scripts/delete-source-maps'; 17 | 18 | checkNodeEnv('production'); 19 | deleteSourceMaps(); 20 | 21 | const configuration: webpack.Configuration = { 22 | devtool: 'source-map', 23 | 24 | mode: 'production', 25 | 26 | target: ['web', 'electron-renderer'], 27 | 28 | entry: [path.join(webpackPaths.srcRendererPath, 'index.tsx')], 29 | 30 | output: { 31 | path: webpackPaths.distRendererPath, 32 | publicPath: './', 33 | filename: 'renderer.js', 34 | library: { 35 | type: 'umd', 36 | }, 37 | }, 38 | 39 | module: { 40 | rules: [ 41 | { 42 | test: /\.s?(a|c)ss$/, 43 | use: [ 44 | MiniCssExtractPlugin.loader, 45 | { 46 | loader: 'css-loader', 47 | options: { 48 | modules: true, 49 | sourceMap: true, 50 | importLoaders: 1, 51 | }, 52 | }, 53 | 'sass-loader', 54 | ], 55 | include: /\.module\.s?(c|a)ss$/, 56 | }, 57 | { 58 | test: /\.s?(a|c)ss$/, 59 | use: [ 60 | MiniCssExtractPlugin.loader, 61 | 'css-loader', 62 | 'sass-loader', 63 | 'postcss-loader', 64 | ], 65 | exclude: /\.module\.s?(c|a)ss$/, 66 | }, 67 | // Fonts 68 | { 69 | test: /\.(woff|woff2|eot|ttf|otf)$/i, 70 | type: 'asset/resource', 71 | }, 72 | // Images 73 | { 74 | test: /\.(png|svg|jpg|jpeg|gif)$/i, 75 | type: 'asset/resource', 76 | }, 77 | ], 78 | }, 79 | 80 | optimization: { 81 | minimize: true, 82 | minimizer: [ 83 | new TerserPlugin({ 84 | parallel: true, 85 | }), 86 | new CssMinimizerPlugin(), 87 | ], 88 | }, 89 | 90 | plugins: [ 91 | /** 92 | * Create global constants which can be configured at compile time. 93 | * 94 | * Useful for allowing different behaviour between development builds and 95 | * release builds 96 | * 97 | * NODE_ENV should be production so that modules do not perform certain 98 | * development checks 99 | */ 100 | new webpack.EnvironmentPlugin({ 101 | NODE_ENV: 'production', 102 | DEBUG_PROD: false, 103 | }), 104 | 105 | new MiniCssExtractPlugin({ 106 | filename: 'style.css', 107 | }), 108 | 109 | new BundleAnalyzerPlugin({ 110 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 111 | }), 112 | 113 | new HtmlWebpackPlugin({ 114 | filename: 'index.html', 115 | template: path.join(webpackPaths.srcRendererPath, 'index.ejs'), 116 | minify: { 117 | collapseWhitespace: true, 118 | removeAttributeQuotes: true, 119 | removeComments: true, 120 | }, 121 | isBrowser: false, 122 | isDevelopment: process.env.NODE_ENV !== 'production', 123 | }), 124 | ], 125 | }; 126 | 127 | export default merge(baseConfig, configuration); 128 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.dev.ts: -------------------------------------------------------------------------------- 1 | import 'webpack-dev-server'; 2 | import path from 'path'; 3 | import fs from 'fs'; 4 | import webpack from 'webpack'; 5 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 6 | import chalk from 'chalk'; 7 | import { merge } from 'webpack-merge'; 8 | import { execSync, spawn } from 'child_process'; 9 | import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; 10 | import baseConfig from './webpack.config.base'; 11 | import webpackPaths from './webpack.paths'; 12 | import checkNodeEnv from '../scripts/check-node-env'; 13 | 14 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's 15 | // at the dev webpack config is not accidentally run in a production environment 16 | if (process.env.NODE_ENV === 'production') { 17 | checkNodeEnv('development'); 18 | } 19 | 20 | const port = process.env.PORT || 1212; 21 | const manifest = path.resolve(webpackPaths.dllPath, 'renderer.json'); 22 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 23 | const requiredByDLLConfig = module.parent!.filename.includes( 24 | 'webpack.config.renderer.dev.dll' 25 | ); 26 | 27 | /** 28 | * Warn if the DLL is not built 29 | */ 30 | if ( 31 | !requiredByDLLConfig && 32 | !(fs.existsSync(webpackPaths.dllPath) && fs.existsSync(manifest)) 33 | ) { 34 | console.log( 35 | chalk.black.bgYellow.bold( 36 | 'The DLL files are missing. Sit back while we build them for you with "npm run build-dll"' 37 | ) 38 | ); 39 | execSync('npm run postinstall'); 40 | } 41 | 42 | const configuration: webpack.Configuration = { 43 | devtool: 'inline-source-map', 44 | 45 | mode: 'development', 46 | 47 | target: ['web', 'electron-renderer'], 48 | 49 | entry: [ 50 | `webpack-dev-server/client?http://localhost:${port}/dist`, 51 | 'webpack/hot/only-dev-server', 52 | path.join(webpackPaths.srcRendererPath, 'index.tsx'), 53 | ], 54 | 55 | output: { 56 | path: webpackPaths.distRendererPath, 57 | publicPath: '/', 58 | filename: 'renderer.dev.js', 59 | library: { 60 | type: 'umd', 61 | }, 62 | }, 63 | 64 | module: { 65 | rules: [ 66 | { 67 | test: /\.s?css$/, 68 | use: [ 69 | 'style-loader', 70 | { 71 | loader: 'css-loader', 72 | options: { 73 | modules: true, 74 | sourceMap: true, 75 | importLoaders: 1, 76 | }, 77 | }, 78 | 'sass-loader', 79 | ], 80 | include: /\.module\.s?(c|a)ss$/, 81 | }, 82 | { 83 | test: /\.s?css$/, 84 | use: ['style-loader', 'css-loader', 'sass-loader', 'postcss-loader'], 85 | exclude: /\.module\.s?(c|a)ss$/, 86 | }, 87 | // Fonts 88 | { 89 | test: /\.(woff|woff2|eot|ttf|otf)$/i, 90 | type: 'asset/resource', 91 | }, 92 | // Images 93 | { 94 | test: /\.(png|svg|jpg|jpeg|gif)$/i, 95 | type: 'asset/resource', 96 | }, 97 | ], 98 | }, 99 | plugins: [ 100 | ...(requiredByDLLConfig 101 | ? [] 102 | : [ 103 | new webpack.DllReferencePlugin({ 104 | context: webpackPaths.dllPath, 105 | manifest: require(manifest), 106 | sourceType: 'var', 107 | }), 108 | ]), 109 | 110 | new webpack.NoEmitOnErrorsPlugin(), 111 | 112 | /** 113 | * Create global constants which can be configured at compile time. 114 | * 115 | * Useful for allowing different behaviour between development builds and 116 | * release builds 117 | * 118 | * NODE_ENV should be production so that modules do not perform certain 119 | * development checks 120 | * 121 | * By default, use 'development' as NODE_ENV. This can be overriden with 122 | * 'staging', for example, by changing the ENV variables in the npm scripts 123 | */ 124 | new webpack.EnvironmentPlugin({ 125 | NODE_ENV: 'development', 126 | }), 127 | 128 | new webpack.LoaderOptionsPlugin({ 129 | debug: true, 130 | }), 131 | 132 | new ReactRefreshWebpackPlugin(), 133 | 134 | new HtmlWebpackPlugin({ 135 | filename: path.join('index.html'), 136 | template: path.join(webpackPaths.srcRendererPath, 'index.ejs'), 137 | minify: { 138 | collapseWhitespace: true, 139 | removeAttributeQuotes: true, 140 | removeComments: true, 141 | }, 142 | isBrowser: false, 143 | env: process.env.NODE_ENV, 144 | isDevelopment: process.env.NODE_ENV !== 'production', 145 | nodeModules: webpackPaths.appNodeModulesPath, 146 | }), 147 | ], 148 | 149 | node: { 150 | __dirname: false, 151 | __filename: false, 152 | }, 153 | 154 | devServer: { 155 | port, 156 | compress: true, 157 | hot: true, 158 | headers: { 'Access-Control-Allow-Origin': '*' }, 159 | static: { 160 | publicPath: '/', 161 | }, 162 | historyApiFallback: { 163 | verbose: true, 164 | }, 165 | setupMiddlewares(middlewares) { 166 | console.log('Starting preload.js builder...'); 167 | const preloadProcess = spawn('npm', ['run', 'start:preload'], { 168 | shell: true, 169 | stdio: 'inherit', 170 | }) 171 | .on('close', (code: number) => process.exit(code!)) 172 | .on('error', (spawnError) => console.error(spawnError)); 173 | 174 | console.log('Starting Main Process...'); 175 | let args = ['run', 'start:main']; 176 | if (process.env.MAIN_ARGS) { 177 | args = args.concat( 178 | ['--', ...process.env.MAIN_ARGS.matchAll(/"[^"]+"|[^\s"]+/g)].flat() 179 | ); 180 | } 181 | spawn('npm', args, { 182 | shell: true, 183 | stdio: 'inherit', 184 | }) 185 | .on('close', (code: number) => { 186 | preloadProcess.kill(); 187 | process.exit(code!); 188 | }) 189 | .on('error', (spawnError) => console.error(spawnError)); 190 | return middlewares; 191 | }, 192 | }, 193 | }; 194 | 195 | export default merge(baseConfig, configuration); 196 | -------------------------------------------------------------------------------- /src/main/menu.ts: -------------------------------------------------------------------------------- 1 | import { 2 | app, 3 | Menu, 4 | shell, 5 | BrowserWindow, 6 | MenuItemConstructorOptions, 7 | } from 'electron'; 8 | 9 | interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions { 10 | selector?: string; 11 | submenu?: DarwinMenuItemConstructorOptions[] | Menu; 12 | } 13 | 14 | export default class MenuBuilder { 15 | mainWindow: BrowserWindow; 16 | 17 | constructor(mainWindow: BrowserWindow) { 18 | this.mainWindow = mainWindow; 19 | } 20 | 21 | buildMenu(): Menu { 22 | if ( 23 | process.env.NODE_ENV === 'development' || 24 | process.env.DEBUG_PROD === 'true' 25 | ) { 26 | this.setupDevelopmentEnvironment(); 27 | } 28 | 29 | const template = 30 | process.platform === 'darwin' 31 | ? this.buildDarwinTemplate() 32 | : this.buildDefaultTemplate(); 33 | 34 | const menu = Menu.buildFromTemplate(template); 35 | Menu.setApplicationMenu(menu); 36 | 37 | return menu; 38 | } 39 | 40 | setupDevelopmentEnvironment(): void { 41 | this.mainWindow.webContents.on('context-menu', (_, props) => { 42 | const { x, y } = props; 43 | 44 | Menu.buildFromTemplate([ 45 | { 46 | label: 'Inspect element', 47 | click: () => { 48 | this.mainWindow.webContents.inspectElement(x, y); 49 | }, 50 | }, 51 | ]).popup({ window: this.mainWindow }); 52 | }); 53 | } 54 | 55 | buildDarwinTemplate(): MenuItemConstructorOptions[] { 56 | const subMenuAbout: DarwinMenuItemConstructorOptions = { 57 | label: 'Help', 58 | submenu: [ 59 | { 60 | label: 'About', 61 | selector: 'orderFrontStandardAboutPanel:', 62 | }, 63 | { type: 'separator' }, 64 | { 65 | label: 'Hide', 66 | accelerator: 'Command+H', 67 | selector: 'hide:', 68 | }, 69 | { 70 | label: 'Hide Others', 71 | accelerator: 'Command+Shift+H', 72 | selector: 'hideOtherApplications:', 73 | }, 74 | { label: 'Show All', selector: 'unhideAllApplications:' }, 75 | { type: 'separator' }, 76 | { 77 | label: 'Quit', 78 | accelerator: 'Command+Q', 79 | click: () => { 80 | app.quit(); 81 | }, 82 | }, 83 | ], 84 | }; 85 | const subMenuViewDev: MenuItemConstructorOptions = { 86 | label: 'View', 87 | submenu: [ 88 | { 89 | label: 'Reload', 90 | accelerator: 'Command+R', 91 | click: () => { 92 | this.mainWindow.webContents.reload(); 93 | }, 94 | }, 95 | { 96 | label: 'Toggle Full Screen', 97 | accelerator: 'Ctrl+Command+F', 98 | click: () => { 99 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); 100 | }, 101 | }, 102 | { 103 | label: 'Toggle Developer Tools', 104 | accelerator: 'Alt+Command+I', 105 | click: () => { 106 | this.mainWindow.webContents.toggleDevTools(); 107 | }, 108 | }, 109 | ], 110 | }; 111 | const subMenuWindow: DarwinMenuItemConstructorOptions = { 112 | label: 'Window', 113 | submenu: [ 114 | { 115 | label: 'Minimize', 116 | accelerator: 'Command+M', 117 | selector: 'performMiniaturize:', 118 | }, 119 | { label: 'Close', accelerator: 'Command+W', selector: 'performClose:' }, 120 | { type: 'separator' }, 121 | { label: 'Bring All to Front', selector: 'arrangeInFront:' }, 122 | ], 123 | }; 124 | const subMenuHelp: MenuItemConstructorOptions = { 125 | label: 'Help', 126 | submenu: [ 127 | { 128 | label: 'Privacy', 129 | click() { 130 | shell.openExternal( 131 | 'https://screenprinter.manhtai.com/privacy.html' 132 | ); 133 | }, 134 | }, 135 | { 136 | label: 'Support', 137 | click() { 138 | shell.openExternal( 139 | 'https://screenprinter.manhtai.com/support.html' 140 | ); 141 | }, 142 | }, 143 | ], 144 | }; 145 | 146 | if ( 147 | process.env.NODE_ENV === 'development' || 148 | process.env.DEBUG_PROD === 'true' 149 | ) { 150 | return [subMenuAbout, subMenuViewDev, subMenuWindow, subMenuHelp]; 151 | } 152 | 153 | return [subMenuAbout, subMenuWindow, subMenuHelp]; 154 | } 155 | 156 | buildDefaultTemplate() { 157 | const templateDefault = [ 158 | { 159 | label: '&File', 160 | submenu: [ 161 | { 162 | label: '&Open', 163 | accelerator: 'Ctrl+O', 164 | }, 165 | { 166 | label: '&Close', 167 | accelerator: 'Ctrl+W', 168 | click: () => { 169 | this.mainWindow.close(); 170 | }, 171 | }, 172 | ], 173 | }, 174 | { 175 | label: '&View', 176 | submenu: 177 | process.env.NODE_ENV === 'development' || 178 | process.env.DEBUG_PROD === 'true' 179 | ? [ 180 | { 181 | label: '&Reload', 182 | accelerator: 'Ctrl+R', 183 | click: () => { 184 | this.mainWindow.webContents.reload(); 185 | }, 186 | }, 187 | { 188 | label: 'Toggle &Full Screen', 189 | accelerator: 'F11', 190 | click: () => { 191 | this.mainWindow.setFullScreen( 192 | !this.mainWindow.isFullScreen() 193 | ); 194 | }, 195 | }, 196 | { 197 | label: 'Toggle &Developer Tools', 198 | accelerator: 'Alt+Ctrl+I', 199 | click: () => { 200 | this.mainWindow.webContents.toggleDevTools(); 201 | }, 202 | }, 203 | ] 204 | : [ 205 | { 206 | label: 'Toggle &Full Screen', 207 | accelerator: 'F11', 208 | click: () => { 209 | this.mainWindow.setFullScreen( 210 | !this.mainWindow.isFullScreen() 211 | ); 212 | }, 213 | }, 214 | ], 215 | }, 216 | { 217 | label: 'Help', 218 | submenu: [ 219 | { 220 | label: 'Privacy', 221 | click() { 222 | shell.openExternal( 223 | 'https://screenprinter.manhtai.com/privacy.html' 224 | ); 225 | }, 226 | }, 227 | { 228 | label: 'Support', 229 | click() { 230 | shell.openExternal( 231 | 'https://screenprinter.manhtai.com/support.html' 232 | ); 233 | }, 234 | }, 235 | ], 236 | }, 237 | ]; 238 | 239 | return templateDefault; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/renderer/components/printer/Main.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { FaTimesCircle } from 'react-icons/fa'; 3 | 4 | interface Coord { 5 | select: string; 6 | x0: number; 7 | y0: number; 8 | x1: number; 9 | y1: number; 10 | } 11 | 12 | const Main = () => { 13 | const [frameCoord, setFrameCoord] = useState(); 14 | const [nextCoord, setNextCoord] = useState(); 15 | 16 | const [pages, setPages] = useState(1); 17 | const [delay, setDelay] = useState(1); 18 | const [pageNum, setPageNum] = useState(0); 19 | const [printing, setPrinting] = useState(false); 20 | 21 | const setFrameCoordValue = (label: string, value: number) => { 22 | if (frameCoord) { 23 | switch (label) { 24 | case 'x0': 25 | setFrameCoord({ ...frameCoord, x0: value }); 26 | break; 27 | case 'x1': 28 | setFrameCoord({ ...frameCoord, x1: value }); 29 | break; 30 | case 'y0': 31 | frameCoord.y0 = value; 32 | setFrameCoord({ ...frameCoord, y0: value }); 33 | break; 34 | case 'y1': 35 | setFrameCoord({ ...frameCoord, y1: value }); 36 | break; 37 | default: 38 | break; 39 | } 40 | } 41 | }; 42 | 43 | const setNextCoordValue = (label: string, value: number) => { 44 | if (nextCoord) { 45 | switch (label) { 46 | case 'x': 47 | setNextCoord({ ...nextCoord, x0: value, x1: value }); 48 | break; 49 | case 'y': 50 | setNextCoord({ ...nextCoord, y0: value, y1: value }); 51 | break; 52 | default: 53 | break; 54 | } 55 | } 56 | }; 57 | 58 | const handleCloseScreen = (c: Coord) => { 59 | if (c.select === 'frame') { 60 | setFrameCoord({ ...c }); 61 | } else if (c.select === 'next') { 62 | setNextCoord({ ...c }); 63 | } 64 | }; 65 | 66 | const handleOpenScreen = (select: string) => { 67 | if (select === 'frame') { 68 | setFrameCoord(undefined); 69 | } else { 70 | setNextCoord(undefined); 71 | } 72 | 73 | window.electron?.ipcRenderer.invoke('open-screen', { select }); 74 | }; 75 | 76 | const handlePrint = () => { 77 | if (printing) { 78 | setPrinting(false); 79 | window.electron?.ipcRenderer.invoke('stop-printing'); 80 | } else { 81 | setPrinting(true); 82 | setPageNum(0); 83 | window.electron?.ipcRenderer.invoke('start-printing', { 84 | frameCoord, 85 | nextCoord, 86 | pages: nextCoord ? pages : 1, 87 | delay, 88 | }); 89 | } 90 | }; 91 | 92 | window.electron?.ipcRenderer.on('close-screen', (_, c: Coord) => { 93 | handleCloseScreen(c); 94 | }); 95 | 96 | window.electron?.ipcRenderer.on( 97 | 'print-progress', 98 | (_, { page, done }: { page: number; done: boolean }) => { 99 | setPageNum(page); 100 | if (done) { 101 | setPrinting(false); 102 | } 103 | } 104 | ); 105 | 106 | return ( 107 |
108 |
109 |
110 | 117 | 118 |

119 | Rectangle: 120 | 121 | 125 | setFrameCoordValue('x0', parseInt(e.target.value, 10)) 126 | } 127 | disabled={!frameCoord} 128 | /> 129 | 133 | setFrameCoordValue('y0', parseInt(e.target.value, 10)) 134 | } 135 | disabled={!frameCoord} 136 | /> 137 | 138 | 139 | 143 | setFrameCoordValue('x1', parseInt(e.target.value, 10)) 144 | } 145 | disabled={!frameCoord} 146 | /> 147 | 151 | setFrameCoordValue('y1', parseInt(e.target.value, 10)) 152 | } 153 | disabled={!frameCoord} 154 | /> 155 | 156 |

157 | {frameCoord && ( 158 | setFrameCoord(undefined)} 160 | className="w-3 h-3 cursor-pointer hover:opacity-50" 161 | /> 162 | )} 163 |
164 |
165 | 166 |
167 |
168 | 175 | 176 |

177 | Point: 178 | 179 | 183 | setNextCoordValue('x', parseInt(e.target.value, 10)) 184 | } 185 | disabled={!nextCoord} 186 | /> 187 | 191 | setNextCoordValue('y', parseInt(e.target.value, 10)) 192 | } 193 | disabled={!nextCoord} 194 | /> 195 | 196 |

197 | {nextCoord && ( 198 | setNextCoord(undefined)} 200 | className="w-3 h-3 cursor-pointer hover:opacity-50" 201 | /> 202 | )} 203 |
204 |
205 | 206 |
211 |

Click next button every:

212 | setDelay(parseInt(e.target.value, 10))} 215 | type="number" 216 | className="w-10" 217 | disabled={!nextCoord} 218 | /> 219 |

second{delay === 1 ? '' : 's'}

220 |
221 | 222 |
227 |

Total clicks:

228 | setPages(parseInt(e.target.value, 10))} 231 | type="number" 232 | className="w-20" 233 | disabled={!nextCoord} 234 | /> 235 |

236 | {pageNum > 0 && printing 237 | ? `(Printing page ${pageNum} of ${pages}...)` 238 | : null} 239 |

240 |
241 |
242 |
243 | 244 |
245 | 253 |
254 |
255 | ); 256 | }; 257 | 258 | export default Main; 259 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Auto PDF screen printer", 3 | "keywords": [ 4 | "pdf", 5 | "screen printer" 6 | ], 7 | "homepage": "https://github.com/plainlab/plainprinter", 8 | "bugs": { 9 | "url": "https://github.com/plainlab/plainprinter/issues" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/plainlab/plainprinter.git" 14 | }, 15 | "license": "GPL-3.0-only", 16 | "author": { 17 | "name": "Tai Vo", 18 | "email": "screenprinter@manhtai.com", 19 | "url": "https://screenprinter.manhtai.com" 20 | }, 21 | "main": "./src/main/main.ts", 22 | "scripts": { 23 | "build": "concurrently \"npm run build:main\" \"npm run build:renderer\"", 24 | "build:main": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.prod.ts", 25 | "build:renderer": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.prod.ts", 26 | "postinstall": "ts-node .erb/scripts/check-native-dep.js && electron-builder install-app-deps && cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.dev.dll.ts", 27 | "lint": "cross-env NODE_ENV=development eslint . --ext .js,.jsx,.ts,.tsx", 28 | "package": "ts-node ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never", 29 | "package:mas": "ts-node ./.erb/scripts/clean.js dist && npm run build && electron-builder build --mac mas --x64 --config.afterSign=.erb/scripts/doNothing.js --publish never", 30 | "prepare": "husky install", 31 | "rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir release/app", 32 | "start": "ts-node ./.erb/scripts/check-port-in-use.js && npm run start:renderer", 33 | "start:main": "cross-env NODE_ENV=development electronmon -r ts-node/register/transpile-only .", 34 | "start:preload": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts", 35 | "start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts", 36 | "test": "jest --passWithNoTests" 37 | }, 38 | "lint-staged": { 39 | "*.{js,jsx,ts,tsx}": [ 40 | "cross-env NODE_ENV=development eslint --cache" 41 | ], 42 | "*.json,.{eslintrc,prettierrc}": [ 43 | "prettier --ignore-path .eslintignore --parser json --write" 44 | ], 45 | "*.{css,scss}": [ 46 | "prettier --ignore-path .eslintignore --single-quote --write" 47 | ], 48 | "*.{html,md,yml}": [ 49 | "prettier --ignore-path .eslintignore --single-quote --write" 50 | ] 51 | }, 52 | "browserslist": [], 53 | "prettier": { 54 | "singleQuote": true, 55 | "overrides": [ 56 | { 57 | "files": [ 58 | ".prettierrc", 59 | ".eslintrc" 60 | ], 61 | "options": { 62 | "parser": "json" 63 | } 64 | } 65 | ] 66 | }, 67 | "jest": { 68 | "moduleDirectories": [ 69 | "node_modules", 70 | "release/app/node_modules" 71 | ], 72 | "moduleFileExtensions": [ 73 | "js", 74 | "jsx", 75 | "ts", 76 | "tsx", 77 | "json" 78 | ], 79 | "moduleNameMapper": { 80 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/.erb/mocks/fileMock.js", 81 | "\\.(css|less|sass|scss)$": "identity-obj-proxy" 82 | }, 83 | "setupFiles": [ 84 | "./.erb/scripts/check-build-exists.ts" 85 | ], 86 | "testEnvironment": "jsdom", 87 | "testEnvironmentOptions": { 88 | "url": "http://localhost/" 89 | }, 90 | "testPathIgnorePatterns": [ 91 | "release/app/dist" 92 | ], 93 | "transform": { 94 | "\\.(ts|tsx|js|jsx)$": "ts-jest" 95 | } 96 | }, 97 | "dependencies": { 98 | "electron-debug": "^3.2.0", 99 | "electron-log": "^4.4.7", 100 | "electron-store": "^8.1.0", 101 | "electron-updater": "^5.0.3", 102 | "history": "^5.0.0", 103 | "qs": "^6.10.1", 104 | "react": "^18.2.0", 105 | "react-dom": "^18.2.0", 106 | "react-helmet": "^6.1.0", 107 | "react-icons": "^4.4.0", 108 | "react-router-dom": "^6.3.0" 109 | }, 110 | "devDependencies": { 111 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.6", 112 | "@teamsupercell/typings-for-css-modules-loader": "^2.5.1", 113 | "@testing-library/jest-dom": "^5.16.4", 114 | "@testing-library/react": "^13.2.0", 115 | "@types/history": "4.7.6", 116 | "@types/jest": "^27.5.1", 117 | "@types/node": "17.0.33", 118 | "@types/qs": "^6.9.7", 119 | "@types/react": "^18.0.9", 120 | "@types/react-dom": "^18.0.4", 121 | "@types/react-test-renderer": "^18.0.0", 122 | "@types/terser-webpack-plugin": "^5.0.4", 123 | "@types/webpack-bundle-analyzer": "^4.4.1", 124 | "@typescript-eslint/eslint-plugin": "^5.23.0", 125 | "@typescript-eslint/parser": "^5.23.0", 126 | "autoprefixer": "^10.4.8", 127 | "browserslist-config-erb": "^0.0.3", 128 | "chalk": "^4.1.2", 129 | "concurrently": "^7.1.0", 130 | "core-js": "^3.22.5", 131 | "cross-env": "^7.0.3", 132 | "css-loader": "^6.7.1", 133 | "css-minimizer-webpack-plugin": "^3.4.1", 134 | "detect-port": "^1.3.0", 135 | "electron": "^18.2.3", 136 | "electron-builder": "^23", 137 | "electron-devtools-installer": "^3.2.0", 138 | "electron-notarize": "^1.2.1", 139 | "electron-rebuild": "^3.2.7", 140 | "electronmon": "^2.0.2", 141 | "eslint": "^8.15.0", 142 | "eslint-config-airbnb-base": "^15.0.0", 143 | "eslint-config-erb": "^4.0.3", 144 | "eslint-import-resolver-typescript": "^2.7.1", 145 | "eslint-import-resolver-webpack": "^0.13.2", 146 | "eslint-plugin-compat": "^4.0.2", 147 | "eslint-plugin-import": "^2.26.0", 148 | "eslint-plugin-jest": "^26.2.2", 149 | "eslint-plugin-jsx-a11y": "^6.5.1", 150 | "eslint-plugin-promise": "^6.0.0", 151 | "eslint-plugin-react": "^7.29.4", 152 | "eslint-plugin-react-hooks": "^4.5.0", 153 | "file-loader": "^6.2.0", 154 | "html-webpack-plugin": "^5.5.0", 155 | "husky": "^8.0.1", 156 | "identity-obj-proxy": "^3.0.0", 157 | "jest": "^28.1.0", 158 | "jest-environment-jsdom": "^28.1.0", 159 | "lint-staged": "^12.4.1", 160 | "mini-css-extract-plugin": "^2.6.0", 161 | "postcss": "^8.3.6", 162 | "postcss-loader": "^6.1.1", 163 | "prettier": "^2.6.2", 164 | "react-refresh": "^0.13.0", 165 | "react-test-renderer": "^18.1.0", 166 | "rimraf": "^3.0.2", 167 | "sass": "^1.51.0", 168 | "sass-loader": "^12.6.0", 169 | "style-loader": "^3.3.1", 170 | "tailwindcss": "2", 171 | "terser-webpack-plugin": "^5.3.1", 172 | "ts-jest": "^28.0.2", 173 | "ts-loader": "^9.3.0", 174 | "ts-node": "^10.7.0", 175 | "typescript": "^4.6.4", 176 | "url-loader": "^4.1.1", 177 | "webpack": "^5.72.1", 178 | "webpack-bundle-analyzer": "^4.5.0", 179 | "webpack-cli": "^4.9.2", 180 | "webpack-dev-server": "^4.9.0", 181 | "webpack-merge": "^5.8.0" 182 | }, 183 | "build": { 184 | "productName": "Screen Printer", 185 | "appId": "com.manhtai.screenprinter", 186 | "asar": true, 187 | "asarUnpack": [ 188 | "node_modules/screenshot-desktop/lib/win32", 189 | "**\\*.{node,dll}" 190 | ], 191 | "files": [ 192 | "dist", 193 | "node_modules", 194 | "package.json" 195 | ], 196 | "afterSign": ".erb/scripts/notarize.js", 197 | "mac": { 198 | "target": [ 199 | { 200 | "target": "dmg", 201 | "arch": [ 202 | "arm64", 203 | "x64" 204 | ] 205 | } 206 | ], 207 | "type": "distribution", 208 | "hardenedRuntime": true, 209 | "entitlements": "assets/entitlements.mac.plist", 210 | "entitlementsInherit": "assets/entitlements.mac.plist", 211 | "provisioningProfile": "assets/embedded.provisionprofile", 212 | "gatekeeperAssess": false 213 | }, 214 | "mas": { 215 | "entitlements": "assets/entitlements.mas.plist", 216 | "entitlementsInherit": "assets/entitlements.mas.inherit.plist", 217 | "entitlementsLoginHelper": "assets/entitlements.mas.inherit.plist", 218 | "hardenedRuntime": false 219 | }, 220 | "dmg": { 221 | "contents": [ 222 | { 223 | "x": 130, 224 | "y": 220 225 | }, 226 | { 227 | "x": 410, 228 | "y": 220, 229 | "type": "link", 230 | "path": "/Applications" 231 | } 232 | ] 233 | }, 234 | "win": { 235 | "target": [ 236 | "nsis" 237 | ] 238 | }, 239 | "linux": { 240 | "target": [ 241 | "AppImage" 242 | ], 243 | "category": "Development" 244 | }, 245 | "directories": { 246 | "app": "release/app", 247 | "buildResources": "assets", 248 | "output": "release/build" 249 | }, 250 | "extraResources": [ 251 | "./assets/**" 252 | ], 253 | "publish": { 254 | "provider": "github", 255 | "owner": "plainlab", 256 | "repo": "plainprinter" 257 | } 258 | }, 259 | "devEngines": { 260 | "node": ">=14.x", 261 | "npm": ">=7.x" 262 | }, 263 | "electronmon": { 264 | "patterns": [ 265 | "!**/**", 266 | "src/main/*" 267 | ], 268 | "logLevel": "quiet" 269 | } 270 | } -------------------------------------------------------------------------------- /src/main/main.ts: -------------------------------------------------------------------------------- 1 | /* eslint global-require: off, no-console: off, promise/always-return: off */ 2 | 3 | /** 4 | * This module executes inside of electron's main process. You can start 5 | * electron renderer process from here and communicate with the other processes 6 | * through IPC. 7 | * 8 | * When running `npm run build` or `npm run build:main`, this file is compiled to 9 | * `./src/main.js` using webpack. This gives us some performance wins. 10 | */ 11 | import path from 'path'; 12 | import { 13 | app, 14 | BrowserWindow, 15 | shell, 16 | ipcMain, 17 | dialog, 18 | nativeImage, 19 | screen, 20 | FileFilter, 21 | Rectangle, 22 | } from 'electron'; 23 | import { autoUpdater } from 'electron-updater'; 24 | import log from 'electron-log'; 25 | import { promisify } from 'util'; 26 | import fs from 'fs'; 27 | import nodeurl from 'url'; 28 | 29 | import MenuBuilder from './menu'; 30 | import { resolveHtmlPath } from './util'; 31 | 32 | const screenshot = require('screenshot-desktop'); 33 | const PDFDocument = require('pdfkit'); 34 | const robot = require('@jitsi/robotjs'); 35 | const Store = require('electron-store'); 36 | 37 | const writeFile = promisify(fs.writeFile); 38 | const readFile = promisify(fs.readFile); 39 | 40 | const store = new Store({ 41 | hotkey: String, 42 | }); 43 | 44 | class AppUpdater { 45 | constructor() { 46 | log.transports.file.level = 'info'; 47 | autoUpdater.logger = log; 48 | autoUpdater.checkForUpdatesAndNotify(); 49 | } 50 | } 51 | 52 | let mainWindow: BrowserWindow | null = null; 53 | let screenWindow: BrowserWindow | null = null; 54 | let stopPrinting = false; 55 | 56 | ipcMain.on('ipc-example', async (event, arg) => { 57 | const msgTemplate = (pingPong: string) => `IPC test: ${pingPong}`; 58 | console.log(msgTemplate(arg)); 59 | event.reply('ipc-example', msgTemplate('pong')); 60 | }); 61 | 62 | if (process.env.NODE_ENV === 'production') { 63 | const sourceMapSupport = require('source-map-support'); 64 | sourceMapSupport.install(); 65 | } 66 | 67 | const isDebug = 68 | process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true'; 69 | 70 | if (isDebug) { 71 | require('electron-debug')(); 72 | } 73 | 74 | const installExtensions = async () => { 75 | const installer = require('electron-devtools-installer'); 76 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS; 77 | const extensions = ['REACT_DEVELOPER_TOOLS']; 78 | 79 | return installer 80 | .default( 81 | extensions.map((name) => installer[name]), 82 | forceDownload 83 | ) 84 | .catch(console.log); 85 | }; 86 | 87 | const createWindow = async () => { 88 | if (isDebug) { 89 | await installExtensions(); 90 | } 91 | 92 | const RESOURCES_PATH = app.isPackaged 93 | ? path.join(process.resourcesPath, 'assets') 94 | : path.join(__dirname, '../../assets'); 95 | 96 | const getAssetPath = (...paths: string[]): string => { 97 | return path.join(RESOURCES_PATH, ...paths); 98 | }; 99 | 100 | mainWindow = new BrowserWindow({ 101 | show: false, 102 | width: 800, 103 | height: 400, 104 | maximizable: false, 105 | fullscreen: false, 106 | fullscreenable: false, 107 | resizable: false, 108 | icon: getAssetPath('icon.png'), 109 | webPreferences: { 110 | preload: app.isPackaged 111 | ? path.join(__dirname, 'preload.js') 112 | : path.join(__dirname, '../../.erb/dll/preload.js'), 113 | }, 114 | }); 115 | 116 | mainWindow.loadURL(resolveHtmlPath('index.html')); 117 | 118 | mainWindow.on('ready-to-show', () => { 119 | if (!mainWindow) { 120 | throw new Error('"mainWindow" is not defined'); 121 | } 122 | if (process.env.START_MINIMIZED) { 123 | mainWindow.minimize(); 124 | } else { 125 | mainWindow.show(); 126 | } 127 | }); 128 | 129 | mainWindow.on('closed', () => { 130 | mainWindow = null; 131 | }); 132 | 133 | const menuBuilder = new MenuBuilder(mainWindow); 134 | menuBuilder.buildMenu(); 135 | 136 | // Open urls in the user's browser 137 | mainWindow.webContents.setWindowOpenHandler((edata) => { 138 | shell.openExternal(edata.url); 139 | return { action: 'deny' }; 140 | }); 141 | 142 | // Remove this if your app does not use auto updates 143 | // eslint-disable-next-line 144 | new AppUpdater(); 145 | }; 146 | 147 | /** 148 | * Handler React calls 149 | */ 150 | 151 | ipcMain.handle( 152 | 'open-file', 153 | async (_event, filters: FileFilter[], type: 'path' | 'buffer') => { 154 | const files = await dialog.showOpenDialog({ 155 | properties: ['openFile'], 156 | filters, 157 | }); 158 | 159 | let content; 160 | if (files) { 161 | const fpath = files.filePaths[0]; 162 | if (type === 'path') { 163 | content = fpath; 164 | } else { 165 | content = await readFile(fpath); 166 | } 167 | } 168 | return content; 169 | } 170 | ); 171 | 172 | ipcMain.handle( 173 | 'save-file', 174 | async (_event, { defaultPath, content, encoding }) => { 175 | const file = await dialog.showSaveDialog({ 176 | defaultPath, 177 | }); 178 | 179 | if (!file || !file.filePath) return; 180 | 181 | await writeFile(file.filePath, content, { 182 | encoding, 183 | }); 184 | } 185 | ); 186 | 187 | const createScreenWindow = (select: string) => { 188 | if (screenWindow == null) { 189 | screenWindow = new BrowserWindow({ 190 | frame: false, 191 | transparent: true, 192 | minimizable: false, 193 | maximizable: false, 194 | parent: mainWindow || undefined, 195 | width: screen.getPrimaryDisplay().size.width, 196 | height: screen.getPrimaryDisplay().size.height, 197 | webPreferences: { 198 | nodeIntegration: true, 199 | contextIsolation: true, 200 | preload: app.isPackaged 201 | ? path.join(__dirname, 'preload.js') 202 | : path.join(__dirname, '../../.erb/dll/preload.js'), 203 | }, 204 | }); 205 | } 206 | screenWindow.loadURL( 207 | `${resolveHtmlPath('index.html')}?page=screen&select=${select}` 208 | ); 209 | 210 | screenWindow.webContents.on('did-finish-load', () => { 211 | screenWindow?.show(); 212 | screenWindow?.webContents.send('screen-show'); 213 | }); 214 | 215 | screenWindow.on('closed', () => { 216 | screenWindow = null; 217 | }); 218 | }; 219 | 220 | const openPdf = (pdfPath: string) => { 221 | const win = new BrowserWindow({ 222 | title: 'Preview', 223 | width: 512, 224 | height: 768, 225 | webPreferences: { 226 | plugins: true, 227 | contextIsolation: false, 228 | }, 229 | }); 230 | win.loadURL(nodeurl.pathToFileURL(pdfPath).toString()); 231 | }; 232 | 233 | ipcMain.handle('open-screen', async (_, { select }) => { 234 | createScreenWindow(select); 235 | }); 236 | 237 | ipcMain.handle('get-store', (_event, { key }) => { 238 | return store.get(key); 239 | }); 240 | 241 | ipcMain.handle('close-screen', (_event, coord) => { 242 | mainWindow?.webContents.send('close-screen', coord); 243 | screenWindow?.close(); 244 | }); 245 | 246 | interface Coord { 247 | select: string; 248 | x0: number; 249 | y0: number; 250 | x1: number; 251 | y1: number; 252 | } 253 | 254 | interface Screenshot { 255 | frameCoord: Coord; 256 | nextCoord: Coord; 257 | pages: number; 258 | delay: number; 259 | } 260 | 261 | ipcMain.handle('stop-printing', () => { 262 | stopPrinting = true; 263 | }); 264 | 265 | ipcMain.handle( 266 | 'start-printing', 267 | async (_, { frameCoord, nextCoord, pages, delay }: Screenshot) => { 268 | // Calculate x, y 269 | let x = frameCoord.x0 > frameCoord.x1 ? frameCoord.x1 : frameCoord.x0; 270 | let y = frameCoord.x0 > frameCoord.x1 ? frameCoord.y1 : frameCoord.y0; 271 | let width = Math.abs(frameCoord.x0 - frameCoord.x1); 272 | let height = Math.abs(frameCoord.y0 - frameCoord.y1); 273 | 274 | // For retina screen and the like 275 | const factor = screen.getPrimaryDisplay().scaleFactor; 276 | x *= factor; 277 | y *= factor; 278 | width *= factor; 279 | height *= factor; 280 | 281 | x = Math.floor(x); 282 | y = Math.floor(y); 283 | width = Math.floor(width); 284 | height = Math.floor(height); 285 | 286 | const doc = new PDFDocument({ autoFirstPage: false }); 287 | const pdfPath = path.join(app.getPath('temp'), 'preview.pdf'); 288 | doc.pipe(fs.createWriteStream(pdfPath)); 289 | 290 | try { 291 | for (let p = 0; p < pages; p += 1) { 292 | // Screenshot 293 | // eslint-disable-next-line no-await-in-loop 294 | const buff: Buffer = await screenshot({ format: 'png' }); 295 | const image = nativeImage.createFromBuffer(buff); 296 | const rect = { x, y, height, width }; 297 | const croppedImage = image.crop(rect as Rectangle); 298 | const png = croppedImage.toPNG(); 299 | 300 | // Create pdf 301 | doc.addPage({ size: [width, height] }); 302 | doc.image(png, 0, 0); 303 | doc.save(); 304 | 305 | // Click 306 | if (nextCoord) { 307 | const nextX = Math.floor((nextCoord.x0 + nextCoord.x1) / 2); 308 | const nextY = Math.floor((nextCoord.y0 + nextCoord.y1) / 2); 309 | robot.moveMouse(nextX, nextY); 310 | robot.mouseClick(); 311 | } 312 | 313 | // Send progress 314 | mainWindow?.webContents.send('print-progress', { 315 | page: p + 1, 316 | done: p + 1 === pages, 317 | }); 318 | 319 | // Sleep 320 | // eslint-disable-next-line no-await-in-loop, no-promise-executor-return 321 | await new Promise((resolve) => setTimeout(resolve, 1000 * delay)); 322 | 323 | if (stopPrinting) { 324 | stopPrinting = false; 325 | mainWindow?.webContents.send('print-progress', { 326 | page: p + 1, 327 | done: true, 328 | }); 329 | break; 330 | } 331 | } 332 | 333 | doc.end(); 334 | openPdf(pdfPath); 335 | } catch (e: any) { 336 | dialog.showErrorBox(e.message, e.stack); 337 | console.error(e); 338 | } 339 | } 340 | ); 341 | 342 | /** 343 | * Add event listeners... 344 | */ 345 | 346 | app.on('window-all-closed', () => { 347 | app.quit(); 348 | }); 349 | 350 | app 351 | .whenReady() 352 | .then(() => { 353 | createWindow(); 354 | app.on('activate', () => { 355 | // On macOS it's common to re-create a window in the app when the 356 | // dock icon is clicked and there are no other windows open. 357 | if (mainWindow === null) createWindow(); 358 | }); 359 | }) 360 | .catch(console.log); 361 | -------------------------------------------------------------------------------- /release/app/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@jitsi/robotjs@^0.6.13": 6 | version "0.6.13" 7 | resolved "https://registry.yarnpkg.com/@jitsi/robotjs/-/robotjs-0.6.13.tgz#4d59d0951a83e786b5819363751cb45b51f3c06f" 8 | integrity sha512-uFxRQp83jbKfMzk3lYIjgevy1X1dU/Z7OMPnqfdO1LcyPaXsOf+FCWSxM/KIxz0PvbBbORxUzuae5O5dAF5Kqw== 9 | dependencies: 10 | node-addon-api "^4.2.0" 11 | node-gyp-build "^4.3.0" 12 | 13 | "@swc/helpers@^0.3.13": 14 | version "0.3.17" 15 | resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.3.17.tgz" 16 | integrity sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q== 17 | dependencies: 18 | tslib "^2.4.0" 19 | 20 | available-typed-arrays@^1.0.5: 21 | version "1.0.5" 22 | resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz" 23 | integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== 24 | 25 | balanced-match@^1.0.0: 26 | version "1.0.2" 27 | resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" 28 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 29 | 30 | base64-js@0.0.8: 31 | version "0.0.8" 32 | resolved "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz" 33 | integrity sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw== 34 | 35 | base64-js@^1.1.2, base64-js@^1.3.0: 36 | version "1.5.1" 37 | resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" 38 | integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== 39 | 40 | brace-expansion@^1.1.7: 41 | version "1.1.11" 42 | resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" 43 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 44 | dependencies: 45 | balanced-match "^1.0.0" 46 | concat-map "0.0.1" 47 | 48 | brotli@^1.3.2: 49 | version "1.3.3" 50 | resolved "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz" 51 | integrity sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg== 52 | dependencies: 53 | base64-js "^1.1.2" 54 | 55 | call-bind@^1.0.0, call-bind@^1.0.2: 56 | version "1.0.2" 57 | resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" 58 | integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== 59 | dependencies: 60 | function-bind "^1.1.1" 61 | get-intrinsic "^1.0.2" 62 | 63 | clone@^2.1.2: 64 | version "2.1.2" 65 | resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz" 66 | integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== 67 | 68 | concat-map@0.0.1: 69 | version "0.0.1" 70 | resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" 71 | integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== 72 | 73 | crypto-js@^4.0.0: 74 | version "4.1.1" 75 | resolved "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz" 76 | integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== 77 | 78 | deep-equal@^2.0.5: 79 | version "2.2.0" 80 | resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz" 81 | integrity sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw== 82 | dependencies: 83 | call-bind "^1.0.2" 84 | es-get-iterator "^1.1.2" 85 | get-intrinsic "^1.1.3" 86 | is-arguments "^1.1.1" 87 | is-array-buffer "^3.0.1" 88 | is-date-object "^1.0.5" 89 | is-regex "^1.1.4" 90 | is-shared-array-buffer "^1.0.2" 91 | isarray "^2.0.5" 92 | object-is "^1.1.5" 93 | object-keys "^1.1.1" 94 | object.assign "^4.1.4" 95 | regexp.prototype.flags "^1.4.3" 96 | side-channel "^1.0.4" 97 | which-boxed-primitive "^1.0.2" 98 | which-collection "^1.0.1" 99 | which-typed-array "^1.1.9" 100 | 101 | define-properties@^1.1.3, define-properties@^1.1.4: 102 | version "1.1.4" 103 | resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz" 104 | integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== 105 | dependencies: 106 | has-property-descriptors "^1.0.0" 107 | object-keys "^1.1.1" 108 | 109 | dfa@^1.2.0: 110 | version "1.2.0" 111 | resolved "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz" 112 | integrity sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q== 113 | 114 | es-get-iterator@^1.1.2: 115 | version "1.1.3" 116 | resolved "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz" 117 | integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== 118 | dependencies: 119 | call-bind "^1.0.2" 120 | get-intrinsic "^1.1.3" 121 | has-symbols "^1.0.3" 122 | is-arguments "^1.1.1" 123 | is-map "^2.0.2" 124 | is-set "^2.0.2" 125 | is-string "^1.0.7" 126 | isarray "^2.0.5" 127 | stop-iteration-iterator "^1.0.0" 128 | 129 | fontkit@^1.8.1: 130 | version "1.9.0" 131 | resolved "https://registry.npmjs.org/fontkit/-/fontkit-1.9.0.tgz" 132 | integrity sha512-HkW/8Lrk8jl18kzQHvAw9aTHe1cqsyx5sDnxncx652+CIfhawokEPkeM3BoIC+z/Xv7a0yMr0f3pRRwhGH455g== 133 | dependencies: 134 | "@swc/helpers" "^0.3.13" 135 | brotli "^1.3.2" 136 | clone "^2.1.2" 137 | deep-equal "^2.0.5" 138 | dfa "^1.2.0" 139 | restructure "^2.0.1" 140 | tiny-inflate "^1.0.3" 141 | unicode-properties "^1.3.1" 142 | unicode-trie "^2.0.0" 143 | 144 | for-each@^0.3.3: 145 | version "0.3.3" 146 | resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz" 147 | integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== 148 | dependencies: 149 | is-callable "^1.1.3" 150 | 151 | fs.realpath@^1.0.0: 152 | version "1.0.0" 153 | resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" 154 | integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== 155 | 156 | function-bind@^1.1.1: 157 | version "1.1.1" 158 | resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" 159 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 160 | 161 | functions-have-names@^1.2.2: 162 | version "1.2.3" 163 | resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz" 164 | integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== 165 | 166 | get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: 167 | version "1.1.3" 168 | resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz" 169 | integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== 170 | dependencies: 171 | function-bind "^1.1.1" 172 | has "^1.0.3" 173 | has-symbols "^1.0.3" 174 | 175 | glob@^7.1.3: 176 | version "7.2.3" 177 | resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" 178 | integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== 179 | dependencies: 180 | fs.realpath "^1.0.0" 181 | inflight "^1.0.4" 182 | inherits "2" 183 | minimatch "^3.1.1" 184 | once "^1.3.0" 185 | path-is-absolute "^1.0.0" 186 | 187 | gopd@^1.0.1: 188 | version "1.0.1" 189 | resolved "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz" 190 | integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== 191 | dependencies: 192 | get-intrinsic "^1.1.3" 193 | 194 | has-bigints@^1.0.1: 195 | version "1.0.2" 196 | resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz" 197 | integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== 198 | 199 | has-property-descriptors@^1.0.0: 200 | version "1.0.0" 201 | resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz" 202 | integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== 203 | dependencies: 204 | get-intrinsic "^1.1.1" 205 | 206 | has-symbols@^1.0.2, has-symbols@^1.0.3: 207 | version "1.0.3" 208 | resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" 209 | integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== 210 | 211 | has-tostringtag@^1.0.0: 212 | version "1.0.0" 213 | resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz" 214 | integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== 215 | dependencies: 216 | has-symbols "^1.0.2" 217 | 218 | has@^1.0.3: 219 | version "1.0.3" 220 | resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" 221 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 222 | dependencies: 223 | function-bind "^1.1.1" 224 | 225 | inflight@^1.0.4: 226 | version "1.0.6" 227 | resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" 228 | integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== 229 | dependencies: 230 | once "^1.3.0" 231 | wrappy "1" 232 | 233 | inherits@2: 234 | version "2.0.4" 235 | resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" 236 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 237 | 238 | internal-slot@^1.0.4: 239 | version "1.0.4" 240 | resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz" 241 | integrity sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ== 242 | dependencies: 243 | get-intrinsic "^1.1.3" 244 | has "^1.0.3" 245 | side-channel "^1.0.4" 246 | 247 | is-arguments@^1.1.1: 248 | version "1.1.1" 249 | resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz" 250 | integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== 251 | dependencies: 252 | call-bind "^1.0.2" 253 | has-tostringtag "^1.0.0" 254 | 255 | is-array-buffer@^3.0.1: 256 | version "3.0.1" 257 | resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz" 258 | integrity sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ== 259 | dependencies: 260 | call-bind "^1.0.2" 261 | get-intrinsic "^1.1.3" 262 | is-typed-array "^1.1.10" 263 | 264 | is-bigint@^1.0.1: 265 | version "1.0.4" 266 | resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz" 267 | integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== 268 | dependencies: 269 | has-bigints "^1.0.1" 270 | 271 | is-boolean-object@^1.1.0: 272 | version "1.1.2" 273 | resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz" 274 | integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== 275 | dependencies: 276 | call-bind "^1.0.2" 277 | has-tostringtag "^1.0.0" 278 | 279 | is-callable@^1.1.3: 280 | version "1.2.7" 281 | resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz" 282 | integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== 283 | 284 | is-date-object@^1.0.5: 285 | version "1.0.5" 286 | resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz" 287 | integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== 288 | dependencies: 289 | has-tostringtag "^1.0.0" 290 | 291 | is-map@^2.0.1, is-map@^2.0.2: 292 | version "2.0.2" 293 | resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz" 294 | integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== 295 | 296 | is-number-object@^1.0.4: 297 | version "1.0.7" 298 | resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz" 299 | integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== 300 | dependencies: 301 | has-tostringtag "^1.0.0" 302 | 303 | is-regex@^1.1.4: 304 | version "1.1.4" 305 | resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" 306 | integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== 307 | dependencies: 308 | call-bind "^1.0.2" 309 | has-tostringtag "^1.0.0" 310 | 311 | is-set@^2.0.1, is-set@^2.0.2: 312 | version "2.0.2" 313 | resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz" 314 | integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== 315 | 316 | is-shared-array-buffer@^1.0.2: 317 | version "1.0.2" 318 | resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz" 319 | integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== 320 | dependencies: 321 | call-bind "^1.0.2" 322 | 323 | is-string@^1.0.5, is-string@^1.0.7: 324 | version "1.0.7" 325 | resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz" 326 | integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== 327 | dependencies: 328 | has-tostringtag "^1.0.0" 329 | 330 | is-symbol@^1.0.3: 331 | version "1.0.4" 332 | resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz" 333 | integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== 334 | dependencies: 335 | has-symbols "^1.0.2" 336 | 337 | is-typed-array@^1.1.10: 338 | version "1.1.10" 339 | resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz" 340 | integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== 341 | dependencies: 342 | available-typed-arrays "^1.0.5" 343 | call-bind "^1.0.2" 344 | for-each "^0.3.3" 345 | gopd "^1.0.1" 346 | has-tostringtag "^1.0.0" 347 | 348 | is-weakmap@^2.0.1: 349 | version "2.0.1" 350 | resolved "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz" 351 | integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== 352 | 353 | is-weakset@^2.0.1: 354 | version "2.0.2" 355 | resolved "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz" 356 | integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg== 357 | dependencies: 358 | call-bind "^1.0.2" 359 | get-intrinsic "^1.1.1" 360 | 361 | isarray@^2.0.5: 362 | version "2.0.5" 363 | resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz" 364 | integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== 365 | 366 | linebreak@^1.0.2: 367 | version "1.1.0" 368 | resolved "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz" 369 | integrity sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ== 370 | dependencies: 371 | base64-js "0.0.8" 372 | unicode-trie "^2.0.0" 373 | 374 | minimatch@^3.1.1: 375 | version "3.1.2" 376 | resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" 377 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 378 | dependencies: 379 | brace-expansion "^1.1.7" 380 | 381 | minimist@^1.2.6: 382 | version "1.2.7" 383 | resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz" 384 | integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== 385 | 386 | mkdirp@^0.5.1: 387 | version "0.5.6" 388 | resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz" 389 | integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== 390 | dependencies: 391 | minimist "^1.2.6" 392 | 393 | node-addon-api@^4.2.0: 394 | version "4.3.0" 395 | resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz" 396 | integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== 397 | 398 | node-gyp-build@^4.3.0: 399 | version "4.6.0" 400 | resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz" 401 | integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== 402 | 403 | object-inspect@^1.9.0: 404 | version "1.12.3" 405 | resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz" 406 | integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== 407 | 408 | object-is@^1.1.5: 409 | version "1.1.5" 410 | resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz" 411 | integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== 412 | dependencies: 413 | call-bind "^1.0.2" 414 | define-properties "^1.1.3" 415 | 416 | object-keys@^1.1.1: 417 | version "1.1.1" 418 | resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" 419 | integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== 420 | 421 | object.assign@^4.1.4: 422 | version "4.1.4" 423 | resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz" 424 | integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== 425 | dependencies: 426 | call-bind "^1.0.2" 427 | define-properties "^1.1.4" 428 | has-symbols "^1.0.3" 429 | object-keys "^1.1.1" 430 | 431 | once@^1.3.0: 432 | version "1.4.0" 433 | resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" 434 | integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== 435 | dependencies: 436 | wrappy "1" 437 | 438 | pako@^0.2.5: 439 | version "0.2.9" 440 | resolved "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz" 441 | integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA== 442 | 443 | path-is-absolute@^1.0.0: 444 | version "1.0.1" 445 | resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" 446 | integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== 447 | 448 | pdfkit@^0.12.3: 449 | version "0.12.3" 450 | resolved "https://registry.npmjs.org/pdfkit/-/pdfkit-0.12.3.tgz" 451 | integrity sha512-+qDLgm2yq6WOKcxTb43lDeo3EtMIDQs0CK1RNqhHC9iT6u0KOmgwAClkYh9xFw2ATbmUZzt4f7KMwDCOfPDluA== 452 | dependencies: 453 | crypto-js "^4.0.0" 454 | fontkit "^1.8.1" 455 | linebreak "^1.0.2" 456 | png-js "^1.0.0" 457 | 458 | png-js@^1.0.0: 459 | version "1.0.0" 460 | resolved "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz" 461 | integrity sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g== 462 | 463 | regexp.prototype.flags@^1.4.3: 464 | version "1.4.3" 465 | resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz" 466 | integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== 467 | dependencies: 468 | call-bind "^1.0.2" 469 | define-properties "^1.1.3" 470 | functions-have-names "^1.2.2" 471 | 472 | restructure@^2.0.1: 473 | version "2.0.1" 474 | resolved "https://registry.npmjs.org/restructure/-/restructure-2.0.1.tgz" 475 | integrity sha512-e0dOpjm5DseomnXx2M5lpdZ5zoHqF1+bqdMJUohoYVVQa7cBdnk7fdmeI6byNWP/kiME72EeTiSypTCVnpLiDg== 476 | 477 | rimraf@~2.6.2: 478 | version "2.6.3" 479 | resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz" 480 | integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== 481 | dependencies: 482 | glob "^7.1.3" 483 | 484 | screenshot-desktop@^1.14.1: 485 | version "1.15.0" 486 | resolved "https://registry.yarnpkg.com/screenshot-desktop/-/screenshot-desktop-1.15.0.tgz#62109463865cdb1bbe61dff39003cd06ac404fb0" 487 | integrity sha512-CLaZNBDEXU+KJ6BGsO8jSbKI7Zck7gQmFJHjzluBdwrVP0jOemP2avpD3ufWu81yqzwB92u2AMv+K9IlaslRsg== 488 | dependencies: 489 | temp "^0.9.4" 490 | 491 | side-channel@^1.0.4: 492 | version "1.0.4" 493 | resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz" 494 | integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== 495 | dependencies: 496 | call-bind "^1.0.0" 497 | get-intrinsic "^1.0.2" 498 | object-inspect "^1.9.0" 499 | 500 | stop-iteration-iterator@^1.0.0: 501 | version "1.0.0" 502 | resolved "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz" 503 | integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== 504 | dependencies: 505 | internal-slot "^1.0.4" 506 | 507 | temp@^0.9.4: 508 | version "0.9.4" 509 | resolved "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz" 510 | integrity sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA== 511 | dependencies: 512 | mkdirp "^0.5.1" 513 | rimraf "~2.6.2" 514 | 515 | tiny-inflate@^1.0.0, tiny-inflate@^1.0.3: 516 | version "1.0.3" 517 | resolved "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz" 518 | integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== 519 | 520 | tslib@^2.4.0: 521 | version "2.4.1" 522 | resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz" 523 | integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== 524 | 525 | unicode-properties@^1.3.1: 526 | version "1.4.1" 527 | resolved "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz" 528 | integrity sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg== 529 | dependencies: 530 | base64-js "^1.3.0" 531 | unicode-trie "^2.0.0" 532 | 533 | unicode-trie@^2.0.0: 534 | version "2.0.0" 535 | resolved "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz" 536 | integrity sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ== 537 | dependencies: 538 | pako "^0.2.5" 539 | tiny-inflate "^1.0.0" 540 | 541 | which-boxed-primitive@^1.0.2: 542 | version "1.0.2" 543 | resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz" 544 | integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== 545 | dependencies: 546 | is-bigint "^1.0.1" 547 | is-boolean-object "^1.1.0" 548 | is-number-object "^1.0.4" 549 | is-string "^1.0.5" 550 | is-symbol "^1.0.3" 551 | 552 | which-collection@^1.0.1: 553 | version "1.0.1" 554 | resolved "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz" 555 | integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== 556 | dependencies: 557 | is-map "^2.0.1" 558 | is-set "^2.0.1" 559 | is-weakmap "^2.0.1" 560 | is-weakset "^2.0.1" 561 | 562 | which-typed-array@^1.1.9: 563 | version "1.1.9" 564 | resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz" 565 | integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== 566 | dependencies: 567 | available-typed-arrays "^1.0.5" 568 | call-bind "^1.0.2" 569 | for-each "^0.3.3" 570 | gopd "^1.0.1" 571 | has-tostringtag "^1.0.0" 572 | is-typed-array "^1.1.10" 573 | 574 | wrappy@1: 575 | version "1.0.2" 576 | resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" 577 | integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== 578 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | 676 | --------------------------------------------------------------------------------