Browse Source

improve(pdf): optimize text selection range rects

charlie 4 years ago
parent
commit
c9c5f20e4c

+ 12 - 6
src/main/frontend/extensions/pdf/utils.cljs

@@ -22,6 +22,11 @@
   [bounding ^js viewport]
   (bean/->clj (js-utils/scaledToViewport (bean/->js bounding) viewport)))
 
+(defn optimize-client-reacts
+  [rects]
+  (when (seq rects)
+    (bean/->clj (js-utils/optimizeClientRects (bean/->js rects)))))
+
 (defn vw-to-scaled-pos
   [^js viewer {:keys [page bounding rects]}]
   (when-let [^js viewport (.. viewer (getPageView (dec page)) -viewport)]
@@ -144,12 +149,13 @@
 
 (defn get-range-rects<-page-cnt
   [^js/Range r ^js page-cnt]
-  (let [rge-rects (js->clj (.getClientRects r))
+  (let [rge-rects (bean/->clj (.getClientRects r))
         ^js cnt-offset (.getBoundingClientRect page-cnt)]
 
     (if (seq rge-rects)
-      (for [rect rge-rects]
-        {:top    (- (+ (.-top rect) (.-scrollTop page-cnt)) (.-top cnt-offset))
-         :left   (- (+ (.-left rect) (.-scrollLeft page-cnt)) (.-left cnt-offset))
-         :width  (.-width rect)
-         :height (.-height rect)}))))
+      (let [rects (for [rect rge-rects]
+                    {:top    (- (+ (.-top rect) (.-scrollTop page-cnt)) (.-top cnt-offset))
+                     :left   (- (+ (.-left rect) (.-scrollLeft page-cnt)) (.-left cnt-offset))
+                     :width  (.-width rect)
+                     :height (.-height rect)})]
+        (optimize-client-reacts rects)))))

+ 75 - 0
src/main/frontend/extensions/pdf/utils.js

@@ -108,4 +108,79 @@ export const scrollToHighlight = (viewer, highlight) => {
     ],
     ignoreDestinationZoom: true
   })
+}
+
+export const optimizeClientRects = (clientRects) => {
+  const sort = rects =>
+    rects.sort((A, B) => {
+      const top = A.top - B.top
+
+      if (top === 0) {
+        return A.left - B.left
+      }
+
+      return top
+    })
+
+  const overlaps = (A, B) => A.left <= B.left && B.left <= A.left + A.width
+  const sameLine = (A, B, yMargin = 5) =>
+    Math.abs(A.top - B.top) < yMargin && Math.abs(A.height - B.height) < yMargin
+
+  const inside = (A, B) =>
+    A.top > B.top &&
+    A.left > B.left &&
+    A.top + A.height < B.top + B.height &&
+    A.left + A.width < B.left + B.width
+
+  const nextTo = (A, B, xMargin = 10) => {
+    const Aright = A.left + A.width
+    const Bright = B.left + B.width
+
+    return A.left <= B.left && Aright <= Bright && B.left - Aright <= xMargin
+  }
+  const extendWidth = (A, B) => {
+    // extend width of A to cover B
+    A.width = Math.max(B.width - A.left + B.left, A.width)
+  }
+
+  const rects = sort(clientRects)
+  const toRemove = new Set()
+
+  const firstPass = rects.filter(rect => {
+    return rects.every(otherRect => {
+      return !inside(rect, otherRect)
+    })
+  })
+
+  let passCount = 0
+
+  while (passCount <= 2) {
+    firstPass.forEach(A => {
+      firstPass.forEach(B => {
+        if (A === B || toRemove.has(A) || toRemove.has(B)) {
+          return
+        }
+
+        if (!sameLine(A, B)) {
+          return
+        }
+
+        if (overlaps(A, B)) {
+          extendWidth(A, B)
+          A.height = Math.max(A.height, B.height)
+
+          toRemove.add(B)
+        }
+
+        if (nextTo(A, B)) {
+          extendWidth(A, B)
+
+          toRemove.add(B)
+        }
+      })
+    })
+    passCount += 1
+  }
+
+  return firstPass.filter(rect => !toRemove.has(rect))
 }