mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 14:53:44 +08:00
This commit is contained in:
1 parent
a4ae065d43
commit
651e3cf5b5
4 files changed
+135
-8
No files matched your search
+90
-8
@@ -57,6 +57,7 @@ const state = {
|
||||
pageCache: new Map(),
|
||||
svgFonts: new Map(),
|
||||
selectedPages: new Set(),
|
||||
documentSelection: null,
|
||||
pageInFlight: new Map(),
|
||||
pageRenderQueue: [],
|
||||
pageRenderRunning: false,
|
||||
@@ -207,11 +208,7 @@ el.panButton.addEventListener("click", togglePan);
|
||||
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);
|
||||
}
|
||||
});
|
||||
document.addEventListener("selectionchange", syncSelection);
|
||||
el.exportFormat.addEventListener("change", () => updateDPIControl());
|
||||
el.exportPageButton.addEventListener("click", () => exportFile(false));
|
||||
el.exportButton.addEventListener("click", openExportPanel);
|
||||
@@ -289,10 +286,22 @@ updateSidebarState();
|
||||
boot();
|
||||
|
||||
function handleKeyDown(event) {
|
||||
if (event.defaultPrevented || event.isComposing || event.altKey || document.body.hasAttribute("aria-busy") || el.exportPanel.open) {
|
||||
if (event.defaultPrevented || event.isComposing || event.altKey) {
|
||||
return;
|
||||
}
|
||||
const key = event.key;
|
||||
const target = event.target;
|
||||
if ((event.ctrlKey || event.metaKey) && !event.shiftKey && key.toLowerCase() === "a" && state.doc
|
||||
&& el.viewerPanel.contains(target) && !target.closest("input, textarea, select, [contenteditable]")) {
|
||||
event.preventDefault();
|
||||
if (!document.body.hasAttribute("aria-busy") && !el.exportPanel.open) {
|
||||
selectDocumentText();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (document.body.hasAttribute("aria-busy") || el.exportPanel.open) {
|
||||
return;
|
||||
}
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
if (!event.shiftKey && key.toLowerCase() === "o") {
|
||||
event.preventDefault();
|
||||
@@ -306,7 +315,13 @@ function handleKeyDown(event) {
|
||||
if (!state.doc) {
|
||||
return;
|
||||
}
|
||||
const target = event.target;
|
||||
if (key === "Escape" && !event.shiftKey && state.documentSelection && el.viewerPanel.contains(target)
|
||||
&& !target.closest("input, textarea, select, [contenteditable]")) {
|
||||
event.preventDefault();
|
||||
document.getSelection().removeAllRanges();
|
||||
syncSelection();
|
||||
return;
|
||||
}
|
||||
if (key === "F3") {
|
||||
event.preventDefault();
|
||||
if (state.searchMatches.length) {
|
||||
@@ -1684,6 +1699,7 @@ function resetPageFlow() {
|
||||
state.svgFonts.clear();
|
||||
state.pageCache.clear();
|
||||
state.selectedPages.clear();
|
||||
state.documentSelection = null;
|
||||
state.visiblePages.clear();
|
||||
for (const task of state.pageInFlight.values()) {
|
||||
task.resolve(null);
|
||||
@@ -1740,7 +1756,8 @@ function flowPageObserver() {
|
||||
function unmountPage(index) {
|
||||
const shell = pageShell(index);
|
||||
const selection = document.getSelection();
|
||||
if (selection.rangeCount && selection.getRangeAt(0).intersectsNode(shell)) {
|
||||
const range = selection.rangeCount ? selection.getRangeAt(0) : null;
|
||||
if (range && (!state.documentSelection || !isDocumentSelection(range)) && range.intersectsNode(shell)) {
|
||||
state.selectedPages.add(index);
|
||||
return;
|
||||
}
|
||||
@@ -1943,6 +1960,62 @@ function createTextLayer(text) {
|
||||
return layer;
|
||||
}
|
||||
|
||||
function syncSelection() {
|
||||
const selection = document.getSelection();
|
||||
if (state.documentSelection && (!selection.rangeCount || !isDocumentSelection(selection.getRangeAt(0)))) {
|
||||
state.documentSelection = null;
|
||||
if (state.doc && !document.body.hasAttribute("aria-busy")) {
|
||||
setStatus(pageStatus(state.pageIndex, state.doc.pageCount));
|
||||
}
|
||||
}
|
||||
for (const index of state.selectedPages) {
|
||||
unmountPage(index);
|
||||
}
|
||||
}
|
||||
|
||||
function isDocumentSelection(range) {
|
||||
return range.startContainer === el.svgHost && range.startOffset === 0
|
||||
&& range.endContainer === el.svgHost && range.endOffset === el.svgHost.childNodes.length;
|
||||
}
|
||||
|
||||
async function selectDocumentText() {
|
||||
const selection = document.getSelection();
|
||||
if (state.documentSelection && selection.rangeCount && isDocumentSelection(selection.getRangeAt(0))) {
|
||||
return;
|
||||
}
|
||||
const openSeq = state.openSeq;
|
||||
const current = { text: null };
|
||||
const active = () => state.documentSelection === current && openSeq === state.openSeq
|
||||
&& selection.rangeCount > 0 && isDocumentSelection(selection.getRangeAt(0));
|
||||
state.documentSelection = current;
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(el.svgHost);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
const pages = [];
|
||||
const count = state.doc.pageCount;
|
||||
try {
|
||||
for (let index = 0; index < count; index += 1) {
|
||||
setStatus(`正在全选 ${index + 1} / ${count} 页`);
|
||||
const text = await callWASM("ofdgoPageTextString", index);
|
||||
if (!active()) {
|
||||
return;
|
||||
}
|
||||
if (text) {
|
||||
pages.push(text);
|
||||
}
|
||||
}
|
||||
current.text = pages.join("\n");
|
||||
setStatus(current.text ? "文字已全选" : "暂无文字");
|
||||
} catch (err) {
|
||||
if (active()) {
|
||||
state.documentSelection = null;
|
||||
selection.removeAllRanges();
|
||||
showError(err, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function copySelection(event) {
|
||||
if (event.target.closest?.("input, textarea, [contenteditable]")) {
|
||||
return;
|
||||
@@ -1952,6 +2025,15 @@ function copySelection(event) {
|
||||
return;
|
||||
}
|
||||
const range = selection.getRangeAt(0);
|
||||
if (state.documentSelection && isDocumentSelection(range)) {
|
||||
event.preventDefault();
|
||||
if (state.documentSelection.text === null) {
|
||||
setStatus("正在全选文字");
|
||||
} else {
|
||||
event.clipboardData.setData("text/plain", state.documentSelection.text);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!el.svgHost.contains(range.startContainer) || !el.svgHost.contains(range.endContainer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ func RunWASM() {
|
||||
registerCallback("ofdgoRenderPage", renderPage)
|
||||
registerCallback("ofdgoSVGFontData", svgFontData)
|
||||
registerCallback("ofdgoSearchPage", searchPage)
|
||||
registerCallback("ofdgoPageTextString", pageTextString)
|
||||
registerCallback("ofdgoExportFormats", exportFormats)
|
||||
registerCallback("ofdgoParsePageRange", parsePageRange)
|
||||
registerExportCallback("ofdgoExportPage", exportPage)
|
||||
@@ -283,6 +284,16 @@ func renderPage(args []js.Value) (any, error) {
|
||||
}), nil
|
||||
}
|
||||
|
||||
// pageTextString 提取OFD页面原文
|
||||
// 入参: args 浏览器参数
|
||||
// 返回: any 页面原文, error 错误信息
|
||||
func pageTextString(args []js.Value) (any, error) {
|
||||
if currentSession == nil {
|
||||
return nil, fmt.Errorf("ofd document is not opened")
|
||||
}
|
||||
return currentSession.PageTextString(args[0].Int())
|
||||
}
|
||||
|
||||
// searchPage 搜索OFD页面文字
|
||||
// 入参: args 浏览器参数
|
||||
// 返回: any 搜索结果, error 错误信息
|
||||
|
||||
@@ -289,6 +289,24 @@ func (s *Session) PageText(index int) (*ofdgo.PageText, error) {
|
||||
return text, nil
|
||||
}
|
||||
|
||||
// PageTextString 获取页面原文,不为全文复制保留额外的页面和文字索引
|
||||
// 入参: index 页面索引
|
||||
// 返回: string 页面原文, error 错误信息
|
||||
func (s *Session) PageTextString(index int) (string, error) {
|
||||
text := s.textCache[index]
|
||||
if text == nil {
|
||||
page, err := s.Reader.PageContentByIndex(index)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
text, err = s.Renderer.PageText(page)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return text.String(), nil
|
||||
}
|
||||
|
||||
// SearchPage 搜索指定页面,复用当前会话的文字索引
|
||||
// 入参: index 页面索引, query 搜索文字
|
||||
// 返回: []ofdgo.TextMatch 匹配结果, error 错误信息
|
||||
|
||||
@@ -74,6 +74,22 @@ func (r *Renderer) PageText(page *PageContent) (*PageText, error) {
|
||||
return renderer.pageText, nil
|
||||
}
|
||||
|
||||
// String 按文本对象顺序合并原文,以换行分隔非空对象
|
||||
// 返回: string 页面原文
|
||||
func (p *PageText) String() string {
|
||||
var text strings.Builder
|
||||
for _, run := range p.Runs {
|
||||
if run.Text == "" {
|
||||
continue
|
||||
}
|
||||
if text.Len() > 0 {
|
||||
text.WriteByte('\n')
|
||||
}
|
||||
text.WriteString(run.Text)
|
||||
}
|
||||
return text.String()
|
||||
}
|
||||
|
||||
// Search 按原文进行忽略大小写的字面匹配,不跨文本对象拼接或识别图像
|
||||
// 入参: query 搜索文字
|
||||
// 返回: []TextMatch 匹配结果及上下文
|
||||
|
||||
Reference in new issue
Block a user