Rebased ref, commits from common ancestor:
commit aef75914e5f711f98e9489e64223911a6fcae45e
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sun May 24 22:58:36 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:45 2020 +0530

    Handle print-twips 'cellviewcursor' msg correctly
    
    Change-Id: I744a24aa54768f12ea8801f6ceabdd4c79fa483a

diff --git a/loleaflet/src/layer/tile/TileLayer.js 
b/loleaflet/src/layer/tile/TileLayer.js
index fde1d4796..e474ebc28 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -1352,9 +1352,11 @@ L.TileLayer = L.GridLayer.extend({
                        var topLeftTwips = new L.Point(parseInt(strTwips[0]), 
parseInt(strTwips[1]));
                        var offset = new L.Point(parseInt(strTwips[2]), 
parseInt(strTwips[3]));
                        var bottomRightTwips = topLeftTwips.add(offset);
+                       var boundsTwips = this._convertToTileTwipsSheetArea(
+                               new L.Bounds(topLeftTwips, bottomRightTwips));
                        this._cellViewCursors[viewId].bounds = new 
L.LatLngBounds(
-                               this._twipsToLatLng(topLeftTwips, 
this._map.getZoom()),
-                               this._twipsToLatLng(bottomRightTwips, 
this._map.getZoom()));
+                               this._twipsToLatLng(boundsTwips.getTopLeft(), 
this._map.getZoom()),
+                               
this._twipsToLatLng(boundsTwips.getBottomRight(), this._map.getZoom()));
                }
 
                this._cellViewCursors[viewId].part = parseInt(obj.part);
commit 0c05ad4725f8c4c4365ff7a79b627cbaa1a462b3
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sun May 24 20:34:25 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:45 2020 +0530

    Handle print-twips referencemarks msg correctly
    
    Change-Id: I53f283e267a5fd84cf3232cc346921b67d3487bc

