function __classPrivateFieldGet(receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
}
function __classPrivateFieldSet(receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value), value;
}
var _Channel_instances;
var _Channel_onmessage;
var _Channel_nextMessageIndex;
var _Channel_pendingMessages;
var _Channel_messageEndIndex;
var _Channel_deliver;
var _Resource_rid;
var SERIALIZE_TO_IPC_FN = "__TAURI_TO_IPC_KEY__";
function transformCallback(callback, once = false) {
return window.__TAURI_INTERNALS__.transformCallback(callback, once);
}
var Channel = class {
constructor(onmessage) {
_Channel_instances.add(this);
_Channel_onmessage.set(this, void 0);
_Channel_nextMessageIndex.set(this, 0);
_Channel_pendingMessages.set(this, []);
_Channel_messageEndIndex.set(this, void 0);
__classPrivateFieldSet(this, _Channel_onmessage, onmessage || (() => {
}), "f");
this.id = transformCallback((rawMessage) => {
const index = rawMessage.index;
if ("end" in rawMessage) {
if (index == __classPrivateFieldGet(this, _Channel_nextMessageIndex, "f")) {
this.cleanupCallback();
} else {
__classPrivateFieldSet(this, _Channel_messageEndIndex, index, "f");
}
return;
}
const message = rawMessage.message;
if (index == __classPrivateFieldGet(this, _Channel_nextMessageIndex, "f")) {
__classPrivateFieldGet(this, _Channel_instances, "m", _Channel_deliver).call(this, message);
__classPrivateFieldSet(this, _Channel_nextMessageIndex, __classPrivateFieldGet(this, _Channel_nextMessageIndex, "f") + 1, "f");
while (__classPrivateFieldGet(this, _Channel_nextMessageIndex, "f") in __classPrivateFieldGet(this, _Channel_pendingMessages, "f")) {
const message2 = __classPrivateFieldGet(this, _Channel_pendingMessages, "f")[__classPrivateFieldGet(this, _Channel_nextMessageIndex, "f")];
__classPrivateFieldGet(this, _Channel_instances, "m", _Channel_deliver).call(this, message2);
delete __classPrivateFieldGet(this, _Channel_pendingMessages, "f")[__classPrivateFieldGet(this, _Channel_nextMessageIndex, "f")];
__classPrivateFieldSet(this, _Channel_nextMessageIndex, __classPrivateFieldGet(this, _Channel_nextMessageIndex, "f") + 1, "f");
}
if (__classPrivateFieldGet(this, _Channel_nextMessageIndex, "f") === __classPrivateFieldGet(this, _Channel_messageEndIndex, "f")) {
this.cleanupCallback();
}
} else {
__classPrivateFieldGet(this, _Channel_pendingMessages, "f")[index] = message;
}
});
}
cleanupCallback() {
window.__TAURI_INTERNALS__.unregisterCallback(this.id);
}
set onmessage(handler) {
__classPrivateFieldSet(this, _Channel_onmessage, handler, "f");
}
get onmessage() {
return __classPrivateFieldGet(this, _Channel_onmessage, "f");
}
[(_Channel_onmessage = new WeakMap(), _Channel_nextMessageIndex = new WeakMap(), _Channel_pendingMessages = new WeakMap(), _Channel_messageEndIndex = new WeakMap(), _Channel_instances = new WeakSet(), _Channel_deliver = function _Channel_deliver2(message) {
try {
__classPrivateFieldGet(this, _Channel_onmessage, "f").call(this, message);
} catch (error) {
queueMicrotask(() => {
throw error;
});
}
}, SERIALIZE_TO_IPC_FN)]() {
return `__CHANNEL__:${this.id}`;
}
toJSON() {
return this[SERIALIZE_TO_IPC_FN]();
}
};
async function invoke(cmd, args = {}, options) {
return window.__TAURI_INTERNALS__.invoke(cmd, args, options);
}
var Resource = class {
get rid() {
return __classPrivateFieldGet(this, _Resource_rid, "f");
}
constructor(rid) {
_Resource_rid.set(this, void 0);
__classPrivateFieldSet(this, _Resource_rid, rid, "f");
}
async close() {
return invoke("plugin:resources|close", {
rid: this.rid
});
}
async [(_Resource_rid = new WeakMap(), Symbol.asyncDispose)]() {
await this.close();
}
};
var IMAGE_KINDS = new Set(["image", "ink"]);
var IMAGE_SCALE = 150 / 72;
function toEntry(a) {
const { id, type, pageLabel, sortIndex, image, onlyTextOrComment, readOnly, position, ...rest } = a;
const entry = {
id,
kind: type,
page: pageLabel || String(position.pageIndex + 1),
color: rest.color,
text: rest.text,
comment: rest.comment,
image: IMAGE_KINDS.has(type) ? `img/${id}.png` : null,
source: "reader",
sort_index: sortIndex,
...rest,
position
};
for (const [key, value] of Object.entries(entry)) {
if (value === void 0 || value === "" || Array.isArray(value) && !value.length) {
entry[key] = null;
}
}
return entry;
}
function fromEntry(e) {
const { kind, page, sort_index, image, ...rest } = e;
return {
text: "",
comment: "",
tags: [],
...rest,
type: kind,
pageLabel: page,
sortIndex: sort_index,
readOnly: rest.source === "pdf"
};
}
var imageKey = (a) => JSON.stringify([a.position, a.color]);
var AnnotationStore = class {
rendered = new Map();
async load() {
const annotations = (await invoke("load_annotations")).map(fromEntry);
await Promise.all(annotations.filter((a) => IMAGE_KINDS.has(a.type)).map(async (a) => {
try {
a.image = await dataUrl(await invoke("read_image", { id: a.id }));
this.rendered.set(a.id, imageKey(a));
} catch {
}
}));
return annotations;
}
async save(annotations, showImage) {
await invoke("save_annotations", { annotations: annotations.map(toEntry) });
for (const a of annotations.filter((a2) => IMAGE_KINDS.has(a2.type))) {
if (this.rendered.get(a.id) !== imageKey(a)) {
showImage({ ...a, image: await this.renderImage(a) });
}
}
}
async renderMissing(annotations, showImage) {
for (const a of annotations.filter((a2) => IMAGE_KINDS.has(a2.type) && !this.rendered.has(a2.id))) {
showImage({ ...a, image: await this.renderImage(a) });
}
}
async delete(ids) {
await invoke("delete_annotations", { ids });
ids.forEach((id) => this.rendered.delete(id));
}
async renderImage(a) {
const canvas = await renderRegion(a);
const png = await new Promise((resolve, reject) => {
canvas.toBlob((b) => b ? resolve(b) : reject(new Error("PNG encoding failed")), "image/png");
});
await invoke("save_image", new Uint8Array(await png.arrayBuffer()), { headers: { id: a.id } });
this.rendered.set(a.id, imageKey(a));
return canvas.toDataURL("image/png");
}
};
async function renderRegion(a) {
const doc = window.PDFViewerApplication?.pdfDocument;
if (!doc) throw new Error("PDF not loaded");
const page = await doc.getPage(a.position.pageIndex + 1);
const viewport = page.getViewport({ scale: IMAGE_SCALE });
const iframe = document.querySelector("#primary-view iframe");
const full = (iframe?.contentDocument ?? document).createElement("canvas");
full.width = Math.ceil(viewport.width);
full.height = Math.ceil(viewport.height);
const ctx = full.getContext("2d");
ctx.skipBlender = true;
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, full.width, full.height);
await page.render({ canvasContext: ctx, viewport }).promise;
const { paths, width = 1 } = a.position;
if (paths) {
ctx.lineCap = ctx.lineJoin = "round";
ctx.lineWidth = width * IMAGE_SCALE;
ctx.strokeStyle = a.color ?? "#000000";
for (const path of paths) {
ctx.beginPath();
for (let i = 0; i < path.length; i += 2) {
ctx.lineTo(...viewport.convertToViewportPoint(path[i], path[i + 1]));
}
ctx.stroke();
}
}
const [x1, y1, x2, y2] = viewport.convertToViewportRectangle(pdfRect(a.position));
const x = Math.max(0, Math.floor(Math.min(x1, x2)));
const y = Math.max(0, Math.floor(Math.min(y1, y2)));
const crop = document.createElement("canvas");
crop.width = Math.max(1, Math.min(full.width, Math.ceil(Math.max(x1, x2))) - x);
crop.height = Math.max(1, Math.min(full.height, Math.ceil(Math.max(y1, y2))) - y);
crop.getContext("2d").drawImage(full, x, y, crop.width, crop.height, 0, 0, crop.width, crop.height);
return crop;
}
function pdfRect({ rects, paths, width = 1 }) {
if (rects?.length) return rects[0];
const xs = paths.flatMap((p) => p.filter((_, i) => i % 2 === 0));
const ys = paths.flatMap((p) => p.filter((_, i) => i % 2 === 1));
return [Math.min(...xs) - width, Math.min(...ys) - width, Math.max(...xs) + width, Math.max(...ys) + width];
}
function dataUrl(buf) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(new Blob([buf], { type: "image/png" }));
});
}
var OVERLAP = 0.05;
var GRAB = 40;
var LAST_LAYOUT = "yeda.columns.layout";
var WHOLE_PAGE = { x0: 0, x1: 1, y0: 0, y1: 1, gutter: 0.5 };
var on = false;
var columnMode = () => on;
function load(key) {
try {
const value = localStorage.getItem(key);
return value === null ? null : JSON.parse(value);
} catch {
return null;
}
}
function store(key, value) {
try {
if (value === null) localStorage.removeItem(key);
else localStorage.setItem(key, JSON.stringify(value));
} catch {
}
}
var COLUMNS_ICON = '<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M2 2H9.375V18H2V2ZM3.25 3.25V16.75H8.125V3.25H3.25ZM10.625 2H18V18H10.625V2ZM11.875 3.25V16.75H16.75V3.25H11.875Z" fill="currentColor"/></svg>';
var ADJUST_ICON = '<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 4.375H15.625V19H14.375V5.625H1V4.375ZM4.375 1H5.625V14.375H19V15.625H4.375V1ZM9.375 6.5H10.625V8.5H9.375V6.5ZM9.375 9.5H10.625V11.5H9.375V9.5ZM9.375 12.5H10.625V13.75H9.375V12.5Z" fill="currentColor"/></svg>';
function toolbarButton(icon, title) {
return Object.assign(document.createElement("button"), { className: "toolbar-button", title, innerHTML: icon, tabIndex: -1 });
}
function installColumns(doc) {
const toggle = toolbarButton(COLUMNS_ICON, "Column mode (C)");
const adjust = toolbarButton(ADJUST_ICON, "Adjust the columns");
window.addEventListener("customEvent", (e) => {
const { type, append } = e.detail;
if (type !== "renderToolbar") return;
append(toggle);
append(adjust);
});
return (reader) => attach(reader, doc, toggle, adjust);
}
function attach(reader, doc, toggle, adjust) {
const view = reader._primaryView;
if (!view) return () => {
};
const onKey = `yeda.columns.${doc}`;
const layoutKey = `yeda.columns.layout.${doc}`;
let layout = load(layoutKey) ?? load(LAST_LAYOUT) ?? WHOLE_PAGE;
const columns = () => [
{ x0: layout.x0, x1: layout.gutter, y0: layout.y0, y1: layout.y1 },
{ x0: layout.gutter, x1: layout.x1, y0: layout.y0, y1: layout.y1 }
];
const viewer = () => view._iframeWindow?.PDFViewerApplication.pdfViewer;
let at = { page: -1, column: 0, step: 0 };
let shown = { left: NaN, top: NaN };
let scaleBefore = "auto";
let pageOff = -1;
const fit = (v, div, c) => {
const r = columns()[c];
const width = v.container.clientWidth / (r.x1 - r.x0);
const screen = v.container.clientHeight / (width * div.clientHeight / div.clientWidth);
const tops = [r.y0];
while (tops[tops.length - 1] + screen < r.y1) tops.push(tops[tops.length - 1] + screen * (1 - OVERLAP));
return { width, tops };
};
const show = (v, page, column, step) => {
const div = v._pages[page]?.div;
if (!div) return;
const r = columns()[column];
const { width, tops } = fit(v, div, column);
step = step < 0 ? tops.length - 1 : Math.min(step, tops.length - 1);
at = { page, column, step };
if (v.currentPageNumber !== page + 1) v.currentPageNumber = page + 1;
if (Math.abs(width / div.clientWidth - 1) > 0.02) v.currentScale *= width / div.clientWidth;
requestAnimationFrame(() => {
const c = v.container;
const box = c.getBoundingClientRect();
const d = div.getBoundingClientRect();
const [x, y] = [c.scrollLeft + d.left - box.left + div.clientLeft, c.scrollTop + d.top - box.top + div.clientTop];
c.scrollTo({
left: x + (r.x0 + r.x1) / 2 * div.clientWidth - c.clientWidth / 2,
top: y + tops[step] * div.clientHeight
});
shown = { left: c.scrollLeft, top: c.scrollTop };
});
};
const turn = (direction, original) => () => {
const v = viewer();
if (!on || !v) return original();
const forward = direction > 0;
let { page, column, step } = at;
if (Math.abs(v.container.scrollTop - shown.top) > 2 || Math.abs(v.container.scrollLeft - shown.left) > 2) {
return show(v, v.currentPageNumber - 1, forward ? 0 : 1, forward ? 0 : -1);
}
const div = v._pages[page]?.div;
step += direction;
if (div && step >= 0 && step < fit(v, div, column).tops.length) return show(v, page, column, step);
column += direction;
if (column === 0 || column === 1) return show(v, page, column, forward ? 0 : -1);
page += direction;
if (page >= 0 && page < v._pages.length) show(v, page, forward ? 0 : 1, forward ? 0 : -1);
};
view.navigateToNextPage = turn(1, view.navigateToNextPage.bind(view));
view.navigateToPreviousPage = turn(-1, view.navigateToPreviousPage.bind(view));
const set = (enable) => {
const v = viewer();
if (!v || enable === on) return;
on = enable;
store(onKey, on || null);
toggle.classList.toggle("active", on);
view.setTool(view._tool);
if (!on) {
v.currentScaleValue = scaleBefore;
requestAnimationFrame(() => {
pageOff = v.currentPageNumber;
});
return;
}
scaleBefore = v.currentScaleValue;
if (at.page >= 0 && v.currentPageNumber === pageOff) show(v, at.page, at.column, at.step);
else show(v, v.currentPageNumber - 1, 0, 0);
};
const edit = () => {
const v = viewer();
if (!v || document.getElementById("yeda-columns-editor")) return;
set(false);
const before = v.currentScaleValue;
const page = v.currentPageNumber - 1;
v.currentScaleValue = "page-fit";
requestAnimationFrame(() => {
const div = v._pages[page]?.div;
if (!div) return;
const frame = view._iframe.getBoundingClientRect();
const d = div.getBoundingClientRect();
const editor = Object.assign(document.createElement("div"), { id: "yeda-columns-editor" });
Object.assign(editor.style, {
position: "fixed",
zIndex: "1000",
touchAction: "none",
left: `${frame.left + d.left + div.clientLeft}px`,
top: `${frame.top + d.top + div.clientTop}px`,
width: `${div.clientWidth}px`,
height: `${div.clientHeight}px`
});
const crop = document.createElement("div");
const gutter = document.createElement("div");
Object.assign(crop.style, { position: "absolute", border: "3px solid #000", outline: "2px solid #fff", boxShadow: "0 0 0 9999px rgba(0, 0, 0, 0.4)" });
Object.assign(gutter.style, { position: "absolute", borderLeft: "3px dashed #000", outline: "2px solid #fff" });
const done = Object.assign(document.createElement("button"), { textContent: "Done" });
Object.assign(done.style, {
position: "fixed",
zIndex: "1001",
left: "50%",
bottom: "24px",
transform: "translateX(-50%)",
minWidth: "120px",
minHeight: "48px",
fontSize: "18px",
background: "#fff",
color: "#000",
border: "3px solid #000"
});
editor.append(crop, gutter);
document.body.append(editor, done);
const draw = () => {
const pct = (n) => `${n * 100}%`;
Object.assign(crop.style, { left: pct(layout.x0), top: pct(layout.y0), width: pct(layout.x1 - layout.x0), height: pct(layout.y1 - layout.y0) });
Object.assign(gutter.style, { left: pct(layout.gutter), top: pct(layout.y0), height: pct(layout.y1 - layout.y0) });
};
draw();
let dragging = null;
const point = (e) => {
const r = editor.getBoundingClientRect();
return { x: (e.clientX - r.left) / r.width, y: (e.clientY - r.top) / r.height, w: r.width, h: r.height };
};
editor.addEventListener("pointerdown", (e) => {
const p = point(e);
const distances = [
["gutter", Math.abs(p.x - layout.gutter) * p.w],
["x0", Math.abs(p.x - layout.x0) * p.w],
["x1", Math.abs(p.x - layout.x1) * p.w],
["y0", Math.abs(p.y - layout.y0) * p.h],
["y1", Math.abs(p.y - layout.y1) * p.h]
];
const [nearest, distance] = distances.sort((a, b) => a[1] - b[1])[0];
if (distance > GRAB) return;
dragging = nearest;
editor.setPointerCapture(e.pointerId);
});
editor.addEventListener("pointermove", (e) => {
if (!dragging) return;
const p = point(e);
const clamp = (n, lo, hi) => Math.min(hi, Math.max(lo, n));
const gap = 0.05;
const value = {
x0: clamp(p.x, 0, layout.gutter - gap),
x1: clamp(p.x, layout.gutter + gap, 1),
y0: clamp(p.y, 0, layout.y1 - gap),
y1: clamp(p.y, layout.y0 + gap, 1),
gutter: clamp(p.x, layout.x0 + gap, layout.x1 - gap)
}[dragging];
layout = { ...layout, [dragging]: value };
draw();
});
editor.addEventListener("pointerup", () => {
dragging = null;
});
done.addEventListener("click", () => {
editor.remove();
done.remove();
store(layoutKey, layout);
store(LAST_LAYOUT, layout);
v.currentScaleValue = before;
at = { page, column: 0, step: 0 };
pageOff = v.currentPageNumber;
set(true);
});
});
};
toggle.addEventListener("click", () => set(!on));
adjust.addEventListener("click", edit);
let refit = 0;
window.addEventListener("resize", () => {
clearTimeout(refit);
refit = window.setTimeout(() => {
const v = viewer();
if (on && v && at.page >= 0) show(v, at.page, at.column, at.step);
}, 300);
});
view.initializedPromise.then(() => {
if (load(onKey)) set(true);
});
return () => set(!on);
}
var EINK_KEY = "yeda.eink";
var PALM_AFTER_PEN_MS = 600;
var PALM_AREA = 5e3;
var SWIPE = 60;
var DEFAULT_ERASER = 16;
var PEN_ERASER = 40;
var ERASE_STEP = 6;
var PAGE_SCROLL_MODE = 3;
var einkMode = () => localStorage.getItem(EINK_KEY) === "1";
var erasing = (e) => (e.buttons & 34) !== 0 || e.button === 5;
var NO_MOTION = "*, *::before, *::after { transition: none !important; animation: none !important; scroll-behavior: auto !important; }";
var BIG_TARGETS = ".toolbar button, .toolbar .toolbar-button { min-width: 48px; min-height: 48px; }";
function addStyle(doc, css) {
doc.head.append(Object.assign(doc.createElement("style"), { textContent: css }));
}
function installPen(reader) {
const view = reader._primaryView;
if (!view) return;
const eink = einkMode();
if (eink) {
addStyle(document, NO_MOTION + BIG_TARGETS);
if (reader._tools.ink) reader._tools.ink.color = "#000000";
}
const setTool = view.setTool.bind(view);
view.setTool = (tool) => {
setTool(tool);
const container = view._iframeWindow?.document.getElementById("viewerContainer");
if (container && (eink || columnMode())) container.style.touchAction = "none";
};
const down = view._handlePointerDown.bind(view);
const move = view._handlePointerMove;
const up = view._handlePointerUp.bind(view);
let penSeen = false;
let penDown = false;
let penUpAt = 0;
let lastPen = null;
let stroke = null;
const startStroke = (e) => {
const v = viewer();
const win = view._iframeWindow;
const pageIndex = view.action?.annotation?.position.pageIndex;
const page = pageIndex === void 0 ? void 0 : v?._pages[pageIndex]?.div;
const ink = reader._tools.ink;
if (!v || !win || !page || !ink) return;
const canvas = win.document.createElement("canvas");
const dpr = win.devicePixelRatio;
Object.assign(canvas.style, { position: "absolute", left: "0", top: "0", width: "100%", height: "100%", pointerEvents: "none", zIndex: "100" });
canvas.width = page.clientWidth * dpr;
canvas.height = page.clientHeight * dpr;
page.append(canvas);
const ctx = canvas.getContext("2d");
ctx.scale(dpr, dpr);
ctx.strokeStyle = ink.color ?? "#000000";
ctx.lineWidth = (ink.size ?? 2) * v.currentScale * 96 / 72;
ctx.lineCap = ctx.lineJoin = "round";
const r = page.getBoundingClientRect();
ctx.beginPath();
ctx.moveTo(e.clientX - r.left, e.clientY - r.top);
stroke = { canvas, ctx, page, render: view._render };
view._render = () => {
};
};
const endStroke = () => {
if (!stroke) return;
const { canvas, render } = stroke;
view._render = render;
stroke = null;
requestAnimationFrame(() => requestAnimationFrame(() => canvas.remove()));
};
let restoreTool = null;
const touches = new Map();
let pinch = 0;
let moved = false;
const viewer = () => view._iframeWindow?.PDFViewerApplication.pdfViewer;
const drawing = () => !["pointer", "hand"].includes(view._tool.type);
const turns = () => eink || columnMode();
const ours = (e) => e.pointerType === "touch" && (turns() || penSeen && drawing());
const zoomed = (v) => {
const page = v._pages[v.currentPageNumber - 1]?.div;
return !!page && (page.offsetWidth > v.container.clientWidth + 2 || page.offsetHeight > v.container.clientHeight + 2);
};
const palm = (e) => penDown || performance.now() - penUpAt < PALM_AFTER_PEN_MS || e.width * e.height > PALM_AREA;
const spread = () => {
const [a, b] = [...touches.values()];
return a && b ? Math.hypot(a.x - b.x, a.y - b.y) : 0;
};
view._handlePointerDown = (e) => {
if (e.pointerType === "pen") {
endStroke();
if (!penSeen && reader._tools.eraser?.size === DEFAULT_ERASER) reader._tools.eraser.size = PEN_ERASER;
penSeen = penDown = true;
lastPen = e;
if (erasing(e) && view._tool.type !== "eraser") {
restoreTool = reader._state.tool.type;
reader.setTool({ type: "eraser" });
view.setTool(reader._tools.eraser);
}
} else if (ours(e)) {
if (!palm(e)) {
if (touches.size === 0) moved = false;
touches.set(e.pointerId, { x: e.clientX, y: e.clientY, start: e });
pinch = touches.size === 2 ? spread() : 0;
}
return;
}
down(e);
if (e.pointerType === "pen" && view.action?.annotation && view._tool.type === "ink") startStroke(e);
};
view._handlePointerMove = (e) => {
const t = touches.get(e.pointerId);
if (t) {
const [dx, dy] = [e.clientX - t.x, e.clientY - t.y];
t.x = e.clientX;
t.y = e.clientY;
const v = viewer();
if (!v) return;
if (touches.size === 2) {
const now = spread();
if (pinch && Math.abs(now / pinch - 1) > 0.08) {
v.currentScale *= now / pinch;
pinch = now;
moved = true;
}
} else if (!columnMode() && (!eink || zoomed(v))) {
v.container.scrollBy(-dx, -dy);
moved ||= Math.hypot(e.clientX - t.start.clientX, e.clientY - t.start.clientY) > 8;
}
return;
}
if (ours(e)) return;
if (e.pointerType === "pen" && e.buttons && ["ink", "eraser"].includes(view._tool.type)) {
const points = e.getCoalescedEvents?.() ?? [];
const render = view._render;
view._render = () => {
};
const page = stroke?.page.getBoundingClientRect();
try {
for (const p of [...points.slice(0, -1), e]) {
if (view._tool.type === "eraser" && lastPen) {
const steps = Math.floor(Math.hypot(p.clientX - lastPen.clientX, p.clientY - lastPen.clientY) / ERASE_STEP);
for (let i = 1; i < steps; i++) {
const f = i / steps;
move(new PointerEvent("pointermove", {
pointerId: p.pointerId,
pointerType: "pen",
buttons: p.buttons,
clientX: lastPen.clientX + (p.clientX - lastPen.clientX) * f,
clientY: lastPen.clientY + (p.clientY - lastPen.clientY) * f
}));
}
}
if (p === e && !stroke) view._render = render;
move(p);
lastPen = p;
if (stroke && page) stroke.ctx.lineTo(p.clientX - page.left, p.clientY - page.top);
}
if (stroke && page) {
const { ctx } = stroke;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(e.clientX - page.left, e.clientY - page.top);
}
} finally {
if (!stroke) view._render = render;
}
return;
}
move(e);
};
view._handlePointerUp = (e) => {
const t = touches.get(e.pointerId);
if (t) {
const single = touches.size === 1;
touches.delete(e.pointerId);
const [dx, dy] = [e.clientX - t.start.clientX, e.clientY - t.start.clientY];
const dominant = Math.abs(dx) > Math.abs(dy) ? dx : dy;
if (single && !moved) {
if (turns() && Math.abs(dominant) > SWIPE) {
if (dominant < 0) reader.navigateToNextPage();
else reader.navigateToPreviousPage();
} else if (Math.abs(dominant) <= SWIPE && !drawing()) {
down(t.start);
up(e);
}
}
if (touches.size < 2) pinch = 0;
return;
}
if (ours(e)) return;
if (e.pointerType === "pen" && stroke) view._render = stroke.render;
up(e);
if (e.pointerType === "pen") {
endStroke();
penDown = false;
penUpAt = performance.now();
if (restoreTool) {
reader.setTool({ type: restoreTool });
view.setTool(reader._tools[restoreTool]);
restoreTool = null;
}
}
};
}
async function settleEink(reader) {
const view = reader._primaryView;
if (!einkMode() || !view) return;
await view.initializedPromise;
const win = view._iframeWindow;
if (!win) return;
addStyle(win.document, NO_MOTION);
const container = win.document.getElementById("viewerContainer");
if (container) container.style.touchAction = "none";
const v = win.PDFViewerApplication.pdfViewer;
v.scrollMode = PAGE_SCROLL_MODE;
v.currentScaleValue = "page-fit";
}
var TOOLS = ["highlight", "underline", "note", "text", "image", "ink", "eraser"];
var LINE = 60;
function isTextInput(target) {
const node = target;
return !!node && (node.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(node.nodeName));
}
function scrollContainer() {
return window.PDFViewerApplication?.pdfViewer?.container;
}
function installVim(getReader, toggleColumns) {
let pending = "";
let count = "";
const toggleSidebarView = (reader, view) => {
const { sidebarOpen, sidebarView } = reader._state;
if (sidebarOpen && sidebarView === view) {
reader.toggleSidebar(false);
} else {
reader.setSidebarView(view);
reader.toggleSidebar(true);
}
};
const editComment = (reader) => {
const [id] = reader._state.selectedAnnotationIDs;
if (!id) return;
reader.setSidebarView("annotations");
reader.toggleSidebar(true);
requestAnimationFrame(() => {
const editor = document.querySelector(
`[data-sidebar-annotation-id="${id}"] .comment [contenteditable="true"]`
);
if (!editor) return;
editor.focus();
const range = document.createRange();
range.selectNodeContents(editor);
range.collapse(false);
window.getSelection()?.removeAllRanges();
window.getSelection()?.addRange(range);
});
};
const handle = (event) => {
const reader = getReader();
if (!reader || event.altKey || event.metaKey) return false;
if (isTextInput(event.target)) {
if (event.key === "Enter" && !event.shiftKey && event.target.closest(".find-popup")) {
setTimeout(() => reader.focusView());
}
return false;
}
if (["Shift", "Control", "Alt", "Meta"].includes(event.key)) return false;
const key = event.ctrlKey ? `C-${event.key}` : event.key;
if (/^[0-9]$/.test(key) && !pending && (count || key !== "0")) {
count += key;
return true;
}
const n = Number(count);
count = "";
if (key === "G" && n) {
reader.navigate({ pageIndex: n - 1 });
return true;
}
if (key === "t") {
if (TOOLS[n - 1]) reader.setTool({ type: TOOLS[n - 1] });
return true;
}
const seq = pending + key;
pending = "";
switch (seq) {
case "j":
scrollContainer()?.scrollBy({ top: LINE });
return true;
case "k":
scrollContainer()?.scrollBy({ top: -LINE });
return true;
case "C-d":
case "C-u": {
const container = scrollContainer();
container?.scrollBy({ top: (key === "C-d" ? 0.5 : -0.5) * container.clientHeight });
return true;
}
case "J":
reader.navigateToNextPage();
return true;
case "K":
reader.navigateToPreviousPage();
return true;
case " ":
if (event.shiftKey) reader.navigateToPreviousPage();
else reader.navigateToNextPage();
return true;
case "gg":
reader.navigateToFirstPage();
return true;
case "G":
reader.navigateToLastPage();
return true;
case "/":
reader.toggleFindPopup({ open: true });
return true;
case "n":
reader.findNext();
return true;
case "N":
reader.findPrevious();
return true;
case "+":
reader.zoomIn();
return true;
case "-":
reader.zoomOut();
return true;
case "=":
reader.zoomReset();
return true;
case "o":
toggleSidebarView(reader, "outline");
return true;
case "a":
toggleSidebarView(reader, "annotations");
return true;
case "dd":
reader.deleteAnnotations(reader._state.selectedAnnotationIDs);
return true;
case "c":
editComment(reader);
return true;
case "y":
reader.copy();
return true;
case "C":
toggleColumns();
return true;
case "Escape":
reader.setTool({ type: "pointer" });
return false;
case "g":
case "d":
pending = seq;
return true;
}
return false;
};
window.addEventListener("keydown", (event) => {
if (handle(event)) {
event.preventDefault();
event.stopImmediatePropagation();
}
}, true);
return (reader) => {
const keyboard = reader._keyboardManager;
const original = keyboard.handleViewKeyDown.bind(keyboard);
keyboard.handleViewKeyDown = (event) => {
if (handle(event)) {
event.preventDefault();
event.stopPropagation();
return;
}
original(event);
};
};
}
var noop = () => {
};
var requiredCallbacks = Object.fromEntries([
"onAddToNote",
"onChangeSidebarView",
"onChangeSidebarWidth",
"onChangeViewState",
"onDeletePages",
"onOpenLink",
"onOpenReadAloudFirstRunPopup",
"onReadAloudEngineStateChanged",
"onRotatePages",
"onSaveCustomThemes",
"onSetDarkTheme",
"onSetDataTransferAnnotations",
"onSetLightTheme",
"onSetReadAloudVoice",
"onSetZoom",
"onTextSelectionAnnotationModeChange",
"onToggleSidebar"
].map((name) => [name, noop]));
if (!("speechSynthesis" in window)) {
Object.defineProperty(window, "speechSynthesis", {
value: Object.assign(new EventTarget(), { getVoices: () => [], speak: noop, cancel: noop })
});
}
function reportError(action) {
return (e) => {
console.error(e);
window.alert(`${action} failed: ${e}`);
};
}
async function openReader() {
let reader;
let toggleColumns = noop;
const hookVimIntoView = installVim(() => reader, () => toggleColumns());
const store2 = new AnnotationStore();
const launch = await invoke("launch");
if (!launch) return false;
const buf = new Uint8Array(await invoke("read_pdf"));
const annotations = await store2.load().catch((e) => {
reportError("Loading annotations")(e);
return [];
});
const showImage = (a) => reader.setAnnotations([a]);
document.title = launch.title;
const attachColumns = installColumns(launch.title);
document.head.append(Object.assign(document.createElement("style"), {
textContent: "@media (max-width: 799px) { .toolbar { overflow-x: auto; scrollbar-width: none; } .toolbar > * { flex-shrink: 0; } .toolbar #numPages div { position: static; } }"
}));
reader = window.createReader({
...requiredCallbacks,
type: "pdf",
data: { buf },
title: launch.title,
annotations,
primaryViewState: launch.page ? { pageIndex: launch.page - 1 } : void 0,
readOnly: false,
showAnnotations: true,
sidebarOpen: window.innerWidth >= 800,
sidebarWidth: 240,
sidebarView: "annotations",
authorName: "",
onConfirm: (title, text) => window.confirm(`${title}
${text}`),
onSaveAnnotations: (changed) => store2.save(changed, showImage).catch(reportError("Saving annotations")),
onDeleteAnnotations: (ids) => store2.delete(ids).catch(reportError("Deleting annotations"))
});
hookVimIntoView(reader);
installPen(reader);
toggleColumns = attachColumns(reader);
settleEink(reader).catch(reportError("Setting up e-ink mode"));
reader.initializedPromise.then(() => store2.renderMissing(annotations, showImage)).catch(reportError("Rendering annotation images"));
return true;
}
await openReader();