From 61f8a47b6e1636575f9fb2cde32bbe5e0bf01438 Mon Sep 17 00:00:00 2001 From: Sahil Gautam Date: Thu, 19 Mar 2026 09:51:17 +0530 Subject: [PATCH 01/10] dynamic-zoom: set 200% as the upper limit on dynamic zoom in impress Signed-off-by: Sahil Gautam Change-Id: I47947b30be54333fe67c52166ea58d22e0d91ed9 --- browser/src/layer/tile/CanvasTileLayer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/src/layer/tile/CanvasTileLayer.js b/browser/src/layer/tile/CanvasTileLayer.js index 510ce25aecfb8..a35a2398b39e9 100644 --- a/browser/src/layer/tile/CanvasTileLayer.js +++ b/browser/src/layer/tile/CanvasTileLayer.js @@ -3280,7 +3280,7 @@ window.L.CanvasTileLayer = window.L.Layer.extend({ } if (!maxZoom) { - if (this.isImpress()) maxZoom = 10; + if (this.isImpress()) maxZoom = 14; else if (this.isWriter()) maxZoom = 13; } From f43649e52124b2ba7496d64260c8eda1a4712976 Mon Sep 17 00:00:00 2001 From: Sahil Gautam Date: Thu, 19 Mar 2026 10:09:57 +0530 Subject: [PATCH 02/10] dynamic-zoom: implement dynamic zoom for impress For writer, it was quite simple, we just had width to account for and compare against. But in impress we have to consider both the width and the height of the slide and based on that decide on a zoom level at which the whole slide is fully visible. Initially the idea was to adjust the document dimensions to have 8% to 14% margins in extreme cases and calculate the zoom. But turns out the zoom averages around 100% in that case because as the window size increased, so did the margins (percentage) and the ratio balanced itself. So we settled on a fixed margin. 50px works quite nicely, 25px on each side. Signed-off-by: Sahil Gautam Change-Id: Ieb7bb02b707cb3b0351f2fc3badcd7e02077502e --- browser/src/layer/tile/CanvasTileLayer.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/browser/src/layer/tile/CanvasTileLayer.js b/browser/src/layer/tile/CanvasTileLayer.js index a35a2398b39e9..32e2ba50643f7 100644 --- a/browser/src/layer/tile/CanvasTileLayer.js +++ b/browser/src/layer/tile/CanvasTileLayer.js @@ -3264,7 +3264,7 @@ window.L.CanvasTileLayer = window.L.Layer.extend({ }, recalculateZoomOnResize: function() { - if (this.isWriter()) + if (this.isWriter() || this.isImpress()) this._invalidateZoomFirstFit = true; }, @@ -3318,11 +3318,32 @@ window.L.CanvasTileLayer = window.L.Layer.extend({ const commentWidth = app.sectionContainer.getSectionWithName(app.CSections.CommentList.name).sectionProperties.commentWidth; let documentWidth = app.activeDocument.fileSize.pX; + let documentHeight = app.activeDocument.fileSize.pY; if (bringCommentsIntoView) documentWidth += commentWidth; var ratio = newSize.x / documentWidth; var zoom = this._map.getScaleZoom(ratio); + if (!this._firstFitDone && this.isImpress()) { + /* assume that this is the range of diagonal sizes we are going to get + * for the window and find where the current window size lies in these, + * clamping it between [0,1]. then find appropriate margin between 4-9% + * based on the window size factor. */ + const MAX_DIAGONAL = 2200; + const MIN_DIAGONAL = 1000; + const diagonal = Math.sqrt(window.innerWidth * window.innerWidth + window.innerHeight + window.innerHeight); + const factor = clamp((diagonal - MIN_DIAGONAL) / (MAX_DIAGONAL - MIN_DIAGONAL), 0, 1); + + const percentMargin = 0.04 + factor * (0.09 - 0.04); + const availW = newSize.x * (1 - percentMargin); + const availH = newSize.y * (1 - percentMargin); + + const xRatio = availW / documentWidth; + const yRatio = availH / documentHeight; + ratio = Math.min(xRatio, yRatio); + zoom = this._map.getScaleZoom(ratio); + } + if (maxZoom) zoom = Math.min(maxZoom, Math.max(0.1, zoom)); From cdea8241eab50970e950ecf42188530c3a465811 Mon Sep 17 00:00:00 2001 From: Sahil Gautam Date: Thu, 19 Mar 2026 10:13:22 +0530 Subject: [PATCH 03/10] dynamic-zoom: add button to the impress statusbar & use appropriate label Signed-off-by: Sahil Gautam Change-Id: Ice381c5da2eebb33c804fc1eefdd9f7e6310aade --- browser/src/control/Control.StatusBar.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/browser/src/control/Control.StatusBar.js b/browser/src/control/Control.StatusBar.js index a9e3cb89bc7e7..617f0f2055679 100644 --- a/browser/src/control/Control.StatusBar.js +++ b/browser/src/control/Control.StatusBar.js @@ -241,6 +241,10 @@ class StatusBar extends JSDialog.Toolbar { } getToolItems() { + let fitWidthZoomLabel = this.map.getDocType() === 'presentation' + ? _('Zoom to Fit Slide') + : _('Zoom to Fit Page Width'); + return [ this._generateHtmlItem('statusdocpos'), // spreadsheet this._generateHtmlItem('rowcolselcount', 1), // spreadsheet @@ -272,7 +276,7 @@ class StatusBar extends JSDialog.Toolbar { configLabel: _('Overview'), configPeers: ['overviewbreak']}, {type: 'separator', id: 'overviewbreak', orientation: 'vertical', dataPriority: 9, visible: !app.isReadOnly()}, ].concat(window.mode.isTablet() ? [] : [ - {type: 'customtoolitem', id: 'fitwidthzoom', command: 'fitwidthzoom', text: _('Zoom to Fit Page Width'), icon: 'pagewidth.svg', dataPriority: 8, visible: false}, + {type: 'customtoolitem', id: 'fitwidthzoom', command: 'fitwidthzoom', text: fitWidthZoomLabel, icon: 'pagewidth.svg', dataPriority: 8, visible: false}, {type: 'customtoolitem', id: 'zoomreset', command: 'zoomreset', text: _('Reset zoom'), icon: 'zoomreset.svg', dataPriority: 8}, {type: 'customtoolitem', id: 'zoomout', command: 'zoomout', text: _UNO('.uno:ZoomMinus'), icon: 'minus.svg'}, {type: 'menubutton', id: 'zoom', text: '100', selected: 'zoom100', menu: this._generateZoomItems(), image: false}, @@ -449,6 +453,8 @@ class StatusBar extends JSDialog.Toolbar { this.showItem('languagestatusbreak', !app.map.isReadOnlyMode()); this.showItem('permissionmode-container', true); this.showItem('documentstatus-container', true); + this.showItem('fitwidthzoom', true); + this.showItem('zoomreset', false); } break; case 'drawing': From b017487e74dc6592b1e55db3f80fef89c07a1555 Mon Sep 17 00:00:00 2001 From: Sahil Gautam Date: Thu, 19 Mar 2026 10:29:26 +0530 Subject: [PATCH 04/10] dynamic-zoom: modify cypress_test to test dynamic zoom instead of zoom reset Signed-off-by: Sahil Gautam Change-Id: I09a75357a10b8ecb0df49322c4d67022f6bc5eaf --- .../desktop/impress/statusbar_spec.js | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/cypress_test/integration_tests/desktop/impress/statusbar_spec.js b/cypress_test/integration_tests/desktop/impress/statusbar_spec.js index a6b9dea4f4d16..4dbd0f7d2321e 100644 --- a/cypress_test/integration_tests/desktop/impress/statusbar_spec.js +++ b/cypress_test/integration_tests/desktop/impress/statusbar_spec.js @@ -10,7 +10,7 @@ describe(['tagdesktop', 'tagnextcloud', 'tagproxy'], 'Statusbar tests.', functio if (Cypress.env('INTEGRATION') === 'nextcloud') { desktopHelper.showStatusBarIfHidden (); } - desktopHelper.shouldHaveZoomLevel('70'); + cy.viewport(1920, 1080); }); it('Selected slide.', function() { @@ -22,7 +22,7 @@ describe(['tagdesktop', 'tagnextcloud', 'tagproxy'], 'Statusbar tests.', functio }); it('Change zoom level.', function() { - desktopHelper.resetZoomLevel(); + desktopHelper.fitWidthZoom(); desktopHelper.shouldHaveZoomLevel('100'); desktopHelper.zoomIn(); desktopHelper.shouldHaveZoomLevel('120'); @@ -31,9 +31,23 @@ describe(['tagdesktop', 'tagnextcloud', 'tagproxy'], 'Statusbar tests.', functio }); it('Select zoom level.', function() { - desktopHelper.resetZoomLevel(); + desktopHelper.fitWidthZoom(); desktopHelper.shouldHaveZoomLevel('100'); desktopHelper.selectZoomLevel('280', false); desktopHelper.shouldHaveZoomLevel('280'); }); + + it('Dynamic Zoom', function () { + desktopHelper.fitWidthZoom(); + desktopHelper.shouldHaveZoomLevel('100'); + + cy.viewport(1420, 1080); + desktopHelper.fitWidthZoom(); + desktopHelper.shouldHaveZoomLevel('70'); + + desktopHelper.zoomIn(); + desktopHelper.zoomIn(); + desktopHelper.fitWidthZoom(); + desktopHelper.shouldHaveZoomLevel('70'); + }); }); From 42fca694a9bd03f2dd197539e7197c317078d446 Mon Sep 17 00:00:00 2001 From: Sahil Gautam Date: Thu, 19 Mar 2026 11:53:57 +0530 Subject: [PATCH 05/10] chore: set expected zoom level in the test to avoid failure Signed-off-by: Sahil Gautam Change-Id: I67cd1a1d86b22a93eca73afea271d0bf369b890e --- .../integration_tests/desktop/impress/editable_area_spec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cypress_test/integration_tests/desktop/impress/editable_area_spec.js b/cypress_test/integration_tests/desktop/impress/editable_area_spec.js index 17187a9e82abd..c1a3177b4480f 100644 --- a/cypress_test/integration_tests/desktop/impress/editable_area_spec.js +++ b/cypress_test/integration_tests/desktop/impress/editable_area_spec.js @@ -2,6 +2,7 @@ var helper = require('../../common/helper'); var impressHelper = require('../../common/impress_helper'); +var desktopHelper = require('../../common/desktop_helper'); var ceHelper = require('../../common/contenteditable_helper'); function selectTextShape(i) { @@ -33,6 +34,7 @@ describe(['taga11yenabled'], 'Editable area - Basic typing and caret moving', fu // do not cover shapes with sidebar cy.cGet('#sidebar-panel').should('not.be.visible'); cy.cGet('div.clipboard').as('clipboard'); + desktopHelper.selectZoomLevel('60', false); }); it.skip('Editing top text shape', function () { From f6fdcfcba10a0eb542b37edef7651b63b1cbb300 Mon Sep 17 00:00:00 2001 From: Sahil Gautam Date: Wed, 25 Mar 2026 12:13:05 +0530 Subject: [PATCH 06/10] dynamic-zoom: recalculate slide zoom on resize Previous patches handled impress similar to writer, but both are quite different. In impress we want the slide to change zoom (size) as we resize the window. Signed-off-by: Sahil Gautam Change-Id: I03693e622682fb718d89d4b597806d7e54674b4b --- browser/src/layer/tile/CanvasTileLayer.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/browser/src/layer/tile/CanvasTileLayer.js b/browser/src/layer/tile/CanvasTileLayer.js index 32e2ba50643f7..60ee99660aacd 100644 --- a/browser/src/layer/tile/CanvasTileLayer.js +++ b/browser/src/layer/tile/CanvasTileLayer.js @@ -3264,6 +3264,8 @@ window.L.CanvasTileLayer = window.L.Layer.extend({ }, recalculateZoomOnResize: function() { + /* in impress, we always recalculate zoom on resize to keep the slide in-view. + * so the second condition is unnecessary, but we keep it for consistency. */ if (this.isWriter() || this.isImpress()) this._invalidateZoomFirstFit = true; }, @@ -3303,6 +3305,8 @@ window.L.CanvasTileLayer = window.L.Layer.extend({ bringCommentsIntoView = true; this._includedCommentsInFirstFit = true; this._firstFitDone = false; + } else if (this.isImpress()) { + recalcFirstFit = true; } // `recalcFirstFit` is used to recalculate/reset the zoom levels to the @@ -3324,7 +3328,7 @@ window.L.CanvasTileLayer = window.L.Layer.extend({ var ratio = newSize.x / documentWidth; var zoom = this._map.getScaleZoom(ratio); - if (!this._firstFitDone && this.isImpress()) { + if (this.isImpress()) { /* assume that this is the range of diagonal sizes we are going to get * for the window and find where the current window size lies in these, * clamping it between [0,1]. then find appropriate margin between 4-9% From b4e3e6771314e81f2d9bde1f9bf3ad60e4dbef76 Mon Sep 17 00:00:00 2001 From: Sahil Gautam Date: Mon, 20 Apr 2026 17:04:30 +0530 Subject: [PATCH 07/10] dynamic-zoom: call _fixWidthZoom from recalculateZoomOnResize Previously this function set a flag and relied on the 'resize' events to call `_fixWidthZoom` which then checked this flag and recalculated the zoom. But since `_fixWidthZoom` is no longer triggered by 'resize' (as we removed that a few commits back), we need to explicitly call `_fixWidthZoom` here. Signed-off-by: Sahil Gautam Change-Id: I5391744b6f1210dd897262935967fc2cff03e826 --- browser/src/layer/tile/CanvasTileLayer.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/browser/src/layer/tile/CanvasTileLayer.js b/browser/src/layer/tile/CanvasTileLayer.js index 60ee99660aacd..c8e20bbba6ab9 100644 --- a/browser/src/layer/tile/CanvasTileLayer.js +++ b/browser/src/layer/tile/CanvasTileLayer.js @@ -3266,8 +3266,10 @@ window.L.CanvasTileLayer = window.L.Layer.extend({ recalculateZoomOnResize: function() { /* in impress, we always recalculate zoom on resize to keep the slide in-view. * so the second condition is unnecessary, but we keep it for consistency. */ - if (this.isWriter() || this.isImpress()) + if (this.isWriter() || this.isImpress()) { this._invalidateZoomFirstFit = true; + this._fitWidthZoom(); + } }, // This is really just called on zoomend From 2870d17861244f13ef7c4380503aa8d536a4a882 Mon Sep 17 00:00:00 2001 From: Sahil Gautam Date: Tue, 7 Apr 2026 17:35:02 +0530 Subject: [PATCH 08/10] chore: adjust image_operation_spec to match the new behavior The slide's zoom isn't static anymore, i.e. if the sidebar shows up, the slide zooms out, therefore the dimensions of the image on the slide will also be different. Signed-off-by: Sahil Gautam Change-Id: I6753484048e412e098946923c13734cd6ba0bdd3 --- .../integration_tests/desktop/impress/image_operation_spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cypress_test/integration_tests/desktop/impress/image_operation_spec.js b/cypress_test/integration_tests/desktop/impress/image_operation_spec.js index 062b0e01fd26a..a2c58309dc166 100644 --- a/cypress_test/integration_tests/desktop/impress/image_operation_spec.js +++ b/cypress_test/integration_tests/desktop/impress/image_operation_spec.js @@ -87,7 +87,7 @@ describe(['tagdesktop'], 'Image Operation Tests', function() { triggerNewSVGForShapeInTheCenter(); - helper.assertImageSize(463, 185); + helper.assertImageSize(322, 129); //Keep ratio checked //sidebar needs more time @@ -101,6 +101,6 @@ describe(['tagdesktop'], 'Image Operation Tests', function() { triggerNewSVGForShapeInTheCenter(); - helper.assertImageSize(579, 232); + helper.assertImageSize(402, 161); }); }); From 0d1dff4933176734bd14c0de6c0c5c14c5d788a5 Mon Sep 17 00:00:00 2001 From: Sahil Gautam Date: Tue, 7 Apr 2026 22:09:58 +0530 Subject: [PATCH 09/10] chore: fix typo Signed-off-by: Sahil Gautam Change-Id: Id0eca9f16fe7c06b1c741b96522ce4d969ec2c50 --- .../integration_tests/desktop/writer/annotation_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress_test/integration_tests/desktop/writer/annotation_spec.js b/cypress_test/integration_tests/desktop/writer/annotation_spec.js index ed8f11ba2685c..5da81ebf309d2 100644 --- a/cypress_test/integration_tests/desktop/writer/annotation_spec.js +++ b/cypress_test/integration_tests/desktop/writer/annotation_spec.js @@ -272,7 +272,7 @@ describe(['tagdesktop'], 'Annotation Tests', function() { cy.cGet('.annotation-button-delete').should('be.visible'); }); - it('Global opreations without doc focused', function () { + it('Global operations without doc focused', function () { cy.getFrameWindow().then(function (win) { cy.spy(win.app.socket, 'sendMessage').as('sendMessage'); }); From 29237449773523949fba1cf5c07930662ec5fcce Mon Sep 17 00:00:00 2001 From: Sahil Gautam Date: Tue, 7 Apr 2026 15:53:28 +0530 Subject: [PATCH 10/10] chore: add some time between the zoom change and check steps Since we removed the multiple resize listeners from the slides a few commits back, zoom changes sometimes (in the tests) don't change the actual zoom of the slide. There should be some time between these clicks and the "has the zoom changed" check. This gives the application enough time to change the document/slide zoom. Signed-off-by: Sahil Gautam Change-Id: If37e782e01611ceba9a2bc6eb6f05e910c2f64e7 --- cypress_test/integration_tests/common/desktop_helper.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cypress_test/integration_tests/common/desktop_helper.js b/cypress_test/integration_tests/common/desktop_helper.js index 459a5788f8289..2b4d988d73015 100644 --- a/cypress_test/integration_tests/common/desktop_helper.js +++ b/cypress_test/integration_tests/common/desktop_helper.js @@ -209,8 +209,11 @@ function selectZoomLevel(zoomLevel, makeZoomVisible = true) { // Force because sometimes the icons are scrolled off the screen to the right if (makeZoomVisible) makeZoomItemsVisible(); + cy.wait(1000); cy.cGet('#toolbar-down #zoom .arrowbackground').click(); + cy.wait(1000); cy.cGet('#zoom-dropdown').contains('.ui-combobox-entry', zoomLevel).click(); + cy.wait(1000); shouldHaveZoomLevel(zoomLevel); cy.log('<< selectZoomLevel - end');