mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 17:27:08 +08:00
This commit is contained in:
1 parent
2ea937b7be
commit
1c7f3c03e9
6 files changed
+189
-11
No files matched your search
@@ -785,6 +785,34 @@ button.outline-link:hover {
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
.text-layer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
color: transparent;
|
||||
opacity: 0.24;
|
||||
pointer-events: none;
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
text-size-adjust: none;
|
||||
}
|
||||
|
||||
.text-run > span {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
font: 100px/1 sans-serif;
|
||||
white-space: pre;
|
||||
transform-origin: 0 0;
|
||||
cursor: text;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.text-run > span::selection {
|
||||
color: transparent;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.page-link {
|
||||
position: absolute;
|
||||
}
|
||||
@@ -833,6 +861,8 @@ button.outline-link:hover {
|
||||
display: block;
|
||||
max-width: none;
|
||||
transform-origin: 0 0;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.meta-list {
|
||||
|
||||
+81
-4
@@ -50,6 +50,7 @@ const state = {
|
||||
continuous: false,
|
||||
renderAnnotations: true,
|
||||
pageCache: new Map(),
|
||||
selectedPages: new Set(),
|
||||
pageInFlight: new Map(),
|
||||
pageRenderQueue: [],
|
||||
pageRenderRunning: false,
|
||||
@@ -194,6 +195,12 @@ el.fitButton.addEventListener("click", fitWidth);
|
||||
el.fitHeightButton.addEventListener("click", fitHeight);
|
||||
el.continuousButton.addEventListener("click", toggleContinuous);
|
||||
el.annotationButton.addEventListener("click", toggleAnnotations);
|
||||
document.addEventListener("copy", copySelection);
|
||||
document.addEventListener("selectionchange", () => {
|
||||
for (const index of state.selectedPages) {
|
||||
unmountPage(index);
|
||||
}
|
||||
});
|
||||
el.pageExportFormat.addEventListener("change", () => updateDPIControl());
|
||||
el.exportPageButton.addEventListener("click", exportCurrentPage);
|
||||
el.exportButton.addEventListener("click", exportPDF);
|
||||
@@ -1471,6 +1478,7 @@ function renderPageFlow() {
|
||||
|
||||
function resetPageFlow() {
|
||||
state.pageCache.clear();
|
||||
state.selectedPages.clear();
|
||||
state.visiblePages.clear();
|
||||
for (const task of state.pageInFlight.values()) {
|
||||
task.resolve(null);
|
||||
@@ -1509,13 +1517,11 @@ function flowPageObserver() {
|
||||
const index = Number.parseInt(entry.target.dataset.pageIndex, 10);
|
||||
if (!entry.isIntersecting) {
|
||||
state.visiblePages.delete(index);
|
||||
if (entry.target.classList.contains("rendered")) {
|
||||
entry.target.querySelector(".page-surface").replaceChildren();
|
||||
entry.target.classList.remove("rendered");
|
||||
}
|
||||
unmountPage(index);
|
||||
continue;
|
||||
}
|
||||
state.visiblePages.add(index);
|
||||
state.selectedPages.delete(index);
|
||||
renderFlowPage(index, { openSeq, priority: 4 });
|
||||
}
|
||||
}, {
|
||||
@@ -1526,6 +1532,18 @@ function flowPageObserver() {
|
||||
return state.pageObserver;
|
||||
}
|
||||
|
||||
function unmountPage(index) {
|
||||
const shell = pageShell(index);
|
||||
const selection = document.getSelection();
|
||||
if (selection.rangeCount && selection.getRangeAt(0).intersectsNode(shell)) {
|
||||
state.selectedPages.add(index);
|
||||
return;
|
||||
}
|
||||
shell.querySelector(".page-surface").replaceChildren();
|
||||
shell.classList.remove("rendered");
|
||||
state.selectedPages.delete(index);
|
||||
}
|
||||
|
||||
async function renderFlowPage(index, options = {}) {
|
||||
const openSeq = options.openSeq || state.openSeq;
|
||||
if (openSeq !== state.openSeq) {
|
||||
@@ -1638,6 +1656,7 @@ function mountPageSVG(index, page, openSeq = state.openSeq) {
|
||||
const svg = parseSVG(page.svg, `p${openSeq}-${index}`);
|
||||
svg.classList.add("ofd-svg");
|
||||
surface.replaceChildren(svg);
|
||||
surface.append(createTextLayer(page.text));
|
||||
for (const link of page.links) {
|
||||
let url;
|
||||
try {
|
||||
@@ -1668,6 +1687,64 @@ function mountPageSVG(index, page, openSeq = state.openSeq) {
|
||||
}
|
||||
}
|
||||
|
||||
function createTextLayer(text) {
|
||||
const layer = document.createElement("div");
|
||||
layer.className = "text-layer";
|
||||
const measure = document.createElement("canvas").getContext("2d");
|
||||
measure.font = "100px sans-serif";
|
||||
for (const run of text.runs || []) {
|
||||
const line = document.createElement("span");
|
||||
line.className = "text-run";
|
||||
const chars = Array.from(run.text);
|
||||
for (const item of run.spans || []) {
|
||||
const span = document.createElement("span");
|
||||
span.textContent = chars.slice(item.start, item.end).join("");
|
||||
const width = measure.measureText(span.textContent).width || 1;
|
||||
const [a, b, c, d, e, f] = item.matrix.map((value) => value * MM_TO_PX);
|
||||
span.style.transform = `matrix(${a / width}, ${b / width}, ${c / 100}, ${d / 100}, ${e}, ${f})`;
|
||||
line.append(span);
|
||||
}
|
||||
layer.append(line);
|
||||
}
|
||||
return layer;
|
||||
}
|
||||
|
||||
function copySelection(event) {
|
||||
if (event.target.closest?.("input, textarea, [contenteditable]")) {
|
||||
return;
|
||||
}
|
||||
const selection = document.getSelection();
|
||||
if (selection.isCollapsed || !selection.rangeCount) {
|
||||
return;
|
||||
}
|
||||
const range = selection.getRangeAt(0);
|
||||
if (!el.svgHost.contains(range.startContainer) || !el.svgHost.contains(range.endContainer)) {
|
||||
return;
|
||||
}
|
||||
const lines = [];
|
||||
for (const run of el.svgHost.querySelectorAll(".text-run")) {
|
||||
if (!range.intersectsNode(run)) {
|
||||
continue;
|
||||
}
|
||||
const part = range.cloneRange();
|
||||
if (!run.contains(part.startContainer)) {
|
||||
part.setStart(run, 0);
|
||||
}
|
||||
if (!run.contains(part.endContainer)) {
|
||||
part.setEnd(run, run.childNodes.length);
|
||||
}
|
||||
const text = part.cloneContents().textContent;
|
||||
if (text) {
|
||||
lines.push(text);
|
||||
}
|
||||
}
|
||||
if (!lines.length) {
|
||||
return;
|
||||
}
|
||||
event.clipboardData.setData("text/plain", lines.join("\n"));
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
function markFlowPageError(index, err) {
|
||||
const shell = pageShell(index);
|
||||
if (!shell) {
|
||||
|
||||
Reference in new issue
Block a user