From 68af0d86c3a941e185b34926fdbc57c457466e28 Mon Sep 17 00:00:00 2001
From: toasted-nutbread <toasted-nutbread@users.noreply.github.com>
Date: Sat, 31 Aug 2019 11:51:31 -0400
Subject: Improve popup position for vertical text

---
 ext/fg/js/frontend.js |   8 ++-
 ext/fg/js/popup.js    | 147 +++++++++++++++++++++++++++++++++++++-------------
 ext/fg/js/source.js   |  25 +++++++++
 3 files changed, 141 insertions(+), 39 deletions(-)

(limited to 'ext/fg/js')

diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index 3c5f2ac8..72379062 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -301,7 +301,11 @@ class Frontend {
         } catch (e) {
             if (window.yomichan_orphaned) {
                 if (textSource && this.options.scanning.modifier !== 'none') {
-                    this.popup.showOrphaned(textSource.getRect(), this.options);
+                    this.popup.showOrphaned(
+                        textSource.getRect(),
+                        textSource.getWritingMode(),
+                        this.options
+                    );
                 }
             } else {
                 this.onError(e);
@@ -332,6 +336,7 @@ class Frontend {
         const url = window.location.href;
         this.popup.termsShow(
             textSource.getRect(),
+            textSource.getWritingMode(),
             definitions,
             this.options,
             {sentence, url, focus}
@@ -357,6 +362,7 @@ class Frontend {
         const url = window.location.href;
         this.popup.kanjiShow(
             textSource.getRect(),
+            textSource.getWritingMode(),
             definitions,
             this.options,
             {sentence, url, focus}
diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js
index 18dc0386..138dec41 100644
--- a/ext/fg/js/popup.js
+++ b/ext/fg/js/popup.js
@@ -48,59 +48,130 @@ class Popup {
         return this.injected;
     }
 
-    async show(elementRect, options) {
+    async show(elementRect, writingMode, options) {
         await this.inject(options);
 
-        const containerStyle = window.getComputedStyle(this.container);
-        const containerHeight = parseInt(containerStyle.height);
-        const containerWidth = parseInt(containerStyle.width);
+        const optionsGeneral = options.general;
+        const container = this.container;
+        const containerRect = container.getBoundingClientRect();
+        const getPosition = (
+            writingMode === 'horizontal-tb' || optionsGeneral.popupVerticalTextPosition === 'default' ?
+            Popup.getPositionForHorizontalText :
+            Popup.getPositionForVerticalText
+        );
 
-        const limitX = document.body.clientWidth;
-        const limitY = window.innerHeight;
+        const [x, y, width, height, below] = getPosition(
+            elementRect,
+            Math.max(containerRect.width, optionsGeneral.popupWidth),
+            Math.max(containerRect.height, optionsGeneral.popupHeight),
+            document.body.clientWidth,
+            window.innerHeight,
+            optionsGeneral,
+            writingMode
+        );
 
-        let x = elementRect.left + options.general.popupHorizontalOffset;
-        let width = Math.max(containerWidth, options.general.popupWidth);
-        const overflowX = Math.max(x + width - limitX, 0);
+        container.classList.toggle('yomichan-float-full-width', optionsGeneral.popupDisplayMode === 'full-width');
+        container.classList.toggle('yomichan-float-above', !below);
+        container.style.left = `${x}px`;
+        container.style.top = `${y}px`;
+        container.style.width = `${width}px`;
+        container.style.height = `${height}px`;
+        container.style.visibility = 'visible';
+    }
+
+    static getPositionForHorizontalText(elementRect, width, height, maxWidth, maxHeight, optionsGeneral) {
+        let x = elementRect.left + optionsGeneral.popupHorizontalOffset;
+        const overflowX = Math.max(x + width - maxWidth, 0);
         if (overflowX > 0) {
             if (x >= overflowX) {
                 x -= overflowX;
             } else {
-                width = limitX;
+                width = maxWidth;
                 x = 0;
             }
         }
 
-        let above = false;
-        let y = 0;
-        let height = Math.max(containerHeight, options.general.popupHeight);
-        const yBelow = elementRect.bottom + options.general.popupVerticalOffset;
-        const yAbove = elementRect.top - options.general.popupVerticalOffset;
-        const overflowBelow = Math.max(yBelow + height - limitY, 0);
-        const overflowAbove = Math.max(height - yAbove, 0);
-        if (overflowBelow > 0 || overflowAbove > 0) {
-            if (overflowBelow < overflowAbove) {
-                height = Math.max(height - overflowBelow, 0);
-                y = yBelow;
+        const verticalOffset = optionsGeneral.popupVerticalOffset;
+        const [y, h, below] = Popup.limitGeometry(
+            elementRect.top - verticalOffset,
+            elementRect.bottom + verticalOffset,
+            height,
+            maxHeight,
+            true
+        );
+
+        return [x, y, width, h, below];
+    }
+
+    static getPositionForVerticalText(elementRect, width, height, maxWidth, maxHeight, optionsGeneral, writingMode) {
+        const preferRight = Popup.isVerticalTextPopupOnRight(optionsGeneral.popupVerticalTextPosition, writingMode);
+        const horizontalOffset = optionsGeneral.popupHorizontalOffset2;
+        const verticalOffset = optionsGeneral.popupVerticalOffset2;
+
+        const [x, w] = Popup.limitGeometry(
+            elementRect.left - horizontalOffset,
+            elementRect.right + horizontalOffset,
+            width,
+            maxWidth,
+            preferRight
+        );
+        const [y, h, below] = Popup.limitGeometry(
+            elementRect.bottom - verticalOffset,
+            elementRect.top + verticalOffset,
+            height,
+            maxHeight,
+            true
+        );
+        return [x, y, w, h, below];
+    }
+
+    static isVerticalTextPopupOnRight(positionPreference, writingMode) {
+        switch (positionPreference) {
+            case 'before':
+                return !Popup.isWritingModeLeftToRight(writingMode);
+            case 'after':
+                return Popup.isWritingModeLeftToRight(writingMode);
+            case 'left':
+                return false;
+            case 'right':
+                return true;
+        }
+    }
+
+    static isWritingModeLeftToRight(writingMode) {
+        switch (writingMode) {
+            case 'vertical-lr':
+            case 'sideways-lr':
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    static limitGeometry(positionBefore, positionAfter, size, limit, preferAfter) {
+        let after = preferAfter;
+        let position = 0;
+        const overflowBefore = Math.max(0, size - positionBefore);
+        const overflowAfter = Math.max(0, positionAfter + size - limit);
+        if (overflowAfter > 0 || overflowBefore > 0) {
+            if (overflowAfter < overflowBefore) {
+                size = Math.max(0, size - overflowAfter);
+                position = positionAfter;
+                after = true;
             } else {
-                height = Math.max(height - overflowAbove, 0);
-                y = Math.max(yAbove - height, 0);
-                above = true;
+                size = Math.max(0, size - overflowBefore);
+                position = Math.max(0, positionBefore - size);
+                after = false;
             }
         } else {
-            y = yBelow;
+            position = preferAfter ? positionAfter : positionBefore - size;
         }
 
-        this.container.classList.toggle('yomichan-float-full-width', options.general.popupDisplayMode === 'full-width');
-        this.container.classList.toggle('yomichan-float-above', above);
-        this.container.style.left = `${x}px`;
-        this.container.style.top = `${y}px`;
-        this.container.style.width = `${width}px`;
-        this.container.style.height = `${height}px`;
-        this.container.style.visibility = 'visible';
+        return [position, size, after];
     }
 
-    async showOrphaned(elementRect, options) {
-        await this.show(elementRect, options);
+    async showOrphaned(elementRect, writingMode, options) {
+        await this.show(elementRect, writingMode, options);
         this.invokeApi('orphaned');
     }
 
@@ -136,13 +207,13 @@ class Popup {
         return contained;
     }
 
-    async termsShow(elementRect, definitions, options, context) {
-        await this.show(elementRect, options);
+    async termsShow(elementRect, writingMode, definitions, options, context) {
+        await this.show(elementRect, writingMode, options);
         this.invokeApi('termsShow', {definitions, options, context});
     }
 
-    async kanjiShow(elementRect, definitions, options, context) {
-        await this.show(elementRect, options);
+    async kanjiShow(elementRect, writingMode, definitions, options, context) {
+        await this.show(elementRect, writingMode, options);
         this.invokeApi('kanjiShow', {definitions, options, context});
     }
 
diff --git a/ext/fg/js/source.js b/ext/fg/js/source.js
index a360b331..a5421934 100644
--- a/ext/fg/js/source.js
+++ b/ext/fg/js/source.js
@@ -61,6 +61,10 @@ class TextSourceRange {
         return this.range.getBoundingClientRect();
     }
 
+    getWritingMode() {
+        return TextSourceRange.getElementWritingMode(TextSourceRange.getParentElement(this.range.startContainer));
+    }
+
     getPaddedRect() {
         const range = this.range.cloneRange();
         const startOffset = range.startOffset;
@@ -204,6 +208,23 @@ class TextSourceRange {
 
         return state.remainder > 0;
     }
+
+    static getParentElement(node) {
+        while (node !== null && node.nodeType !== Node.ELEMENT_NODE) {
+            node = node.parentNode;
+        }
+        return node;
+    }
+
+    static getElementWritingMode(element) {
+        if (element === null) {
+            return 'horizontal-tb';
+        }
+
+        const style = window.getComputedStyle(element);
+        const writingMode = style.writingMode;
+        return typeof writingMode === 'string' ? writingMode : 'horizontal-tb';
+    }
 }
 
 
@@ -267,6 +288,10 @@ class TextSourceElement {
         return this.element.getBoundingClientRect();
     }
 
+    getWritingMode() {
+        return 'horizontal-tb';
+    }
+
     select() {
         // NOP
     }
-- 
cgit v1.2.3