feat(pdf-editor): read, mark up and reshape a PDF without leaving the browser
A PDF that needs a signature, a highlight or a page removed currently sends the user out to a desktop application or, worse, to a web service that wants the document uploaded first. Both are poor answers for a browser whose point is that nothing has to leave the machine. This is a full-tab editor that opens a PDF, marks it up, fills its forms and saves a new copy, entirely locally. Two engines, vendored rather than installed, because an add-on ships as a self-contained folder over the signed update channel and nothing runs a package manager on the way: pdf.js reads and renders, pdf-lib writes. They share no state. Everything in between lives in PDF user space — points, origin bottom-left — which is the one coordinate vocabulary both speak, so a mark survives zooming, rotating and reordering with no conversion table and save-time needs to know nothing about how a page happened to be displayed. The page strip is built from pdf.js's PDFPageView components rather than its PDFViewer, which renders pages in the file's own order and cannot hide, reorder or individually rotate one — three of the features here. Text layers are ours and stay attached for every page, drawn or not, because Theseus's find bar is Chromium's findInPage over the live DOM and a torn-down text layer is a page Ctrl+F cannot see. Canvases are virtualised; a letter page at 100% is 3.4 MB of bitmap. Redaction is the part worth being careful about. A black box over text hides nothing — the text stays in the content stream and comes straight out of a copy-paste — so the editor says so in a modal before the tool can be used, and on save rebuilds each redacted page as an image, which genuinely removes it. Pages that were not redacted are untouched. Form widgets and links are kept, since they were never the leak. Saving never writes over the original: every save reloads the source bytes and replays the session onto a fresh copy, so a botched save cannot poison the next one. Out of scope for this first version: editing the text that is already in the document, and writing XFA forms back (pdf-lib cannot, so those are fill-and- print only, and the editor says so on open).
62
bundled-addons/pdf-editor/VENDOR.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# Vendored libraries
|
||||
|
||||
Both engines are copied in rather than installed, because Theseus's own
|
||||
`node_modules` is a build-time tree and an add-on has to be a self-contained
|
||||
folder: it is copied to `<userData>/addons/pdf-editor/` on first run and
|
||||
shipped as a tarball over the signed update channel, neither of which runs a
|
||||
package manager.
|
||||
|
||||
| Path | Library | Version | Licence |
|
||||
|---|---|---|---|
|
||||
| `vendor/pdfjs/` | [pdf.js](https://mozilla.github.io/pdf.js/) (`pdfjs-dist`) | 6.3.289 | Apache-2.0 |
|
||||
| `vendor/pdf-lib/` | [pdf-lib](https://pdf-lib.js.org/) | 1.17.1 | MIT |
|
||||
|
||||
Licence texts ship beside the code: `vendor/pdfjs/LICENSE`,
|
||||
`vendor/pdfjs/wasm/LICENSE_*` and `vendor/pdf-lib/LICENSE.md`.
|
||||
|
||||
pdf.js reads; pdf-lib writes. They never look at each other's state — the
|
||||
editor holds the document model in between, in PDF user-space coordinates,
|
||||
which is the only vocabulary both of them speak.
|
||||
|
||||
## What was taken, and what was left behind
|
||||
|
||||
From `pdfjs-dist`:
|
||||
|
||||
- `build/pdf.min.mjs`, `build/pdf.worker.min.mjs` — the API and its worker.
|
||||
- `web/pdf_viewer.mjs` + `web/pdf_viewer.css` + `web/images/` — the viewer
|
||||
*components*, for `PDFPageView`, `PDFLinkService` and `EventBus`. Not the
|
||||
full viewer application. There is no minified build of this file upstream.
|
||||
- `standard_fonts/` — the base-14 metrics. A PDF that names Helvetica without
|
||||
embedding it is extremely common, and without these it renders wrong.
|
||||
- `wasm/*.wasm` — JPEG 2000, JBIG2 and colour management.
|
||||
- `iccs/` — ICC profiles for the colour management above.
|
||||
|
||||
Left out on purpose:
|
||||
|
||||
- `cmaps/` (~1.5 MB) — needed only for CJK documents that use a non-embedded
|
||||
CID font. Dropping it keeps the add-on at about 4 MB. If CJK support is
|
||||
wanted, copy `cmaps/` in and pass `cMapUrl: "vendor/pdfjs/cmaps/"` to
|
||||
`getDocument` in `editor.js`; nothing else changes.
|
||||
- `wasm/*_nowasm_fallback.js` (~580 KB) — pure-JS fallbacks for engines
|
||||
without WebAssembly. Electron always has it.
|
||||
- `wasm/quickjs-eval.*` and `build/pdf.sandbox.*` (~525 KB) — the interpreter
|
||||
for JavaScript embedded in a PDF. The editor loads documents with
|
||||
`enableScripting: false`, so a document's own scripts never run.
|
||||
- Every `.map` file, the TypeScript definitions, the CommonJS and `legacy`
|
||||
builds, and `image_decoders/`.
|
||||
|
||||
From `pdf-lib`, only `dist/pdf-lib.esm.min.js`.
|
||||
|
||||
## Upgrading
|
||||
|
||||
Re-run the extraction against a fresh tarball, keeping the list above, then
|
||||
run the verification suite in `scratchpad/verify-pdf-editor/`. Two things to
|
||||
watch, because both have bitten this add-on already:
|
||||
|
||||
- `web/pdf_viewer.mjs` reads the core API off `globalThis.pdfjsLib` instead of
|
||||
importing it, so `editor.js` must keep assigning that global *before* it
|
||||
imports the components bundle.
|
||||
- pdf.js's `AnnotationStorage` has no stable public shape. `getAll()`, which
|
||||
older releases had, does not exist in 6.x — form values are read through
|
||||
`has(id)` and `getRawValue(id)` against the ids from `getFieldObjects()`.
|
||||
A silent change here looks like a form that saves nothing.
|
||||
26
bundled-addons/pdf-editor/addon.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"id": "pdf-editor",
|
||||
"name": "PDF Editor",
|
||||
"version": "0.1.0",
|
||||
"description": "Open a PDF in a full tab: read it, highlight and mark it up, reorder or rotate or drop pages, fill in its form fields, then save a new copy. Annotations are written into the saved file as real PDF content. The original is never touched.",
|
||||
"author": "Silent Mode",
|
||||
"icon": "📄",
|
||||
"main": "index.js",
|
||||
"capabilities": ["toolbar-menu", "open-tab", "context-menu-item"],
|
||||
"toolbar-menu": {
|
||||
"title": "PDF Editor",
|
||||
"icon": "📄",
|
||||
"items": [
|
||||
{ "id": "open", "label": "Open a PDF…", "icon": "📄" }
|
||||
]
|
||||
},
|
||||
"context-menu-items": [
|
||||
{
|
||||
"id": "open-link",
|
||||
"label": "Open PDF in editor",
|
||||
"when": "linkURL",
|
||||
"icon": "📄"
|
||||
}
|
||||
],
|
||||
"updateURL": "https://navigate.st/bns/theseus.x/extensions/pdf-editor/updates.json"
|
||||
}
|
||||
227
bundled-addons/pdf-editor/editor.css
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
/* PDF Editor — full-tab. Same palette and control shapes as the screenshot
|
||||
editor so the two add-ons read as one family; loaded AFTER pdf.js's
|
||||
pdf_viewer.css so the overrides at the bottom win. */
|
||||
:root { color-scheme: light dark;
|
||||
--bg:#0e131c; --panel:#141a24; --panel2:#191f2b; --line:rgba(255,255,255,.09);
|
||||
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
|
||||
--danger:#ff5b5b; --board:#0a0d13; --shadow:rgba(0,0,0,.45); }
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root { --bg:#f8faff; --panel:#ffffff; --panel2:#eff3fb; --line:rgba(0,0,0,.10);
|
||||
--ink:#1a1f2b; --mut:#5c6577; --dim:#8a93a5; --acid:#0AC18E;
|
||||
--board:#dde3ee; --shadow:rgba(20,30,50,.20); }
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; height: 100%; }
|
||||
body { background: var(--bg); color: var(--ink);
|
||||
font: 13px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
||||
display: flex; flex-direction: column; overflow: hidden; }
|
||||
/* pdf.js's stylesheet sets display on some of the nodes we toggle, so plain
|
||||
[hidden] loses the cascade fight. */
|
||||
[hidden] { display: none !important; }
|
||||
|
||||
/* ---- bars ----------------------------------------------------------- */
|
||||
.topbar, .toolbar { display: flex; align-items: center; gap: 4px; padding: 6px 8px;
|
||||
border-bottom: 1px solid var(--line); background: var(--panel);
|
||||
user-select: none; }
|
||||
.topbar { flex-wrap: nowrap; }
|
||||
.toolbar { align-items: flex-start; gap: 8px; row-gap: 6px; flex-wrap: wrap; }
|
||||
.tool-cluster { display: flex; flex-wrap: wrap; align-items: center;
|
||||
gap: 10px; row-gap: 6px; flex: 1 1 auto; justify-content: center; }
|
||||
.tgroup { display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto; }
|
||||
.tgroup.zoomgroup { align-self: center; }
|
||||
.spacer { flex: 1; }
|
||||
.sep { width: 1px; height: 20px; background: var(--line); margin: 0 4px; flex: 0 0 auto; }
|
||||
|
||||
.docname { color: var(--mut); font-size: 12.5px; overflow: hidden; min-width: 0;
|
||||
text-overflow: ellipsis; white-space: nowrap; max-width: 46vw; }
|
||||
.docname.dirty::after { content: " •"; color: var(--acid); font-weight: 700; }
|
||||
|
||||
.btn, .tool {
|
||||
border: 1px solid var(--line); background: var(--panel2); color: var(--ink);
|
||||
width: 30px; height: 30px; border-radius: 6px; cursor: pointer;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
padding: 0; transition: background 100ms, border-color 100ms;
|
||||
font: inherit; line-height: 0; flex: 0 0 auto;
|
||||
}
|
||||
.btn:hover, .tool:hover { border-color: rgb(from var(--acid) r g b / .5); }
|
||||
.btn:disabled, .tool:disabled { opacity: .4; cursor: default; }
|
||||
.btn:disabled:hover { border-color: var(--line); }
|
||||
.btn svg, .tool svg { width: 16px; height: 16px; display: block; }
|
||||
.btn.wide { width: auto; padding: 0 10px; gap: 6px; line-height: 1; }
|
||||
.btn.wide span { font-size: 12px; }
|
||||
.btn.primary { background: var(--acid); color: #101418; border-color: transparent; font-weight: 600; }
|
||||
.btn.primary:hover { filter: brightness(1.06); border-color: transparent; }
|
||||
.btn.primary:disabled:hover { filter: none; }
|
||||
.btn.danger { color: var(--danger); }
|
||||
.btn.danger:hover { border-color: rgba(255,91,91,.55); }
|
||||
.tool.active { background: rgb(from var(--acid) r g b / .15);
|
||||
border-color: rgb(from var(--acid) r g b / .7); color: var(--acid); }
|
||||
/* Redact is destructive; it reads red when armed rather than acid. */
|
||||
.tool[data-tool="redact"].active { background: rgba(255,91,91,.16);
|
||||
border-color: rgba(255,91,91,.7); color: var(--danger); }
|
||||
|
||||
.swatch { width: 22px; height: 22px; border-radius: 50%; padding: 0;
|
||||
border: 2px solid var(--line); cursor: pointer; flex: 0 0 auto;
|
||||
/* The near-black ink swatch would otherwise disappear into a dark
|
||||
toolbar; a faint inner ring keeps every colour countable. */
|
||||
box-shadow: inset 0 0 0 1px rgb(from var(--ink) r g b / .28); }
|
||||
.swatch.active { border-color: var(--acid);
|
||||
outline: 1px solid rgb(from var(--acid) r g b / .35); outline-offset: 1px; }
|
||||
.width { border: 1px solid var(--line); background: var(--panel2); color: var(--ink);
|
||||
width: 30px; height: 30px; border-radius: 6px; cursor: pointer;
|
||||
display: inline-flex; align-items: center; justify-content: center; padding: 0; }
|
||||
.width.active { border-color: rgb(from var(--acid) r g b / .7);
|
||||
background: rgb(from var(--acid) r g b / .15); }
|
||||
.width .dot { border-radius: 50%; background: currentColor; color: var(--ink); }
|
||||
.width.active .dot { color: var(--acid); }
|
||||
|
||||
select, input[type="text"], textarea {
|
||||
border: 1px solid var(--line); background: var(--panel2); color: var(--ink);
|
||||
border-radius: 6px; padding: 5px 7px; font: inherit; height: 30px;
|
||||
}
|
||||
textarea { height: auto; resize: vertical; width: 100%; line-height: 1.45; }
|
||||
select:focus, input:focus, textarea:focus { outline: none; border-color: rgb(from var(--acid) r g b / .6); }
|
||||
|
||||
/* ---- body: viewer + rail -------------------------------------------- */
|
||||
.body { flex: 1; display: flex; min-height: 0; }
|
||||
.viewerContainer { position: absolute; inset: 0; overflow: auto; background: var(--board); }
|
||||
.body { position: relative; }
|
||||
.viewerContainer { position: relative; flex: 1 1 auto; min-width: 0; }
|
||||
|
||||
.rail { flex: 0 0 176px; border-left: 1px solid var(--line); background: var(--panel);
|
||||
display: flex; flex-direction: column; min-height: 0; }
|
||||
body[data-rail="off"] .rail { display: none; }
|
||||
.rail-head { display: flex; align-items: center; gap: 6px; padding: 7px 10px;
|
||||
border-bottom: 1px solid var(--line); color: var(--dim); font-size: 11.5px; }
|
||||
.rail-list { flex: 1; overflow: auto; padding: 8px; display: flex;
|
||||
flex-direction: column; gap: 8px; }
|
||||
|
||||
.thumb { position: relative; border: 1px solid var(--line); border-radius: 6px;
|
||||
background: var(--panel2); padding: 6px; cursor: grab; }
|
||||
.thumb.current { border-color: rgb(from var(--acid) r g b / .75); }
|
||||
.thumb.dragging { opacity: .45; }
|
||||
.thumb.dropbefore { box-shadow: 0 -3px 0 0 var(--acid); }
|
||||
.thumb.dropafter { box-shadow: 0 3px 0 0 var(--acid); }
|
||||
.thumb.deleted { opacity: .38; }
|
||||
.thumb.deleted .tcanvas { filter: grayscale(1); }
|
||||
.thumb .tcanvas { display: block; width: 100%; height: auto; background: #fff;
|
||||
border-radius: 3px; box-shadow: 0 1px 4px var(--shadow); }
|
||||
.thumb .tfoot { display: flex; align-items: center; gap: 3px; margin-top: 5px; }
|
||||
.thumb .tnum { color: var(--dim); font-size: 11px; font-variant-numeric: tabular-nums;
|
||||
flex: 1 1 auto; min-width: 0; }
|
||||
.thumb .tbtn { width: 20px; height: 20px; border-radius: 4px; border: 1px solid var(--line);
|
||||
background: var(--panel); color: var(--ink); cursor: pointer; padding: 0;
|
||||
display: inline-flex; align-items: center; justify-content: center; }
|
||||
.thumb .tbtn svg { width: 12px; height: 12px; display: block; }
|
||||
.thumb .tbtn:hover { border-color: rgb(from var(--acid) r g b / .55); }
|
||||
.thumb .tbtn.del:hover { border-color: rgba(255,91,91,.6); color: var(--danger); }
|
||||
|
||||
.fbtn { border: 1px solid var(--line); background: var(--panel2); color: var(--ink);
|
||||
border-radius: 6px; cursor: pointer; padding: 3px 7px; font: inherit;
|
||||
font-size: 11px; line-height: 1.4; }
|
||||
.fbtn:hover { border-color: rgb(from var(--acid) r g b / .55); }
|
||||
|
||||
/* ---- footer --------------------------------------------------------- */
|
||||
.footer { display: flex; align-items: center; gap: 8px; padding: 5px 10px;
|
||||
border-top: 1px solid var(--line); background: var(--panel); font-size: 11.5px; }
|
||||
.fstat { color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.fstat.err { color: var(--danger); }
|
||||
|
||||
/* ---- drop zone ------------------------------------------------------ */
|
||||
.dropzone { position: absolute; inset: 0; display: none; align-items: center;
|
||||
justify-content: center; pointer-events: none; z-index: 20; }
|
||||
body[data-empty="1"] .dropzone { display: flex; pointer-events: auto; }
|
||||
body[data-dragover="1"] .dropzone { display: flex; pointer-events: none;
|
||||
background: rgb(from var(--acid) r g b / .08); }
|
||||
.dz-inner { text-align: center; color: var(--dim); border: 2px dashed var(--line);
|
||||
border-radius: 14px; padding: 40px 54px; background: var(--panel);
|
||||
max-width: 82%; }
|
||||
body[data-dragover="1"] .dz-inner { border-color: rgb(from var(--acid) r g b / .8); color: var(--ink); }
|
||||
.dz-inner svg { width: 48px; height: 48px; margin-bottom: 10px; }
|
||||
.dz-title { font-size: 15px; color: var(--ink); font-weight: 600; }
|
||||
.dz-sub { margin-top: 5px; }
|
||||
.linkish { background: none; border: none; padding: 0; font: inherit; cursor: pointer;
|
||||
color: var(--acid); text-decoration: underline; }
|
||||
|
||||
/* ---- modals --------------------------------------------------------- */
|
||||
.scrim { position: fixed; inset: 0; background: rgba(4,7,12,.62); z-index: 50;
|
||||
display: flex; align-items: center; justify-content: center; padding: 24px; }
|
||||
.modal { background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
|
||||
box-shadow: 0 18px 60px rgba(0,0,0,.5); padding: 18px 20px; max-width: 560px;
|
||||
width: 100%; max-height: 90vh; overflow: auto; }
|
||||
.modal h2 { margin: 0 0 10px; font-size: 15px; }
|
||||
.modal p { margin: 0 0 10px; color: var(--mut); }
|
||||
.modal p strong { color: var(--ink); }
|
||||
.mhint { font-size: 12px; }
|
||||
.mhint.warn { color: var(--danger); }
|
||||
.mrow { display: flex; align-items: center; gap: 8px; margin-top: 14px; }
|
||||
.mfield { display: inline-flex; align-items: center; gap: 6px; color: var(--mut); font-size: 12px; }
|
||||
.mfield.block { display: flex; flex-direction: column; align-items: stretch; gap: 5px; }
|
||||
.check { display: flex; align-items: flex-start; gap: 8px; margin: 12px 0 0;
|
||||
color: var(--ink); font-size: 12.5px; cursor: pointer; }
|
||||
.check input { margin: 2px 0 0; accent-color: var(--acid); }
|
||||
#sign-pad { width: 100%; height: auto; aspect-ratio: 640 / 220; background: #fff;
|
||||
border: 1px solid var(--line); border-radius: 8px; cursor: crosshair;
|
||||
touch-action: none; display: block; }
|
||||
.summary { margin: 10px 0 0; padding-left: 18px; color: var(--mut); font-size: 12px; }
|
||||
.summary li { margin: 2px 0; }
|
||||
.summary li.warn { color: var(--danger); }
|
||||
|
||||
.toast { position: fixed; left: 50%; bottom: 44px; transform: translateX(-50%) translateY(20px);
|
||||
padding: 8px 14px; border-radius: 6px; background: rgba(11,14,20,.95);
|
||||
color: #e7eaf1; font-size: 12.5px; border: 1px solid var(--line);
|
||||
opacity: 0; pointer-events: none; transition: opacity 180ms, transform 180ms;
|
||||
z-index: 100; max-width: 80vw; text-align: center; }
|
||||
.toast.on { opacity: 1; transform: translateX(-50%) translateY(0); }
|
||||
.toast.err { border-color: rgba(255,91,91,.6); color: #ffb3b3; }
|
||||
|
||||
/* ---- pdf.js layer overrides ----------------------------------------- */
|
||||
/* A real gap between pages plus a drop shadow, instead of pdf.js's
|
||||
transparent-border trick. */
|
||||
/* overflow-anchor off: pages change height as they take their real size and
|
||||
again on every zoom, and scroll anchoring "helpfully" moves the scroll
|
||||
position to compensate — which walks the reader away from the page they
|
||||
were on and, worse, can park the whole document outside the viewport. */
|
||||
.viewerContainer { overflow-anchor: none; }
|
||||
.pdfViewer { --page-margin: 0 auto 14px; --page-border: none; padding: 14px 0 4px; }
|
||||
.pdfViewer .page { box-shadow: 0 2px 14px var(--shadow); }
|
||||
|
||||
/* Our overlay sits above the canvas but below the annotation (form) layer, so
|
||||
a form field stays clickable even with marks drawn around it. */
|
||||
/* Clipped to the page, because the saved file is too: a PDF viewer shows
|
||||
nothing outside the crop box, so a mark that spilled past the edge on
|
||||
screen would simply vanish on save. */
|
||||
.pdfViewer .page .smOverlay { position: absolute; inset: 0; z-index: 3;
|
||||
pointer-events: none; overflow: hidden; }
|
||||
.pdfViewer .page .annotationLayer { z-index: 4; }
|
||||
.pdfViewer .page .textLayer { z-index: 2; }
|
||||
|
||||
/* Tool modality. When a drawing tool is armed the text and form layers stop
|
||||
taking pointer events, so a drag paints instead of selecting text or
|
||||
landing in a field. Highlight is the exception: it works BY selecting
|
||||
text, so the text layer stays live for it. */
|
||||
body[data-mode="draw"] .textLayer,
|
||||
body[data-mode="draw"] .annotationLayer { pointer-events: none !important; }
|
||||
body[data-mode="draw"] .pdfViewer .page { cursor: crosshair; }
|
||||
body[data-mode="stamp"] .textLayer,
|
||||
body[data-mode="stamp"] .annotationLayer { pointer-events: none !important; }
|
||||
body[data-mode="stamp"] .pdfViewer .page { cursor: copy; }
|
||||
body[data-mode="highlight"] .annotationLayer { pointer-events: none !important; }
|
||||
body[data-mode="highlight"] .pdfViewer .page { cursor: text; }
|
||||
|
||||
/* Marks are only hit-testable with the select tool, so a drawing drag is
|
||||
never stolen by something already on the page. */
|
||||
body[data-mode="select"] .smOverlay { pointer-events: none; }
|
||||
body[data-mode="select"] .smOverlay .mark { pointer-events: stroke; cursor: move; }
|
||||
body[data-mode="select"] .smOverlay .mark[data-fill="1"] { pointer-events: all; }
|
||||
.smOverlay .mark.sel { outline: none; }
|
||||
.smOverlay .selbox { fill: none; stroke: var(--acid); stroke-width: 1;
|
||||
stroke-dasharray: 4 3; vector-effect: non-scaling-stroke; }
|
||||
.smOverlay .handle { fill: var(--acid); stroke: #101418; stroke-width: 1;
|
||||
vector-effect: non-scaling-stroke; cursor: pointer; pointer-events: all; }
|
||||
|
||||
/* pdf.js renders form widgets with a coloured backdrop; keep it, it is the
|
||||
only cue that a field is fillable. */
|
||||
.annotationLayer .textWidgetAnnotation input,
|
||||
.annotationLayer .textWidgetAnnotation textarea,
|
||||
.annotationLayer .choiceWidgetAnnotation select { font: inherit; }
|
||||
216
bundled-addons/pdf-editor/editor.html
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>PDF Editor</title>
|
||||
<link rel="stylesheet" href="vendor/pdfjs/web/pdf_viewer.css">
|
||||
<link rel="stylesheet" href="editor.css">
|
||||
</head>
|
||||
<body data-empty="1">
|
||||
|
||||
<!-- Top bar: document identity on the left, destructive/close actions right.
|
||||
Same split as the screenshot editor's topbar. -->
|
||||
<div class="topbar">
|
||||
<button class="btn wide" id="open" title="Open a PDF (Ctrl+O)">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 5h4l1.5 2H14v6H2z"/><path d="M2 5V3h5"/></svg>
|
||||
<span>Open</span>
|
||||
</button>
|
||||
<span class="sep"></span>
|
||||
<span class="docname" id="docname">No document</span>
|
||||
<span class="spacer"></span>
|
||||
<button class="btn wide primary" id="save" title="Save a copy (Ctrl+S)" disabled>
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v9M4 7l4 4 4-4M2 14h12"/></svg>
|
||||
<span>Save a copy</span>
|
||||
</button>
|
||||
<button class="btn danger" id="discard" title="Close this tab without saving (Esc)">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M4 4l8 8M12 4l-8 8"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Tool bar. `.tool-cluster` centres the drawing groups so the view
|
||||
controls can right-anchor without the tools crowding the left edge. -->
|
||||
<div class="toolbar" id="toolbar">
|
||||
<div class="tool-cluster">
|
||||
<div class="tgroup" role="group" aria-label="Tools">
|
||||
<button class="tool active" data-tool="select" title="Select — move or delete a mark, select text (V)">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 2l4 11 1.6-4.4L13 7z"/></svg>
|
||||
</button>
|
||||
<button class="tool" data-tool="highlight" title="Highlight — drag across text (H)">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12h10"/><path d="M5 9.5l4.5-6.5 3 2L8 11.5z"/></svg>
|
||||
</button>
|
||||
<button class="tool" data-tool="pen" title="Pen — freehand (P)">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 14c1-3 3-6 6-8s5-3 6-3l-3 4c-2 1-4 3-6 4s-2 2-3 3z"/></svg>
|
||||
</button>
|
||||
<button class="tool" data-tool="rect" title="Rectangle (R)">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2.5" y="3.5" width="11" height="9"/></svg>
|
||||
</button>
|
||||
<button class="tool" data-tool="ellipse" title="Ellipse (O)">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><ellipse cx="8" cy="8" rx="5.5" ry="4"/></svg>
|
||||
</button>
|
||||
<button class="tool" data-tool="arrow" title="Arrow (A)">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 13L13 3M13 3H8M13 3v5"/></svg>
|
||||
</button>
|
||||
<button class="tool" data-tool="text" title="Text stamp (T)">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M3 3h10M8 3v10M6 13h4"/></svg>
|
||||
</button>
|
||||
<button class="tool" data-tool="signature" title="Signature stamp (G)">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M2 11c2 0 3-7 4.5-7S8 10 9.5 10 12 6 13.5 6"/><path d="M2 14h12"/></svg>
|
||||
</button>
|
||||
<button class="tool" data-tool="redact" title="Redact — drag a box over content to remove it" data-warn="1">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2.5" y="4.5" width="11" height="7" fill="currentColor"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="tgroup" role="group" aria-label="Colours">
|
||||
<button class="swatch active" data-color="#ffd400" style="background:#ffd400" title="Yellow"></button>
|
||||
<button class="swatch" data-color="#ff3b30" style="background:#ff3b30" title="Red"></button>
|
||||
<button class="swatch" data-color="#0aa8ff" style="background:#0aa8ff" title="Blue"></button>
|
||||
<button class="swatch" data-color="#2bd97c" style="background:#2bd97c" title="Green"></button>
|
||||
<button class="swatch" data-color="#1a1f2b" style="background:#1a1f2b" title="Ink"></button>
|
||||
</div>
|
||||
|
||||
<div class="tgroup" role="group" aria-label="Widths">
|
||||
<button class="width" data-width="1.5" title="Thin"><span class="dot" style="width:4px;height:4px"></span></button>
|
||||
<button class="width active" data-width="2.5" title="Medium"><span class="dot" style="width:7px;height:7px"></span></button>
|
||||
<button class="width" data-width="4.5" title="Thick"><span class="dot" style="width:11px;height:11px"></span></button>
|
||||
</div>
|
||||
|
||||
<div class="tgroup" role="group" aria-label="History">
|
||||
<button class="btn" id="undo" title="Undo (Ctrl+Z)" disabled>
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M3 8c0-3 2-5 5-5s5 2 5 5-2 5-5 5"/><path d="M6 5L3 8l3 3"/></svg>
|
||||
</button>
|
||||
<button class="btn" id="redo" title="Redo (Ctrl+Shift+Z)" disabled>
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M13 8c0-3-2-5-5-5S3 5 3 8s2 5 5 5"/><path d="M10 5l3 3-3 3"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tgroup zoomgroup" role="group" aria-label="Zoom">
|
||||
<button class="btn" id="zoom-out" title="Zoom out (−)">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M3 8h10"/></svg>
|
||||
</button>
|
||||
<select id="zoom" title="Zoom">
|
||||
<option value="page-width">Fit width</option>
|
||||
<option value="page-fit">Fit page</option>
|
||||
<option value="0.5">50%</option>
|
||||
<option value="0.75">75%</option>
|
||||
<option value="1">100%</option>
|
||||
<option value="1.25">125%</option>
|
||||
<option value="1.5">150%</option>
|
||||
<option value="2">200%</option>
|
||||
<option value="4">400%</option>
|
||||
</select>
|
||||
<button class="btn" id="zoom-in" title="Zoom in (+)">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M8 3v10M3 8h10"/></svg>
|
||||
</button>
|
||||
<button class="btn" id="toggle-rail" title="Show or hide the page thumbnails">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="2" width="7" height="12"/><rect x="11" y="2" width="3" height="5"/><rect x="11" y="9" width="3" height="5"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="body">
|
||||
<!-- Scroll container. pdf.js's own CSS keys off #viewer .page, and reads
|
||||
--scale-factor from the container, so the structure below mirrors what
|
||||
the pdf.js viewer builds. -->
|
||||
<div class="viewerContainer" id="viewerContainer">
|
||||
<div id="viewer" class="pdfViewer"></div>
|
||||
|
||||
<div class="dropzone" id="dropzone">
|
||||
<div class="dz-inner">
|
||||
<svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 6h16l8 8v28H12z"/><path d="M28 6v8h8"/><path d="M24 22v12M19 29l5 5 5-5"/>
|
||||
</svg>
|
||||
<div class="dz-title">Drop a PDF here</div>
|
||||
<div class="dz-sub">or <button class="linkish" id="open2">choose a file</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Page rail. Thumbnails are draggable to reorder; each carries rotate
|
||||
and delete buttons. -->
|
||||
<aside class="rail" id="rail">
|
||||
<div class="rail-head">
|
||||
<span id="rail-count">0 pages</span>
|
||||
<button class="fbtn" id="restore-pages" hidden title="Bring back every deleted page">Undo deletes</button>
|
||||
</div>
|
||||
<div class="rail-list" id="rail-list"></div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<span class="fstat" id="status">Ready</span>
|
||||
<span class="spacer"></span>
|
||||
<span class="fstat" id="pageinfo"></span>
|
||||
</div>
|
||||
|
||||
<!-- Modal shell, reused by the redaction warning, the signature pad and the
|
||||
text-stamp prompt. One at a time; `data-modal` names the open one. -->
|
||||
<div class="scrim" id="scrim" hidden>
|
||||
<div class="modal" id="modal-redact" hidden>
|
||||
<h2>About redaction</h2>
|
||||
<p>A redaction box hides content visually. On its own it does <strong>not</strong> remove the
|
||||
text underneath, which stays extractable by anyone who copies from the file.</p>
|
||||
<p>To actually remove it, this editor rebuilds each redacted page as an image when you save.
|
||||
That page's text stops being selectable and any form fields on it become static. Pages you
|
||||
did not redact are untouched.</p>
|
||||
<label class="check"><input type="checkbox" id="redact-flatten" checked>
|
||||
<span>Rebuild redacted pages as images when saving (recommended)</span></label>
|
||||
<div class="mrow">
|
||||
<button class="btn wide" id="redact-cancel">Cancel</button>
|
||||
<button class="btn wide primary" id="redact-ok">Use the redaction tool</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="modal-sign" hidden>
|
||||
<h2>Draw your signature</h2>
|
||||
<p class="mhint">Draw in the box, then place it on the page. It is stored as lines, not a
|
||||
picture, so it stays sharp at any zoom.</p>
|
||||
<canvas id="sign-pad" width="640" height="220"></canvas>
|
||||
<div class="mrow">
|
||||
<button class="btn wide" id="sign-clear">Clear</button>
|
||||
<span class="spacer"></span>
|
||||
<button class="btn wide" id="sign-cancel">Cancel</button>
|
||||
<button class="btn wide primary" id="sign-ok" disabled>Use this signature</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="modal-text" hidden>
|
||||
<h2>Text stamp</h2>
|
||||
<textarea id="text-body" rows="4" placeholder="Type the text to place…"></textarea>
|
||||
<div class="mrow">
|
||||
<label class="mfield">Size
|
||||
<select id="text-size">
|
||||
<option value="8">8</option><option value="10">10</option>
|
||||
<option value="12" selected>12</option><option value="14">14</option>
|
||||
<option value="18">18</option><option value="24">24</option><option value="36">36</option>
|
||||
</select>
|
||||
</label>
|
||||
<span class="spacer"></span>
|
||||
<button class="btn wide" id="text-cancel">Cancel</button>
|
||||
<button class="btn wide primary" id="text-ok">Place</button>
|
||||
</div>
|
||||
<p class="mhint" id="text-warn" hidden></p>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="modal-save" hidden>
|
||||
<h2>Save a copy</h2>
|
||||
<label class="mfield block">File name
|
||||
<input type="text" id="save-name" spellcheck="false">
|
||||
</label>
|
||||
<ul class="summary" id="save-summary"></ul>
|
||||
<div class="mrow">
|
||||
<span class="spacer"></span>
|
||||
<button class="btn wide" id="save-cancel">Cancel</button>
|
||||
<button class="btn wide primary" id="save-go">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
<input type="file" id="file-input" accept="application/pdf,.pdf" hidden>
|
||||
<a id="download-link" style="display:none"></a>
|
||||
|
||||
<script type="module" src="editor.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
707
bundled-addons/pdf-editor/editor.js
Normal file
|
|
@ -0,0 +1,707 @@
|
|||
// PDF Editor — the page. A full Theseus tab, opened through the "open-tab"
|
||||
// capability, talking to the add-on's Node half over window.silentmode.
|
||||
//
|
||||
// Boot order matters: pdf.js's viewer components are published as a bundle
|
||||
// that reads its API off globalThis.pdfjsLib rather than importing it, so the
|
||||
// core has to be imported and parked on the global BEFORE the components
|
||||
// bundle is pulled in.
|
||||
//
|
||||
// Find is deliberately absent from this file. Theseus captures Ctrl+F at the
|
||||
// app level (main.js's before-input-event handler) and drives Chromium's own
|
||||
// findInPage over the tab — so the browser's find bar already searches this
|
||||
// document, with the count and next/previous the user knows from every other
|
||||
// page. A second find UI inside the tab would be the worse one. What this file
|
||||
// owes that arrangement is a text layer for every page, drawn or not, which is
|
||||
// why lib/view.js keeps its own (see the comment there).
|
||||
|
||||
import { DocModel } from "./lib/model.js";
|
||||
import { PageStrip } from "./lib/view.js";
|
||||
import { Rail } from "./lib/rail.js";
|
||||
import { Tools } from "./lib/tools.js";
|
||||
import { SignaturePad } from "./lib/signature.js";
|
||||
import { buildPdf, unencodableStamps } from "./lib/save.js";
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const sm = window.silentmode || null;
|
||||
|
||||
const FLATTEN_DPI = 150; // raster resolution when rebuilding a redacted page
|
||||
|
||||
// ---- boot pdf.js ------------------------------------------------------
|
||||
let pdfjsLib = null;
|
||||
async function bootPdfjs() {
|
||||
if (pdfjsLib) return pdfjsLib;
|
||||
const core = await import("./vendor/pdfjs/pdf.min.mjs");
|
||||
core.GlobalWorkerOptions.workerSrc = new URL("./vendor/pdfjs/pdf.worker.min.mjs", import.meta.url).href;
|
||||
// pdf_viewer.mjs is built with the core as an external and reads it off the
|
||||
// global at module-evaluation time, so this assignment has to happen first.
|
||||
globalThis.pdfjsLib = core;
|
||||
const viewer = await import("./vendor/pdfjs/web/pdf_viewer.mjs");
|
||||
// A module namespace object is frozen, so the components go on a wrapper
|
||||
// rather than onto `core` itself.
|
||||
pdfjsLib = {
|
||||
getDocument: core.getDocument,
|
||||
TextLayer: core.TextLayer,
|
||||
setLayerDimensions: core.setLayerDimensions,
|
||||
AnnotationMode: core.AnnotationMode,
|
||||
viewer,
|
||||
};
|
||||
return pdfjsLib;
|
||||
}
|
||||
|
||||
// ---- session ----------------------------------------------------------
|
||||
const session = {
|
||||
bytes: null, // Uint8Array, the untouched original
|
||||
pdf: null, // pdf.js PDFDocumentProxy
|
||||
model: null,
|
||||
strip: null,
|
||||
rail: null,
|
||||
tools: null,
|
||||
scratchId: null, // set when the document came from the add-on's scratch dir
|
||||
name: "document.pdf",
|
||||
};
|
||||
|
||||
// ---- chrome -----------------------------------------------------------
|
||||
function toast(msg, err) {
|
||||
const t = $("toast");
|
||||
t.textContent = msg;
|
||||
t.classList.toggle("err", !!err);
|
||||
t.classList.add("on");
|
||||
clearTimeout(toast._t);
|
||||
toast._t = setTimeout(() => t.classList.remove("on"), err ? 4200 : 2000);
|
||||
}
|
||||
function status(msg, err) {
|
||||
const s = $("status");
|
||||
s.textContent = msg;
|
||||
s.classList.toggle("err", !!err);
|
||||
}
|
||||
function setBusy(on, why) {
|
||||
document.body.dataset.busy = on ? "1" : "";
|
||||
if (on && why) status(why);
|
||||
}
|
||||
|
||||
function paintChrome() {
|
||||
const m = session.model;
|
||||
$("save").disabled = !m;
|
||||
$("undo").disabled = !m || !m.canUndo();
|
||||
$("redo").disabled = !m || !m.canRedo();
|
||||
const nameEl = $("docname");
|
||||
nameEl.textContent = session.name || "No document";
|
||||
nameEl.classList.toggle("dirty", !!m && m.isDirty());
|
||||
document.body.dataset.empty = m ? "" : "1";
|
||||
}
|
||||
|
||||
function paintPageInfo() {
|
||||
const m = session.model, strip = session.strip;
|
||||
if (!m || !strip) { $("pageinfo").textContent = ""; return; }
|
||||
const vis = m.visible();
|
||||
const at = strip.current ? vis.indexOf(strip.current) + 1 : 1;
|
||||
const pct = Math.round(strip.scale * 100);
|
||||
$("pageinfo").textContent = `Page ${at} of ${vis.length} · ${pct}%`;
|
||||
}
|
||||
|
||||
// ---- modals -----------------------------------------------------------
|
||||
let modalResolve = null;
|
||||
function openModal(id) {
|
||||
for (const m of document.querySelectorAll(".modal")) m.hidden = true;
|
||||
$(id).hidden = false;
|
||||
$("scrim").hidden = false;
|
||||
return new Promise((res) => { modalResolve = res; });
|
||||
}
|
||||
function closeModal(value) {
|
||||
$("scrim").hidden = true;
|
||||
for (const m of document.querySelectorAll(".modal")) m.hidden = true;
|
||||
const r = modalResolve; modalResolve = null;
|
||||
r?.(value);
|
||||
}
|
||||
$("scrim").addEventListener("pointerdown", (e) => { if (e.target === $("scrim")) closeModal(null); });
|
||||
|
||||
// ---- loading ----------------------------------------------------------
|
||||
async function loadBytes(bytes, name, scratchId = null) {
|
||||
if (session.pdf) await teardown();
|
||||
// pdf.js TRANSFERS the buffer it is given to its worker, which detaches our
|
||||
// view of it — and pdf-lib needs those same bytes at save time. Keep the
|
||||
// original and hand the worker a copy.
|
||||
session.bytes = bytes;
|
||||
session.name = name;
|
||||
session.scratchId = scratchId;
|
||||
setBusy(true, "Opening…");
|
||||
const lib = await bootPdfjs();
|
||||
const task = lib.getDocument({
|
||||
data: bytes.slice(),
|
||||
standardFontDataUrl: "vendor/pdfjs/standard_fonts/",
|
||||
wasmUrl: "vendor/pdfjs/wasm/",
|
||||
iccUrl: "vendor/pdfjs/iccs/",
|
||||
// XFA forms render best-effort. pdf-lib cannot write XFA back, so a
|
||||
// dynamic XFA form is view-and-print only here; see the warning below.
|
||||
enableXfa: true,
|
||||
enableScripting: false,
|
||||
isEvalSupported: false,
|
||||
});
|
||||
let pdf;
|
||||
try { pdf = await task.promise; }
|
||||
catch (e) {
|
||||
setBusy(false);
|
||||
status(`Could not open ${name}: ${e?.message || e}`, true);
|
||||
toast(`Could not open that PDF: ${e?.message || e}`, true);
|
||||
return false;
|
||||
}
|
||||
session.pdf = pdf;
|
||||
|
||||
const pageInfo = [];
|
||||
for (let i = 1; i <= pdf.numPages; i++) {
|
||||
const p = await pdf.getPage(i);
|
||||
const vp = p.getViewport({ scale: 1, rotation: 0 });
|
||||
pageInfo.push({ width: vp.width, height: vp.height, rotate: p.rotate });
|
||||
}
|
||||
session.model = new DocModel({ name, pageCount: pdf.numPages, pageInfo });
|
||||
|
||||
const eventBus = new lib.viewer.EventBus();
|
||||
const linkService = new lib.viewer.PDFLinkService({
|
||||
eventBus, externalLinkTarget: lib.viewer.LinkTarget.BLANK,
|
||||
});
|
||||
const layerProperties = {
|
||||
annotationEditorUIManager: null,
|
||||
annotationStorage: pdf.annotationStorage,
|
||||
downloadManager: null,
|
||||
enableScripting: false,
|
||||
fieldObjectsPromise: pdf.getFieldObjects().catch(() => null),
|
||||
findController: null,
|
||||
hasJSActionsPromise: pdf.hasJSActions().catch(() => false),
|
||||
linkService,
|
||||
};
|
||||
|
||||
session.strip = new PageStrip({
|
||||
container: $("viewerContainer"), viewer: $("viewer"),
|
||||
pdfjsLib: lib, pdf, model: session.model, eventBus, layerProperties,
|
||||
});
|
||||
await session.strip.build();
|
||||
|
||||
// A shim is all PDFLinkService needs to make a PDF's own table-of-contents
|
||||
// links jump to the right page.
|
||||
linkService.setViewer(linkShim(session.strip, session.model));
|
||||
linkService.setDocument(pdf, null);
|
||||
|
||||
session.rail = new Rail({
|
||||
list: $("rail-list"), countEl: $("rail-count"),
|
||||
model: session.model, strip: session.strip,
|
||||
host: { rotate: onRotate, toggleDelete: onToggleDelete, reorder: onReorder },
|
||||
});
|
||||
session.rail.build();
|
||||
|
||||
session.tools = new Tools({
|
||||
strip: session.strip, model: session.model,
|
||||
host: {
|
||||
toast, onChange: () => { paintChrome(); },
|
||||
openTextModal: onTextModal,
|
||||
openSignaturePad: onSignaturePad,
|
||||
signature: () => savedSignature,
|
||||
},
|
||||
});
|
||||
session.tools.setTool("select");
|
||||
|
||||
session.strip.onCurrentChange((uid) => { session.rail.markCurrent(uid); paintPageInfo(); });
|
||||
session.model.onChange(() => { paintChrome(); });
|
||||
|
||||
setBusy(false);
|
||||
const forms = await countFields(pdf);
|
||||
const notes = [`${pdf.numPages} page${pdf.numPages === 1 ? "" : "s"}`];
|
||||
if (forms.fields) notes.push(`${forms.fields} form field${forms.fields === 1 ? "" : "s"}`);
|
||||
if (forms.xfa) notes.push("XFA form — fields can be filled here but cannot be saved back");
|
||||
status(notes.join(" · "), !!forms.xfa);
|
||||
paintChrome();
|
||||
paintPageInfo();
|
||||
return true;
|
||||
}
|
||||
|
||||
async function countFields(pdf) {
|
||||
let fields = 0, xfa = false;
|
||||
try {
|
||||
const fo = await pdf.getFieldObjects();
|
||||
if (fo) for (const v of fo.values()) fields += v.length;
|
||||
} catch {}
|
||||
try { xfa = !!(await pdf.getMetadata())?.info?.IsXFAPresent; } catch {}
|
||||
return { fields, xfa };
|
||||
}
|
||||
|
||||
function linkShim(strip, model) {
|
||||
return {
|
||||
get pagesCount() { return model.visible().length; },
|
||||
get currentPageNumber() { return Math.max(1, model.visible().indexOf(strip.current) + 1); },
|
||||
set currentPageNumber(n) { const uid = model.visible()[n - 1]; if (uid) strip.scrollTo(uid); },
|
||||
get currentScaleValue() { return strip.scaleMode; },
|
||||
set currentScaleValue(v) { strip.setScaleMode(Number.isFinite(+v) ? +v : v); },
|
||||
get currentScale() { return strip.scale; },
|
||||
get isInPresentationMode() { return false; },
|
||||
scrollPageIntoView({ pageNumber }) {
|
||||
// pageNumber is a SOURCE page number; map it through our order so a link
|
||||
// still lands on the right page after a reorder.
|
||||
const uid = model.order.find((u) => model.page(u).src === pageNumber - 1);
|
||||
if (uid && !model.page(uid).deleted) strip.scrollTo(uid);
|
||||
},
|
||||
get pageViewsReady() { return true; },
|
||||
getPageView(i) { const uid = model.visible()[i]; return uid ? strip.views.get(uid)?.pv : null; },
|
||||
};
|
||||
}
|
||||
|
||||
async function teardown() {
|
||||
// Tools first: it is the one holding listeners on the container, and a stale
|
||||
// one would go on handling clicks against a strip that no longer exists.
|
||||
try { session.tools?.destroy(); } catch {}
|
||||
try { session.strip?.destroy(); } catch {}
|
||||
try { session.rail?.destroy(); } catch {}
|
||||
try { await session.pdf?.destroy(); } catch {}
|
||||
session.pdf = null; session.model = null; session.strip = null;
|
||||
session.rail = null; session.tools = null;
|
||||
$("viewer").textContent = "";
|
||||
}
|
||||
|
||||
// ---- open paths -------------------------------------------------------
|
||||
async function openFile(file) {
|
||||
if (!file) return;
|
||||
const looksPdf = /\.pdf$/i.test(file.name) || file.type === "application/pdf";
|
||||
if (!looksPdf) { toast(`${file.name} is not a PDF`, true); return; }
|
||||
if (!(await confirmDiscard())) return;
|
||||
const buf = new Uint8Array(await file.arrayBuffer());
|
||||
if (buf.subarray(0, 5).every((b, i) => b === "%PDF-".charCodeAt(i)) === false) {
|
||||
toast("That file does not start with a PDF header", true);
|
||||
return;
|
||||
}
|
||||
await loadBytes(buf, file.name);
|
||||
}
|
||||
|
||||
$("file-input").addEventListener("change", (e) => {
|
||||
const f = e.target.files?.[0];
|
||||
e.target.value = "";
|
||||
openFile(f);
|
||||
});
|
||||
const pick = () => $("file-input").click();
|
||||
$("open").addEventListener("click", pick);
|
||||
$("open2").addEventListener("click", pick);
|
||||
|
||||
// Drag and drop anywhere in the tab.
|
||||
for (const type of ["dragenter", "dragover"]) {
|
||||
window.addEventListener(type, (e) => {
|
||||
if (![...(e.dataTransfer?.types || [])].includes("Files")) return;
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = "copy";
|
||||
document.body.dataset.dragover = "1";
|
||||
});
|
||||
}
|
||||
window.addEventListener("dragleave", (e) => {
|
||||
if (e.relatedTarget) return; // still inside the window
|
||||
document.body.dataset.dragover = "";
|
||||
});
|
||||
window.addEventListener("drop", (e) => {
|
||||
if (![...(e.dataTransfer?.types || [])].includes("Files")) return;
|
||||
e.preventDefault();
|
||||
document.body.dataset.dragover = "";
|
||||
openFile(e.dataTransfer.files?.[0]);
|
||||
});
|
||||
|
||||
// ---- page operations --------------------------------------------------
|
||||
async function onRotate(uid) {
|
||||
session.model.rotatePage(uid, 90);
|
||||
session.strip.layout();
|
||||
await session.strip.redraw(uid);
|
||||
session.rail.refresh(uid);
|
||||
paintChrome();
|
||||
}
|
||||
function onToggleDelete(uid) {
|
||||
const was = session.model.page(uid).deleted;
|
||||
const ok = session.model.setDeleted(uid, !was);
|
||||
if (!ok && !was) { toast("A PDF needs at least one page", true); return; }
|
||||
session.strip.layout();
|
||||
session.rail.build();
|
||||
paintChrome();
|
||||
paintPageInfo();
|
||||
}
|
||||
function onReorder(uid, beforeUid) {
|
||||
session.model.reorder(uid, beforeUid);
|
||||
session.strip.layout();
|
||||
session.rail.build();
|
||||
paintChrome();
|
||||
paintPageInfo();
|
||||
}
|
||||
$("restore-pages").addEventListener("click", () => {
|
||||
session.model.restoreAllPages();
|
||||
session.strip.layout();
|
||||
session.rail.build();
|
||||
paintChrome();
|
||||
});
|
||||
|
||||
// ---- toolbar ----------------------------------------------------------
|
||||
let redactAcknowledged = false;
|
||||
let flattenRedactions = true;
|
||||
|
||||
for (const b of document.querySelectorAll(".tool[data-tool]")) {
|
||||
b.addEventListener("click", async () => {
|
||||
const t = b.dataset.tool;
|
||||
if (t === "redact" && !redactAcknowledged) {
|
||||
const ok = await openModal("modal-redact");
|
||||
if (!ok) return;
|
||||
redactAcknowledged = true;
|
||||
flattenRedactions = $("redact-flatten").checked;
|
||||
try { await sm?.storage?.set("redactAcknowledged", true); } catch {}
|
||||
}
|
||||
for (const o of document.querySelectorAll(".tool")) o.classList.toggle("active", o === b);
|
||||
session.tools?.setTool(t);
|
||||
status(TOOL_HINTS[t] || "");
|
||||
});
|
||||
}
|
||||
$("redact-ok").addEventListener("click", () => closeModal(true));
|
||||
$("redact-cancel").addEventListener("click", () => closeModal(null));
|
||||
|
||||
const TOOL_HINTS = {
|
||||
select: "Click a mark to select it, drag to move it, Delete to remove it.",
|
||||
highlight: "Drag across text to highlight it.",
|
||||
pen: "Drag to draw.",
|
||||
rect: "Drag to draw a rectangle.",
|
||||
ellipse: "Drag to draw an ellipse.",
|
||||
arrow: "Drag from tail to head.",
|
||||
text: "Click where the text should start.",
|
||||
signature: "Click to place your signature.",
|
||||
redact: "Drag a box over what has to go. Redacted pages are rebuilt as images on save.",
|
||||
};
|
||||
|
||||
for (const s of document.querySelectorAll(".swatch")) {
|
||||
s.addEventListener("click", () => {
|
||||
for (const o of document.querySelectorAll(".swatch")) o.classList.toggle("active", o === s);
|
||||
session.tools?.setColor(s.dataset.color);
|
||||
});
|
||||
}
|
||||
for (const w of document.querySelectorAll(".width")) {
|
||||
w.addEventListener("click", () => {
|
||||
for (const o of document.querySelectorAll(".width")) o.classList.toggle("active", o === w);
|
||||
session.tools?.setWidth(parseFloat(w.dataset.width));
|
||||
});
|
||||
}
|
||||
|
||||
$("undo").addEventListener("click", () => { session.model?.undo(); afterHistory(); });
|
||||
$("redo").addEventListener("click", () => { session.model?.redo(); afterHistory(); });
|
||||
function afterHistory() {
|
||||
if (!session.model) return;
|
||||
session.strip.layout();
|
||||
session.rail.build();
|
||||
session.tools.selected = null;
|
||||
session.strip.refreshAllOverlays(null);
|
||||
paintChrome();
|
||||
paintPageInfo();
|
||||
}
|
||||
|
||||
$("zoom").addEventListener("change", (e) => {
|
||||
const v = e.target.value;
|
||||
session.strip?.setScaleMode(/^[\d.]+$/.test(v) ? parseFloat(v) : v);
|
||||
paintPageInfo();
|
||||
});
|
||||
$("zoom-in").addEventListener("click", () => { session.strip?.stepScale(1); syncZoomSelect(); });
|
||||
$("zoom-out").addEventListener("click", () => { session.strip?.stepScale(-1); syncZoomSelect(); });
|
||||
function syncZoomSelect() {
|
||||
if (!session.strip) return;
|
||||
const exact = String(session.strip.scale);
|
||||
const opt = [...$("zoom").options].find((o) => o.value === exact);
|
||||
$("zoom").value = opt ? exact : $("zoom").value;
|
||||
paintPageInfo();
|
||||
}
|
||||
$("toggle-rail").addEventListener("click", () => {
|
||||
const off = document.body.dataset.rail === "off";
|
||||
document.body.dataset.rail = off ? "" : "off";
|
||||
if (off) session.rail?.build();
|
||||
session.strip?.computeScale();
|
||||
session.strip?.layout();
|
||||
paintPageInfo();
|
||||
});
|
||||
|
||||
let resizeT = null;
|
||||
window.addEventListener("resize", () => {
|
||||
clearTimeout(resizeT);
|
||||
resizeT = setTimeout(() => {
|
||||
if (!session.strip) return;
|
||||
if (typeof session.strip.scaleMode === "string") { session.strip.computeScale(); session.strip.layout(); }
|
||||
paintPageInfo();
|
||||
}, 140);
|
||||
});
|
||||
|
||||
// ---- text stamp -------------------------------------------------------
|
||||
async function onTextModal(at) {
|
||||
$("text-body").value = "";
|
||||
$("text-warn").hidden = true;
|
||||
const p = openModal("modal-text");
|
||||
setTimeout(() => $("text-body").focus(), 30);
|
||||
const ok = await p;
|
||||
if (!ok) return null;
|
||||
const text = $("text-body").value;
|
||||
const size = parseFloat($("text-size").value) || 12;
|
||||
return session.tools.placeText(at, text, size);
|
||||
}
|
||||
$("text-ok").addEventListener("click", () => closeModal(true));
|
||||
$("text-cancel").addEventListener("click", () => closeModal(null));
|
||||
$("text-body").addEventListener("input", () => {
|
||||
const n = unencodableStampsIn($("text-body").value);
|
||||
const w = $("text-warn");
|
||||
w.hidden = n === 0;
|
||||
w.classList.add("warn");
|
||||
if (n) w.textContent = `${n} character${n === 1 ? "" : "s"} cannot be written with the built-in font and will be saved as "?".`;
|
||||
});
|
||||
function unencodableStampsIn(text) {
|
||||
// Same WinAnsi test the writer uses, applied live so the user finds out
|
||||
// before placing the stamp rather than after saving.
|
||||
let n = 0;
|
||||
for (const ch of String(text)) {
|
||||
const code = ch.codePointAt(0);
|
||||
const ok = (code >= 0x20 && code <= 0x7e) || (code >= 0xa0 && code <= 0xff) ||
|
||||
[0x2018, 0x2019, 0x201c, 0x201d, 0x2013, 0x2014, 0x2022, 0x20ac, 0x2026, 0x2122].includes(code) ||
|
||||
ch === "\n";
|
||||
if (!ok) n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// ---- signature --------------------------------------------------------
|
||||
let savedSignature = null;
|
||||
let pad = null;
|
||||
async function onSignaturePad() {
|
||||
if (!pad) {
|
||||
pad = new SignaturePad($("sign-pad"));
|
||||
pad.onChange((has) => { $("sign-ok").disabled = !has; });
|
||||
}
|
||||
pad.clear();
|
||||
const ok = await openModal("modal-sign");
|
||||
if (!ok) return null;
|
||||
const res = pad.result();
|
||||
if (!res) return null;
|
||||
savedSignature = res;
|
||||
try { await sm?.storage?.set("signature", res); } catch {}
|
||||
return res;
|
||||
}
|
||||
$("sign-ok").addEventListener("click", () => closeModal(true));
|
||||
$("sign-cancel").addEventListener("click", () => closeModal(null));
|
||||
$("sign-clear").addEventListener("click", () => pad?.clear());
|
||||
|
||||
// ---- save -------------------------------------------------------------
|
||||
function defaultSaveName() {
|
||||
const base = String(session.name || "document.pdf").replace(/\.pdf$/i, "");
|
||||
return `${base}-edited.pdf`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Everything the user typed into a form field, keyed by field NAME.
|
||||
*
|
||||
* Driven from the document's field list rather than from the storage's
|
||||
* contents. pdf.js keys its annotation storage by annotation id ("11R"), which
|
||||
* means nothing to pdf-lib — pdf-lib addresses fields by name — so the field
|
||||
* list is needed for the mapping either way, and iterating it also keeps
|
||||
* pdf.js's own editor bookkeeping out of the results for free.
|
||||
*/
|
||||
async function harvestFormValues(pdf) {
|
||||
const out = [];
|
||||
let fields = null;
|
||||
try { fields = await pdf.getFieldObjects(); } catch { return out; }
|
||||
if (!fields) return out;
|
||||
const storage = pdf.annotationStorage;
|
||||
for (const [name, objs] of fields) {
|
||||
for (const o of objs || []) {
|
||||
// `has` distinguishes a field the user touched from one they left alone;
|
||||
// writing back every field would rewrite appearances across the whole
|
||||
// document for no reason.
|
||||
if (!o?.id || !storage.has(o.id)) continue;
|
||||
const entry = storage.getRawValue(o.id);
|
||||
if (!entry || typeof entry !== "object" || !("value" in entry)) continue;
|
||||
out.push({ name, value: entry.value });
|
||||
break; // a field's kids (radio buttons) share one value
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Render one page to a PNG at FLATTEN_DPI, unrotated, for the flatten pass. */
|
||||
async function rasterizePage(uid) {
|
||||
const entry = session.strip.views.get(uid);
|
||||
const scale = FLATTEN_DPI / 72;
|
||||
// rotation 0, so the image lands in the page's own unrotated coordinate
|
||||
// space — the page's /Rotate then presents it exactly as before.
|
||||
const vp = entry.pdfPage.getViewport({ scale, rotation: 0 });
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = Math.max(1, Math.ceil(vp.width));
|
||||
canvas.height = Math.max(1, Math.ceil(vp.height));
|
||||
const ctx = canvas.getContext("2d", { alpha: false });
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
await entry.pdfPage.render({ canvasContext: ctx, viewport: vp, intent: "print" }).promise;
|
||||
const blob = await new Promise((res, rej) =>
|
||||
canvas.toBlob((b) => b ? res(b) : rej(new Error("toBlob returned null")), "image/png"));
|
||||
return new Uint8Array(await blob.arrayBuffer());
|
||||
}
|
||||
|
||||
async function doSave() {
|
||||
const m = session.model;
|
||||
if (!m) return;
|
||||
$("save-name").value = defaultSaveName();
|
||||
const summary = $("save-summary");
|
||||
summary.textContent = "";
|
||||
const lines = [];
|
||||
if (m.annots.length) lines.push([`${m.annots.length} mark${m.annots.length === 1 ? "" : "s"} written into the file`, false]);
|
||||
const formValues = await harvestFormValues(session.pdf);
|
||||
if (formValues.length) lines.push([`${formValues.length} form field${formValues.length === 1 ? "" : "s"} filled in`, false]);
|
||||
if (m.deletedCount()) lines.push([`${m.deletedCount()} page${m.deletedCount() === 1 ? "" : "s"} removed`, false]);
|
||||
if (m.pagesChanged() && !m.deletedCount()) lines.push(["pages reordered", false]);
|
||||
if (m.rotationsChanged()) lines.push(["page rotation changed", false]);
|
||||
const red = m.redactedPages();
|
||||
if (red.size) {
|
||||
lines.push([flattenRedactions
|
||||
? `${red.size} redacted page${red.size === 1 ? "" : "s"} rebuilt as an image, so the text under the boxes is gone`
|
||||
: `${red.size} redacted page${red.size === 1 ? "" : "s"} — the text under the boxes stays extractable`,
|
||||
!flattenRedactions]);
|
||||
}
|
||||
const bad = unencodableStamps(m);
|
||||
if (bad) lines.push([`${bad} text stamp${bad === 1 ? "" : "s"} contain characters the built-in font cannot write`, true]);
|
||||
if (!lines.length) lines.push(["no changes — this saves a copy of the original", false]);
|
||||
for (const [text, warn] of lines) {
|
||||
const li = document.createElement("li");
|
||||
li.textContent = text;
|
||||
if (warn) li.className = "warn";
|
||||
summary.append(li);
|
||||
}
|
||||
|
||||
const go = await openModal("modal-save");
|
||||
if (!go) return;
|
||||
let filename = String($("save-name").value || "").trim() || defaultSaveName();
|
||||
if (!/\.pdf$/i.test(filename)) filename += ".pdf";
|
||||
|
||||
setBusy(true, "Writing…");
|
||||
try {
|
||||
const { bytes, report } = await buildPdf({
|
||||
originalBytes: session.bytes,
|
||||
model: m,
|
||||
formValues,
|
||||
flatten: flattenRedactions,
|
||||
rasterize: rasterizePage,
|
||||
});
|
||||
const blob = new Blob([bytes], { type: "application/pdf" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = $("download-link");
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
a.click();
|
||||
setTimeout(() => URL.revokeObjectURL(url), 8000);
|
||||
m.markSaved();
|
||||
paintChrome();
|
||||
const kb = (bytes.length / 1024).toFixed(0);
|
||||
status(`Saved ${filename} · ${kb} KB${report.flattened ? ` · ${report.flattened} page(s) rebuilt as images` : ""}`);
|
||||
toast(`Saved ${filename} (${kb} KB)`);
|
||||
for (const w of report.warn) toast(w, true);
|
||||
} catch (e) {
|
||||
console.error("save failed:", e);
|
||||
status(`Save failed: ${e?.message || e}`, true);
|
||||
toast(`Save failed: ${e?.message || e}`, true);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
$("save").addEventListener("click", doSave);
|
||||
$("save-go").addEventListener("click", () => closeModal(true));
|
||||
$("save-cancel").addEventListener("click", () => closeModal(null));
|
||||
|
||||
// ---- discard / close --------------------------------------------------
|
||||
async function confirmDiscard() {
|
||||
if (!session.model || !session.model.isDirty()) return true;
|
||||
// Deliberately the browser's own confirm(): it is modal against the tab, it
|
||||
// cannot be dismissed by a stray click the way the in-page scrim can, and
|
||||
// losing an hour of markup to a misplaced click is not a risk worth a
|
||||
// prettier dialog.
|
||||
return window.confirm("This document has unsaved changes. Discard them?");
|
||||
}
|
||||
$("discard").addEventListener("click", async () => {
|
||||
if (!(await confirmDiscard())) return;
|
||||
if (session.scratchId && sm?.invoke) {
|
||||
try { await sm.invoke("release", { id: session.scratchId }); } catch {}
|
||||
}
|
||||
try { sm?.closeTab?.(); } catch {}
|
||||
});
|
||||
// Belt and braces for a tab closed from the tab strip rather than our button.
|
||||
window.addEventListener("beforeunload", (e) => {
|
||||
if (session.model?.isDirty()) { e.preventDefault(); e.returnValue = ""; }
|
||||
});
|
||||
|
||||
// A link inside a PDF must not navigate this tab — that would drop the whole
|
||||
// editing session on the floor. Hand it to the add-on, which opens a new tab.
|
||||
$("viewerContainer").addEventListener("click", (e) => {
|
||||
const a = e.target?.closest?.("a[href]");
|
||||
if (!a) return;
|
||||
const href = a.getAttribute("href") || "";
|
||||
if (!/^https?:/i.test(href)) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
sm?.invoke?.("openExternal", { url: href })
|
||||
.catch((err) => toast(`Could not open that link: ${err?.message || err}`, true));
|
||||
}, true);
|
||||
|
||||
// ---- keyboard ---------------------------------------------------------
|
||||
// Ctrl+F, Ctrl+B and Ctrl +/-/0 never reach this page — Theseus claims them
|
||||
// app-wide for find, the add-on sidebar and tab zoom. PDF zoom therefore lives
|
||||
// on bare +/-/0, which is what desktop readers use anyway.
|
||||
const TOOL_KEYS = { v: "select", h: "highlight", p: "pen", r: "rect", o: "ellipse", a: "arrow", t: "text", g: "signature" };
|
||||
window.addEventListener("keydown", (e) => {
|
||||
// A modal owns the keyboard while it is up: Escape cancels, and Enter
|
||||
// commits unless the focus is in the textarea, where it means a newline.
|
||||
if (!$("scrim").hidden) {
|
||||
if (e.key === "Escape") { closeModal(null); e.preventDefault(); return; }
|
||||
if (e.key === "Enter" && e.target?.tagName !== "TEXTAREA" && !e.shiftKey) {
|
||||
const go = document.querySelector(".modal:not([hidden]) .btn.primary:not(:disabled)");
|
||||
if (go) { go.click(); e.preventDefault(); }
|
||||
}
|
||||
return;
|
||||
}
|
||||
const typing = /^(INPUT|TEXTAREA|SELECT)$/.test(e.target?.tagName || "") || e.target?.isContentEditable;
|
||||
const meta = e.ctrlKey || e.metaKey;
|
||||
|
||||
if (meta && e.key.toLowerCase() === "s") { e.preventDefault(); doSave(); return; }
|
||||
if (meta && e.key.toLowerCase() === "o") { e.preventDefault(); pick(); return; }
|
||||
if (meta && e.key.toLowerCase() === "z") {
|
||||
e.preventDefault();
|
||||
if (e.shiftKey) session.model?.redo(); else session.model?.undo();
|
||||
afterHistory();
|
||||
return;
|
||||
}
|
||||
if (typing) return;
|
||||
|
||||
if (e.key === "Escape") {
|
||||
if (session.tools?.tool !== "select") {
|
||||
document.querySelector('.tool[data-tool="select"]')?.click();
|
||||
e.preventDefault();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e.key === "Delete" || e.key === "Backspace") {
|
||||
if (session.tools?.deleteSelected()) { paintChrome(); e.preventDefault(); }
|
||||
return;
|
||||
}
|
||||
if (e.key === "+" || e.key === "=") { session.strip?.stepScale(1); syncZoomSelect(); e.preventDefault(); return; }
|
||||
if (e.key === "-") { session.strip?.stepScale(-1); syncZoomSelect(); e.preventDefault(); return; }
|
||||
if (e.key === "0") { $("zoom").value = "page-width"; session.strip?.setScaleMode("page-width"); paintPageInfo(); e.preventDefault(); return; }
|
||||
|
||||
const t = TOOL_KEYS[e.key.toLowerCase()];
|
||||
if (t) { document.querySelector(`.tool[data-tool="${t}"]`)?.click(); e.preventDefault(); }
|
||||
});
|
||||
|
||||
// ---- start ------------------------------------------------------------
|
||||
(async function start() {
|
||||
try {
|
||||
redactAcknowledged = (await sm?.storage?.get("redactAcknowledged", false)) === true;
|
||||
const sig = await sm?.storage?.get("signature", null);
|
||||
if (sig && Array.isArray(sig.strokes)) savedSignature = sig;
|
||||
} catch {}
|
||||
|
||||
const id = new URLSearchParams(location.search).get("doc");
|
||||
if (!id) { status("Drop a PDF here, or use Open."); paintChrome(); return; }
|
||||
if (!sm?.invoke) { status("This page has to be opened from the PDF Editor add-on.", true); return; }
|
||||
setBusy(true, "Fetching…");
|
||||
try {
|
||||
const res = await sm.invoke("getBytes", { id });
|
||||
const bytes = res.data instanceof Uint8Array ? res.data : new Uint8Array(res.data);
|
||||
await loadBytes(bytes, res.name || "document.pdf", id);
|
||||
} catch (e) {
|
||||
setBusy(false);
|
||||
status(`Could not load that document: ${e?.message || e}`, true);
|
||||
paintChrome();
|
||||
}
|
||||
})();
|
||||
189
bundled-addons/pdf-editor/index.js
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
// PDF Editor — the Node half. Small on purpose: everything interesting
|
||||
// happens in editor.html, which is a full Theseus tab (the "open-tab"
|
||||
// capability) rendering with pdf.js and writing with pdf-lib.
|
||||
//
|
||||
// This file exists for the three things a file:// page cannot do itself:
|
||||
//
|
||||
// 1. Launch. The dock's "Open a PDF…" item opens the editor tab. Nothing
|
||||
// is handed over; the editor shows its own drop zone.
|
||||
// 2. Fetch a PDF the user right-clicked. A file:// page has an opaque
|
||||
// origin, so its cross-origin fetch of https://host/doc.pdf is refused
|
||||
// by CORS on essentially every real server. electron's `net.fetch`
|
||||
// runs on the app session instead, so it follows whatever proxy the
|
||||
// user configured, and the bytes land in a scratch file.
|
||||
// 3. Serve those bytes back. The editor pulls them with
|
||||
// invoke("getBytes", {id}).
|
||||
//
|
||||
// Handoff is a scratch FILE plus an id in the query string, not a
|
||||
// storage.__pending entry. The screenshot add-on shipped __pending first
|
||||
// and had to abandon it: the receiving page drains the entry on first
|
||||
// paint, so a reload — or Theseus restoring the tab on next launch — finds
|
||||
// an empty slot and an editor with no document. An id in the URL survives
|
||||
// both, because the bytes are still on disk. It also keeps a 20 MB PDF out
|
||||
// of a kv store whose every write rewrites the whole JSON file.
|
||||
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const SCRATCH_DIR = "pdf-editor-scratch";
|
||||
const MAX_RECENT = 8; // scratch files kept before the oldest is reaped
|
||||
const MAX_BYTES = 200 * 1024 * 1024; // refuse anything absurd before we buffer it
|
||||
|
||||
module.exports = {
|
||||
activate(api) {
|
||||
// Per-add-on scratch dir under <userData>/addons-data/, a sibling of the
|
||||
// kv JSON. We never write inside the add-on folder — a user reading the
|
||||
// shipped source should see only what shipped.
|
||||
const dataParent = path.dirname(path.join(api.folder, ".."));
|
||||
const scratchDir = path.join(dataParent, "addons-data", SCRATCH_DIR);
|
||||
try { fs.mkdirSync(scratchDir, { recursive: true }); }
|
||||
catch (e) { api.log("scratch mkdir failed:", e?.message); }
|
||||
|
||||
// ---- scratch bookkeeping ------------------------------------------
|
||||
// The ring lives in api.storage so it survives restarts, and every read
|
||||
// prunes entries whose file has gone missing.
|
||||
function readRing() {
|
||||
const r = api.storage.get("recent", []);
|
||||
return Array.isArray(r) ? r : [];
|
||||
}
|
||||
function pruneRing(ring) {
|
||||
const alive = ring.filter((r) => { try { fs.accessSync(r.path); return true; } catch { return false; } });
|
||||
const keep = alive.slice(0, MAX_RECENT);
|
||||
for (const r of alive.slice(MAX_RECENT)) { try { fs.unlinkSync(r.path); } catch {} }
|
||||
return keep;
|
||||
}
|
||||
function remember(entry) {
|
||||
let ring = readRing();
|
||||
ring.unshift(entry);
|
||||
ring = pruneRing(ring);
|
||||
api.storage.set("recent", ring);
|
||||
}
|
||||
function findEntry(id) {
|
||||
return pruneRing(readRing()).find((r) => r.id === id) || null;
|
||||
}
|
||||
function newId() {
|
||||
return "doc-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 8);
|
||||
}
|
||||
|
||||
// A filename we are willing to put on disk and later show as the
|
||||
// document's name. Anything path-ish or non-printable is discarded, not
|
||||
// escaped — the fallback name is always acceptable.
|
||||
function safeName(raw) {
|
||||
const base = String(raw || "").split(/[\\/]/).pop().trim();
|
||||
const clean = base.replace(/[\x00-\x1f<>:"|?*]/g, "").slice(0, 120);
|
||||
if (!clean || clean === "." || clean === "..") return "document.pdf";
|
||||
return /\.pdf$/i.test(clean) ? clean : clean + ".pdf";
|
||||
}
|
||||
// The last path segment of a URL is the best filename guess we have.
|
||||
function nameFromUrl(url) {
|
||||
try {
|
||||
const u = new URL(url);
|
||||
const seg = decodeURIComponent(u.pathname.split("/").filter(Boolean).pop() || "");
|
||||
return safeName(seg || u.hostname + ".pdf");
|
||||
} catch { return "document.pdf"; }
|
||||
}
|
||||
|
||||
function writeScratch(buf, name) {
|
||||
const id = newId();
|
||||
const file = path.join(scratchDir, id + ".pdf");
|
||||
fs.writeFileSync(file, buf);
|
||||
const entry = { id, name, path: file, size: buf.length, at: Date.now() };
|
||||
remember(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
// ---- launch paths -------------------------------------------------
|
||||
// Dock → "Open a PDF…": a bare editor tab. The editor's own drop zone
|
||||
// and File → Open take it from there, so no bytes cross IPC at all.
|
||||
api.onMessage("menu-select", (payload) => {
|
||||
const item = String(payload && payload.id || "");
|
||||
if (item !== "open") throw new Error(`unknown menu item: ${item}`);
|
||||
api.openTab("editor.html");
|
||||
api.log("opened an empty editor tab");
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
// Right-click a link → fetch it here, hand the editor an id.
|
||||
api.onMessage("context-menu", async (payload) => {
|
||||
if (String(payload && payload.itemId || "") !== "open-link") return;
|
||||
const url = String(payload && payload.linkURL || "").trim();
|
||||
if (!url) { api.log("context-menu fired with no link"); return { ok: false }; }
|
||||
let parsed;
|
||||
try { parsed = new URL(url); } catch { throw new Error(`not a URL: ${url.slice(0, 80)}`); }
|
||||
if (!/^https?:$/.test(parsed.protocol)) {
|
||||
throw new Error(`refusing to fetch a ${parsed.protocol} link`);
|
||||
}
|
||||
const { net } = api.require("electron");
|
||||
api.log(`fetching ${parsed.origin}${parsed.pathname.slice(0, 60)} …`);
|
||||
const res = await net.fetch(url);
|
||||
if (!res.ok) throw new Error(`${res.status} ${res.statusText || "fetch failed"}`);
|
||||
const len = Number(res.headers.get("content-length") || 0);
|
||||
if (len > MAX_BYTES) throw new Error(`refusing ${(len / 1048576).toFixed(0)} MB — over the ${MAX_BYTES / 1048576} MB cap`);
|
||||
const buf = Buffer.from(await res.arrayBuffer());
|
||||
if (buf.length > MAX_BYTES) throw new Error(`refusing ${(buf.length / 1048576).toFixed(0)} MB — over the cap`);
|
||||
// %PDF is the only header a PDF can start with. A server that answered
|
||||
// an HTML error page with 200 would otherwise reach the editor and
|
||||
// fail there with a much less useful message.
|
||||
if (buf.subarray(0, 5).toString("latin1") !== "%PDF-") {
|
||||
throw new Error("that link did not answer with a PDF");
|
||||
}
|
||||
const entry = writeScratch(buf, nameFromUrl(url));
|
||||
api.openTab("editor.html", { query: { doc: entry.id } });
|
||||
api.log(`fetched ${entry.name} (${entry.size} bytes) → ${entry.id}`);
|
||||
return { ok: true, id: entry.id };
|
||||
});
|
||||
|
||||
// ---- editor-facing handlers ---------------------------------------
|
||||
// Bytes go across as a Buffer, which structured-clones into a
|
||||
// Uint8Array on the page side. Base64 would inflate a 20 MB file to 27
|
||||
// MB of string for no benefit.
|
||||
api.onMessage("getBytes", (payload) => {
|
||||
const id = String(payload && payload.id || "");
|
||||
const hit = findEntry(id);
|
||||
if (!hit) throw new Error(`no scratch document "${id}"`);
|
||||
return { id: hit.id, name: hit.name, size: hit.size, at: hit.at, data: fs.readFileSync(hit.path) };
|
||||
});
|
||||
|
||||
// The editor calls this when the user closes a document it had been
|
||||
// handed, so a fetched PDF does not sit in scratch forever. Missing ids
|
||||
// are fine — the ring may already have reaped it.
|
||||
api.onMessage("release", (payload) => {
|
||||
const id = String(payload && payload.id || "");
|
||||
const hit = findEntry(id);
|
||||
if (hit) { try { fs.unlinkSync(hit.path); } catch {} }
|
||||
api.storage.set("recent", readRing().filter((r) => r.id !== id));
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
// A link inside a PDF, clicked. It must NOT navigate the editor's own tab —
|
||||
// that would throw away an unsaved editing session — so the editor hands
|
||||
// the URL here and we open it as a new tab instead.
|
||||
api.onMessage("openExternal", (payload) => {
|
||||
const url = String(payload && payload.url || "").trim();
|
||||
let parsed;
|
||||
try { parsed = new URL(url); } catch { throw new Error("not a URL"); }
|
||||
if (!/^https?:$/.test(parsed.protocol)) throw new Error(`refusing to open a ${parsed.protocol} link`);
|
||||
api.openTab(parsed.href);
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
// Reveal the scratch dir. Useful when a save went somewhere the user
|
||||
// cannot find, and for support questions about what is cached.
|
||||
api.onMessage("openScratch", () => {
|
||||
const { shell } = api.require("electron");
|
||||
shell.openPath(scratchDir);
|
||||
return { ok: true, path: scratchDir };
|
||||
});
|
||||
|
||||
// Reap on activation: scratch is a cache, and a crash mid-session can
|
||||
// leave files with no ring entry pointing at them.
|
||||
try {
|
||||
const known = new Set(readRing().map((r) => path.basename(r.path)));
|
||||
for (const f of fs.readdirSync(scratchDir)) {
|
||||
if (!known.has(f)) fs.unlinkSync(path.join(scratchDir, f));
|
||||
}
|
||||
} catch (e) { api.log("scratch reap skipped:", e?.message); }
|
||||
|
||||
api.log("registered pdf-editor toolbar menu + link context-menu item");
|
||||
},
|
||||
};
|
||||
188
bundled-addons/pdf-editor/lib/model.js
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
// The editable state of one open document, and the undo stack over it.
|
||||
//
|
||||
// Two rules hold this together:
|
||||
//
|
||||
// 1. Every mark is stored in PDF user space — points, origin bottom-left,
|
||||
// the page's own unrotated coordinate system. Not screen pixels. That
|
||||
// is the one space shared by pdf.js (viewport.convertToPdfPoint) and
|
||||
// pdf-lib (page.drawX), so a mark survives zooming, rotating and
|
||||
// reordering without a conversion table, and save-time needs no
|
||||
// knowledge of how the page happened to be displayed.
|
||||
//
|
||||
// 2. Marks are keyed to a page's UID, never to its position. Reordering
|
||||
// rewrites `order`; it does not touch a single annotation. A UID also
|
||||
// stays valid while a page is deleted, so undeleting restores that
|
||||
// page's marks with it.
|
||||
//
|
||||
// Undo is snapshot-based, like the screenshot editor's. The alternative —
|
||||
// paired do/undo commands — needs a correct inverse for each of nine mark
|
||||
// types plus reorder, rotate and delete, and one wrong inverse corrupts the
|
||||
// document silently. A snapshot of this state is a few KB even with long pen
|
||||
// paths, so there is nothing to win by being clever.
|
||||
|
||||
const UNDO_MAX = 60;
|
||||
|
||||
let seq = 0;
|
||||
export function nextId(prefix = "m") { return `${prefix}${++seq}-${Math.random().toString(36).slice(2, 7)}`; }
|
||||
|
||||
export class DocModel {
|
||||
constructor({ name, pageCount, pageInfo }) {
|
||||
this.name = name;
|
||||
// pageInfo[i] = { width, height, rotate } straight from pdf.js, in the
|
||||
// source document's order. Immutable — it describes the file, not our
|
||||
// edits.
|
||||
this.pageInfo = pageInfo;
|
||||
this.pages = new Map();
|
||||
this.order = [];
|
||||
for (let i = 0; i < pageCount; i++) {
|
||||
const uid = `p${i}`;
|
||||
this.pages.set(uid, { uid, src: i, rotate: 0, deleted: false });
|
||||
this.order.push(uid);
|
||||
}
|
||||
this.annots = [];
|
||||
this._undo = [];
|
||||
this._redo = [];
|
||||
this._onChange = [];
|
||||
this.savedMark = this._key(); // snapshot identity at last save, for the dirty flag
|
||||
}
|
||||
|
||||
onChange(fn) { this._onChange.push(fn); }
|
||||
_emit(what) { for (const fn of this._onChange) { try { fn(what); } catch (e) { console.warn("model listener threw:", e); } } }
|
||||
|
||||
// ---- derived views ------------------------------------------------
|
||||
/** UIDs in display order, deleted pages left out. */
|
||||
visible() { return this.order.filter((uid) => !this.pages.get(uid).deleted); }
|
||||
page(uid) { return this.pages.get(uid); }
|
||||
/** Total rotation to display a page at: the file's own /Rotate plus ours. */
|
||||
displayRotation(uid) {
|
||||
const p = this.pages.get(uid);
|
||||
return (((this.pageInfo[p.src].rotate + p.rotate) % 360) + 360) % 360;
|
||||
}
|
||||
/** Our added rotation only — what PDFPageView.update({rotation}) wants. */
|
||||
extraRotation(uid) { return ((this.pages.get(uid).rotate % 360) + 360) % 360; }
|
||||
size(uid) { const p = this.pages.get(uid); return this.pageInfo[p.src]; }
|
||||
annotsFor(uid) { return this.annots.filter((a) => a.page === uid); }
|
||||
annot(id) { return this.annots.find((a) => a.id === id) || null; }
|
||||
deletedCount() { return this.order.filter((uid) => this.pages.get(uid).deleted).length; }
|
||||
|
||||
hasRedactions() { return this.annots.some((a) => a.kind === "redact"); }
|
||||
redactedPages() {
|
||||
return new Set(this.annots.filter((a) => a.kind === "redact").map((a) => a.page));
|
||||
}
|
||||
/** True when the page list differs from the file's, so save must rebuild it. */
|
||||
pagesChanged() {
|
||||
if (this.deletedCount() > 0) return true;
|
||||
return this.order.some((uid, i) => this.pages.get(uid).src !== i);
|
||||
}
|
||||
rotationsChanged() { return this.order.some((uid) => this.pages.get(uid).rotate % 360 !== 0); }
|
||||
|
||||
// ---- history ------------------------------------------------------
|
||||
_snapshot() {
|
||||
return {
|
||||
annots: structuredClone(this.annots),
|
||||
order: this.order.slice(),
|
||||
pages: this.order.map((uid) => { const p = this.pages.get(uid); return [uid, p.rotate, p.deleted]; }),
|
||||
};
|
||||
}
|
||||
_restore(s) {
|
||||
this.annots = structuredClone(s.annots);
|
||||
this.order = s.order.slice();
|
||||
for (const [uid, rotate, deleted] of s.pages) {
|
||||
const p = this.pages.get(uid);
|
||||
if (p) { p.rotate = rotate; p.deleted = deleted; }
|
||||
}
|
||||
}
|
||||
// A cheap identity for "has anything changed since the last save".
|
||||
_key() {
|
||||
return JSON.stringify([this.order, this.order.map((u) => [this.pages.get(u).rotate, this.pages.get(u).deleted]), this.annots.length,
|
||||
this.annots.map((a) => a.id)]);
|
||||
}
|
||||
|
||||
/** Wrap a mutation so it lands on the undo stack as one step. */
|
||||
edit(label, fn) {
|
||||
const before = this._snapshot();
|
||||
const r = fn();
|
||||
this._undo.push({ label, before });
|
||||
if (this._undo.length > UNDO_MAX) this._undo.shift();
|
||||
this._redo = [];
|
||||
this._emit(label);
|
||||
return r;
|
||||
}
|
||||
canUndo() { return this._undo.length > 0; }
|
||||
canRedo() { return this._redo.length > 0; }
|
||||
undo() {
|
||||
const step = this._undo.pop();
|
||||
if (!step) return null;
|
||||
this._redo.push({ label: step.label, before: this._snapshot() });
|
||||
this._restore(step.before);
|
||||
this._emit("undo:" + step.label);
|
||||
return step.label;
|
||||
}
|
||||
redo() {
|
||||
const step = this._redo.pop();
|
||||
if (!step) return null;
|
||||
this._undo.push({ label: step.label, before: this._snapshot() });
|
||||
this._restore(step.before);
|
||||
this._emit("redo:" + step.label);
|
||||
return step.label;
|
||||
}
|
||||
markSaved() { this.savedMark = this._key(); this._emit("saved"); }
|
||||
isDirty() { return this._key() !== this.savedMark; }
|
||||
|
||||
// ---- mutations ----------------------------------------------------
|
||||
addAnnot(a) {
|
||||
return this.edit(a.kind, () => {
|
||||
const full = { id: nextId("a"), ...a };
|
||||
this.annots.push(full);
|
||||
return full;
|
||||
});
|
||||
}
|
||||
removeAnnot(id) {
|
||||
const a = this.annot(id);
|
||||
if (!a) return false;
|
||||
return this.edit("delete " + a.kind, () => {
|
||||
this.annots = this.annots.filter((x) => x.id !== id);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
/** Shift a mark by a user-space delta. `patch` is applied to the mark. */
|
||||
moveAnnot(id, patch) {
|
||||
const a = this.annot(id);
|
||||
if (!a) return false;
|
||||
return this.edit("move " + a.kind, () => { Object.assign(a, patch); return true; });
|
||||
}
|
||||
|
||||
rotatePage(uid, deltaDeg) {
|
||||
return this.edit("rotate", () => {
|
||||
const p = this.pages.get(uid);
|
||||
p.rotate = (((p.rotate + deltaDeg) % 360) + 360) % 360;
|
||||
return p.rotate;
|
||||
});
|
||||
}
|
||||
setDeleted(uid, deleted) {
|
||||
return this.edit(deleted ? "delete page" : "restore page", () => {
|
||||
this.pages.get(uid).deleted = !!deleted;
|
||||
// Never leave nothing behind — a zero-page PDF cannot be written.
|
||||
if (this.visible().length === 0) { this.pages.get(uid).deleted = false; return false; }
|
||||
return true;
|
||||
});
|
||||
}
|
||||
restoreAllPages() {
|
||||
return this.edit("restore pages", () => {
|
||||
for (const uid of this.order) this.pages.get(uid).deleted = false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
/** Move `uid` so it sits immediately before `beforeUid` (or last if null). */
|
||||
reorder(uid, beforeUid) {
|
||||
if (uid === beforeUid) return false;
|
||||
return this.edit("reorder", () => {
|
||||
const from = this.order.indexOf(uid);
|
||||
if (from < 0) return false;
|
||||
this.order.splice(from, 1);
|
||||
const at = beforeUid == null ? this.order.length : this.order.indexOf(beforeUid);
|
||||
this.order.splice(at < 0 ? this.order.length : at, 0, uid);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
197
bundled-addons/pdf-editor/lib/overlay.js
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
// The on-screen layer that draws marks over a rendered page.
|
||||
//
|
||||
// One SVG per page, sized to that page's current viewport in CSS pixels.
|
||||
// Every mark is stored in PDF user space, so drawing means running each point
|
||||
// through `viewport.convertToViewportPoint` — which already accounts for the
|
||||
// page's scale AND its rotation. That is why rotating a page needs no special
|
||||
// handling here: the viewport changes, we re-render, and the marks turn with
|
||||
// the content they were drawn on.
|
||||
//
|
||||
// SVG rather than a canvas because marks have to be individually
|
||||
// hit-testable, selectable and movable, and because it stays crisp when the
|
||||
// user zooms without us re-rasterising anything.
|
||||
|
||||
import { arrowBarbs, boundsOf, signatureStrokes, textLayout } from "./shape.js";
|
||||
|
||||
const NS = "http://www.w3.org/2000/svg";
|
||||
function el(name, attrs) {
|
||||
const n = document.createElementNS(NS, name);
|
||||
for (const k in attrs) if (attrs[k] != null) n.setAttribute(k, attrs[k]);
|
||||
return n;
|
||||
}
|
||||
|
||||
export class Overlay {
|
||||
/** @param {HTMLElement} pageDiv the .page div owned by a PDFPageView */
|
||||
constructor(pageDiv) {
|
||||
this.svg = el("svg", { class: "smOverlay", xmlns: NS });
|
||||
this.marks = el("g", { class: "marks" });
|
||||
this.chrome = el("g", { class: "chrome" }); // selection box + handles
|
||||
this.svg.append(this.marks, this.chrome);
|
||||
pageDiv.append(this.svg);
|
||||
this.viewport = null;
|
||||
}
|
||||
|
||||
destroy() { this.svg.remove(); }
|
||||
|
||||
/** Map a user-space point to CSS pixels inside the page div. */
|
||||
toView(x, y) {
|
||||
const [vx, vy] = this.viewport.convertToViewportPoint(x, y);
|
||||
return [vx, vy];
|
||||
}
|
||||
/** Map a page-relative CSS pixel point back to user space. */
|
||||
toPdf(vx, vy) {
|
||||
const [x, y] = this.viewport.convertToPdfPoint(vx, vy);
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
render(viewport, annots, selectedId) {
|
||||
this.viewport = viewport;
|
||||
this.svg.setAttribute("width", viewport.width);
|
||||
this.svg.setAttribute("height", viewport.height);
|
||||
this.svg.setAttribute("viewBox", `0 0 ${viewport.width} ${viewport.height}`);
|
||||
this.marks.textContent = "";
|
||||
for (const a of annots) {
|
||||
const node = this._draw(a);
|
||||
if (!node) continue;
|
||||
node.classList.add("mark");
|
||||
node.dataset.id = a.id;
|
||||
if (a.id === selectedId) node.classList.add("sel");
|
||||
this.marks.append(node);
|
||||
}
|
||||
this._drawChrome(annots.find((a) => a.id === selectedId) || null);
|
||||
}
|
||||
|
||||
// Scale a user-space length (a stroke width, a font size) into CSS pixels.
|
||||
// The viewport's scale already carries zoom; rotation does not change length.
|
||||
get k() { return this.viewport ? this.viewport.scale : 1; }
|
||||
|
||||
_draw(a) {
|
||||
const g = el("g", {});
|
||||
switch (a.kind) {
|
||||
case "highlight": {
|
||||
for (const r of a.rects || []) {
|
||||
const [x0, y1] = this.toView(r.x, r.y + r.h);
|
||||
const [x1, y0] = this.toView(r.x + r.w, r.y);
|
||||
g.append(el("rect", {
|
||||
x: Math.min(x0, x1), y: Math.min(y0, y1),
|
||||
width: Math.abs(x1 - x0), height: Math.abs(y0 - y1),
|
||||
fill: a.color, "fill-opacity": a.opacity ?? 0.38,
|
||||
style: "mix-blend-mode:multiply",
|
||||
}));
|
||||
}
|
||||
g.dataset.fill = "1";
|
||||
return g;
|
||||
}
|
||||
case "pen": {
|
||||
const pts = (a.pts || []).map(([x, y]) => this.toView(x, y));
|
||||
if (pts.length < 2) return null;
|
||||
g.append(el("polyline", {
|
||||
points: pts.map((p) => `${p[0]},${p[1]}`).join(" "),
|
||||
fill: "none", stroke: a.color, "stroke-width": (a.width || 2) * this.k,
|
||||
"stroke-linecap": "round", "stroke-linejoin": "round",
|
||||
}));
|
||||
return g;
|
||||
}
|
||||
case "rect": {
|
||||
const b = this._box(a);
|
||||
g.append(el("rect", { ...b, fill: "none", stroke: a.color, "stroke-width": (a.width || 2) * this.k }));
|
||||
return g;
|
||||
}
|
||||
case "ellipse": {
|
||||
const b = this._box(a);
|
||||
g.append(el("ellipse", {
|
||||
cx: b.x + b.width / 2, cy: b.y + b.height / 2,
|
||||
rx: Math.max(0.5, b.width / 2), ry: Math.max(0.5, b.height / 2),
|
||||
fill: "none", stroke: a.color, "stroke-width": (a.width || 2) * this.k,
|
||||
}));
|
||||
return g;
|
||||
}
|
||||
case "arrow": {
|
||||
const [x1, y1] = this.toView(a.x1, a.y1);
|
||||
const [x2, y2] = this.toView(a.x2, a.y2);
|
||||
const w = (a.width || 2) * this.k;
|
||||
const barbs = arrowBarbs(a.x1, a.y1, a.x2, a.y2, a.width || 2).map((p) => this.toView(p.x, p.y));
|
||||
g.append(el("line", { x1, y1, x2, y2, stroke: a.color, "stroke-width": w, "stroke-linecap": "round" }));
|
||||
g.append(el("polyline", {
|
||||
points: `${barbs[0][0]},${barbs[0][1]} ${x2},${y2} ${barbs[1][0]},${barbs[1][1]}`,
|
||||
fill: "none", stroke: a.color, "stroke-width": w,
|
||||
"stroke-linecap": "round", "stroke-linejoin": "round",
|
||||
}));
|
||||
return g;
|
||||
}
|
||||
case "text": {
|
||||
const size = (a.size || 12) * this.k;
|
||||
let widest = 0;
|
||||
for (const line of textLayout(a)) {
|
||||
const [vx, vy] = this.toView(line.x, line.y);
|
||||
const t = el("text", {
|
||||
x: vx, y: vy, fill: a.color,
|
||||
"font-family": "Helvetica, Arial, sans-serif",
|
||||
"font-size": size, "xml:space": "preserve",
|
||||
});
|
||||
t.textContent = line.text;
|
||||
g.append(t);
|
||||
}
|
||||
g.dataset.fill = "1";
|
||||
// Measure once attached so the selection box is tight. Cached on the
|
||||
// mark in user-space units; boundsOf() reads it.
|
||||
requestAnimationFrame(() => {
|
||||
try {
|
||||
for (const t of g.querySelectorAll("text")) widest = Math.max(widest, t.getComputedTextLength());
|
||||
if (widest > 0) a._w = widest / this.k;
|
||||
} catch {}
|
||||
});
|
||||
return g;
|
||||
}
|
||||
case "signature": {
|
||||
const w = (a.width || 1.6) * this.k;
|
||||
for (const stroke of signatureStrokes(a)) {
|
||||
if (stroke.length < 2) continue;
|
||||
g.append(el("polyline", {
|
||||
points: stroke.map(([x, y]) => this.toView(x, y).join(",")).join(" "),
|
||||
fill: "none", stroke: a.color, "stroke-width": w,
|
||||
"stroke-linecap": "round", "stroke-linejoin": "round",
|
||||
}));
|
||||
}
|
||||
return g;
|
||||
}
|
||||
case "redact": {
|
||||
const b = this._box(a);
|
||||
g.append(el("rect", { ...b, fill: "#000" }));
|
||||
g.dataset.fill = "1";
|
||||
return g;
|
||||
}
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
// A user-space {x,y,w,h} box as an SVG rect's attributes. Done through both
|
||||
// corners so a rotated viewport lands the right way up.
|
||||
_box(a) {
|
||||
const [ax, ay] = this.toView(a.x, a.y);
|
||||
const [bx, by] = this.toView(a.x + a.w, a.y + a.h);
|
||||
return { x: Math.min(ax, bx), y: Math.min(ay, by),
|
||||
width: Math.abs(bx - ax), height: Math.abs(by - ay) };
|
||||
}
|
||||
|
||||
_drawChrome(sel) {
|
||||
this.chrome.textContent = "";
|
||||
if (!sel) return;
|
||||
const b = boundsOf(sel);
|
||||
const r = this._box({ x: b.x, y: b.y, w: b.w, h: b.h });
|
||||
const pad = 3;
|
||||
this.chrome.append(el("rect", {
|
||||
class: "selbox", x: r.x - pad, y: r.y - pad,
|
||||
width: r.width + pad * 2, height: r.height + pad * 2,
|
||||
}));
|
||||
}
|
||||
|
||||
/** The mark id under a page-relative CSS pixel point, or null. */
|
||||
hitTest(vx, vy) {
|
||||
const node = document.elementFromPoint(
|
||||
vx + this.svg.getBoundingClientRect().left,
|
||||
vy + this.svg.getBoundingClientRect().top);
|
||||
const mark = node?.closest?.(".mark");
|
||||
return mark ? mark.dataset.id : null;
|
||||
}
|
||||
}
|
||||
161
bundled-addons/pdf-editor/lib/rail.js
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
// The page rail: a thumbnail per page, draggable to reorder, with rotate and
|
||||
// delete on each one.
|
||||
//
|
||||
// Thumbnails render off the same pdf.js page objects as the main strip but at
|
||||
// a fixed small width, lazily, and are cached as bitmaps. A rotation re-renders
|
||||
// just that one; a reorder re-renders nothing at all, because the canvas is
|
||||
// already correct and only its position in the list changes.
|
||||
//
|
||||
// Reordering uses HTML5 drag-and-drop rather than pointer maths: it gives us
|
||||
// the drag image, the escape-to-cancel and the autoscroll for free, all of
|
||||
// which a hand-rolled version gets wrong first.
|
||||
|
||||
const THUMB_W = 148;
|
||||
|
||||
export class Rail {
|
||||
constructor({ list, countEl, model, strip, host }) {
|
||||
this.list = list;
|
||||
this.countEl = countEl;
|
||||
this.model = model;
|
||||
this.strip = strip;
|
||||
this.host = host;
|
||||
this.cache = new Map(); // uid -> canvas
|
||||
this.dragUid = null;
|
||||
this._io = new IntersectionObserver((entries) => {
|
||||
for (const ent of entries) {
|
||||
if (ent.isIntersecting) this._render(ent.target.dataset.uid);
|
||||
}
|
||||
}, { root: this.list, rootMargin: "300px 0px" });
|
||||
}
|
||||
|
||||
build() {
|
||||
this.list.textContent = "";
|
||||
this._io.disconnect();
|
||||
const visible = this.model.visible();
|
||||
this.model.order.forEach((uid) => {
|
||||
const p = this.model.page(uid);
|
||||
const el = document.createElement("div");
|
||||
el.className = "thumb" + (p.deleted ? " deleted" : "");
|
||||
el.dataset.uid = uid;
|
||||
el.draggable = !p.deleted;
|
||||
const pos = visible.indexOf(uid);
|
||||
el.innerHTML = `
|
||||
<canvas class="tcanvas"></canvas>
|
||||
<div class="tfoot">
|
||||
<span class="tnum">${p.deleted ? "removed" : pos + 1}</span>
|
||||
<button class="tbtn rot" title="Rotate this page 90° clockwise">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M13 8a5 5 0 1 1-1.5-3.5"/><path d="M13 2v3h-3"/></svg>
|
||||
</button>
|
||||
<button class="tbtn del" title="${p.deleted ? "Bring this page back" : "Remove this page"}">
|
||||
${p.deleted
|
||||
? '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M3 8c0-3 2-5 5-5s5 2 5 5-2 5-5 5"/><path d="M6 5L3 8l3 3"/></svg>'
|
||||
: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 4h10"/><path d="M6 4V2h4v2"/><path d="M4 4l1 10h6l1-10"/></svg>'}
|
||||
</button>
|
||||
</div>`;
|
||||
this.list.append(el);
|
||||
this._io.observe(el);
|
||||
this._wire(el, uid);
|
||||
});
|
||||
this._paintCount();
|
||||
this.markCurrent(this.strip.current);
|
||||
}
|
||||
|
||||
_paintCount() {
|
||||
const n = this.model.visible().length;
|
||||
const gone = this.model.deletedCount();
|
||||
this.countEl.textContent = `${n} page${n === 1 ? "" : "s"}${gone ? ` · ${gone} removed` : ""}`;
|
||||
const restore = document.getElementById("restore-pages");
|
||||
if (restore) restore.hidden = gone === 0;
|
||||
}
|
||||
|
||||
_wire(el, uid) {
|
||||
el.querySelector(".rot").addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
this.host.rotate(uid);
|
||||
});
|
||||
el.querySelector(".del").addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
this.host.toggleDelete(uid);
|
||||
});
|
||||
el.addEventListener("click", () => {
|
||||
if (this.model.page(uid).deleted) return;
|
||||
this.strip.scrollTo(uid);
|
||||
});
|
||||
|
||||
el.addEventListener("dragstart", (e) => {
|
||||
this.dragUid = uid;
|
||||
el.classList.add("dragging");
|
||||
e.dataTransfer.effectAllowed = "move";
|
||||
// Firefox and Chromium both need *something* set or the drag aborts.
|
||||
e.dataTransfer.setData("text/plain", uid);
|
||||
});
|
||||
el.addEventListener("dragend", () => {
|
||||
this.dragUid = null;
|
||||
el.classList.remove("dragging");
|
||||
for (const t of this.list.children) t.classList.remove("dropbefore", "dropafter");
|
||||
});
|
||||
el.addEventListener("dragover", (e) => {
|
||||
if (!this.dragUid || this.dragUid === uid) return;
|
||||
e.preventDefault();
|
||||
const r = el.getBoundingClientRect();
|
||||
const after = e.clientY > r.top + r.height / 2;
|
||||
for (const t of this.list.children) t.classList.remove("dropbefore", "dropafter");
|
||||
el.classList.add(after ? "dropafter" : "dropbefore");
|
||||
});
|
||||
el.addEventListener("drop", (e) => {
|
||||
if (!this.dragUid || this.dragUid === uid) return;
|
||||
e.preventDefault();
|
||||
const r = el.getBoundingClientRect();
|
||||
const after = e.clientY > r.top + r.height / 2;
|
||||
const order = this.model.order;
|
||||
let beforeUid = uid;
|
||||
if (after) {
|
||||
const i = order.indexOf(uid);
|
||||
beforeUid = order[i + 1] ?? null;
|
||||
}
|
||||
this.host.reorder(this.dragUid, beforeUid);
|
||||
});
|
||||
}
|
||||
|
||||
async _render(uid) {
|
||||
const el = this.list.querySelector(`.thumb[data-uid="${uid}"]`);
|
||||
if (!el) return;
|
||||
const canvas = el.querySelector(".tcanvas");
|
||||
if (canvas.dataset.done === this._sig(uid)) return;
|
||||
const entry = this.strip.views.get(uid);
|
||||
if (!entry) return;
|
||||
const base = entry.pdfPage.getViewport({ scale: 1, rotation: this.model.displayRotation(uid) });
|
||||
const scale = THUMB_W / base.width;
|
||||
const vp = entry.pdfPage.getViewport({ scale, rotation: this.model.displayRotation(uid) });
|
||||
const dpr = Math.min(2, window.devicePixelRatio || 1);
|
||||
canvas.width = Math.max(1, Math.floor(vp.width * dpr));
|
||||
canvas.height = Math.max(1, Math.floor(vp.height * dpr));
|
||||
canvas.style.aspectRatio = `${vp.width} / ${vp.height}`;
|
||||
const ctx = canvas.getContext("2d", { alpha: false });
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.scale(dpr, dpr);
|
||||
try {
|
||||
await entry.pdfPage.render({ canvasContext: ctx, viewport: vp, intent: "display" }).promise;
|
||||
canvas.dataset.done = this._sig(uid);
|
||||
} catch (e) {
|
||||
if (e?.name !== "RenderingCancelledException") console.warn(`thumb ${uid}:`, e?.message || e);
|
||||
}
|
||||
}
|
||||
|
||||
// A thumbnail only needs re-rendering when its rotation changes.
|
||||
_sig(uid) { return String(this.model.displayRotation(uid)); }
|
||||
|
||||
/** Re-render one thumbnail — after a rotation. */
|
||||
refresh(uid) {
|
||||
const el = this.list.querySelector(`.thumb[data-uid="${uid}"]`);
|
||||
if (el) el.querySelector(".tcanvas").dataset.done = "";
|
||||
this._render(uid);
|
||||
}
|
||||
|
||||
markCurrent(uid) {
|
||||
for (const t of this.list.children) t.classList.toggle("current", t.dataset.uid === uid);
|
||||
}
|
||||
|
||||
destroy() { this._io.disconnect(); this.list.textContent = ""; }
|
||||
}
|
||||
274
bundled-addons/pdf-editor/lib/save.js
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
// Turning the editing session back into a PDF, with pdf-lib.
|
||||
//
|
||||
// The source bytes are never mutated. Every save re-loads the original and
|
||||
// replays the session onto that fresh copy, so a botched save cannot poison
|
||||
// the next one and the file on disk stays whatever it was.
|
||||
//
|
||||
// Step order is load-bearing:
|
||||
//
|
||||
// 1. form values — before anything strips a widget
|
||||
// 2. flatten — replaces a redacted page's content stream, which
|
||||
// would wipe marks drawn in step 3
|
||||
// 3. marks — drawn against ORIGINAL page indices, which are still
|
||||
// valid because nothing has moved yet
|
||||
// 4. page tree — reorder and delete last, so step 3's indices held
|
||||
// 5. rotation — independent of order, done in the same pass
|
||||
//
|
||||
// Marks are drawn with pdf-lib's operator helpers rather than drawSvgPath.
|
||||
// drawSvgPath takes an SVG-shaped path with y running downwards from an
|
||||
// anchor, so every mark would need a second coordinate convention and a
|
||||
// matching inverse in the overlay. Raw operators take the user-space points we
|
||||
// already store, unchanged.
|
||||
|
||||
import {
|
||||
PDFDocument, PDFName, StandardFonts, rgb, degrees,
|
||||
pushGraphicsState, popGraphicsState, moveTo, lineTo, stroke, fill,
|
||||
setStrokingColor, setFillingColor, setLineWidth, setLineCap, setLineJoin,
|
||||
LineCapStyle, LineJoinStyle,
|
||||
} from "../vendor/pdf-lib/pdf-lib.esm.min.js";
|
||||
|
||||
import { arrowBarbs, signatureStrokes, textLayout } from "./shape.js";
|
||||
|
||||
/** #rrggbb -> pdf-lib rgb(). */
|
||||
function colorOf(hex) {
|
||||
const m = /^#?([0-9a-f]{6})$/i.exec(String(hex || "#000000"));
|
||||
const n = m ? parseInt(m[1], 16) : 0;
|
||||
return rgb(((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255);
|
||||
}
|
||||
|
||||
// Helvetica is a WinAnsi font: it simply cannot encode most of Unicode.
|
||||
// Embedding a font that could would mean shipping a several-MB typeface for a
|
||||
// feature most stamps never need, so instead we substitute and say so.
|
||||
const WIN_ANSI_OK = /^[\x20-\x7E\xA0-\xFF‘’“”–—•€…‰‹›ŒœŠšŸŽžƒˆ˜™†‡‰]*$/;
|
||||
export function winAnsiSafe(text) {
|
||||
const s = String(text ?? "");
|
||||
if (WIN_ANSI_OK.test(s)) return { text: s, dropped: 0 };
|
||||
let dropped = 0;
|
||||
const out = Array.from(s).map((ch) => {
|
||||
if (WIN_ANSI_OK.test(ch)) return ch;
|
||||
dropped++;
|
||||
return "?";
|
||||
}).join("");
|
||||
return { text: out, dropped };
|
||||
}
|
||||
|
||||
/** True when any text stamp carries characters Helvetica cannot write. */
|
||||
export function unencodableStamps(model) {
|
||||
return model.annots.filter((a) => a.kind === "text" && winAnsiSafe(a.text).dropped > 0).length;
|
||||
}
|
||||
|
||||
// ---- mark drawing -----------------------------------------------------
|
||||
function strokePolyline(page, pts, color, width) {
|
||||
if (pts.length < 2) return;
|
||||
const ops = [
|
||||
pushGraphicsState(),
|
||||
setStrokingColor(colorOf(color)),
|
||||
setLineWidth(width),
|
||||
setLineCap(LineCapStyle.Round),
|
||||
setLineJoin(LineJoinStyle.Round),
|
||||
moveTo(pts[0][0], pts[0][1]),
|
||||
];
|
||||
for (let i = 1; i < pts.length; i++) ops.push(lineTo(pts[i][0], pts[i][1]));
|
||||
ops.push(stroke(), popGraphicsState());
|
||||
page.pushOperators(...ops);
|
||||
}
|
||||
|
||||
function fillRect(page, x, y, w, h, color, opacity) {
|
||||
if (opacity != null && opacity < 1) {
|
||||
// drawRectangle is the only path that can set a fill opacity, because it
|
||||
// registers the ExtGState for us.
|
||||
page.drawRectangle({ x, y, width: w, height: h, color: colorOf(color), opacity, borderWidth: 0 });
|
||||
return;
|
||||
}
|
||||
page.pushOperators(
|
||||
pushGraphicsState(), setFillingColor(colorOf(color)),
|
||||
moveTo(x, y), lineTo(x + w, y), lineTo(x + w, y + h), lineTo(x, y + h),
|
||||
fill(), popGraphicsState());
|
||||
}
|
||||
|
||||
function drawMark(page, a, font) {
|
||||
switch (a.kind) {
|
||||
case "highlight":
|
||||
for (const r of a.rects || []) {
|
||||
if (r.w <= 0 || r.h <= 0) continue;
|
||||
fillRect(page, r.x, r.y, r.w, r.h, a.color, a.opacity ?? 0.38);
|
||||
}
|
||||
return;
|
||||
case "pen":
|
||||
strokePolyline(page, a.pts || [], a.color, a.width || 2);
|
||||
return;
|
||||
case "rect":
|
||||
page.drawRectangle({ x: a.x, y: a.y, width: a.w, height: a.h,
|
||||
borderColor: colorOf(a.color), borderWidth: a.width || 2, opacity: 0 });
|
||||
return;
|
||||
case "ellipse":
|
||||
page.drawEllipse({ x: a.x + a.w / 2, y: a.y + a.h / 2,
|
||||
xScale: Math.max(0.5, a.w / 2), yScale: Math.max(0.5, a.h / 2),
|
||||
borderColor: colorOf(a.color), borderWidth: a.width || 2, opacity: 0 });
|
||||
return;
|
||||
case "arrow": {
|
||||
const w = a.width || 2;
|
||||
strokePolyline(page, [[a.x1, a.y1], [a.x2, a.y2]], a.color, w);
|
||||
const b = arrowBarbs(a.x1, a.y1, a.x2, a.y2, w);
|
||||
strokePolyline(page, [[b[0].x, b[0].y], [a.x2, a.y2], [b[1].x, b[1].y]], a.color, w);
|
||||
return;
|
||||
}
|
||||
case "text": {
|
||||
const size = a.size || 12;
|
||||
for (const line of textLayout(a)) {
|
||||
const { text } = winAnsiSafe(line.text);
|
||||
if (!text) continue;
|
||||
page.drawText(text, { x: line.x, y: line.y, size, font, color: colorOf(a.color) });
|
||||
}
|
||||
return;
|
||||
}
|
||||
case "signature":
|
||||
for (const s of signatureStrokes(a)) strokePolyline(page, s, a.color, a.width || 1.6);
|
||||
return;
|
||||
case "redact":
|
||||
// Opaque, no border, no transparency — a redaction that can be seen
|
||||
// through is not a redaction.
|
||||
fillRect(page, a.x, a.y, a.w, a.h, "#000000", 1);
|
||||
return;
|
||||
default:
|
||||
console.warn("save: unknown mark kind", a.kind);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- form fields ------------------------------------------------------
|
||||
function applyFormValues(doc, values, font, report) {
|
||||
if (!values.length) return;
|
||||
let form;
|
||||
try { form = doc.getForm(); }
|
||||
catch (e) { report.warn.push("This file's form could not be opened, so field values were not saved."); return; }
|
||||
const byName = new Map();
|
||||
try { for (const f of form.getFields()) byName.set(f.getName(), f); }
|
||||
catch (e) { report.warn.push("This file's field list could not be read."); return; }
|
||||
|
||||
let wrote = 0, missed = 0;
|
||||
for (const { name, value } of values) {
|
||||
const f = byName.get(name);
|
||||
if (!f) { missed++; continue; }
|
||||
try {
|
||||
if (typeof f.setText === "function") {
|
||||
const { text, dropped } = winAnsiSafe(value == null ? "" : String(value));
|
||||
if (dropped) report.substituted += dropped;
|
||||
f.setText(text);
|
||||
} else if (typeof f.check === "function") {
|
||||
const on = value === true || value === "On" || (typeof value === "string" && value !== "Off" && value !== "");
|
||||
if (on) f.check(); else f.uncheck();
|
||||
} else if (typeof f.select === "function") {
|
||||
if (value == null || value === "") { if (typeof f.clear === "function") f.clear(); }
|
||||
else f.select(Array.isArray(value) ? value[0] : String(value));
|
||||
} else { missed++; continue; }
|
||||
// Per-field so one field Helvetica cannot draw does not abort the save.
|
||||
try { if (typeof f.updateAppearances === "function") f.updateAppearances(font); } catch {}
|
||||
wrote++;
|
||||
} catch (e) {
|
||||
missed++;
|
||||
console.warn(`form field "${name}":`, e?.message || e);
|
||||
}
|
||||
}
|
||||
report.fieldsWritten = wrote;
|
||||
if (missed) report.warn.push(`${missed} field${missed === 1 ? "" : "s"} could not be written back.`);
|
||||
}
|
||||
|
||||
// ---- flattening -------------------------------------------------------
|
||||
// A redaction box that only paints over text is theatre: the text is still in
|
||||
// the content stream and still comes out of a copy-paste. The only fix that
|
||||
// pdf-lib alone can reach is to replace the page's whole content stream with a
|
||||
// picture of it, which is what this does. Annotations (form widgets, links) are
|
||||
// deliberately KEPT — they are not part of the content stream, so they were
|
||||
// never the leak, and dropping them would quietly break a form.
|
||||
async function flattenPage(doc, page, pngBytes, report) {
|
||||
const img = await doc.embedPng(pngBytes);
|
||||
const box = page.getCropBox();
|
||||
// Empty array, not a fresh empty stream: pdf-lib appends its own stream to
|
||||
// this array on the first draw call, so the original marks are simply gone.
|
||||
page.node.set(PDFName.of("Contents"), doc.context.obj([]));
|
||||
page.drawImage(img, { x: box.x, y: box.y, width: box.width, height: box.height });
|
||||
report.flattened++;
|
||||
}
|
||||
|
||||
// ---- page tree --------------------------------------------------------
|
||||
function rebuildPages(doc, originalPages, srcOrder) {
|
||||
const wanted = srcOrder.map((i) => originalPages[i]).filter(Boolean);
|
||||
if (!wanted.length) throw new Error("a PDF needs at least one page");
|
||||
for (let i = doc.getPageCount() - 1; i >= 0; i--) doc.removePage(i);
|
||||
// addPage re-registers a page whose ref was dropped by removePage, so the
|
||||
// same page objects (and the widget annotations that point at them) come
|
||||
// back intact in the new order.
|
||||
for (const p of wanted) doc.addPage(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} o
|
||||
* @param {Uint8Array} o.originalBytes untouched source
|
||||
* @param {DocModel} o.model
|
||||
* @param {{name:string,value:any}[]} o.formValues harvested from pdf.js
|
||||
* @param {boolean} o.flatten rebuild redacted pages as images
|
||||
* @param {(uid:string)=>Promise<Uint8Array>} o.rasterize renders one page to PNG
|
||||
* @returns {Promise<{bytes:Uint8Array, report:object}>}
|
||||
*/
|
||||
export async function buildPdf({ originalBytes, model, formValues = [], flatten = true, rasterize }) {
|
||||
const report = { flattened: 0, marks: 0, fieldsWritten: 0, substituted: 0, warn: [] };
|
||||
const doc = await PDFDocument.load(originalBytes, {
|
||||
ignoreEncryption: true,
|
||||
// Leave the file's own Producer/ModDate alone. A privacy-minded browser
|
||||
// should not stamp "this was edited, with this library, at this time" into
|
||||
// every document that passes through it.
|
||||
updateMetadata: false,
|
||||
});
|
||||
const font = await doc.embedFont(StandardFonts.Helvetica);
|
||||
const originalPages = doc.getPages();
|
||||
|
||||
// 1. form values
|
||||
applyFormValues(doc, formValues, font, report);
|
||||
|
||||
// 2. flatten redacted pages
|
||||
const redacted = model.redactedPages();
|
||||
if (flatten && redacted.size && typeof rasterize === "function") {
|
||||
for (const uid of redacted) {
|
||||
const src = model.page(uid).src;
|
||||
const page = originalPages[src];
|
||||
if (!page) continue;
|
||||
try { await flattenPage(doc, page, await rasterize(uid), report); }
|
||||
catch (e) {
|
||||
report.warn.push(`Page ${src + 1} could not be rebuilt as an image; its redaction only covers the text.`);
|
||||
console.warn("flatten failed:", e);
|
||||
}
|
||||
}
|
||||
} else if (redacted.size && !flatten) {
|
||||
report.warn.push("Redactions cover the text but do not remove it — it is still extractable from the saved file.");
|
||||
}
|
||||
|
||||
// 3. marks, against original indices
|
||||
for (const uid of model.visible()) {
|
||||
const page = originalPages[model.page(uid).src];
|
||||
if (!page) continue;
|
||||
// Redaction boxes go on top of everything else on their page.
|
||||
const marks = model.annotsFor(uid);
|
||||
for (const a of marks) if (a.kind !== "redact") { drawMark(page, a, font); report.marks++; }
|
||||
for (const a of marks) if (a.kind === "redact") { drawMark(page, a, font); report.marks++; }
|
||||
}
|
||||
|
||||
// 4. page tree
|
||||
const srcOrder = model.visible().map((uid) => model.page(uid).src);
|
||||
if (model.pagesChanged()) {
|
||||
rebuildPages(doc, originalPages, srcOrder);
|
||||
report.pagesRebuilt = true;
|
||||
}
|
||||
|
||||
// 5. rotation — applied to the page objects, so order does not matter
|
||||
for (const uid of model.visible()) {
|
||||
const extra = model.extraRotation(uid);
|
||||
if (!extra) continue;
|
||||
const page = originalPages[model.page(uid).src];
|
||||
if (!page) continue;
|
||||
try { page.setRotation(degrees((page.getRotation().angle + extra) % 360)); }
|
||||
catch (e) { console.warn("setRotation:", e?.message); }
|
||||
}
|
||||
|
||||
const bytes = await doc.save({ useObjectStreams: true });
|
||||
return { bytes, report };
|
||||
}
|
||||
111
bundled-addons/pdf-editor/lib/shape.js
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// Geometry shared by the on-screen overlay and the pdf-lib writer.
|
||||
//
|
||||
// Both need to agree exactly on where an arrowhead's barbs land, where each
|
||||
// line of a text stamp sits, and how a normalised signature stroke maps into
|
||||
// its placed box. Keeping that arithmetic in one place is what makes "what I
|
||||
// drew" and "what got saved" the same picture; two copies of it drift the
|
||||
// first time either side is touched.
|
||||
//
|
||||
// Everything here works in PDF user space: points, origin bottom-left, y up.
|
||||
|
||||
/** Approximate ascent as a fraction of font size, for Helvetica. */
|
||||
export const ASCENT = 0.76;
|
||||
/** Line advance as a fraction of font size. */
|
||||
export const LINE_GAP = 1.2;
|
||||
|
||||
/**
|
||||
* Where each line of a text stamp is drawn. `a.y` is the TOP of the block —
|
||||
* that is what the user clicked — so every baseline hangs below it.
|
||||
* @returns {{text:string, x:number, y:number}[]} y is the baseline.
|
||||
*/
|
||||
export function textLayout(a) {
|
||||
const lines = String(a.text ?? "").split("\n");
|
||||
const size = a.size || 12;
|
||||
return lines.map((text, i) => ({
|
||||
text,
|
||||
x: a.x,
|
||||
y: a.y - size * ASCENT - i * size * LINE_GAP,
|
||||
}));
|
||||
}
|
||||
|
||||
/** Height of a text stamp's block, for hit-testing and the selection box. */
|
||||
export function textBlockHeight(a) {
|
||||
const lines = String(a.text ?? "").split("\n").length;
|
||||
const size = a.size || 12;
|
||||
return size * ASCENT + (lines - 1) * size * LINE_GAP + size * 0.24;
|
||||
}
|
||||
|
||||
/**
|
||||
* The two barb points of an arrowhead at (x2,y2) coming from (x1,y1).
|
||||
* Head length scales with stroke width but is floored so a thin arrow still
|
||||
* reads as an arrow.
|
||||
*/
|
||||
export function arrowBarbs(x1, y1, x2, y2, width) {
|
||||
const dx = x2 - x1, dy = y2 - y1;
|
||||
const len = Math.hypot(dx, dy) || 1;
|
||||
const head = Math.max(6, Math.min(len * 0.34, (width || 2) * 4.5));
|
||||
const ux = dx / len, uy = dy / len;
|
||||
// 24° either side of the shaft.
|
||||
const cos = Math.cos(0.42), sin = Math.sin(0.42);
|
||||
return [
|
||||
{ x: x2 - head * (ux * cos - uy * sin), y: y2 - head * (uy * cos + ux * sin) },
|
||||
{ x: x2 - head * (ux * cos + uy * sin), y: y2 - head * (uy * cos - ux * sin) },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* A placed signature's strokes in user space. Strokes are stored normalised
|
||||
* (0..1, y measured DOWN from the top of the pad) so the same drawing can be
|
||||
* dropped at any size, on any page, any number of times.
|
||||
*/
|
||||
export function signatureStrokes(a) {
|
||||
const { x, y, w, h } = a;
|
||||
return (a.strokes || []).map((s) => s.map(([nx, ny]) => [x + nx * w, y + (1 - ny) * h]));
|
||||
}
|
||||
|
||||
/** Normalise a box that may have been dragged right-to-left or bottom-to-top. */
|
||||
export function normBox(x0, y0, x1, y1) {
|
||||
return { x: Math.min(x0, x1), y: Math.min(y0, y1), w: Math.abs(x1 - x0), h: Math.abs(y1 - y0) };
|
||||
}
|
||||
|
||||
/** Axis-aligned bounds of a mark, in user space. Used for selection and hit-testing. */
|
||||
export function boundsOf(a) {
|
||||
switch (a.kind) {
|
||||
case "highlight": {
|
||||
const rs = a.rects || [];
|
||||
if (!rs.length) return { x: 0, y: 0, w: 0, h: 0 };
|
||||
const x0 = Math.min(...rs.map((r) => r.x)), y0 = Math.min(...rs.map((r) => r.y));
|
||||
const x1 = Math.max(...rs.map((r) => r.x + r.w)), y1 = Math.max(...rs.map((r) => r.y + r.h));
|
||||
return { x: x0, y: y0, w: x1 - x0, h: y1 - y0 };
|
||||
}
|
||||
case "pen": {
|
||||
const pts = a.pts || [];
|
||||
if (!pts.length) return { x: 0, y: 0, w: 0, h: 0 };
|
||||
const xs = pts.map((p) => p[0]), ys = pts.map((p) => p[1]);
|
||||
const pad = (a.width || 2) / 2;
|
||||
return { x: Math.min(...xs) - pad, y: Math.min(...ys) - pad,
|
||||
w: Math.max(...xs) - Math.min(...xs) + pad * 2, h: Math.max(...ys) - Math.min(...ys) + pad * 2 };
|
||||
}
|
||||
case "arrow": return normBox(a.x1, a.y1, a.x2, a.y2);
|
||||
case "text": {
|
||||
// Width is unknown without measuring the font; the renderer measures it
|
||||
// and caches it on the mark as `_w` so selection boxes are tight.
|
||||
return { x: a.x, y: a.y - textBlockHeight(a), w: a._w || (a.size || 12) * 6, h: textBlockHeight(a) };
|
||||
}
|
||||
default: return { x: a.x, y: a.y, w: a.w, h: a.h };
|
||||
}
|
||||
}
|
||||
|
||||
/** Shift a mark by (dx,dy) in user space. Returns the patch, does not mutate. */
|
||||
export function translatePatch(a, dx, dy) {
|
||||
switch (a.kind) {
|
||||
case "highlight":
|
||||
return { rects: (a.rects || []).map((r) => ({ ...r, x: r.x + dx, y: r.y + dy })) };
|
||||
case "pen":
|
||||
return { pts: (a.pts || []).map(([x, y]) => [x + dx, y + dy]) };
|
||||
case "arrow":
|
||||
return { x1: a.x1 + dx, y1: a.y1 + dy, x2: a.x2 + dx, y2: a.y2 + dy };
|
||||
default:
|
||||
return { x: a.x + dx, y: a.y + dy };
|
||||
}
|
||||
}
|
||||
114
bundled-addons/pdf-editor/lib/signature.js
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
// The signature pad.
|
||||
//
|
||||
// What comes out is a list of strokes, each a list of points normalised to
|
||||
// 0..1 inside the pad's box, y measured downwards. Not a picture. That matters
|
||||
// for three reasons: the same signature can be dropped at any size on any page
|
||||
// and stay sharp, it survives into the saved PDF as vector line art rather
|
||||
// than a fuzzy PNG, and it is a few hundred bytes in storage instead of tens
|
||||
// of kilobytes of base64.
|
||||
//
|
||||
// The pad also trims the drawing to its own bounding box before normalising,
|
||||
// so a signature scrawled in the top-left corner of the pad still fills the
|
||||
// box it gets placed in.
|
||||
|
||||
export class SignaturePad {
|
||||
constructor(canvas) {
|
||||
this.canvas = canvas;
|
||||
this.ctx = canvas.getContext("2d");
|
||||
this.strokes = []; // in canvas pixel space while drawing
|
||||
this.cur = null;
|
||||
this._onChange = null;
|
||||
canvas.addEventListener("pointerdown", (e) => this._down(e));
|
||||
canvas.addEventListener("pointermove", (e) => this._move(e));
|
||||
window.addEventListener("pointerup", () => this._up());
|
||||
this.clear();
|
||||
}
|
||||
|
||||
onChange(fn) { this._onChange = fn; }
|
||||
|
||||
clear() {
|
||||
this.strokes = [];
|
||||
this.cur = null;
|
||||
this._paint();
|
||||
this._onChange?.(false);
|
||||
}
|
||||
|
||||
get isEmpty() { return !this.strokes.some((s) => s.length > 1); }
|
||||
|
||||
_pt(e) {
|
||||
const r = this.canvas.getBoundingClientRect();
|
||||
// The canvas is CSS-scaled to the modal's width, so a client point has to
|
||||
// come back through that ratio to land in the backing store's pixels.
|
||||
return [
|
||||
(e.clientX - r.left) * (this.canvas.width / r.width),
|
||||
(e.clientY - r.top) * (this.canvas.height / r.height),
|
||||
];
|
||||
}
|
||||
_down(e) {
|
||||
if (e.button !== 0) return;
|
||||
this.cur = [this._pt(e)];
|
||||
this.strokes.push(this.cur);
|
||||
this.canvas.setPointerCapture?.(e.pointerId);
|
||||
e.preventDefault();
|
||||
}
|
||||
_move(e) {
|
||||
if (!this.cur) return;
|
||||
this.cur.push(this._pt(e));
|
||||
this._paint();
|
||||
}
|
||||
_up() {
|
||||
if (!this.cur) return;
|
||||
if (this.cur.length < 2) this.strokes.pop();
|
||||
this.cur = null;
|
||||
this._paint();
|
||||
this._onChange?.(!this.isEmpty);
|
||||
}
|
||||
|
||||
_paint() {
|
||||
const { ctx, canvas } = this;
|
||||
ctx.save();
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
// A baseline, so people sign at a sensible height rather than in a corner.
|
||||
ctx.strokeStyle = "rgba(20,30,50,.18)";
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(24, canvas.height * 0.72);
|
||||
ctx.lineTo(canvas.width - 24, canvas.height * 0.72);
|
||||
ctx.stroke();
|
||||
ctx.strokeStyle = "#101418";
|
||||
ctx.lineWidth = 2.6;
|
||||
ctx.lineCap = "round";
|
||||
ctx.lineJoin = "round";
|
||||
for (const s of this.strokes) {
|
||||
if (s.length < 2) continue;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(s[0][0], s[0][1]);
|
||||
for (let i = 1; i < s.length; i++) ctx.lineTo(s[i][0], s[i][1]);
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{strokes:[number,number][][], aspect:number}|null}
|
||||
* strokes normalised into 0..1 over the drawing's own bounds; `aspect` is
|
||||
* height/width of those bounds, so the caller can place it undistorted.
|
||||
*/
|
||||
result() {
|
||||
const live = this.strokes.filter((s) => s.length > 1);
|
||||
if (!live.length) return null;
|
||||
const xs = live.flat().map((p) => p[0]);
|
||||
const ys = live.flat().map((p) => p[1]);
|
||||
const x0 = Math.min(...xs), x1 = Math.max(...xs);
|
||||
const y0 = Math.min(...ys), y1 = Math.max(...ys);
|
||||
// A single horizontal or vertical line has zero extent on one axis; floor
|
||||
// it so the division below cannot produce Infinity.
|
||||
const w = Math.max(1, x1 - x0), h = Math.max(1, y1 - y0);
|
||||
return {
|
||||
strokes: live.map((s) => s.map(([x, y]) => [(x - x0) / w, (y - y0) / h])),
|
||||
aspect: Math.min(1.2, Math.max(0.12, h / w)),
|
||||
};
|
||||
}
|
||||
}
|
||||
287
bundled-addons/pdf-editor/lib/tools.js
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
// Pointer behaviour for every tool.
|
||||
//
|
||||
// The editor is modal: one tool is armed, and what a drag does depends only on
|
||||
// which. The alternative — inferring intent from what is under the cursor — is
|
||||
// what makes PDF annotators infuriating, because a drag meant to draw a box
|
||||
// selects a paragraph instead. Modality is enforced in CSS (see the
|
||||
// body[data-mode] rules): with a drawing tool armed, the text and form layers
|
||||
// stop taking pointer events entirely, so a drag cannot be stolen.
|
||||
//
|
||||
// Highlight is the deliberate exception. It works BY selecting text — the
|
||||
// text layer stays live, the browser does the selection, and on release we
|
||||
// convert the selection's client rects into user space. Rebuilding text
|
||||
// selection from scratch over pdf.js's span soup would be worse at it.
|
||||
|
||||
import { boundsOf, normBox, translatePatch } from "./shape.js";
|
||||
|
||||
const MIN_DRAG = 3; // CSS px before a press counts as a drag
|
||||
const SIG_WIDTH_PT = 170; // default placed width of a signature
|
||||
|
||||
export class Tools {
|
||||
constructor({ strip, model, host }) {
|
||||
this.strip = strip;
|
||||
this.model = model;
|
||||
this.host = host; // editor.js: {toast, openTextModal, openSignaturePad, signature(), onChange}
|
||||
this.tool = "select";
|
||||
this.color = "#ffd400";
|
||||
this.width = 2.5;
|
||||
this.selected = null;
|
||||
this.drag = null;
|
||||
// Opening a second document builds a second Tools over the same container.
|
||||
// Without a way to unbind, the first one stays subscribed, keeps pointing
|
||||
// at a torn-down strip, and throws on the next click — so every listener
|
||||
// goes on one signal and destroy() drops the lot.
|
||||
this._abort = new AbortController();
|
||||
const opts = { signal: this._abort.signal };
|
||||
const c = strip.container;
|
||||
c.addEventListener("pointerdown", (e) => this.onDown(e), opts);
|
||||
c.addEventListener("pointermove", (e) => this.onMove(e), opts);
|
||||
window.addEventListener("pointerup", (e) => this.onUp(e), opts);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._abort.abort();
|
||||
this.drag = null;
|
||||
this.selected = null;
|
||||
}
|
||||
|
||||
get mode() {
|
||||
if (this.tool === "select") return "select";
|
||||
if (this.tool === "highlight") return "highlight";
|
||||
if (this.tool === "text" || this.tool === "signature") return "stamp";
|
||||
return "draw";
|
||||
}
|
||||
|
||||
setTool(t) {
|
||||
this.tool = t;
|
||||
this.selected = null;
|
||||
document.body.dataset.mode = this.mode;
|
||||
this.strip.refreshAllOverlays(null);
|
||||
this.host.onChange?.();
|
||||
}
|
||||
setColor(c) { this.color = c; if (this.selected) this._recolorSelected(c); }
|
||||
setWidth(w) { this.width = w; }
|
||||
|
||||
_recolorSelected(c) {
|
||||
const a = this.model.annot(this.selected);
|
||||
if (!a || a.kind === "redact") return;
|
||||
this.model.moveAnnot(a.id, { color: c });
|
||||
this.strip.refreshAllOverlays(this.selected);
|
||||
}
|
||||
|
||||
deleteSelected() {
|
||||
if (!this.selected) return false;
|
||||
const ok = this.model.removeAnnot(this.selected);
|
||||
this.selected = null;
|
||||
this.strip.refreshAllOverlays(null);
|
||||
return ok;
|
||||
}
|
||||
|
||||
// ---- pointer ------------------------------------------------------
|
||||
onDown(e) {
|
||||
if (e.button !== 0) return;
|
||||
const hit = this.strip.pageAt(e.clientX, e.clientY);
|
||||
if (!hit) return;
|
||||
const ov = this.strip.overlayFor(hit.uid);
|
||||
if (!ov || !ov.viewport) return;
|
||||
|
||||
if (this.tool === "highlight") { this._selDirty = false; return; } // let the browser select
|
||||
|
||||
const [x, y] = ov.toPdf(hit.vx, hit.vy);
|
||||
|
||||
if (this.tool === "select") {
|
||||
const id = e.target?.closest?.(".mark")?.dataset?.id || null;
|
||||
this.selected = id;
|
||||
this.strip.refreshAllOverlays(id);
|
||||
if (id) {
|
||||
const a = this.model.annot(id);
|
||||
this.drag = { kind: "move", uid: hit.uid, id, x0: x, y0: y, orig: structuredClone(a), moved: false };
|
||||
e.preventDefault();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.tool === "text") {
|
||||
this.host.openTextModal({ uid: hit.uid, x, y });
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (this.tool === "signature") {
|
||||
this._placeSignature(hit.uid, x, y);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// Drawing tools: start a drag with a live preview.
|
||||
this.drag = { kind: this.tool, uid: hit.uid, x0: x, y0: y, x, y,
|
||||
pts: this.tool === "pen" ? [[x, y]] : null,
|
||||
cx: e.clientX, cy: e.clientY, moved: false };
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
onMove(e) {
|
||||
const d = this.drag;
|
||||
if (!d) return;
|
||||
const ov = this.strip.overlayFor(d.uid);
|
||||
if (!ov || !ov.viewport) return;
|
||||
const view = this.strip.views.get(d.uid);
|
||||
if (!view) { this.drag = null; return; }
|
||||
const rect = view.pv.div.getBoundingClientRect();
|
||||
// A drag that wanders off the page stays on it. Marks outside the page
|
||||
// box are invisible in any PDF reader, so letting one be drawn there
|
||||
// would only produce something that disappears on save.
|
||||
const cx = Math.min(Math.max(e.clientX, rect.left), rect.right);
|
||||
const cy = Math.min(Math.max(e.clientY, rect.top), rect.bottom);
|
||||
const [x, y] = ov.toPdf(cx - rect.left, cy - rect.top);
|
||||
d.x = x; d.y = y;
|
||||
if (!d.moved && Math.hypot(e.clientX - (d.cx ?? e.clientX), e.clientY - (d.cy ?? e.clientY)) > MIN_DRAG) d.moved = true;
|
||||
|
||||
if (d.kind === "move") {
|
||||
const a = this.model.annot(d.id);
|
||||
if (!a) return;
|
||||
const patch = translatePatch(d.orig, x - d.x0, y - d.y0);
|
||||
Object.assign(a, patch); // live, un-journalled; committed on up
|
||||
d.moved = true;
|
||||
this.strip.refreshOverlay(d.uid, d.id);
|
||||
return;
|
||||
}
|
||||
if (d.kind === "pen") { d.pts.push([x, y]); }
|
||||
this.strip.setPreview(d.uid, this._previewAnnot(d));
|
||||
}
|
||||
|
||||
onUp(e) {
|
||||
if (this.tool === "highlight") { this._commitHighlight(); return; }
|
||||
const d = this.drag;
|
||||
this.drag = null;
|
||||
if (!d) return;
|
||||
this.strip.setPreview(null, null);
|
||||
|
||||
if (d.kind === "move") {
|
||||
const a = this.model.annot(d.id);
|
||||
if (!a || !d.moved) { this.strip.refreshOverlay(d.uid, this.selected); return; }
|
||||
// Put the mark back where it started, then move it through the model so
|
||||
// the whole gesture lands on the undo stack as one step.
|
||||
const moved = structuredClone(a);
|
||||
Object.assign(a, d.orig);
|
||||
const patch = {};
|
||||
for (const k of Object.keys(moved)) if (k !== "id" && k !== "page" && k !== "kind") patch[k] = moved[k];
|
||||
this.model.moveAnnot(d.id, patch);
|
||||
this.strip.refreshAllOverlays(d.id);
|
||||
return;
|
||||
}
|
||||
|
||||
const a = this._previewAnnot(d, true);
|
||||
if (a) {
|
||||
const made = this.model.addAnnot(a);
|
||||
this.selected = this.tool === "select" ? made.id : null;
|
||||
}
|
||||
this.strip.refreshAllOverlays(this.selected);
|
||||
this.host.onChange?.();
|
||||
}
|
||||
|
||||
// The mark a drag currently describes. `final` rejects degenerate ones so a
|
||||
// stray click does not leave an invisible zero-size rectangle behind.
|
||||
_previewAnnot(d, final = false) {
|
||||
const common = { page: d.uid, color: this.color, width: this.width };
|
||||
switch (d.kind) {
|
||||
case "pen": {
|
||||
if (final && d.pts.length < 2) return null;
|
||||
return { kind: "pen", ...common, pts: d.pts.slice() };
|
||||
}
|
||||
case "arrow": {
|
||||
if (final && !d.moved) return null;
|
||||
return { kind: "arrow", ...common, x1: d.x0, y1: d.y0, x2: d.x, y2: d.y };
|
||||
}
|
||||
case "rect": case "ellipse": case "redact": {
|
||||
const b = normBox(d.x0, d.y0, d.x, d.y);
|
||||
if (final && (b.w < 2 || b.h < 2)) return null;
|
||||
if (d.kind === "redact") return { kind: "redact", page: d.uid, ...b };
|
||||
return { kind: d.kind, ...common, ...b };
|
||||
}
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- highlight ----------------------------------------------------
|
||||
// Take the live selection's client rects, drop them onto whichever page
|
||||
// each one sits on, and convert to user space. A selection spanning a page
|
||||
// break therefore produces one highlight mark per page, which is also what
|
||||
// the PDF format wants.
|
||||
_commitHighlight() {
|
||||
const sel = window.getSelection();
|
||||
if (!sel || sel.isCollapsed || sel.rangeCount === 0) return;
|
||||
const byPage = new Map();
|
||||
for (let i = 0; i < sel.rangeCount; i++) {
|
||||
for (const cr of sel.getRangeAt(i).getClientRects()) {
|
||||
if (cr.width < 0.5 || cr.height < 0.5) continue;
|
||||
const hit = this.strip.pageAt(cr.left + cr.width / 2, cr.top + cr.height / 2);
|
||||
if (!hit) continue;
|
||||
const ov = this.strip.overlayFor(hit.uid);
|
||||
if (!ov || !ov.viewport) continue;
|
||||
const r = this.strip.views.get(hit.uid).pv.div.getBoundingClientRect();
|
||||
const [ax, ay] = ov.toPdf(cr.left - r.left, cr.top - r.top);
|
||||
const [bx, by] = ov.toPdf(cr.right - r.left, cr.bottom - r.top);
|
||||
const box = normBox(ax, ay, bx, by);
|
||||
if (box.w < 0.5 || box.h < 0.5) continue;
|
||||
if (!byPage.has(hit.uid)) byPage.set(hit.uid, []);
|
||||
byPage.get(hit.uid).push(box);
|
||||
}
|
||||
}
|
||||
if (!byPage.size) return;
|
||||
for (const [uid, rects] of byPage) {
|
||||
this.model.addAnnot({ kind: "highlight", page: uid, color: this.color, opacity: 0.38, rects: mergeRows(rects) });
|
||||
}
|
||||
sel.removeAllRanges();
|
||||
this.strip.refreshAllOverlays(null);
|
||||
this.host.onChange?.();
|
||||
}
|
||||
|
||||
// ---- stamps -------------------------------------------------------
|
||||
placeText({ uid, x, y }, text, size) {
|
||||
if (!String(text || "").trim()) return null;
|
||||
const a = this.model.addAnnot({ kind: "text", page: uid, color: this.color, size, x, y, text });
|
||||
this.strip.refreshAllOverlays(a.id);
|
||||
this.selected = a.id;
|
||||
this.host.onChange?.();
|
||||
return a;
|
||||
}
|
||||
|
||||
async _placeSignature(uid, x, y) {
|
||||
let sig = this.host.signature();
|
||||
if (!sig) {
|
||||
sig = await this.host.openSignaturePad();
|
||||
if (!sig) return;
|
||||
}
|
||||
const w = SIG_WIDTH_PT;
|
||||
const h = w * (sig.aspect || 0.34);
|
||||
const a = this.model.addAnnot({
|
||||
kind: "signature", page: uid, color: this.color === "#ffd400" ? "#1a1f2b" : this.color,
|
||||
width: 1.6, x, y: y - h, w, h, strokes: sig.strokes,
|
||||
});
|
||||
this.selected = a.id;
|
||||
this.strip.refreshAllOverlays(a.id);
|
||||
this.host.onChange?.();
|
||||
}
|
||||
|
||||
/** Bounds of the current selection, for the status line. */
|
||||
selectedBounds() {
|
||||
const a = this.model.annot(this.selected);
|
||||
return a ? boundsOf(a) : null;
|
||||
}
|
||||
}
|
||||
|
||||
// Client rects for a text selection come one per text run, so a single
|
||||
// highlighted line arrives as a dozen slivers. Merging the ones that share a
|
||||
// baseline turns them back into one band per line — fewer objects in the saved
|
||||
// file, and no seams where two slivers overlap at 38% opacity.
|
||||
function mergeRows(rects) {
|
||||
const rows = [];
|
||||
for (const r of rects.slice().sort((a, b) => b.y - a.y || a.x - b.x)) {
|
||||
const row = rows.find((q) => Math.abs((q.y + q.h / 2) - (r.y + r.h / 2)) < Math.min(q.h, r.h) * 0.5);
|
||||
if (!row) { rows.push({ ...r }); continue; }
|
||||
const x0 = Math.min(row.x, r.x), x1 = Math.max(row.x + row.w, r.x + r.w);
|
||||
const y0 = Math.min(row.y, r.y), y1 = Math.max(row.y + row.h, r.y + r.h);
|
||||
row.x = x0; row.w = x1 - x0; row.y = y0; row.h = y1 - y0;
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
441
bundled-addons/pdf-editor/lib/view.js
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
// The scrolling page strip.
|
||||
//
|
||||
// Built out of pdf.js's PDFPageView components rather than its PDFViewer.
|
||||
// PDFViewer renders `pdfDocument.numPages` pages in the file's own order and
|
||||
// offers no way to hide, reorder or individually rotate one — which is three
|
||||
// of this editor's features. Owning the strip costs us a scroll observer and
|
||||
// a scale calculation; it buys pages that move the instant the user drags a
|
||||
// thumbnail, with no round-trip through the file.
|
||||
//
|
||||
// Two layers are ours rather than PDFPageView's:
|
||||
//
|
||||
// * The text layer. PDFPageView builds one, but tears it down whenever the
|
||||
// page's canvas is discarded — and Theseus's find bar is Chromium's
|
||||
// findInPage over the live DOM, so a torn-down text layer is a page that
|
||||
// Ctrl+F cannot see. We build our own from cached text content and keep it
|
||||
// attached for every page, drawn or not, so find covers the whole
|
||||
// document rather than the dozen pages currently rasterised.
|
||||
//
|
||||
// * The overlay (lib/overlay.js), for the same reason plus hit-testing.
|
||||
//
|
||||
// Canvases ARE virtualised: a letter page at 100% is ~3.4 MB of bitmap, so a
|
||||
// few hundred of them is gigabytes. Only pages near the viewport hold one.
|
||||
|
||||
import { Overlay } from "./overlay.js";
|
||||
|
||||
const GUTTER = 28; // px of breathing room either side at fit-width
|
||||
const DRAW_MARGIN = "180% 0px"; // how far outside the viewport to keep canvases
|
||||
const MAX_PUMP_PASSES = 12; // retry sweeps before the pump gives up
|
||||
const MAX_DRAW_RETRIES = 6; // per-page redraw attempts before we stop asking
|
||||
|
||||
export class PageStrip {
|
||||
constructor({ container, viewer, pdfjsLib, pdf, model, eventBus, layerProperties }) {
|
||||
this.container = container; // the scrolling element
|
||||
this.viewer = viewer; // #viewer, holds the .page divs
|
||||
this.lib = pdfjsLib;
|
||||
this.pdf = pdf;
|
||||
this.model = model;
|
||||
this.eventBus = eventBus;
|
||||
this.layerProperties = layerProperties;
|
||||
this.views = new Map(); // uid -> {pv, overlay, textDiv, textContent, stale}
|
||||
this.scaleMode = "page-width"; // "page-width" | "page-fit" | number
|
||||
this.scale = 1;
|
||||
this.current = null; // uid of the page nearest the top of the view
|
||||
this._onCurrent = [];
|
||||
this._io = null;
|
||||
this._visIo = null;
|
||||
// A hidden tab produces no frames: requestAnimationFrame never runs, the
|
||||
// IntersectionObserver never reports, and pdf.js's own render never
|
||||
// publishes its canvas. Nothing can be done about that while hidden — and
|
||||
// nothing needs to be, since nobody is looking — but the moment the tab
|
||||
// comes back we have to re-measure and rasterise, or it stays blank.
|
||||
this._onVisibility = () => {
|
||||
if (document.hidden) return;
|
||||
this._markVisibleByGeometry();
|
||||
this._pump();
|
||||
};
|
||||
document.addEventListener("visibilitychange", this._onVisibility);
|
||||
}
|
||||
|
||||
onCurrentChange(fn) { this._onCurrent.push(fn); }
|
||||
|
||||
// ---- construction -------------------------------------------------
|
||||
async build() {
|
||||
const { PDFPageView } = this.lib.viewer;
|
||||
for (const uid of this.model.order) {
|
||||
const p = this.model.page(uid);
|
||||
const pdfPage = await this.pdf.getPage(p.src + 1);
|
||||
const defaultViewport = pdfPage.getViewport({ scale: 1 });
|
||||
const pv = new PDFPageView({
|
||||
container: null, // we place the div ourselves
|
||||
id: p.src + 1,
|
||||
scale: 1,
|
||||
defaultViewport,
|
||||
eventBus: this.eventBus,
|
||||
layerProperties: this.layerProperties,
|
||||
// TextLayerMode is internal to the components bundle and not exported,
|
||||
// hence the literal; 0 is DISABLE. We supply our own text layer, for
|
||||
// the find-bar reason at the top of this file.
|
||||
textLayerMode: 0,
|
||||
annotationMode: this.lib.AnnotationMode.ENABLE_FORMS,
|
||||
imageResourcesPath: "vendor/pdfjs/web/images/",
|
||||
enableAutoLinking: false,
|
||||
});
|
||||
pv.setPdfPage(pdfPage);
|
||||
const entry = { pv, pdfPage, overlay: null, textDiv: null, textContent: null,
|
||||
drawn: false, drawing: false, visible: false };
|
||||
this.views.set(uid, entry);
|
||||
}
|
||||
this.computeScale();
|
||||
this.layout();
|
||||
// Start at the top. Without this the container keeps whatever scroll
|
||||
// offset the previous document left behind — and because the observer
|
||||
// only rasterises what is near the viewport, a short document opened
|
||||
// after a long one can land entirely out of view and never draw at all.
|
||||
// Page divs also change height as they take their real dimensions, and
|
||||
// Chromium's scroll anchoring happily follows that, so reset again once
|
||||
// the layout has settled.
|
||||
this.container.scrollTop = 0;
|
||||
this._observe();
|
||||
this._trackCurrent();
|
||||
this._markVisibleByGeometry();
|
||||
this._pump();
|
||||
// Page divs settle into their real heights a frame later; re-measure then
|
||||
// so the second screenful is queued without waiting for a scroll.
|
||||
requestAnimationFrame(() => {
|
||||
this.container.scrollTop = 0;
|
||||
this._markVisibleByGeometry();
|
||||
this._pump();
|
||||
});
|
||||
}
|
||||
|
||||
/** Viewport for a page at the current scale and rotation. */
|
||||
viewportOf(uid) {
|
||||
const e = this.views.get(uid);
|
||||
if (!e) return null;
|
||||
return e.pdfPage.getViewport({
|
||||
scale: this.scale,
|
||||
rotation: this.model.displayRotation(uid),
|
||||
});
|
||||
}
|
||||
|
||||
// ---- scale --------------------------------------------------------
|
||||
computeScale() {
|
||||
const uid = this.current || this.model.visible()[0];
|
||||
if (!uid) { this.scale = 1; return; }
|
||||
const e = this.views.get(uid);
|
||||
const base = e.pdfPage.getViewport({ scale: 1, rotation: this.model.displayRotation(uid) });
|
||||
if (typeof this.scaleMode === "number") { this.scale = this.scaleMode; return; }
|
||||
const availW = Math.max(120, this.container.clientWidth - GUTTER * 2);
|
||||
if (this.scaleMode === "page-width") {
|
||||
this.scale = availW / base.width;
|
||||
} else {
|
||||
const availH = Math.max(120, this.container.clientHeight - GUTTER);
|
||||
this.scale = Math.min(availW / base.width, availH / base.height);
|
||||
}
|
||||
// pdf.js clamps its own viewer to this band; going outside it produces
|
||||
// canvases the compositor refuses or a page too small to read.
|
||||
this.scale = Math.min(10, Math.max(0.1, this.scale));
|
||||
}
|
||||
|
||||
setScaleMode(mode) {
|
||||
this.scaleMode = mode;
|
||||
this.computeScale();
|
||||
this.layout();
|
||||
}
|
||||
/** Step the numeric zoom, switching out of a fit mode if we are in one. */
|
||||
stepScale(dir) {
|
||||
const steps = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 3, 4, 6];
|
||||
const at = this.scale;
|
||||
const next = dir > 0 ? steps.find((s) => s > at + 0.001) : [...steps].reverse().find((s) => s < at - 0.001);
|
||||
if (next) this.setScaleMode(next);
|
||||
}
|
||||
|
||||
// ---- layout -------------------------------------------------------
|
||||
/** Re-place every page div in display order and re-size all layers. */
|
||||
layout() {
|
||||
this.viewer.style.setProperty("--scale-factor", String(this.scale));
|
||||
const visible = this.model.visible();
|
||||
// Detach deleted pages, then append in order. appendChild on an element
|
||||
// already in the tree moves it, so this is also the reorder path.
|
||||
for (const [uid, e] of this.views) {
|
||||
if (!visible.includes(uid)) e.pv.div.remove();
|
||||
}
|
||||
visible.forEach((uid, i) => {
|
||||
const e = this.views.get(uid);
|
||||
this.viewer.append(e.pv.div);
|
||||
e.pv.updatePageNumber(i + 1);
|
||||
e.pv.div.dataset.uid = uid;
|
||||
});
|
||||
for (const uid of visible) this.applyPageGeometry(uid);
|
||||
this._observe();
|
||||
this._markVisibleByGeometry();
|
||||
this._pump();
|
||||
}
|
||||
|
||||
/** Push the current scale + rotation into one page's view and its layers. */
|
||||
applyPageGeometry(uid) {
|
||||
const e = this.views.get(uid);
|
||||
if (!e) return;
|
||||
const rotation = this.model.extraRotation(uid);
|
||||
// A window resize recomputes a fit scale and lands on a value a hair off
|
||||
// the old one. Re-rendering every page for a difference nobody can see
|
||||
// would also cancel whatever was mid-render, so ignore the noise.
|
||||
const scaleChanged = Math.abs(e.pv.scale - this.scale) > 0.0005;
|
||||
if (scaleChanged || e.pv.rotation !== rotation) {
|
||||
e.pv.update({ scale: this.scale, rotation });
|
||||
e.drawn = false; // update() resets the canvas
|
||||
// update() wipes the div's children, ours included.
|
||||
if (e.textDiv) { e.textDiv.remove(); e.textDiv = null; }
|
||||
if (e.overlay) { e.overlay.destroy(); e.overlay = null; }
|
||||
}
|
||||
this.ensureOverlay(uid);
|
||||
this.refreshOverlay(uid);
|
||||
if (!e.textDiv) this.buildTextLayer(uid);
|
||||
}
|
||||
|
||||
ensureOverlay(uid) {
|
||||
const e = this.views.get(uid);
|
||||
if (!e) return null;
|
||||
if (!e.overlay) e.overlay = new Overlay(e.pv.div);
|
||||
return e.overlay;
|
||||
}
|
||||
refreshOverlay(uid, selectedId = this._selectedId) {
|
||||
const e = this.views.get(uid);
|
||||
if (!e || !e.overlay) return;
|
||||
const viewport = this.viewportOf(uid);
|
||||
if (!viewport) return;
|
||||
this._selectedId = selectedId;
|
||||
let marks = this.model.annotsFor(uid);
|
||||
// The in-progress drag is drawn from the same code as a committed mark, so
|
||||
// what the user sees mid-drag is exactly what lands when they release.
|
||||
if (this._preview && this._preview.uid === uid && this._preview.annot) {
|
||||
marks = marks.concat([{ id: "__preview", ...this._preview.annot }]);
|
||||
}
|
||||
e.overlay.render(viewport, marks, selectedId);
|
||||
}
|
||||
|
||||
/** Show a not-yet-committed mark on one page. Pass (null, null) to clear. */
|
||||
setPreview(uid, annot) {
|
||||
const prev = this._preview?.uid;
|
||||
this._preview = uid ? { uid, annot } : null;
|
||||
if (prev && prev !== uid) this.refreshOverlay(prev);
|
||||
if (uid) this.refreshOverlay(uid);
|
||||
}
|
||||
refreshAllOverlays(selectedId) {
|
||||
this._selectedId = selectedId;
|
||||
for (const uid of this.model.visible()) this.refreshOverlay(uid, selectedId);
|
||||
}
|
||||
overlayFor(uid) { return this.views.get(uid)?.overlay || null; }
|
||||
|
||||
// ---- text layer ---------------------------------------------------
|
||||
// Built from cached text content so a re-layout is DOM work only — no
|
||||
// second trip to the worker. Kept attached for every page regardless of
|
||||
// whether its canvas is currently alive.
|
||||
async buildTextLayer(uid) {
|
||||
const e = this.views.get(uid);
|
||||
if (!e || e.textDiv) return;
|
||||
const div = document.createElement("div");
|
||||
div.className = "textLayer";
|
||||
e.textDiv = div;
|
||||
e.pv.div.append(div);
|
||||
const viewport = this.viewportOf(uid);
|
||||
this.lib.setLayerDimensions(div, viewport);
|
||||
try {
|
||||
if (!e.textContent) e.textContent = await e.pdfPage.getTextContent();
|
||||
const tl = new this.lib.TextLayer({ textContentSource: e.textContent, container: div, viewport });
|
||||
await tl.render();
|
||||
} catch (err) {
|
||||
// A page with no extractable text is normal (a scan). Anything else is
|
||||
// worth a line in the console but must not stop the page rendering.
|
||||
if (err?.name !== "AbortException") console.warn(`text layer for ${uid}:`, err?.message || err);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- canvas virtualisation ----------------------------------------
|
||||
//
|
||||
// The observer only records what the user can see; the pump decides what to
|
||||
// rasterise. Keeping those apart is what makes this survive a relayout: a
|
||||
// render cancelled because the scale changed mid-flight simply leaves the
|
||||
// page wanted-but-not-drawn, and the next pump picks it up. An earlier
|
||||
// version drew straight from the observer callback, and a resize during
|
||||
// startup — Theseus restoring its window bounds — could cancel the first
|
||||
// render of every page, after which nothing ever asked again and the user
|
||||
// sat looking at blank pages.
|
||||
_observe() {
|
||||
this._io?.disconnect();
|
||||
this._io = new IntersectionObserver((entries) => {
|
||||
for (const ent of entries) {
|
||||
const uid = ent.target.dataset.uid;
|
||||
const e = uid && this.views.get(uid);
|
||||
if (!e) continue;
|
||||
e.visible = ent.isIntersecting;
|
||||
}
|
||||
this._pump();
|
||||
}, { root: this.container, rootMargin: DRAW_MARGIN });
|
||||
for (const uid of this.model.visible()) {
|
||||
const e = this.views.get(uid);
|
||||
if (e) this._io.observe(e.pv.div);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Work out what is on screen by measuring, instead of waiting to be told.
|
||||
*
|
||||
* An IntersectionObserver only reports from the browser's rendering step, so
|
||||
* a tab that has not produced a frame yet never gets its first callback —
|
||||
* and an editor whose whole render schedule hangs off that observer then
|
||||
* shows a document of blank pages until something else forces a paint. That
|
||||
* is not hypothetical: it was intermittent on every cold open of a tab. The
|
||||
* observer stays, because it is the efficient way to follow scrolling, but
|
||||
* the first paint no longer depends on it.
|
||||
*/
|
||||
_markVisibleByGeometry() {
|
||||
const root = this.container.getBoundingClientRect();
|
||||
if (!root.height) return;
|
||||
const margin = root.height * 1.8; // matches DRAW_MARGIN
|
||||
for (const uid of this.model.visible()) {
|
||||
const e = this.views.get(uid);
|
||||
if (!e) continue;
|
||||
const r = e.pv.div.getBoundingClientRect();
|
||||
e.visible = r.bottom >= root.top - margin && r.top <= root.bottom + margin;
|
||||
}
|
||||
}
|
||||
|
||||
/** Bring every page's canvas into line with whether it is currently wanted. */
|
||||
_pump() {
|
||||
if (this._dead) return;
|
||||
if (this._pumping) { this._pumpAgain = true; return; }
|
||||
this._pumping = true;
|
||||
queueMicrotask(async () => {
|
||||
try {
|
||||
let passes = 0;
|
||||
this._pumpAgain = false;
|
||||
do {
|
||||
for (const uid of this.model.visible()) {
|
||||
if (this._dead) return;
|
||||
const e = this.views.get(uid);
|
||||
if (!e) continue;
|
||||
if (e.visible && !e.drawn) await this._draw(uid);
|
||||
else if (!e.visible && e.drawn) this._undraw(uid);
|
||||
}
|
||||
if (!this._pumpAgain) break;
|
||||
this._pumpAgain = false;
|
||||
// Hand the frame back between passes. A retry that fails the same
|
||||
// way every time would otherwise spin as fast as pdf.js can reject,
|
||||
// freezing the tab instead of just failing one page. setTimeout, not
|
||||
// rAF: a tab that is not painting never runs a rAF callback, which is
|
||||
// exactly the situation a retry may be recovering from.
|
||||
if (++passes < MAX_PUMP_PASSES) {
|
||||
await new Promise((r) => setTimeout(r, 16));
|
||||
this._pumpAgain = true;
|
||||
} else {
|
||||
console.warn("page rendering gave up after", passes, "passes");
|
||||
break;
|
||||
}
|
||||
} while (this._pumpAgain);
|
||||
} finally { this._pumping = false; }
|
||||
});
|
||||
}
|
||||
|
||||
async _draw(uid) {
|
||||
const e = this.views.get(uid);
|
||||
if (!e || e.drawn || e.drawing) return;
|
||||
e.drawing = true;
|
||||
try {
|
||||
await e.pv.draw();
|
||||
e.drawn = true;
|
||||
e.retries = 0;
|
||||
} catch (err) {
|
||||
// A cancelled render is routine — the scale or rotation moved under it.
|
||||
// Leave the page undrawn and let the next pump have another go, but
|
||||
// give up eventually so one unrenderable page cannot hold the pump open.
|
||||
if (err?.name !== "RenderingCancelledException") console.warn(`draw ${uid}:`, err?.message || err);
|
||||
if ((e.retries = (e.retries || 0) + 1) <= MAX_DRAW_RETRIES) this._pumpAgain = true;
|
||||
return;
|
||||
} finally { e.drawing = false; }
|
||||
// draw() appends the canvas wrapper and the annotation layer; ours have to
|
||||
// end up above them, and update()/reset() may have removed them entirely.
|
||||
if (this._dead || !this.views.has(uid)) return;
|
||||
if (!e.textDiv) await this.buildTextLayer(uid);
|
||||
else e.pv.div.append(e.textDiv);
|
||||
if (this._dead || !this.views.has(uid)) return;
|
||||
this.ensureOverlay(uid);
|
||||
e.pv.div.append(e.overlay.svg);
|
||||
this.refreshOverlay(uid);
|
||||
}
|
||||
|
||||
_undraw(uid) {
|
||||
const e = this.views.get(uid);
|
||||
if (!e || !e.drawn) return;
|
||||
e.drawn = false;
|
||||
// Drop the canvas and the form layer; keep ours. reset() removes every
|
||||
// child it was not told to keep, so re-attach after.
|
||||
e.pv.reset();
|
||||
if (e.textDiv) e.pv.div.append(e.textDiv);
|
||||
if (e.overlay) e.pv.div.append(e.overlay.svg);
|
||||
}
|
||||
|
||||
/** Force a redraw of one page — after a rotation, say. */
|
||||
async redraw(uid) {
|
||||
const e = this.views.get(uid);
|
||||
if (!e) return;
|
||||
this.applyPageGeometry(uid);
|
||||
e.drawn = false;
|
||||
this._pump();
|
||||
}
|
||||
|
||||
// ---- current page tracking ----------------------------------------
|
||||
_trackCurrent() {
|
||||
this._visIo?.disconnect();
|
||||
// A page counts as "current" once its top edge is in the upper half of
|
||||
// the viewport; that matches how every PDF reader reports a page number.
|
||||
this._visIo = new IntersectionObserver((entries) => {
|
||||
let best = null, bestTop = Infinity;
|
||||
for (const ent of entries) {
|
||||
if (!ent.isIntersecting) continue;
|
||||
const top = Math.abs(ent.boundingClientRect.top - ent.rootBounds.top);
|
||||
if (top < bestTop) { bestTop = top; best = ent.target.dataset.uid; }
|
||||
}
|
||||
if (best && best !== this.current) {
|
||||
this.current = best;
|
||||
for (const fn of this._onCurrent) { try { fn(best); } catch {} }
|
||||
}
|
||||
}, { root: this.container, threshold: [0, 0.1, 0.5] });
|
||||
for (const uid of this.model.visible()) {
|
||||
const e = this.views.get(uid);
|
||||
if (e) this._visIo.observe(e.pv.div);
|
||||
}
|
||||
}
|
||||
|
||||
scrollTo(uid) {
|
||||
const e = this.views.get(uid);
|
||||
if (e) e.pv.div.scrollIntoView({ block: "start", behavior: "smooth" });
|
||||
}
|
||||
|
||||
/** Which page div is under a client point, and where inside it. */
|
||||
pageAt(clientX, clientY) {
|
||||
for (const uid of this.model.visible()) {
|
||||
const e = this.views.get(uid);
|
||||
if (!e) continue;
|
||||
const r = e.pv.div.getBoundingClientRect();
|
||||
if (clientX >= r.left && clientX <= r.right && clientY >= r.top && clientY <= r.bottom) {
|
||||
return { uid, vx: clientX - r.left, vy: clientY - r.top, rect: r };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._dead = true;
|
||||
document.removeEventListener("visibilitychange", this._onVisibility);
|
||||
this._io?.disconnect();
|
||||
this._visIo?.disconnect();
|
||||
for (const e of this.views.values()) {
|
||||
try { e.overlay?.destroy(); } catch {}
|
||||
try { e.pv.destroy(); } catch {}
|
||||
}
|
||||
this.views.clear();
|
||||
this.viewer.textContent = "";
|
||||
}
|
||||
}
|
||||
21
bundled-addons/pdf-editor/vendor/pdf-lib/LICENSE.md
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 Andrew Dillon
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
16
bundled-addons/pdf-editor/vendor/pdf-lib/pdf-lib.esm.min.js
vendored
Normal file
177
bundled-addons/pdf-editor/vendor/pdfjs/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
BIN
bundled-addons/pdf-editor/vendor/pdfjs/iccs/CGATS001Compat-v2-micro.icc
vendored
Normal file
116
bundled-addons/pdf-editor/vendor/pdfjs/iccs/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
CC0 1.0 Universal
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator and
|
||||
subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for the
|
||||
purpose of contributing to a commons of creative, cultural and scientific
|
||||
works ("Commons") that the public can reliably and without fear of later
|
||||
claims of infringement build upon, modify, incorporate in other works, reuse
|
||||
and redistribute as freely as possible in any form whatsoever and for any
|
||||
purposes, including without limitation commercial purposes. These owners may
|
||||
contribute to the Commons to promote the ideal of a free culture and the
|
||||
further production of creative, cultural and scientific works, or to gain
|
||||
reputation or greater distribution for their Work in part through the use and
|
||||
efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any expectation
|
||||
of additional consideration or compensation, the person associating CC0 with a
|
||||
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
|
||||
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
|
||||
and publicly distribute the Work under its terms, with knowledge of his or her
|
||||
Copyright and Related Rights in the Work and the meaning and intended legal
|
||||
effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not limited
|
||||
to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display, communicate,
|
||||
and translate a Work;
|
||||
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
|
||||
iii. publicity and privacy rights pertaining to a person's image or likeness
|
||||
depicted in a Work;
|
||||
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data in
|
||||
a Work;
|
||||
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation thereof,
|
||||
including any amended or successor version of such directive); and
|
||||
|
||||
vii. other similar, equivalent or corresponding rights throughout the world
|
||||
based on applicable law or treaty, and any national implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention of,
|
||||
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
|
||||
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
|
||||
and Related Rights and associated claims and causes of action, whether now
|
||||
known or unknown (including existing as well as future claims and causes of
|
||||
action), in the Work (i) in all territories worldwide, (ii) for the maximum
|
||||
duration provided by applicable law or treaty (including future time
|
||||
extensions), (iii) in any current or future medium and for any number of
|
||||
copies, and (iv) for any purpose whatsoever, including without limitation
|
||||
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
|
||||
the Waiver for the benefit of each member of the public at large and to the
|
||||
detriment of Affirmer's heirs and successors, fully intending that such Waiver
|
||||
shall not be subject to revocation, rescission, cancellation, termination, or
|
||||
any other legal or equitable action to disrupt the quiet enjoyment of the Work
|
||||
by the public as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason be
|
||||
judged legally invalid or ineffective under applicable law, then the Waiver
|
||||
shall be preserved to the maximum extent permitted taking into account
|
||||
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
|
||||
is so judged Affirmer hereby grants to each affected person a royalty-free,
|
||||
non transferable, non sublicensable, non exclusive, irrevocable and
|
||||
unconditional license to exercise Affirmer's Copyright and Related Rights in
|
||||
the Work (i) in all territories worldwide, (ii) for the maximum duration
|
||||
provided by applicable law or treaty (including future time extensions), (iii)
|
||||
in any current or future medium and for any number of copies, and (iv) for any
|
||||
purpose whatsoever, including without limitation commercial, advertising or
|
||||
promotional purposes (the "License"). The License shall be deemed effective as
|
||||
of the date CC0 was applied by Affirmer to the Work. Should any part of the
|
||||
License for any reason be judged legally invalid or ineffective under
|
||||
applicable law, such partial invalidity or ineffectiveness shall not
|
||||
invalidate the remainder of the License, and in such case Affirmer hereby
|
||||
affirms that he or she will not (i) exercise any of his or her remaining
|
||||
Copyright and Related Rights in the Work or (ii) assert any associated claims
|
||||
and causes of action with respect to the Work, in either case contrary to
|
||||
Affirmer's express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
|
||||
b. Affirmer offers the Work as-is and makes no representations or warranties
|
||||
of any kind concerning the Work, express, implied, statutory or otherwise,
|
||||
including without limitation warranties of title, merchantability, fitness
|
||||
for a particular purpose, non infringement, or the absence of latent or
|
||||
other defects, accuracy, or the present or absence of errors, whether or not
|
||||
discoverable, all to the greatest extent permissible under applicable law.
|
||||
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without limitation
|
||||
any person's Copyright and Related Rights in the Work. Further, Affirmer
|
||||
disclaims responsibility for obtaining any necessary consents, permissions
|
||||
or other rights required for any use of the Work.
|
||||
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to this
|
||||
CC0 or use of the Work.
|
||||
|
||||
For more information, please see
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
29
bundled-addons/pdf-editor/vendor/pdfjs/pdf.min.mjs
vendored
Normal file
29
bundled-addons/pdf-editor/vendor/pdfjs/pdf.worker.min.mjs
vendored
Normal file
BIN
bundled-addons/pdf-editor/vendor/pdfjs/standard_fonts/FoxitDingbats.pfb
vendored
Normal file
BIN
bundled-addons/pdf-editor/vendor/pdfjs/standard_fonts/FoxitFixed.pfb
vendored
Normal file
BIN
bundled-addons/pdf-editor/vendor/pdfjs/standard_fonts/FoxitFixedBold.pfb
vendored
Normal file
BIN
bundled-addons/pdf-editor/vendor/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb
vendored
Normal file
BIN
bundled-addons/pdf-editor/vendor/pdfjs/standard_fonts/FoxitFixedItalic.pfb
vendored
Normal file
BIN
bundled-addons/pdf-editor/vendor/pdfjs/standard_fonts/FoxitSerif.pfb
vendored
Normal file
BIN
bundled-addons/pdf-editor/vendor/pdfjs/standard_fonts/FoxitSerifBold.pfb
vendored
Normal file
BIN
bundled-addons/pdf-editor/vendor/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb
vendored
Normal file
BIN
bundled-addons/pdf-editor/vendor/pdfjs/standard_fonts/FoxitSerifItalic.pfb
vendored
Normal file
BIN
bundled-addons/pdf-editor/vendor/pdfjs/standard_fonts/FoxitSymbol.pfb
vendored
Normal file
27
bundled-addons/pdf-editor/vendor/pdfjs/standard_fonts/LICENSE_FOXIT
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2014 PDFium Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
363
bundled-addons/pdf-editor/vendor/pdfjs/standard_fonts/LICENSE_LIBERATION
vendored
Normal file
|
|
@ -0,0 +1,363 @@
|
|||
LICENSE AGREEMENT AND LIMITED PRODUCT WARRANTY
|
||||
LIBERATION FONT SOFTWARE
|
||||
|
||||
This agreement governs the use of the Software and any updates to the Software, regardless of the delivery mechanism. Subject to the following terms, Red Hat, Inc. ("Red Hat") grants to the user ("Client") a license to this work pursuant to the GNU General Public License v.2 with the exceptions set forth below and such other terms as are set forth in this End User License Agreement.
|
||||
|
||||
1. The Software and License Exception. LIBERATION font software (the "Software") consists of TrueType-OpenType formatted font software for rendering LIBERATION typefaces in sans-serif, serif, and monospaced character styles. You are licensed to use, modify, copy, and distribute the Software pursuant to the GNU General Public License v.2 with the following exceptions:
|
||||
|
||||
(a) As a special exception, if you create a document which uses this font, and embed this font or unaltered portions of this font into the document, this font does not by itself cause the resulting document to be covered by the GNU General Public License. This exception does not however invalidate any other reasons why the document might be covered by the GNU General Public License. If you modify this font, you may extend this exception to your version of the font, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version.
|
||||
|
||||
(b) As a further exception, any distribution of the object code of the Software in a physical product must provide you the right to access and modify the source code for the Software and to reinstall that modified version of the Software in object code form on the same physical product on which you received it.
|
||||
|
||||
2. Intellectual Property Rights. The Software and each of its components, including the source code, documentation, appearance, structure and organization are owned by Red Hat and others and are protected under copyright and other laws. Title to the Software and any component, or to any copy, modification, or merged portion shall remain with the aforementioned, subject to the applicable license. The "LIBERATION" trademark is a trademark of Red Hat, Inc. in the U.S. and other countries. This agreement does not permit Client to distribute modified versions of the Software using Red Hat's trademarks. If Client makes a redistribution of a modified version of the Software, then Client must modify the files names to remove any reference to the Red Hat trademarks and must not use the Red Hat trademarks in any way to reference or promote the modified Software.
|
||||
|
||||
3. Limited Warranty. To the maximum extent permitted under applicable law, the Software is provided and licensed "as is" without warranty of any kind, expressed or implied, including the implied warranties of merchantability, non-infringement or fitness for a particular purpose. Red Hat does not warrant that the functions contained in the Software will meet Client's requirements or that the operation of the Software will be entirely error free or appear precisely as described in the accompanying documentation.
|
||||
|
||||
4. Limitation of Remedies and Liability. To the maximum extent permitted by applicable law, Red Hat or any Red Hat authorized dealer will not be liable to Client for any incidental or consequential damages, including lost profits or lost savings arising out of the use or inability to use the Software, even if Red Hat or such dealer has been advised of the possibility of such damages.
|
||||
|
||||
5. General. If any provision of this agreement is held to be unenforceable, that shall not affect the enforceability of the remaining provisions. This agreement shall be governed by the laws of the State of North Carolina and of the United States, without regard to any conflict of laws provisions, except that the United Nations Convention on the International Sale of Goods shall not apply.
|
||||
Copyright © 2007-2011 Red Hat, Inc. All rights reserved. LIBERATION is a trademark of Red Hat, Inc.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
The text of the GNU General Public License, version 2, referenced above:
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
BIN
bundled-addons/pdf-editor/vendor/pdfjs/standard_fonts/LiberationSans-Bold.ttf
vendored
Normal file
BIN
bundled-addons/pdf-editor/vendor/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf
vendored
Normal file
BIN
bundled-addons/pdf-editor/vendor/pdfjs/standard_fonts/LiberationSans-Italic.ttf
vendored
Normal file
BIN
bundled-addons/pdf-editor/vendor/pdfjs/standard_fonts/LiberationSans-Regular.ttf
vendored
Normal file
196
bundled-addons/pdf-editor/vendor/pdfjs/wasm/LICENSE_JBIG2
vendored
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
// Copyright 2014 The PDFium Authors
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://www.apache.org/licenses/
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
1. Definitions.
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
END OF TERMS AND CONDITIONS
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
39
bundled-addons/pdf-editor/vendor/pdfjs/wasm/LICENSE_OPENJPEG
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* The copyright in this software is being made available under the 2-clauses
|
||||
* BSD License, included below. This software may be subject to other third
|
||||
* party and contributor rights, including patent rights, and no such rights
|
||||
* are granted under this license.
|
||||
*
|
||||
* Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium
|
||||
* Copyright (c) 2002-2014, Professor Benoit Macq
|
||||
* Copyright (c) 2003-2014, Antonin Descampe
|
||||
* Copyright (c) 2003-2009, Francois-Olivier Devaux
|
||||
* Copyright (c) 2005, Herve Drolon, FreeImage Team
|
||||
* Copyright (c) 2002-2003, Yannick Verschueren
|
||||
* Copyright (c) 2001-2003, David Janssens
|
||||
* Copyright (c) 2011-2012, Centre National d'Etudes Spatiales (CNES), France
|
||||
* Copyright (c) 2012, CS Systemes d'Information, France
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS'
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
13
bundled-addons/pdf-editor/vendor/pdfjs/wasm/LICENSE_PDFJS_JBIG2
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
Copyright 2026 Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
22
bundled-addons/pdf-editor/vendor/pdfjs/wasm/LICENSE_PDFJS_OPENJPEG
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2024, Mozilla Foundation
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
19
bundled-addons/pdf-editor/vendor/pdfjs/wasm/LICENSE_PDFJS_QCMS
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
21
bundled-addons/pdf-editor/vendor/pdfjs/wasm/LICENSE_QCMS
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
qcms
|
||||
Copyright (C) 2009-2024 Mozilla Corporation
|
||||
Copyright (C) 1998-2007 Marti Maria
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
||||
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
BIN
bundled-addons/pdf-editor/vendor/pdfjs/wasm/jbig2.wasm
vendored
Normal file
BIN
bundled-addons/pdf-editor/vendor/pdfjs/wasm/openjpeg.wasm
vendored
Normal file
BIN
bundled-addons/pdf-editor/vendor/pdfjs/wasm/qcms_bg.wasm
vendored
Normal file
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/altText_add.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="12" height="13" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.375 7.625V11.875C5.375 12.0408 5.44085 12.1997 5.55806 12.3169C5.67527 12.4342 5.83424 12.5 6 12.5C6.16576 12.5 6.32473 12.4342 6.44194 12.3169C6.55915 12.1997 6.625 12.0408 6.625 11.875V7.625L7.125 7.125H11.375C11.5408 7.125 11.6997 7.05915 11.8169 6.94194C11.9342 6.82473 12 6.66576 12 6.5C12 6.33424 11.9342 6.17527 11.8169 6.05806C11.6997 5.94085 11.5408 5.875 11.375 5.875H7.125L6.625 5.375V1.125C6.625 0.95924 6.55915 0.800269 6.44194 0.683058C6.32473 0.565848 6.16576 0.5 6 0.5C5.83424 0.5 5.67527 0.565848 5.55806 0.683058C5.44085 0.800269 5.375 0.95924 5.375 1.125V5.375L4.875 5.875H0.625C0.45924 5.875 0.300269 5.94085 0.183058 6.05806C0.065848 6.17527 0 6.33424 0 6.5C0 6.66576 0.065848 6.82473 0.183058 6.94194C0.300269 7.05915 0.45924 7.125 0.625 7.125H4.762L5.375 7.625Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 920 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/altText_disclaimer.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.49073 1.3015L3.30873 2.1505C3.29349 2.22246 3.25769 2.28844 3.20568 2.34045C3.15368 2.39246 3.08769 2.42826 3.01573 2.4435L2.16673 2.6255C1.76473 2.7125 1.76473 3.2865 2.16673 3.3725L3.01573 3.5555C3.08769 3.57074 3.15368 3.60654 3.20568 3.65855C3.25769 3.71056 3.29349 3.77654 3.30873 3.8485L3.49073 4.6975C3.57773 5.0995 4.15173 5.0995 4.23773 4.6975L4.42073 3.8485C4.43598 3.77654 4.47177 3.71056 4.52378 3.65855C4.57579 3.60654 4.64178 3.57074 4.71373 3.5555L5.56173 3.3725C5.96373 3.2855 5.96373 2.7115 5.56173 2.6255L4.71273 2.4435C4.64083 2.42814 4.57491 2.3923 4.52292 2.34031C4.47093 2.28832 4.43509 2.2224 4.41973 2.1505L4.23773 1.3015C4.15073 0.8995 3.57673 0.8995 3.49073 1.3015ZM10.8647 13.9995C10.4853 14.0056 10.1158 13.8782 9.82067 13.6397C9.52553 13.4013 9.32347 13.0667 9.24973 12.6945L8.89273 11.0275C8.83676 10.7687 8.70738 10.5316 8.52009 10.3445C8.3328 10.1574 8.09554 10.0282 7.83673 9.9725L6.16973 9.6155C5.38873 9.4465 4.86473 8.7975 4.86473 7.9995C4.86473 7.2015 5.38873 6.5525 6.16973 6.3845L7.83673 6.0275C8.09551 5.97135 8.33267 5.84193 8.51992 5.65468C8.70716 5.46744 8.83658 5.23028 8.89273 4.9715L9.25073 3.3045C9.41773 2.5235 10.0667 1.9995 10.8647 1.9995C11.6627 1.9995 12.3117 2.5235 12.4797 3.3045L12.8367 4.9715C12.9507 5.4995 13.3647 5.9135 13.8927 6.0265L15.5597 6.3835C16.3407 6.5525 16.8647 7.2015 16.8647 7.9995C16.8647 8.7975 16.3407 9.4465 15.5597 9.6145L13.8927 9.9715C13.6337 10.0275 13.3963 10.157 13.209 10.3445C13.0217 10.5319 12.8925 10.7694 12.8367 11.0285L12.4787 12.6945C12.4054 13.0667 12.2036 13.4014 11.9086 13.6399C11.6135 13.8784 11.2441 14.0057 10.8647 13.9995ZM10.8647 3.2495C10.7667 3.2495 10.5337 3.2795 10.4727 3.5655L10.1147 5.2335C10.0081 5.72777 9.76116 6.18082 9.40361 6.53837C9.04606 6.89593 8.59301 7.14283 8.09873 7.2495L6.43173 7.6065C6.14573 7.6685 6.11473 7.9015 6.11473 7.9995C6.11473 8.0975 6.14573 8.3305 6.43173 8.3925L8.09873 8.7495C8.59301 8.85617 9.04606 9.10307 9.40361 9.46062C9.76116 9.81817 10.0081 10.2712 10.1147 10.7655L10.4727 12.4335C10.5337 12.7195 10.7667 12.7495 10.8647 12.7495C10.9627 12.7495 11.1957 12.7195 11.2567 12.4335L11.6147 10.7665C11.7212 10.272 11.9681 9.81878 12.3256 9.46103C12.6832 9.10329 13.1363 8.85624 13.6307 8.7495L15.2977 8.3925C15.5837 8.3305 15.6147 8.0975 15.6147 7.9995C15.6147 7.9015 15.5837 7.6685 15.2977 7.6065L13.6307 7.2495C13.1365 7.14283 12.6834 6.89593 12.3259 6.53837C11.9683 6.18082 11.7214 5.72777 11.6147 5.2335L11.2567 3.5655C11.1957 3.2795 10.9627 3.2495 10.8647 3.2495ZM3.30873 12.1505L3.49073 11.3015C3.57673 10.8995 4.15073 10.8995 4.23773 11.3015L4.41973 12.1505C4.43509 12.2224 4.47093 12.2883 4.52292 12.3403C4.57491 12.3923 4.64083 12.4281 4.71273 12.4435L5.56173 12.6255C5.96373 12.7115 5.96373 13.2855 5.56173 13.3725L4.71273 13.5545C4.64083 13.5699 4.57491 13.6057 4.52292 13.6577C4.47093 13.7097 4.43509 13.7756 4.41973 13.8475L4.23773 14.6965C4.15173 15.0985 3.57773 15.0985 3.49073 14.6965L3.30873 13.8475C3.29337 13.7756 3.25754 13.7097 3.20555 13.6577C3.15356 13.6057 3.08764 13.5699 3.01573 13.5545L2.16673 13.3725C1.76473 13.2865 1.76473 12.7125 2.16673 12.6255L3.01573 12.4435C3.08769 12.4283 3.15368 12.3925 3.20568 12.3405C3.25769 12.2884 3.29349 12.2225 3.30873 12.1505Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/altText_done.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="12" height="13" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 0.5C5.21207 0.5 4.43185 0.655195 3.7039 0.956723C2.97595 1.25825 2.31451 1.70021 1.75736 2.25736C1.20021 2.81451 0.758251 3.47595 0.456723 4.2039C0.155195 4.93185 0 5.71207 0 6.5C0 7.28793 0.155195 8.06815 0.456723 8.7961C0.758251 9.52405 1.20021 10.1855 1.75736 10.7426C2.31451 11.2998 2.97595 11.7417 3.7039 12.0433C4.43185 12.3448 5.21207 12.5 6 12.5C7.5913 12.5 9.11742 11.8679 10.2426 10.7426C11.3679 9.61742 12 8.0913 12 6.5C12 4.9087 11.3679 3.38258 10.2426 2.25736C9.11742 1.13214 7.5913 0.5 6 0.5ZM5.06 8.9L2.9464 6.7856C2.85273 6.69171 2.80018 6.56446 2.80033 6.43183C2.80048 6.29921 2.85331 6.17207 2.9472 6.0784C3.04109 5.98473 3.16834 5.93218 3.30097 5.93233C3.43359 5.93248 3.56073 5.98531 3.6544 6.0792L5.3112 7.7368L8.3464 4.7008C8.44109 4.6109 8.56715 4.56153 8.69771 4.56322C8.82827 4.56492 8.95301 4.61754 9.04534 4.70986C9.13766 4.80219 9.19028 4.92693 9.19198 5.05749C9.19367 5.18805 9.1443 5.31411 9.0544 5.4088L5.5624 8.9H5.06Z" fill="#FBFBFE"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
30
bundled-addons/pdf-editor/vendor/pdfjs/web/images/altText_spinner.svg
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
|
||||
<style>
|
||||
@media not (prefers-reduced-motion) {
|
||||
@keyframes loadingRotate {
|
||||
from { rotate: 0; } to { rotate: 360deg }
|
||||
}
|
||||
#circle-arrows {
|
||||
animation: loadingRotate 1.8s linear infinite;
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
#hourglass {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
#circle-arrows {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<path id="circle-arrows" d="M9 5.528c0 .42.508.63.804.333l2.528-2.528a.47.47 0 0 0 0-.666L9.805.14A.471.471 0 0 0 9 .472v1.866A5.756 5.756 0 0 0 2.25 8c0 .942.232 1.83.635 2.615l1.143-1.143A4.208 4.208 0 0 1 3.75 8 4.254 4.254 0 0 1 8 3.75c.345 0 .68.042 1 .122v1.656zM7 10.472v1.656c.32.08.655.122 1 .122A4.254 4.254 0 0 0 12.25 8c0-.52-.107-1.013-.279-1.474l1.143-1.143c.404.786.636 1.674.636 2.617A5.756 5.756 0 0 1 7 13.662v1.866a.47.47 0 0 1-.804.333l-2.528-2.528a.47.47 0 0 1 0-.666l2.528-2.528a.47.47 0 0 1 .804.333z"/>
|
||||
<g id="hourglass">
|
||||
<path d="M13,1 C13.5522847,1 14,1.44771525 14,2 C14,2.55228475 13.5522847,3 13,3 L12.9854217,2.99990801 C12.9950817,3.16495885 13,3.33173274 13,3.5 C13,5.24679885 10.9877318,6.01090495 10.9877318,8.0017538 C10.9877318,9.99260264 13,10.7536922 13,12.5 C13,12.6686079 12.9950617,12.8357163 12.985363,13.0010943 L13,13 C13.5522847,13 14,13.4477153 14,14 C14,14.5522847 13.5522847,15 13,15 L3,15 C2.44771525,15 2,14.5522847 2,14 C2,13.4477153 2.44771525,13 3,13 L3.01463704,13.0010943 C3.00493827,12.8357163 3,12.6686079 3,12.5 C3,10.7536922 4.9877318,9.99260264 5,8.0017538 C5.0122682,6.01090495 3,5.24679885 3,3.5 C3,3.33173274 3.00491834,3.16495885 3.01457832,2.99990801 L3,3 C2.44771525,3 2,2.55228475 2,2 C2,1.44771525 2.44771525,1 3,1 L13,1 Z M10.987,3 L5.012,3 L5.00308914,3.24815712 C5.00103707,3.33163368 5,3.4155948 5,3.5 C5,5.36125069 6.99153646,6.01774089 6.99153646,8.0017538 C6.99153646,9.98576671 5,10.6393737 5,12.5 L5.00307746,12.7513676 L5.01222201,12.9998392 L5.60191711,12.9988344 L6.0425138,12.2959826 C7.02362731,10.7653275 7.67612271,10 8,10 C8.37014547,10 9.16950644,10.9996115 10.3980829,12.9988344 L10.987778,12.9998392 C10.9958674,12.8352104 11,12.66849 11,12.5 C11,10.6393737 8.98689779,10.0147381 8.98689779,8.0017538 C8.98689779,5.98876953 11,5.36125069 11,3.5 L10.9969109,3.24815712 L10.987,3 Z"/>
|
||||
<path d="M6,4 L10,4 C8.95166016,6 8.28499349,7 8,7 C7.71500651,7 7.04833984,6 6,4 Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/altText_warning.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.78182 2.63903C8.58882 2.28803 8.25782 2.25003 8.12482 2.25003C7.99019 2.24847 7.85771 2.28393 7.74185 2.35253C7.62599 2.42113 7.5312 2.52023 7.46782 2.63903L1.97082 12.639C1.90673 12.7528 1.87406 12.8816 1.87617 13.0122C1.87828 13.1427 1.91509 13.2704 1.98282 13.382C2.04798 13.4951 2.14207 13.5888 2.25543 13.6535C2.36879 13.7182 2.49732 13.7515 2.62782 13.75H13.6218C13.7523 13.7515 13.8809 13.7182 13.9942 13.6535C14.1076 13.5888 14.2017 13.4951 14.2668 13.382C14.3346 13.2704 14.3714 13.1427 14.3735 13.0122C14.3756 12.8816 14.3429 12.7528 14.2788 12.639L8.78182 2.63903ZM6.37282 2.03703C6.75182 1.34603 7.43882 1.00003 8.12482 1.00003C8.48341 0.997985 8.83583 1.09326 9.14454 1.2757C9.45325 1.45814 9.70668 1.72092 9.87782 2.03603L15.3748 12.036C16.1078 13.369 15.1438 15 13.6228 15H2.62782C1.10682 15 0.141823 13.37 0.875823 12.037L6.37282 2.03703ZM8.74982 9.06203C8.74982 9.22779 8.68397 9.38676 8.56676 9.50397C8.44955 9.62118 8.29058 9.68703 8.12482 9.68703C7.95906 9.68703 7.80009 9.62118 7.68288 9.50397C7.56566 9.38676 7.49982 9.22779 7.49982 9.06203V5.62503C7.49982 5.45927 7.56566 5.3003 7.68288 5.18309C7.80009 5.06588 7.95906 5.00003 8.12482 5.00003C8.29058 5.00003 8.44955 5.06588 8.56676 5.18309C8.68397 5.3003 8.74982 5.45927 8.74982 5.62503V9.06203ZM7.74982 12L7.49982 11.75V11L7.74982 10.75H8.49982L8.74982 11V11.75L8.49982 12H7.74982Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
11
bundled-addons/pdf-editor/vendor/pdfjs/web/images/annotation-check.svg
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40">
|
||||
<path
|
||||
d="M 1.5006714,23.536225 6.8925879,18.994244 14.585721,26.037937 34.019683,4.5410479 38.499329,9.2235032 14.585721,35.458952 z"
|
||||
id="path4"
|
||||
style="fill:#ffff00;fill-opacity:1;stroke:#000000;stroke-width:1.25402856;stroke-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 415 B |
16
bundled-addons/pdf-editor/vendor/pdfjs/web/images/annotation-comment.svg
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="40"
|
||||
width="40"
|
||||
viewBox="0 0 40 40">
|
||||
<rect
|
||||
style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
width="33.76017"
|
||||
height="33.76017"
|
||||
x="3.119915"
|
||||
y="3.119915" />
|
||||
<path
|
||||
d="m 20.677967,8.54499 c -7.342801,0 -13.295293,4.954293 -13.295293,11.065751 0,2.088793 0.3647173,3.484376 1.575539,5.150563 L 6.0267418,31.45501 13.560595,29.011117 c 2.221262,1.387962 4.125932,1.665377 7.117372,1.665377 7.3428,0 13.295291,-4.954295 13.295291,-11.065753 0,-6.111458 -5.952491,-11.065751 -13.295291,-11.065751 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.93031836;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 883 B |
26
bundled-addons/pdf-editor/vendor/pdfjs/web/images/annotation-help.svg
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40">
|
||||
<g
|
||||
transform="translate(0,-60)"
|
||||
id="layer1">
|
||||
<rect
|
||||
width="36.460953"
|
||||
height="34.805603"
|
||||
x="1.7695236"
|
||||
y="62.597198"
|
||||
style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.30826771;stroke-opacity:1" />
|
||||
<g
|
||||
transform="matrix(0.88763677,0,0,0.88763677,2.2472646,8.9890584)">
|
||||
<path
|
||||
d="M 20,64.526342 C 11.454135,64.526342 4.5263421,71.454135 4.5263421,80 4.5263421,88.545865 11.454135,95.473658 20,95.473658 28.545865,95.473658 35.473658,88.545865 35.473658,80 35.473658,71.454135 28.545865,64.526342 20,64.526342 z m -0.408738,9.488564 c 3.527079,0 6.393832,2.84061 6.393832,6.335441 0,3.494831 -2.866753,6.335441 -6.393832,6.335441 -3.527079,0 -6.393832,-2.84061 -6.393832,-6.335441 0,-3.494831 2.866753,-6.335441 6.393832,-6.335441 z"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.02768445;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
d="m 7.2335209,71.819938 4.9702591,4.161823 c -1.679956,2.581606 -1.443939,6.069592 0.159325,8.677725 l -5.1263071,3.424463 c 0.67516,1.231452 3.0166401,3.547686 4.2331971,4.194757 l 3.907728,-4.567277 c 2.541952,1.45975 5.730694,1.392161 8.438683,-0.12614 l 3.469517,6.108336 c 1.129779,-0.44367 4.742234,-3.449633 5.416358,-5.003859 l -5.46204,-4.415541 c 1.44319,-2.424098 1.651175,-5.267515 0.557303,-7.748623 l 5.903195,-3.833951 C 33.14257,71.704996 30.616217,69.018606 29.02952,67.99296 l -4.118813,4.981678 C 22.411934,71.205099 18.900853,70.937534 16.041319,72.32916 l -3.595408,-5.322091 c -1.345962,0.579488 -4.1293881,2.921233 -5.2123901,4.812869 z m 8.1010311,3.426672 c 2.75284,-2.446266 6.769149,-2.144694 9.048998,0.420874 2.279848,2.56557 2.113919,6.596919 -0.638924,9.043185 -2.752841,2.446267 -6.775754,2.13726 -9.055604,-0.428308 -2.279851,-2.565568 -2.107313,-6.589485 0.64553,-9.035751 z"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
10
bundled-addons/pdf-editor/vendor/pdfjs/web/images/annotation-insert.svg
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="64"
|
||||
height="64"
|
||||
viewBox="0 0 64 64">
|
||||
<path
|
||||
d="M 32.003143,1.4044602 57.432701,62.632577 6.5672991,62.627924 z"
|
||||
style="fill:#ffff00;fill-opacity:0.94117647;fill-rule:nonzero;stroke:#000000;stroke-width:1.00493038;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 408 B |
11
bundled-addons/pdf-editor/vendor/pdfjs/web/images/annotation-key.svg
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="64"
|
||||
height="64"
|
||||
viewBox="0 0 64 64">
|
||||
<path
|
||||
d="M 25.470843,9.4933766 C 25.30219,12.141818 30.139101,14.445969 34.704831,13.529144 40.62635,12.541995 41.398833,7.3856498 35.97505,5.777863 31.400921,4.1549155 25.157674,6.5445892 25.470843,9.4933766 z M 4.5246282,17.652051 C 4.068249,11.832873 9.2742983,5.9270407 18.437379,3.0977088 29.751911,-0.87185184 45.495663,1.4008022 53.603953,7.1104009 c 9.275765,6.1889221 7.158128,16.2079421 -3.171076,21.5939521 -1.784316,1.635815 -6.380222,1.21421 -7.068351,3.186186 -1.04003,0.972427 -1.288046,2.050158 -1.232864,3.168203 1.015111,2.000108 -3.831548,1.633216 -3.270553,3.759574 0.589477,5.264544 -0.179276,10.53738 -0.362842,15.806257 -0.492006,2.184998 1.163456,4.574232 -0.734888,6.610642 -2.482919,2.325184 -7.30604,2.189143 -9.193497,-0.274767 -2.733688,-1.740626 -8.254447,-3.615254 -6.104247,-6.339626 3.468112,-1.708686 -2.116197,-3.449897 0.431242,-5.080274 5.058402,-1.39256 -2.393215,-2.304318 -0.146889,-4.334645 3.069198,-0.977415 2.056986,-2.518352 -0.219121,-3.540397 1.876567,-1.807151 1.484149,-4.868919 -2.565455,-5.942205 0.150866,-1.805474 2.905737,-4.136876 -1.679967,-5.20493 C 10.260902,27.882167 4.6872697,22.95045 4.5245945,17.652051 z"
|
||||
id="path604"
|
||||
style="fill:#ffff00;fill-opacity:1;stroke:#000000;stroke-width:1.72665179;stroke-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
11
bundled-addons/pdf-editor/vendor/pdfjs/web/images/annotation-newparagraph.svg
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="64"
|
||||
height="64"
|
||||
viewBox="0 0 64 64">
|
||||
<path
|
||||
d="M 32.003143,10.913072 57.432701,53.086929 6.567299,53.083723 z"
|
||||
id="path2985"
|
||||
style="fill:#ffff00;fill-opacity:0.94117647;fill-rule:nonzero;stroke:#000000;stroke-width:0.83403099;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 426 B |
7
bundled-addons/pdf-editor/vendor/pdfjs/web/images/annotation-noicon.svg
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40">
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 158 B |
42
bundled-addons/pdf-editor/vendor/pdfjs/web/images/annotation-note.svg
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40">
|
||||
<rect
|
||||
width="36.075428"
|
||||
height="31.096582"
|
||||
x="1.962286"
|
||||
y="4.4517088"
|
||||
id="rect4"
|
||||
style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.23004246;stroke-opacity:1" />
|
||||
<rect
|
||||
width="27.96859"
|
||||
height="1.5012145"
|
||||
x="6.0157046"
|
||||
y="10.285"
|
||||
id="rect6"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none" />
|
||||
<rect
|
||||
width="27.96859"
|
||||
height="0.85783684"
|
||||
x="6.0157056"
|
||||
y="23.21689"
|
||||
id="rect8"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none" />
|
||||
<rect
|
||||
width="27.96859"
|
||||
height="0.85783684"
|
||||
x="5.8130345"
|
||||
y="28.964394"
|
||||
id="rect10"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none" />
|
||||
<rect
|
||||
width="27.96859"
|
||||
height="0.85783684"
|
||||
x="6.0157046"
|
||||
y="17.426493"
|
||||
id="rect12"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1 KiB |
6
bundled-addons/pdf-editor/vendor/pdfjs/web/images/annotation-paperclip.svg
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" height="40" width="40">
|
||||
<path d="M9 3.5a1.5 1.5 0 0 0-3-.001v7.95C6 12.83 7.12 14 8.5 14s2.5-1.17 2.5-2.55V5.5a.5.5 0 0 1 1 0v6.03C11.955 13.427 10.405 15 8.5 15S5.044 13.426 5 11.53V3.5a2.5 2.5 0 0 1 5 0v7.003a1.5 1.5 0 0 1-3-.003v-5a.5.5 0 0 1 1 0v5a.5.5 0 0 0 1 0Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 552 B |
16
bundled-addons/pdf-editor/vendor/pdfjs/web/images/annotation-paragraph.svg
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40">
|
||||
<rect
|
||||
width="33.76017"
|
||||
height="33.76017"
|
||||
x="3.119915"
|
||||
y="3.119915"
|
||||
style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
d="m 17.692678,34.50206 0,-16.182224 c -1.930515,-0.103225 -3.455824,-0.730383 -4.57593,-1.881473 -1.12011,-1.151067 -1.680164,-2.619596 -1.680164,-4.405591 0,-1.992435 0.621995,-3.5796849 1.865988,-4.7617553 1.243989,-1.1820288 3.06352,-1.7730536 5.458598,-1.7730764 l 9.802246,0 0,2.6789711 -2.229895,0 0,26.3251486 -2.632515,0 0,-26.3251486 -3.45324,0 0,26.3251486 z"
|
||||
style="font-size:29.42051125px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.07795751;stroke-opacity:1;font-family:Arial;-inkscape-font-specification:Arial" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
7
bundled-addons/pdf-editor/vendor/pdfjs/web/images/annotation-pushpin.svg
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" height="40" width="40">
|
||||
<path d="M8.156 12.5a.99.99 0 0 0 .707-.294l.523-2.574L10.5 8.499l1.058-1.04 2.65-.601a.996.996 0 0 0 0-1.414l-3.657-3.658a.996.996 0 0 0-1.414 0l-.523 2.576L7.5 5.499 6.442 6.535l-2.65.6a.996.996 0 0 0 0 1.413l3.657 3.658a.999.999 0 0 0 .707.295z"/>
|
||||
<path d="M9.842.996c-.386 0-.77.146-1.06.44a.5.5 0 0 0-.136.251l-.492 2.43-1.008 1.03-.953.933-2.511.566a.5.5 0 0 0-.243.133 1.505 1.505 0 0 0-.002 2.123l1.477 1.477-2.768 2.767a.5.5 0 0 0 0 .707.5.5 0 0 0 .708 0l2.767-2.767 1.475 1.474a1.494 1.494 0 0 0 2.123-.002.5.5 0 0 0 .135-.254l.492-2.427 1.008-1.024.953-.937 2.511-.57a.5.5 0 0 0 .243-.132c.586-.58.583-1.543.002-2.125l-3.659-3.656A1.501 1.501 0 0 0 9.842.996Zm.05 1.025a.394.394 0 0 1 .305.12l3.658 3.657c.18.18.141.432.002.627l-2.41.545a.5.5 0 0 0-.24.131L10.15 8.142a.5.5 0 0 0-.007.006L9.029 9.283a.5.5 0 0 0-.133.25l-.48 2.36c-.082.053-.165.109-.26.109a.492.492 0 0 1-.353-.149L4.145 8.195c-.18-.18-.141-.432-.002-.627l2.41-.545a.5.5 0 0 0 .238-.13L7.85 5.857a.5.5 0 0 0 .007-.008l1.114-1.138a.5.5 0 0 0 .133-.25l.472-2.323a.619.619 0 0 1 .317-.117Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
5
bundled-addons/pdf-editor/vendor/pdfjs/web/images/checkmark.svg
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.79253 0.14683C10.2086 0.418844 10.3253 0.976607 10.0533 1.39263L4.56097 9.79263C4.4081 10.0264 4.15533 10.176 3.87682 10.1974C3.5983 10.2189 3.32561 10.1098 3.13874 9.90217L0.231043 6.6714C-0.10147 6.30194 -0.0715196 5.73288 0.29794 5.40037C0.667399 5.06786 1.23646 5.09781 1.56897 5.46727L3.69438 7.82883L8.54674 0.407579C8.81875 -0.00844234 9.37651 -0.125183 9.79253 0.14683Z" fill="black"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 565 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/comment-actionsButton.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.51562 11H6.01562L5.51562 11.5V13L6.01562 13.5H7.51562L8.01562 13V11.5L7.51562 11ZM13.2656 11H11.7656L11.2656 11.5V13L11.7656 13.5H13.2656L13.7656 13V11.5L13.2656 11ZM17.5156 11H19.0156L19.5156 11.5V13L19.0156 13.5H17.5156L17.0156 13V11.5L17.5156 11Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 421 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/comment-closeButton.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.6241 11.7759L18.3331 7.06694C18.4423 6.94811 18.5015 6.79167 18.4981 6.63028C18.4948 6.46889 18.4292 6.31502 18.3152 6.20081C18.2011 6.0866 18.0473 6.02088 17.8859 6.01736C17.7245 6.01384 17.568 6.0728 17.4491 6.18194L12.7601 10.8709H12.2721L7.58306 6.18294C7.52495 6.12489 7.45598 6.07886 7.38008 6.04747C7.30418 6.01609 7.22284 5.99995 7.14071 6C7.05857 6.00005 6.97725 6.01627 6.90139 6.04774C6.82553 6.07922 6.75661 6.12533 6.69856 6.18344C6.64052 6.24155 6.59449 6.31052 6.5631 6.38642C6.53171 6.46232 6.51558 6.54366 6.51563 6.62579C6.51572 6.79167 6.5817 6.95071 6.69906 7.06794L11.3861 11.7539V12.2449L6.69906 16.9319C6.5898 17.0508 6.53066 17.2072 6.53399 17.3686C6.53732 17.53 6.60288 17.6839 6.71696 17.7981C6.83104 17.9123 6.98483 17.978 7.14622 17.9815C7.3076 17.985 7.46411 17.9261 7.58306 17.8169L12.2701 13.1299H12.7611L17.4481 17.8169C17.5656 17.934 17.7247 17.9997 17.8906 17.9997C18.0564 17.9997 18.2155 17.934 18.3331 17.8169C18.4504 17.6996 18.5163 17.5404 18.5163 17.3744C18.5163 17.2085 18.4504 17.0493 18.3331 16.9319L13.6241 12.2229V11.7759Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
8
bundled-addons/pdf-editor/vendor/pdfjs/web/images/comment-editButton.svg
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<path d="M10.75 7H12.25V5.5H10.75V7Z" fill="black"/>
|
||||
<path d="M7.5 7H9V5.5H7.5V7Z" fill="black"/>
|
||||
<path d="M4.25 7H5.75V5.5H4.25V7Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 0C0.895786 0 0 0.895786 0 2V10.5C0 11.6042 0.895786 12.5 2 12.5H3V15.25C3 15.5405 3.16778 15.805 3.43066 15.9287C3.6937 16.0523 4.00473 16.0126 4.22852 15.8271L8.27051 12.4775L13.9941 12.4961C15.1007 12.4991 15.9999 11.6033 16 10.4961V2C16 0.895786 15.1042 0 14 0H2ZM14 1.5C14.2758 1.5 14.5 1.72421 14.5 2V10.4961C14.4999 10.7727 14.2753 10.9969 13.998 10.9961L8.00195 10.9775L7.87207 10.9893C7.74389 11.0115 7.62281 11.0664 7.52148 11.1504L4.5 13.6543V11.75C4.5 11.3358 4.16421 11 3.75 11H2C1.72421 11 1.5 10.7758 1.5 10.5V2C1.5 1.72421 1.72421 1.5 2 1.5H14Z" fill="black"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 908 B |
5
bundled-addons/pdf-editor/vendor/pdfjs/web/images/comment-popup-editButton.svg
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.0189877 13.6645L0.612989 10.4635C0.687989 10.0545 0.884989 9.6805 1.18099 9.3825L9.98199 0.5805C10.756 -0.1925 12.015 -0.1945 12.792 0.5805L14.42 2.2085C15.194 2.9835 15.194 4.2435 14.42 5.0185L5.61599 13.8215C5.31999 14.1165 4.94599 14.3125 4.53799 14.3875L1.33599 14.9815C1.26599 14.9935 1.19799 15.0005 1.12999 15.0005C0.832989 15.0005 0.544988 14.8835 0.330988 14.6695C0.0679874 14.4055 -0.0490122 14.0305 0.0189877 13.6645ZM12.472 5.1965L13.632 4.0365L13.631 3.1885L11.811 1.3675L10.963 1.3685L9.80299 2.5285L12.472 5.1965ZM4.31099 13.1585C4.47099 13.1285 4.61799 13.0515 4.73399 12.9345L11.587 6.0815L8.91899 3.4135L2.06599 10.2655C1.94899 10.3835 1.87199 10.5305 1.84099 10.6915L1.36699 13.2485L1.75199 13.6335L4.31099 13.1585Z" fill="black"/>
|
||||
</svg>
|
||||
|
||||
<!--path d="M0.0189877 14.1645L0.612989 10.9635C0.687989 10.5545 0.884989 10.1805 1.18099 9.8825L9.98199 1.0805C10.756 0.3075 12.015 0.3055 12.792 1.0805L14.42 2.7085C15.194 3.4835 15.194 4.7435 14.42 5.5185L5.61599 14.3215C5.31999 14.6165 4.94599 14.8125 4.53799 14.8875L1.33599 15.4815C1.26599 15.4935 1.19799 15.5005 1.12999 15.5005C0.832989 15.5005 0.544988 15.3835 0.330988 15.1695C0.0679874 14.9055 -0.0490122 14.5305 0.0189877 14.1645ZM12.472 5.6965L13.632 4.5365L13.631 3.6885L11.811 1.8675L10.963 1.8685L9.80299 3.0285L12.472 5.6965ZM4.31099 13.6585C4.47099 13.6285 4.61799 13.5515 4.73399 13.4345L11.587 6.5815L8.91899 3.9135L2.06599 10.7655C1.94899 10.8835 1.87199 11.0305 1.84099 11.1915L1.36699 13.7485L1.75199 14.1335L4.31099 13.6585Z" fill="black"/-->
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
6
bundled-addons/pdf-editor/vendor/pdfjs/web/images/cursor-editorFreeHighlight.svg
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.2 3.09C12.28 3.01 12.43 3 12.43 3C12.48 3 12.58 3.02 12.66 3.1L14.45 4.89C14.58 5.02 14.58 5.22 14.45 5.35L11.7713 8.02872L9.51628 5.77372L12.2 3.09ZM13.2658 5.12L11.7713 6.6145L10.9305 5.77372L12.425 4.27921L13.2658 5.12Z" fill="#FBFBFE"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.98 9.32L8.23 11.57L10.7106 9.08938L8.45562 6.83438L5.98 9.31V9.32ZM8.23 10.1558L9.29641 9.08938L8.45562 8.24859L7.38921 9.315L8.23 10.1558Z" fill="#FBFBFE"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.1526 13.1816L16.2125 7.1217C16.7576 6.58919 17.05 5.8707 17.05 5.12C17.05 4.36931 16.7576 3.65084 16.2126 3.11834L14.4317 1.33747C13.8992 0.79242 13.1807 0.5 12.43 0.5C11.6643 0.5 10.9529 0.812929 10.4329 1.33289L3.68289 8.08289C3.04127 8.72452 3.00459 9.75075 3.57288 10.4363L1.29187 12.7239C1.09186 12.9245 0.990263 13.1957 1.0007 13.4685L1 14.5C0.447715 14.5 0 14.9477 0 15.5V17.5C0 18.0523 0.447715 18.5 1 18.5H16C16.5523 18.5 17 18.0523 17 17.5V15.5C17 14.9477 16.5523 14.5 16 14.5H10.2325C9.83594 14.5 9.39953 13.9347 10.1526 13.1816ZM4.39 9.85L4.9807 10.4407L2.39762 13.0312H6.63877L7.10501 12.565L7.57125 13.0312H8.88875L15.51 6.41C15.86 6.07 16.05 5.61 16.05 5.12C16.05 4.63 15.86 4.17 15.51 3.83L13.72 2.04C13.38 1.69 12.92 1.5 12.43 1.5C11.94 1.5 11.48 1.7 11.14 2.04L4.39 8.79C4.1 9.08 4.1 9.56 4.39 9.85ZM16 17.5V15.5H1V17.5H16Z" fill="#FBFBFE"/>
|
||||
<path d="M15.1616 6.05136L15.1616 6.05132L15.1564 6.05645L8.40645 12.8064C8.35915 12.8537 8.29589 12.88 8.23 12.88C8.16411 12.88 8.10085 12.8537 8.05355 12.8064L7.45857 12.2115L7.10501 11.8579L6.75146 12.2115L6.03289 12.93H3.20465L5.33477 10.7937L5.6873 10.4402L5.33426 10.0871L4.74355 9.49645C4.64882 9.40171 4.64882 9.23829 4.74355 9.14355L11.4936 2.39355C11.7436 2.14354 12.0779 2 12.43 2C12.7883 2 13.1179 2.13776 13.3614 2.38839L13.3613 2.38843L13.3664 2.39355L15.1564 4.18355L15.1564 4.18359L15.1616 4.18864C15.4122 4.43211 15.55 4.76166 15.55 5.12C15.55 5.47834 15.4122 5.80789 15.1616 6.05136ZM7.87645 11.9236L8.23 12.2771L8.58355 11.9236L11.0642 9.44293L11.4177 9.08938L11.0642 8.73582L8.80918 6.48082L8.45562 6.12727L8.10207 6.48082L5.62645 8.95645L5.48 9.10289V9.31V9.32V9.52711L5.62645 9.67355L7.87645 11.9236ZM11.4177 8.38227L11.7713 8.73582L12.1248 8.38227L14.8036 5.70355C15.1288 5.37829 15.1288 4.86171 14.8036 4.53645L13.0136 2.74645C12.8186 2.55146 12.5792 2.5 12.43 2.5H12.4134L12.3967 2.50111L12.43 3C12.3967 2.50111 12.3966 2.50112 12.3965 2.50112L12.3963 2.50114L12.3957 2.50117L12.3947 2.50125L12.3924 2.50142L12.387 2.50184L12.3732 2.50311C12.3628 2.50416 12.3498 2.50567 12.3346 2.50784C12.3049 2.51208 12.2642 2.51925 12.2178 2.53146C12.1396 2.55202 11.9797 2.60317 11.8464 2.73645L9.16273 5.42016L8.80918 5.77372L9.16273 6.12727L11.4177 8.38227ZM1.5 16H15.5V17H1.5V16Z" stroke="#15141A"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/cursor-editorFreeText.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2.75H12.5V2.25V1V0.5H12H10.358C9.91165 0.5 9.47731 0.625661 9.09989 0.860442L9.09886 0.861087L8 1.54837L6.89997 0.860979L6.89911 0.860443C6.5218 0.625734 6.08748 0.5 5.642 0.5H4H3.5V1V2.25V2.75H4H5.642C5.66478 2.75 5.6885 2.75641 5.71008 2.76968C5.71023 2.76977 5.71038 2.76986 5.71053 2.76995L6.817 3.461C6.81704 3.46103 6.81709 3.46105 6.81713 3.46108C6.81713 3.46108 6.81713 3.46108 6.81714 3.46109C6.8552 3.48494 6.876 3.52285 6.876 3.567V8V12.433C6.876 12.4771 6.85523 12.515 6.81722 12.5389C6.81715 12.5389 6.81707 12.539 6.817 12.539L5.70953 13.23C5.70941 13.2301 5.70929 13.2302 5.70917 13.2303C5.68723 13.2438 5.6644 13.25 5.641 13.25H4H3.5V13.75V15V15.5H4H5.642C6.08835 15.5 6.52269 15.3743 6.90011 15.1396L6.90086 15.1391L8 14.4526L9.10003 15.14L9.10089 15.1406C9.47831 15.3753 9.91265 15.501 10.359 15.501H12H12.5V15.001V13.751V13.251H12H10.358C10.3352 13.251 10.3115 13.2446 10.2899 13.2313C10.2897 13.2312 10.2896 13.2311 10.2895 13.231L9.183 12.54C9.18298 12.54 9.18295 12.54 9.18293 12.54C9.18291 12.5399 9.18288 12.5399 9.18286 12.5399C9.14615 12.5169 9.125 12.4797 9.125 12.434V8V3.567C9.125 3.52266 9.14603 3.48441 9.18364 3.4606C9.18377 3.46052 9.1839 3.46043 9.18404 3.46035L10.2895 2.76995C10.2896 2.76985 10.2898 2.76975 10.2899 2.76966C10.3119 2.75619 10.3346 2.75 10.358 2.75H12Z" fill="black" stroke="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
4
bundled-addons/pdf-editor/vendor/pdfjs/web/images/cursor-editorInk.svg
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.0189877 13.6645L0.612989 10.4635C0.687989 10.0545 0.884989 9.6805 1.18099 9.3825L9.98199 0.5805C10.756 -0.1925 12.015 -0.1945 12.792 0.5805L14.42 2.2085C15.194 2.9835 15.194 4.2435 14.42 5.0185L5.61599 13.8215C5.31999 14.1165 4.94599 14.3125 4.53799 14.3875L1.33599 14.9815C1.26599 14.9935 1.19799 15.0005 1.12999 15.0005C0.832989 15.0005 0.544988 14.8835 0.330988 14.6695C0.0679874 14.4055 -0.0490122 14.0305 0.0189877 13.6645Z" fill="white"/>
|
||||
<path d="M0.0189877 13.6645L0.612989 10.4635C0.687989 10.0545 0.884989 9.6805 1.18099 9.3825L9.98199 0.5805C10.756 -0.1925 12.015 -0.1945 12.792 0.5805L14.42 2.2085C15.194 2.9835 15.194 4.2435 14.42 5.0185L5.61599 13.8215C5.31999 14.1165 4.94599 14.3125 4.53799 14.3875L1.33599 14.9815C1.26599 14.9935 1.19799 15.0005 1.12999 15.0005C0.832989 15.0005 0.544988 14.8835 0.330988 14.6695C0.0679874 14.4055 -0.0490122 14.0305 0.0189877 13.6645ZM12.472 5.1965L13.632 4.0365L13.631 3.1885L11.811 1.3675L10.963 1.3685L9.80299 2.5285L12.472 5.1965ZM4.31099 13.1585C4.47099 13.1285 4.61799 13.0515 4.73399 12.9345L11.587 6.0815L8.91899 3.4135L2.06599 10.2655C1.94899 10.3835 1.87199 10.5305 1.84099 10.6915L1.36699 13.2485L1.75199 13.6335L4.31099 13.1585Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
8
bundled-addons/pdf-editor/vendor/pdfjs/web/images/cursor-editorTextHighlight.svg
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<svg width="29" height="32" viewBox="0 0 29 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M28 16.75C28.2761 16.75 28.5 16.5261 28.5 16.25V15C28.5 14.7239 28.2761 14.5 28 14.5H26.358C25.9117 14.5 25.4773 14.6257 25.0999 14.8604L25.0989 14.8611L24 15.5484L22.9 14.861L22.8991 14.8604C22.5218 14.6257 22.0875 14.5 21.642 14.5H20C19.7239 14.5 19.5 14.7239 19.5 15V16.25C19.5 16.5261 19.7239 16.75 20 16.75H21.642C21.6648 16.75 21.6885 16.7564 21.7101 16.7697C21.7102 16.7698 21.7104 16.7699 21.7105 16.77L22.817 17.461C22.817 17.461 22.8171 17.4611 22.8171 17.4611C22.8171 17.4611 22.8171 17.4611 22.8171 17.4611C22.8552 17.4849 22.876 17.5229 22.876 17.567V22.625V27.683C22.876 27.7271 22.8552 27.765 22.8172 27.7889C22.8171 27.7889 22.8171 27.789 22.817 27.789L21.7095 28.48C21.7094 28.4801 21.7093 28.4802 21.7092 28.4803C21.6872 28.4938 21.6644 28.5 21.641 28.5H20C19.7239 28.5 19.5 28.7239 19.5 29V30.25C19.5 30.5261 19.7239 30.75 20 30.75H21.642C22.0883 30.75 22.5227 30.6243 22.9001 30.3896L22.9009 30.3891L24 29.7026L25.1 30.39L25.1009 30.3906C25.4783 30.6253 25.9127 30.751 26.359 30.751H28C28.2761 30.751 28.5 30.5271 28.5 30.251V29.001C28.5 28.7249 28.2761 28.501 28 28.501H26.358C26.3352 28.501 26.3115 28.4946 26.2899 28.4813C26.2897 28.4812 26.2896 28.4811 26.2895 28.481L25.183 27.79C25.183 27.79 25.183 27.79 25.1829 27.79C25.1829 27.7899 25.1829 27.7899 25.1829 27.7899C25.1462 27.7669 25.125 27.7297 25.125 27.684V22.625V17.567C25.125 17.5227 25.146 17.4844 25.1836 17.4606C25.1838 17.4605 25.1839 17.4604 25.184 17.4603L26.2895 16.77C26.2896 16.7699 26.2898 16.7698 26.2899 16.7697C26.3119 16.7562 26.3346 16.75 26.358 16.75H28Z" fill="black" stroke="#FBFBFE" stroke-linejoin="round"/>
|
||||
<path d="M24.625 17.567C24.625 17.35 24.735 17.152 24.918 17.037L26.026 16.345C26.126 16.283 26.24 16.25 26.358 16.25H28V15H26.358C26.006 15 25.663 15.099 25.364 15.285L24.256 15.978C24.161 16.037 24.081 16.113 24 16.187C23.918 16.113 23.839 16.037 23.744 15.978L22.635 15.285C22.336 15.099 21.993 15 21.642 15H20V16.25H21.642C21.759 16.25 21.874 16.283 21.974 16.345L23.082 17.037C23.266 17.152 23.376 17.35 23.376 17.567V22.625V27.683C23.376 27.9 23.266 28.098 23.082 28.213L21.973 28.905C21.873 28.967 21.759 29 21.641 29H20V30.25H21.642C21.994 30.25 22.337 30.151 22.636 29.965L23.744 29.273C23.84 29.213 23.919 29.137 24 29.064C24.081 29.137 24.161 29.213 24.256 29.273L25.365 29.966C25.664 30.152 26.007 30.251 26.359 30.251H28V29.001H26.358C26.241 29.001 26.126 28.968 26.026 28.906L24.918 28.214C24.734 28.099 24.625 27.901 24.625 27.684V22.625V17.567Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.2 2.59C12.28 2.51 12.43 2.5 12.43 2.5C12.48 2.5 12.58 2.52 12.66 2.6L14.45 4.39C14.58 4.52 14.58 4.72 14.45 4.85L11.7713 7.52872L9.51628 5.27372L12.2 2.59ZM13.2658 4.62L11.7713 6.1145L10.9305 5.27372L12.425 3.77921L13.2658 4.62Z" fill="#FBFBFE"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.98 8.82L8.23 11.07L10.7106 8.58938L8.45562 6.33438L5.98 8.81V8.82ZM8.23 9.65579L9.29641 8.58938L8.45562 7.74859L7.38921 8.815L8.23 9.65579Z" fill="#FBFBFE"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.1526 12.6816L16.2125 6.6217C16.7576 6.08919 17.05 5.3707 17.05 4.62C17.05 3.86931 16.7576 3.15084 16.2126 2.61834L14.4317 0.837474C13.8992 0.29242 13.1807 0 12.43 0C11.6643 0 10.9529 0.312929 10.4329 0.832893L3.68289 7.58289C3.04127 8.22452 3.00459 9.25075 3.57288 9.93634L1.29187 12.2239C1.09186 12.4245 0.990263 12.6957 1.0007 12.9685L1 14C0.447715 14 0 14.4477 0 15V17C0 17.5523 0.447715 18 1 18H16C16.5523 18 17 17.5523 17 17V15C17 14.4477 16.5523 14 16 14H10.2325C9.83594 14 9.39953 13.4347 10.1526 12.6816ZM4.39 9.35L4.9807 9.9407L2.39762 12.5312H6.63877L7.10501 12.065L7.57125 12.5312H8.88875L15.51 5.91C15.86 5.57 16.05 5.11 16.05 4.62C16.05 4.13 15.86 3.67 15.51 3.33L13.72 1.54C13.38 1.19 12.92 1 12.43 1C11.94 1 11.48 1.2 11.14 1.54L4.39 8.29C4.1 8.58 4.1 9.06 4.39 9.35ZM16 17V15H1V17H16Z" fill="#FBFBFE"/>
|
||||
<path d="M15.1616 5.55136L15.1616 5.55132L15.1564 5.55645L8.40645 12.3064C8.35915 12.3537 8.29589 12.38 8.23 12.38C8.16411 12.38 8.10085 12.3537 8.05355 12.3064L7.45857 11.7115L7.10501 11.3579L6.75146 11.7115L6.03289 12.43H3.20465L5.33477 10.2937L5.6873 9.94019L5.33426 9.58715L4.74355 8.99645C4.64882 8.90171 4.64882 8.73829 4.74355 8.64355L11.4936 1.89355C11.7436 1.64354 12.0779 1.5 12.43 1.5C12.7883 1.5 13.1179 1.63776 13.3614 1.88839L13.3613 1.88843L13.3664 1.89355L15.1564 3.68355L15.1564 3.68359L15.1616 3.68864C15.4122 3.93211 15.55 4.26166 15.55 4.62C15.55 4.97834 15.4122 5.30789 15.1616 5.55136ZM5.48 8.82V9.02711L5.62645 9.17355L7.87645 11.4236L8.23 11.7771L8.58355 11.4236L11.0642 8.94293L11.4177 8.58938L11.0642 8.23582L8.80918 5.98082L8.45562 5.62727L8.10207 5.98082L5.62645 8.45645L5.48 8.60289V8.81V8.82ZM11.4177 7.88227L11.7713 8.23582L12.1248 7.88227L14.8036 5.20355C15.1288 4.87829 15.1288 4.36171 14.8036 4.03645L13.0136 2.24645C12.8186 2.05146 12.5792 2 12.43 2H12.4134L12.3967 2.00111L12.43 2.5C12.3967 2.00111 12.3966 2.00112 12.3965 2.00112L12.3963 2.00114L12.3957 2.00117L12.3947 2.00125L12.3924 2.00142L12.387 2.00184L12.3732 2.00311C12.3628 2.00416 12.3498 2.00567 12.3346 2.00784C12.3049 2.01208 12.2642 2.01925 12.2178 2.03146C12.1396 2.05202 11.9797 2.10317 11.8464 2.23645L9.16273 4.92016L8.80918 5.27372L9.16273 5.62727L11.4177 7.88227ZM1.5 16.5V15.5H15.5V16.5H1.5Z" stroke="#15141A"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
5
bundled-addons/pdf-editor/vendor/pdfjs/web/images/editor-toolbar-delete.svg
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M11 3H13.6C14 3 14.3 3.3 14.3 3.6C14.3 3.9 14 4.2 13.7 4.2H13.3V14C13.3 15.1 12.4 16 11.3 16H4.80005C3.70005 16 2.80005 15.1 2.80005 14V4.2H2.40005C2.00005 4.2 1.80005 4 1.80005 3.6C1.80005 3.2 2.00005 3 2.40005 3H5.00005V2C5.00005 0.9 5.90005 0 7.00005 0H9.00005C10.1 0 11 0.9 11 2V3ZM6.90005 1.2L6.30005 1.8V3H9.80005V1.8L9.20005 1.2H6.90005ZM11.4 14.7L12 14.1V4.2H4.00005V14.1L4.60005 14.7H11.4ZM7.00005 12.4C7.00005 12.7 6.70005 13 6.40005 13C6.10005 13 5.80005 12.7 5.80005 12.4V7.6C5.70005 7.3 6.00005 7 6.40005 7C6.80005 7 7.00005 7.3 7.00005 7.6V12.4ZM10.2001 12.4C10.2001 12.7 9.90006 13 9.60006 13C9.30006 13 9.00006 12.7 9.00006 12.4V7.6C9.00006 7.3 9.30006 7 9.60006 7C9.90006 7 10.2001 7.3 10.2001 7.6V12.4Z"
|
||||
fill="black" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 909 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/editor-toolbar-edit.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.9815 14.3461L14.3875 11.1451C14.3125 10.7361 14.1155 10.3621 13.8195 10.0641L5.0185 1.26214C4.2445 0.489141 2.9855 0.487141 2.2085 1.26214L0.5805 2.89014C-0.1935 3.66514 -0.1935 4.92514 0.5805 5.70014L9.3845 14.5031C9.6805 14.7981 10.0545 14.9941 10.4625 15.0691L13.6645 15.6631C13.7345 15.6751 13.8025 15.6821 13.8705 15.6821C14.1675 15.6821 14.4555 15.5651 14.6695 15.3511C14.9325 15.0871 15.0495 14.7121 14.9815 14.3461ZM2.5285 5.87814L1.3685 4.71814L1.3695 3.87014L3.1895 2.04914L4.0375 2.05014L5.1975 3.21014L2.5285 5.87814ZM10.6895 13.8401C10.5295 13.8101 10.3825 13.7331 10.2665 13.6161L3.4135 6.76314L6.0815 4.09514L12.9345 10.9471C13.0515 11.0651 13.1285 11.2121 13.1595 11.3731L13.6335 13.9301L13.2485 14.3151L10.6895 13.8401Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 869 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/findbarButton-next.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.999 8.352L5.534 13.818C5.41551 13.9303 5.25786 13.9918 5.09466 13.9895C4.93146 13.9872 4.77561 13.9212 4.66033 13.8057C4.54505 13.6902 4.47945 13.5342 4.47752 13.3709C4.47559 13.2077 4.53748 13.0502 4.65 12.932L9.585 7.998L4.651 3.067C4.53862 2.94864 4.47691 2.79106 4.47903 2.62786C4.48114 2.46466 4.54692 2.30874 4.66233 2.19333C4.77774 2.07792 4.93366 2.01215 5.09686 2.01003C5.26006 2.00792 5.41763 2.06962 5.536 2.182L11 7.647L10.999 8.352Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 578 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/findbarButton-previous.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.001 8.352L10.466 13.818C10.5845 13.9303 10.7421 13.9918 10.9053 13.9895C11.0685 13.9872 11.2244 13.9212 11.3397 13.8057C11.4549 13.6902 11.5205 13.5342 11.5225 13.3709C11.5244 13.2077 11.4625 13.0502 11.35 12.932L6.416 7.999L11.349 3.067C11.4614 2.94864 11.5231 2.79106 11.521 2.62786C11.5189 2.46466 11.4531 2.30874 11.3377 2.19333C11.2223 2.07792 11.0663 2.01215 10.9031 2.01003C10.7399 2.00792 10.5824 2.06962 10.464 2.182L5 7.647L5.001 8.352Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 578 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/gv-toolbarButton-download.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.969 10.059C17.262 9.766 17.737 9.766 18.03 10.059C18.323 10.352 18.323 10.827 18.03 11.12L12.15 17H11.35L5.46896 11.12C5.17596 10.827 5.17596 10.352 5.46896 10.059C5.76196 9.766 6.23696 9.766 6.52996 10.059L11 14.529V2.75C11 2.336 11.336 2 11.75 2C12.164 2 12.5 2.336 12.499 2.75V14.529L16.969 10.059ZM4.98193 19.7L5.78193 20.5H17.7169L18.5169 19.7V17.75C18.5169 17.336 18.8529 17 19.2669 17C19.6809 17 20.0169 17.336 20.0169 17.75V19.5C20.0169 20.881 18.8979 22 17.5169 22H5.98193C4.60093 22 3.48193 20.881 3.48193 19.5V17.75C3.48193 17.336 3.81793 17 4.23193 17C4.64593 17 4.98193 17.336 4.98193 17.75V19.7Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 782 B |
BIN
bundled-addons/pdf-editor/vendor/pdfjs/web/images/loading-icon.gif
vendored
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
1
bundled-addons/pdf-editor/vendor/pdfjs/web/images/loading.svg
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" style="animation:spinLoadingIcon 1s steps(12,end) infinite"><style>@keyframes spinLoadingIcon{to{transform:rotate(360deg)}}</style><path d="M7 3V1s0-1 1-1 1 1 1 1v2s0 1-1 1-1-1-1-1z"/><path d="M4.63 4.1l-1-1.73S3.13 1.5 4 1c.87-.5 1.37.37 1.37.37l1 1.73s.5.87-.37 1.37c-.87.57-1.37-.37-1.37-.37z" fill-opacity=".93"/><path d="M3.1 6.37l-1.73-1S.5 4.87 1 4c.5-.87 1.37-.37 1.37-.37l1.73 1s.87.5.37 1.37c-.5.87-1.37.37-1.37.37z" fill-opacity=".86"/><path d="M3 9H1S0 9 0 8s1-1 1-1h2s1 0 1 1-1 1-1 1z" fill-opacity=".79"/><path d="M4.1 11.37l-1.73 1S1.5 12.87 1 12c-.5-.87.37-1.37.37-1.37l1.73-1s.87-.5 1.37.37c.5.87-.37 1.37-.37 1.37z" fill-opacity=".72"/><path d="M3.63 13.56l1-1.73s.5-.87 1.37-.37c.87.5.37 1.37.37 1.37l-1 1.73s-.5.87-1.37.37c-.87-.5-.37-1.37-.37-1.37z" fill-opacity=".65"/><path d="M7 15v-2s0-1 1-1 1 1 1 1v2s0 1-1 1-1-1-1-1z" fill-opacity=".58"/><path d="M10.63 14.56l-1-1.73s-.5-.87.37-1.37c.87-.5 1.37.37 1.37.37l1 1.73s.5.87-.37 1.37c-.87.5-1.37-.37-1.37-.37z" fill-opacity=".51"/><path d="M13.56 12.37l-1.73-1s-.87-.5-.37-1.37c.5-.87 1.37-.37 1.37-.37l1.73 1s.87.5.37 1.37c-.5.87-1.37.37-1.37.37z" fill-opacity=".44"/><path d="M15 9h-2s-1 0-1-1 1-1 1-1h2s1 0 1 1-1 1-1 1z" fill-opacity=".37"/><path d="M14.56 5.37l-1.73 1s-.87.5-1.37-.37c-.5-.87.37-1.37.37-1.37l1.73-1s.87-.5 1.37.37c.5.87-.37 1.37-.37 1.37z" fill-opacity=".3"/><path d="M9.64 3.1l.98-1.66s.5-.874 1.37-.37c.87.5.37 1.37.37 1.37l-1 1.73s-.5.87-1.37.37c-.87-.5-.37-1.37-.37-1.37z" fill-opacity=".23"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/messageBar_closingButton.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.85822 8.84922L4.85322 11.8542C4.75891 11.9453 4.63261 11.9957 4.50151 11.9946C4.37042 11.9934 4.24501 11.9408 4.15231 11.8481C4.0596 11.7554 4.00702 11.63 4.00588 11.4989C4.00474 11.3678 4.05514 11.2415 4.14622 11.1472L7.15122 8.14222V7.85922L4.14622 4.85322C4.05514 4.75891 4.00474 4.63261 4.00588 4.50151C4.00702 4.37042 4.0596 4.24501 4.15231 4.15231C4.24501 4.0596 4.37042 4.00702 4.50151 4.00588C4.63261 4.00474 4.75891 4.05514 4.85322 4.14622L7.85822 7.15122H8.14122L11.1462 4.14622C11.2405 4.05514 11.3668 4.00474 11.4979 4.00588C11.629 4.00702 11.7544 4.0596 11.8471 4.15231C11.9398 4.24501 11.9924 4.37042 11.9936 4.50151C11.9947 4.63261 11.9443 4.75891 11.8532 4.85322L8.84822 7.85922V8.14222L11.8532 11.1472C11.9443 11.2415 11.9947 11.3678 11.9936 11.4989C11.9924 11.63 11.9398 11.7554 11.8471 11.8481C11.7544 11.9408 11.629 11.9934 11.4979 11.9946C11.3668 11.9957 11.2405 11.9453 11.1462 11.8542L8.14122 8.84922L8.14222 8.85022L7.85822 8.84922Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/messageBar_info.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.625 0.5C6.64009 0.5 5.66482 0.693993 4.75487 1.0709C3.84493 1.44781 3.01814 2.00026 2.3217 2.6967C1.62526 3.39314 1.07281 4.21993 0.695904 5.12987C0.318993 6.03982 0.125 7.01509 0.125 8C0.125 8.98491 0.318993 9.96018 0.695904 10.8701C1.07281 11.7801 1.62526 12.6069 2.3217 13.3033C3.01814 13.9997 3.84493 14.5522 4.75487 14.9291C5.66482 15.306 6.64009 15.5 7.625 15.5C9.61412 15.5 11.5218 14.7098 12.9283 13.3033C14.3348 11.8968 15.125 9.98912 15.125 8C15.125 6.01088 14.3348 4.10322 12.9283 2.6967C11.5218 1.29018 9.61412 0.5 7.625 0.5ZM8.25 11.375C8.25 11.5408 8.18415 11.6997 8.06694 11.8169C7.94973 11.9342 7.79076 12 7.625 12C7.45924 12 7.30027 11.9342 7.18306 11.8169C7.06585 11.6997 7 11.5408 7 11.375V6.938C7 6.77224 7.06585 6.61327 7.18306 6.49606C7.30027 6.37885 7.45924 6.313 7.625 6.313C7.79076 6.313 7.94973 6.37885 8.06694 6.49606C8.18415 6.61327 8.25 6.77224 8.25 6.938V11.375ZM8.25 5L8 5.25H7.25L7 5V4.25L7.25 4H8L8.25 4.25V5Z" fill="black" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/messageBar_warning.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.8748 12.037L9.37782 2.037C8.99682 1.346 8.31082 1 7.62482 1C6.93882 1 6.25282 1.346 5.87282 2.037L0.375823 12.037C-0.358177 13.37 0.606823 15 2.12782 15H13.1228C14.6428 15 15.6078 13.37 14.8748 12.037ZM8.24982 11.75L7.99982 12H7.24982L6.99982 11.75V11L7.24982 10.75H7.99982L8.24982 11V11.75ZM8.24982 9.062C8.24982 9.22776 8.18398 9.38673 8.06677 9.50394C7.94955 9.62115 7.79058 9.687 7.62482 9.687C7.45906 9.687 7.30009 9.62115 7.18288 9.50394C7.06567 9.38673 6.99982 9.22776 6.99982 9.062V5.625C6.99982 5.45924 7.06567 5.30027 7.18288 5.18306C7.30009 5.06585 7.45906 5 7.62482 5C7.79058 5 7.94955 5.06585 8.06677 5.18306C8.18398 5.30027 8.24982 5.45924 8.24982 5.625V9.062Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 811 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/pages_closeButton.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 3.06055L9.06055 8L14 12.9385L12.9395 14L8 9.06055L3.06152 14L2.00098 12.9395L6.93848 8L2 3.06152L3.06055 2.00098L7.99902 6.93945L12.9385 2L14 3.06055Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 284 B |
7
bundled-addons/pdf-editor/vendor/pdfjs/web/images/pages_selected.svg
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask">
|
||||
<rect width="16" height="16" rx="2" fill="white"/>
|
||||
<path d="M12 8L4 8" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</mask>
|
||||
<rect width="16" height="16" fill="black" mask="url(#mask)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 355 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/pages_viewArrow.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="8" height="10" viewBox="0 0 8 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.23336 7.46639L7.84736 3.85339C7.89401 3.8071 7.93104 3.75203 7.9563 3.69136C7.98157 3.63069 7.99458 3.56562 7.99458 3.49989C7.99458 3.43417 7.98157 3.3691 7.9563 3.30843C7.93104 3.24776 7.89401 3.19269 7.84736 3.14639C7.75359 3.05266 7.62644 3 7.49386 3C7.36127 3 7.23412 3.05266 7.14036 3.14639L3.99236 6.29339L0.847356 3.14739C0.753055 3.05631 0.626754 3.00592 0.495655 3.00706C0.364557 3.0082 0.239151 3.06078 0.146447 3.15348C0.0537425 3.24619 0.00115811 3.37159 1.89013e-05 3.50269C-0.00112031 3.63379 0.0492769 3.76009 0.140356 3.85439L3.75236 7.46739L4.23336 7.46639Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 705 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/pages_viewButton.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 7H5C5.53043 7 6.03914 6.78929 6.41421 6.41421C6.78929 6.03914 7 5.53043 7 5V3C7 2.46957 6.78929 1.96086 6.41421 1.58579C6.03914 1.21071 5.53043 1 5 1H3C2.46957 1 1.96086 1.21071 1.58579 1.58579C1.21071 1.96086 1 2.46957 1 3V5C1 5.53043 1.21071 6.03914 1.58579 6.41421C1.96086 6.78929 2.46957 7 3 7ZM11 7H13C13.5304 7 14.0391 6.78929 14.4142 6.41421C14.7893 6.03914 15 5.53043 15 5V3C15 2.46957 14.7893 1.96086 14.4142 1.58579C14.0391 1.21071 13.5304 1 13 1H11C10.4696 1 9.96086 1.21071 9.58579 1.58579C9.21071 1.96086 9 2.46957 9 3V5C9 5.53043 9.21071 6.03914 9.58579 6.41421C9.96086 6.78929 10.4696 7 11 7ZM5 15H3C2.46957 15 1.96086 14.7893 1.58579 14.4142C1.21071 14.0391 1 13.5304 1 13V11C1 10.4696 1.21071 9.96086 1.58579 9.58579C1.96086 9.21071 2.46957 9 3 9H5C5.53043 9 6.03914 9.21071 6.41421 9.58579C6.78929 9.96086 7 10.4696 7 11V13C7 13.5304 6.78929 14.0391 6.41421 14.4142C6.03914 14.7893 5.53043 15 5 15ZM11 15H13C13.5304 15 14.0391 14.7893 14.4142 14.4142C14.7893 14.0391 15 13.5304 15 13V11C15 10.4696 14.7893 9.96086 14.4142 9.58579C14.0391 9.21071 13.5304 9 13 9H11C10.4696 9 9.96086 9.21071 9.58579 9.58579C9.21071 9.96086 9 10.4696 9 11V13C9 13.5304 9.21071 14.0391 9.58579 14.4142C9.96086 14.7893 10.4696 15 11 15Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 1.5C4.41015 1.5 1.5 4.41015 1.5 8C1.5 11.5899 4.41015 14.5 8 14.5C11.5899 14.5 14.5 11.5899 14.5 8C14.5 4.41015 11.5899 1.5 8 1.5ZM0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8ZM8.75 4V5.5H7.25V4H8.75ZM8.75 12V7H7.25V12H8.75Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 417 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/secondaryToolbarButton-firstPage.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 3.5H2V5H14V3.5ZM8 8.811L12.939 13.75L14.001 12.689L8.531 7.219C8.238 6.926 7.763 6.926 7.47 7.219L2 12.689L3.061 13.75L8 8.811Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 260 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/secondaryToolbarButton-handTool.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.75 2.125C7.75 1.78021 8.03021 1.5 8.375 1.5C8.71979 1.5 9 1.78021 9 2.125V3.125V8H10.5V3.125C10.5 2.78021 10.7802 2.5 11.125 2.5C11.4698 2.5 11.75 2.78021 11.75 3.125V4.625V8H13.25V4.625C13.25 4.28021 13.5302 4 13.875 4C14.2198 4 14.5 4.28021 14.5 4.625V12.0188L13.3802 13.6628C13.2954 13.7872 13.25 13.9344 13.25 14.085V16H14.75V14.3162L15.8698 12.6722C15.9546 12.5478 16 12.4006 16 12.25V4.625C16 3.45179 15.0482 2.5 13.875 2.5C13.6346 2.5 13.4035 2.53996 13.188 2.6136C12.959 1.68724 12.1219 1 11.125 1C10.8235 1 10.5366 1.06286 10.2768 1.17618C9.9281 0.478968 9.20726 0 8.375 0C7.54274 0 6.8219 0.478968 6.47323 1.17618C6.21337 1.06286 5.9265 1 5.625 1C4.45179 1 3.5 1.95179 3.5 3.125V7.25317C2.66504 6.54282 1.41035 6.58199 0.621672 7.37067C-0.208221 8.20056 -0.208221 9.54644 0.621672 10.3763L0.62188 10.3765L5.499 15.2498V16H6.999V14.939C6.999 14.74 6.9199 14.5491 6.77912 14.4085L1.68233 9.31567C1.43823 9.07156 1.43823 8.67544 1.68233 8.43133C1.92644 8.18722 2.32257 8.18722 2.56667 8.43133L3.71967 9.58433C3.93417 9.79883 4.25676 9.863 4.53701 9.74691C4.81727 9.63082 5 9.35735 5 9.054V3.125C5 2.78021 5.28022 2.5 5.625 2.5C5.96921 2.5 6.24906 2.77927 6.25 3.12326V8H7.75L7.75 3.125L7.75 3.12178V2.125Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/secondaryToolbarButton-lastPage.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 8.189L12.939 3.25L14 4.311L8.531 9.781C8.238 10.074 7.763 10.074 7.47 9.781L2 4.311L3.061 3.25L8 8.189ZM14 13.5V12H2V13.5H14Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 257 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/secondaryToolbarButton-rotateCcw.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.4105 4.83612L4.77001 6.19601C5.06701 6.49201 4.85701 7.00001 4.43701 7.00001H0.862006C0.602006 7.00001 0.391006 6.78901 0.391006 6.52901V2.95401C0.391006 2.53401 0.899006 2.32401 1.19601 2.62101L2.32796 3.75328C3.67958 1.78973 5.9401 0.5 8.5 0.5C12.636 0.5 16 3.864 16 8C16 12.136 12.636 15.5 8.5 15.5C4.704 15.5 1.566 12.663 1.075 9H2.59C3.068 11.833 5.532 14 8.5 14C11.809 14 14.5 11.309 14.5 8C14.5 4.691 11.809 2 8.5 2C6.35262 2 4.46893 3.13503 3.4105 4.83612Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 596 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/secondaryToolbarButton-rotateCw.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.5895 4.83613L11.23 6.19601C10.933 6.49201 11.143 7.00001 11.563 7.00001H15.138C15.398 7.00001 15.609 6.78901 15.609 6.52901V2.95401C15.609 2.53401 15.101 2.32401 14.804 2.62101L13.672 3.75328C12.3204 1.78973 10.0599 0.5 7.5 0.5C3.364 0.5 0 3.864 0 8C0 12.136 3.364 15.5 7.5 15.5C11.296 15.5 14.434 12.663 14.925 9H13.41C12.932 11.833 10.468 14 7.5 14C4.191 14 1.5 11.309 1.5 8C1.5 4.691 4.191 2 7.5 2C9.64738 2 11.5311 3.13503 12.5895 4.83613Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 576 B |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 3.78C3 2.7621 2.13279 2.11834 1.25 2.01476V2H1V3.5C1.18133 3.5 1.32279 3.5609 1.40708 3.63029C1.48961 3.69823 1.5 3.75458 1.5 3.78V11.72C1.5 11.7454 1.48961 11.8018 1.40708 11.8697C1.32279 11.9391 1.18133 12 1 12V13.5H1.25V13.4852C2.13279 13.3817 3 12.7379 3 11.72V3.78ZM10.5 4C10.5 3.72386 10.2761 3.5 10 3.5H6.5C6.22386 3.5 6 3.72386 6 4V11.5C6 11.7761 6.22386 12 6.5 12H10C10.2761 12 10.5 11.7761 10.5 11.5V4ZM10 2C11.1046 2 12 2.89543 12 4V11.5C12 12.6046 11.1046 13.5 10 13.5H6.5C5.39543 13.5 4.5 12.6046 4.5 11.5V4C4.5 2.89543 5.39543 2 6.5 2H10ZM15.5 2H15.25V2.01476C14.3672 2.11834 13.5 2.7621 13.5 3.78V11.72C13.5 12.7379 14.3672 13.3817 15.25 13.4852V13.5H15.5V12C15.3187 12 15.1772 11.9391 15.0929 11.8697C15.0104 11.8018 15 11.7454 15 11.72V3.78C15 3.75458 15.0104 3.69823 15.0929 3.63029C15.1772 3.5609 15.3187 3.5 15.5 3.5V2Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 971 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/secondaryToolbarButton-scrollPage.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.5 2C3.5 1.72421 3.72421 1.5 4 1.5H12C12.2758 1.5 12.5 1.72421 12.5 2V14C12.5 14.2758 12.2758 14.5 12 14.5H4C3.72421 14.5 3.5 14.2758 3.5 14V2ZM4 0C2.89579 0 2 0.895786 2 2V14C2 15.1042 2.89579 16 4 16H12C13.1042 16 14 15.1042 14 14V2C14 0.895786 13.1042 0 12 0H4ZM5.89301 6H7.25V10H5.89301C5.54301 10 5.36801 10.423 5.61501 10.67L7.72101 12.776C7.87401 12.929 8.12301 12.929 8.27601 12.776L10.383 10.669C10.63 10.422 10.455 9.99902 10.105 9.99902H8.75V6H10.106C10.456 6 10.632 5.577 10.383 5.331L8.27601 3.224C8.12301 3.071 7.87401 3.071 7.72101 3.224L5.61501 5.33C5.36801 5.577 5.54301 6 5.89301 6Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 731 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/secondaryToolbarButton-scrollVertical.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 1V1.25H2.01476C2.11834 2.13279 2.7621 3 3.78 3H11.72C12.7379 3 13.3817 2.13279 13.4852 1.25H13.5V1H12C12 1.18133 11.9391 1.32279 11.8697 1.40708C11.8018 1.48961 11.7454 1.5 11.72 1.5H3.78C3.75458 1.5 3.69823 1.48961 3.63029 1.40708C3.5609 1.32279 3.5 1.18133 3.5 1H2ZM4 6C3.72386 6 3.5 6.22386 3.5 6.5V10C3.5 10.2761 3.72386 10.5 4 10.5H11.5C11.7761 10.5 12 10.2761 12 10V6.5C12 6.22386 11.7761 6 11.5 6H4ZM2 6.5C2 5.39543 2.89543 4.5 4 4.5H11.5C12.6046 4.5 13.5 5.39543 13.5 6.5V10C13.5 11.1046 12.6046 12 11.5 12H4C2.89543 12 2 11.1046 2 10V6.5ZM3.78 13.5C2.7621 13.5 2.11834 14.3672 2.01476 15.25H2V15.5H3.5C3.5 15.3187 3.5609 15.1772 3.63029 15.0929C3.69823 15.0104 3.75458 15 3.78 15H11.72C11.7454 15 11.8018 15.0104 11.8697 15.0929C11.9391 15.1772 12 15.3187 12 15.5H13.5V15.25H13.4852C13.3817 14.3672 12.7379 13.5 11.72 13.5H3.78Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 969 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/secondaryToolbarButton-scrollWrapped.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.5 1C2.5 1.27579 2.72421 1.5 3 1.5H5C5.27579 1.5 5.5 1.27579 5.5 1H7C7 2.10421 6.10421 3 5 3H3C1.89579 3 1 2.10421 1 1H2.5ZM2.5 6C2.5 5.72421 2.72421 5.5 3 5.5H5C5.27579 5.5 5.5 5.72421 5.5 6V10C5.5 10.2758 5.27579 10.5 5 10.5H3C2.72421 10.5 2.5 10.2758 2.5 10V6ZM3 4C1.89579 4 1 4.89579 1 6V10C1 11.1042 1.89579 12 3 12H5C6.10421 12 7 11.1042 7 10V6C7 4.89579 6.10421 4 5 4H3ZM10 6C10 5.72421 10.2242 5.5 10.5 5.5H12.5C12.7758 5.5 13 5.72421 13 6V10C13 10.2758 12.7758 10.5 12.5 10.5H10.5C10.2242 10.5 10 10.2758 10 10V6ZM10.5 4C9.39579 4 8.5 4.89579 8.5 6V10C8.5 11.1042 9.39579 12 10.5 12H12.5C13.6042 12 14.5 11.1042 14.5 10V6C14.5 4.89579 13.6042 4 12.5 4H10.5ZM3 14.5C2.72421 14.5 2.5 14.7242 2.5 15H1C1 13.8958 1.89579 13 3 13H5C6.10421 13 7 13.8958 7 15H5.5C5.5 14.7242 5.27579 14.5 5 14.5H3ZM10 15C10 14.7242 10.2242 14.5 10.5 14.5H12.5C12.7758 14.5 13 14.7242 13 15H14.5C14.5 13.8958 13.6042 13 12.5 13H10.5C9.39579 13 8.5 13.8958 8.5 15H10ZM10.5 1.5C10.2242 1.5 10 1.27579 10 1H8.5C8.5 2.10421 9.39579 3 10.5 3H12.5C13.6042 3 14.5 2.10421 14.5 1H13C13 1.27579 12.7758 1.5 12.5 1.5H10.5Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/secondaryToolbarButton-selectTool.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.371588 2.93131C-0.203366 1.33422 1.3342 -0.20335 2.93129 0.371603L2.93263 0.372085L12.0716 3.68171C12.0718 3.68178 12.0714 3.68163 12.0716 3.68171C13.4459 4.17758 13.8478 5.9374 12.8076 6.9776L11.8079 7.97727L14.6876 10.8569C15.4705 11.6398 15.4705 12.9047 14.6876 13.6876L13.6476 14.7276C12.8647 15.5105 11.5998 15.5105 10.8169 14.7276L7.93725 11.8479L6.97758 12.8076C5.93739 13.8478 4.17779 13.4465 3.68192 12.0722C3.68184 12.072 3.682 12.0724 3.68192 12.0722L0.371588 2.93131ZM1.78292 2.42323C1.78298 2.4234 1.78286 2.42305 1.78292 2.42323L5.09281 11.5629C5.21725 11.9082 5.65728 12.0066 5.91692 11.7469L7.93725 9.72661L11.8776 13.6669C12.0747 13.864 12.3898 13.864 12.5869 13.6669L13.6269 12.6269C13.824 12.4298 13.824 12.1147 13.6269 11.9176L9.68659 7.97727L11.7469 5.91694C12.0066 5.65729 11.9081 5.21727 11.5629 5.09283L11.5619 5.09245L2.42321 1.78293C2.42304 1.78287 2.42339 1.783 2.42321 1.78293C2.02067 1.63847 1.63846 2.02069 1.78292 2.42323Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/secondaryToolbarButton-spreadEven.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M2 3.5C1.72421 3.5 1.5 3.72421 1.5 4V12.5C1.5 12.7758 1.72421 13 2 13H7.25V3.5H2ZM14 13H8.75V3.5H14C14.2758 3.5 14.5 3.72421 14.5 4V12.5C14.5 12.7758 14.2758 13 14 13ZM0 4C0 2.89579 0.895786 2 2 2H14C15.1042 2 16 2.89579 16 4V12.5C16 13.6042 15.1042 14.5 14 14.5H2C0.895786 14.5 0 13.6042 0 12.5V4ZM10 6.5H11.5V7.5H10V9H11.5V10H10V11.5H12.25C12.6642 11.5 13 11.1642 13 10.75V5.75C13 5.33579 12.6642 5 12.25 5H10V6.5ZM4.5 6.5H3V5H5.25C5.66421 5 6 5.33579 6 5.75V7.75C6 8.03408 5.8395 8.29378 5.58541 8.42082L4.5 8.96353V10H6V11.5H3.75C3.33579 11.5 3 11.1642 3 10.75V8.5C3 8.21592 3.1605 7.95622 3.41459 7.82918L4.5 7.28647V6.5Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 775 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/secondaryToolbarButton-spreadNone.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 1.5C3.72421 1.5 3.5 1.72421 3.5 2V14C3.5 14.2758 3.72421 14.5 4 14.5H12C12.2758 14.5 12.5 14.2758 12.5 14V2C12.5 1.72421 12.2758 1.5 12 1.5H4ZM2 2C2 0.895786 2.89579 0 4 0H12C13.1042 0 14 0.895786 14 2V14C14 15.1042 13.1042 16 12 16H4C2.89579 16 2 15.1042 2 14V2Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 395 B |
3
bundled-addons/pdf-editor/vendor/pdfjs/web/images/secondaryToolbarButton-spreadOdd.svg
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.5 4C1.5 3.72421 1.72421 3.5 2 3.5H7.25V13H2C1.72421 13 1.5 12.7758 1.5 12.5V4ZM8.75 13V3.5H14C14.2758 3.5 14.5 3.72421 14.5 4V12.5C14.5 12.7758 14.2758 13 14 13H8.75ZM2 2C0.895786 2 0 2.89579 0 4V12.5C0 13.6042 0.895786 14.5 2 14.5H14C15.1042 14.5 16 13.6042 16 12.5V4C16 2.89579 15.1042 2 14 2H2ZM4.75 5H3V6.5H4V11.5H5.5V5.75C5.5 5.33579 5.16421 5 4.75 5ZM10 6.5H11.5V7.28647L10.4146 7.82918C10.1605 7.95622 10 8.21592 10 8.5V10.75C10 11.1642 10.3358 11.5 10.75 11.5H13V10H11.5V8.96353L12.5854 8.42082C12.8395 8.29378 13 8.03408 13 7.75V5.75C13 5.33579 12.6642 5 12.25 5H10V6.5Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 711 B |
4
bundled-addons/pdf-editor/vendor/pdfjs/web/images/signature-properties-row-check.svg
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="8" cy="8" r="7" stroke="black" stroke-width="1" fill="none"/>
|
||||
<path d="M5 8.4l2.4 2.4L11.6 6" stroke="black" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 296 B |