import { MissingPageContentsEmbeddingError , UnrecognizedStreamTypeError , } from 'src/core/errors' ; import PDFArray from 'src/core/objects/PDFArray' ; import PDFNumber from 'src/core/objects/PDFNumber' ; import PDFRawStream from 'src/core/objects/PDFRawStream' ; import PDFRef from 'src/core/objects/PDFRef' ; import PDFStream from 'src/core/objects/PDFStream' ; import PDFContext from 'src/core/PDFContext' ; import { decodePDFRawStream } from 'src/core/streams/decode' ; import PDFContentStream from 'src/core/structures/PDFContentStream' ; import PDFPageLeaf from 'src/core/structures/PDFPageLeaf' ; import CharCodes from 'src/core/syntax/CharCodes' ; import { TransformationMatrix } from 'src/types/matrix' ; import { mergeIntoTypedArray } from 'src/utils' ; /**
 * Represents a page bounding box.
 * Usually `left` and `bottom` are 0 and right, top are equal
 * to width, height if you want to clip to the whole page.
 *
 *       y
 *       ^
 *       | +--------+ (width,height)
 *       | |        |
 *       | |  Page  |
 *       | |        |
 *       | |        |
 * (0,0) | +--------+
 *       +----------> x
 */ export interface PageBoundingBox { left : number ; bottom : number ; right : number ; top : number ; } const fullPageBoundingBox = ( page ) => { const mediaBox = page . MediaBox ( ) ; const width = mediaBox . lookup ( 2 , PDFNumber ) . asNumber ( ) - mediaBox . lookup ( 0 , PDFNumber ) . asNumber ( ) ; const height = mediaBox . lookup ( 3 , PDFNumber ) . asNumber ( ) - mediaBox . lookup ( 1 , PDFNumber ) . asNumber ( ) ; return { left : 0 , bottom : 0 , right : width , top : height } ; } ; const boundingBoxAdjustedMatrix = ( bb ) : TransformationMatrix => [ 1 , 0 , 0 , 1 , - bb . left , - bb . bottom ] ; class PDFPageEmbedder { static async for ( page , boundingBox ? , transformationMatrix ? ) { return new PDFPageEmbedder ( page , boundingBox , transformationMatrix ) ; } readonly width : number ; readonly height : number ; readonly boundingBox : PageBoundingBox ; readonly transformationMatrix : TransformationMatrix ; private readonly page : PDFPageLeaf ; private constructor ( page , boundingBox ? , transformationMatrix ? ) { this . page = page ; const bb = boundingBox ?? fullPageBoundingBox ( page ) ; this . width = bb . right - bb . left ; this . height = bb . top - bb . bottom ; this . boundingBox = bb ; this . transformationMatrix = transformationMatrix ?? boundingBoxAdjustedMatrix ( bb ) ; } async embedIntoContext ( context , ref ? ) { const { Contents , Resources } = this . page . normalizedEntries ( ) ; if ( ! Contents ) throw new MissingPageContentsEmbeddingError ( ) ; const decodedContents = this . decodeContents ( Contents ) ; const { left , bottom , right , top } = this . boundingBox ; const xObject = context . flateStream ( decodedContents , { Type : 'XObject' , Subtype : 'Form' , FormType : 1 , BBox : [ left , bottom , right , top ] , Matrix : this . transformationMatrix , Resources , } ) ; if ( ref ) { context . assign ( ref , xObject ) ; return ref ; } else { return context . register ( xObject ) ; } } private decodeContents ( contents ) { const newline = Uint8Array . of ( CharCodes . Newline ) ; const decodedContents = [ ] ; for ( let idx = 0 , len = contents . size ( ) ; idx < len ; idx ++ ) { const stream = contents . lookup ( idx , PDFStream ) ; let content ; if ( stream instanceof PDFRawStream ) { content = decodePDFRawStream ( stream ) . decode ( ) ; } else if ( stream instanceof PDFContentStream ) { content = stream . getUnencodedContents ( ) ; } else { throw new UnrecognizedStreamTypeError ( stream ) ; } decodedContents . push ( content , newline ) ; } return mergeIntoTypedArray ( ... decodedContents ) ; } } export default PDFPageEmbedder ;