diff --git a/packages/api/src/files/documents/html.spec.ts b/packages/api/src/files/documents/html.spec.ts
index a4bc924025..ff963e9af0 100644
--- a/packages/api/src/files/documents/html.spec.ts
+++ b/packages/api/src/files/documents/html.spec.ts
@@ -477,11 +477,23 @@ describe('Office HTML producers', () => {
const html = await _internal.pptxToHtmlViaCdn(pptx);
/* The wrapper class used by the CSS rules. */
expect(html).toContain('lc-slide-wrap');
- /* The wrap function + the scale computation derived from
- * `clientWidth / SLIDE_W`. */
+ /* The wrap function + the per-slide scale function. The scale
+ * uses each slide's actual rendered native width (not a
+ * constant) so panels wider than 960px still fill — no
+ * upscale cap means we never leave whitespace on the sides
+ * of the panel. */
expect(html).toContain('wrapSlides');
- expect(html).toContain('SLIDE_W');
- expect(html).toContain('available / SLIDE_W');
+ expect(html).toContain('scaleFor');
+ expect(html).toContain('availableWidth');
+ /* The scale formula divides available width by the slides
+ * rendered native width — this is the line that ensures full
+ * panel coverage. */
+ expect(html).toContain('availableWidth() / (nativeW || SLIDE_W)');
+ /* Negative assertion: the previous version capped the scale
+ * at 1.0 with `Math.min(1, ...)`, which left whitespace on
+ * panels wider than 960px. The new code must not reintroduce
+ * that cap. */
+ expect(html).not.toContain('Math.min(1');
/* Container is hidden during render and revealed by the
* `finalize` step so the unscaled flash never reaches the
* user. */
diff --git a/packages/api/src/files/documents/html.ts b/packages/api/src/files/documents/html.ts
index 96bc424d92..caf560e682 100644
--- a/packages/api/src/files/documents/html.ts
+++ b/packages/api/src/files/documents/html.ts
@@ -1161,10 +1161,21 @@ html, body { margin: 0; padding: 0; background: var(--bg); color: var(--fg); fon
container.style.visibility = 'hidden';
var previewer = pptxPreview.init(container, { width: SLIDE_W, height: SLIDE_H });
- function computeScale() {
- var available = (container.clientWidth || window.innerWidth) - 32;
- if (available <= 0) { available = 600; }
- return Math.min(1, available / SLIDE_W);
+ function availableWidth() {
+ /* clientWidth includes the 16px padding on each side via
+ * box-sizing:border-box; we subtract both so the wrap exactly
+ * fills the content box without spilling into padding. */
+ var w = (container.clientWidth || window.innerWidth) - 32;
+ return w > 0 ? w : 600;
+ }
+
+ /* Per-slide scale: divides each slides actual rendered native
+ * width into the panel width, so we always fill the panel. No
+ * upscale cap — pptx-preview always rasterizes against the init
+ * canvas (960×540 here), so text/charts inside the slide are
+ * vector or canvas-rendered and stay legible when scaled up. */
+ function scaleFor(nativeW) {
+ return availableWidth() / (nativeW || SLIDE_W);
}
/* Wrap each rendered slide and apply the scale. Called ONCE,
@@ -1172,19 +1183,23 @@ html, body { margin: 0; padding: 0; background: var(--bg); color: var(--fg); fon
* move slides out from under the librarys references and break
* its internal state. */
function wrapSlides() {
- var scale = computeScale();
var children = Array.prototype.slice.call(container.children);
for (var i = 0; i < children.length; i++) {
var slide = children[i];
if (!slide.classList || slide.classList.contains('lc-slide-wrap') || slide.classList.contains('lc-pptx-loading')) {
continue;
}
+ /* Cache the slides actual rendered size BEFORE applying any
+ * transform — measurements after a CSS scale no longer reflect
+ * native pixels and would feed back into wrong sizing on
+ * resize. */
var nativeW = slide.offsetWidth || SLIDE_W;
var nativeH = slide.offsetHeight || SLIDE_H;
if (slide.dataset) {
slide.dataset.lcNativeW = String(nativeW);
slide.dataset.lcNativeH = String(nativeH);
}
+ var scale = scaleFor(nativeW);
var wrap = document.createElement('div');
wrap.className = 'lc-slide-wrap';
wrap.style.width = (nativeW * scale) + 'px';
@@ -1200,7 +1215,6 @@ html, body { margin: 0; padding: 0; background: var(--bg); color: var(--fg); fon
* resize). Reads the cached native dimensions from the slides
* dataset so we never re-measure an already-scaled box. */
function refit() {
- var scale = computeScale();
var wraps = container.querySelectorAll('.lc-slide-wrap');
for (var i = 0; i < wraps.length; i++) {
var wrap = wraps[i];
@@ -1208,6 +1222,7 @@ html, body { margin: 0; padding: 0; background: var(--bg); color: var(--fg); fon
if (!inner || !inner.dataset) { continue; }
var nativeW = parseFloat(inner.dataset.lcNativeW) || SLIDE_W;
var nativeH = parseFloat(inner.dataset.lcNativeH) || SLIDE_H;
+ var scale = scaleFor(nativeW);
wrap.style.width = (nativeW * scale) + 'px';
wrap.style.height = (nativeH * scale) + 'px';
inner.style.transform = 'scale(' + scale + ')';