diff --git a/loleaflet/src/layer/tile/TileLayer.js 
b/loleaflet/src/layer/tile/TileLayer.js
index f74adf613..fde1d4796 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -1742,10 +1742,10 @@ L.TileLayer = L.GridLayer.extend({
                                for (var i = 0; i < strTwips.length; i += 4) {
                                        var topLeftTwips = new 
L.Point(parseInt(strTwips[i]), parseInt(strTwips[i + 1]));
                                        var offset = new 
L.Point(parseInt(strTwips[i + 2]), parseInt(strTwips[i + 3]));
-                                       var topRightTwips = 
topLeftTwips.add(new L.Point(offset.x, 0));
-                                       var bottomLeftTwips = 
topLeftTwips.add(new L.Point(0, offset.y));
-                                       var bottomRightTwips = 
topLeftTwips.add(offset);
-                                       rectangles.push([bottomLeftTwips, 
bottomRightTwips, topLeftTwips, topRightTwips]);
+                                       var boundsTwips = 
this._convertToTileTwipsSheetArea(
+                                               new L.Bounds(topLeftTwips, 
topLeftTwips.add(offset)));
+                                       
rectangles.push([boundsTwips.getBottomLeft(), boundsTwips.getBottomRight(),
+                                               boundsTwips.getTopLeft(), 
boundsTwips.getTopRight()]);
                                }
 
                                var polygons = 
L.PolyUtil.rectanglesToPolygons(rectangles, this);
commit 5f735f3085f53f2306edeb1d85ae9e26df9a9a55
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sun May 24 18:26:05 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:45 2020 +0530

    Handle print-twips 'cellcursor' msg from correctly
    
    This is conditioned on the flag printTwipsMsgsEnabled.
    
    Change-Id: I61a9165a9f81ce7473c3fa6579947f34552dbdaf

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 852df733b..e5ef90114 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -6,7 +6,9 @@
 /* global */
 L.CalcTileLayer = L.TileLayer.extend({
        options: {
-               sheetGeometryDataEnabled: true
+               // TODO: sync these automatically from SAL_LOK_OPTIONS
+               sheetGeometryDataEnabled: true,
+               printTwipsMsgsEnabled: true
        },
 
        STD_EXTRA_WIDTH: 113, /* 2mm extra for optimal width,
diff --git a/loleaflet/src/layer/tile/TileLayer.js 
b/loleaflet/src/layer/tile/TileLayer.js
index ded1b572c..f74adf613 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -1132,10 +1132,11 @@ L.TileLayer = L.GridLayer.extend({
                        var topLeftTwips = new L.Point(parseInt(strTwips[0]), 
parseInt(strTwips[1]));
                        var offset = new L.Point(parseInt(strTwips[2]), 
parseInt(strTwips[3]));
                        var bottomRightTwips = topLeftTwips.add(offset);
-                       this._cellCursorTwips = new L.Bounds(topLeftTwips, 
bottomRightTwips);
+                       this._cellCursorTwips = 
this._convertToTileTwipsSheetArea(
+                                                       new 
L.Bounds(topLeftTwips, bottomRightTwips));
                        this._cellCursor = new L.LatLngBounds(
-                                                       
this._twipsToLatLng(topLeftTwips, this._map.getZoom()),
-                                                       
this._twipsToLatLng(bottomRightTwips, this._map.getZoom()));
+                                       
this._twipsToLatLng(this._cellCursorTwips.getTopLeft(), this._map.getZoom()),
+                                       
this._twipsToLatLng(this._cellCursorTwips.getBottomRight(), 
this._map.getZoom()));
                        this._cellCursorXY = new L.Point(parseInt(strTwips[4]), 
parseInt(strTwips[5]));
                }
 
@@ -3367,6 +3368,15 @@ L.TileLayer = L.GridLayer.extend({
                }
        },
 
+       // convert the area in print-twips to tile-twips by computing the 
involved cell-range.
+       _convertToTileTwipsSheetArea: function (rectangle) {
+               if (!(rectangle instanceof L.Bounds) || 
!this.options.printTwipsMsgsEnabled) {
+                       return rectangle;
+               }
+
+               return 
this.sheetGeometry.getTileTwipsSheetAreaFromPrint(rectangle);
+       },
+
        _debugGetTimeArray: function() {
                return {count: 0, ms: 0, best: Number.MAX_SAFE_INTEGER, worst: 
0, date: 0};
        },
commit 050ae52c03a39b05ec1b85563ec10a5eee4e9cc6
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sun May 24 18:08:52 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:45 2020 +0530

    Allow conversion of print-twips coordinates to tile-twips...
    
    in L.SheetGeometry/L.SheetDimension classes.
    
    Change-Id: If212e6ef2bebfeae32635f58a3025fbdf42e6ef2

diff --git a/loleaflet/src/geometry/Bounds.js b/loleaflet/src/geometry/Bounds.js
index b1b73ec1d..212089d86 100644
--- a/loleaflet/src/geometry/Bounds.js
+++ b/loleaflet/src/geometry/Bounds.js
@@ -44,6 +44,14 @@ L.Bounds.prototype = {
                return new L.Point(this.max.x, this.min.y);
        },
 
+       getTopLeft: function () { // -> Point
+               return new L.Point(this.min.x, this.min.y);
+       },
+
+       getBottomRight: function () { // -> Point
+               return new L.Point(this.max.x, this.max.y);
+       },
+
        getSize: function () {
                return this.max.subtract(this.min);
        },
diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 286ee1525..852df733b 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -847,6 +847,26 @@ L.SheetGeometry = L.Class.extend({
                return this._rows.getGroupsDataInView();
        },
 
+       // accepts a rectangle in print twips coordinates and returns the 
equivalent rectangle
+       // in tile-twips aligned to the cells.
+       getTileTwipsSheetAreaFromPrint: function (rectangle) { // (L.Bounds) -> 
L.Bounds
+               if (!(rectangle instanceof L.Bounds)) {
+                       console.error('Bad argument type, expected L.Bounds');
+                       return rectangle;
+               }
+
+               var topLeft = rectangle.getTopLeft();
+               var bottomRight = rectangle.getBottomRight();
+
+               var horizBounds = 
this._columns.getTileTwipsRangeFromPrint(topLeft.x, bottomRight.x);
+               var vertBounds = 
this._rows.getTileTwipsRangeFromPrint(topLeft.y, bottomRight.y);
+
+               topLeft = new L.Point(horizBounds.startpos, 
vertBounds.startpos);
+               bottomRight = new L.Point(horizBounds.endpos, 
vertBounds.endpos);
+
+               return new L.Bounds(topLeft, bottomRight);
+       },
+
        _testValidity: function (sheetGeomJSON, checkCompleteness) {
 
                if (!sheetGeomJSON.hasOwnProperty('commandName')) {
@@ -998,6 +1018,7 @@ L.SheetDimension = L.Class.extend({
        _updatePositions: function() {
 
                var posDevPx = 0; // position in device pixels.
+               var posPrintTwips = 0;
                var dimensionObj = this;
                this._visibleSizes.addCustomDataForEachSpan(function (
                        index,
@@ -1010,12 +1031,14 @@ L.SheetDimension = L.Class.extend({
                        var posCssPx = posDevPx / 
dimensionObj._devPixelsPerCssPixel;
                        // position in device-pixel aligned twips.
                        var posTileTwips = Math.floor(posCssPx * 
dimensionObj._twipsPerCSSPixel);
+                       posPrintTwips += (size * spanLength);
 
                        var customData = {
                                sizedev: sizeDevPxOne,
                                posdevpx: posDevPx,
                                poscsspx: posCssPx,
-                               postiletwips: posTileTwips
+                               postiletwips: posTileTwips,
+                               posprinttwips: posPrintTwips
                        };
 
                        return customData;
@@ -1034,15 +1057,48 @@ L.SheetDimension = L.Class.extend({
 
        // returns element pos/size in css pixels by default.
        _getElementDataFromSpanByIndex: function (index, span, useDevicePixels) 
{
+               return this._getElementDataAnyFromSpanByIndex(index, span,
+                               useDevicePixels ? 'devpixels' : 'csspixels');
+       },
+
+       // returns element pos/size in the requested unit.
+       _getElementDataAnyFromSpanByIndex: function (index, span, unitName) {
+
                if (span === undefined || index < span.start || span.end < 
index) {
                        return undefined;
                }
 
+               if (unitName !== 'csspixels' && unitName !== 'devpixels' &&
+                               unitName !== 'tiletwips' && unitName !== 
'printtwips') {
+                       console.error('unsupported unitName: ' + unitName);
+                       return undefined;
+               }
+
                var numSizes = span.end - index + 1;
-               var pixelScale = useDevicePixels ? 1 : 
this._devPixelsPerCssPixel;
+               var inPixels = (unitName === 'csspixels' || unitName === 
'devpixels');
+               if (inPixels) {
+                       var useDevicePixels = (unitName === 'devpixels');
+                       var pixelScale = useDevicePixels ? 1 : 
this._devPixelsPerCssPixel;
+                       return {
+                               startpos: (span.data.posdevpx - 
span.data.sizedev * numSizes) / pixelScale,
+                               size: span.data.sizedev / pixelScale
+                       };
+               }
+
+               if (unitName === 'printtwips') {
+                       return {
+                               startpos: (span.data.posprinttwips - span.size 
* numSizes),
+                               size: span.size
+                       };
+               }
+
+               // unitName is 'tiletwips'
+               // It is very important to calculate this from device pixel 
units to mirror the core calculations.
+               var twipsPerDevPixels = this._twipsPerCSSPixel / 
this._devPixelsPerCssPixel;
                return {
-                       startpos: (span.data.posdevpx - span.data.sizedev * 
numSizes) / pixelScale,
-                       size: span.data.sizedev / pixelScale
+                       startpos: Math.floor(
+                               (span.data.posdevpx - span.data.sizedev * 
numSizes) * twipsPerDevPixels),
+                       size: Math.floor(span.data.sizedev * twipsPerDevPixels)
                };
        },
 
@@ -1077,6 +1133,29 @@ L.SheetDimension = L.Class.extend({
                return span.start + relativeIndex;
        },
 
+       // computes element index from print twips position and returns
+       // an object with this index and the span data.
+       _getSpanAndIndexFromPrintTwipsPos: function (pos) {
+               var result = {};
+               var span = this._visibleSizes.getSpanDataByCustomDataField(pos, 
'posprinttwips');
+               result.span = span;
+               if (span === undefined) {
+                       // enforce limits.
+                       result.index = (pos >= 0) ? this._maxIndex : 0;
+                       result.span = 
this._visibleSizes.getSpanDataByIndex(result.index);
+                       return result;
+               }
+               var elementCount = span.end - span.start + 1;
+               var posStart = (span.data.posprinttwips - span.size * 
elementCount);
+               var sizeOne = span.size;
+
+               // always round down as relativeIndex is zero-based.
+               var relativeIndex = Math.floor((pos - posStart) / sizeOne);
+
+               result.index = span.start + relativeIndex;
+               return result;
+       },
+
        setViewLimits: function (startPosTileTwips, endPosTileTwips) {
 
                this._viewStartIndex = Math.max(0, 
this._getIndexFromTileTwipsPos(startPosTileTwips));
@@ -1121,7 +1200,37 @@ L.SheetDimension = L.Class.extend({
 
        getMaxIndex: function () {
                return this._maxIndex;
-       }
+       },
+
+       // Accepts a start and end positions in print twips, and returns the
+       // corresponding positions in tile twips, by first computing the 
element range.
+       getTileTwipsRangeFromPrint: function (posStartPT, posEndPT) {
+               var startElement = 
this._getSpanAndIndexFromPrintTwipsPos(posStartPT);
+               var startData = 
this._getElementDataAnyFromSpanByIndex(startElement.index, startElement.span, 
'tiletwips');
+               if (posStartPT === posEndPT) {
+                       // range is hidden, send a minimal sized tile-twips 
range.
+                       // Set the size = twips equivalent of 1 device pixel,
+                       // to imitate what core does when it sends 
cursor/ranges in tile-twips coordinates.
+                       var rangeSize = Math.floor(this._twipsPerCSSPixel / 
this._devPixelsPerCssPixel);
+                       return {
+                               startpos: startData.startpos,
+                               endpos: startData.startpos + rangeSize
+                       };
+               }
+               var endElement = 
this._getSpanAndIndexFromPrintTwipsPos(posEndPT);
+               var endData = 
this._getElementDataAnyFromSpanByIndex(endElement.index, endElement.span, 
'tiletwips');
+
+               var startPos = startData.startpos;
+               var endPos = endData.startpos + endData.size;
+               if (endPos < startPos) {
+                       endPos = startPos;
+               }
+
+               return {
+                       startpos: startPos,
+                       endpos: endPos
+               };
+       },
 });
 
 L.SpanList = L.Class.extend({
@@ -1311,10 +1420,10 @@ L.SpanList = L.Class.extend({
                // from 0 at the start of first span and are in non-decreasing 
order.
 
                return binarySearch(this._spanlist, value,
-                       function directionProvider(testValue, prevSpan, 
curSpan) {
+                       function directionProvider(testValue, prevSpan, 
curSpan, nextSpan) {
                                var valueStart = prevSpan ?
-                                       prevSpan.data[fieldName] + 1 : 0;
-                               var valueEnd = curSpan.data[fieldName];
+                                       prevSpan.data[fieldName] : 0;
+                               var valueEnd = curSpan.data[fieldName] - 
(nextSpan ? 1 : 0);
                                if (valueStart === undefined || valueEnd === 
undefined) {
                                        // fieldName not present in the 'data' 
property.
                                        return -1;
commit 2814ed25e782e67d7cdd7b0b3f1e3990ab714ed9
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sun May 24 12:53:57 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:45 2020 +0530

    Enable option 'sc_print_twips_msgs' in SAL_LOK_OPTIONS
    
    Change-Id: I948c42ba038fd10c15eabaca08cc480dbd465907

diff --git a/kit/Kit.cpp b/kit/Kit.cpp
index db95047f9..94ec89a37 100644
--- a/kit/Kit.cpp
+++ b/kit/Kit.cpp
@@ -2418,6 +2418,7 @@ void setupKitEnvironment()
         options += ":profile_events";
 #endif
 //    options += ":sc_no_grid_bg"; // leave this disabled for now, 
merged-cells needs more work.
+    options += ":sc_print_twips_msgs";
     ::setenv("SAL_LOK_OPTIONS", options.c_str(), 0);
 }
 
commit 0f0962d72c5133197bff86fa144fe7d91c6f8796
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Fri May 22 23:41:52 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:44 2020 +0530

    row(col) header's canvas height(width) should not auto-adjust...
    
    with the height(width) of the parent container, because if it does, the
    header-canvas scales its contents to adjust to the new height(width) and
    of course goes out of sync with the tiles.
    
    This bug is visible when we do synchronous update of headers and if one
    or both of the headers change size due to increase/decrease of
    table-outline levels.
    
    Change-Id: Ib65fd27f3598c7d58323d37c80ce2a147e594911

diff --git a/loleaflet/css/spreadsheet.css b/loleaflet/css/spreadsheet.css
index 1934933b5..a24792192 100644
--- a/loleaflet/css/spreadsheet.css
+++ b/loleaflet/css/spreadsheet.css
@@ -135,7 +135,6 @@
 .spreadsheet-header-columns {
        display: inline-block;
        white-space: nowrap;
-       width: 100%;
        height: 100%;
        border-spacing: 0px !important;
        position: relative;
@@ -178,7 +177,6 @@
 
 .spreadsheet-header-rows {
        width: 100%;
-       height: 100%;
        border-spacing: 0px !important;
        position: relative;
        margin: 0px;
commit 54577f30ff33ebb4b9b55bcfa2cab0baa506741e
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Fri May 22 22:52:12 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:44 2020 +0530

    fix a logical error regarding useDevicePixels flag
    
    Change-Id: I4a930544a8f09d4d97c5e8257f0a353b4fccefac

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 89807f433..286ee1525 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -1039,7 +1039,7 @@ L.SheetDimension = L.Class.extend({
                }
 
                var numSizes = span.end - index + 1;
-               var pixelScale = useDevicePixels ? this._devPixelsPerCssPixel : 
1;
+               var pixelScale = useDevicePixels ? 1 : 
this._devPixelsPerCssPixel;
                return {
                        startpos: (span.data.posdevpx - span.data.sizedev * 
numSizes) / pixelScale,
                        size: span.data.sizedev / pixelScale
commit 80f48fb07648cfdfc6ed754924da33f670543a98
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Thu May 21 23:43:17 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:44 2020 +0530

    Avoid double parse/load of the first sheet geometry message
    
    Change-Id: I381c63608dd4858b7f4e6e22d3f1d010cd2308e0

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 022397b6f..89807f433 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -613,8 +613,10 @@ L.CalcTileLayer = L.TileLayer.extend({
                                this._tileWidthTwips, this._tileHeightTwips,
                                this._tileSize, this._tilePixelScale);
                }
+               else {
+                       this.sheetGeometry.update(jsonMsgObj);
+               }
 
-               this.sheetGeometry.update(jsonMsgObj);
                
this.sheetGeometry.setViewArea(this._pixelsToTwips(this._map._getTopLeftPoint()),
                        this._pixelsToTwips(this._map.getSize()));
                this._updateHeadersGridLines(undefined, true /* updateCols */,
commit 6040ac5ff58f4ecb93191c938e2788d059be6236
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Thu May 21 23:30:04 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:44 2020 +0530

    Supress multiple .uno:SheetGeometryData requests...
    
    at document load till we get a response, by using flag.
    
    Change-Id: I6296d6e18a122ee3cb714f26c019f9080c7febc7

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index e8a2c47bd..022397b6f 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -530,8 +530,10 @@ L.CalcTileLayer = L.TileLayer.extend({
                        return;
                }
 
-               this.sheetGeometry.setViewArea(pos, size);
-               this._updateHeadersGridLines(undefined, updateCols, updateRows);
+               if (this.sheetGeometry) {
+                       this.sheetGeometry.setViewArea(pos, size);
+                       this._updateHeadersGridLines(undefined, updateCols, 
updateRows);
+               }
        },
 
        // This send .uno:ViewRowColumnHeaders command to core with the new 
view coordinates (tile-twips).
@@ -545,6 +547,13 @@ L.CalcTileLayer = L.TileLayer.extend({
 
        // sends the .uno:SheetGeometryData command optionally with arguments.
        requestSheetGeometryData: function (flags) {
+               if (!this.sheetGeometry) {
+                       // Supress multiple requests at document load, till we 
get a response.
+                       if (this._sheetGeomFirstWait === true) {
+                               return;
+                       }
+                       this._sheetGeomFirstWait = true;
+               }
                var unoCmd = '.uno:SheetGeometryData';
                var haveArgs = (typeof flags == 'object' &&
                        (flags.columns === true || flags.rows === true || 
flags.all === true));
@@ -599,6 +608,7 @@ L.CalcTileLayer = L.TileLayer.extend({
 
        _handleSheetGeometryDataMsg: function (jsonMsgObj) {
                if (!this.sheetGeometry) {
+                       this._sheetGeomFirstWait = false;
                        this.sheetGeometry = new L.SheetGeometry(jsonMsgObj,
                                this._tileWidthTwips, this._tileHeightTwips,
                                this._tileSize, this._tilePixelScale);
commit 0c8d85ecdfa4f86982ddf4fcbc4fa87f5982ee74
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Thu May 21 21:41:38 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:44 2020 +0530

    loleaflet: avoid double logging of messages
    
    Change-Id: Ib19e3f26ec0fe5bb66364a488f764bdcf88acf83

diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js
index a5e394c67..e04307b75 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -769,8 +769,6 @@ L.Socket = L.Class.extend({
                        this._map.fire(mobileEvent);
                }
                else if (!textMsg.startsWith('tile:') && 
!textMsg.startsWith('renderfont:') && !textMsg.startsWith('windowpaint:')) {
-                       // log the tile msg separately as we need the tile 
coordinates
-                       this._logSocket('INCOMING', textMsg);
 
                        if (imgBytes !== undefined) {
                                try {
commit 339be9e5470197b960c372bdd819b5b91811c95a
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Thu May 21 16:09:39 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:44 2020 +0530

    handle 'invalidatesheetgeometry' message
    
    and use it to fetch the changed sheet geometry data and update the view.
    
    Change-Id: I5a72012ce08f23e456cd64388e22385872f665a5

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index b261ebadc..e8a2c47bd 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -279,6 +279,14 @@ L.CalcTileLayer = L.TileLayer.extend({
                        this.refreshViewData({x: 
this._map._getTopLeftPoint().x, y: this._map._getTopLeftPoint().y,
                                offset: {x: undefined, y: undefined}}, true /* 
compatDataSrcOnly */);
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
+               } else if (this.options.sheetGeometryDataEnabled &&
+                               textMsg.startsWith('invalidatesheetgeometry:')) 
{
+                       var params = 
textMsg.substring('invalidatesheetgeometry:'.length).trim().split(' ');
+                       var flags = {};
+                       params.forEach(function (param) {
+                               flags[param] = true;
+                       });
+                       this.requestSheetGeometryData(flags);
                } else {
                        L.TileLayer.prototype._onMessage.call(this, textMsg, 
img);
                }
@@ -539,16 +547,16 @@ L.CalcTileLayer = L.TileLayer.extend({
        requestSheetGeometryData: function (flags) {
                var unoCmd = '.uno:SheetGeometryData';
                var haveArgs = (typeof flags == 'object' &&
-                       (flags.columns === true || flags.rows === true) &&
-                       (flags.columns !== flags.rows));
+                       (flags.columns === true || flags.rows === true || 
flags.all === true));
                var payload = 'commandvalues command=' + unoCmd;
 
                if (haveArgs) {
                        var argList = [];
-                       if (flags.columns === true) {
+                       var both = (flags.all === true);
+                       if (both || flags.columns === true) {
                                argList.push('columns=1');
                        }
-                       if (flags.rows === true) {
+                       if (both || flags.rows === true) {
                                argList.push('rows=1');
                        }
 
commit e553b5ec9f0aba96272999d9c6b67ef6732b6035
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Thu May 21 12:29:52 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:44 2020 +0530

    Ignore the "invalidateheader:" msgs when...
    
    sheet-geometry data source is enabled, because these messages are not
    just for sheet geometry changes.
    
    Change-Id: I8b2cca968a189607e46a4f9512c60e4b29f124bc

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 096ba66c4..b261ebadc 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -269,15 +269,15 @@ L.CalcTileLayer = L.TileLayer.extend({
                        }
                } else if (textMsg.startsWith('invalidateheader: column')) {
                        this.refreshViewData({x: 
this._map._getTopLeftPoint().x, y: 0,
-                               offset: {x: undefined, y: 0}});
+                               offset: {x: undefined, y: 0}}, true /* 
compatDataSrcOnly */);
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
                } else if (textMsg.startsWith('invalidateheader: row')) {
                        this.refreshViewData({x: 0, y: 
this._map._getTopLeftPoint().y,
-                               offset: {x: 0, y: undefined}});
+                               offset: {x: 0, y: undefined}}, true /* 
compatDataSrcOnly */);
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
                } else if (textMsg.startsWith('invalidateheader: all')) {
                        this.refreshViewData({x: 
this._map._getTopLeftPoint().x, y: this._map._getTopLeftPoint().y,
-                               offset: {x: undefined, y: undefined}});
+                               offset: {x: undefined, y: undefined}}, true /* 
compatDataSrcOnly */);
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
                } else {
                        L.TileLayer.prototype._onMessage.call(this, textMsg, 
img);
@@ -365,7 +365,7 @@ L.CalcTileLayer = L.TileLayer.extend({
        _onSetPartMsg: function (textMsg) {
                var part = parseInt(textMsg.match(/\d+/g)[0]);
                if (!this.isHiddenPart(part)) {
-                       this.refreshViewData(undefined, true /* 
sheetGeometryChanged */);
+                       this.refreshViewData(undefined, false /* 
compatDataSrcOnly */, true /* sheetGeometryChanged */);
                }
        },
 
@@ -467,8 +467,11 @@ L.CalcTileLayer = L.TileLayer.extend({
        // zooming, cursor moving out of view-area etc.).  Depending on the
        // active sheet geometry data-source, it may ask core to send current
        // view area's data or the global data on geometry changes.
-       refreshViewData: function (coordinatesData, sheetGeometryChanged) {
+       refreshViewData: function (coordinatesData, compatDataSrcOnly, 
sheetGeometryChanged) {
 
+               if (this.options.sheetGeometryDataEnabled && compatDataSrcOnly) 
{
+                       return;
+               }
                // There are places that call this function with no arguments 
to indicate that the
                // command arguments should be the current map area coordinates.
                if (typeof coordinatesData != 'object') {
commit b16d19be445854b203d4cf95ecc83b0ac529feeb
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Wed May 20 01:28:50 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:44 2020 +0530

    supress clang -Wparentheses-equality for WIF*() spurious case
    
    Change-Id: I213ba6085aa1b662d5a6283cf9d84742bce288e1

diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index b03574724..cace5c43e 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -1547,6 +1547,15 @@ bool LOOLWSD::checkAndRestoreForKit()
     return false;
 #else
 
+// clang issues warning for WIF*() macro usages below:
+// "equality comparison with extraneous parentheses 
[-Werror,-Wparentheses-equality]"
+// https://bugs.llvm.org/show_bug.cgi?id=22949
+
+#if defined __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wparentheses-equality"
+#endif
+
     if (ForKitProcId == -1)
     {
         // Fire the ForKit process for the first time.
@@ -1624,6 +1633,11 @@ bool LOOLWSD::checkAndRestoreForKit()
     }
 
     return false;
+
+#if defined __clang__
+#pragma clang diagnostic pop
+#endif
+
 #endif
 }
 
commit f0b0eed3876f96523670945e298c3f968df3cd4f
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Tue May 19 18:51:11 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:44 2020 +0530

    handle LOK_CALLBACK_INVALIDATE_SHEET_GEOMETRY
    
    Change-Id: I2f894f32d4c9e852d89159a55c0dd9effb45c09e

diff --git a/kit/ChildSession.cpp b/kit/ChildSession.cpp
index ba8bdf8ee..a59a643c7 100644
--- a/kit/ChildSession.cpp
+++ b/kit/ChildSession.cpp
@@ -2268,6 +2268,7 @@ void ChildSession::rememberEventsForInactiveUser(const 
int type, const std::stri
              type == LOK_CALLBACK_GRAPHIC_SELECTION ||
              type == LOK_CALLBACK_DOCUMENT_SIZE_CHANGED ||
              type == LOK_CALLBACK_INVALIDATE_HEADER ||
+             type == LOK_CALLBACK_INVALIDATE_SHEET_GEOMETRY ||
              type == LOK_CALLBACK_CELL_ADDRESS ||
              type == LOK_CALLBACK_REFERENCE_MARKS)
     {
@@ -2593,6 +2594,9 @@ void ChildSession::loKitCallback(const int type, const 
std::string& payload)
     case LOK_CALLBACK_FORM_FIELD_BUTTON:
         sendTextFrame("formfieldbutton: " + payload);
         break;
+    case LOK_CALLBACK_INVALIDATE_SHEET_GEOMETRY:
+        sendTextFrame("invalidatesheetgeometry: " + payload);
+        break;
 
 #if !ENABLE_DEBUG
     // we want a compilation-time failure in the debug builds; but ERR in the
commit 6d9075701218a1bb739ec3c7dad1127437506d76
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sat May 16 18:36:04 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:44 2020 +0530

    Initialize outlines ds on empty outline
    
    Change-Id: If845991272a05f026fad7819d39ff4b23622abae

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 272db0cb9..096ba66c4 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -1469,6 +1469,7 @@ L.DimensionOutlines = L.Class.extend({
                var levels = encoding.split(' ');
                if (levels.length < 2) {
                        // No outline.
+                       this._outlines = [];
                        return true;
                }
 
commit dff408296e8c7f26c605ae81487fc30ed530e335
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sat May 16 18:07:56 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:44 2020 +0530

    call refreshViewData() on setpart message...
    
    if the part is not hidden. The member _selectedPart is already set
    to the new part in Parts.js's setPart(), as a result the code inside
    the if was never getting executed. There is no need to call
    map.setPart() as this was also done in Parts.js setPart(), and finally
    there are no handler for 'setpart' event as of now, so lets remove the
    fire() call too. All of this was not a problem when the
    '.uno:ViewRowColumnHeader' data source was used, because that data
    was getting requested unintentionally as part of related scroll events
    during a sheet switch.
    
    Change-Id: I3ea3916ba738d9616e822659fc64903eda8f99cf

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 2b29e734b..272db0cb9 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -364,9 +364,7 @@ L.CalcTileLayer = L.TileLayer.extend({
 
        _onSetPartMsg: function (textMsg) {
                var part = parseInt(textMsg.match(/\d+/g)[0]);
-               if (part !== this._selectedPart && !this.isHiddenPart(part)) {
-                       this._map.setPart(part, true);
-                       this._map.fire('setpart', {selectedPart: 
this._selectedPart});
+               if (!this.isHiddenPart(part)) {
                        this.refreshViewData(undefined, true /* 
sheetGeometryChanged */);
                }
        },
commit 36f7b9be84f136a961d02049b644312c9a7b4c8a
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sat May 16 16:41:29 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:44 2020 +0530

    Add ability to get first match in binarySearch
    
    (more details in the comments)
    This can help in a corner case (very improbable though) when we query
    for the exact end-position of a span with trailing empty span.  Lets do
    the right thing, even if that does happen only extremely rarely.
    
    Change-Id: Ib1370811c257e6ef3d52d29caf2963641bad8e40

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index cfadbb2be..2b29e734b 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -1300,7 +1300,9 @@ L.SpanList = L.Class.extend({
                                }
                                return (testValue < valueStart) ? -1 :
                                        (valueEnd < testValue) ? 1 : 0;
-                       });
+                       }, true /* find the first match in case of duplicates 
*/);
+               // About the last argument: duplicates can happen, for instance 
if the
+               // custom field represents positions, and there are spans with 
zero sizes (hidden/filtered).
        }
 
 });
@@ -1582,6 +1584,9 @@ L.DimensionOutlines = L.Class.extend({
 // Of course, this assumes that the array is sorted (w.r.t to the semantics of
 // the directionProvider when it is provided).
 // It returns the index of the match if successful else returns -1.
+// 'firstMatch' if true, some additional work is done to ensure that the index 
of
+// the first match (from the 0 index of the array) is returned in case there 
are
+// duplicates.
 //
 // directionProvider will be provided the following parameters :
 // (key, previousArrayElement, currentArrayElement, nextArrayElement)
@@ -1592,19 +1597,21 @@ L.DimensionOutlines = L.Class.extend({
 //   1: to try searching upper half,
 //  -1: to try searching lower half
 
-function binarySearch(array, key, directionProvider) {
+function binarySearch(array, key, directionProvider, firstMatch) {
 
        if (!Array.isArray(array) || !array.length) {
                return -1;
        }
 
        if (typeof directionProvider != 'function') {
-               directionProvider = function (key, testvalue) {
+               directionProvider = function (key, prevvalue, testvalue) {
                        return (key === testvalue) ? 0 :
                                (key < testvalue) ? -1 : 1;
                };
        }
 
+       firstMatch = (firstMatch === true);
+
        var start = 0;
        var end = array.length - 1;
 
@@ -1616,7 +1623,12 @@ function binarySearch(array, key, directionProvider) {
 
        var endDir = directionProvider(key, array[end - 1], array[end]);
        if (endDir >= 0) {
-               return endDir ? -1 : end;
+
+               if (endDir === 1) {
+                       return -1;
+               }
+
+               return firstMatch ? _findFirstMatch(array, key, 
directionProvider, end) : end;
        }
 
        var mid = -1;
@@ -1637,5 +1649,22 @@ function binarySearch(array, key, directionProvider) {
                }
        }
 
-       return (start > end) ? -1 : mid;
+       return (start > end) ? -1 :
+               firstMatch ? _findFirstMatch(array, key, directionProvider, 
mid) : mid;
+}
+
+// Helper function for binarySearch().
+function _findFirstMatch(array, key, directionProvider, randomMatchingIndex) {
+
+       if (randomMatchingIndex === 0) {
+               return 0;
+       }
+
+       var index = randomMatchingIndex - 1;
+       while (index >= 0 && directionProvider(key,
+               array[index - 1], array[index], array[index + 1]) == 0) {
+               --index;
+       }
+
+       return index + 1;
 }
commit 3c0cb1a638ccc8e5c0c6f486046de7a18aaf50a4
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sat May 16 15:09:51 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:43 2020 +0530

    Make the newly added interfaces more robust
    
    against wrong argument counts/types.
    
    Change-Id: Ibfed2ba4f3e907ef8a038a3b13b3081cc6248c20

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 830c9084b..cfadbb2be 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -1193,6 +1193,10 @@ L.SpanList = L.Class.extend({
 
        addCustomDataForEachSpan: function (getCustomDataCallback) {
 
+               if (typeof getCustomDataCallback != 'function') {
+                       return;
+               }
+
                var prevIndex = -1;
                this._spanlist.forEach(function (span) {
                        span.data = getCustomDataCallback(
@@ -1203,6 +1207,11 @@ L.SpanList = L.Class.extend({
        },
 
        getSpanDataByIndex: function (index) {
+
+               if (typeof index != 'number') {
+                       return undefined;
+               }
+
                var spanid = this._searchByIndex(index);
                if (spanid == -1) {
                        return undefined;
@@ -1212,6 +1221,11 @@ L.SpanList = L.Class.extend({
        },
 
        getSpanDataByCustomDataField: function (value, fieldName) {
+
+               if (typeof value != 'number' || typeof fieldName != 'string' || 
!fieldName) {
+                       return undefined;
+               }
+
                var spanid = this._searchByCustomDataField(value, fieldName);
                if (spanid == -1) {
                        return undefined;
@@ -1222,7 +1236,8 @@ L.SpanList = L.Class.extend({
 
        forEachSpanInRange: function (start, end, callback) {
 
-               if (start > end) {
+               if (typeof start != 'number' || typeof end != 'number' ||
+                       typeof callback != 'function' || start > end) {
                        return;
                }
 
@@ -1279,6 +1294,10 @@ L.SpanList = L.Class.extend({
                                var valueStart = prevSpan ?
                                        prevSpan.data[fieldName] + 1 : 0;
                                var valueEnd = curSpan.data[fieldName];
+                               if (valueStart === undefined || valueEnd === 
undefined) {
+                                       // fieldName not present in the 'data' 
property.
+                                       return -1;
+                               }
                                return (testValue < valueStart) ? -1 :
                                        (valueEnd < testValue) ? 1 : 0;
                        });
@@ -1501,7 +1520,7 @@ L.DimensionOutlines = L.Class.extend({
        // 'callback' is called with these parameters : (levelIdx, groupIdx, 
groupStart, groupEnd, groupHidden).
        forEachGroupInRange: function (start, end, callback) {
 
-               if (start === undefined || end === undefined || callback === 
undefined) {
+               if (typeof start != 'number' || typeof end != 'number' || 
typeof callback != 'function') {
                        return;
                }
 
@@ -1575,11 +1594,11 @@ L.DimensionOutlines = L.Class.extend({
 
 function binarySearch(array, key, directionProvider) {
 
-       if (array === undefined || !array.length) {
+       if (!Array.isArray(array) || !array.length) {
                return -1;
        }
 
-       if (directionProvider === undefined) {
+       if (typeof directionProvider != 'function') {
                directionProvider = function (key, testvalue) {
                        return (key === testvalue) ? 0 :
                                (key < testvalue) ? -1 : 1;
commit 854b30f201a8763a3fe73d90692becd880502a01
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sat May 16 14:12:10 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:43 2020 +0530

    Reuse binarySearch routine in L.SpanList search functions
    
    Change-Id: I2f5411ef4da6b236978c08e3ea03ee05f9c8dabc

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 361dbe120..830c9084b 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -1259,61 +1259,29 @@ L.SpanList = L.Class.extend({
 
        _searchByIndex: function (index) {
 
-               if (index < 0 || index > this._spanlist[this._spanlist.length - 
1].index) {
-                       return -1;
-               }
-
-               var start = 0;
-               var end = this._spanlist.length - 1;
-               var mid = -1;
-               while (start <= end) {
-                       mid = Math.round((start + end) / 2);
-                       var spanstart = mid ? this._spanlist[mid - 1].index + 1 
: 0;
-                       var spanend = this._spanlist[mid].index;
-                       if (spanstart <= index && index <= spanend) {
-                               break;
-                       }
-
-                       if (index < spanstart) {
-                               end = mid - 1;
-                       }
-                       else { // spanend < index
-                               start = mid + 1;
-                       }
-               }
-
-               return mid;
+               return binarySearch(this._spanlist, index,
+                       function directionProvider(testIndex, prevSpan, 
curSpan) {
+                               var spanStart = prevSpan ?
+                                       prevSpan.index + 1 : 0;
+                               var spanEnd = curSpan.index;
+                               return (testIndex < spanStart) ? -1 :
+                                       (spanEnd < testIndex) ? 1 : 0;
+                       });
        },
 
        _searchByCustomDataField: function (value, fieldName) {
 
-               // All custom searchable data values are assumed to start from 
0 at the start of first span.
-               var maxValue = this._spanlist[this._spanlist.length - 
1].data[fieldName];
-               if (value < 0 || value > maxValue) {
-                       return -1;
-               }
+               // All custom searchable data values are assumed to start
+               // from 0 at the start of first span and are in non-decreasing 
order.
 
-               var start = 0;
-               var end = this._spanlist.length - 1;
-               var mid = -1;
-               while (start <= end) {
-                       mid = Math.round((start + end) / 2);
-                       var valuestart = mid ? this._spanlist[mid - 
1].data[fieldName] + 1 : 0;
-                       var valueend = this._spanlist[mid].data[fieldName];
-                       if (valuestart <= value && value <= valueend) {
-                               break;
-                       }
-
-                       if (value < valuestart) {
-                               end = mid - 1;
-                       }
-                       else { // valueend < value
-                               start = mid + 1;
-                       }
-               }
-
-               // may fail for custom data ?
-               return (start <= end) ? mid : -1;
+               return binarySearch(this._spanlist, value,
+                       function directionProvider(testValue, prevSpan, 
curSpan) {
+                               var valueStart = prevSpan ?
+                                       prevSpan.data[fieldName] + 1 : 0;
+                               var valueEnd = curSpan.data[fieldName];
+                               return (testValue < valueStart) ? -1 :
+                                       (valueEnd < testValue) ? 1 : 0;
+                       });
        }
 
 });
commit 61ff38d5d50e606a76a5811f87c149c5192d40aa
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sat May 16 11:14:17 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:43 2020 +0530

    Restore the scope of 'offset' tampering code
    
    as it was before 317afcecb4
    
    This 'adjustment' was just meant for refreshViewData() or whatever it
    was called before, to indicate that both column/row headers/gridlines
    should be updated, no matter what the actual offset is (probably only
    meant for zoom changes?). The offset passed to refreshViewData is only
    going to be used as a boolean flag.
    
    This patch fixes the row/col headers getting a off-by-one pixel when
    changing zooms with the new data-source (.uno:SheetGeometryData). If
    using the older source (.uno:ViewRowColumnHeader), this bug is hidden
    because of the delay for fetching the JSON everytime before painting the
    headers.
    
    TODO: Refactor all calls of refreshViewData to get rid of the 'offset'
    and this adjustment code and only send the boolean flags to
    refreshViewData().
    
    Change-Id: I4c30e8f06a6a2d58b9a9a89e283d7a214d00b99c

diff --git a/loleaflet/src/control/Control.Scroll.js 
b/loleaflet/src/control/Control.Scroll.js
index 36eed31fe..0cde2b4c9 100644
--- a/loleaflet/src/control/Control.Scroll.js
+++ b/loleaflet/src/control/Control.Scroll.js
@@ -259,15 +259,21 @@ L.Control.Scroll = L.Control.extend({
                // used on window resize
                // also when dragging
                var offset = new L.Point(e.x - this._prevScrollX, e.y - 
this._prevScrollY);
-               if (offset.x === 0) {
-                       offset.x = 1;
-               }
-               if (offset.y === 0) {
-                       offset.y = 1;
-               }
 
                this._map.fire('scrolloffset', offset);
                if (e.updateHeaders && this._map._docLayer._docType === 
'spreadsheet') {
+                       // This adjustment was just meant for refreshViewData()
+                       // to indicate that both column/row headers/gridlines
+                       // should be updated, no matter what the actual offset
+                       // is (unsure why).
+                       // TODO: Get rid of the 'offset' adjustment and
+                       // only send boolean flags to refreshViewData().
+                       if (offset.x === 0) {
+                               offset.x = 1;
+                       }
+                       if (offset.y === 0) {
+                               offset.y = 1;
+                       }
                        this._map._docLayer.refreshViewData({x: e.x, y: e.y, 
offset: offset});
                }
 
commit 20587245c02cca00764b00c4a1c62395c753d5d6
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Fri May 15 23:12:16 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:43 2020 +0530

    enforce bound-checks on setViewArea()
    
    Change-Id: Ic129181095c301ce27421bac5d3f1f94d1932248

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index d7218b458..361dbe120 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -1040,6 +1040,10 @@ L.SheetDimension = L.Class.extend({
        // computes element index from tile-twips position.
        _getIndexFromTileTwipsPos: function (pos) {
                var span = this._visibleSizes.getSpanDataByCustomDataField(pos, 
'postiletwips');
+               if (span === undefined) {
+                       // enforce limits.
+                       return (pos >= 0) ? this._maxIndex : 0;
+               }
                var elementCount = span.end - span.start + 1;
                var posStart = ((span.data.posdevpx - span.data.sizedev * 
elementCount) /
                        this._devPixelsPerCssPixel * this._twipsPerCSSPixel);
commit 38ebb96b8eff154239943a6fc42877b58dfbaa63
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Fri May 15 22:43:19 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:43 2020 +0530

    call refreshViewData after 'scrolloffset' is fired
    
    otherwise the header controls won't have the right position info
    when refreshViewData causes an ~immediate header/gridline rendering
    (.uno:SheetGeometryData source). This was not a problem in case of
    .uno:ViewRowColumnHeader source, because of the roundtrip delay for
    getting the msg from core.
    
    Change-Id: I48298dbfb8d62acc64adbfd662a5304b856d702a

diff --git a/loleaflet/src/control/Control.Scroll.js 
b/loleaflet/src/control/Control.Scroll.js
index cb323e1c7..36eed31fe 100644
--- a/loleaflet/src/control/Control.Scroll.js
+++ b/loleaflet/src/control/Control.Scroll.js
@@ -114,11 +114,10 @@ L.Control.Scroll = L.Control.extend({
                        return;
                }
 
-               this._map._docLayer.refreshViewData({ x: newLeft, y: newTop, 
offset: offset});
-
                this._prevScrollY = newTop;
                this._prevScrollX = newLeft;
                this._map.fire('scrolloffset', offset);
+               this._map._docLayer.refreshViewData({ x: newLeft, y: newTop, 
offset: offset});
                this._map.scroll(offset.x, offset.y);
        },
 
@@ -266,10 +265,12 @@ L.Control.Scroll = L.Control.extend({
                if (offset.y === 0) {
                        offset.y = 1;
                }
+
+               this._map.fire('scrolloffset', offset);
                if (e.updateHeaders && this._map._docLayer._docType === 
'spreadsheet') {
                        this._map._docLayer.refreshViewData({x: e.x, y: e.y, 
offset: offset});
                }
-               this._map.fire('scrolloffset', offset);
+
                this._ignoreScroll = null;
                $('.scroll-container').mCustomScrollbar('stop');
                this._prevScrollY = e.y;
commit 436161f8eaf1b59c0a26e5cdb34c28f157414304
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Fri May 15 22:05:44 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:43 2020 +0530

    Don't ask core for sheet-geometry data for invalidateheader msgs
    
    We may need to have a dedicated sheetgeometrychanged msg for geometry
    changes like change of col/row sizes, hidden/filtered, groups/outline
    states.
    
    Change-Id: I45a8038546c66797aed4b58f11c6450cbe6e2965

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 37a08755e..d7218b458 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -269,15 +269,15 @@ L.CalcTileLayer = L.TileLayer.extend({
                        }
                } else if (textMsg.startsWith('invalidateheader: column')) {
                        this.refreshViewData({x: 
this._map._getTopLeftPoint().x, y: 0,
-                               offset: {x: undefined, y: 0}}, true /* 
sheetGeometryChanged */);
+                               offset: {x: undefined, y: 0}});
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
                } else if (textMsg.startsWith('invalidateheader: row')) {
                        this.refreshViewData({x: 0, y: 
this._map._getTopLeftPoint().y,
-                               offset: {x: 0, y: undefined}}, true /* 
sheetGeometryChanged */);
+                               offset: {x: 0, y: undefined}});
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
                } else if (textMsg.startsWith('invalidateheader: all')) {
                        this.refreshViewData({x: 
this._map._getTopLeftPoint().x, y: this._map._getTopLeftPoint().y,
-                               offset: {x: undefined, y: undefined}}, true /* 
sheetGeometryChanged */);
+                               offset: {x: undefined, y: undefined}});
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
                } else {
                        L.TileLayer.prototype._onMessage.call(this, textMsg, 
img);
commit e9c9e2a6292baf53b44f6c01d0a9381b173bea1d
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Fri May 15 08:12:03 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:43 2020 +0530

    use SheetGeometry data to draw headers/gridlines if enabled
    
    Change-Id: If146512a50c24f5fd81f6df7e0a3746f70bf21f9

diff --git a/loleaflet/src/control/Control.ColumnHeader.js 
b/loleaflet/src/control/Control.ColumnHeader.js
index a2846f78e..254a4df41 100644
--- a/loleaflet/src/control/Control.ColumnHeader.js
+++ b/loleaflet/src/control/Control.ColumnHeader.js
@@ -193,7 +193,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
        },
 
        _updateColumnHeader: function () {
-               this._map._docLayer.requestViewRowColumnData({x: 
this._map._getTopLeftPoint().x, y: 0, offset: {x: undefined, y: 0}});
+               this._map._docLayer.refreshViewData({x: 
this._map._getTopLeftPoint().x, y: 0, offset: {x: undefined, y: 0}});
        },
 
        drawHeaderEntry: function (entry, isOver, isHighlighted, isCurrent) {
@@ -376,8 +376,10 @@ L.Control.ColumnHeader = L.Control.Header.extend({
        },
 
        viewRowColumnHeaders: function (e) {
-               if (e.data.columns && e.data.columns.length > 0) {
-                       this.fillColumns(e.data.columns, e.data.columnGroups, 
e.converter, e.context);
+               var dataInEvent = (e.data && e.data.columns && 
e.data.columns.length > 0);
+               if (dataInEvent || e.updatecolumns) {
+                       dataInEvent ? this.fillColumns(e.data.columns, 
e.data.columnGroups, e.converter, e.context) :
+                               this.fillColumns(undefined, undefined, 
e.converter, e.context);
                        this._onUpdateCurrentColumn(e.cursor);
                        if (e.selection && e.selection.hasSelection) {
                                this._onUpdateSelection(e.selection);
@@ -389,7 +391,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
        },
 
        fillColumns: function (columns, colGroups, converter, context) {
-               if (columns.length < 2)
+               if (columns && columns.length < 2)
                        return;
 
                var canvas = this._canvas;
@@ -413,21 +415,28 @@ L.Control.ColumnHeader = L.Control.Header.extend({
                        this._lastMouseOverIndex = undefined;
                }
 
+               var sheetGeometry = this._map._docLayer.sheetGeometry;
+               var columnsGeometry = sheetGeometry ? 
sheetGeometry.getColumnsGeometry() : undefined;
+
                // create data structure for column widths
-               this._tickMap = new L.Control.Header.GapTickMap(this._map, 
columns);
+               this._tickMap = new L.Control.Header.GapTickMap(this._map, 
columns, columnsGeometry);
                this._startOffset = this._tickMap.getStartOffset();
 
                // setup conversion routine
                this.converter = L.Util.bind(converter, context);
 
                // create group array
-               this._groupLevels = parseInt(columns[0].groupLevels);
+               this._groupLevels = columns ? parseInt(columns[0].groupLevels):
+                       sheetGeometry.getColumnGroupLevels();
                this._groups = this._groupLevels ? new Array(this._groupLevels) 
: null;
 
                // collect group controls data
                if (colGroups !== undefined && this._groups) {
                        this._collectGroupsData(colGroups);
                }
+               else if (sheetGeometry) {
+                       
this._collectGroupsData(sheetGeometry.getColumnGroupsDataInView());
+               }
 
                if (this._groups) {
                        this.resize(this._computeOutlineWidth() + 
this._borderWidth + this._headerHeight);
diff --git a/loleaflet/src/control/Control.Header.js 
b/loleaflet/src/control/Control.Header.js
index 5aeac06da..3ef4bb2e0 100644
--- a/loleaflet/src/control/Control.Header.js
+++ b/loleaflet/src/control/Control.Header.js
@@ -804,7 +804,35 @@ L.Control.Header.colHeaderHeight = undefined;
  */
 L.Control.Header.GapTickMap = L.Class.extend({
 
-       initialize: function (map, ticks) {
+       initialize: function (map, ticks, dimensionGeometry) {
+
+               if (dimensionGeometry) {
+                       // Until .uno:ViewRowColumnHeaders is not phased out, 
we need to live with
+                       // GapTickMap datastructure to avoid an invasive 
refactoring.
+                       // L.SheetGeometry and L.SheetDimension datastructures 
can directly provide
+                       // position/size of any row/column intuitively without 
using unnecessary
+                       // terminologies like (1-based) Gap and (0-based) Tick.
+                       var dimrange = dimensionGeometry.getViewElementRange();
+                       var start = Math.max(0, dimrange.start - 2);
+                       var startData = dimensionGeometry.getElementData(start);
+                       var startText = start ? start + 1 : 0;
+                       var endText = Math.min(dimensionGeometry.getMaxIndex(), 
dimrange.end + 2) + 1;
+
+                       this._minTickIdx = startText;
+                       this._maxTickIdx = endText;
+                       this._startOffset = start ? startData.startpos + 
startData.size : 0;
+                       this._tilePixelScale = 1; // We already have everything 
in css px.
+
+                       ticks = start ? [] : [0];
+                       dimensionGeometry.forEachInRange(start,
+                               this._maxTickIdx - 1, function (idx, data) {
+                                       ticks[idx + 1] = data.startpos + 
data.size;
+                               });
+
+                       this._ticks = ticks;
+
+                       return;
+               }
 
                var gapSize;
                this._ticks = [];
diff --git a/loleaflet/src/control/Control.RowHeader.js 
b/loleaflet/src/control/Control.RowHeader.js
index afbba3c45..820f2205b 100644
--- a/loleaflet/src/control/Control.RowHeader.js
+++ b/loleaflet/src/control/Control.RowHeader.js
@@ -186,7 +186,7 @@ L.Control.RowHeader = L.Control.Header.extend({
        },
 
        _updateRowHeader: function () {
-               this._map._docLayer.requestViewRowColumnData({x: 0, y: 
this._map._getTopLeftPoint().y, offset: {x: 0, y: undefined}});
+               this._map._docLayer.refreshViewData({x: 0, y: 
this._map._getTopLeftPoint().y, offset: {x: 0, y: undefined}});
        },
 
        drawHeaderEntry: function (entry, isOver, isHighlighted, isCurrent) {
@@ -365,8 +365,10 @@ L.Control.RowHeader = L.Control.Header.extend({
        },
 
        viewRowColumnHeaders: function (e) {
-               if (e.data.rows && e.data.rows.length) {
-                       this.fillRows(e.data.rows, e.data.rowGroups, 
e.converter, e.context);
+               var dataInEvent = (e.data && e.data.rows && e.data.rows.length);
+               if (dataInEvent || e.updaterows) {
+                       dataInEvent ? this.fillRows(e.data.rows, 
e.data.rowGroups, e.converter, e.context) :
+                               this.fillRows(undefined, undefined, 
e.converter, e.context);
                        this._onUpdateCurrentRow(e.cursor);
                        if (e.selection && e.selection.hasSelection) {
                                this._onUpdateSelection(e.selection);
@@ -378,7 +380,7 @@ L.Control.RowHeader = L.Control.Header.extend({
        },
 
        fillRows: function (rows, rowGroups, converter, context) {
-               if (rows.length < 2)
+               if (rows && rows.length < 2)
                        return;
 
                var canvas = this._canvas;
@@ -394,21 +396,28 @@ L.Control.RowHeader = L.Control.Header.extend({
                        this._lastMouseOverIndex = undefined;
                }
 
+               var sheetGeometry = this._map._docLayer.sheetGeometry;
+               var rowsGeometry = sheetGeometry ? 
sheetGeometry.getRowsGeometry() : undefined;
+
                // create data structure for row heights
-               this._tickMap = new L.Control.Header.GapTickMap(this._map, 
rows);
+               this._tickMap = new L.Control.Header.GapTickMap(this._map, 
rows, rowsGeometry);
                this._startOffset = this._tickMap.getStartOffset();
 
                // setup conversion routine
                this.converter = L.Util.bind(converter, context);
 
                // create group array
-               this._groupLevels = parseInt(rows[0].groupLevels);
+               this._groupLevels = rows ? parseInt(rows[0].groupLevels) :
+                       sheetGeometry.getRowGroupLevels();
                this._groups = this._groupLevels ? new Array(this._groupLevels) 
: null;
 
                // collect group controls data
                if (rowGroups !== undefined && this._groups) {
                        this._collectGroupsData(rowGroups);
                }
+               else if (sheetGeometry) {
+                       
this._collectGroupsData(sheetGeometry.getRowGroupsDataInView());
+               }
 
                if (this._groups) {
                        this.resize(this._computeOutlineWidth() + 
this._borderWidth + this._headerWidth);
diff --git a/loleaflet/src/control/Control.Scroll.js 
b/loleaflet/src/control/Control.Scroll.js
index e3f9cf4dc..cb323e1c7 100644
--- a/loleaflet/src/control/Control.Scroll.js
+++ b/loleaflet/src/control/Control.Scroll.js
@@ -114,7 +114,7 @@ L.Control.Scroll = L.Control.extend({
                        return;
                }
 
-               this._map._docLayer.requestViewRowColumnData({ x: newLeft, y: 
newTop, offset: offset});
+               this._map._docLayer.refreshViewData({ x: newLeft, y: newTop, 
offset: offset});
 
                this._prevScrollY = newTop;
                this._prevScrollX = newLeft;
@@ -267,7 +267,7 @@ L.Control.Scroll = L.Control.extend({
                        offset.y = 1;
                }
                if (e.updateHeaders && this._map._docLayer._docType === 
'spreadsheet') {
-                       this._map._docLayer.requestViewRowColumnData({x: e.x, 
y: e.y, offset: offset});
+                       this._map._docLayer.refreshViewData({x: e.x, y: e.y, 
offset: offset});
                }
                this._map.fire('scrolloffset', offset);
                this._ignoreScroll = null;
diff --git a/loleaflet/src/layer/CalcGridLines.js 
b/loleaflet/src/layer/CalcGridLines.js
index 4b4e58d3a..37492b606 100644
--- a/loleaflet/src/layer/CalcGridLines.js
+++ b/loleaflet/src/layer/CalcGridLines.js
@@ -78,8 +78,13 @@ L.CalcGridLines = L.LayerGroup.extend({
                // into map coordinate units
                var pixelToMapUnitRatio = 
this._map.options.crs.scale(this._map.getZoom());
 
-               if (ev.data.columns && ev.data.columns.length) {
-                       ticks = new L.Control.Header.GapTickMap(this._map, 
ev.data.columns);
+               var colDataInEvent = ev.data && ev.data.columns && 
ev.data.columns.length;
+               var rowDataInEvent = ev.data && ev.data.rows && 
ev.data.rows.length;
+
+               if (colDataInEvent || ev.updatecolumns) {
+                       var columnsData = colDataInEvent ? ev.data.columns : 
undefined;
+                       var columnsGeometry = colDataInEvent ? undefined : 
this._map._docLayer.sheetGeometry.getColumnsGeometry();
+                       ticks = new L.Control.Header.GapTickMap(this._map, 
columnsData, columnsGeometry);
                        this._colLines.clearLayers();
 
                        ticks.forEachTick(function(idx, pos) {
@@ -92,8 +97,10 @@ L.CalcGridLines = L.LayerGroup.extend({
                        }.bind(this));
                }
 
-               if (ev.data.rows && ev.data.rows.length) {
-                       ticks = new L.Control.Header.GapTickMap(this._map, 
ev.data.rows);
+               if (rowDataInEvent || ev.updaterows) {
+                       var rowsData = rowDataInEvent ? ev.data.rows : 
undefined;
+                       var rowsGeometry = rowDataInEvent ? undefined : 
this._map._docLayer.sheetGeometry.getRowsGeometry();
+                       ticks = new L.Control.Header.GapTickMap(this._map, 
rowsData, rowsGeometry);
                        this._rowLines.clearLayers();
 
                        ticks.forEachTick(function(idx, pos) {
diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 6ed8ae4b4..37a08755e 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -5,6 +5,10 @@
 
 /* global */
 L.CalcTileLayer = L.TileLayer.extend({
+       options: {
+               sheetGeometryDataEnabled: true
+       },
+
        STD_EXTRA_WIDTH: 113, /* 2mm extra for optimal width,
                                                          * 0.1986cm with TeX 
points,
                                                          * 0.1993cm with PS 
points. */
@@ -264,13 +268,16 @@ L.CalcTileLayer = L.TileLayer.extend({
                                }
                        }
                } else if (textMsg.startsWith('invalidateheader: column')) {
-                       this.requestViewRowColumnData({x: 
this._map._getTopLeftPoint().x, y: 0, offset: {x: undefined, y: 0}});
+                       this.refreshViewData({x: 
this._map._getTopLeftPoint().x, y: 0,
+                               offset: {x: undefined, y: 0}}, true /* 
sheetGeometryChanged */);
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
                } else if (textMsg.startsWith('invalidateheader: row')) {
-                       this.requestViewRowColumnData({x: 0, y: 
this._map._getTopLeftPoint().y, offset: {x: 0, y: undefined}});
+                       this.refreshViewData({x: 0, y: 
this._map._getTopLeftPoint().y,
+                               offset: {x: 0, y: undefined}}, true /* 
sheetGeometryChanged */);
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
                } else if (textMsg.startsWith('invalidateheader: all')) {
-                       this.requestViewRowColumnData({x: 
this._map._getTopLeftPoint().x, y: this._map._getTopLeftPoint().y, offset: {x: 
undefined, y: undefined}});
+                       this.refreshViewData({x: 
this._map._getTopLeftPoint().x, y: this._map._getTopLeftPoint().y,
+                               offset: {x: undefined, y: undefined}}, true /* 
sheetGeometryChanged */);
                        this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
                } else {
                        L.TileLayer.prototype._onMessage.call(this, textMsg, 
img);
@@ -360,15 +367,17 @@ L.CalcTileLayer = L.TileLayer.extend({
                if (part !== this._selectedPart && !this.isHiddenPart(part)) {
                        this._map.setPart(part, true);
                        this._map.fire('setpart', {selectedPart: 
this._selectedPart});
-                       // TODO: test it!
-                       this.requestViewRowColumnData();
+                       this.refreshViewData(undefined, true /* 
sheetGeometryChanged */);
                }
        },
 
        _onZoomRowColumns: function () {
                this._sendClientZoom();
-               // TODO: test it!
-               this.requestViewRowColumnData();
+               if (this.sheetGeometry) {
+                       
this.sheetGeometry.setTileGeometryData(this._tileWidthTwips, 
this._tileHeightTwips,
+                               this._tileSize, this._tilePixelScale);
+               }
+               this.refreshViewData();
                this._map._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotationsPosition');
        },
 
@@ -454,8 +463,13 @@ L.CalcTileLayer = L.TileLayer.extend({
                }
        },
 
-       // This send .uno:ViewRowColumnHeaders command to core with the new 
view coordinates.
-       requestViewRowColumnData: function (coordinatesData) {
+       // This initiates a selective repainting of row/col headers and
+       // gridlines based on the settings of coordinatesData.offset. This
+       // should be called whenever the view area changes (scrolling, panning,
+       // zooming, cursor moving out of view-area etc.).  Depending on the
+       // active sheet geometry data-source, it may ask core to send current
+       // view area's data or the global data on geometry changes.
+       refreshViewData: function (coordinatesData, sheetGeometryChanged) {
 
                // There are places that call this function with no arguments 
to indicate that the
                // command arguments should be the current map area coordinates.
@@ -475,32 +489,57 @@ L.CalcTileLayer = L.TileLayer.extend({
                        topLeftPoint.y = this._map._getTopLeftPoint().y;
                }
 
+               var updateRows = true;
+               var updateCols = true;
+
                if (offset.x === 0) {
-                       topLeftPoint.x = -1;
-                       sizePx.x = 0;
+                       updateCols = false;
+                       if (!this.options.sheetGeometryDataEnabled) {
+                               topLeftPoint.x = -1;
+                               sizePx.x = 0;
+                       }
                }
                if (offset.y === 0) {
-                       topLeftPoint.y = -1;
-                       sizePx.y = 0;
+                       updateRows = false;
+                       if (!this.options.sheetGeometryDataEnabled) {
+                               topLeftPoint.y = -1;
+                               sizePx.y = 0;
+                       }
                }
 
                var pos = this._pixelsToTwips(topLeftPoint);
                var size = this._pixelsToTwips(sizePx);
-               var payload = 'commandvalues 
command=.uno:ViewRowColumnHeaders?x=' + Math.round(pos.x) + '&y=' + 
Math.round(pos.y) +
-                       '&width=' + Math.round(size.x) + '&height=' + 
Math.round(size.y);
 
-               if (coordinatesData.outline) {
-                       payload += '&columnOutline=' + 
coordinatesData.outline.column + '&groupLevel=' + coordinatesData.outline.level
-                               + '&groupIndex=' + 
coordinatesData.outline.index + '&groupHidden=' + 
coordinatesData.outline.hidden;
+               if (!this.options.sheetGeometryDataEnabled) {
+                       this.requestViewRowColumnData(pos, size);
+                       return;
+               }
+
+               if (sheetGeometryChanged || !this.sheetGeometry) {
+                       this.requestSheetGeometryData(
+                               {columns: updateCols, rows: updateRows});
+                       return;
                }
 
+               this.sheetGeometry.setViewArea(pos, size);
+               this._updateHeadersGridLines(undefined, updateCols, updateRows);
+       },
+
+       // This send .uno:ViewRowColumnHeaders command to core with the new 
view coordinates (tile-twips).
+       requestViewRowColumnData: function (pos, size) {
+
+               var payload = 'commandvalues 
command=.uno:ViewRowColumnHeaders?x=' + Math.round(pos.x) + '&y=' + 
Math.round(pos.y) +
+                       '&width=' + Math.round(size.x) + '&height=' + 
Math.round(size.y);
+
                this._map._socket.sendMessage(payload);
        },
 
        // sends the .uno:SheetGeometryData command optionally with arguments.
-       requestSheetGeomtryData: function (flags) {
+       requestSheetGeometryData: function (flags) {
                var unoCmd = '.uno:SheetGeometryData';
-               var haveArgs = (typeof flags == 'object' && (flags.columns === 
true || flags.rows === true));
+               var haveArgs = (typeof flags == 'object' &&
+                       (flags.columns === true || flags.rows === true) &&
+                       (flags.columns !== flags.rows));
                var payload = 'commandvalues command=' + unoCmd;
 
                if (haveArgs) {
@@ -533,9 +572,15 @@ L.CalcTileLayer = L.TileLayer.extend({
                this._map._socket.sendMessage(payload);
        },
 
-       _handleViewRowColumnHeadersMsg: function (jsonMsgObj) {
+       // Sends a notification to the row/col header and gridline controls that
+       // they need repainting.
+       // viewAreaData is the parsed .uno:ViewRowColumnHeaders JSON if that 
source is used.
+       // else it should be undefined.
+       _updateHeadersGridLines: function (viewAreaData, updateCols, 
updateRows) {
                this._map.fire('viewrowcolumnheaders', {
-                       data: jsonMsgObj,
+                       data: viewAreaData,
+                       updaterows: updateRows,
+                       updatecolumns: updateCols,
                        cursor: this._getCursorPosSize(),
                        selection: this._getSelectionHeaderData(),
                        converter: this._twipsToPixels,
@@ -544,8 +589,17 @@ L.CalcTileLayer = L.TileLayer.extend({
        },
 
        _handleSheetGeometryDataMsg: function (jsonMsgObj) {
-               // TODO: use the L.SheetGeometry datastructure
-               this._map.sheetGeomData = jsonMsgObj;
+               if (!this.sheetGeometry) {
+                       this.sheetGeometry = new L.SheetGeometry(jsonMsgObj,
+                               this._tileWidthTwips, this._tileHeightTwips,
+                               this._tileSize, this._tilePixelScale);
+               }
+
+               this.sheetGeometry.update(jsonMsgObj);
+               
this.sheetGeometry.setViewArea(this._pixelsToTwips(this._map._getTopLeftPoint()),
+                       this._pixelsToTwips(this._map.getSize()));
+               this._updateHeadersGridLines(undefined, true /* updateCols */,
+                       true /* updateRows */);
        },
 
        _onCommandValuesMsg: function (textMsg) {
@@ -560,7 +614,7 @@ L.CalcTileLayer = L.TileLayer.extend({
 
                var comment;
                if (values.commandName === '.uno:ViewRowColumnHeaders') {
-                       this._handleViewRowColumnHeadersMsg(values);
+                       this._updateHeadersGridLines(values);
 
                } else if (values.commandName === '.uno:SheetGeometryData') {
                        this._handleSheetGeometryDataMsg(values);
@@ -697,6 +751,14 @@ L.SheetGeometry = L.Class.extend({
                return true;
        },
 
+       getColumnsGeometry: function () {
+               return this._columns;
+       },
+
+       getRowsGeometry: function () {
+               return this._rows;
+       },
+
        // returns an object with keys 'start' and 'end' indicating the
        // column range in the current view area.
        getViewColumnRange: function () {
diff --git a/loleaflet/src/map/Map.js b/loleaflet/src/map/Map.js
index 8e05b5544..18dbe5b57 100644
--- a/loleaflet/src/map/Map.js
+++ b/loleaflet/src/map/Map.js
@@ -324,7 +324,7 @@ L.Map = L.Evented.extend({
                this._socket.sendMessage('commandvalues 
command=.uno:LanguageStatus');
                this._socket.sendMessage('commandvalues 
command=.uno:ViewAnnotations');
                if (this._docLayer._docType === 'spreadsheet') {
-                       this._docLayer.requestViewRowColumnData();
+                       this._docLayer.refreshViewData();
                }
                this._docLayer._getToolbarCommandsValues();
        },
commit f13b9aa3ecb5687261d5e8d15f5b160de75c1aba
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Thu May 14 23:11:07 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:43 2020 +0530

    Round down when computing row/col index from tile-twips position
    
    And lets not unnecessarily extend the cellrange in the view as the
    computation is accurate.
    
    Change-Id: I62de80ce42430c62a399d4e39bafab7896217bf1

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 974c1daae..6ed8ae4b4 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -975,6 +975,7 @@ L.SheetDimension = L.Class.extend({
                });
        },
 
+       // computes element index from tile-twips position.
        _getIndexFromTileTwipsPos: function (pos) {
                var span = this._visibleSizes.getSpanDataByCustomDataField(pos, 
'postiletwips');
                var elementCount = span.end - span.start + 1;
@@ -982,16 +983,17 @@ L.SheetDimension = L.Class.extend({
                        this._devPixelsPerCssPixel * this._twipsPerCSSPixel);
                var posEnd = span.data.postiletwips;
                var sizeOne = (posEnd - posStart) / elementCount;
-               var relativeIndex = Math.round((pos - posStart) / sizeOne);
+
+               // always round down as relativeIndex is zero-based.
+               var relativeIndex = Math.floor((pos - posStart) / sizeOne);
 
                return span.start + relativeIndex;
        },
 
        setViewLimits: function (startPosTileTwips, endPosTileTwips) {
 
-               // Extend the range a bit, to compensate for rounding errors.
-               this._viewStartIndex = Math.max(0, 
this._getIndexFromTileTwipsPos(startPosTileTwips) - 2);
-               this._viewEndIndex = Math.min(this._maxIndex, 
this._getIndexFromTileTwipsPos(endPosTileTwips) + 2);
+               this._viewStartIndex = Math.max(0, 
this._getIndexFromTileTwipsPos(startPosTileTwips));
+               this._viewEndIndex = Math.min(this._maxIndex, 
this._getIndexFromTileTwipsPos(endPosTileTwips));
        },
 
        getViewElementRange: function () {
commit 16fec63f7fc490eadb04c7cb53ab1966eacd28dd
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Thu May 14 22:49:30 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:43 2020 +0530

    add table outline query interfaces
    
    Change-Id: Ibd26592e321af1dca5ee7ff369e58b1f51dc480b

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index b3b7c7037..974c1daae 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -841,7 +841,7 @@ L.SheetDimension = L.Class.extend({
                this._sizes = new L.SpanList();
                this._hidden = new L.BoolSpanList();
                this._filtered = new L.BoolSpanList();
-               this._groups = new L.DimensionOutlines();
+               this._outlines = new L.DimensionOutlines();
 
                // This is used to store the span-list of sizes
                // with hidden/filtered elements set to zero size.
@@ -876,7 +876,7 @@ L.SheetDimension = L.Class.extend({
                }
 
                if (jsonObject.hasOwnProperty('groups')) {
-                       thisLoadOK = this._groups.load(jsonObject.groups);
+                       thisLoadOK = this._outlines.load(jsonObject.groups);
                        loadsOK = loadsOK && thisLoadOK;
                }
 
@@ -939,25 +939,27 @@ L.SheetDimension = L.Class.extend({
                });
        },
 
-       getElementData: function (index) {
+       // returns the element pos/size in css pixels by default.
+       getElementData: function (index, useDevicePixels) {
                var span = this._visibleSizes.getSpanDataByIndex(index);
                if (span === undefined) {
                        return undefined;
                }
 
-               return this._getElementDataFromSpanByIndex(index, span);
+               return this._getElementDataFromSpanByIndex(index, span, 
useDevicePixels);
        },
 
-       _getElementDataFromSpanByIndex: function (index, span) {
+       // returns element pos/size in css pixels by default.
+       _getElementDataFromSpanByIndex: function (index, span, useDevicePixels) 
{
                if (span === undefined || index < span.start || span.end < 
index) {
                        return undefined;
                }
 
                var numSizes = span.end - index + 1;
-               // all in css pixels.
+               var pixelScale = useDevicePixels ? this._devPixelsPerCssPixel : 
1;
                return {
-                       startpos: (span.data.posdevpx - span.data.sizedev * 
numSizes) / this._devPixelsPerCssPixel,
-                       size: span.data.sizedev / this._devPixelsPerCssPixel
+                       startpos: (span.data.posdevpx - span.data.sizedev * 
numSizes) / pixelScale,
+                       size: span.data.sizedev / pixelScale
                };
        },
 
@@ -1432,16 +1434,19 @@ L.DimensionOutlines = L.Class.extend({
 
                                var olineEntry = {
                                        start: parseInt(entrySplits[0]),
-                                       size: parseInt(entrySplits[1]),
+                                       end: parseInt(entrySplits[1]), // this 
is size.
                                        hidden: parseInt(entrySplits[2]),
                                        visible: parseInt(entrySplits[3])
                                };
 
-                               if (isNaN(olineEntry.start) || 
isNaN(olineEntry.size) ||
+                               if (isNaN(olineEntry.start) || 
isNaN(olineEntry.end) ||
                                        isNaN(olineEntry.hidden) || 
isNaN(olineEntry.visible)) {
                                        return false;
                                }
 
+                               // correct the 'end' attribute.
+                               olineEntry.end += (olineEntry.start - 1);
+
                                collections.push(olineEntry);
                        }
 
@@ -1450,5 +1455,132 @@ L.DimensionOutlines = L.Class.extend({
 
                this._outlines = outlines;
                return true;
+       },
+
+       getLevels: function () {
+               return this._outlines.length;
+       },
+
+       // Calls 'callback' for all groups in all levels that have an 
intersection with the inclusive element range [start, end].
+       // 'callback' is called with these parameters : (levelIdx, groupIdx, 
groupStart, groupEnd, groupHidden).
+       forEachGroupInRange: function (start, end, callback) {
+
+               if (start === undefined || end === undefined || callback === 
undefined) {
+                       return;
+               }
+
+               if (!this._outlines.length || start > end) {
+                       return;
+               }
+
+               // Search direction provider for binarySearch().
+               // Here we want to find the first group after or intersects 
elementIdx.
+               // return value : 0 for match, -1 for "try previous entries", 
+1 for "try next entries".
+               var directionProvider = function (elementIdx, prevGroup, 
curGroup/*, nextGroup*/) {
+
+                       var direction = (elementIdx < curGroup.start) ? -1 :
+                               (curGroup.end < elementIdx) ? 1 : 0;
+
+                       if (direction >= 0) {
+                               return direction;
+                       }
+
+                       // If curGroup is the first one, or elementidx is after 
prevGroup's end, then it is a match.
+                       if (!prevGroup || (prevGroup.end < elementIdx)) {
+                               return 0;
+                       }
+
+                       return -1;
+               };
+
+               for (var levelIdx = this._outlines.length - 1; levelIdx >= 0; 
--levelIdx) {
+
+                       var groupsInLevel = this._outlines[levelIdx];
+                       // Find the first group after or that intersects 
'start'.
+                       var startGroupIdx = binarySearch(groupsInLevel, start, 
directionProvider);
+                       if (startGroupIdx == -1) {
+                               // All groups at this level are before 'start'.
+                               continue;
+                       }
+
+                       var startGroup = groupsInLevel[startGroupIdx];
+                       if (end < startGroup.start) {
+                               // No group at this level intersects the range 
[start, end].
+                               continue;
+                       }
+
+                       for (var groupIdx = startGroupIdx; groupIdx < 
groupsInLevel.length; ++groupIdx) {
+                               var group = groupsInLevel[groupIdx];
+                               if (end < group.start) {
+                                       continue;
+                               }
+
+                               callback(levelIdx, groupIdx, group.start,
+                                       group.end, group.hidden);
+                       }
+               }
        }
 });
+
+
+// Does binary search on array for key, possibly using a custom direction 
provider.
+// Of course, this assumes that the array is sorted (w.r.t to the semantics of
+// the directionProvider when it is provided).
+// It returns the index of the match if successful else returns -1.
+//
+// directionProvider will be provided the following parameters :
+// (key, previousArrayElement, currentArrayElement, nextArrayElement)
+// previousArrayElement and nextArrayElement can be undefined when
+// currentArrayElement is the first or the last element of the array
+// respectively. This function should return:
+//   0: for a match(to stop search),
+//   1: to try searching upper half,
+//  -1: to try searching lower half
+
+function binarySearch(array, key, directionProvider) {
+
+       if (array === undefined || !array.length) {
+               return -1;
+       }
+
+       if (directionProvider === undefined) {
+               directionProvider = function (key, testvalue) {
+                       return (key === testvalue) ? 0 :
+                               (key < testvalue) ? -1 : 1;
+               };
+       }
+
+       var start = 0;
+       var end = array.length - 1;
+
+       // Bound checks and early exit.
+       var startDir = directionProvider(key, undefined, array[0], array[1]);
+       if (startDir <= 0) {
+               return startDir;
+       }
+
+       var endDir = directionProvider(key, array[end - 1], array[end]);
+       if (endDir >= 0) {
+               return endDir ? -1 : end;
+       }
+
+       var mid = -1;
+       while (start <= end) {
+               mid = Math.round((start + end) / 2);
+               var direction = directionProvider(key, array[mid-1],
+                       array[mid], array[mid+1]);
+
+               if (direction == 0) {
+                       break;
+               }
+
+               if (direction == -1) {
+                       end = mid - 1;
+               }
+               else {
+                       start = mid + 1;
+               }
+       }
+
+       return (start > end) ? -1 : mid;
+}
commit 3de148f8c6f6db60e48ace1eec70ba84eb9c81a3
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Tue May 12 10:52:50 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:43 2020 +0530

    introduce .uno:SheetGeometryData requester/handler
    
    Change-Id: Iaeef462346f22996daddb583c459a5f67aa6c290

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index b0773251d..b3b7c7037 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -497,6 +497,42 @@ L.CalcTileLayer = L.TileLayer.extend({
                this._map._socket.sendMessage(payload);
        },
 
+       // sends the .uno:SheetGeometryData command optionally with arguments.
+       requestSheetGeomtryData: function (flags) {
+               var unoCmd = '.uno:SheetGeometryData';
+               var haveArgs = (typeof flags == 'object' && (flags.columns === 
true || flags.rows === true));
+               var payload = 'commandvalues command=' + unoCmd;
+
+               if (haveArgs) {
+                       var argList = [];
+                       if (flags.columns === true) {
+                               argList.push('columns=1');
+                       }
+                       if (flags.rows === true) {
+                               argList.push('rows=1');
+                       }
+
+                       var dataTypeFlagNames = ['sizes', 'hidden', 'filtered', 
'groups'];
+                       var dataTypesPresent = false;
+                       dataTypeFlagNames.forEach(function (name) {
+                               if (flags[name] === true) {
+                                       argList.push(name + '=1');
+                                       dataTypesPresent = true;
+                               }
+                       });
+
+                       if (!dataTypesPresent) {
+                               dataTypeFlagNames.forEach(function (name) {
+                                       argList.push(name + '=1');
+                               });
+                       }
+
+                       payload += '?' + argList.join('&');
+               }
+
+               this._map._socket.sendMessage(payload);
+       },
+
        _handleViewRowColumnHeadersMsg: function (jsonMsgObj) {
                this._map.fire('viewrowcolumnheaders', {
                        data: jsonMsgObj,
@@ -507,6 +543,11 @@ L.CalcTileLayer = L.TileLayer.extend({
                });
        },
 
+       _handleSheetGeometryDataMsg: function (jsonMsgObj) {
+               // TODO: use the L.SheetGeometry datastructure
+               this._map.sheetGeomData = jsonMsgObj;
+       },
+
        _onCommandValuesMsg: function (textMsg) {
                var jsonIdx = textMsg.indexOf('{');
                if (jsonIdx === -1)
@@ -521,6 +562,9 @@ L.CalcTileLayer = L.TileLayer.extend({
                if (values.commandName === '.uno:ViewRowColumnHeaders') {
                        this._handleViewRowColumnHeadersMsg(values);
 
+               } else if (values.commandName === '.uno:SheetGeometryData') {
+                       this._handleSheetGeometryDataMsg(values);
+
                } else if (values.comments) {
                        this.clearAnnotations();
                        for (var index in values.comments) {
commit 5c48a1698eed022cdf37db0a23c135bc60ec687d
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Sat May 9 20:34:37 2020 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Mon May 25 00:52:43 2020 +0530

    add sheet-geometry datastructures
    
    to parse, store the .uno:SheetGeometryData JSON efficiently although it
    is optimized for fast querying. L.SheetGeometry is the external class
    that exposes all necessary sheet query interfaces.
    
    Change-Id: I24df8d85734a6cdf9c393fd2c3c5ed4de0ea29f3

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js 
b/loleaflet/src/layer/tile/CalcTileLayer.js
index 06ffa2bef..b0773251d 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -567,3 +567,844 @@ L.CalcTileLayer = L.TileLayer.extend({
                this._onUpdateCurrentHeader();
        }
 });
+
+
+// TODO: Move these somewhere more appropriate.
+
+// Sheet geometry data
+L.SheetGeometry = L.Class.extend({
+
+       // sheetGeomJSON is expected to be the parsed JSON message from core
+       // in response to client command '.uno:SheetGeometryData' with
+       // all flags (ie 'columns', 'rows', 'sizes', 'hidden', 'filtered',
+       // 'groups') enabled.
+       initialize: function (sheetGeomJSON, tileWidthTwips, tileHeightTwips,
+               tileSizeCSSPixels, dpiScale) {
+
+               if (typeof sheetGeomJSON !== 'object' ||
+                       typeof tileWidthTwips !== 'number' ||
+                       typeof tileHeightTwips !== 'number' ||
+                       typeof tileSizeCSSPixels !== 'number' ||
+                       typeof dpiScale !== 'number') {
+                       console.error('Incorrect constructor argument types or 
missing required arguments');
+                       return;
+               }
+
+               this._columns = new L.SheetDimension();
+               this._rows = new L.SheetDimension();
+               this._unoCommand = '.uno:SheetGeometryData';
+
+               // Set various unit conversion info early on because on 
update() call below, these info are needed.
+               this.setTileGeometryData(tileWidthTwips, tileHeightTwips, 
tileSizeCSSPixels,
+                       dpiScale, false /* update position info ?*/);
+
+               this.update(sheetGeomJSON, /* checkCompleteness */ true);
+       },
+
+       update: function (sheetGeomJSON, checkCompleteness) {
+
+               if (!this._testValidity(sheetGeomJSON, checkCompleteness)) {
+                       return false;
+               }
+
+               var updateOK = true;
+               if (sheetGeomJSON.columns) {
+                       if (!this._columns.update(sheetGeomJSON.columns)) {
+                               console.error(this._unoCommand + ': columns 
update failed.');
+                               updateOK = false;
+                       }
+               }
+
+               if (sheetGeomJSON.rows) {
+                       if (!this._rows.update(sheetGeomJSON.rows)) {
+                               console.error(this._unoCommand + ': rows update 
failed.');
+                               updateOK = false;
+                       }
+               }
+
+               this._columns.setMaxIndex(+sheetGeomJSON.maxtiledcolumn);
+               this._rows.setMaxIndex(+sheetGeomJSON.maxtiledrow);
+
+               return updateOK;
+       },
+
+       setTileGeometryData: function (tileWidthTwips, tileHeightTwips, 
tileSizeCSSPixels,
+               dpiScale, updatePositions) {
+
+               this._columns.setTileGeometryData(tileWidthTwips, 
tileSizeCSSPixels, dpiScale, updatePositions);
+               this._rows.setTileGeometryData(tileHeightTwips, 
tileSizeCSSPixels, dpiScale, updatePositions);
+       },
+
+       setViewArea: function (topLeftTwipsPoint, sizeTwips) {
+
+               if (!(topLeftTwipsPoint instanceof L.Point) || !(sizeTwips 
instanceof L.Point)) {
+                       console.error('invalid argument types');
+                       return false;
+               }
+
+               var left   = topLeftTwipsPoint.x;
+               var top    = topLeftTwipsPoint.y;
+               var right  = left + sizeTwips.x;
+               var bottom = top + sizeTwips.y;
+
+               this._columns.setViewLimits(left, right);
+               this._rows.setViewLimits(top, bottom);
+
+               return true;
+       },
+
+       // returns an object with keys 'start' and 'end' indicating the
+       // column range in the current view area.
+       getViewColumnRange: function () {
+               return this._columns.getViewElementRange();
+       },
+
+       // returns an object with keys 'start' and 'end' indicating the
+       // row range in the current view area.
+       getViewRowRange: function () {
+               return this._rows.getViewElementRange();
+       },
+
+       getViewCellRange: function () {
+               return {
+                       columnrange: this.getViewColumnRange(),
+                       rowrange: this.getViewRowRange()
+               };
+       },
+
+       // Returns an object with the following fields:
+       // columnIndex should be zero based.
+       // 'startpos' (start position of the column in css pixels), 'size' 
(column size in css pixels).
+       // Note: All these fields are computed by assuming zero sizes for 
hidden/filtered columns.
+       getColumnData: function (columnIndex) {
+               return this._columns.getElementData(columnIndex);
+       },
+
+       // Returns an object with the following fields:
+       // rowIndex should be zero based.
+       // 'startpos' (start position of the row in css pixels), 'size' (row 
size in css pixels).
+       // Note: All these fields are computed by assuming zero sizes for 
hidden/filtered rows.
+       getRowData: function (rowIndex) {
+               return this._rows.getElementData(rowIndex);
+       },
+
+       // Runs the callback for every column in the inclusive range 
[columnStart, columnEnd].
+       // callback is expected to have a signature of (column, columnData)
+       // where 'column' will contain the column index(zero based) and 
'columnData' will be an object with
+       // the same fields as returned by getColumnData().
+       forEachColumnInRange: function (columnStart, columnEnd, callback) {
+               this._columns.forEachInRange(columnStart, columnEnd, callback);
+       },
+
+       // Runs the callback for every row in the inclusive range [rowStart, 
rowEnd].
+       // callback is expected to have a signature of (row, rowData)
+       // where 'row' will contain the row index(zero based) and 'rowData' 
will be an object with
+       // the same fields as returned by getRowData().
+       forEachRowInRange: function (rowStart, rowEnd, callback) {
+               this._rows.forEachInRange(rowStart, rowEnd, callback);
+       },
+
+       getColumnGroupLevels: function () {
+               return this._columns.getGroupLevels();
+       },
+
+       getRowGroupLevels: function () {
+               return this._rows.getGroupLevels();
+       },
+
+       getColumnGroupsDataInView: function () {
+               return this._columns.getGroupsDataInView();
+       },
+
+       getRowGroupsDataInView: function () {
+               return this._rows.getGroupsDataInView();
+       },
+
+       _testValidity: function (sheetGeomJSON, checkCompleteness) {
+
+               if (!sheetGeomJSON.hasOwnProperty('commandName')) {
+                       console.error(this._unoCommand + ' response has no 
property named "commandName".');
+                       return false;
+               }
+
+               if (sheetGeomJSON.commandName !== this._unoCommand) {
+                       console.error('JSON response has wrong commandName: ' +
+                               sheetGeomJSON.commandName + ' expected: ' +
+                               this._unoCommand);
+                       return false;
+               }
+
+               if (typeof sheetGeomJSON.maxtiledcolumn !== 'string' ||
+                       !/^\d+$/.test(sheetGeomJSON.maxtiledcolumn)) {
+                       console.error('JSON is missing/unreadable 
maxtiledcolumn property');
+                       return false;
+               }
+
+               if (typeof sheetGeomJSON.maxtiledrow !== 'string' ||
+                       !/^\d+$/.test(sheetGeomJSON.maxtiledrow)) {
+                       console.error('JSON is missing/unreadable maxtiledrow 
property');
+                       return false;
+               }
+
+               if (checkCompleteness) {
+
+                       if (!sheetGeomJSON.hasOwnProperty('rows') ||
+                               !sheetGeomJSON.hasOwnProperty('columns')) {
+
+                               console.error(this._unoCommand + ' response is 
incomplete.');
+                               return false;
+                       }
+
+                       if (typeof sheetGeomJSON.rows !== 'object' ||
+                               typeof sheetGeomJSON.columns !== 'object') {
+
+                               console.error(this._unoCommand + ' response has 
invalid rows/columns children.');
+                               return false;
+                       }
+
+                       var expectedFields = ['sizes', 'hidden', 'filtered'];
+                       for (var idx = 0; idx < expectedFields.length; idx++) {
+
+                               var fieldName = expectedFields[idx];
+                               var encodingForCols = 
sheetGeomJSON.columns[fieldName];
+                               var encodingForRows = 
sheetGeomJSON.rows[fieldName];
+
+                               // Don't accept empty string or any other types.
+                               if (typeof encodingForRows !== 'string' || 
!encodingForRows) {
+                                       console.error(this._unoCommand + ' 
response has invalid value for rows.' +
+                                               fieldName);
+                                       return false;
+                               }
+
+                               // Don't accept empty string or any other types.
+                               if (typeof encodingForCols !== 'string' || 
!encodingForCols) {
+                                       console.error(this._unoCommand + ' 
response has invalid value for columns.' +
+                                               fieldName);
+                                       return false;
+                               }
+                       }
+               }
+
+               return true;
+       }
+});
+
+// Used to represent/query geometry data about either rows or columns.
+L.SheetDimension = L.Class.extend({
+
+       initialize: function () {
+
+               this._sizes = new L.SpanList();
+               this._hidden = new L.BoolSpanList();
+               this._filtered = new L.BoolSpanList();
+               this._groups = new L.DimensionOutlines();
+
+               // This is used to store the span-list of sizes
+               // with hidden/filtered elements set to zero size.
+               // This needs to be updated whenever
+               // this._sizes/this._hidden/this._filtered are modified.
+               this._visibleSizes = undefined;
+       },
+
+       update: function (jsonObject) {
+
+               if (typeof jsonObject !== 'object') {
+                       return false;
+               }
+
+               var regenerateVisibleSizes = false;
+               var loadsOK = true;
+               if (jsonObject.hasOwnProperty('sizes')) {
+                       loadsOK = this._sizes.load(jsonObject.sizes);
+                       regenerateVisibleSizes = true;
+               }
+
+               if (jsonObject.hasOwnProperty('hidden')) {
+                       var thisLoadOK = this._hidden.load(jsonObject.hidden);
+                       loadsOK = loadsOK && thisLoadOK;
+                       regenerateVisibleSizes = true;
+               }
+
+               if (jsonObject.hasOwnProperty('filtered')) {
+                       thisLoadOK = this._filtered.load(jsonObject.filtered);
+                       loadsOK = loadsOK && thisLoadOK;
+                       regenerateVisibleSizes = true;
+               }
+
+               if (jsonObject.hasOwnProperty('groups')) {
+                       thisLoadOK = this._groups.load(jsonObject.groups);
+                       loadsOK = loadsOK && thisLoadOK;
+               }
+
+               if (loadsOK && regenerateVisibleSizes) {
+                       this._updateVisible();
+               }
+
+               return loadsOK;
+       },
+
+       setMaxIndex: function (maxIndex) {
+               this._maxIndex = maxIndex;
+       },
+
+       setTileGeometryData: function (tileSizeTwips, tileSizeCSSPixels, 
dpiScale, updatePositions) {
+               if (updatePositions === undefined) {
+                       updatePositions = true;
+               }
+
+               this._twipsPerCSSPixel = tileSizeTwips / tileSizeCSSPixels;
+               this._devPixelsPerCssPixel = dpiScale;
+
+               if (updatePositions) {
+                       // We need to compute positions data for every zoom 
change.
+                       this._updatePositions();
+               }
+       },
+
+       _updateVisible: function () {
+
+               var invisibleSpanList = this._hidden.union(this._filtered); // 
this._hidden is not modified.
+               this._visibleSizes = 
this._sizes.applyZeroValues(invisibleSpanList); // this._sizes is not modified.
+               this._updatePositions();
+       },
+
+       _updatePositions: function() {
+
+               var posDevPx = 0; // position in device pixels.
+               var dimensionObj = this;
+               this._visibleSizes.addCustomDataForEachSpan(function (
+                       index,
+                       size, /* size in twips of one element in the span */
+                       spanLength /* #elements in the span */) {
+
+                       // Important: rounding needs to be done in device 
pixels exactly like the core.
+                       var sizeDevPxOne = Math.floor(size / 
dimensionObj._twipsPerCSSPixel * dimensionObj._devPixelsPerCssPixel);
+                       posDevPx += (sizeDevPxOne * spanLength);
+                       var posCssPx = posDevPx / 
dimensionObj._devPixelsPerCssPixel;
+                       // position in device-pixel aligned twips.
+                       var posTileTwips = Math.floor(posCssPx * 
dimensionObj._twipsPerCSSPixel);
+
+                       var customData = {
+                               sizedev: sizeDevPxOne,
+                               posdevpx: posDevPx,
+                               poscsspx: posCssPx,
+                               postiletwips: posTileTwips
+                       };
+
+                       return customData;
+               });
+       },
+
+       getElementData: function (index) {
+               var span = this._visibleSizes.getSpanDataByIndex(index);
+               if (span === undefined) {
+                       return undefined;
+               }
+
+               return this._getElementDataFromSpanByIndex(index, span);
+       },
+
+       _getElementDataFromSpanByIndex: function (index, span) {
+               if (span === undefined || index < span.start || span.end < 
index) {
+                       return undefined;
+               }
+
+               var numSizes = span.end - index + 1;
+               // all in css pixels.
+               return {
+                       startpos: (span.data.posdevpx - span.data.sizedev * 
numSizes) / this._devPixelsPerCssPixel,
+                       size: span.data.sizedev / this._devPixelsPerCssPixel
+               };
+       },
+
+       forEachInRange: function (start, end, callback) {
+
+               var dimensionObj = this;
+               this._visibleSizes.forEachSpanInRange(start, end, function 
(span) {
+                       var first = Math.max(span.start, start);
+                       var last = Math.min(span.end, end);
+                       for (var index = first; index <= last; ++index) {
+                               callback(index, 
dimensionObj._getElementDataFromSpanByIndex(index, span));
+                       }
+               });
+       },
+
+       _getIndexFromTileTwipsPos: function (pos) {
+               var span = this._visibleSizes.getSpanDataByCustomDataField(pos, 
'postiletwips');
+               var elementCount = span.end - span.start + 1;
+               var posStart = ((span.data.posdevpx - span.data.sizedev * 
elementCount) /
+                       this._devPixelsPerCssPixel * this._twipsPerCSSPixel);
+               var posEnd = span.data.postiletwips;
+               var sizeOne = (posEnd - posStart) / elementCount;
+               var relativeIndex = Math.round((pos - posStart) / sizeOne);
+
+               return span.start + relativeIndex;
+       },
+
+       setViewLimits: function (startPosTileTwips, endPosTileTwips) {
+
+               // Extend the range a bit, to compensate for rounding errors.
+               this._viewStartIndex = Math.max(0, 
this._getIndexFromTileTwipsPos(startPosTileTwips) - 2);
+               this._viewEndIndex = Math.min(this._maxIndex, 
this._getIndexFromTileTwipsPos(endPosTileTwips) + 2);
+       },
+
+       getViewElementRange: function () {
+               return {
+                       start: this._viewStartIndex,
+                       end: this._viewEndIndex
+               };
+       },
+
+       getGroupLevels: function () {
+               return this._outlines.getLevels();
+       },
+
+       getGroupsDataInView: function () {
+               var groupsData = [];
+               var levels = this._outlines.getLevels();
+               if (!levels) {
+                       return groupsData;
+               }
+
+               var dimensionObj = this;
+               this._outlines.forEachGroupInRange(this._viewStartIndex, 
this._viewEndIndex,
+                       function (levelIdx, groupIdx, start, end, hidden) {
+
+                               var startElementData = 
dimensionObj.getElementData(start, true /* device pixels */);
+                               var endElementData = 
dimensionObj.getElementData(end, true /* device pixels */);
+                               groupsData.push({
+                                       level: (levelIdx + 1).toString(),
+                                       index: groupIdx.toString(),
+                                       startPos: 
startElementData.startpos.toString(),
+                                       endPos: (endElementData.startpos + 
endElementData.size).toString(),
+                                       hidden: hidden ? '1' : '0'
+                               });
+                       });
+
+               return groupsData;
+       },
+
+       getMaxIndex: function () {
+               return this._maxIndex;
+       }
+});
+
+L.SpanList = L.Class.extend({
+
+       initialize: function (encoding) {
+
+               // spans are objects with keys: 'index' and 'value'.
+               // 'index' holds the last element of the span.
+               // Optionally custom data of a span can be added
+               // under the key 'data' via addCustomDataForEachSpan.
+               this._spanlist = [];
+               if (typeof encoding !== 'string') {
+                       return;
+               }
+
+               this.load(encoding);
+       },
+
+       load: function (encoding) {
+
+               if (typeof encoding !== 'string') {
+                       return false;
+               }
+
+               var result = parseSpanListEncoding(encoding, false /* boolean 
value ? */);
+               if (result === undefined) {
+                       return false;
+               }
+
+               this._spanlist = result.spanlist;
+               return true;
+       },
+
+       // Runs in O(#spans in 'this' + #spans in 'other')
+       applyZeroValues: function (other) {
+
+               if (!(other instanceof L.BoolSpanList)) {
+                       return undefined;
+               }
+
+               // Ensure both spanlists have the same total range.
+               if (this._spanlist[this._spanlist.length - 1].index !== 
other._spanlist[other._spanlist.length - 1]) {
+                       return undefined;
+               }
+
+               var maxElement = this._spanlist[this._spanlist.length - 
1].index;
+               var result = new L.SpanList();
+
+               var thisIdx = 0;
+               var otherIdx = 0;
+               var zeroBit = other._startBit;
+               var resultValue = zeroBit ? 0 : this._spanlist[thisIdx].value;
+
+               while (thisIdx < this._spanlist.length && otherIdx < 
other._spanlist.length) {
+
+                       // end elements of the current spans of 'this' and 
'other'.
+                       var thisElement = this._spanlist[thisIdx].index;
+                       var otherElement = other._spanlist[otherIdx];
+
+                       var lastElement = otherElement;
+                       if (thisElement < otherElement) {
+                               lastElement = thisElement;
+                               ++thisIdx;
+                       }
+                       else if (otherElement < thisElement) {
+                               zeroBit = !zeroBit;
+                               ++otherIdx;
+                       }
+                       else { // both elements are equal.
+                               zeroBit = !zeroBit;
+                               ++thisIdx;
+                               ++otherIdx;
+                       }
+
+                       var nextResultValue = resultValue;
+                       if (thisIdx < this._spanlist.length) {
+                               nextResultValue = zeroBit ? 0 : 
this._spanlist[thisIdx].value;
+                       }
+
+                       if (resultValue != nextResultValue || lastElement >= 
maxElement) {
+                               // In the result spanlist a new span start from 
lastElement+1
+                               // or reached the maximum possible element.
+                               result._spanlist.push({index: lastElement, 
value: resultValue});
+                               resultValue = nextResultValue;
+                       }
+               }
+
+               return result;
+       },
+
+       addCustomDataForEachSpan: function (getCustomDataCallback) {
+
+               var prevIndex = -1;
+               this._spanlist.forEach(function (span) {
+                       span.data = getCustomDataCallback(
+                               span.index, span.value,
+                               span.index - prevIndex);
+                       prevIndex = span.index;
+               });
+       },
+
+       getSpanDataByIndex: function (index) {
+               var spanid = this._searchByIndex(index);
+               if (spanid == -1) {
+                       return undefined;
+               }
+
+               return this._getSpanData(spanid);
+       },
+
+       getSpanDataByCustomDataField: function (value, fieldName) {
+               var spanid = this._searchByCustomDataField(value, fieldName);
+               if (spanid == -1) {
+                       return undefined;
+               }
+
+               return this._getSpanData(spanid);
+       },
+
+       forEachSpanInRange: function (start, end, callback) {
+
+               if (start > end) {
+                       return;
+               }
+
+               var startId = this._searchByIndex(start);
+               var endId = this._searchByIndex(end);
+
+               if (startId == -1 || endId == -1) {
+                       return;
+               }
+
+               for (var id = startId; id <= endId; ++id) {
+                       callback(this._getSpanData(id));
+               }
+       },
+
+       _getSpanData: function (spanid) {
+
+               var span = this._spanlist[spanid];
+               var dataClone = undefined;
+               if (span.data) {
+                       dataClone = {};
+                       Object.keys(span.data).forEach(function (key) {
+                               dataClone[key] = span.data[key];
+                       });
+               }
+
+               return {
+                       start: spanid ? this._spanlist[spanid - 1].index + 1 : 
0,
+                       end: span.index,
+                       size: span.value,
+                       data: dataClone
+               };
+       },
+
+       _searchByIndex: function (index) {
+
+               if (index < 0 || index > this._spanlist[this._spanlist.length - 
1].index) {
+                       return -1;
+               }
+
+               var start = 0;
+               var end = this._spanlist.length - 1;
+               var mid = -1;
+               while (start <= end) {
+                       mid = Math.round((start + end) / 2);
+                       var spanstart = mid ? this._spanlist[mid - 1].index + 1 
: 0;
+                       var spanend = this._spanlist[mid].index;
+                       if (spanstart <= index && index <= spanend) {
+                               break;
+                       }
+
+                       if (index < spanstart) {
+                               end = mid - 1;
+                       }
+                       else { // spanend < index
+                               start = mid + 1;
+                       }
+               }
+
+               return mid;
+       },
+
+       _searchByCustomDataField: function (value, fieldName) {
+
+               // All custom searchable data values are assumed to start from 
0 at the start of first span.
+               var maxValue = this._spanlist[this._spanlist.length - 
1].data[fieldName];
+               if (value < 0 || value > maxValue) {
+                       return -1;
+               }
+
+               var start = 0;
+               var end = this._spanlist.length - 1;
+               var mid = -1;
+               while (start <= end) {
+                       mid = Math.round((start + end) / 2);
+                       var valuestart = mid ? this._spanlist[mid - 
1].data[fieldName] + 1 : 0;
+                       var valueend = this._spanlist[mid].data[fieldName];
+                       if (valuestart <= value && value <= valueend) {
+                               break;
+                       }
+
+                       if (value < valuestart) {
+                               end = mid - 1;
+                       }
+                       else { // valueend < value
+                               start = mid + 1;
+                       }
+               }
+
+               // may fail for custom data ?
+               return (start <= end) ? mid : -1;
+       }
+
+});
+
+L.BoolSpanList = L.SpanList.extend({
+
+       load: function (encoding) {
+
+               if (typeof encoding !== 'string') {
+                       return false;
+               }
+
+               var result = parseSpanListEncoding(encoding, true /* boolean 
value ? */);
+               if (result === undefined) {
+                       return false;
+               }
+
+               this._spanlist = result.spanlist;
+               this._startBit = result.startBit;
+               return true;
+       },
+
+       // Runs in O(#spans in 'this' + #spans in 'other')
+       union: function (other) {
+
+               if (!(other instanceof L.BoolSpanList)) {
+                       return undefined;
+               }
+
+               // Ensure both spanlists have the same total range.
+               if (this._spanlist[this._spanlist.length - 1] !== 
other._spanlist[other._spanlist.length - 1]) {
+                       return undefined;
+               }
+
+               var maxElement = this._spanlist[this._spanlist.length - 1];
+
+               var result = new L.BoolSpanList();
+               var thisBit = this._startBit;
+               var otherBit = other._startBit;
+               var resultBit = thisBit || otherBit;
+               result._startBit = resultBit;
+
+               var thisIdx = 0;
+               var otherIdx = 0;
+
+               while (thisIdx < this._spanlist.length && otherIdx < 
other._spanlist.length) {
+
+                       // end elements of the current spans of 'this' and 
'other'.
+                       var thisElement = this._spanlist[thisIdx];
+                       var otherElement = other._spanlist[otherIdx];
+
+                       var lastElement = otherElement;
+                       if (thisElement < otherElement) {
+                               lastElement = thisElement;
+                               thisBit = !thisBit;
+                               ++thisIdx;
+                       }
+                       else if (otherElement < thisElement) {
+                               otherBit = !otherBit;
+                               ++otherIdx;
+                       }
+                       else { // both elements are equal.
+                               thisBit = !thisBit;
+                               otherBit = !otherBit;
+                               ++thisIdx;
+                               ++otherIdx;
+                       }
+
+                       var nextResultBit = (thisBit || otherBit);
+                       if (resultBit != nextResultBit || lastElement >= 
maxElement) {
+                               // In the result spanlist a new span start from 
lastElement+1
+                               // or reached the maximum possible element.
+                               result._spanlist.push(lastElement);
+                               resultBit = nextResultBit;
+                       }
+               }
+
+               return result;
+       }
+});
+
+function parseSpanListEncoding(encoding, booleanValue) {
+
+       var spanlist = [];
+       var splits = encoding.split(' ');
+       if (splits.length < 2) {
+               return undefined;
+       }
+
+       var startBit = false;
+       if (booleanValue) {
+               var parts = splits[0].split(':');
+               if (parts.length != 2) {
+                       return undefined;
+               }
+               startBit = parseInt(parts[0]);
+               var first = parseInt(parts[1]);
+               if (isNaN(startBit) || isNaN(first)) {
+                       return undefined;
+               }
+               spanlist.push(first);
+       }
+
+       startBit = Boolean(startBit);
+
+       for (var idx = 0; idx < splits.length - 1; ++idx) {
+
+               if (booleanValue) {
+                       if (!idx) {
+                               continue;
+                       }
+
+                       var entry = parseInt(splits[idx]);
+                       if (isNaN(entry)) {
+                               return undefined;
+                       }
+
+                       spanlist.push(entry);
+                       continue;
+               }
+
+               var spanParts = splits[idx].split(':');
+               if (spanParts.length != 2) {
+                       return undefined;
+               }
+
+               var span = {
+                       index: parseInt(spanParts[1]),
+                       value: parseInt(spanParts[0])
+               };
+
+               if (isNaN(span.index) || isNaN(span.value)) {
+                       return undefined;
+               }
+
+               spanlist.push(span);
+       }
+
+       var result = {spanlist: spanlist};
+
+       if (booleanValue) {
+               result.startBit = startBit;
+       }
+
+       return result;
+}
+

... etc. - the rest is truncated
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to