feat(注解渲染): 修复注解命中、点击与定位异常
build.yaml / build (push) Successful in 48s

This commit is contained in:
xiaoqidun committed 2026-10-14 19:47:20 +08:00
1 parent 07e1ec18e1
commit 82d78feb6b
3 files changed
+49 -22

No files matched your search

-1
View File
@@ -3129,7 +3129,6 @@ audio.page-media {
.annotation-note {
position: absolute;
z-index: 2;
opacity: 0;
pointer-events: none;
width: 22px;
+20 -10
View File
@@ -10221,8 +10221,9 @@ async function focusPageRegion(region, label, alignment = "nearest") {
if (label === "注解") {
for (const note of pageShell(pageIndex)?.querySelectorAll(".annotation-note") || []) {
if (note.dataset.annotationId !== String(region.id)) continue;
window.clearTimeout(note.locateTimer);
note.classList.add("located");
window.setTimeout(() => note.classList.remove("located"), 1800);
note.locateTimer = window.setTimeout(() => note.classList.remove("located"), 1800);
}
}
setStatus(`已定位${label}(第${region.page}页)`);
@@ -10254,20 +10255,30 @@ function highlightPageRegion(region, alignment = "nearest") {
}
function clearRegionHighlights() {
for (const note of el.svgHost.querySelectorAll(".annotation-note.located")) note.classList.remove("located");
for (const note of el.svgHost.querySelectorAll(".annotation-note.located")) {
window.clearTimeout(note.locateTimer);
note.classList.remove("located");
}
for (const mark of el.svgHost.querySelectorAll(".region-highlight")) {
mark.remove();
}
}
function mountAnnotationNotes(index, surface, annotations) {
for (const note of surface.querySelectorAll(".annotation-note")) note.remove();
for (const note of surface.querySelectorAll(".annotation-note")) {
window.clearTimeout(note.locateTimer);
note.remove();
}
surface.onpointermove = surface.onpointerleave = null;
if (!state.renderAnnotations) return;
const page = state.doc.pages[index];
const notes = [];
for (const annotation of annotations || []) {
if (!annotation.visible || annotationKind(annotation) === "Link" || annotation.page !== index + 1 || !annotation.remark?.trim()) continue;
const bounds = annotation.bounds;
const region = !bounds ? annotation : bounds.W > 0 && bounds.H > 0
? {x: bounds.X, y: bounds.Y, width: bounds.W, height: bounds.H, contours: annotation.contours || []}
: {x: annotation.x, y: annotation.y, width: 22 / MM_TO_PX, height: 22 / MM_TO_PX, contours: []};
const note = document.createElement("button");
note.type = "button";
note.className = "annotation-note";
@@ -10278,8 +10289,8 @@ function mountAnnotationNotes(index, surface, annotations) {
note.title = "查看注解";
note.setAttribute("aria-label", "查看注解");
note.setAttribute("aria-haspopup", "dialog");
note.style.left = `${Math.max(22 / MM_TO_PX, Math.min(page.width, annotation.x + annotation.width)) * MM_TO_PX}px`;
note.style.top = `${Math.max(0, Math.min(page.height - 22 / MM_TO_PX, annotation.y)) * MM_TO_PX}px`;
note.style.left = `${Math.max(22 / MM_TO_PX, Math.min(page.width, region.x + region.width)) * MM_TO_PX}px`;
note.style.top = `${Math.max(0, Math.min(page.height - 22 / MM_TO_PX, region.y)) * MM_TO_PX}px`;
note.addEventListener("click", () => {
if (state.editing || document.body.hasAttribute("aria-busy")) return;
el.annotationDetail.replaceChildren();
@@ -10295,18 +10306,17 @@ function mountAnnotationNotes(index, surface, annotations) {
el.annotationNote.showModal();
});
surface.append(note);
notes.push({note, annotation});
notes.push({note, region});
}
if (!notes.length) return;
// 从页面监听悬停,不用透明热区拦截正文选择和链接操作。
surface.onpointermove = event => {
if (event.pointerType === "touch" || state.editing) return;
const point = pagePoint(event.clientX, event.clientY, surface.getBoundingClientRect(), page, state.rotation);
const hovered = notes.map(({note, annotation}) => {
const overAction = event.target?.closest?.(".page-link, .page-media");
const hovered = notes.map(({note, region}) => {
const rect = note.getBoundingClientRect();
const overNote = event.clientX >= rect.left && event.clientX <= rect.right && event.clientY >= rect.top && event.clientY <= rect.bottom;
const overRegion = point.x >= annotation.x && point.x <= annotation.x + annotation.width && point.y >= annotation.y && point.y <= annotation.y + annotation.height;
return overNote || overRegion;
return !overAction && (overNote || canvasEditor.contains(region, point));
});
notes.forEach(({note}, i) => note.classList.toggle("active", hovered[i]));
};
+29 -11
View File
@@ -158,16 +158,23 @@ type PageInfo struct {
// PageSVG 页面SVG结果
type PageSVG struct {
Index int `json:"index"`
Number int `json:"number"`
ID string `json:"id"`
Width float64 `json:"width"`
Height float64 `json:"height"`
SVG string `json:"svg"`
Links []ofdgo.PageLink `json:"-"`
Fonts []ofdgo.SVGFont `json:"-"`
Images []ofdgo.SVGImage `json:"-"`
Annotations []ofdgo.AnnotationInfo `json:"annotations,omitempty"`
Index int `json:"index"`
Number int `json:"number"`
ID string `json:"id"`
Width float64 `json:"width"`
Height float64 `json:"height"`
SVG string `json:"svg"`
Links []ofdgo.PageLink `json:"-"`
Fonts []ofdgo.SVGFont `json:"-"`
Images []ofdgo.SVGImage `json:"-"`
Annotations []AnnotationPreview `json:"annotations,omitempty"`
}
// AnnotationPreview 注解预览信息及页面坐标中的交互范围
type AnnotationPreview struct {
ofdgo.AnnotationInfo
Bounds ofdgo.Box `json:"bounds"`
Contours []ofdgo.ObjectContour `json:"contours,omitempty"`
}
// ExportFormat 导出格式
@@ -617,10 +624,21 @@ func (s *Session) RenderPage(index int, backend string, dpi float64, raster bool
if err != nil {
return PageSVG{}, err
}
annotations, err := s.Reader.AnnotationInfosByIndex(index)
infos, err := s.Reader.AnnotationInfosByIndex(index)
if err != nil {
return PageSVG{}, err
}
annotations := make([]AnnotationPreview, len(infos))
for i, info := range infos {
annotations[i].AnnotationInfo = info
if !renderer.RenderAnnotations || !info.Visible || strings.TrimSpace(info.Remark) == "" || info.Type == "Link" || info.Type == "Path" && info.Subtype == "Link" {
continue
}
bounds, contours, err := renderer.AnnotationGeometry(s.Reader.Annots[page.ID][i])
if err == nil {
annotations[i].Bounds, annotations[i].Contours = bounds, contours
}
}
clear(s.svgFonts)
for i, font := range resources.Fonts {
s.svgFonts[font.Name] = font.Data