diff --git a/src/index.html b/src/index.html index 98f70dc..625cd10 100644 --- a/src/index.html +++ b/src/index.html @@ -29140,7 +29140,7 @@ // Render one designed page (master underlay + page layers) to a // print-DPI canvas. Token substitution is applied to master text // layers so `{page-number}` etc resolve correctly per page. - async function renderBookDesignedPageToCanvas(entry, dpi, ctxToken) { + async function renderBookDesignedPageToCanvas(entry, dpi, ctxToken, textOps = null, fontWarnings = null) { const book = state.book || {}; const spec = book.spec || {}; const trimW = Number(spec.trimW) || 6; @@ -29200,7 +29200,7 @@ catch (err) { console.warn('PDF thread paginate failed', err); continue; } const slice = paginated.perFrame[myIndex]; if (!slice || !slice.lines.length) continue; - _drawFrameLinesToCanvas(ctx, slice, layer, dpi, bleed, localTypography); + _drawFrameLinesToCanvas(ctx, slice, layer, dpi, bleed, localTypography, textOps, fontWarnings); } return canvas; } @@ -29209,7 +29209,14 @@ // _paintFrameLines but writes onto a Canvas2D context (the PDF // export's per-page raster). Coordinates: frame is sheet-relative // (includes bleed); canvas is trim-only, so subtract bleed. - function _drawFrameLinesToCanvas(ctx, frameSlice, frameLayer, dpi, bleed, typography) { + // + // J4.e (#4): when `textOps` is provided (PDF export path), also + // emit selectable + searchable real-text descriptors that + // exportBookToPdf overlays on top of the raster via pdf-lib's + // drawText. This is what the reflow path has been doing — now + // threaded frames participate too. `fontWarnings` collects + // unsupported font families for user feedback. + function _drawFrameLinesToCanvas(ctx, frameSlice, frameLayer, dpi, bleed, typography, textOps = null, fontWarnings = null) { const padding = Number(frameLayer.shape?.padding) || 0; const innerX = (Number(frameLayer.x) || 0) + padding - bleed; const innerY = (Number(frameLayer.y) || 0) + padding - bleed; @@ -29224,14 +29231,42 @@ : typography.alignment === 'right' ? 'right' : 'left'; const lines = frameSlice.lines || []; const breaksAfter = frameSlice.paragraphBreaksAfter || new Set(); + // J4.e: probe font + record warning. Typography defaults to + // 'Georgia, serif'; that lacks bold/italic flags here — threaded + // typography doesn't carry per-frame weights yet, so we pass + // false/false. Bold/italic per-line is a later enhancement. + const collect = Array.isArray(textOps); + const fontKey = collect ? _resolveStandardFontKey(typography.fontFamily, false, false) : null; + if (collect && !fontKey && fontWarnings) fontWarnings.add(typography.fontFamily); + const lineHIn = lineH / dpi; + const innerYIn = innerY; let yPx = innerY * dpi; + let yLineIdx = 0; // independent of breaks for textOps positioning for (let i = 0; i < lines.length; i++) { let xPx; if (typography.alignment === 'center') xPx = (innerX + innerW / 2) * dpi; else if (typography.alignment === 'right') xPx = (innerX + innerW) * dpi; else xPx = innerX * dpi; ctx.fillText(lines[i], xPx, yPx); + // Real-text op for the PDF text layer. Coords are inches + // from the trim corner (exportBookToPdf converts to pdf-lib's + // bottom-up point grid). Empty lines skipped — no glyphs. + if (collect && fontKey && lines[i] && lines[i].trim()) { + const topInchesFromTrim = innerYIn + (yPx - innerY * dpi) / dpi; + textOps.push({ + text: lines[i], + fontKey, + sizePt: typography.fontSize, + color: typography.color, + align: typography.alignment === 'center' ? 'center' : + typography.alignment === 'right' ? 'right' : 'left', + leftIn: innerX, + widthIn: innerW, + topInchesFromTrim, + }); + } yPx += lineH; + yLineIdx++; if (breaksAfter.has(i)) yPx += paragraphGapPx; } } @@ -29589,7 +29624,11 @@ textOps = []; canvas = await renderBookReflowPhysicalPageToCanvas(entry, pag, slot.indexInEntry, dpi, ctxToken, textOps, fallbackFontFamilies); } else { - canvas = await renderBookDesignedPageToCanvas(entry, dpi, ctxToken); + // J4.e: designed-page export also collects textOps for + // any threaded text frames on the page — overlaid as + // selectable/searchable PDF text on top of the raster. + textOps = []; + canvas = await renderBookDesignedPageToCanvas(entry, dpi, ctxToken, textOps, fallbackFontFamilies); } } catch (err) { warnings.push(`Page ${(slot.pageNumber != null) ? slot.pageNumber : '?'}: ${err.message || err}`);