No functional changes. Normalizes epub.js to match the formatting style
used throughout the rest of the codebase (2-space indentation, ASI
semicolons, double-quoted strings, trailing commas in multiline).
When pressing 0 to reset zoom, the code was calling
removeAttribute('zoom') on the renderer, which removed the DOM
attribute but left the internal zoom/transform state stale. This meant
subsequent zoom operations would compound on the old hidden state.
Replace with the new resetZoom() method that properly clears the
numeric zoom and triggers a clean re-render.
Several interrelated fixes for the drag/pan behavior when zoomed in:
- Add dedicated #onResize() handler: numeric zoom rescales frames in
place (preserving pan position) while fit-page/fit-width does a full
re-render. Replaces the old bare #render() in the ResizeObserver.
- Add resetZoom() public method that clears the numeric zoom state and
re-renders, replacing the old removeAttribute('zoom') pattern which
left stale internal state.
- Guard spread-side sync in #handleMouseUp with !this.#portrait so
dual-frame logic only runs in landscape spreads, preventing incorrect
side-switching in portrait mode.
- Guard #updateTransform spread offset with !portrait for the same
reason — portrait layouts don't have left/right frames.
- Fix #handleMouseDown to return false when declining drag (select mode
in PDFs) and return true when starting a drag, so the iframe event
proxy can correctly decide whether to preventDefault.
- Only preventDefault on iframe mousemove when actively dragging or
magnifier is enabled, allowing normal text selection to work.
- Store zoom level in #zoom during #zoomByRatio and dispatch zoom event
instead of writing back to the DOM attribute.
- Add debug console.log statements to zoomByRatio and handleWheel for
tracing transform calculations.
The toggle button was defaulting to 'Pan Mode' with a plus-icon path,
but the actual default interaction mode is 'select' (the cursor/arrow).
Update the tooltip to 'Select Mode' and replace the SVG path with an
arrow/cursor shape so the icon matches the active mode at startup.
Magnifier (fixed-layout.js):
- Rewrite magnifier to use CSS background-image for comics (blob URLs
from iframe img elements) and canvas drawImage for PDFs. Replaces
broken iframe.cloneNode approach that created empty sandboxed clones.
- Detect which iframe is under the cursor by checking bounding rects,
so magnifier works on both left and right panes.
- Forward mousemove events from iframe documents to handleMagnifierMove
so the magnifier follows the cursor inside iframes.
- Add background-repeat: no-repeat to lens styles.
- Add zoomMagnifierEnabled getter for external state checks.
Interaction mode toggle (PDF only):
- Add 'interaction-mode' observed attribute with 'select'/'pan' values.
- In select mode (default for PDFs): mouse events pass through for text
selection; Shift+drag enables panning as a fallback.
- In pan mode: drag-to-pan works as normal.
- Comic books always use pan mode (no text to select).
- Auto-detect PDF via #isPDF flag set in #showSpread from onZoom presence.
- Add isPDF getter so reader.js can show/hide the toggle button.
- Add toggle button in reader.html, hidden by default, shown for PDFs
via the view 'load' event.
- Escape key dismisses the magnifier.
Zoom percentage display (reader.js):
- Dispatch 'zoom' CustomEvent from #zoomByRatio with the new scale.
- Listener in reader.js updates the percentage display on scroll zoom.
Also includes minor formatting (line wrapping) in #goLeft/#goRight.
When removeAttribute('zoom') was called, parseFloat(null) produced NaN,
and typeof NaN === 'number' is true in JS, so #goLeft/#goRight still
entered the zoomed-in dual-pane navigation branch.
Root cause fix: handle null value in attributeChangedCallback by setting
#zoom to undefined before the parseFloat path.
Defense in depth: add isNaN() guards to the zoom checks in #goLeft and
#goRight, matching the pattern already used in #render().
When a numeric zoom is active and both panes have content, #render()
now centers on the frame indicated by #side rather than centering the
entire spread. This means on page turns the view starts on the correct
frame (left for LTR, right for RTL) instead of the middle of the spread.
For fit-page/fit-width and single-pane views, centering behavior is
unchanged.
Ensures browsers use highest-quality interpolation when scaling
raster comic book images during zoom. No effect on browsers that
don't support this value (they fall back to default bilinear).
Replace the overflow:auto + scrollTo zoom approach with a CSS transform
model to fix the scroll-to-zoom cursor targeting bug in dual-pane layouts.
Root cause: flex centering (justify-content:center) and overflow:auto
conflict -- when content fits, flex positions it; when zoomed (overflow),
scrollLeft=0 jumps to the left edge. The zoom-to-cursor formula used
scrollLeft as reference without accounting for the centering offset,
causing systematic drift toward the right pane.
Changes:
- Host CSS: overflow:hidden, display:block, position:relative
- New #wrapper div with transform:translate(x,y) for pan positioning
- New #transform = { x, y, scale } as single source of truth
- #zoomByRatio(cx,cy,ratio) uses panzoom formula: x = cx - ratio*(cx - x)
- #render() centers via transform.x/y = (hostSize - contentSize) / 2
- #updateFrameScales() handles iframe size/scale separately from centering
- Simplified drag: tracks startTX/startTY, updates transform directly
- Simplified attributeChangedCallback: detects external vs internal zoom
changes and zooms from viewport center for external ones
- Remove all scrollLeft/scrollTop/scrollTo usage
- Remove marginBlock:auto, long-press drag detection, debug console.logs
- Preserve external API: zoom attribute, dragOffset, toggleMagnifier(),
load/relocate events
This commit enhances the FixedLayout component by including additional
contextual information when zoom or pan operations occur:
- scrollLeft and scrollTop: Current scroll position of the viewport
- source: Origin of the zoom/pan action (e.g., mouse wheel, pinch zoom, button)
- leftWidth: Width of the left page/panel in dual-page layouts
These parameters provide downstream handlers with complete state information
needed to implement sophisticated zoom behaviors, such as:
- Preserving scroll position during zoom operations
- Differentiating between user-initiated and programmatic zoom changes
- Handling asymmetric layouts with different panel widths
This change is part of the ongoing zoom control implementation for
fixed-layout e-books.
This commit addresses several issues with zoom and pan functionality,
particularly when zooming from the right iframe in a two-page spread:
- Add source iframe tracking to wheel and mouse events
* Pass frameId parameter through frame creation pipeline
* Tag events with sourceIframe and sourceFrame properties
* Enable proper cursor position calculation regardless of event source
- Fix zoom cursor calculation for two-page spreads
* Calculate cursor position relative to spread viewport, not individual pages
* Simplify scroll position calculation to work uniformly for both left and right pages
* Add debug logging for cursor calculation verification
- Improve drag/pan behavior across zoom modes
* Add draggable state property to control drag availability
* Handle fit-page mode differently with dragOffset tracking
* Direct scroll manipulation for zoom levels above 1
* Add console logging for ignored drag scenarios
- Prevent native image drag interference
* Set draggable="false" on all images in iframe documents
* Disable user select and webkit drag on images
* Prevent default behavior on iframe mouse events
These changes ensure that zoom operations work correctly from both left and
right iframes, maintaining proper cursor focus and scroll position.
This commit fixes several issues with the drag functionality in the FixedLayout
component to ensure smooth and accurate dragging behavior:
Changes:
- Initialize dragOffset to (0, 0) instead of scroll position when drag starts
- Fix drag offset calculation direction (changed from -= to +=)
- Add proper scroll position updates during drag operations
- Ensure dragOffset is reset to (0, 0) when drag completes
The previous implementation was incorrectly initializing dragOffset with the
current scroll position, which caused the drag offset to compound with scroll
position and led to incorrect rendering. The new approach starts drag offset
at zero and directly updates the scroll position during drag operations.
This ensures that:
1. Drag movements are accurately tracked relative to the drag start point
2. Scroll position is properly synchronized with drag movements
3. Drag state is cleanly reset when drag operations complete
This commit adds comprehensive zoom control capabilities for fixed-layout
e-book content, allowing users to zoom in, out, and reset zoom levels for
better readability and content inspection.
Key features added:
- Zoom control UI with +/- buttons and percentage display
- Keyboard shortcuts for zoom operations (Ctrl/Cmd +, -, 0)
- Mouse wheel zoom with Ctrl/Cmd modifier key
- Drag-to-pan functionality when zoomed in
- Smooth zoom transitions and state management
- Zoom constraints (min/max limits) to prevent over-zooming
Changes include:
- FixedLayout: Enhanced with zoom state management, drag handlers,
and keyboard/mouse event listeners for intuitive zoom control
- Reader UI: Added zoom control toolbar with visual feedback
- FixedLayout renderer integration with reader zoom controls
The zoom controls provide a seamless experience for users who need to
examine fixed-layout content more closely or adjust content size for
their preferred reading experience.
The MOBI6.init() method builds #fileposList (the map of all filepos byte-offset references, used to insert <a id="fileposXXXX"> anchors into section HTML) after calling getGuide() and the TOC-section loader — both of which call createDocument() → loadText(), which caches the section HTML. Since #fileposList is still empty at that point, the cached HTML has no anchor elements.
Later, when Foliate calls getElementById('filepos12345') on the cached (anchor-less) document, it returns null, so the paginator falls back to ?? 0 — i.e. scroll to position 0 = first page.
For EPUB this doesn't happen because chapters are separate files; navigation loads a new section by index, not by searching for an anchor element within one section.
Co-authored-by: Lars E. <lars@ermert.es>
If the viewer is running in an RTL context (this is not the same as the
reading direction of the Book itself), and a fixed-layout rendered view
is scaled (which is often the case), anchoring the transform to the
left breaks page positioning.