hterm: switch cursor positioning to use CSS vars

Calculating the position of the cursor in pixels is done entirely in JS.
The actual display of rows and their cumulative height is done in CSS.
This leads to a situation where, on some zoom settings, the precision of
floating point math in the two systems are slightly different leading to
skew in positioning.

Since we're already exporting the character size to CSS variables, we can
export the position (in cols/rows) to CSS too, and then let the CSS side
do the final calculation.  This simplifies the JS code, makes the CSS code
more readable, and improves the cursor positioning over zoom levels.

Change-Id: Ibcb98c8f37cede5f2538892a4eb4d260602498d0
Reviewed-on: https://chromium-review.googlesource.com/602793
Reviewed-by: Brandon Gilmore <varz@google.com>
Tested-by: Mike Frysinger <vapier@chromium.org>
diff --git a/hterm/js/hterm_terminal.js b/hterm/js/hterm_terminal.js
index f40bfc1..374e71c 100644
--- a/hterm/js/hterm_terminal.js
+++ b/hterm/js/hterm_terminal.js
@@ -1408,6 +1408,8 @@
        ':root {' +
        '  --hterm-charsize-width: ' + this.scrollPort_.characterSize.width + 'px;' +
        '  --hterm-charsize-height: ' + this.scrollPort_.characterSize.height + 'px;' +
+       '  --hterm-cursor-offset-col: 0;' +
+       '  --hterm-cursor-offset-row: 0;' +
        '  --hterm-blink-node-duration: 0.7s;' +
        '  --hterm-mouse-cursor-text: text;' +
        '  --hterm-mouse-cursor-pointer: default;' +
@@ -1430,7 +1432,8 @@
   this.cursorNode_.className = 'cursor-node';
   this.cursorNode_.style.cssText =
       ('position: absolute;' +
-       'top: -99px;' +
+       'left: calc(var(--hterm-charsize-width) * var(--hterm-cursor-offset-col));' +
+       'top: calc(var(--hterm-charsize-height) * var(--hterm-cursor-offset-row));' +
        'display: block;' +
        'width: var(--hterm-charsize-width);' +
        'height: var(--hterm-charsize-height);' +
@@ -2619,7 +2622,7 @@
 
   if (cursorRowIndex > bottomRowIndex) {
     // Cursor is scrolled off screen, move it outside of the visible area.
-    this.cursorNode_.style.top = -this.scrollPort_.characterSize.height + 'px';
+    this.setCssVar('cursor-offset-row', '-1');
     return;
   }
 
@@ -2629,16 +2632,18 @@
     this.cursorNode_.style.display = '';
   }
 
-
-  this.cursorNode_.style.top = this.scrollPort_.visibleRowTopMargin +
-      this.scrollPort_.characterSize.height * (cursorRowIndex - topRowIndex) +
-      'px';
-  this.cursorNode_.style.left = this.scrollPort_.characterSize.width *
-      this.screen_.cursorPosition.column + 'px';
+  // Position the cursor using CSS variable math.  If we do the math in JS,
+  // the float math will end up being more precise than the CSS which will
+  // cause the cursor tracking to be off.
+  this.setCssVar(
+      'cursor-offset-row',
+      `${cursorRowIndex - topRowIndex} + ` +
+      `${this.scrollPort_.visibleRowTopMargin}px`);
+  this.setCssVar('cursor-offset-col', this.screen_.cursorPosition.column);
 
   this.cursorNode_.setAttribute('title',
-                                '(' + this.screen_.cursorPosition.row +
-                                ', ' + this.screen_.cursorPosition.column +
+                                '(' + this.screen_.cursorPosition.column +
+                                ', ' + this.screen_.cursorPosition.row +
                                 ')');
 
   // Update the caret for a11y purposes.