From d500bcaf83065b898deb96f0bb5c2b9f77adff2d Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 2 Jun 2026 21:26:03 -0400 Subject: [PATCH] book(C.5.3): paint paginated pages into reflow tile thumbnails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reflow tiles in the Pages overview now show a real composited preview: master layers underneath (text-frame outline removed — the prose fills its place visually), then the first paginated page of the chapter rendered as positioned text blocks on top. - New `paintReflowPageOverlay(host, page, frame, trimW, trimH)` helper: takes one paginated page + the master's text-frame geometry and paints each block as an absolutely-positioned div inside the thumb. Block styles (font, size, weight, leading, alignment, colour, background) all come from the engine's resolved style; coordinates convert to percent of the canvas inch-space so the overlay tracks any thumb size. - Tiles get a page-count suffix: "Chapter 1 · 14 pg". - Master layers in reflow thumbs skip any text-frame layer (its outline is meant for the editor, not the rendered output — the prose IS the frame's content at render time). Phase C.5.3 ships tile thumbnails; the chapter editor's right pane stays as an HTML preview for now. A paginated-preview toggle there lands in a later slice. --- src/index.html | 123 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 113 insertions(+), 10 deletions(-) diff --git a/src/index.html b/src/index.html index bd4e7f2..76677d7 100644 --- a/src/index.html +++ b/src/index.html @@ -8787,19 +8787,16 @@ tile.dataset.entryId = p.id; tile.dataset.index = String(idx); tile.draggable = true; - // Live thumbnail of the page's current layers — only for - // designed pages. Reflow tiles keep the striped placeholder. + const thumb = document.createElement('div'); + thumb.className = 'bookPageTileThumb'; + tile.appendChild(thumb); if (p.type === 'design') { - const thumb = document.createElement('div'); - thumb.className = 'bookPageTileThumb'; - tile.appendChild(thumb); + // Designed page thumb: master layers underneath, then the + // page's own. Both remapped to 'canvas' so the preview + // helper's flat-canvas filter accepts them. const pageTarget = `book-page:${p.id}`; const masterId = effectiveMasterIdForPage(p); const masterTarget = masterId ? `book-master:${masterId}` : null; - // Composite master layers first (so they render below - // the page's own layers) then the page's own. Both get - // their target remapped to 'canvas' so the preview - // helper's flat-canvas filter accepts them. const masterLayers = masterTarget ? (state.layers || []).filter(l => l.target === masterTarget && !l.hidden) : []; @@ -8820,6 +8817,42 @@ } catch (err) { console.warn('book page thumb render failed', err); } + } else if (p.type === 'reflow') { + // Reflow tile: same master underlay as designed pages, + // then the first paginated page of the chapter rendered + // as positioned text blocks on top. + const masterId = p.masterId || book.spec?.defaultMasterId || null; + const masterTarget = masterId ? `book-master:${masterId}` : null; + const masterLayers = masterTarget + ? (state.layers || []).filter(l => l.target === masterTarget && !l.hidden + // Don't render the text-frame outline in the thumb — + // the reflowed prose takes its place visually. + && !(l.type === 'shape' && l.shape?.kind === 'text-frame')) + : []; + const composed = masterLayers.map((l, i) => ({ ...l, target: 'canvas', z: -1e6 + i })); + try { + renderCustomArtPreviewDOM(thumb, { + customArt: { + width: trimW * 96, + height: trimH * 96, + background: p.background || book.spec?.background || '#ffffff', + }, + layers: composed, + }, { compact: true }); + } catch (err) { + console.warn('book chapter thumb (master) render failed', err); + } + // Paint the first paginated page on top, positioned at + // the master's text-frame coordinates (converted to + // percent of the thumb). + try { + const pag = paginateChapter(p, book); + if (pag.pages.length && pag.frame) { + paintReflowPageOverlay(thumb, pag.pages[0], pag.frame, trimW, trimH); + } + } catch (err) { + console.warn('book chapter thumb (text) render failed', err); + } } const numEl = document.createElement('div'); numEl.className = 'bookPageTileNum'; @@ -8859,7 +8892,13 @@ const label = document.createElement('div'); label.className = 'bookPageTileLabel'; if (p.type === 'reflow') { - label.textContent = p.chapterTitle || 'Chapter'; + let count = ''; + try { + const pag = paginateChapter(p, book); + const n = pag.pages.length; + if (n) count = ` · ${n} pg`; + } catch {} + label.textContent = (p.chapterTitle || 'Chapter') + count; } else { label.textContent = p.label || 'Designed page'; } @@ -9185,6 +9224,70 @@ return result; } + // Paint a single paginated page's blocks onto a preview host + // (a tile thumb or the chapter editor's preview pane). The + // host must already have a master preview rendered into it via + // `renderCustomArtPreviewDOM`; the overlay sits on top + // positioned to the master's text-frame in the same inch space. + // + // host: a div (.bookPageTileThumb or similar) sized to + // the trim aspect; its inner preview is already + // position:relative. + // page: one entry from paginateChapter(...).pages + // frame: { x, y, w, h, padding } in canvas-inch space + // trimW/H: the trim dimensions (with bleed already + // accounted for in the frame coords). + function paintReflowPageOverlay(host, page, frame, trimW, trimH) { + if (!host || !page || !frame) return; + // The renderCustomArtPreviewDOM helper appends a single child + // div (the page canvas) — overlay on top of it. + const parent = host.firstChild || host; + const canvasW = trimW + 2 * (state.book?.spec?.bleed || 0); + const canvasH = trimH + 2 * (state.book?.spec?.bleed || 0); + const overlay = document.createElement('div'); + const padding = Number(frame.padding) || 0; + const innerX = frame.x + padding; + const innerY = frame.y + padding; + const innerW = Math.max(0.1, frame.w - 2 * padding); + overlay.style.cssText = [ + 'position:absolute', + `left:${(innerX / canvasW) * 100}%`, + `top:${(innerY / canvasH) * 100}%`, + `width:${(innerW / canvasW) * 100}%`, + `height:${((frame.h - 2 * padding) / canvasH) * 100}%`, + 'pointer-events:none', + 'overflow:hidden', + 'z-index:50', + ].join(';'); + // Each block becomes a positioned div with its style + lines. + const scale = (parent.offsetWidth || 200) / (canvasW * 96); + for (const block of page.blocks) { + const blk = document.createElement('div'); + const style = block.style || {}; + const fs = (Number(style.fontSize) || 11); + blk.style.cssText = [ + 'position:absolute', + `left:${((Number(style.marginLeft) || 0) / innerW) * 100}%`, + `right:${((Number(style.marginRight) || 0) / innerW) * 100}%`, + `top:${(block.y / (frame.h - 2 * padding)) * 100}%`, + `font-family:${style.fontFamily || 'Georgia, serif'}`, + `font-size:${fs * scale}px`, + `font-weight:${style.fontWeight || 400}`, + `font-style:${style.fontStyle || 'normal'}`, + `line-height:${style.lineHeight || 1.5}`, + `color:${style.color || '#111111'}`, + `text-align:${style.alignment || 'left'}`, + style.background ? `background:${style.background}` : '', + 'white-space:pre-wrap', + ].filter(Boolean).join(';'); + // Lines were already wrapped by the engine; preserve them + // with \n so the white-space:pre-wrap above lays them out. + blk.textContent = block.lines.join('\n'); + overlay.appendChild(blk); + } + parent.appendChild(overlay); + } + // Chapter editor (Phase C.5). Reflow entries open a two-pane // textarea + live preview instead of the canvas. The book stays // in 'book' template mode; an `editingChapterId` field tracks