import PDFDocument from 'src/api/PDFDocument' ; import PDFPage from 'src/api/PDFPage' ; import PDFFont from 'src/api/PDFFont' ; import PDFImage from 'src/api/PDFImage' ; import PDFField , { FieldAppearanceOptions , assertFieldAppearanceOptions , } from 'src/api/form/PDFField' ; import { AppearanceProviderFor , normalizeAppearance , defaultTextFieldAppearanceProvider , } from 'src/api/form/appearances' ; import { rgb } from 'src/api/colors' ; import { degrees } from 'src/api/rotations' ; import { RichTextFieldReadError , ExceededMaxLengthError , InvalidMaxLengthError , } from 'src/api/errors' ; import { ImageAlignment } from 'src/api/image/alignment' ; import { TextAlignment } from 'src/api/text/alignment' ; import { PDFHexString , PDFRef , PDFStream , PDFAcroText , AcroTextFlags , PDFWidgetAnnotation , } from 'src/core' ; import { assertIs , assertIsOneOf , assertOrUndefined , assertPositive , assertRangeOrUndefined , } from 'src/utils' ; /**
 * Represents a text field of a [[PDFForm]].
 *
 * [[PDFTextField]] fields are boxes that display text entered by the user. The
 * purpose of a text field is to enable users to enter text or view text values
 * in the document prefilled by software. Users can click on a text field and
 * input text via their keyboard. Some text fields allow multiple lines of text
 * to be entered (see [[PDFTextField.isMultiline]]).
 */ export default class PDFTextField extends PDFField { param acroText param ref param doc static of = ( acroText , ref , doc ) => new PDFTextField ( acroText , ref , doc ) ; /** The low-level PDFAcroText wrapped by this text field. */ readonly acroField : PDFAcroText ; private constructor ( acroText , ref , doc ) { super ( acroText , ref , doc ) ; assertIs ( acroText , 'acroText' , [ [ PDFAcroText , 'PDFAcroText' ] ] ) ; this . acroField = acroText ; } returns getText ( ) { const value = this . acroField . getValue ( ) ; if ( ! value && this . isRichFormatted ( ) ) { throw new RichTextFieldReadError ( this . getName ( ) ) ; } return value ?. decodeText ( ) ; } param text setText ( text ) { assertOrUndefined ( text , 'text' , [ 'string' ] ) ; const maxLength = this . getMaxLength ( ) ; if ( maxLength !== undefined && text && text . length > maxLength ) { throw new ExceededMaxLengthError ( text . length , maxLength , this . getName ( ) ) ; } this . markAsDirty ( ) ; this . disableRichFormatting ( ) ; if ( text ) { this . acroField . setValue ( PDFHexString . fromText ( text ) ) ; } else { this . acroField . removeValue ( ) ; } } returns getAlignment ( ) { const quadding = this . acroField . getQuadding ( ) ; return ( quadding === 0 ? TextAlignment . Left : quadding === 1 ? TextAlignment . Center : quadding === 2 ? TextAlignment . Right : TextAlignment . Left ) ; } param alignment setAlignment ( alignment ) { assertIsOneOf ( alignment , 'alignment' , TextAlignment ) ; this . markAsDirty ( ) ; this . acroField . setQuadding ( alignment ) ; } returns getMaxLength ( ) { return this . acroField . getMaxLength ( ) ; } param maxLength setMaxLength ( maxLength ? ) { assertRangeOrUndefined ( maxLength , 'maxLength' , 0 , Number . MAX_SAFE_INTEGER ) ; this . markAsDirty ( ) ; if ( maxLength === undefined ) { this . acroField . removeMaxLength ( ) ; } else { const text = this . getText ( ) ; if ( text && text . length > maxLength ) { throw new InvalidMaxLengthError ( text . length , maxLength , this . getName ( ) ) ; } this . acroField . setMaxLength ( maxLength ) ; } } /**
     * Remove the maximum length for this text field. This allows any number of
     * characters to be typed into this field by the user. For example:
     * ```js
     * const textField = form.getTextField('some.text.field')
     * textField.removeMaxLength()
     * ```
     * Calling this method is equivalent to passing `undefined` to
     * [[PDFTextField.setMaxLength]].
     */ removeMaxLength ( ) { this . markAsDirty ( ) ; this . acroField . removeMaxLength ( ) ; } param image setImage ( image ) { const fieldAlignment = this . getAlignment ( ) ; const alignment = fieldAlignment === TextAlignment . Center ? ImageAlignment . Center : fieldAlignment === TextAlignment . Right ? ImageAlignment . Right : ImageAlignment . Left ; const widgets = this . acroField . getWidgets ( ) ; for ( let idx = 0 , len = widgets . length ; idx < len ; idx ++ ) { const widget = widgets [ idx ] ; const streamRef = this . createImageAppearanceStream ( widget , image , alignment ) ; this . updateWidgetAppearances ( widget , { normal : streamRef } ) ; } this . markAsClean ( ) ; } param fontSize setFontSize ( fontSize ) { assertPositive ( fontSize , 'fontSize' ) ; this . acroField . setFontSize ( fontSize ) ; this . markAsDirty ( ) ; } returns isMultiline ( ) { return this . acroField . hasFlag ( AcroTextFlags . Multiline ) ; } /**
     * Display each line of text on a new line when this field is displayed in a
     * PDF reader. For example:
     * ```js
     * const textField = form.getTextField('some.text.field')
     * textField.enableMultiline()
     * ```
     * This method will mark this text field as dirty. See
     * [[PDFTextField.setText]] for more details about what this means.
     */ enableMultiline ( ) { this . markAsDirty ( ) ; this . acroField . setFlagTo ( AcroTextFlags . Multiline , true ) ; } /**
     * Display each line of text on the same line when this field is displayed
     * in a PDF reader. For example:
     * ```js
     * const textField = form.getTextField('some.text.field')
     * textField.disableMultiline()
     * ```
     * This method will mark this text field as dirty. See
     * [[PDFTextField.setText]] for more details about what this means.
     */ disableMultiline ( ) { this . markAsDirty ( ) ; this . acroField . setFlagTo ( AcroTextFlags . Multiline , false ) ; } returns isPassword ( ) { return this . acroField . hasFlag ( AcroTextFlags . Password ) ; } /**
     * Indicate that this text field is intended for storing a secure password.
     * For example:
     * ```js
     * const textField = form.getTextField('some.text.field')
     * textField.enablePassword()
     * ```
     * Values entered into password text fields should not be displayed on the
     * screen by PDF readers. Most PDF readers will display the value as
     * asterisks or bullets. PDF readers should never store values entered by the
     * user into password text fields. Similarly, applications should not
     * write data to a password text field.
     *
     * **Please note that this method does not cause entered values to be
     * encrypted or secured in any way! It simply sets a flag that PDF software
     * and readers can access to determine the _purpose_ of this field.**
     */ enablePassword ( ) { this . acroField . setFlagTo ( AcroTextFlags . Password , true ) ; } /**
     * Indicate that this text field is **not** intended for storing a secure
     * password. For example:
     * ```js
     * const textField = form.getTextField('some.text.field')
     * textField.disablePassword()
     * ```
     */ disablePassword ( ) { this . acroField . setFlagTo ( AcroTextFlags . Password , false ) ; } returns isFileSelector ( ) { return this . acroField . hasFlag ( AcroTextFlags . FileSelect ) ; } /**
     * Indicate that this text field is intended to store a file path. The
     * contents of the file stored at that path should be submitted as the value
     * of the field. For example:
     * ```js
     * const textField = form.getTextField('some.text.field')
     * textField.enableFileSelection()
     * ```
     */ enableFileSelection ( ) { this . acroField . setFlagTo ( AcroTextFlags . FileSelect , true ) ; } /**
     * Indicate that this text field is **not** intended to store a file path.
     * For example:
     * ```js
     * const textField = form.getTextField('some.text.field')
     * textField.disableFileSelection()
     * ```
     */ disableFileSelection ( ) { this . acroField . setFlagTo ( AcroTextFlags . FileSelect , false ) ; } returns isSpellChecked ( ) { return ! this . acroField . hasFlag ( AcroTextFlags . DoNotSpellCheck ) ; } /**
     * Allow PDF readers to spell check the text entered in this field.
     * For example:
     * ```js
     * const textField = form.getTextField('some.text.field')
     * textField.enableSpellChecking()
     * ```
     */ enableSpellChecking ( ) { this . acroField . setFlagTo ( AcroTextFlags . DoNotSpellCheck , false ) ; } /**
     * Do not allow PDF readers to spell check the text entered in this field.
     * For example:
     * ```js
     * const textField = form.getTextField('some.text.field')
     * textField.disableSpellChecking()
     * ```
     */ disableSpellChecking ( ) { this . acroField . setFlagTo ( AcroTextFlags . DoNotSpellCheck , true ) ; } returns isScrollable ( ) { return ! this . acroField . hasFlag ( AcroTextFlags . DoNotScroll ) ; } /**
     * Allow PDF readers to present a scroll bar to the user when the contents
     * of this text field do not fit within its view bounds. For example:
     * ```js
     * const textField = form.getTextField('some.text.field')
     * textField.enableScrolling()
     * ```
     * A horizontal scroll bar should be shown for singleline fields. A vertical
     * scroll bar should be shown for multiline fields.
     */ enableScrolling ( ) { this . acroField . setFlagTo ( AcroTextFlags . DoNotScroll , false ) ; } /**
     * Do not allow PDF readers to present a scroll bar to the user when the
     * contents of this text field do not fit within its view bounds. For example:
     * ```js
     * const textField = form.getTextField('some.text.field')
     * textField.disableScrolling()
     * ```
     */ disableScrolling ( ) { this . acroField . setFlagTo ( AcroTextFlags . DoNotScroll , true ) ; } returns isCombed ( ) { return ( this . acroField . hasFlag ( AcroTextFlags . Comb ) && ! this . isMultiline ( ) && ! this . isPassword ( ) && ! this . isFileSelector ( ) && this . getMaxLength ( ) !== undefined ) ; } /**
     * Split this field into `n` equal size cells with one character in each
     * (where `n` is equal to the max length of the text field). This will cause
     * all characters in the field to be displayed an equal distance apart from
     * one another. For example:
     * ```js
     * const textField = form.getTextField('some.text.field')
     * textField.enableCombing()
     * ```
     *
     * In addition to calling this method, text fields must have a max length
     * defined in order to be combed (see [[PDFTextField.setMaxLength]]).
     *
     * This method will also call the following three methods internally:
     * * [[PDFTextField.disableMultiline]]
     * * [[PDFTextField.disablePassword]]
     * * [[PDFTextField.disableFileSelection]]
     *
     * This method will mark this text field as dirty. See
     * [[PDFTextField.setText]] for more details about what this means.
     */ enableCombing ( ) { if ( this . getMaxLength ( ) === undefined ) { const msg = `PDFTextFields must have a max length in order to be combed` ; console . warn ( msg ) ; } this . markAsDirty ( ) ; this . disableMultiline ( ) ; this . disablePassword ( ) ; this . disableFileSelection ( ) ; this . acroField . setFlagTo ( AcroTextFlags . Comb , true ) ; } /**
     * Turn off combing for this text field. For example:
     * ```js
     * const textField = form.getTextField('some.text.field')
     * textField.disableCombing()
     * ```
     * See [[PDFTextField.isCombed]] and [[PDFTextField.enableCombing]] for more
     * information about what combing is.
     *
     * This method will mark this text field as dirty. See
     * [[PDFTextField.setText]] for more details about what this means.
     */ disableCombing ( ) { this . markAsDirty ( ) ; this . acroField . setFlagTo ( AcroTextFlags . Comb , false ) ; } returns isRichFormatted ( ) { return this . acroField . hasFlag ( AcroTextFlags . RichText ) ; } /**
     * Indicate that this field contains XFA data - or rich text. For example:
     * ```js
     * const textField = form.getTextField('some.text.field')
     * textField.enableRichFormatting()
     * ```
     * Note that `pdf-lib` does not support reading or writing rich text fields.
     * Nor do most PDF readers and writers. Rich text fields are based on XFA
     * (XML Forms Architecture). Relatively few PDFs use rich text fields or XFA.
     * Unlike PDF itself, XFA is not an ISO standard. XFA has been deprecated in
     * PDF 2.0:
     * * https://en.wikipedia.org/wiki/XFA
     * * http://blog.pdfshareforms.com/pdf-2-0-release-bid-farewell-xfa-forms/
     */ enableRichFormatting ( ) { this . acroField . setFlagTo ( AcroTextFlags . RichText , true ) ; } /**
     * Indicate that this is a standard text field that does not XFA data (rich
     * text). For example:
     * ```js
     * const textField = form.getTextField('some.text.field')
     * textField.disableRichFormatting()
     * ```
     */ disableRichFormatting ( ) { this . acroField . setFlagTo ( AcroTextFlags . RichText , false ) ; } param page param options addToPage ( page , options ? ) { assertIs ( page , 'page' , [ [ PDFPage , 'PDFPage' ] ] ) ; assertFieldAppearanceOptions ( options ) ; if ( ! options ) options = { } ; if ( ! ( 'textColor' in options ) ) options . textColor = rgb ( 0 , 0 , 0 ) ; if ( ! ( 'backgroundColor' in options ) ) options . backgroundColor = rgb ( 1 , 1 , 1 ) ; if ( ! ( 'borderColor' in options ) ) options . borderColor = rgb ( 0 , 0 , 0 ) ; if ( ! ( 'borderWidth' in options ) ) options . borderWidth = 1 ; const widget = this . createWidget ( { x : options . x ?? 0 , y : options . y ?? 0 , width : options . width ?? 200 , height : options . height ?? 50 , textColor : options . textColor , backgroundColor : options . backgroundColor , borderColor : options . borderColor , borderWidth : options . borderWidth ?? 0 , rotate : options . rotate ?? degrees ( 0 ) , hidden : options . hidden , page : page . ref , } ) ; const widgetRef = this . doc . context . register ( widget . dict ) ; this . acroField . addWidget ( widgetRef ) ; const font = options . font ?? this . doc . getForm ( ) . getDefaultFont ( ) ; this . updateWidgetAppearance ( widget , font ) ; page . node . addAnnot ( widgetRef ) ; } returns needsAppearancesUpdate ( ) { if ( this . isDirty ( ) ) return true ; const widgets = this . acroField . getWidgets ( ) ; for ( let idx = 0 , len = widgets . length ; idx < len ; idx ++ ) { const widget = widgets [ idx ] ; const hasAppearances = widget . getAppearances ( ) ?. normal instanceof PDFStream ; if ( ! hasAppearances ) return true ; } return false ; } param font defaultUpdateAppearances ( font ) { assertIs ( font , 'font' , [ [ PDFFont , 'PDFFont' ] ] ) ; this . updateAppearances ( font ) ; } param font param provider updateAppearances ( font , provider ? ) { assertIs ( font , 'font' , [ [ PDFFont , 'PDFFont' ] ] ) ; assertOrUndefined ( provider , 'provider' , [ Function ] ) ; const widgets = this . acroField . getWidgets ( ) ; for ( let idx = 0 , len = widgets . length ; idx < len ; idx ++ ) { const widget = widgets [ idx ] ; this . updateWidgetAppearance ( widget , font , provider ) ; } this . markAsClean ( ) ; } private updateWidgetAppearance ( widget , font , provider ? ) { const apProvider = provider ?? defaultTextFieldAppearanceProvider ; const appearances = normalizeAppearance ( apProvider ( this , widget , font ) ) ; this . updateWidgetAppearanceWithFont ( widget , font , appearances ) ; } }