@@ -7031,6 +7031,17 @@ document.addEventListener("DOMContentLoaded", function () {
70317031 return ;
70327032 }
70337033
7034+ // Skip any elements nested inside list items that contain block children (treat list items as atomic)
7035+ if ( el . parentElement ) {
7036+ const liAncestor = el . parentElement . closest ( 'li' ) ;
7037+ if ( liAncestor ) {
7038+ const hasBlockChildren = liAncestor . querySelector ( 'p, blockquote, pre, table, ul, ol' ) !== null ;
7039+ if ( hasBlockChildren ) {
7040+ return ;
7041+ }
7042+ }
7043+ }
7044+
70347045 let type = '' ;
70357046
70367047 if ( tag === 'img' ) type = 'img' ;
@@ -7048,9 +7059,11 @@ document.addEventListener("DOMContentLoaded", function () {
70487059 else if ( [ 'p' , 'h1' , 'h2' , 'h3' , 'h4' , 'h5' , 'h6' ] . includes ( tag ) ) {
70497060 type = 'text' ;
70507061 } else if ( tag === 'li' ) {
7051- // Only target li if they don't contain other block elements to avoid double targeting
7062+ // Treat list items with block children as atomic containers, otherwise treat as text
70527063 const hasBlockChildren = el . querySelector ( 'p, blockquote, pre, table, ul, ol' ) !== null ;
7053- if ( ! hasBlockChildren ) {
7064+ if ( hasBlockChildren ) {
7065+ type = 'li' ;
7066+ } else {
70547067 type = 'text' ;
70557068 }
70567069 } else if ( el . classList . contains ( 'math-block' ) || tag === 'mjx-container' ) {
@@ -7335,6 +7348,10 @@ document.addEventListener("DOMContentLoaded", function () {
73357348 el . style . fontSize = el . dataset . pdfOriginalFontSize ;
73367349 el . removeAttribute ( 'data-pdf-original-font-size' ) ;
73377350 } ) ;
7351+ container . querySelectorAll ( '[data-pdf-original-overflow]' ) . forEach ( el => {
7352+ el . style . overflow = el . dataset . pdfOriginalOverflow ;
7353+ el . removeAttribute ( 'data-pdf-original-overflow' ) ;
7354+ } ) ;
73387355 }
73397356
73407357 function mergeSplitTables ( container ) {
@@ -7513,10 +7530,10 @@ document.addEventListener("DOMContentLoaded", function () {
75137530
75147531 // 2. If not already pushed by Keep-With-Next, perform standard page-split calculations
75157532 if ( targetMargin === 0 ) {
7516- // Check if this element crosses any page boundary
7533+ // Check if this element crosses any page boundary or starts extremely close to it (sub-pixel safety)
75177534 let splitPageIndex = - 1 ;
75187535 for ( let i = 0 ; i < pageBoundaries . length ; i ++ ) {
7519- if ( currentTop < pageBoundaries [ i ] && currentBottom > pageBoundaries [ i ] ) {
7536+ if ( currentTop < pageBoundaries [ i ] + 12 && currentBottom > pageBoundaries [ i ] ) {
75207537 splitPageIndex = i ;
75217538 break ;
75227539 }
@@ -7533,15 +7550,16 @@ document.addEventListener("DOMContentLoaded", function () {
75337550 targetMargin = shift ;
75347551 }
75357552 } else {
7536- // Graphic element (svg, img, pre, math) splitting
7537- const buffer = 5 ;
7553+ // Graphic element splitting (with larger buffer to ensure complete clearance)
7554+ const buffer = 15 ;
75387555 const scaleNeeded = ( remainingSpace - buffer ) / item . height ;
75397556 const remainingSpacePercent = remainingSpace / pageHeightPxFromAnalysis ;
75407557
7541- // Rule 3: Enforce safety zone. If remaining page space is less than 20% of page height,
7542- // or if the required scale factor to fit is less than 0.6, push the element entirely to the next page.
7543- if ( remainingSpacePercent >= 0.20 && scaleNeeded >= 0.6 ) {
7544- // Fit on current page by scaling
7558+ const isTextContainer = [ 'blockquote' , 'li' , 'table' , 'pre' , 'math' ] . includes ( item . type ) ;
7559+
7560+ // Fit on current page by scaling if it's an image/svg and space/scale are acceptable.
7561+ // Otherwise, always push text/block containers to next page to prevent transform-scaling bugs.
7562+ if ( ! isTextContainer && remainingSpacePercent >= 0.20 && scaleNeeded >= 0.6 ) {
75457563 targetScale = Math . min ( 1.0 , scaleNeeded ) ;
75467564 } else {
75477565 // Push to next page
@@ -7553,15 +7571,15 @@ document.addEventListener("DOMContentLoaded", function () {
75537571 const newBottom = newTop + item . height ;
75547572 const nextBoundaryY = pageBoundaries [ splitPageIndex + 1 ] || ( boundaryY + pageHeightPxFromAnalysis ) ;
75557573 if ( newBottom > nextBoundaryY ) {
7556- const scaleToFitPage = ( pageHeightPxFromAnalysis - 10 ) / item . height ;
7574+ const scaleToFitPage = ( pageHeightPxFromAnalysis - 20 ) / item . height ;
75577575 targetScale = Math . max ( 0.5 , Math . min ( 1.0 , scaleToFitPage ) ) ;
75587576 }
75597577 }
75607578 }
75617579 } else {
75627580 // Element is not split. But graphic elements taller than a page must still scale to fit!
75637581 if ( item . type !== 'text' && item . height > pageHeightPxFromAnalysis ) {
7564- const scaleToFitPage = ( pageHeightPxFromAnalysis - 10 ) / item . height ;
7582+ const scaleToFitPage = ( pageHeightPxFromAnalysis - 20 ) / item . height ;
75657583 targetScale = Math . max ( 0.5 , Math . min ( 1.0 , scaleToFitPage ) ) ;
75667584 }
75677585 }
@@ -7600,16 +7618,24 @@ document.addEventListener("DOMContentLoaded", function () {
76007618 }
76017619 }
76027620
7603- // Create a physical spacer element to avoid margin collapse issues entirely
7604- const spacer = document . createElement ( 'div' ) ;
7605- spacer . className = 'pdf-page-break-spacer' ;
7606- spacer . style . height = `${ targetMargin } px` ;
7607- spacer . style . margin = '0' ;
7608- spacer . style . padding = '0' ;
7609- spacer . style . border = 'none' ;
7610- spacer . style . display = 'block' ;
7611-
7612- targetElement . parentNode . insertBefore ( spacer , targetElement ) ;
7621+ // If target is a list item, apply marginTop directly to avoid invalid HTML / collapsed spacers
7622+ if ( targetElement . tagName . toLowerCase ( ) === 'li' ) {
7623+ if ( ! targetElement . dataset . hasOwnProperty ( 'pdfOriginalMarginTop' ) ) {
7624+ targetElement . dataset . pdfOriginalMarginTop = targetElement . style . marginTop || '' ;
7625+ }
7626+ targetElement . style . marginTop = `${ targetMargin } px` ;
7627+ } else {
7628+ // Create a physical spacer element to avoid margin collapse issues entirely
7629+ const spacer = document . createElement ( 'div' ) ;
7630+ spacer . className = 'pdf-page-break-spacer' ;
7631+ spacer . style . height = `${ targetMargin } px` ;
7632+ spacer . style . margin = '0' ;
7633+ spacer . style . padding = '0' ;
7634+ spacer . style . border = 'none' ;
7635+ spacer . style . display = 'block' ;
7636+
7637+ targetElement . parentNode . insertBefore ( spacer , targetElement ) ;
7638+ }
76137639 accumulatedShift += targetMargin ;
76147640 }
76157641
@@ -7710,25 +7736,28 @@ document.addEventListener("DOMContentLoaded", function () {
77107736 if ( elementType === 'svg' ) {
77117737 element . style . maxWidth = 'none' ;
77127738 }
7713- } else if ( elementType === 'math' || elementType === 'pre' || elementType === 'blockquote' ) {
7714- if ( ! element . dataset . hasOwnProperty ( 'pdfOriginalFontSize' ) ) {
7715- element . dataset . pdfOriginalFontSize = element . style . fontSize || '' ;
7739+ } else {
7740+ // For pre, table, blockquote, math, li, etc.
7741+ // Use transform: scale combined with physical height and overflow hidden to guarantee no native splits
7742+ if ( ! element . dataset . hasOwnProperty ( 'pdfOriginalHeight' ) ) {
7743+ element . dataset . pdfOriginalHeight = element . style . height || '' ;
77167744 }
7717- let origFontSize = parseFloat ( element . dataset . pdfOriginalClientFontSize ) ;
7718- if ( isNaN ( origFontSize ) ) {
7719- const style = window . getComputedStyle ( element ) ;
7720- origFontSize = parseFloat ( style . fontSize ) || 14 ;
7721- element . dataset . pdfOriginalClientFontSize = String ( origFontSize ) ;
7745+ if ( ! element . dataset . hasOwnProperty ( 'pdfOriginalOverflow' ) ) {
7746+ element . dataset . pdfOriginalOverflow = element . style . overflow || '' ;
77227747 }
7723- element . style . fontSize = `${ origFontSize * scaleFactor } px` ;
7724- } else {
7748+
77257749 element . style . transform = `scale(${ scaleFactor } )` ;
77267750 element . style . transformOrigin = 'top left' ;
77277751
7728- const originalHeight = element . offsetHeight ;
7729- const scaledHeight = originalHeight * scaleFactor ;
7730- const marginAdjustment = originalHeight - scaledHeight ;
7731- element . style . marginBottom = `-${ marginAdjustment } px` ;
7752+ let origHeight = parseFloat ( element . dataset . pdfOriginalClientHeight ) ;
7753+ if ( isNaN ( origHeight ) ) {
7754+ origHeight = element . offsetHeight || element . getBoundingClientRect ( ) . height ;
7755+ element . dataset . pdfOriginalClientHeight = String ( origHeight ) ;
7756+ }
7757+
7758+ const scaledHeight = origHeight * scaleFactor ;
7759+ element . style . height = `${ scaledHeight } px` ;
7760+ element . style . overflow = 'hidden' ;
77327761 }
77337762 }
77347763
@@ -7952,10 +7981,15 @@ document.addEventListener("DOMContentLoaded", function () {
79527981 await waitForPdfFrame ( progressState ) ;
79537982 throwIfPdfExportAborted ( progressState . signal ) ;
79547983
7984+ console . log ( `[PDF DEBUG] canvas.width = ${ canvas . width } , canvas.height = ${ canvas . height } ` ) ;
7985+ console . log ( `[PDF DEBUG] tempElement.offsetWidth = ${ tempElement . offsetWidth } , rect.width = ${ tempElement . getBoundingClientRect ( ) . width } ` ) ;
79557986 const scaleFactor = canvas . width / contentWidth ;
7987+ console . log ( `[PDF DEBUG] scaleFactor = ${ scaleFactor } , PAGE_CONFIG.scale = ${ PAGE_CONFIG . scale } , captureScale = ${ captureScale } ` ) ;
79567988 const imgHeight = canvas . height / scaleFactor ;
7989+ console . log ( `[PDF DEBUG] imgHeight = ${ imgHeight } , contentHeight = ${ pageHeight - margin * 2 } ` ) ;
79577990 // Introduce a 0.5mm tolerance to prevent rounding errors from creating a trailing blank page
79587991 const pagesCount = Math . ceil ( ( imgHeight - 0.5 ) / ( pageHeight - margin * 2 ) ) ;
7992+ console . log ( `[PDF DEBUG] pagesCount = ${ pagesCount } ` ) ;
79597993
79607994 updatePdfProgress ( progressState , 76 , "Rendering pages" ) ;
79617995 for ( let page = 0 ; page < pagesCount ; page ++ ) {
0 commit comments