[Libreoffice-commits] online.git: Branch 'feature/calc-canvas' - 44 commits - common/Authorization.cpp common/Util.cpp common/Util.hpp cypress_test/integration_tests cypress_test/Makefile.am cypress_test/README cypress_test/run_parallel.sh ios/CollaboraOnlineWebViewKeyboardManager ios/Mobile kit/ChildSession.cpp loleaflet/css loleaflet/html loleaflet/images loleaflet/src

Michael Meeks (via logerrit) logerrit at kemper.freedesktop.org
Wed Sep 2 13:51:57 UTC 2020


Rebased ref, commits from common ancestor:
commit f59a9cf7fca78c1d8095a8d020862a6203c03b33
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Tue Sep 1 21:35:35 2020 +0100
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    calc canvas: paint invalid tiles until their replacement arrives.
    
    This avoids display corruption when panning, whereby stale/old
    canvas content would continue to be rendered in the 'holes'
    where invalid tiles were not rendered.
    
    Change-Id: Ic886c0924c5a930116b1437c8e0cf35726ab76a5

diff --git a/loleaflet/src/layer/tile/CanvasTileLayer.js b/loleaflet/src/layer/tile/CanvasTileLayer.js
index 8643f8bf7..6d6aacecd 100644
--- a/loleaflet/src/layer/tile/CanvasTileLayer.js
+++ b/loleaflet/src/layer/tile/CanvasTileLayer.js
@@ -253,7 +253,7 @@ L.CanvasTilePainter = L.Class.extend({
 			!splitPosChanged &&
 			!scaleChanged);
 
-		console.debug('Tile size: ' + this._layer._getTileSize());
+//		console.debug('Tile size: ' + this._layer._getTileSize());
 
 		if (skipUpdate)
 			return;
@@ -306,10 +306,14 @@ L.CanvasTilePainter = L.Class.extend({
 
 					var key = coords.key();
 					var tile = this._layer._tiles[key];
-					var invalid = tile && tile._invalidCount && tile._invalidCount > 0;
-					if (tile && tile.loaded && !invalid) {
+//					var invalid = tile && tile._invalidCount && tile._invalidCount > 0;
+					if (tile && tile.loaded) {
 						this.paint(tile, ctx);
 					}
+/*					else
+						console.log('missing tile at ' + i + ', ' + j + ' ' +
+							    tile + ' ' + (tile && tile.loaded) + ' ' +
+							    (tile ? tile._invalidCount : -42) + ' ' + invalid); */
 				}
 			}
 		}
commit db0f5292b3af7e94cd8d2676add83f0f03c72824
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Tue Sep 1 16:53:02 2020 +0100
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    calc tiles: don't send un-necessary zoom / visible area changes.
    
    Lots of redundant zoom messages seem unhelpful.
    
    Change-Id: I944a3202739adfc89aab81902b467a4e34977202

diff --git a/loleaflet/src/layer/tile/CanvasTileLayer.js b/loleaflet/src/layer/tile/CanvasTileLayer.js
index 8ef3cb886..8643f8bf7 100644
--- a/loleaflet/src/layer/tile/CanvasTileLayer.js
+++ b/loleaflet/src/layer/tile/CanvasTileLayer.js
@@ -697,9 +697,8 @@ L.CanvasTileLayer = L.TileLayer.extend({
 			}
 		}
 
-		this._sendClientVisibleArea(true);
-
-		this._sendClientZoom(true);
+		this._sendClientVisibleArea();
+		this._sendClientZoom();
 
 		if (queue.length !== 0) {
 			if (cancelTiles) {
commit 91d6a485382494f618f970cb31be4be570ca9643
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Tue Sep 1 16:52:35 2020 +0100
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    calc tiles: use canvas rendering for mobile too.
    
    This way the row/column headers line up - and it's the future.
    
    Change-Id: I56b2c2527dcc751ed06fc3b30aff22544ec4c269

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js
index eedf8966a..fc96f0546 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -4,8 +4,7 @@
  */
 
 /* global */
-var BaseTileLayer = L.Browser.mobile ? L.TileLayer : L.CanvasTileLayer;
-L.CalcTileLayer = BaseTileLayer.extend({
+L.CalcTileLayer = L.CanvasTileLayer.extend({
 	options: {
 		// TODO: sync these automatically from SAL_LOK_OPTIONS
 		sheetGeometryDataEnabled: true,
@@ -82,7 +81,7 @@ L.CalcTileLayer = BaseTileLayer.extend({
 		map.addControl(L.control.tabs());
 		map.addControl(L.control.columnHeader());
 		map.addControl(L.control.rowHeader());
-		BaseTileLayer.prototype.onAdd.call(this, map);
+		L.CanvasTileLayer.prototype.onAdd.call(this, map);
 
 		map.on('resize', function () {
 			if (this.isCursorVisible()) {
commit 322fcef3e7b7630c5707068d8d43fb8588332331
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Tue Sep 1 16:24:18 2020 +0100
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    calc canvas: ensure that the fraction width rounds to the pixel width.
    
    Slave CSS geometry from integral canvas pixels, don't attempt the
    reverse.
    
    Change-Id: I369ed1bea3c4a5a199192aa1e84bb4e03dcb2e94

diff --git a/loleaflet/src/layer/tile/CanvasTileLayer.js b/loleaflet/src/layer/tile/CanvasTileLayer.js
index 4983fef2a..8ef3cb886 100644
--- a/loleaflet/src/layer/tile/CanvasTileLayer.js
+++ b/loleaflet/src/layer/tile/CanvasTileLayer.js
@@ -96,11 +96,20 @@ L.CanvasTilePainter = L.Class.extend({
 	},
 
 	_setCanvasSize: function (widthCSSPx, heightCSSPx) {
-		this._canvas.style.width = widthCSSPx + 'px';
-		this._canvas.style.height = heightCSSPx + 'px';
-		this._canvas.width = Math.floor(widthCSSPx * this._dpiScale);
-		this._canvas.height = Math.floor(heightCSSPx * this._dpiScale);
+		var pixWidth = Math.floor(widthCSSPx * this._dpiScale);
+		var pixHeight = Math.floor(heightCSSPx * this._dpiScale);
 
+		// real pixels have to be integral
+		this._canvas.width = pixWidth;
+		this._canvas.height = pixHeight;
+
+		// CSS pixels can be fractional, but need to round to the same real pixels
+		var cssWidth = pixWidth / this._dpiScale; // NB. beware
+		var cssHeight = pixHeight / this._dpiScale;
+		this._canvas.style.width = cssWidth.toFixed(4) + 'px';
+		this._canvas.style.height = cssHeight.toFixed(4) + 'px';
+
+		// FIXME: is this a good idea ? :
 		this._width = parseInt(this._canvas.style.width);
 		this._height = parseInt(this._canvas.style.height);
 		this.clear();
commit 21141b1777807ff8bc8a6be5c32fee26e1483ffc
Author:     Jan Holesovsky <kendy at collabora.com>
AuthorDate: Tue Sep 1 01:39:07 2020 +0200
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    calc canvas: Fix occasional off-by-one error that results in a blurry canvas.
    
    The core of the fix is in _getNewPixelOrigin() where the round() behaves
    non-predictably / inconsistently with the rest of the code, causing
    random off-by-one error that shows (or not) depending on the window
    size.
    
    The biggest problem of this is that this off-by-one is then multiplied
    somewhere by the zoom factor, causing the canvas being completely
    blurry; but eventually when the user clicked into the sheet, it
    'magically' fixed itself.
    
    The rest of the changes (in setZoom()) should actually do the same thing
    as the previous code, but using existing methods, instead of computing
    the shifts manually.
    
    Change-Id: If0ecb1301b7c1e65cfe8126385ef959c584c5d16

diff --git a/loleaflet/src/map/Map.js b/loleaflet/src/map/Map.js
index 24b247656..4221d8a4d 100644
--- a/loleaflet/src/map/Map.js
+++ b/loleaflet/src/map/Map.js
@@ -474,6 +474,8 @@ L.Map = L.Evented.extend({
 			this._zoom = this._limitZoom(zoom);
 			return this;
 		}
+
+		var curCenter = this.getCenter();
 		if (this._docLayer && this._docLayer._docType === 'spreadsheet') {
 			// for spreadsheets, when the document is smaller than the viewing area
 			// we want it to be glued to the row/column headers instead of being centered
@@ -483,14 +485,18 @@ L.Map = L.Evented.extend({
 				var sheetGeom = calcLayer.sheetGeometry;
 				var cellRange = sheetGeom.getViewCellRange();
 				var col = cellRange.columnrange.start, row = cellRange.rowrange.start;
-				var zoomScaleAbs = Math.pow(1.2, (zoom - this.options.zoom));
+				var zoomScaleAbs = this.zoomToFactor(zoom);
+
 				var newTopLeftPx = sheetGeom.getCellRect(col, row, zoomScaleAbs).getTopLeft();
-				var newCenterPx = newTopLeftPx.add(this.getSize().divideBy(2)._floor());
-				var newCenterLatLng = this.unproject(newCenterPx, zoom);
+				var moveByPoint = this._getTopLeftPoint(curCenter, zoom).subtract(newTopLeftPx);
+
+				// move the center (which is in LatLng) by the computed amount of pixels
+				var newCenterLatLng = this.unproject(this.project(curCenter, zoom).subtract(moveByPoint), zoom);
+
 				return this.setView(newCenterLatLng, zoom, {zoom: options});
 			}
 		}
-		var curCenter = this.getCenter();
+
 		if (this._docLayer && this._docLayer._visibleCursor && this.getBounds().contains(this._docLayer._visibleCursor.getCenter())) {
 			// Calculate new center after zoom. The intent is that the caret
 			// position stays the same.
@@ -1671,13 +1677,13 @@ L.Map = L.Evented.extend({
 		var pixelOrigin = center && zoom !== undefined ?
 			this._getNewPixelOrigin(center, zoom) :
 			this.getPixelOrigin();
+
 		return pixelOrigin.subtract(this._getMapPanePos());
 	},
 
 	_getNewPixelOrigin: function (center, zoom) {
 		var viewHalf = this.getSize()._divideBy(2);
-		// TODO round on display, not calculation to increase precision?
-		return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
+		return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._floor();
 	},
 
 	_latLngToNewLayerPoint: function (latlng, zoom, center) {
commit cbf0c7c7e85d81c34ed1d7f6789711036190a31f
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Fri Aug 28 16:05:04 2020 +0100
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    calc tiles: connect debug to global setting.
    
    Change-Id: I0db008ac40020c9173d37969aa6c23b3a1696f79

diff --git a/loleaflet/src/layer/tile/CanvasTileLayer.js b/loleaflet/src/layer/tile/CanvasTileLayer.js
index 35f23437a..4983fef2a 100644
--- a/loleaflet/src/layer/tile/CanvasTileLayer.js
+++ b/loleaflet/src/layer/tile/CanvasTileLayer.js
@@ -36,10 +36,6 @@ L.TileCoordData.parseKey = function (keyString) {
 
 L.CanvasTilePainter = L.Class.extend({
 
-	options: {
-		debug: true,
-	},
-
 	initialize: function (layer) {
 		this._layer = layer;
 		this._canvas = this._layer._canvas;
@@ -126,8 +122,8 @@ L.CanvasTilePainter = L.Class.extend({
 	clear: function () {
 		this._canvasCtx.save();
 		this._canvasCtx.scale(1, 1);
-		if (this.options.debug)
-			this._canvasCtx.fillStyle = 'red';
+		if (this._layer._debug)
+			this._canvasCtx.fillStyle = 'rgba(255, 0, 0, 0.5)';
 		else
 			this._canvasCtx.fillStyle = 'white';
 		this._canvasCtx.fillRect(0, 0, this._width, this._height);
@@ -181,9 +177,9 @@ L.CanvasTilePainter = L.Class.extend({
 			this._canvasCtx.clip();
 
 			this._canvasCtx.drawImage(tile.el, tile.coords.x, tile.coords.y);
-			if (this.options.debug)
+			if (this._layer._debug)
 			{
-				this._canvasCtx.strokeStyle = 'red';
+				this._canvasCtx.strokeStyle = 'rgba(255, 0, 0, 0.5)';
 				this._canvasCtx.strokeRect(tile.coords.x, tile.coords.y, 256, 256);
 			}
 			this._canvasCtx.restore();
@@ -273,13 +269,13 @@ L.CanvasTilePainter = L.Class.extend({
 		this._topLeft = newTopLeft;
 		this._paintWholeCanvas();
 
-		if (this.options.debug)
+		if (this._layer._debug)
 			this._drawSplits();
 	},
 
 	_paintWholeCanvas: function () {
 
-		if (this.options.debug)
+		if (this._layer._debug)
 			this.clear();
 
 		var zoom = this._lastZoom || Math.round(this._map.getZoom());
commit 5797e1315738bbb77a129aea4bfc2de04b273c98
Author:     Jan Holesovsky <kendy at collabora.com>
AuthorDate: Wed Aug 26 15:15:55 2020 +0200
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    calc canvas: Keep the document zoom separate from the browser zoom.
    
    With this, if you increase or decrease the browser zoom, the document
    zoom still stays the same.
    
    Before this, when you had eg. 100% document zoom and 150% browser zoom
    and try to zoom out, it actually zooms in instead, because the browser's
    zoom is added to the mix; and it displays the wrong value in the
    dropdown.  Even worse, to get the 100% again, you have to choose 80% so
    that the correction for the browser zoom is added, resulting in the
    100%.
    
    We should keep both the document and browser zoom separately.  The
    questions is then whether to combine them later for the actual document
    rendering; I believe we should not, but even if we should, we cannot do
    it directly in the setZoom() method, but instead closer to the painting
    itself.
    
    Change-Id: Ib7f3d2ae8b4e6e6086f14e933b215c32326c6be6

diff --git a/loleaflet/src/map/Map.js b/loleaflet/src/map/Map.js
index 760a5c615..24b247656 100644
--- a/loleaflet/src/map/Map.js
+++ b/loleaflet/src/map/Map.js
@@ -461,19 +461,6 @@ L.Map = L.Evented.extend({
 		return Math.round(relzoom) + this.options.zoom;
 	},
 
-	// Compute the nearest zoom level corresponding to the effective zoom-scale (ie, with dpiscale included).
-	findNearestProductZoom: function (zoom) {
-		var clientZoomScale = this.zoomToFactor(zoom);
-
-		var dpiScale = this._docLayer ? this._docLayer.canvasDPIScale() : L.getDpiScaleFactor(true /* useExactDPR */);
-
-		var zoomScale = clientZoomScale * dpiScale;
-		var nearestZoom = this.factorToZoom(zoomScale);
-		nearestZoom = this._limitZoom(nearestZoom);
-
-		return nearestZoom;
-	},
-
 	setZoom: function (zoom, options) {
 
 		if (this._docLayer instanceof L.CanvasTileLayer) {
@@ -481,8 +468,6 @@ L.Map = L.Evented.extend({
 				zoom = this._clientZoom || this.options.zoom;
 			else
 				this._clientZoom = zoom;
-
-			zoom = this.findNearestProductZoom(zoom);
 		}
 
 		if (!this._loaded) {
commit 8ab5774b228c4cde9495dbb668cc2359c2f567c8
Author:     Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Tue Aug 25 12:01:51 2020 +0530
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    specialize twips/core-pixels/css-pixels conversion methods
    
    Change-Id: Ifb0a67b938fdd34a06bb7e75832498d566247010

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js
index c98d2fef4..eedf8966a 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -502,17 +502,16 @@ L.CalcTileLayer = BaseTileLayer.extend({
 			return;
 		}
 
-		var newWidthPx = newDocWidth / this._tileWidthTwips * this._tileSize;
-		var newHeightPx = newDocHeight / this._tileHeightTwips * this._tileSize;
+		var newSizePx = this._twipsToCorePixels(new L.Point(newDocWidth, newDocHeight));
 
 		var topLeft = this._map.unproject(new L.Point(0, 0));
-		var bottomRight = this._map.unproject(new L.Point(newWidthPx, newHeightPx));
+		var bottomRight = this._map.unproject(newSizePx);
 		this._map.setMaxBounds(new L.LatLngBounds(topLeft, bottomRight));
 
-		this._docPixelSize = {x: newWidthPx, y: newHeightPx};
+		this._docPixelSize = newSizePx.clone();
 		this._docWidthTwips = newDocWidth;
 		this._docHeightTwips = newDocHeight;
-		this._map.fire('docsize', {x: newWidthPx, y: newHeightPx});
+		this._map.fire('docsize', newSizePx.clone());
 	},
 
 	_onUpdateCurrentHeader: function() {
@@ -566,8 +565,9 @@ L.CalcTileLayer = BaseTileLayer.extend({
 			this._selectedPart = command.selectedPart;
 			this._viewId = parseInt(command.viewid);
 			var mapSize = this._map.getSize();
-			var width = this._docWidthTwips / this._tileWidthTwips * this._tileSize;
-			var height = this._docHeightTwips / this._tileHeightTwips * this._tileSize;
+			var sizePx = this._twipsToPixels(new L.Point(this._docWidthTwips, this._docHeightTwips));
+			var width = sizePx.x;
+			var height = sizePx.y;
 			if (width < mapSize.x || height < mapSize.y) {
 				width = Math.max(width, mapSize.x);
 				height = Math.max(height, mapSize.y);
diff --git a/loleaflet/src/layer/tile/CanvasTileLayer.js b/loleaflet/src/layer/tile/CanvasTileLayer.js
index 88e271431..35f23437a 100644
--- a/loleaflet/src/layer/tile/CanvasTileLayer.js
+++ b/loleaflet/src/layer/tile/CanvasTileLayer.js
@@ -158,16 +158,17 @@ L.CanvasTilePainter = L.Class.extend({
 		var tileBounds = new L.Bounds(tileTopLeft, tileTopLeft.add(ctx.tileSize));
 
 		for (var i = 0; i < ctx.paneBoundsList.length; ++i) {
-			var paneBounds = ctx.paneBoundsList[i];
+			var paneBounds = this._layer._cssBoundsToCore(ctx.paneBoundsList[i]);
+			var viewBounds = this._layer._cssBoundsToCore(ctx.viewBounds);
 
 			if (!paneBounds.intersects(tileBounds))
 				continue;
 
 			var topLeft = paneBounds.getTopLeft();
 			if (topLeft.x)
-				topLeft.x = ctx.viewBounds.min.x;
+				topLeft.x = viewBounds.min.x;
 			if (topLeft.y)
-				topLeft.y = ctx.viewBounds.min.y;
+				topLeft.y = viewBounds.min.y;
 
 			this._canvasCtx.save();
 			this._canvasCtx.scale(1, 1);
@@ -194,7 +195,7 @@ L.CanvasTilePainter = L.Class.extend({
 		if (!splitPanesContext) {
 			return;
 		}
-		var splitPos = splitPanesContext.getSplitPos();
+		var splitPos = this._layer._cssPixelsToCore(splitPanesContext.getSplitPos());
 		this._canvasCtx.save();
 		this._canvasCtx.scale(1, 1);
 		this._canvasCtx.strokeStyle = 'red';
@@ -497,8 +498,75 @@ L.CanvasTileLayer = L.TileLayer.extend({
 
 	_pxBoundsToTileRange: function (bounds) {
 		return new L.Bounds(
-			bounds.min.divideBy(this._tileSize).floor(),
-			bounds.max.divideBy(this._tileSize).floor());
+			this._cssPixelsToCore(bounds.min)._divideBy(this._tileSize)._floor(),
+			this._cssPixelsToCore(bounds.max)._divideBy(this._tileSize)._floor());
+	},
+
+	_getCoreZoomFactor: function () {
+		return new L.Point(
+			this._tileSize * 15.0 / this._tileWidthTwips,
+			this._tileSize * 15.0 / this._tileHeightTwips);
+	},
+
+	_corePixelsToCss: function (corePixels) {
+		var dpiScale = this.canvasDPIScale();
+		return corePixels.divideBy(dpiScale);
+	},
+
+	_cssPixelsToCore: function (cssPixels) {
+		var dpiScale = this.canvasDPIScale();
+		return cssPixels.multiplyBy(dpiScale);
+	},
+
+	_cssBoundsToCore: function (bounds) {
+		return new L.Bounds(
+			this._cssPixelsToCore(bounds.min),
+			this._cssPixelsToCore(bounds.max)
+		);
+	},
+
+	_twipsToCorePixels: function (twips) {
+		return new L.Point(
+			twips.x / this._tileWidthTwips * this._tileSize,
+			twips.y / this._tileHeightTwips * this._tileSize);
+	},
+
+	_corePixelsToTwips: function (corePixels) {
+		return new L.Point(
+			corePixels.x / this._tileSize * this._tileWidthTwips,
+			corePixels.y / this._tileSize * this._tileHeightTwips);
+	},
+
+	_twipsToCssPixels: function (twips) {
+		var dpiScale = this.canvasDPIScale();
+		return new L.Point(
+			twips.x / this._tileWidthTwips * this._tileSize / dpiScale,
+			twips.y / this._tileHeightTwips * this._tileSize / dpiScale);
+	},
+
+	_cssPixelsToTwips: function (pixels) {
+		var dpiScale = this.canvasDPIScale();
+		return new L.Point(
+			pixels.x * dpiScale / this._tileSize * this._tileWidthTwips,
+			pixels.y * dpiScale / this._tileSize * this._tileHeightTwips);
+	},
+
+	_twipsToLatLng: function (twips, zoom) {
+		var pixels = this._twipsToCssPixels(twips);
+		return this._map.unproject(pixels, zoom);
+	},
+
+	_latLngToTwips: function (latLng, zoom) {
+		var pixels = this._map.project(latLng, zoom);
+		return this._cssPixelsToTwips(pixels);
+	},
+
+	_twipsToPixels: function (twips) { // css pixels
+		return this._twipsToCssPixels(twips);
+	},
+
+	_pixelsToTwips: function (pixels) { // css pixels
+		return this._cssPixelsToTwips(pixels);
 	},
 
 	_twipsToCoords: function (twips) {
@@ -524,6 +592,48 @@ L.CanvasTileLayer = L.TileLayer.extend({
 		return true;
 	},
 
+	_updateMaxBounds: function (sizeChanged, extraSize, options, zoom) {
+		if (this._docWidthTwips === undefined || this._docHeightTwips === undefined) {
+			return;
+		}
+		if (!zoom) {
+			zoom = this._map.getZoom();
+		}
+
+		var dpiScale = this.canvasDPIScale();
+		var docPixelLimits = new L.Point(this._docWidthTwips / this.options.tileWidthTwips,
+			this._docHeightTwips / this.options.tileHeightTwips);
+		// docPixelLimits should be in csspx.
+		docPixelLimits = docPixelLimits.multiplyBy(this._tileSize / dpiScale);
+		var scale = this._map.getZoomScale(zoom, 10);
+		var topLeft = new L.Point(0, 0);
+		topLeft = this._map.unproject(topLeft.multiplyBy(scale));
+		var bottomRight = new L.Point(docPixelLimits.x, docPixelLimits.y);
+		bottomRight = bottomRight.multiplyBy(scale);
+		if (extraSize) {
+			// extraSize is unscaled.
+			bottomRight = bottomRight.add(extraSize);
+		}
+		bottomRight = this._map.unproject(bottomRight);
+
+		if (this._documentInfo === '' || sizeChanged) {
+			// we just got the first status so we need to center the document
+			this._map.setMaxBounds(new L.LatLngBounds(topLeft, bottomRight), options);
+			this._map.setDocBounds(new L.LatLngBounds(topLeft, this._map.unproject(docPixelLimits.multiplyBy(scale))));
+		}
+
+		var scrollPixelLimits = new L.Point(this._docWidthTwips / this._tileWidthTwips,
+			this._docHeightTwips / this._tileHeightTwips);
+		scrollPixelLimits = scrollPixelLimits.multiplyBy(this._tileSize / dpiScale);
+		if (extraSize) {
+			// extraSize is unscaled.
+			scrollPixelLimits = scrollPixelLimits.add(extraSize);
+		}
+		this._docPixelSize = {x: scrollPixelLimits.x, y: scrollPixelLimits.y};
+		this._map.fire('docsize', {x: scrollPixelLimits.x, y: scrollPixelLimits.y, extraSize: extraSize});
+	},
+
+
 	_update: function (center, zoom) {
 		var map = this._map;
 		if (!map || this._documentInfo === '') {
@@ -1048,6 +1158,8 @@ L.CanvasTileLayer = L.TileLayer.extend({
 
 		var nwPoint = new L.Point(coords.x, coords.y);
 		var sePoint = nwPoint.add([tileSize, tileSize]);
+		nwPoint = this._corePixelsToCss(nwPoint);
+		sePoint = this._corePixelsToCss(sePoint);
 
 		var nw = map.wrapLatLng(map.unproject(nwPoint, coords.z));
 		var se = map.wrapLatLng(map.unproject(sePoint, coords.z));
commit d8c06b9d8a15c8af6d297375351c6c1ae7dd45a8
Author:     Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Tue Aug 25 11:55:25 2020 +0530
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    detect change in dpi and update zoom
    
    Change-Id: I034727a8fe8495445350648fea2422c56fda1875

diff --git a/loleaflet/src/layer/tile/CanvasTileLayer.js b/loleaflet/src/layer/tile/CanvasTileLayer.js
index 2a28a3e9e..88e271431 100644
--- a/loleaflet/src/layer/tile/CanvasTileLayer.js
+++ b/loleaflet/src/layer/tile/CanvasTileLayer.js
@@ -210,6 +210,14 @@ L.CanvasTilePainter = L.Class.extend({
 
 	update: function () {
 
+		var newDpiScale = L.getDpiScaleFactor(true /* useExactDPR */);
+		var scaleChanged = this._dpiScale != newDpiScale;
+
+		if (scaleChanged) {
+			this._dpiScale = L.getDpiScaleFactor(true /* useExactDPR */);
+			this._map.setZoom();
+		}
+
 		var splitPanesContext = this._layer.getSplitPanesContext();
 		var zoom = Math.round(this._map.getZoom());
 		var pixelBounds = this._map.getPixelBounds();
@@ -218,11 +226,9 @@ L.CanvasTilePainter = L.Class.extend({
 		var part = this._layer._selectedPart;
 		var newSplitPos = splitPanesContext ?
 		    splitPanesContext.getSplitPos(): this._splitPos;
-		var newDpiScale = L.getDpiScaleFactor(true /* useExactDPR */);
 
 		var zoomChanged = (zoom !== this._lastZoom);
 		var partChanged = (part !== this._lastPart);
-		var scaleChanged = this._dpiScale != newDpiScale;
 
 		var mapSizeChanged = !newMapSize.equals(this._lastMapSize);
 		// To avoid flicker, only resize the canvas element if width or height of the map increases.
@@ -246,11 +252,6 @@ L.CanvasTilePainter = L.Class.extend({
 		if (skipUpdate)
 			return;
 
-		if (scaleChanged) {
-			this._dpiScale = L.getDpiScaleFactor(true /* useExactDPR */);
-			console.log('DEBUG: scaleChanged : this._dpiScale = ' + this._dpiScale);
-		}
-
 		if (resizeCanvas || scaleChanged) {
 			this._setCanvasSize(newSize.x, newSize.y);
 			this._lastSize = newSize;
@@ -384,12 +385,8 @@ L.CanvasTileLayer = L.TileLayer.extend({
 		this._tileHeightPx = this.options.tileSize;
 		this._tilePixelScale = 1;
 
-		// FIXME: workaround for correcting initial zoom with dpiscale included.
-		// The one set during Map constructor is does not include dpiscale because
-		// there we don't have enough info to specialize for calc-canvas
-		map.setZoom(map.getZoom());
-
 		L.TileLayer.prototype.onAdd.call(this, map);
+		map.setZoom();
 	},
 
 	onRemove: function (map) {
diff --git a/loleaflet/src/map/Map.js b/loleaflet/src/map/Map.js
index c93abac94..760a5c615 100644
--- a/loleaflet/src/map/Map.js
+++ b/loleaflet/src/map/Map.js
@@ -476,8 +476,14 @@ L.Map = L.Evented.extend({
 
 	setZoom: function (zoom, options) {
 
-		if (this._docLayer instanceof L.CanvasTileLayer)
+		if (this._docLayer instanceof L.CanvasTileLayer) {
+			if (!zoom)
+				zoom = this._clientZoom || this.options.zoom;
+			else
+				this._clientZoom = zoom;
+
 			zoom = this.findNearestProductZoom(zoom);
+		}
 
 		if (!this._loaded) {
 			this._zoom = this._limitZoom(zoom);
commit 1661e58c044b8a4e81911a446b99234aef7a4702
Author:     Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Tue Aug 25 11:36:03 2020 +0530
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    use the main canvas dpiScale everywhere
    
    Change-Id: I2bea44a000552ce8f2fee2b0ebb5a4d162d3576f

diff --git a/loleaflet/src/control/Control.ColumnHeader.js b/loleaflet/src/control/Control.ColumnHeader.js
index 4639acd96..6027c47da 100644
--- a/loleaflet/src/control/Control.ColumnHeader.js
+++ b/loleaflet/src/control/Control.ColumnHeader.js
@@ -39,7 +39,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		this._setCanvasHeight();
 		this._canvasBaseHeight = this._canvasHeight;
 
-		var scale = L.getDpiScaleFactor();
+		var scale = this.canvasDPIScale();
 		this._canvasContext.scale(scale, scale);
 
 		this._headerHeight = this._canvasHeight;
@@ -224,8 +224,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 			return;
 
 		ctx.save();
-		var useExactDPR = this._map && (this._map._docLayer instanceof L.CanvasTileLayer);
-		var scale = L.getDpiScaleFactor(useExactDPR);
+		var scale = this.canvasDPIScale();
 		ctx.scale(scale, scale);
 		// background gradient
 		var selectionBackgroundGradient = null;
@@ -291,7 +290,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		var height = group.endPos - group.startPos;
 
 		ctx.save();
-		var scale = L.getDpiScaleFactor();
+		var scale = this.canvasDPIScale();
 		ctx.scale(scale, scale);
 
 		// clip mask
@@ -340,7 +339,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 		var ctx = this._cornerCanvasContext;
 		var ctrlHeadSize = this._groupHeadSize;
 		var levelSpacing = this._levelSpacing;
-		var scale = L.getDpiScaleFactor();
+		var scale = this.canvasDPIScale();
 
 		var startOrt = levelSpacing + (ctrlHeadSize + levelSpacing) * level;
 		var startPar = this._cornerCanvas.width / scale - (ctrlHeadSize + (L.Control.Header.rowHeaderWidth - ctrlHeadSize) / 2);
@@ -533,7 +532,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 			return;
 		}
 
-		var scale = L.getDpiScaleFactor();
+		var scale = this.canvasDPIScale();
 		var rowOutlineWidth = this._cornerCanvas.width / scale - L.Control.Header.rowHeaderWidth - this._borderWidth;
 		if (pos.x <= rowOutlineWidth) {
 			// empty rectangle on the left select all
diff --git a/loleaflet/src/control/Control.Header.js b/loleaflet/src/control/Control.Header.js
index 757cfd5c8..89fc78fe0 100644
--- a/loleaflet/src/control/Control.Header.js
+++ b/loleaflet/src/control/Control.Header.js
@@ -578,8 +578,13 @@ L.Control.Header = L.Control.extend({
 		return Math.round(this._getParallelPos(this.converter(point)));
 	},
 
+	canvasDPIScale: function () {
+		var docLayer = this._map && this._map._docLayer;
+		var scale = docLayer && docLayer.canvasDPIScale ? docLayer.canvasDPIScale() : L.getDpiScaleFactor();
+		return scale;
+	},
+
 	_setCanvasSizeImpl: function (container, canvas, property, value, isCorner) {
-		var useExactDPR = this._map && (this._map._docLayer instanceof L.CanvasTileLayer);
 		if (!value) {
 			value = parseInt(L.DomUtil.getStyle(container, property));
 		}
@@ -587,15 +592,15 @@ L.Control.Header = L.Control.extend({
 			L.DomUtil.setStyle(container, property, value + 'px');
 		}
 
-		var scale = L.getDpiScaleFactor(useExactDPR);
+		var scale = this.canvasDPIScale();
 		if (property === 'width') {
-			canvas.width = value * scale;
+			canvas.width = Math.floor(value * scale);
 			if (!isCorner)
 				this._canvasWidth = value;
 //			console.log('Header._setCanvasSizeImpl: _canvasWidth' + this._canvasWidth);
 		}
 		else if (property === 'height') {
-			canvas.height = value * scale;
+			canvas.height = Math.floor(value * scale);
 			if (!isCorner)
 				this._canvasHeight = value;
 //			console.log('Header._setCanvasSizeImpl: _canvasHeight' + this._canvasHeight);
@@ -718,7 +723,7 @@ L.Control.Header = L.Control.extend({
 			return;
 
 		ctx.save();
-		var scale = L.getDpiScaleFactor();
+		var scale = this.canvasDPIScale();
 		ctx.scale(scale, scale);
 
 		ctx.fillStyle = this._borderColor;
diff --git a/loleaflet/src/control/Control.RowHeader.js b/loleaflet/src/control/Control.RowHeader.js
index 80ccbbe1b..fb53bfce6 100644
--- a/loleaflet/src/control/Control.RowHeader.js
+++ b/loleaflet/src/control/Control.RowHeader.js
@@ -39,7 +39,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 		this._setCanvasWidth();
 		this._setCanvasHeight();
 
-		var scale = L.getDpiScaleFactor();
+		var scale = this.canvasDPIScale();
 		this._canvasContext.scale(scale, scale);
 		this._headerWidth = this._canvasWidth;
 		L.Control.Header.rowHeaderWidth = this._canvasWidth;
@@ -217,8 +217,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 			return;
 
 		ctx.save();
-		var useExactDPR = this._map && (this._map._docLayer instanceof L.CanvasTileLayer);
-		var scale = L.getDpiScaleFactor(useExactDPR);
+		var scale = this.canvasDPIScale();
 		ctx.scale(scale, scale);
 		// background gradient
 		var selectionBackgroundGradient = null;
@@ -280,7 +279,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 		var height = group.endPos - group.startPos;
 
 		ctx.save();
-		var scale = L.getDpiScaleFactor();
+		var scale = this.canvasDPIScale();
 		ctx.scale(scale, scale);
 
 		// clip mask
@@ -329,7 +328,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 		var ctx = this._cornerCanvasContext;
 		var ctrlHeadSize = this._groupHeadSize;
 		var levelSpacing = this._levelSpacing;
-		var scale = L.getDpiScaleFactor();
+		var scale = this.canvasDPIScale();
 
 		var startOrt = levelSpacing + (ctrlHeadSize + levelSpacing) * level;
 		var startPar = this._cornerCanvas.height / scale - (ctrlHeadSize + (L.Control.Header.colHeaderHeight - ctrlHeadSize) / 2);
diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js
index c7b0ef1f2..c98d2fef4 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -79,7 +79,6 @@ L.CalcTileLayer = BaseTileLayer.extend({
 	},
 
 	onAdd: function (map) {
-		this._useExactDPR = this._hasCanvasRenderer = (this instanceof L.CanvasTileLayer);
 		map.addControl(L.control.tabs());
 		map.addControl(L.control.columnHeader());
 		map.addControl(L.control.rowHeader());
@@ -476,7 +475,7 @@ L.CalcTileLayer = BaseTileLayer.extend({
 		this._sendClientZoom();
 		if (this.sheetGeometry) {
 			this.sheetGeometry.setTileGeometryData(this._tileWidthTwips, this._tileHeightTwips,
-				this._tileSize, this._hasCanvasRenderer ? L.getDpiScaleFactor(true /* useExactDPR */) : this._tilePixelScale);
+				this._tileSize, this.hasCanvasRenderer() ? this.canvasDPIScale() : this._tilePixelScale);
 		}
 		this._restrictDocumentSize();
 		this._replayPrintTwipsMsgs();
@@ -742,7 +741,7 @@ L.CalcTileLayer = BaseTileLayer.extend({
 	_handleSheetGeometryDataMsg: function (jsonMsgObj) {
 		if (!this.sheetGeometry) {
 			this._sheetGeomFirstWait = false;
-			var dpiScale = this._hasCanvasRenderer ? L.getDpiScaleFactor(true /* useExactDPR */) : this._tilePixelScale;
+			var dpiScale = this.hasCanvasRenderer() ? this.canvasDPIScale() : this._tilePixelScale;
 			this.sheetGeometry = new L.SheetGeometry(jsonMsgObj,
 				this._tileWidthTwips, this._tileHeightTwips,
 				this._tileSize, dpiScale, this._selectedPart);
diff --git a/loleaflet/src/layer/tile/CanvasTileLayer.js b/loleaflet/src/layer/tile/CanvasTileLayer.js
index f8b739a2a..2a28a3e9e 100644
--- a/loleaflet/src/layer/tile/CanvasTileLayer.js
+++ b/loleaflet/src/layer/tile/CanvasTileLayer.js
@@ -111,6 +111,10 @@ L.CanvasTilePainter = L.Class.extend({
 		this._syncTileContainerSize();
 	},
 
+	canvasDPIScale: function () {
+		return parseInt(this._canvas.width) / this._width;
+	},
+
 	_syncTileContainerSize: function () {
 		var tileContainer = this._layer._container;
 		if (tileContainer) {
@@ -481,6 +485,10 @@ L.CanvasTileLayer = L.TileLayer.extend({
 			coords.part);
 	},
 
+	canvasDPIScale: function () {
+		return this._painter.canvasDPIScale();
+	},
+
 	_pxBoundsToTileRanges: function (bounds) {
 		if (!this._splitPanesContext) {
 			return [this._pxBoundsToTileRange(bounds)];
@@ -1210,6 +1218,10 @@ L.CanvasTileLayer = L.TileLayer.extend({
 		return !!(this._ySplitter);
 	},
 
+	hasCanvasRenderer: function () {
+		return true;
+	},
+
 });
 
 L.TilesPreFetcher = L.Class.extend({
diff --git a/loleaflet/src/layer/tile/GridLayer.js b/loleaflet/src/layer/tile/GridLayer.js
index 5d3ce69db..4ad07d8fa 100644
--- a/loleaflet/src/layer/tile/GridLayer.js
+++ b/loleaflet/src/layer/tile/GridLayer.js
@@ -1352,6 +1352,10 @@ L.GridLayer = L.Layer.extend({
 		return docPosPixY;
 	},
 
+	hasCanvasRenderer: function () {
+		return false;
+	},
+
 	hasSplitPanesSupport: function () {
 		return false;
 	},
diff --git a/loleaflet/src/map/Map.js b/loleaflet/src/map/Map.js
index 1bf71a07e..c93abac94 100644
--- a/loleaflet/src/map/Map.js
+++ b/loleaflet/src/map/Map.js
@@ -452,12 +452,23 @@ L.Map = L.Evented.extend({
 		this._progressBar.end(this);
 	},
 
+	zoomToFactor: function (zoom) {
+		return Math.pow(1.2, (zoom - this.options.zoom));
+	},
+
+	factorToZoom: function (zoomFactor) {
+		var relzoom = Math.log(zoomFactor) / Math.log(1.2);
+		return Math.round(relzoom) + this.options.zoom;
+	},
+
 	// Compute the nearest zoom level corresponding to the effective zoom-scale (ie, with dpiscale included).
 	findNearestProductZoom: function (zoom) {
-		var clientZoomScale = Math.pow(1.2, (zoom - this.options.zoom));
+		var clientZoomScale = this.zoomToFactor(zoom);
+
+		var dpiScale = this._docLayer ? this._docLayer.canvasDPIScale() : L.getDpiScaleFactor(true /* useExactDPR */);
 
-		var zoomScale = clientZoomScale * L.getCanvasScaleFactor();
-		var nearestZoom = Math.round((Math.log(zoomScale) / Math.log(1.2)) + this.options.zoom);
+		var zoomScale = clientZoomScale * dpiScale;
+		var nearestZoom = this.factorToZoom(zoomScale);
 		nearestZoom = this._limitZoom(nearestZoom);
 
 		return nearestZoom;
commit 349569c368b8bab3a522ebecd7527402c31bd3c4
Author:     Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Mon Aug 24 22:34:27 2020 +0530
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    sheetGeometry: use exact dpiScale when canvas is used
    
    Also use the term core-pixels instead of 'device pixels' which is more
    appropriate.
    
    Change-Id: I18952393f17e0391167e0219b829be47723c5c47

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js
index 417cf0e16..c7b0ef1f2 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -79,6 +79,7 @@ L.CalcTileLayer = BaseTileLayer.extend({
 	},
 
 	onAdd: function (map) {
+		this._useExactDPR = this._hasCanvasRenderer = (this instanceof L.CanvasTileLayer);
 		map.addControl(L.control.tabs());
 		map.addControl(L.control.columnHeader());
 		map.addControl(L.control.rowHeader());
@@ -475,7 +476,7 @@ L.CalcTileLayer = BaseTileLayer.extend({
 		this._sendClientZoom();
 		if (this.sheetGeometry) {
 			this.sheetGeometry.setTileGeometryData(this._tileWidthTwips, this._tileHeightTwips,
-				this._tileSize, this._tilePixelScale);
+				this._tileSize, this._hasCanvasRenderer ? L.getDpiScaleFactor(true /* useExactDPR */) : this._tilePixelScale);
 		}
 		this._restrictDocumentSize();
 		this._replayPrintTwipsMsgs();
@@ -741,9 +742,10 @@ L.CalcTileLayer = BaseTileLayer.extend({
 	_handleSheetGeometryDataMsg: function (jsonMsgObj) {
 		if (!this.sheetGeometry) {
 			this._sheetGeomFirstWait = false;
+			var dpiScale = this._hasCanvasRenderer ? L.getDpiScaleFactor(true /* useExactDPR */) : this._tilePixelScale;
 			this.sheetGeometry = new L.SheetGeometry(jsonMsgObj,
 				this._tileWidthTwips, this._tileHeightTwips,
-				this._tileSize, this._tilePixelScale, this._selectedPart);
+				this._tileSize, dpiScale, this._selectedPart);
 		}
 		else {
 			this.sheetGeometry.update(jsonMsgObj, /* checkCompleteness */ false, this._selectedPart);
@@ -1223,12 +1225,12 @@ L.SheetGeometry = L.Class.extend({
 	// all flags (ie 'columns', 'rows', 'sizes', 'hidden', 'filtered',
 	// 'groups') enabled.
 	initialize: function (sheetGeomJSON, tileWidthTwips, tileHeightTwips,
-		tileSizeCSSPixels, dpiScale, part) {
+		tileSizePixels, dpiScale, part) {
 
 		if (typeof sheetGeomJSON !== 'object' ||
 			typeof tileWidthTwips !== 'number' ||
 			typeof tileHeightTwips !== 'number' ||
-			typeof tileSizeCSSPixels !== 'number' ||
+			typeof tileSizePixels !== 'number' ||
 			typeof dpiScale !== 'number' ||
 			typeof part !== 'number') {
 			console.error('Incorrect constructor argument types or missing required arguments');
@@ -1241,7 +1243,7 @@ L.SheetGeometry = L.Class.extend({
 		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,
+		this.setTileGeometryData(tileWidthTwips, tileHeightTwips, tileSizePixels,
 			dpiScale, false /* update position info ?*/);
 
 		this.update(sheetGeomJSON, /* checkCompleteness */ true, part);
@@ -1285,10 +1287,10 @@ L.SheetGeometry = L.Class.extend({
 		return this._part;
 	},
 
-	setTileGeometryData: function (tileWidthTwips, tileHeightTwips, tileSizeCSSPixels,
+	setTileGeometryData: function (tileWidthTwips, tileHeightTwips, tileSizePixels,
 		dpiScale, updatePositions) {
-		this._columns.setTileGeometryData(tileWidthTwips, tileSizeCSSPixels, dpiScale, updatePositions);
-		this._rows.setTileGeometryData(tileHeightTwips, tileSizeCSSPixels, dpiScale, updatePositions);
+		this._columns.setTileGeometryData(tileWidthTwips, tileSizePixels, dpiScale, updatePositions);
+		this._rows.setTileGeometryData(tileHeightTwips, tileSizePixels, dpiScale, updatePositions);
 	},
 
 	setViewArea: function (topLeftTwipsPoint, sizeTwips) {
@@ -1429,7 +1431,7 @@ L.SheetGeometry = L.Class.extend({
 	},
 
 	// Returns full sheet size as L.Point in the given unit.
-	// unit must be one of 'csspixels', 'devpixels', 'tiletwips', 'printtwips'
+	// unit must be one of 'csspixels', 'corepixels', 'tiletwips', 'printtwips'
 	getSize: function (unit) {
 		return new L.Point(this._columns.getSize(unit),
 			this._rows.getSize(unit));
@@ -1437,8 +1439,8 @@ L.SheetGeometry = L.Class.extend({
 
 	// Returns the CSS pixel position/size of the requested cell at a specified zoom.
 	getCellRect: function (columnIndex, rowIndex, zoomScale) {
-		var horizPosSize = this._columns.getElementData(columnIndex, false /* devicePixels */, zoomScale);
-		var vertPosSize  = this._rows.getElementData(rowIndex, false /* devicePixels */, zoomScale);
+		var horizPosSize = this._columns.getElementData(columnIndex, false /* corePixels */, zoomScale);
+		var vertPosSize  = this._rows.getElementData(rowIndex, false /* corePixels */, zoomScale);
 
 		var topLeft = new L.Point(horizPosSize.startpos, vertPosSize.startpos);
 		var size = new L.Point(horizPosSize.size, vertPosSize.size);
@@ -1455,13 +1457,13 @@ L.SheetGeometry = L.Class.extend({
 	},
 
 	// Returns the start position of the column containing posX in the specified unit.
-	// unit must be one of 'csspixels', 'devpixels', 'tiletwips', 'printtwips'
+	// unit must be one of 'csspixels', 'corepixels', 'tiletwips', 'printtwips'
 	getSnapDocPosX: function (posX, unit) {
 		return this._columns.getSnapPos(posX, unit);
 	},
 
 	// Returns the start position of the row containing posY in the specified unit.
-	// unit must be one of 'csspixels', 'devpixels', 'tiletwips', 'printtwips'
+	// unit must be one of 'csspixels', 'corepixels', 'tiletwips', 'printtwips'
 	getSnapDocPosY: function (posY, unit) {
 		return this._rows.getSnapPos(posY, unit);
 	},
@@ -1593,7 +1595,7 @@ L.SheetDimension = L.Class.extend({
 		this._maxIndex = maxIndex;
 	},
 
-	setTileGeometryData: function (tileSizeTwips, tileSizeCSSPixels, dpiScale, updatePositions) {
+	setTileGeometryData: function (tileSizeTwips, tileSizePixels, dpiScale, updatePositions) {
 
 		if (updatePositions === undefined) {
 			updatePositions = true;
@@ -1601,17 +1603,22 @@ L.SheetDimension = L.Class.extend({
 
 		// Avoid position re-computations if no change in Zoom/dpiScale.
 		if (this._tileSizeTwips === tileSizeTwips &&
-			this._tileSizeCSSPixels === tileSizeCSSPixels &&
+			this._tileSizePixels === tileSizePixels &&
 			this._dpiScale === dpiScale) {
 			return;
 		}
 
 		this._tileSizeTwips = tileSizeTwips;
-		this._tileSizeCSSPixels = tileSizeCSSPixels;
+		this._tileSizePixels = tileSizePixels;
 		this._dpiScale = dpiScale;
 
-		this._twipsPerCSSPixel = tileSizeTwips / tileSizeCSSPixels;
-		this._devPixelsPerCssPixel = dpiScale;
+		// number of core-pixels in the tile is the same as the number of device pixels used to render the tile.
+		// (Note that when not using L.CanvasTileLayer, we do not use the exact window.devicePixelRatio
+		// for dpiScale hence the usage of the term device-pixels is not accurate.)
+		this._coreZoomFactor = this._tileSizePixels * 15.0 / this._tileSizeTwips;
+		this._twipsPerCorePixel = this._tileSizeTwips / this._tileSizePixels;
+
+		this._corePixelsPerCssPixel = this._dpiScale;
 
 		if (updatePositions) {
 			// We need to compute positions data for every zoom change.
@@ -1628,7 +1635,7 @@ L.SheetDimension = L.Class.extend({
 
 	_updatePositions: function() {
 
-		var posDevPx = 0; // position in device pixels.
+		var posCorePx = 0; // position in core pixels.
 		var posPrintTwips = 0;
 		var dimensionObj = this;
 		this._visibleSizes.addCustomDataForEachSpan(function (
@@ -1636,17 +1643,17 @@ L.SheetDimension = L.Class.extend({
 			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);
+			// Important: rounding needs to be done in core pixels to match core.
+			var sizeCorePxOne = Math.floor(size / dimensionObj._twipsPerCorePixel);
+			posCorePx += (sizeCorePxOne * spanLength);
+			var posCssPx = posCorePx / dimensionObj._corePixelsPerCssPixel;
+			// position in core-pixel aligned twips.
+			var posTileTwips = Math.floor(posCorePx * dimensionObj._twipsPerCorePixel);
 			posPrintTwips += (size * spanLength);
 
 			var customData = {
-				sizedev: sizeDevPxOne,
-				posdevpx: posDevPx,
+				sizecore: sizeCorePxOne,
+				poscorepx: posCorePx,
 				poscsspx: posCssPx,
 				postiletwips: posTileTwips,
 				posprinttwips: posPrintTwips
@@ -1657,23 +1664,29 @@ L.SheetDimension = L.Class.extend({
 	},
 
 	// returns the element pos/size in css pixels by default.
-	getElementData: function (index, useDevicePixels, zoomScale) {
+	getElementData: function (index, useCorePixels, zoomScale) {
 		if (zoomScale !== undefined) {
 			var startpos = 0;
 			var size = 0;
 			this._visibleSizes.forEachSpanInRange(0, index, function (spanData) {
 				var count = spanData.end - spanData.start + 1;
-				var sizeOneCSSPx = Math.floor(spanData.size * zoomScale / 15.0);
+				var sizeOneCorePx = Math.floor(spanData.size * zoomScale / 15.0);
 				if (index > spanData.end) {
-					startpos += (sizeOneCSSPx * count);
+					startpos += (sizeOneCorePx * count);
 				}
 				else if (index >= spanData.start && index <= spanData.end) {
 					// final span
-					startpos += (sizeOneCSSPx * (index - spanData.start));
-					size = sizeOneCSSPx;
+					startpos += (sizeOneCorePx * (index - spanData.start));
+					size = sizeOneCorePx;
 				}
 			});
 
+			if (!useCorePixels) {
+				// startpos and size are now in core pixels, so convert to css pixels.
+				startpos = Math.floor(startpos / this._corePixelsPerCssPixel);
+				size = Math.floor(size / this._corePixelsPerCssPixel);
+			}
+
 			return {
 				startpos: startpos,
 				size: size
@@ -1685,7 +1698,7 @@ L.SheetDimension = L.Class.extend({
 			return undefined;
 		}
 
-		return this._getElementDataFromSpanByIndex(index, span, useDevicePixels);
+		return this._getElementDataFromSpanByIndex(index, span, useCorePixels);
 	},
 
 	getElementDataAny: function (index, unitName) {
@@ -1698,9 +1711,9 @@ L.SheetDimension = L.Class.extend({
 	},
 
 	// returns element pos/size in css pixels by default.
-	_getElementDataFromSpanByIndex: function (index, span, useDevicePixels) {
+	_getElementDataFromSpanByIndex: function (index, span, useCorePixels) {
 		return this._getElementDataAnyFromSpanByIndex(index, span,
-				useDevicePixels ? 'devpixels' : 'csspixels');
+				useCorePixels ? 'corepixels' : 'csspixels');
 	},
 
 	// returns element pos/size in the requested unit.
@@ -1710,20 +1723,20 @@ L.SheetDimension = L.Class.extend({
 			return undefined;
 		}
 
-		if (unitName !== 'csspixels' && unitName !== 'devpixels' &&
+		if (unitName !== 'csspixels' && unitName !== 'corepixels' &&
 				unitName !== 'tiletwips' && unitName !== 'printtwips') {
 			console.error('unsupported unitName: ' + unitName);
 			return undefined;
 		}
 
 		var numSizes = span.end - index + 1;
-		var inPixels = (unitName === 'csspixels' || unitName === 'devpixels');
+		var inPixels = (unitName === 'csspixels' || unitName === 'corepixels');
 		if (inPixels) {
-			var useDevicePixels = (unitName === 'devpixels');
-			var pixelScale = useDevicePixels ? 1 : this._devPixelsPerCssPixel;
+			var useCorePixels = (unitName === 'corepixels');
+			var pixelScale = useCorePixels ? 1 : this._corePixelsPerCssPixel;
 			return {
-				startpos: (span.data.posdevpx - span.data.sizedev * numSizes) / pixelScale,
-				size: span.data.sizedev / pixelScale
+				startpos: (span.data.poscorepx - span.data.sizecore * numSizes) / pixelScale,
+				size: span.data.sizecore / pixelScale
 			};
 		}
 
@@ -1735,12 +1748,12 @@ L.SheetDimension = L.Class.extend({
 		}
 
 		// 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;
+		// It is very important to calculate this from core pixel units to mirror the core calculations.
+		var twipsPerCorePixel = this._twipsPerCorePixel;
 		return {
 			startpos: Math.floor(
-				(span.data.posdevpx - span.data.sizedev * numSizes) * twipsPerDevPixels),
-			size: Math.floor(span.data.sizedev * twipsPerDevPixels)
+				(span.data.poscorepx - span.data.sizecore * numSizes) * twipsPerCorePixel),
+			size: Math.floor(span.data.sizecore * twipsPerCorePixel)
 		};
 	},
 
@@ -1769,8 +1782,7 @@ L.SheetDimension = L.Class.extend({
 			return result;
 		}
 		var elementCount = span.end - span.start + 1;
-		var posStart = ((span.data.posdevpx - span.data.sizedev * elementCount) /
-			this._devPixelsPerCssPixel * this._twipsPerCSSPixel);
+		var posStart = ((span.data.poscorepx - span.data.sizecore * elementCount) * this._twipsPerCorePixel);
 		var posEnd = span.data.postiletwips;
 		var sizeOne = (posEnd - posStart) / elementCount;
 
@@ -1837,8 +1849,8 @@ L.SheetDimension = L.Class.extend({
 		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 */);
+				var startElementData = dimensionObj.getElementData(start, true /* core pixels */);
+				var endElementData = dimensionObj.getElementData(end, true /* core pixels */);
 				groupsData.push({
 					level: (levelIdx + 1).toString(),
 					index: groupIdx.toString(),
@@ -1900,9 +1912,9 @@ L.SheetDimension = L.Class.extend({
 		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,
+			// Set the size = twips equivalent of 1 core pixel,
 			// to imitate what core does when it sends cursor/ranges in tile-twips coordinates.
-			var rangeSize = Math.floor(this._twipsPerCSSPixel / this._devPixelsPerCssPixel);
+			var rangeSize = Math.floor(this._twipsPerCorePixel);
 			return {
 				startpos: startData.startpos,
 				endpos: startData.startpos + rangeSize
@@ -1935,7 +1947,7 @@ L.SheetDimension = L.Class.extend({
 	isUnitSupported: function (unitName) {
 		return (
 			unitName === 'csspixels' ||
-			unitName === 'devpixels' ||
+			unitName === 'corepixels' ||
 			unitName === 'tiletwips' ||
 			unitName === 'printtwips'
 		);
@@ -1947,12 +1959,12 @@ L.SheetDimension = L.Class.extend({
 
 		var origUnit = unit;
 
-		if (unit === 'devpixels') {
-			pos = (pos * this._twipsPerCSSPixel) / this._devPixelsPerCssPixel;
+		if (unit === 'corepixels') {
+			pos = pos * this._twipsPerCorePixel;
 			unit = 'tiletwips';
 		}
 		else if (unit === 'csspixels') {
-			pos = pos * this._twipsPerCSSPixel;
+			pos = pos * this._corePixelsPerCssPixel * this._twipsPerCorePixel;
 			unit = 'tiletwips';
 		}
 
@@ -1968,12 +1980,12 @@ L.SheetDimension = L.Class.extend({
 		console.assert(typeof pos === 'number', 'pos is not a number');
 		console.assert(this.isUnitSupported(unit), 'unit: ' + unit + ' is not supported');
 
-		if (unit === 'devpixels') {
-			pos = (pos * this._twipsPerCSSPixel) / this._devPixelsPerCssPixel;
+		if (unit === 'corepixels') {
+			pos = pos * this._twipsPerCorePixel;
 			unit = 'tiletwips';
 		}
 		else if (unit === 'csspixels') {
-			pos = pos * this._twipsPerCSSPixel;
+			pos = pos * this._corePixelsPerCssPixel * this._twipsPerCorePixel;
 			unit = 'tiletwips';
 		}
 
commit 5a0448b376aff2959e2621fdde1231a72f4bcbbd
Author:     Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Mon Aug 24 22:31:16 2020 +0530
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    header canvases should resize with map-resize
    
    This is now safe as we update their contents on resize.
    
    Change-Id: Ie8b33e03e9b67de0f5c4d0e4822154032c171a70

diff --git a/loleaflet/css/spreadsheet.css b/loleaflet/css/spreadsheet.css
index c0c6b95e0..abf6cad98 100644
--- a/loleaflet/css/spreadsheet.css
+++ b/loleaflet/css/spreadsheet.css
@@ -130,6 +130,7 @@
 .spreadsheet-header-columns {
 	display: inline-block;
 	white-space: nowrap;
+	width: 100%;
 	height: 100%;
 	border-spacing: 0px !important;
 	position: relative;
@@ -172,6 +173,7 @@
 
 .spreadsheet-header-rows {
 	width: 100%;
+	height: 100%;
 	border-spacing: 0px !important;
 	position: relative;
 	margin: 0px;
commit 95544951ca46563e01bbf6eea056cfa6104569e0
Author:     Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Mon Aug 24 22:12:18 2020 +0530
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    setup the header canvases in the same way as the tile-canvas
    
    All drawings to it needs to in css pixels for now, because the
    mouse/touch handlers need positions in css pixels and the HeaderInfo
    datastructure has everything in css pixels.
    
    Moving the headers to the main-canvas needs more work but this change
    will help in doing that.
    
    Change-Id: I6a19e62a67b2b42975a51bb695db300ce493ba01

diff --git a/loleaflet/src/control/Control.ColumnHeader.js b/loleaflet/src/control/Control.ColumnHeader.js
index d6eccf5b1..4639acd96 100644
--- a/loleaflet/src/control/Control.ColumnHeader.js
+++ b/loleaflet/src/control/Control.ColumnHeader.js
@@ -224,7 +224,8 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 			return;
 
 		ctx.save();
-		var scale = L.getDpiScaleFactor();
+		var useExactDPR = this._map && (this._map._docLayer instanceof L.CanvasTileLayer);
+		var scale = L.getDpiScaleFactor(useExactDPR);
 		ctx.scale(scale, scale);
 		// background gradient
 		var selectionBackgroundGradient = null;
diff --git a/loleaflet/src/control/Control.Header.js b/loleaflet/src/control/Control.Header.js
index bb0d4433f..757cfd5c8 100644
--- a/loleaflet/src/control/Control.Header.js
+++ b/loleaflet/src/control/Control.Header.js
@@ -579,6 +579,7 @@ L.Control.Header = L.Control.extend({
 	},
 
 	_setCanvasSizeImpl: function (container, canvas, property, value, isCorner) {
+		var useExactDPR = this._map && (this._map._docLayer instanceof L.CanvasTileLayer);
 		if (!value) {
 			value = parseInt(L.DomUtil.getStyle(container, property));
 		}
@@ -586,7 +587,7 @@ L.Control.Header = L.Control.extend({
 			L.DomUtil.setStyle(container, property, value + 'px');
 		}
 
-		var scale = L.getDpiScaleFactor();
+		var scale = L.getDpiScaleFactor(useExactDPR);
 		if (property === 'width') {
 			canvas.width = value * scale;
 			if (!isCorner)
diff --git a/loleaflet/src/control/Control.RowHeader.js b/loleaflet/src/control/Control.RowHeader.js
index 8e80aec73..80ccbbe1b 100644
--- a/loleaflet/src/control/Control.RowHeader.js
+++ b/loleaflet/src/control/Control.RowHeader.js
@@ -217,7 +217,8 @@ L.Control.RowHeader = L.Control.Header.extend({
 			return;
 
 		ctx.save();
-		var scale = L.getDpiScaleFactor();
+		var useExactDPR = this._map && (this._map._docLayer instanceof L.CanvasTileLayer);
+		var scale = L.getDpiScaleFactor(useExactDPR);
 		ctx.scale(scale, scale);
 		// background gradient
 		var selectionBackgroundGradient = null;
commit 197e28d45486303bf1b7ee866ce628811cc595a5
Author:     Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Mon Aug 24 16:49:47 2020 +0530
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    compute nearest zoomlevel with devpixelratio included
    
    and use this for every setZoom call.
    
    Change-Id: I37f0d7503e4087f062576bc03b13bd8155c3c994

diff --git a/loleaflet/src/layer/tile/CanvasTileLayer.js b/loleaflet/src/layer/tile/CanvasTileLayer.js
index ba74b2c4c..f8b739a2a 100644
--- a/loleaflet/src/layer/tile/CanvasTileLayer.js
+++ b/loleaflet/src/layer/tile/CanvasTileLayer.js
@@ -380,6 +380,11 @@ L.CanvasTileLayer = L.TileLayer.extend({
 		this._tileHeightPx = this.options.tileSize;
 		this._tilePixelScale = 1;
 
+		// FIXME: workaround for correcting initial zoom with dpiscale included.
+		// The one set during Map constructor is does not include dpiscale because
+		// there we don't have enough info to specialize for calc-canvas
+		map.setZoom(map.getZoom());
+
 		L.TileLayer.prototype.onAdd.call(this, map);
 	},
 
diff --git a/loleaflet/src/map/Map.js b/loleaflet/src/map/Map.js
index 9f3633929..1bf71a07e 100644
--- a/loleaflet/src/map/Map.js
+++ b/loleaflet/src/map/Map.js
@@ -452,7 +452,22 @@ L.Map = L.Evented.extend({
 		this._progressBar.end(this);
 	},
 
+	// Compute the nearest zoom level corresponding to the effective zoom-scale (ie, with dpiscale included).
+	findNearestProductZoom: function (zoom) {
+		var clientZoomScale = Math.pow(1.2, (zoom - this.options.zoom));
+
+		var zoomScale = clientZoomScale * L.getCanvasScaleFactor();
+		var nearestZoom = Math.round((Math.log(zoomScale) / Math.log(1.2)) + this.options.zoom);
+		nearestZoom = this._limitZoom(nearestZoom);
+
+		return nearestZoom;
+	},
+
 	setZoom: function (zoom, options) {
+
+		if (this._docLayer instanceof L.CanvasTileLayer)
+			zoom = this.findNearestProductZoom(zoom);
+
 		if (!this._loaded) {
 			this._zoom = this._limitZoom(zoom);
 			return this;
commit d8f5d7a6601e78533fc5a6d833c7f7d37ea7f05e
Author:     Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Mon Aug 24 15:50:44 2020 +0530
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    calc-canvas: make tile size fixed (256) for every device
    
    Change-Id: I4e00b8b43f73f001a8bcfc77931f5fa22982642e

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js
index a178a59da..417cf0e16 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -4,7 +4,8 @@
  */
 
 /* global */
-L.CalcTileLayer = (L.Browser.mobile ? L.TileLayer : L.CanvasTileLayer).extend({
+var BaseTileLayer = L.Browser.mobile ? L.TileLayer : L.CanvasTileLayer;
+L.CalcTileLayer = BaseTileLayer.extend({
 	options: {
 		// TODO: sync these automatically from SAL_LOK_OPTIONS
 		sheetGeometryDataEnabled: true,
@@ -81,7 +82,7 @@ L.CalcTileLayer = (L.Browser.mobile ? L.TileLayer : L.CanvasTileLayer).extend({
 		map.addControl(L.control.tabs());
 		map.addControl(L.control.columnHeader());
 		map.addControl(L.control.rowHeader());
-		L.TileLayer.prototype.onAdd.call(this, map);
+		BaseTileLayer.prototype.onAdd.call(this, map);
 
 		map.on('resize', function () {
 			if (this.isCursorVisible()) {
diff --git a/loleaflet/src/layer/tile/CanvasTileLayer.js b/loleaflet/src/layer/tile/CanvasTileLayer.js
index 70f6c8a36..ba74b2c4c 100644
--- a/loleaflet/src/layer/tile/CanvasTileLayer.js
+++ b/loleaflet/src/layer/tile/CanvasTileLayer.js
@@ -373,6 +373,16 @@ L.CanvasTileLayer = L.TileLayer.extend({
 		return false;
 	},
 
+	onAdd: function (map) {
+
+		// Override L.TileLayer._tilePixelScale to 1 (independent of the device).
+		this._tileWidthPx = this.options.tileSize;
+		this._tileHeightPx = this.options.tileSize;
+		this._tilePixelScale = 1;
+
+		L.TileLayer.prototype.onAdd.call(this, map);
+	},
+
 	onRemove: function (map) {
 		this._painter.dispose();
 		L.TileLayer.prototype.onRemove.call(this, map);
commit 7ffda6fceb177ef9d35b47a1c059229ec2b719d1
Author:     Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Mon Aug 24 15:34:30 2020 +0530
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    use window.devicePixelRatio without rounding
    
    at least for the canvas tile layer.
    
    Change-Id: Ia830cad1fe0aaac6df03288cc1ee9e0371ef6f47

diff --git a/loleaflet/src/core/Util.js b/loleaflet/src/core/Util.js
index b2900b62b..caa8e8cc2 100644
--- a/loleaflet/src/core/Util.js
+++ b/loleaflet/src/core/Util.js
@@ -159,8 +159,11 @@ L.Util = {
 	// minimal image URI, set to an image when disposing to flush memory
 	emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=',
 
-	getDpiScaleFactor: function() {
-		var dpiScale = window.devicePixelRatio ? Math.ceil(window.devicePixelRatio) : 1;
+	getDpiScaleFactor: function(useExactDPR) {
+		var dpiScale = window.devicePixelRatio ? window.devicePixelRatio : 1;
+		if (!useExactDPR)
+			dpiScale = Math.ceil(dpiScale);
+
 		if (dpiScale == 1 && L.Browser.retina) {
 			dpiScale = 2;
 		}
diff --git a/loleaflet/src/layer/tile/CanvasTileLayer.js b/loleaflet/src/layer/tile/CanvasTileLayer.js
index d189ecf6a..70f6c8a36 100644
--- a/loleaflet/src/layer/tile/CanvasTileLayer.js
+++ b/loleaflet/src/layer/tile/CanvasTileLayer.js
@@ -44,7 +44,7 @@ L.CanvasTilePainter = L.Class.extend({
 		this._layer = layer;
 		this._canvas = this._layer._canvas;
 
-		var dpiScale = L.getDpiScaleFactor();
+		var dpiScale = L.getDpiScaleFactor(true /* useExactDPR */);
 		this._dpiScale = dpiScale;
 
 		this._map = this._layer._map;
@@ -214,7 +214,7 @@ L.CanvasTilePainter = L.Class.extend({
 		var part = this._layer._selectedPart;
 		var newSplitPos = splitPanesContext ?
 		    splitPanesContext.getSplitPos(): this._splitPos;
-		var newDpiScale = L.getDpiScaleFactor();
+		var newDpiScale = L.getDpiScaleFactor(true /* useExactDPR */);
 
 		var zoomChanged = (zoom !== this._lastZoom);
 		var partChanged = (part !== this._lastPart);
@@ -242,8 +242,10 @@ L.CanvasTilePainter = L.Class.extend({
 		if (skipUpdate)
 			return;
 
-		if (scaleChanged)
-			this._dpiScale = L.getDpiScaleFactor();
+		if (scaleChanged) {
+			this._dpiScale = L.getDpiScaleFactor(true /* useExactDPR */);
+			console.log('DEBUG: scaleChanged : this._dpiScale = ' + this._dpiScale);
+		}
 
 		if (resizeCanvas || scaleChanged) {
 			this._setCanvasSize(newSize.x, newSize.y);
commit 8172e199d0b1f97292af5076f29c0296ad235ebd
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Fri Aug 21 20:43:47 2020 +0100
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    Don't merge - grim hack try to get 1:1 pixels in canvas.
    
    Change-Id: I8ff3f157112295e0c6ef6743de3c878329b98adb

diff --git a/loleaflet/src/layer/tile/CanvasTileLayer.js b/loleaflet/src/layer/tile/CanvasTileLayer.js
index 9f3b6e02f..d189ecf6a 100644
--- a/loleaflet/src/layer/tile/CanvasTileLayer.js
+++ b/loleaflet/src/layer/tile/CanvasTileLayer.js
@@ -40,22 +40,15 @@ L.CanvasTilePainter = L.Class.extend({
 		debug: true,
 	},
 
-	initialize: function (layer, enableImageSmoothing) {
+	initialize: function (layer) {
 		this._layer = layer;
 		this._canvas = this._layer._canvas;
 
 		var dpiScale = L.getDpiScaleFactor();
-		if (dpiScale === 1 || dpiScale === 2) {
-			enableImageSmoothing = (enableImageSmoothing === true);
-		}
-		else {
-			enableImageSmoothing = (enableImageSmoothing === undefined || enableImageSmoothing);
-		}
-
 		this._dpiScale = dpiScale;
 
 		this._map = this._layer._map;
-		this._setupCanvas(enableImageSmoothing);
+		this._setupCanvas();
 
 		this._topLeft = undefined;
 		this._lastZoom = undefined;
@@ -95,15 +88,11 @@ L.CanvasTilePainter = L.Class.extend({
 		this.stopUpdates();
 	},
 
-	setImageSmoothing: function (enable) {
-		this._canvasCtx.imageSmoothingEnabled = enable;
-		this._canvasCtx.msImageSmoothingEnabled = enable;
-	},
-
-	_setupCanvas: function (enableImageSmoothing) {
+	_setupCanvas: function () {
 		console.assert(this._canvas, 'no canvas element');
 		this._canvasCtx = this._canvas.getContext('2d', { alpha: false });
-		this.setImageSmoothing(enableImageSmoothing);
+		this._canvasCtx.imageSmoothingEnabled = false;
+		this._canvasCtx.msImageSmoothingEnabled = false;
 		var mapSize = this._map.getPixelBounds().getSize();
 		this._lastSize = mapSize;
 		this._lastMapSize = mapSize;
@@ -132,7 +121,7 @@ L.CanvasTilePainter = L.Class.extend({
 
 	clear: function () {
 		this._canvasCtx.save();
-		this._canvasCtx.scale(this._dpiScale, this._dpiScale);
+		this._canvasCtx.scale(1, 1);
 		if (this.options.debug)
 			this._canvasCtx.fillStyle = 'red';
 		else
@@ -177,7 +166,7 @@ L.CanvasTilePainter = L.Class.extend({
 				topLeft.y = ctx.viewBounds.min.y;
 
 			this._canvasCtx.save();
-			this._canvasCtx.scale(this._dpiScale, this._dpiScale);
+			this._canvasCtx.scale(1, 1);
 			this._canvasCtx.translate(-topLeft.x, -topLeft.y);
 
 			// create a clip for the pane/view.
@@ -186,12 +175,11 @@ L.CanvasTilePainter = L.Class.extend({
 			this._canvasCtx.rect(paneBounds.min.x, paneBounds.min.y, paneSize.x + 1, paneSize.y + 1);
 			this._canvasCtx.clip();
 
-			if (this._dpiScale !== 1) {
-				// FIXME: avoid this scaling when possible (dpiScale = 2).
-				this._canvasCtx.drawImage(tile.el, tile.coords.x, tile.coords.y, ctx.tileSize.x, ctx.tileSize.y);
-			}
-			else {
-				this._canvasCtx.drawImage(tile.el, tile.coords.x, tile.coords.y);
+			this._canvasCtx.drawImage(tile.el, tile.coords.x, tile.coords.y);
+			if (this.options.debug)
+			{
+				this._canvasCtx.strokeStyle = 'red';
+				this._canvasCtx.strokeRect(tile.coords.x, tile.coords.y, 256, 256);
 			}
 			this._canvasCtx.restore();
 		}
@@ -204,7 +192,7 @@ L.CanvasTilePainter = L.Class.extend({
 		}
 		var splitPos = splitPanesContext.getSplitPos();
 		this._canvasCtx.save();
-		this._canvasCtx.scale(this._dpiScale, this._dpiScale);
+		this._canvasCtx.scale(1, 1);
 		this._canvasCtx.strokeStyle = 'red';
 		this._canvasCtx.strokeRect(0, 0, splitPos.x, splitPos.y);
 		this._canvasCtx.restore();
@@ -249,6 +237,8 @@ L.CanvasTilePainter = L.Class.extend({
 			!splitPosChanged &&
 			!scaleChanged);
 
+		console.debug('Tile size: ' + this._layer._getTileSize());
+
 		if (skipUpdate)
 			return;
 
@@ -296,8 +286,8 @@ L.CanvasTilePainter = L.Class.extend({
 			for (var j = tileRange.min.y; j <= tileRange.max.y; ++j) {
 				for (var i = tileRange.min.x; i <= tileRange.max.x; ++i) {
 					var coords = new L.TileCoordData(
-						i * ctx.tileSize,
-						j * ctx.tileSize,
+						i * ctx.tileSize.x,
+						j * ctx.tileSize.y,
 						zoom,
 						part);
 
commit 0fd5cfc611ea683fbd22bbe6f0bb8dc9fd78437f
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Fri Aug 21 16:40:29 2020 +0100
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    calc tiles: more debug helpers
    
    Change-Id: I24370b2a35fdfeca360cbaeb296cd2dd3a11e768

diff --git a/loleaflet/src/layer/tile/CanvasTileLayer.js b/loleaflet/src/layer/tile/CanvasTileLayer.js
index 5829239fa..9f3b6e02f 100644
--- a/loleaflet/src/layer/tile/CanvasTileLayer.js
+++ b/loleaflet/src/layer/tile/CanvasTileLayer.js
@@ -37,7 +37,7 @@ L.TileCoordData.parseKey = function (keyString) {
 L.CanvasTilePainter = L.Class.extend({
 
 	options: {
-		debug: false,
+		debug: true,
 	},
 
 	initialize: function (layer, enableImageSmoothing) {
@@ -133,7 +133,10 @@ L.CanvasTilePainter = L.Class.extend({
 	clear: function () {
 		this._canvasCtx.save();
 		this._canvasCtx.scale(this._dpiScale, this._dpiScale);
-		this._canvasCtx.fillStyle = 'white';
+		if (this.options.debug)
+			this._canvasCtx.fillStyle = 'red';
+		else
+			this._canvasCtx.fillStyle = 'white';
 		this._canvasCtx.fillRect(0, 0, this._width, this._height);
 		this._canvasCtx.restore();
 	},
@@ -277,6 +280,10 @@ L.CanvasTilePainter = L.Class.extend({
 	},
 
 	_paintWholeCanvas: function () {
+
+		if (this.options.debug)
+			this.clear();
+
 		var zoom = this._lastZoom || Math.round(this._map.getZoom());
 		var part = this._lastPart || this._layer._selectedPart;
 
commit 55ebf8dc2ba46e406f640d0f861753c94a73e4fb
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Fri Aug 21 15:54:50 2020 +0100
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    calc tiles: share code for building bounds and panes.
    
    Avoid duplication between tileReady and paint.
    
    Change-Id: Ic3d1c22a1dbeffe1abfffd35ea0d7fbcfd5c1ccc

diff --git a/loleaflet/src/layer/tile/CanvasTileLayer.js b/loleaflet/src/layer/tile/CanvasTileLayer.js
index 5881803c8..5829239fa 100644
--- a/loleaflet/src/layer/tile/CanvasTileLayer.js
+++ b/loleaflet/src/layer/tile/CanvasTileLayer.js
@@ -63,8 +63,6 @@ L.CanvasTilePainter = L.Class.extend({
 		var splitPanesContext = this._layer.getSplitPanesContext();
 		this._splitPos = splitPanesContext ?
 			splitPanesContext.getSplitPos() : new L.Point(0, 0);
-
-		this._tileSizeCSSPx = undefined;
 		this._updatesRunning = false;
 	},
 
@@ -140,38 +138,40 @@ L.CanvasTilePainter = L.Class.extend({
 		this._canvasCtx.restore();
 	},
 
-	paint: function (tile, viewBounds, paneBoundsList) {
+	// Details of tile areas to render
+	_paintContext: function() {
+		var tileSize = new L.Point(this._layer._getTileSize(), this._layer._getTileSize());
 
-		if (this._tileSizeCSSPx === undefined) {
-			this._tileSizeCSSPx = this._layer._getTileSize();
-		}
+		var viewBounds = this._map.getPixelBounds();
+		var splitPanesContext = this._layer.getSplitPanesContext();
+		var paneBoundsList = splitPanesContext ?
+		    splitPanesContext.getPxBoundList(viewBounds) :
+		    [viewBounds];
+
+		return { tileSize: tileSize,
+			 viewBounds: viewBounds,
+			 paneBoundsList: paneBoundsList };
+	},
+
+	paint: function (tile, ctx) {
+
+		if (!ctx)
+			ctx = this._paintContext();
 
 		var tileTopLeft = tile.coords.getPos();
-		var tileSize = new L.Point(this._tileSizeCSSPx, this._tileSizeCSSPx);
-		var tileBounds = new L.Bounds(tileTopLeft, tileTopLeft.add(tileSize));
+		var tileBounds = new L.Bounds(tileTopLeft, tileTopLeft.add(ctx.tileSize));
 
-		viewBounds = viewBounds || this._map.getPixelBounds();
-		var splitPanesContext = this._layer.getSplitPanesContext();
-		paneBoundsList = paneBoundsList || (
-			splitPanesContext ?
-			splitPanesContext.getPxBoundList(viewBounds) :
-			[viewBounds]
-		);
+		for (var i = 0; i < ctx.paneBoundsList.length; ++i) {
+			var paneBounds = ctx.paneBoundsList[i];
 
-		for (var i = 0; i < paneBoundsList.length; ++i) {
-			var paneBounds = paneBoundsList[i];
-			if (!paneBounds.intersects(tileBounds)) {
+			if (!paneBounds.intersects(tileBounds))
 				continue;
-			}
 
 			var topLeft = paneBounds.getTopLeft();
-			if (topLeft.x) {
-				topLeft.x = viewBounds.min.x;
-			}
-
-			if (topLeft.y) {
-				topLeft.y = viewBounds.min.y;
-			}
+			if (topLeft.x)
+				topLeft.x = ctx.viewBounds.min.x;
+			if (topLeft.y)
+				topLeft.y = ctx.viewBounds.min.y;
 
 			this._canvasCtx.save();
 			this._canvasCtx.scale(this._dpiScale, this._dpiScale);
@@ -185,7 +185,7 @@ L.CanvasTilePainter = L.Class.extend({
 
 			if (this._dpiScale !== 1) {
 				// FIXME: avoid this scaling when possible (dpiScale = 2).
-				this._canvasCtx.drawImage(tile.el, tile.coords.x, tile.coords.y, this._tileSizeCSSPx, this._tileSizeCSSPx);
+				this._canvasCtx.drawImage(tile.el, tile.coords.x, tile.coords.y, ctx.tileSize.x, ctx.tileSize.y);
 			}
 			else {
 				this._canvasCtx.drawImage(tile.el, tile.coords.x, tile.coords.y);
@@ -280,24 +280,17 @@ L.CanvasTilePainter = L.Class.extend({
 		var zoom = this._lastZoom || Math.round(this._map.getZoom());
 		var part = this._lastPart || this._layer._selectedPart;
 
-		var viewSize = new L.Point(this._width, this._height);
-		var viewBounds = new L.Bounds(this._topLeft, this._topLeft.add(viewSize));
-
-		var splitPanesContext = this._layer.getSplitPanesContext();
 		// Calculate all this here intead of doing it per tile.
-		var paneBoundsList = splitPanesContext ?
-			splitPanesContext.getPxBoundList(viewBounds) : [viewBounds];
-		var tileRanges = paneBoundsList.map(this._layer._pxBoundsToTileRange, this._layer);
-
-		var tileSize = this._tileSizeCSSPx || this._layer._getTileSize();
+		var ctx = this._paintContext();
+		var tileRanges = ctx.paneBoundsList.map(this._layer._pxBoundsToTileRange, this._layer);
 
 		for (var rangeIdx = 0; rangeIdx < tileRanges.length; ++rangeIdx) {
 			var tileRange = tileRanges[rangeIdx];
 			for (var j = tileRange.min.y; j <= tileRange.max.y; ++j) {
 				for (var i = tileRange.min.x; i <= tileRange.max.x; ++i) {
 					var coords = new L.TileCoordData(
-						i * tileSize,
-						j * tileSize,
+						i * ctx.tileSize,
+						j * ctx.tileSize,
 						zoom,
 						part);
 
@@ -305,7 +298,7 @@ L.CanvasTilePainter = L.Class.extend({
 					var tile = this._layer._tiles[key];
 					var invalid = tile && tile._invalidCount && tile._invalidCount > 0;
 					if (tile && tile.loaded && !invalid) {
-						this.paint(tile, viewBounds, paneBoundsList);
+						this.paint(tile, ctx);
 					}
 				}
 			}
commit cf6b718d2a3586087b7a5d317f361853e0c4cc35
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Fri Aug 21 15:47:58 2020 +0100
Commit:     Jan Holesovsky <kendy at collabora.com>
CommitDate: Wed Sep 2 14:57:34 2020 +0200

    calc tiles: remove partial re-rendering for now.
    
    Drops _shiftAndPaint and _paintRects, blits are fast.
    
    Change-Id: I64779f1037784f4efbe74cdf564b5f09e13b3316

diff --git a/loleaflet/src/layer/tile/CanvasTileLayer.js b/loleaflet/src/layer/tile/CanvasTileLayer.js
index f6c29d57a..5881803c8 100644
--- a/loleaflet/src/layer/tile/CanvasTileLayer.js
+++ b/loleaflet/src/layer/tile/CanvasTileLayer.js
@@ -236,7 +236,6 @@ L.CanvasTilePainter = L.Class.extend({
 		var resizeCanvas = !newSize.equals(this._lastSize);
 
 		var topLeftChanged = this._topLeft === undefined || !newTopLeft.equals(this._topLeft);
-
 		var splitPosChanged = !newSplitPos.equals(this._splitPos);
 
 		var skipUpdate = (
@@ -247,9 +246,9 @@ L.CanvasTilePainter = L.Class.extend({
 			!splitPosChanged &&
 			!scaleChanged);
 
-		if (skipUpdate) {
+		if (skipUpdate)
 			return;
-		}
+
 		if (scaleChanged)
 			this._dpiScale = L.getDpiScaleFactor();
 
@@ -261,130 +260,20 @@ L.CanvasTilePainter = L.Class.extend({
 			this.clear();
 		}
 
-		if (mapSizeChanged) {
+		if (mapSizeChanged)
 			this._lastMapSize = newMapSize;
-		}
 
-		if (splitPosChanged) {
+		if (splitPosChanged)
 			this._splitPos = newSplitPos;
-		}
-
-		// TODO: fix _shiftAndPaint for high DPI.
-		var shiftPaintDisabled = true;
-		var fullRepaintNeeded = zoomChanged || partChanged || resizeCanvas ||
-		    shiftPaintDisabled || scaleChanged;
 
 		this._lastZoom = zoom;
 		this._lastPart = part;
 
-		if (fullRepaintNeeded) {
-
-			this._topLeft = newTopLeft;
-			this._paintWholeCanvas();
-
-			if (this.options.debug) {
-				this._drawSplits();
-			}
-
-			return;
-		}
-
-		this._shiftAndPaint(newTopLeft);
-	},
-
-	_shiftAndPaint: function (newTopLeft) {
-
-		console.assert(!this._layer.getSplitPanesContext(), '_shiftAndPaint is broken for split-panes.');
-		var offset = new L.Point(this._width - 1, this._height - 1);
-
-		var dx = newTopLeft.x - this._topLeft.x;
-		var dy = newTopLeft.y - this._topLeft.y;
-		if (!dx && !dy) {
-			return;
-		}
-
-		// Determine the area that needs to be painted as max. two disjoint rectangles.
-		var rectsToPaint = [];
-		this._inMove = true;
-		var oldTopLeft = this._topLeft;
-		var oldBottomRight = oldTopLeft.add(offset);
-		var newBottomRight = newTopLeft.add(offset);
-
-		if (Math.abs(dx) < this._width && Math.abs(dy) < this._height) {
-
-			this._canvasCtx.save();
-			this._canvasCtx.scale(this._dpiScale, this._dpiScale);
-			this._canvasCtx.globalCompositeOperation = 'copy';
-			this._canvasCtx.drawImage(this._canvas, -dx, -dy);
-			this._canvasCtx.globalCompositeOperation = 'source-over';
-			this._canvasCtx.restore();
-
-			var xstart = newTopLeft.x, xend = newBottomRight.x;
-			var ystart = newTopLeft.y, yend = newBottomRight.y;
-			if (dx) {
-				xstart = dx > 0 ? oldBottomRight.x + 1 : newTopLeft.x;
-				xend   = xstart + Math.abs(dx) - 1;
-			}
-
-			if (dy) {
-				ystart = dy > 0 ? oldBottomRight.y + 1 : newTopLeft.y;
-				yend   = ystart + Math.abs(dy) - 1;
-			}
-
-			// rectangle including the x-range that needs painting with full y-range.
-			// This will take care of simultaneous non-zero dx and dy.
-			if (dx) {
-				rectsToPaint.push(new L.Bounds(
-					new L.Point(xstart, newTopLeft.y),
-					new L.Point(xend,   newBottomRight.y)
-				));
-			}
-
-			// rectangle excluding the x-range that needs painting + needed y-range.
-			if (dy) {
-				rectsToPaint.push(new L.Bounds(
-					new L.Point(dx > 0 ? newTopLeft.x : (dx ? xend + 1 : newTopLeft.x), ystart),
-					new L.Point(dx > 0 ? xstart - 1   : newBottomRight.x,               yend)
-				));
-			}
-
-		}
-		else {
-			rectsToPaint.push(new L.Bounds(newTopLeft, newBottomRight));
-		}
-
 		this._topLeft = newTopLeft;
+		this._paintWholeCanvas();
 
-		this._paintRects(rectsToPaint, newTopLeft);
-	},
-
-	_paintRects: function (rects, topLeft) {
-		for (var i = 0; i < rects.length; ++i) {
-			this._paintRect(rects[i], topLeft);
-		}
-	},
-
-	_paintRect: function (rect) {
-		var zoom = this._lastZoom || Math.round(this._map.getZoom());
-		var part = this._lastPart || this._layer._selectedPart;
-		var tileRange = this._layer._pxBoundsToTileRange(rect);
-		var tileSize = this._tileSizeCSSPx || this._layer._getTileSize();
-		for (var j = tileRange.min.y; j <= tileRange.max.y; ++j) {
-			for (var i = tileRange.min.x; i <= tileRange.max.x; ++i) {
-				var coords = new L.TileCoordData(
-					i * tileSize,
-					j * tileSize,
-					zoom,
-					part);
-
-				var key = coords.key();
-				var tile = this._layer._tiles[key];
-				var invalid = tile && tile._invalidCount && tile._invalidCount > 0;
-				if (tile && tile.loaded && !invalid) {
-					this.paint(tile);
-				}
-			}
-		}
+		if (this.options.debug)
+			this._drawSplits();
 	},
 
 	_paintWholeCanvas: function () {
commit e32fc7981e3fffe218d6211929b11a6d1b6f9ad6
Author:     thais-dev <thais.vieira at collabora.com>
AuthorDate: Tue Sep 1 07:22:23 2020 -0300
Commit:     Aron Budea <aron.budea at collabora.com>
CommitDate: Wed Sep 2 14:02:29 2020 +0200

    Loleaflet: add time value
    
    for the context menu of header be in sync
    with the context menu of the cells.
    
    Change-Id: I3f228e57375b10671b98de1a1444af87e8b2ed38
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/101924
    Tested-by: Jenkins
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Tor Lillqvist <tml at collabora.com>
    Reviewed-by: Aron Budea <aron.budea at collabora.com>

diff --git a/loleaflet/src/control/Control.ColumnHeader.js b/loleaflet/src/control/Control.ColumnHeader.js
index cbd73c919..d6eccf5b1 100644
--- a/loleaflet/src/control/Control.ColumnHeader.js
+++ b/loleaflet/src/control/Control.ColumnHeader.js
@@ -100,7 +100,7 @@ L.Control.ColumnHeader = L.Control.Header.extend({
 			});
 		} else {
 			var menuData = L.Control.JSDialogBuilder.getMenuStructureForMobileWizard(this._menuItem, true, '');
-			(new Hammer(this._canvas, {recognizers: [[Hammer.Press]]}))
+			(new Hammer(this._canvas, {recognizers: [[Hammer.Press, {time: 500}]]}))
 			.on('press', L.bind(function () {
 				if (this._map.isPermissionEdit()) {
 					window.contextMenuWizard = true;
diff --git a/loleaflet/src/control/Control.RowHeader.js b/loleaflet/src/control/Control.RowHeader.js
index e39162ff2..8e80aec73 100644
--- a/loleaflet/src/control/Control.RowHeader.js
+++ b/loleaflet/src/control/Control.RowHeader.js
@@ -97,7 +97,7 @@ L.Control.RowHeader = L.Control.Header.extend({
 			});
 		} else {
 			var menuData = L.Control.JSDialogBuilder.getMenuStructureForMobileWizard(this._menuItem, true, '');
-			(new Hammer(this._canvas, {recognizers: [[Hammer.Press]]}))
+			(new Hammer(this._canvas, {recognizers: [[Hammer.Press, {time: 500}]]}))
 			.on('press', L.bind(function () {
 				if (this._map.isPermissionEdit()) {
 					window.contextMenuWizard = true;
commit 0a54b23251eaae201e198366190c8525bf23d038
Author:     Pranam Lashkari <lpranam at collabora.com>
AuthorDate: Wed Aug 26 22:53:37 2020 +0530
Commit:     Andras Timar <andras.timar at collabora.com>
CommitDate: Wed Sep 2 10:41:33 2020 +0200

    leaflet: wsd: select the page before opening the slide wizard
    
    problem:
    In the mobile view taping on the selected slide preview would open the wizard
    but when some object is selected on the slide
    wizard would open for that object
    this patch helps us to set the Page as selection and as result
    mobile wizard opens for the slide even when some object on slide is selected
    
    Change-Id: Ia4f0d5fe6a4d82d101ee26b75f557a44e0627704
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/101422
    Tested-by: Jenkins
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Andras Timar <andras.timar at collabora.com>

diff --git a/kit/ChildSession.cpp b/kit/ChildSession.cpp
index 7eba3aa19..bae3cce4f 100644
--- a/kit/ChildSession.cpp
+++ b/kit/ChildSession.cpp
@@ -2146,7 +2146,7 @@ bool ChildSession::setClientPart(const char* /*buffer*/, int /*length*/, const S
 
     getLOKitDocument()->setView(_viewId);
 
-    if (getLOKitDocument()->getDocumentType() != LOK_DOCTYPE_TEXT && part != getLOKitDocument()->getPart())
+    if (getLOKitDocument()->getDocumentType() != LOK_DOCTYPE_TEXT)
     {
         getLOKitDocument()->setPart(part);
     }
diff --git a/loleaflet/src/control/Control.PartsPreview.js b/loleaflet/src/control/Control.PartsPreview.js
index e529fad22..58408b48f 100644
--- a/loleaflet/src/control/Control.PartsPreview.js
+++ b/loleaflet/src/control/Control.PartsPreview.js
@@ -225,7 +225,8 @@ L.Control.PartsPreview = L.Control.extend({
 				var partId = parseInt(part) - 1; // The first part is just a drop-site for reordering.
 			if (!window.mode.isDesktop() && partId === this._map._docLayer._selectedPart) {
 				// if mobile or tab then second tap will open the mobile wizard
-				if (this._map._permission === 'edit') {
+				if (this._map.isPermissionEdit()) {
+					this._setPart(e);
 					setTimeout(function () {
 						w2ui['actionbar'].click('mobile_wizard');
 					}, 0);
commit 9c55d2d5b918c27a30859c152c102f944b2ba612
Author:     Szymon Kłos <szymon.klos at collabora.com>
AuthorDate: Wed Sep 2 09:36:52 2020 +0200
Commit:     Szymon Kłos <szymon.klos at collabora.com>
CommitDate: Wed Sep 2 10:01:43 2020 +0200

    Fix name formatting
    
    Change-Id: I2aed56cf362c0e68918955340d35013179a54ad1
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/101909
    Tested-by: Jenkins
    Reviewed-by: Szymon Kłos <szymon.klos at collabora.com>

diff --git a/loleaflet/src/control/Control.UserList.js b/loleaflet/src/control/Control.UserList.js
index 47a259eca..ed902a7c7 100644
--- a/loleaflet/src/control/Control.UserList.js
+++ b/loleaflet/src/control/Control.UserList.js
@@ -96,7 +96,7 @@ L.Control.UserList = L.Control.extend({
 			$(img).css({'background-color': color});
 		}
 
-		nameTd.innerHTML = userName;
+		nameTd.textContent = userName;
 
 		return content;
 	},
@@ -211,10 +211,11 @@ L.Control.UserList = L.Control.extend({
 
 	onRemoveView: function(e) {
 		var that = this;
+		var username = this.escapeHtml(e.username);
 		$('#tb_actionbar_item_userlist')
 			.w2overlay({
 				class: 'loleaflet-font',
-				html: this.options.userLeftPopupMessage.replace('%user', e.username),
+				html: this.options.userLeftPopupMessage.replace('%user', username),
 				style: 'padding: 5px'
 			});
 		clearTimeout(this.options.userPopupTimeout);
diff --git a/loleaflet/src/layer/marker/Cursor.js b/loleaflet/src/layer/marker/Cursor.js
index 71cd97a00..b9ffb02c4 100644
--- a/loleaflet/src/layer/marker/Cursor.js
+++ b/loleaflet/src/layer/marker/Cursor.js
@@ -96,7 +96,7 @@ L.Cursor = L.Layer.extend({
 		if (this.options.header) {
 			this._cursorHeader = L.DomUtil.create('div', 'leaflet-cursor-header', this._container);
 
-			this._cursorHeader.innerHTML = this.options.headerName;
+			this._cursorHeader.textContent = this.options.headerName;
 
 			clearTimeout(this._blinkTimeout);
 			this._blinkTimeout = setTimeout(L.bind(function() {
commit 845554a6a3a76dc4161409f9d6648b8ff4133068
Author:     Gülşah Köse <gulsah.kose at collabora.com>
AuthorDate: Tue Sep 1 20:15:30 2020 +0300
Commit:     Gülşah Köse <gulsah.kose at collabora.com>
CommitDate: Tue Sep 1 23:17:03 2020 +0200

    Revert "wsd: parse headers with Poco::MessageHeader"
    
    This reverts commit dbc562d9abc997b196fd6d4e5e71f42d442488d0.
    
    tst-05694-05694 2020-08-26 12:59:14.343136 [ unittest ]
    ERR Invalid HTTP header [def]: Malformed message:
    Field name too long/no colon found| ../common/Util.cpp:980
    
    Following part of the code tests a request with corrupted http header:
        Authorization auth2(Authorization::Type::Header, "def");
        Poco::Net::HTTPRequest req2;
        auth2.authorizeRequest(req2);
        LOK_ASSERT(!req2.has("Authorization"));
    
    Poco library throws exception.
    
    Change-Id: Ic31a80c0e1e325de27c23059e2bcb3f00d39ad16
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/101887
    Tested-by: Jenkins
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Gülşah Köse <gulsah.kose at collabora.com>

diff --git a/common/Authorization.cpp b/common/Authorization.cpp
index 5613aa91a..93c5704fc 100644
--- a/common/Authorization.cpp
+++ b/common/Authorization.cpp
@@ -45,15 +45,42 @@ void Authorization::authorizeRequest(Poco::Net::HTTPRequest& request) const
     switch (_type)
     {
         case Type::Token:
-            Util::setHttpHeaders(request, "Authorization: Bearer " + _data);
-            assert(request.has("Authorization") && "HTTPRequest missing Authorization header");
+            request.set("Authorization", "Bearer " + _data);
             break;
         case Type::Header:
+        {
             // there might be more headers in here; like
             //   Authorization: Basic ....
             //   X-Something-Custom: Huh
-            Util::setHttpHeaders(request, _data);
+            // Split based on \n's or \r's and trim, to avoid nonsense in the
+            // headers
+            StringVector tokens(Util::tokenizeAnyOf(_data, "\n\r"));
+            for (auto it = tokens.begin(); it != tokens.end(); ++it)
+            {
+                std::string token = tokens.getParam(*it);
+
+                size_t separator = token.find_first_of(':');
+                if (separator != std::string::npos)
+                {
+                    size_t headerStart = token.find_first_not_of(' ', 0);
+                    size_t headerEnd = token.find_last_not_of(' ', separator - 1);
+
+                    size_t valueStart = token.find_first_not_of(' ', separator + 1);
+                    size_t valueEnd = token.find_last_not_of(' ');
+
+                    // set the header
+                    if (headerStart != std::string::npos && headerEnd != std::string::npos &&
+                            valueStart != std::string::npos && valueEnd != std::string::npos)
+                    {
+                        size_t headerLength = headerEnd - headerStart + 1;
+                        size_t valueLength = valueEnd - valueStart + 1;
+
+                        request.set(token.substr(headerStart, headerLength), token.substr(valueStart, valueLength));
+                    }
+                }
+            }
             break;
+        }
         default:
             // assert(false);
             throw BadRequestException("Invalid HTTP request type");
diff --git a/common/Util.cpp b/common/Util.cpp
index f1cd61b69..e0ce00250 100644
--- a/common/Util.cpp
+++ b/common/Util.cpp
@@ -59,7 +59,7 @@
 #include <Poco/Util/Application.h>
 
 #include "Common.hpp"
-#include <common/Log.hpp>
+#include "Log.hpp"
 #include "Protocol.hpp"
 #include "Util.hpp"
 
@@ -953,38 +953,6 @@ namespace Util
         return std::ctime(&t);
     }
 
-    void setHttpHeaders(Poco::Net::HTTPRequest& request, const std::string& headers)
-    {
-        // Look for either \r or \n and replace them with a single \r\n
-        // as prescribed by rfc2616 as valid header delimeter, removing
-        // any invalid line breaks, to avoid nonsense in the headers.
-        const StringVector tokens = Util::tokenizeAnyOf(headers, "\n\r");
-        const std::string header = tokens.cat("\r\n", 0);
-        try
-        {
-            // Now parse to preserve folded headers and other
-            // corner cases that is conformant to the rfc,
-            // detecting any errors and/or invalid entries.
-            // NB: request.read() expects full message and will fail.
-            Poco::Net::MessageHeader msgHeader;
-            std::istringstream iss(header);
-            msgHeader.read(iss);
-            for (const auto& entry : msgHeader)
-            {
-                // Set each header entry.
-                request.set(Util::trimmed(entry.first), Util::trimmed(entry.second));
-            }
-        }
-        catch (const Poco::Exception& ex)
-        {
-            LOG_ERR("Invalid HTTP header [" << header << "]: " << ex.displayText());
-        }
-        catch (const std::exception& ex)
-        {
-            LOG_ERR("Invalid HTTP header [" << header << "]: " << ex.what());
-        }
-    }
-
     bool isFuzzing()
     {
 #if LIBFUZZER
diff --git a/common/Util.hpp b/common/Util.hpp
index bb81cfb75..9dbfebe8b 100644
--- a/common/Util.hpp
+++ b/common/Util.hpp
@@ -35,7 +35,6 @@
 #include <Poco/File.h>
 #include <Poco/Path.h>
 #include <Poco/RegularExpression.h>
-#include <Poco/Net/HTTPRequest.h>
 
 #define LOK_USE_UNSTABLE_API
 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
@@ -1083,12 +1082,6 @@ int main(int argc, char**argv)
     /// conversion from steady_clock for debugging / tracing
     std::string getSteadyClockAsString(const std::chrono::steady_clock::time_point &time);
 
-    /// Set the request header by splitting multiple entries by \r or \n.
-    /// Needed to sanitize user-provided http headers, after decoding.
-    /// This is much more tolerant to line breaks than the rfc allows.
-    /// Note: probably should move to a more appropriate home.
-    void setHttpHeaders(Poco::Net::HTTPRequest& request, const std::string& headers);
-
     /// Automatically execute code at end of current scope.
     /// Used for exception-safe code.
     class ScopeGuard
commit 35cc34c420995b89ecafc9bc16f2d973a2a5dc29
Author:     Pranam Lashkari <lpranam at collabora.com>
AuthorDate: Tue Sep 1 22:10:43 2020 +0530
Commit:     Pranam Lashkari <lpranam at collabora.com>
CommitDate: Tue Sep 1 20:06:24 2020 +0200

    leaflet: updated paste shortcuts in the help menu
    
    Change-Id: Ie151debcbe169f59e662512448f4df5372cf52a6
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/101886
    Tested-by: Jenkins
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Pranam Lashkari <lpranam at collabora.com>

diff --git a/loleaflet/html/loleaflet-help.html b/loleaflet/html/loleaflet-help.html
index c81924837..44b62d72d 100644
--- a/loleaflet/html/loleaflet-help.html
+++ b/loleaflet/html/loleaflet-help.html
@@ -23,7 +23,8 @@
             <tr> <td class="function">Undo</td> <td class="shortcut">Ctrl + Z</td> </tr>
             <tr> <td class="function">Redo</td> <td class="shortcut">Ctrl + Y</td> </tr>
             <tr> <td class="function">Cut</td> <td class="shortcut">Ctrl + X</td> </tr>
-            <tr> <td class="function">Paste as unformatted text</td> <td class="shortcut">Ctrl + Alt + Shift + V</td> </tr>
+            <tr> <td class="function">Paste as unformatted text</td> <td class="shortcut">Ctrl + Shift + V</td> </tr>
+            <tr> <td class="function">Paste special</td> <td class="shortcut">Ctrl + Alt + Shift + V</td> </tr>
             <tr> <td class="function">Print (Download as PDF)</td> <td class="shortcut">Ctrl + P</td> </tr>
             <tr> <td class="function">Display the Keyboard shortcuts help</td> <td class="shortcut">Ctrl + Shift + ?</td> </tr>
         </table>
@@ -299,7 +300,7 @@
     <p>To continue editing, click on the document and the layover and message disappear. Any changes that may have been made by other users – while collaboratively editing the document – are re-loaded.</p>
     <h4>Pasting</h4>
     <p>When you paste content copied from within the same document, the format and elements are maintained. If you copy from another document, in another tab or browser window, or from outside of the browser, the pasted content will preserve rich text.</p>
-    <p>You can paste as unformatted text with the keyboard shortcut: <span class="kbd">Ctrl</span> + <span class="kbd">Alt</span> + <span class="kbd">Shift</span> + <span class="kbd">V</span></p>
+    <p>You can paste as unformatted text with the keyboard shortcut: <span class="kbd">Ctrl</span> + <span class="kbd">Shift</span> + <span class="kbd">V</span></p>
     <p>When you paste text from within the document, formatting will be respected. You can also paste objects, such as images, if they are copied from the document you are working in.</p>
     <p>When you paste text from outside of the document (another browser window or a desktop application, it will be pasted as rich text.</p>
     <p>When you have internal cut or copied content, you can paste this content using the context menu.</p>
commit 457fc3d538aed7bc2bc41fd022399749f4c5a3e4
Author:     Pranam Lashkari <lpranam at collabora.com>
AuthorDate: Tue Aug 18 19:33:13 2020 +0530
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Tue Sep 1 18:23:03 2020 +0200

    clipboard: leaflet: unformatted paste shortcut changed
    
    Problems:
    1: Browsers hard-code ctrl+shift+v as "paste without formatting" - ie. plain text
    We need access to the clipboard to get the rich data needed for paste-special,
    which we can only get security context /  access to with a ctrl-v keypress
    2: we cannot directly access the clipboard data with ctrl+shift+alt+v
    
    Solution:
    Externally copied data could not be pasted directly with paste special
    and unformatted paste due to no access to the clipboard data
    
    To access the data copied externally we rely on user to trigger paste event
    We use default browser shortcut for unformatted paste(ctrl+shift+v)
    this triggers a paste event
    
    for paste special we ask user to press ctrl+v with a popup and then
    if that popup is open and paste event is triggered we trigger paste special
    
    New shortcuts:
    Paste: ctrl+v
    unformatted Paste: ctrl+shift+v
    Paste special: ctrl+shift+alt+v
    
    Change-Id: Ib15c701f5e03123cb91e36d1c1d64f0c12aa9cfb
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/100927
    Tested-by: Jenkins
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Michael Meeks <michael.meeks at collabora.com>

diff --git a/loleaflet/src/core/Util.js b/loleaflet/src/core/Util.js
index 596dfa756..b2900b62b 100644
--- a/loleaflet/src/core/Util.js
+++ b/loleaflet/src/core/Util.js
@@ -207,6 +207,23 @@ L.Util = {
 		context.font = font;
 		var metrics = context.measureText(text);
 		return Math.floor(metrics.width);
+	},
+
+	replaceCtrlInMac: function(msg) {
+		if (navigator.appVersion.indexOf('Mac') != -1 || navigator.userAgent.indexOf('Mac') != -1) {
+			var ctrl = /Ctrl/g;
+			if (String.locale.startsWith('de') || String.locale.startsWith('dsb') || String.locale.startsWith('hsb')) {
+				ctrl = /Strg/g;
+			}
+			if (String.locale.startsWith('lt')) {
+				ctrl = /Vald/g;
+			}
+			if (String.locale.startsWith('sl')) {
+				ctrl = /Krmilka/g;
+			}
+			return msg.replace(ctrl, '⌘');
+		}
+		return msg;
 	}
 };
 
diff --git a/loleaflet/src/map/Clipboard.js b/loleaflet/src/map/Clipboard.js
index ede448410..85925f4a2 100644
--- a/loleaflet/src/map/Clipboard.js
+++ b/loleaflet/src/map/Clipboard.js
@@ -264,8 +264,15 @@ L.Clipboard = L.Class.extend({
 				that._doAsyncDownload(
 					'POST', dest, formData,
 					function() {
-						console.log('up-load done, now paste');
-						that._map._socket.sendMessage('uno .uno:Paste');
+						if (this.pasteSpecialVex && this.pasteSpecialVex.isOpen) {
+							vex.close(this.pasteSpecialVex);
+							console.log('up-load done, now paste special');
+							that.map._socket.sendMessage('uno .uno:PasteSpecial');
+						} else {
+							console.log('up-load done, now paste');
+							that._map._socket.sendMessage('uno .uno:Paste');
+						}
+
 					},
 					function(progress) { return 50 + progress/2; }
 				);
@@ -292,8 +299,15 @@ L.Clipboard = L.Class.extend({
 				that._doAsyncDownload(
 					'POST', dest, formData,
 					function() {
-						console.log('up-load of fallback done, now paste');
-						that._map._socket.sendMessage('uno .uno:Paste');
+						if (this.pasteSpecialVex && this.pasteSpecialVex.isOpen) {
+							vex.close(this.pasteSpecialVex);
+							console.log('up-load of fallback done, now paste special');
+							that.map._socket.sendMessage('uno .uno:PasteSpecial');
+						} else {
+							console.log('up-load of fallback done, now paste');
+							that._map._socket.sendMessage('uno .uno:Paste');
+						}
+
 					},
 					function(progress) { return 50 + progress/2; },
 					function() {
@@ -665,6 +679,9 @@ L.Clipboard = L.Class.extend({
 			// paste into dialog
 			var KEY_PASTE = 1299;
 			map._textInput._sendKeyEvent(0, KEY_PASTE);
+		} else if (this.pasteSpecialVex && this.pasteSpecialVex.isOpen) {
+			this.pasteSpecialVex.close();
+			map._socket.sendMessage('uno .uno:PasteSpecial');
 		} else {
 			// paste into document
 			map._socket.sendMessage('uno .uno:Paste');
@@ -678,7 +695,7 @@ L.Clipboard = L.Class.extend({
 	paste: function(ev) {
 		console.log('Paste');
 
-		if (isAnyVexDialogActive() && !this._map.hasFocus())
+		if (isAnyVexDialogActive() && !(this.pasteSpecialVex && this.pasteSpecialVex.isOpen))
 			return;
 
 		if ($('.annotation-active').length > 0 && !this._map.hasFocus())
@@ -821,19 +838,7 @@ L.Clipboard = L.Class.extend({
 			msg = _('<p>Please use the copy/paste buttons on your on-screen keyboard.</p>');
 		} else {
 			msg = _('<p>Your browser has very limited access to the clipboard, so use these keyboard shortcuts:<ul><li><b>Ctrl+C</b>: For copying.</li><li><b>Ctrl+X</b>: For cutting.</li><li><b>Ctrl+V</b>: For pasting.</li></ul></p>');
-			if (navigator.appVersion.indexOf('Mac') != -1 || navigator.userAgent.indexOf('Mac') != -1) {
-				var ctrl = /Ctrl/g;
-				if (String.locale.startsWith('de') || String.locale.startsWith('dsb') || String.locale.startsWith('hsb')) {
-					ctrl = /Strg/g;
-				}
-				if (String.locale.startsWith('lt')) {
-					ctrl = /Vald/g;
-				}
-				if (String.locale.startsWith('sl')) {
-					ctrl = /Krmilka/g;
-				}
-				msg = msg.replace(ctrl, '⌘');
-			}
+			msg = L.Util.replaceCtrlInMac(msg);
 		}
 		vex.dialog.alert({
 			unsafeMessage: msg,
diff --git a/loleaflet/src/map/handler/Map.Keyboard.js b/loleaflet/src/map/handler/Map.Keyboard.js
index c3f578612..dd735ab5f 100644
--- a/loleaflet/src/map/handler/Map.Keyboard.js
+++ b/loleaflet/src/map/handler/Map.Keyboard.js
@@ -7,6 +7,8 @@
  * at TextInput.
  */
 
+ /* global vex _ */
+
 L.Map.mergeOptions({
 	keyboard: true,
 	keyboardPanOffset: 20,
@@ -416,6 +418,29 @@ L.Map.Keyboard = L.Handler.extend({
 			return true;
 		}
 
+		// Handles paste special
+		if (e.ctrlKey && e.shiftKey && e.altKey && (e.key === 'v' || e.key === 'V')) {
+			var map = this._map;
+			var msg = _('<p>Your browser has very limited access to the clipboard, so now press:</li><li><b>Ctrl+V</b>: To open paste special menu.</li></ul></p><p>Close popup to ignore paste special</p>');
+			msg = L.Util.replaceCtrlInMac(msg);
+			this._map._clip.pasteSpecialVex = vex.open({
+				unsafeContent: msg,
+				showCloseButton: true,
+				escapeButtonCloses: true,
+				overlayClosesOnClick: false,
+				buttons: {},
+				afterOpen: function() {
+					map.focus();
+				}
+			});
+			return true;
+		}
+
+		// Handles unformatted paste
+		if (e.ctrlKey && e.shiftKey && (e.key === 'v' || e.key === 'V')) {
+			return true;
+		}
+
 		if (e.ctrlKey && (e.key === 'k' || e.key === 'K')) {
 			this._map.showHyperlinkDialog();
 			e.preventDefault();
commit 3df718aac72138849ecafadc13f0207c3ec52283
Author:     Tor Lillqvist <tml at collabora.com>
AuthorDate: Tue Sep 1 16:58:17 2020 +0300
Commit:     Tor Lillqvist <tml at collabora.com>
CommitDate: Tue Sep 1 17:24:36 2020 +0200

    tdf#133284: Improve hardware and on-screen keyboard in the iOS app
    
    This is a quite complicated change that should both fix tdf#133284
    (cursor keys on a hardware keyboard do not work in a spreadsheet
    document) and also improve the interaction with
    CollaboraOnlineWebViewKeyboardManager that manages the on-screen
    keyboard. We need to jump through complicated hoops in order to get
    the hardware cursor keys handled right after loading a spreadsheet
    document.
    
    In the CollaboraOnlineWebViewKeyboardManager case we try harder to
    keep loleaflet's _textArea buffer in sync with what the UITextView in
    CollaboraOnlineWebViewKeyboardManager uses to provide suggestions

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list