@@ -22,6 +22,8 @@ import type {
2222 ParaFragment ,
2323 ResolvedLayout ,
2424 ResolvedPage ,
25+ TableFragment ,
26+ DrawingFragment ,
2527} from '@superdoc/contracts' ;
2628import { buildMultiSectionIdentifier , type HeaderFooterLayoutResult } from '@superdoc/layout-bridge' ;
2729import {
@@ -779,6 +781,106 @@ describe('HeaderFooterSessionManager', () => {
779781 expect ( payload ! . items ! [ 0 ] ) . toMatchObject ( { blockId : 'p1' , x : 72 , y : 0 } ) ;
780782 } ) ;
781783
784+ it ( 'does not shift normal rId footer fragments for negative minY from page-relative behindDoc drawings' , ( ) => {
785+ const deps : SessionManagerDependencies = {
786+ getLayoutOptions : vi . fn ( ( ) => ( { } ) ) ,
787+ getPageElement : vi . fn ( ( ) => null ) ,
788+ scrollPageIntoView : vi . fn ( ) ,
789+ waitForPageMount : vi . fn ( async ( ) => true ) ,
790+ convertPageLocalToOverlayCoords : vi . fn ( ( ) => ( { x : 0 , y : 0 } ) ) ,
791+ isViewLocked : vi . fn ( ( ) => false ) ,
792+ getBodyPageHeight : vi . fn ( ( ) => 800 ) ,
793+ notifyInputBridgeTargetChanged : vi . fn ( ) ,
794+ scheduleRerender : vi . fn ( ) ,
795+ setPendingDocChange : vi . fn ( ) ,
796+ getBodyPageCount : vi . fn ( ( ) => 1 ) ,
797+ } ;
798+ const tableFragment : TableFragment = {
799+ kind : 'table' ,
800+ blockId : 'footer-table' ,
801+ fromRow : 0 ,
802+ toRow : 1 ,
803+ x : 72 ,
804+ y : 0 ,
805+ width : 468 ,
806+ height : 24 ,
807+ } ;
808+ const behindDocFragment : DrawingFragment = {
809+ kind : 'drawing' ,
810+ blockId : 'footer-bg' ,
811+ drawingKind : 'vectorShape' ,
812+ x : 0 ,
813+ y : - 36 ,
814+ width : 612 ,
815+ height : 120 ,
816+ isAnchored : true ,
817+ behindDoc : true ,
818+ zIndex : 0 ,
819+ geometry : { width : 612 , height : 120 } ,
820+ scale : 1 ,
821+ sourceAnchor : { vRelativeFrom : 'page' } ,
822+ } as DrawingFragment ;
823+ const footerResult : HeaderFooterLayoutResult = {
824+ kind : 'footer' ,
825+ type : 'default' ,
826+ layout : {
827+ height : 48 ,
828+ minY : - 36 ,
829+ pages : [ { number : 1 , fragments : [ tableFragment , behindDocFragment ] } ] ,
830+ } ,
831+ blocks : [
832+ { kind : 'table' , id : 'footer-table' , rows : [ { id : 'row-1' , cells : [ ] } ] } ,
833+ {
834+ kind : 'drawing' ,
835+ id : 'footer-bg' ,
836+ drawingKind : 'vectorShape' ,
837+ anchor : { isAnchored : true , vRelativeFrom : 'page' , behindDoc : true } ,
838+ geometry : { width : 612 , height : 120 } ,
839+ } ,
840+ ] as FlowBlock [ ] ,
841+ measures : [
842+ { kind : 'table' , rowHeights : [ 24 ] , columnWidths : [ 468 ] , cells : [ ] , rows : [ ] } ,
843+ { kind : 'drawing' , width : 612 , height : 120 } ,
844+ ] as unknown as Measure [ ] ,
845+ } ;
846+
847+ manager = new HeaderFooterSessionManager ( {
848+ painterHost,
849+ visibleHost,
850+ selectionOverlay,
851+ editor : createMainEditorStub ( ) ,
852+ defaultPageSize : { w : 612 , h : 792 } ,
853+ defaultMargins : { top : 72 , right : 72 , bottom : 72 , left : 72 , header : 36 , footer : 36 } ,
854+ } ) ;
855+ manager . setDependencies ( deps ) ;
856+ manager . headerFooterIdentifier = {
857+ headerIds : { default : null , first : null , even : null , odd : null } ,
858+ footerIds : { default : 'rId-footer-default' , first : null , even : null , odd : null } ,
859+ titlePg : false ,
860+ alternateHeaders : false ,
861+ } ;
862+ manager . footerLayoutsByRId . set ( 'rId-footer-default' , footerResult ) ;
863+
864+ const layout : Layout = {
865+ version : 1 ,
866+ flowMode : 'paginated' ,
867+ pageGap : 0 ,
868+ pageSize : { w : 612 , h : 792 } ,
869+ pages : [ { number : 1 , margins : { top : 72 , right : 72 , bottom : 72 , left : 72 , header : 36 , footer : 36 } } as never ] ,
870+ } as unknown as Layout ;
871+ const provider = manager . createDecorationProvider ( 'footer' , layout as unknown as ResolvedLayout ) ;
872+ const payload = provider ! ( 1 , layout . pages [ 0 ] ! . margins , layout . pages [ 0 ] as unknown as ResolvedPage ) ;
873+
874+ expect ( payload ) . not . toBeNull ( ) ;
875+ expect ( payload ! . minY ) . toBe ( - 36 ) ;
876+ expect ( payload ! . fragments ) . toHaveLength ( 2 ) ;
877+ expect ( payload ! . fragments [ 0 ] ) . toMatchObject ( { kind : 'table' , blockId : 'footer-table' , y : 0 } ) ;
878+ expect ( payload ! . fragments [ 1 ] ) . toMatchObject ( { kind : 'drawing' , blockId : 'footer-bg' , y : - 36 , behindDoc : true } ) ;
879+ expect ( payload ! . items ) . toHaveLength ( 2 ) ;
880+ expect ( payload ! . items ! [ 0 ] ) . toMatchObject ( { fragmentKind : 'table' , blockId : 'footer-table' , y : 0 } ) ;
881+ expect ( payload ! . items ! [ 1 ] ) . toMatchObject ( { fragmentKind : 'drawing' , blockId : 'footer-bg' , y : - 36 } ) ;
882+ } ) ;
883+
782884 it ( 'uses section titlePg state when selecting decoration-provider variants' , ( ) => {
783885 const deps : SessionManagerDependencies = {
784886 getLayoutOptions : vi . fn ( ( ) => ( { } ) ) ,
0 commit comments