@@ -7468,6 +7468,39 @@ small scripts as well as for`);
74687468 } ) ;
74697469
74707470 describe ( "AcroForm" , function ( ) {
7471+ const buildTextFieldPdf = (
7472+ fieldName ,
7473+ fontKey = "Helv" ,
7474+ baseFont = "Helvetica"
7475+ ) => {
7476+ const appearance = `BT /${ fontKey } 10 Tf (x) Tj ET` ;
7477+ return assemblePdf ( [
7478+ "1 0 obj\n<< /Type /Catalog /Pages 2 0 R /AcroForm 6 0 R >>\nendobj\n" ,
7479+ "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n" ,
7480+ "3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] " +
7481+ "/Resources << >> /Annots [4 0 R] >>\nendobj\n" ,
7482+ "4 0 obj\n<< /Type /Annot /Subtype /Widget /FT /Tx " +
7483+ `/T (${ fieldName } ) /Rect [0 0 20 10] /AP << /N 5 0 R >> >>\nendobj\n` ,
7484+ `5 0 obj\n<< /Subtype /Form /BBox [0 0 20 10] /Resources 7 0 R ` +
7485+ `/Length ${ appearance . length } >>\n` +
7486+ `stream\n${ appearance } \nendstream\nendobj\n` ,
7487+ `6 0 obj\n<< /Fields [4 0 R] /DR << /Font << ` +
7488+ `/${ fontKey } 8 0 R >> >> /DA (/${ fontKey } 10 Tf) >>\nendobj\n` ,
7489+ "7 0 obj\n<< >>\nendobj\n" ,
7490+ `8 0 obj\n<< /Type /Font /Subtype /Type1 ` +
7491+ `/BaseFont /${ baseFont } >>\nendobj\n` ,
7492+ ] ) ;
7493+ } ;
7494+
7495+ const getAppearanceFontName = async ( pdfDoc , pageNumber ) => {
7496+ const page = await pdfDoc . getPage ( pageNumber ) ;
7497+ const operatorList = await page . getOperatorList ( {
7498+ annotationMode : AnnotationMode . ENABLE ,
7499+ } ) ;
7500+ const fontIndex = operatorList . fnArray . indexOf ( OPS . setFont ) ;
7501+ return fontIndex < 0 ? null : operatorList . argsArray [ fontIndex ] [ 0 ] ;
7502+ } ;
7503+
74717504 it ( "extract page 2 and check AcroForm Fields T entries" , async function ( ) {
74727505 let loadingTask = getDocument (
74737506 buildGetDocumentParams ( "form_two_pages.pdf" )
@@ -7623,6 +7656,116 @@ small scripts as well as for`);
76237656 expect ( newPdfDoc . numPages ) . toEqual ( 2 ) ;
76247657 await newLoadingTask . destroy ( ) ;
76257658 } ) ;
7659+
7660+ it ( "merges appearance resources from conflicting AcroForms" , async function ( ) {
7661+ let loadingTask = getDocument ( {
7662+ data : buildTextFieldPdf ( "first" , "F1" , "Helvetica" ) ,
7663+ } ) ;
7664+ let pdfDoc = await loadingTask . promise ;
7665+ const data = await pdfDoc . extractPages ( [
7666+ { document : null } ,
7667+ { document : buildTextFieldPdf ( "second" , "F2" , "Courier" ) } ,
7668+ ] ) ;
7669+ expect ( data ) . not . toBeNull ( ) ;
7670+ await loadingTask . destroy ( ) ;
7671+
7672+ loadingTask = getDocument ( { data } ) ;
7673+ pdfDoc = await loadingTask . promise ;
7674+ expect ( pdfDoc . numPages ) . toEqual ( 2 ) ;
7675+ expect (
7676+ Object . keys ( ( await pdfDoc . getFieldObjects ( ) ) ?? { } ) . sort ( )
7677+ ) . toEqual ( [ "first" , "second" ] ) ;
7678+ for ( const pageNumber of [ 1 , 2 ] ) {
7679+ const fontName = await getAppearanceFontName ( pdfDoc , pageNumber ) ;
7680+ expect ( fontName ) . not . toBeNull ( ) ;
7681+ expect ( fontName ) . not . toEqual ( "g_font_error" ) ;
7682+ }
7683+ await loadingTask . destroy ( ) ;
7684+ } ) ;
7685+
7686+ it ( "replaces invalid appearance resources with default resources" , async function ( ) {
7687+ const appearance = "BT /Cour 10 Tf (x) Tj ET" ;
7688+ const broken = assemblePdf ( [
7689+ "1 0 obj\n<< /Type /Catalog /Pages 2 0 R /AcroForm 6 0 R >>\nendobj\n" ,
7690+ "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n" ,
7691+ "3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] " +
7692+ "/Resources << >> /Annots [4 0 R] >>\nendobj\n" ,
7693+ "4 0 obj\n<< /Type /Annot /Subtype /Widget /FT /Tx /T (broken) " +
7694+ "/Rect [0 0 20 10] /AP << /N 5 0 R >> >>\nendobj\n" ,
7695+ "5 0 obj\n<< /Subtype /Form /BBox [0 0 20 10] " +
7696+ `/Resources /Nope /Length ${ appearance . length } >>\n` +
7697+ `stream\n${ appearance } \nendstream\nendobj\n` ,
7698+ "6 0 obj\n<< /Fields [4 0 R] /DR << /Font << /Cour 7 0 R >> >> " +
7699+ "/DA (/Cour 10 Tf) >>\nendobj\n" ,
7700+ "7 0 obj\n<< /Type /Font /Subtype /Type1 " +
7701+ "/BaseFont /Courier >>\nendobj\n" ,
7702+ ] ) ;
7703+
7704+ let loadingTask = getDocument ( { data : buildTextFieldPdf ( "main" ) } ) ;
7705+ let pdfDoc = await loadingTask . promise ;
7706+ const data = await pdfDoc . extractPages ( [
7707+ { document : null } ,
7708+ { document : broken } ,
7709+ ] ) ;
7710+ expect ( data ) . not . toBeNull ( ) ;
7711+ await loadingTask . destroy ( ) ;
7712+
7713+ loadingTask = getDocument ( { data } ) ;
7714+ pdfDoc = await loadingTask . promise ;
7715+ expect ( pdfDoc . numPages ) . toEqual ( 2 ) ;
7716+ expect (
7717+ Object . keys ( ( await pdfDoc . getFieldObjects ( ) ) ?? { } ) . sort ( )
7718+ ) . toEqual ( [ "broken" , "main" ] ) ;
7719+ const fontName = await getAppearanceFontName ( pdfDoc , 2 ) ;
7720+ expect ( fontName ) . not . toBeNull ( ) ;
7721+ expect ( fontName ) . not . toEqual ( "g_font_error" ) ;
7722+ await loadingTask . destroy ( ) ;
7723+ } ) ;
7724+
7725+ it ( "merges default resources into checkbox appearance streams" , async function ( ) {
7726+ const on = "/Tx BMC q BT /ZaDb 12 Tf (4) Tj ET Q EMC" ;
7727+ const withCheckbox = assemblePdf ( [
7728+ "1 0 obj\n<< /Type /Catalog /Pages 2 0 R /AcroForm 7 0 R >>\nendobj\n" ,
7729+ "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n" ,
7730+ "3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] " +
7731+ "/Resources << >> /Annots [4 0 R] >>\nendobj\n" ,
7732+ "4 0 obj\n<< /Type /Annot /Subtype /Widget /FT /Btn /T (check) " +
7733+ "/Rect [0 0 20 20] /AS /On " +
7734+ "/AP << /N << /On 5 0 R /Off 6 0 R >> >> >>\nendobj\n" ,
7735+ `5 0 obj\n<< /Subtype /Form /BBox [0 0 20 20] /Resources 8 0 R ` +
7736+ `/Length ${ on . length } >>\nstream\n${ on } \nendstream\nendobj\n` ,
7737+ "6 0 obj\n<< /Subtype /Form /BBox [0 0 20 20] /Resources 8 0 R " +
7738+ "/Length 1 >>\nstream\n \nendstream\nendobj\n" ,
7739+ "7 0 obj\n<< /Fields [4 0 R] /DR << " +
7740+ "/Font << /ZaDb 9 0 R >> " +
7741+ "/ColorSpace << /CsDefault /DeviceRGB >> >> " +
7742+ "/DA (/ZaDb 0 Tf) >>\nendobj\n" ,
7743+ "8 0 obj\n<< >>\nendobj\n" ,
7744+ "9 0 obj\n<< /Type /Font /Subtype /Type1 " +
7745+ "/BaseFont /ZapfDingbats >>\nendobj\n" ,
7746+ ] ) ;
7747+
7748+ let loadingTask = getDocument ( { data : buildTextFieldPdf ( "main" ) } ) ;
7749+ let pdfDoc = await loadingTask . promise ;
7750+ const data = await pdfDoc . extractPages ( [
7751+ { document : null } ,
7752+ { document : withCheckbox } ,
7753+ ] ) ;
7754+ expect ( data ) . not . toBeNull ( ) ;
7755+ expect ( countMarker ( data , "/CsDefault" ) ) . toEqual ( 1 ) ;
7756+ await loadingTask . destroy ( ) ;
7757+
7758+ loadingTask = getDocument ( { data } ) ;
7759+ pdfDoc = await loadingTask . promise ;
7760+ expect ( pdfDoc . numPages ) . toEqual ( 2 ) ;
7761+ expect (
7762+ Object . keys ( ( await pdfDoc . getFieldObjects ( ) ) ?? { } ) . sort ( )
7763+ ) . toEqual ( [ "check" , "main" ] ) ;
7764+ const fontName = await getAppearanceFontName ( pdfDoc , 2 ) ;
7765+ expect ( fontName ) . not . toBeNull ( ) ;
7766+ expect ( fontName ) . not . toEqual ( "g_font_error" ) ;
7767+ await loadingTask . destroy ( ) ;
7768+ } ) ;
76267769 } ) ;
76277770
76287771 describe ( "Outlines" , function ( ) {
0 commit comments