Rebased ref, commits from common ancestor: commit f59a9cf7fca78c1d8095a8d020862a6203c03b33 Author: Michael Meeks <michael.me...@collabora.com> AuthorDate: Tue Sep 1 21:35:35 2020 +0100 Commit: Jan Holesovsky <ke...@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.me...@collabora.com> AuthorDate: Tue Sep 1 16:53:02 2020 +0100 Commit: Jan Holesovsky <ke...@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.me...@collabora.com> AuthorDate: Tue Sep 1 16:52:35 2020 +0100 Commit: Jan Holesovsky <ke...@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.me...@collabora.com> AuthorDate: Tue Sep 1 16:24:18 2020 +0100 Commit: Jan Holesovsky <ke...@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 <ke...@collabora.com> AuthorDate: Tue Sep 1 01:39:07 2020 +0200 Commit: Jan Holesovsky <ke...@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.me...@collabora.com> AuthorDate: Fri Aug 28 16:05:04 2020 +0100 Commit: Jan Holesovsky <ke...@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 <ke...@collabora.com> AuthorDate: Wed Aug 26 15:15:55 2020 +0200 Commit: Jan Holesovsky <ke...@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.fran...@collabora.com> AuthorDate: Tue Aug 25 12:01:51 2020 +0530 Commit: Jan Holesovsky <ke...@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.fran...@collabora.com> AuthorDate: Tue Aug 25 11:55:25 2020 +0530 Commit: Jan Holesovsky <ke...@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.fran...@collabora.com> AuthorDate: Tue Aug 25 11:36:03 2020 +0530 Commit: Jan Holesovsky <ke...@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.fran...@collabora.com> AuthorDate: Mon Aug 24 22:34:27 2020 +0530 Commit: Jan Holesovsky <ke...@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.fran...@collabora.com> AuthorDate: Mon Aug 24 22:31:16 2020 +0530 Commit: Jan Holesovsky <ke...@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.fran...@collabora.com> AuthorDate: Mon Aug 24 22:12:18 2020 +0530 Commit: Jan Holesovsky <ke...@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.fran...@collabora.com> AuthorDate: Mon Aug 24 16:49:47 2020 +0530 Commit: Jan Holesovsky <ke...@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.fran...@collabora.com> AuthorDate: Mon Aug 24 15:50:44 2020 +0530 Commit: Jan Holesovsky <ke...@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.fran...@collabora.com> AuthorDate: Mon Aug 24 15:34:30 2020 +0530 Commit: Jan Holesovsky <ke...@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.me...@collabora.com> AuthorDate: Fri Aug 21 20:43:47 2020 +0100 Commit: Jan Holesovsky <ke...@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.me...@collabora.com> AuthorDate: Fri Aug 21 16:40:29 2020 +0100 Commit: Jan Holesovsky <ke...@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.me...@collabora.com> AuthorDate: Fri Aug 21 15:54:50 2020 +0100 Commit: Jan Holesovsky <ke...@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.me...@collabora.com> AuthorDate: Fri Aug 21 15:47:58 2020 +0100 Commit: Jan Holesovsky <ke...@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.vie...@collabora.com> AuthorDate: Tue Sep 1 07:22:23 2020 -0300 Commit: Aron Budea <aron.bu...@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 <jenkinscollaboraoff...@gmail.com> Reviewed-by: Tor Lillqvist <t...@collabora.com> Reviewed-by: Aron Budea <aron.bu...@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 <lpra...@collabora.com> AuthorDate: Wed Aug 26 22:53:37 2020 +0530 Commit: Andras Timar <andras.ti...@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 <jenkinscollaboraoff...@gmail.com> Reviewed-by: Andras Timar <andras.ti...@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.k...@collabora.com> AuthorDate: Wed Sep 2 09:36:52 2020 +0200 Commit: Szymon Kłos <szymon.k...@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.k...@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.k...@collabora.com> AuthorDate: Tue Sep 1 20:15:30 2020 +0300 Commit: Gülşah Köse <gulsah.k...@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 <jenkinscollaboraoff...@gmail.com> Reviewed-by: Gülşah Köse <gulsah.k...@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 <lpra...@collabora.com> AuthorDate: Tue Sep 1 22:10:43 2020 +0530 Commit: Pranam Lashkari <lpra...@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 <jenkinscollaboraoff...@gmail.com> Reviewed-by: Pranam Lashkari <lpra...@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 <lpra...@collabora.com> AuthorDate: Tue Aug 18 19:33:13 2020 +0530 Commit: Michael Meeks <michael.me...@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 <jenkinscollaboraoff...@gmail.com> Reviewed-by: Michael Meeks <michael.me...@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 <t...@collabora.com> AuthorDate: Tue Sep 1 16:58:17 2020 +0300 Commit: Tor Lillqvist <t...@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 _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits