first commit
This commit is contained in:
7
node_modules/pdf-lib/src/api/Embeddable.ts
generated
vendored
Normal file
7
node_modules/pdf-lib/src/api/Embeddable.ts
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* A PDF entity, like images or fonts, which needs to be embedded into the
|
||||
* document before saving.
|
||||
*/
|
||||
export default interface Embeddable {
|
||||
embed: () => Promise<void>;
|
||||
}
|
||||
1395
node_modules/pdf-lib/src/api/PDFDocument.ts
generated
vendored
Normal file
1395
node_modules/pdf-lib/src/api/PDFDocument.ts
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
44
node_modules/pdf-lib/src/api/PDFDocumentOptions.ts
generated
vendored
Normal file
44
node_modules/pdf-lib/src/api/PDFDocumentOptions.ts
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
import { EmbeddedFileOptions } from 'src/core/embedders/FileEmbedder';
|
||||
import { TypeFeatures } from 'src/types/fontkit';
|
||||
|
||||
export enum ParseSpeeds {
|
||||
Fastest = Infinity,
|
||||
Fast = 1500,
|
||||
Medium = 500,
|
||||
Slow = 100,
|
||||
}
|
||||
|
||||
export interface AttachmentOptions extends EmbeddedFileOptions {}
|
||||
|
||||
export interface SaveOptions {
|
||||
useObjectStreams?: boolean;
|
||||
addDefaultPage?: boolean;
|
||||
objectsPerTick?: number;
|
||||
updateFieldAppearances?: boolean;
|
||||
}
|
||||
|
||||
export interface Base64SaveOptions extends SaveOptions {
|
||||
dataUri?: boolean;
|
||||
}
|
||||
|
||||
export interface LoadOptions {
|
||||
ignoreEncryption?: boolean;
|
||||
parseSpeed?: ParseSpeeds | number;
|
||||
throwOnInvalidObject?: boolean;
|
||||
updateMetadata?: boolean;
|
||||
capNumbers?: boolean;
|
||||
}
|
||||
|
||||
export interface CreateOptions {
|
||||
updateMetadata?: boolean;
|
||||
}
|
||||
|
||||
export interface EmbedFontOptions {
|
||||
subset?: boolean;
|
||||
customName?: string;
|
||||
features?: TypeFeatures;
|
||||
}
|
||||
|
||||
export interface SetTitleOptions {
|
||||
showInWindowTitleBar: boolean;
|
||||
}
|
||||
90
node_modules/pdf-lib/src/api/PDFEmbeddedFile.ts
generated
vendored
Normal file
90
node_modules/pdf-lib/src/api/PDFEmbeddedFile.ts
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
import Embeddable from 'src/api/Embeddable';
|
||||
import PDFDocument from 'src/api/PDFDocument';
|
||||
import FileEmbedder from 'src/core/embedders/FileEmbedder';
|
||||
import { PDFName, PDFArray, PDFDict, PDFHexString, PDFRef } from 'src/core';
|
||||
|
||||
/**
|
||||
* Represents a file that has been embedded in a [[PDFDocument]].
|
||||
*/
|
||||
export default class PDFEmbeddedFile implements Embeddable {
|
||||
/**
|
||||
* > **NOTE:** You probably don't want to call this method directly. Instead,
|
||||
* > consider using the [[PDFDocument.attach]] method, which will create
|
||||
* instances of [[PDFEmbeddedFile]] for you.
|
||||
*
|
||||
* Create an instance of [[PDFEmbeddedFile]] from an existing ref and embedder
|
||||
*
|
||||
* @param ref The unique reference for this file.
|
||||
* @param doc The document to which the file will belong.
|
||||
* @param embedder The embedder that will be used to embed the file.
|
||||
*/
|
||||
static of = (ref: PDFRef, doc: PDFDocument, embedder: FileEmbedder) =>
|
||||
new PDFEmbeddedFile(ref, doc, embedder);
|
||||
|
||||
/** The unique reference assigned to this embedded file within the document. */
|
||||
readonly ref: PDFRef;
|
||||
|
||||
/** The document to which this embedded file belongs. */
|
||||
readonly doc: PDFDocument;
|
||||
|
||||
private alreadyEmbedded = false;
|
||||
private readonly embedder: FileEmbedder;
|
||||
|
||||
private constructor(ref: PDFRef, doc: PDFDocument, embedder: FileEmbedder) {
|
||||
this.ref = ref;
|
||||
this.doc = doc;
|
||||
this.embedder = embedder;
|
||||
}
|
||||
|
||||
/**
|
||||
* > **NOTE:** You probably don't need to call this method directly. The
|
||||
* > [[PDFDocument.save]] and [[PDFDocument.saveAsBase64]] methods will
|
||||
* > automatically ensure all embeddable files get embedded.
|
||||
*
|
||||
* Embed this embeddable file in its document.
|
||||
*
|
||||
* @returns Resolves when the embedding is complete.
|
||||
*/
|
||||
async embed(): Promise<void> {
|
||||
if (!this.alreadyEmbedded) {
|
||||
const ref = await this.embedder.embedIntoContext(
|
||||
this.doc.context,
|
||||
this.ref,
|
||||
);
|
||||
|
||||
if (!this.doc.catalog.has(PDFName.of('Names'))) {
|
||||
this.doc.catalog.set(PDFName.of('Names'), this.doc.context.obj({}));
|
||||
}
|
||||
const Names = this.doc.catalog.lookup(PDFName.of('Names'), PDFDict);
|
||||
|
||||
if (!Names.has(PDFName.of('EmbeddedFiles'))) {
|
||||
Names.set(PDFName.of('EmbeddedFiles'), this.doc.context.obj({}));
|
||||
}
|
||||
const EmbeddedFiles = Names.lookup(PDFName.of('EmbeddedFiles'), PDFDict);
|
||||
|
||||
if (!EmbeddedFiles.has(PDFName.of('Names'))) {
|
||||
EmbeddedFiles.set(PDFName.of('Names'), this.doc.context.obj([]));
|
||||
}
|
||||
const EFNames = EmbeddedFiles.lookup(PDFName.of('Names'), PDFArray);
|
||||
|
||||
EFNames.push(PDFHexString.fromText(this.embedder.fileName));
|
||||
EFNames.push(ref);
|
||||
|
||||
/**
|
||||
* The AF-Tag is needed to achieve PDF-A3 compliance for embedded files
|
||||
*
|
||||
* The following document outlines the uses cases of the associated files (AF) tag.
|
||||
* See:
|
||||
* https://www.pdfa.org/wp-content/uploads/2018/10/PDF20_AN002-AF.pdf
|
||||
*/
|
||||
|
||||
if (!this.doc.catalog.has(PDFName.of('AF'))) {
|
||||
this.doc.catalog.set(PDFName.of('AF'), this.doc.context.obj([]));
|
||||
}
|
||||
const AF = this.doc.catalog.lookup(PDFName.of('AF'), PDFArray);
|
||||
AF.push(ref);
|
||||
|
||||
this.alreadyEmbedded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
104
node_modules/pdf-lib/src/api/PDFEmbeddedPage.ts
generated
vendored
Normal file
104
node_modules/pdf-lib/src/api/PDFEmbeddedPage.ts
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
import Embeddable from 'src/api/Embeddable';
|
||||
import PDFDocument from 'src/api/PDFDocument';
|
||||
import { PDFPageEmbedder, PDFRef } from 'src/core';
|
||||
import { assertIs } from 'src/utils';
|
||||
|
||||
/**
|
||||
* Represents a PDF page that has been embedded in a [[PDFDocument]].
|
||||
*/
|
||||
export default class PDFEmbeddedPage implements Embeddable {
|
||||
/**
|
||||
* > **NOTE:** You probably don't want to call this method directly. Instead,
|
||||
* > consider using the [[PDFDocument.embedPdf]] and
|
||||
* > [[PDFDocument.embedPage]] methods, which will create instances of
|
||||
* > [[PDFEmbeddedPage]] for you.
|
||||
*
|
||||
* Create an instance of [[PDFEmbeddedPage]] from an existing ref and embedder
|
||||
*
|
||||
* @param ref The unique reference for this embedded page.
|
||||
* @param doc The document to which the embedded page will belong.
|
||||
* @param embedder The embedder that will be used to embed the page.
|
||||
*/
|
||||
static of = (ref: PDFRef, doc: PDFDocument, embedder: PDFPageEmbedder) =>
|
||||
new PDFEmbeddedPage(ref, doc, embedder);
|
||||
|
||||
/** The unique reference assigned to this embedded page within the document. */
|
||||
readonly ref: PDFRef;
|
||||
|
||||
/** The document to which this embedded page belongs. */
|
||||
readonly doc: PDFDocument;
|
||||
|
||||
/** The width of this page in pixels. */
|
||||
readonly width: number;
|
||||
|
||||
/** The height of this page in pixels. */
|
||||
readonly height: number;
|
||||
|
||||
private alreadyEmbedded = false;
|
||||
private readonly embedder: PDFPageEmbedder;
|
||||
|
||||
private constructor(
|
||||
ref: PDFRef,
|
||||
doc: PDFDocument,
|
||||
embedder: PDFPageEmbedder,
|
||||
) {
|
||||
assertIs(ref, 'ref', [[PDFRef, 'PDFRef']]);
|
||||
assertIs(doc, 'doc', [[PDFDocument, 'PDFDocument']]);
|
||||
assertIs(embedder, 'embedder', [[PDFPageEmbedder, 'PDFPageEmbedder']]);
|
||||
|
||||
this.ref = ref;
|
||||
this.doc = doc;
|
||||
this.width = embedder.width;
|
||||
this.height = embedder.height;
|
||||
|
||||
this.embedder = embedder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the width and height of this page after being scaled by the
|
||||
* given `factor`. For example:
|
||||
* ```js
|
||||
* embeddedPage.width // => 500
|
||||
* embeddedPage.height // => 250
|
||||
*
|
||||
* const scaled = embeddedPage.scale(0.5)
|
||||
* scaled.width // => 250
|
||||
* scaled.height // => 125
|
||||
* ```
|
||||
* This operation is often useful before drawing a page with
|
||||
* [[PDFPage.drawPage]] to compute the `width` and `height` options.
|
||||
* @param factor The factor by which this page should be scaled.
|
||||
* @returns The width and height of the page after being scaled.
|
||||
*/
|
||||
scale(factor: number) {
|
||||
assertIs(factor, 'factor', ['number']);
|
||||
return { width: this.width * factor, height: this.height * factor };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width and height of this page. For example:
|
||||
* ```js
|
||||
* const { width, height } = embeddedPage.size()
|
||||
* ```
|
||||
* @returns The width and height of the page.
|
||||
*/
|
||||
size() {
|
||||
return this.scale(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* > **NOTE:** You probably don't need to call this method directly. The
|
||||
* > [[PDFDocument.save]] and [[PDFDocument.saveAsBase64]] methods will
|
||||
* > automatically ensure all embeddable pages get embedded.
|
||||
*
|
||||
* Embed this embeddable page in its document.
|
||||
*
|
||||
* @returns Resolves when the embedding is complete.
|
||||
*/
|
||||
async embed(): Promise<void> {
|
||||
if (!this.alreadyEmbedded) {
|
||||
await this.embedder.embedIntoContext(this.doc.context, this.ref);
|
||||
this.alreadyEmbedded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
154
node_modules/pdf-lib/src/api/PDFFont.ts
generated
vendored
Normal file
154
node_modules/pdf-lib/src/api/PDFFont.ts
generated
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
import Embeddable from 'src/api//Embeddable';
|
||||
import PDFDocument from 'src/api/PDFDocument';
|
||||
import {
|
||||
CustomFontEmbedder,
|
||||
PDFHexString,
|
||||
PDFRef,
|
||||
StandardFontEmbedder,
|
||||
} from 'src/core';
|
||||
import { assertIs, assertOrUndefined } from 'src/utils';
|
||||
|
||||
export type FontEmbedder = CustomFontEmbedder | StandardFontEmbedder;
|
||||
|
||||
/**
|
||||
* Represents a font that has been embedded in a [[PDFDocument]].
|
||||
*/
|
||||
export default class PDFFont implements Embeddable {
|
||||
/**
|
||||
* > **NOTE:** You probably don't want to call this method directly. Instead,
|
||||
* > consider using the [[PDFDocument.embedFont]] and
|
||||
* > [[PDFDocument.embedStandardFont]] methods, which will create instances
|
||||
* > of [[PDFFont]] for you.
|
||||
*
|
||||
* Create an instance of [[PDFFont]] from an existing ref and embedder
|
||||
*
|
||||
* @param ref The unique reference for this font.
|
||||
* @param doc The document to which the font will belong.
|
||||
* @param embedder The embedder that will be used to embed the font.
|
||||
*/
|
||||
static of = (ref: PDFRef, doc: PDFDocument, embedder: FontEmbedder) =>
|
||||
new PDFFont(ref, doc, embedder);
|
||||
|
||||
/** The unique reference assigned to this font within the document. */
|
||||
readonly ref: PDFRef;
|
||||
|
||||
/** The document to which this font belongs. */
|
||||
readonly doc: PDFDocument;
|
||||
|
||||
/** The name of this font. */
|
||||
readonly name: string;
|
||||
|
||||
private modified = true;
|
||||
private readonly embedder: FontEmbedder;
|
||||
|
||||
private constructor(ref: PDFRef, doc: PDFDocument, embedder: FontEmbedder) {
|
||||
assertIs(ref, 'ref', [[PDFRef, 'PDFRef']]);
|
||||
assertIs(doc, 'doc', [[PDFDocument, 'PDFDocument']]);
|
||||
assertIs(embedder, 'embedder', [
|
||||
[CustomFontEmbedder, 'CustomFontEmbedder'],
|
||||
[StandardFontEmbedder, 'StandardFontEmbedder'],
|
||||
]);
|
||||
|
||||
this.ref = ref;
|
||||
this.doc = doc;
|
||||
this.name = embedder.fontName;
|
||||
|
||||
this.embedder = embedder;
|
||||
}
|
||||
|
||||
/**
|
||||
* > **NOTE:** You probably don't need to call this method directly. The
|
||||
* > [[PDFPage.drawText]] method will automatically encode the text it is
|
||||
* > given.
|
||||
*
|
||||
* Encodes a string of text in this font.
|
||||
*
|
||||
* @param text The text to be encoded.
|
||||
* @returns The encoded text as a hex string.
|
||||
*/
|
||||
encodeText(text: string): PDFHexString {
|
||||
assertIs(text, 'text', ['string']);
|
||||
this.modified = true;
|
||||
return this.embedder.encodeText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure the width of a string of text drawn in this font at a given size.
|
||||
* For example:
|
||||
* ```js
|
||||
* const width = font.widthOfTextAtSize('Foo Bar Qux Baz', 36)
|
||||
* ```
|
||||
* @param text The string of text to be measured.
|
||||
* @param size The font size to be used for this measurement.
|
||||
* @returns The width of the string of text when drawn in this font at the
|
||||
* given size.
|
||||
*/
|
||||
widthOfTextAtSize(text: string, size: number): number {
|
||||
assertIs(text, 'text', ['string']);
|
||||
assertIs(size, 'size', ['number']);
|
||||
return this.embedder.widthOfTextAtSize(text, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure the height of this font at a given size. For example:
|
||||
* ```js
|
||||
* const height = font.heightAtSize(24)
|
||||
* ```
|
||||
*
|
||||
* The `options.descender` value controls whether or not the font's
|
||||
* descender is included in the height calculation.
|
||||
*
|
||||
* @param size The font size to be used for this measurement.
|
||||
* @param options The options to be used when computing this measurement.
|
||||
* @returns The height of this font at the given size.
|
||||
*/
|
||||
heightAtSize(size: number, options?: { descender?: boolean }): number {
|
||||
assertIs(size, 'size', ['number']);
|
||||
assertOrUndefined(options?.descender, 'options.descender', ['boolean']);
|
||||
return this.embedder.heightOfFontAtSize(size, {
|
||||
descender: options?.descender ?? true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the font size at which this font is a given height. For example:
|
||||
* ```js
|
||||
* const fontSize = font.sizeAtHeight(12)
|
||||
* ```
|
||||
* @param height The height to be used for this calculation.
|
||||
* @returns The font size at which this font is the given height.
|
||||
*/
|
||||
sizeAtHeight(height: number): number {
|
||||
assertIs(height, 'height', ['number']);
|
||||
return this.embedder.sizeOfFontAtHeight(height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of unicode code points that can be represented by this font.
|
||||
* @returns The set of unicode code points supported by this font.
|
||||
*/
|
||||
getCharacterSet(): number[] {
|
||||
if (this.embedder instanceof StandardFontEmbedder) {
|
||||
return this.embedder.encoding.supportedCodePoints;
|
||||
} else {
|
||||
return this.embedder.font.characterSet;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* > **NOTE:** You probably don't need to call this method directly. The
|
||||
* > [[PDFDocument.save]] and [[PDFDocument.saveAsBase64]] methods will
|
||||
* > automatically ensure all fonts get embedded.
|
||||
*
|
||||
* Embed this font in its document.
|
||||
*
|
||||
* @returns Resolves when the embedding is complete.
|
||||
*/
|
||||
async embed(): Promise<void> {
|
||||
// TODO: Cleanup orphan embedded objects if a font is embedded multiple times...
|
||||
if (this.modified) {
|
||||
await this.embedder.embedIntoContext(this.doc.context, this.ref);
|
||||
this.modified = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
143
node_modules/pdf-lib/src/api/PDFImage.ts
generated
vendored
Normal file
143
node_modules/pdf-lib/src/api/PDFImage.ts
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
import Embeddable from 'src/api/Embeddable';
|
||||
import PDFDocument from 'src/api/PDFDocument';
|
||||
import { JpegEmbedder, PDFRef, PngEmbedder } from 'src/core';
|
||||
import { assertIs } from 'src/utils';
|
||||
|
||||
export type ImageEmbedder = JpegEmbedder | PngEmbedder;
|
||||
|
||||
/**
|
||||
* Represents an image that has been embedded in a [[PDFDocument]].
|
||||
*/
|
||||
export default class PDFImage implements Embeddable {
|
||||
/**
|
||||
* > **NOTE:** You probably don't want to call this method directly. Instead,
|
||||
* > consider using the [[PDFDocument.embedPng]] and [[PDFDocument.embedJpg]]
|
||||
* > methods, which will create instances of [[PDFImage]] for you.
|
||||
*
|
||||
* Create an instance of [[PDFImage]] from an existing ref and embedder
|
||||
*
|
||||
* @param ref The unique reference for this image.
|
||||
* @param doc The document to which the image will belong.
|
||||
* @param embedder The embedder that will be used to embed the image.
|
||||
*/
|
||||
static of = (ref: PDFRef, doc: PDFDocument, embedder: ImageEmbedder) =>
|
||||
new PDFImage(ref, doc, embedder);
|
||||
|
||||
/** The unique reference assigned to this image within the document. */
|
||||
readonly ref: PDFRef;
|
||||
|
||||
/** The document to which this image belongs. */
|
||||
readonly doc: PDFDocument;
|
||||
|
||||
/** The width of this image in pixels. */
|
||||
readonly width: number;
|
||||
|
||||
/** The height of this image in pixels. */
|
||||
readonly height: number;
|
||||
|
||||
private embedder: ImageEmbedder | undefined;
|
||||
private embedTask: Promise<PDFRef> | undefined;
|
||||
|
||||
private constructor(ref: PDFRef, doc: PDFDocument, embedder: ImageEmbedder) {
|
||||
assertIs(ref, 'ref', [[PDFRef, 'PDFRef']]);
|
||||
assertIs(doc, 'doc', [[PDFDocument, 'PDFDocument']]);
|
||||
assertIs(embedder, 'embedder', [
|
||||
[JpegEmbedder, 'JpegEmbedder'],
|
||||
[PngEmbedder, 'PngEmbedder'],
|
||||
]);
|
||||
|
||||
this.ref = ref;
|
||||
this.doc = doc;
|
||||
this.width = embedder.width;
|
||||
this.height = embedder.height;
|
||||
|
||||
this.embedder = embedder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the width and height of this image after being scaled by the
|
||||
* given `factor`. For example:
|
||||
* ```js
|
||||
* image.width // => 500
|
||||
* image.height // => 250
|
||||
*
|
||||
* const scaled = image.scale(0.5)
|
||||
* scaled.width // => 250
|
||||
* scaled.height // => 125
|
||||
* ```
|
||||
* This operation is often useful before drawing an image with
|
||||
* [[PDFPage.drawImage]] to compute the `width` and `height` options.
|
||||
* @param factor The factor by which this image should be scaled.
|
||||
* @returns The width and height of the image after being scaled.
|
||||
*/
|
||||
scale(factor: number) {
|
||||
assertIs(factor, 'factor', ['number']);
|
||||
return { width: this.width * factor, height: this.height * factor };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width and height of this image after scaling it as large as
|
||||
* possible while maintaining its aspect ratio and not exceeding the
|
||||
* specified `width` and `height`. For example:
|
||||
* ```
|
||||
* image.width // => 500
|
||||
* image.height // => 250
|
||||
*
|
||||
* const scaled = image.scaleToFit(750, 1000)
|
||||
* scaled.width // => 750
|
||||
* scaled.height // => 375
|
||||
* ```
|
||||
* The `width` and `height` parameters can also be thought of as the width
|
||||
* and height of a box that the scaled image must fit within.
|
||||
* @param width The bounding box's width.
|
||||
* @param height The bounding box's height.
|
||||
* @returns The width and height of the image after being scaled.
|
||||
*/
|
||||
scaleToFit(width: number, height: number) {
|
||||
assertIs(width, 'width', ['number']);
|
||||
assertIs(height, 'height', ['number']);
|
||||
|
||||
const imgWidthScale = width / this.width;
|
||||
const imgHeightScale = height / this.height;
|
||||
const scale = Math.min(imgWidthScale, imgHeightScale);
|
||||
|
||||
return this.scale(scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width and height of this image. For example:
|
||||
* ```js
|
||||
* const { width, height } = image.size()
|
||||
* ```
|
||||
* @returns The width and height of the image.
|
||||
*/
|
||||
size() {
|
||||
return this.scale(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* > **NOTE:** You probably don't need to call this method directly. The
|
||||
* > [[PDFDocument.save]] and [[PDFDocument.saveAsBase64]] methods will
|
||||
* > automatically ensure all images get embedded.
|
||||
*
|
||||
* Embed this image in its document.
|
||||
*
|
||||
* @returns Resolves when the embedding is complete.
|
||||
*/
|
||||
async embed(): Promise<void> {
|
||||
if (!this.embedder) return;
|
||||
|
||||
// The image should only be embedded once. If there's a pending embed
|
||||
// operation then wait on it. Otherwise we need to start the embed.
|
||||
if (!this.embedTask) {
|
||||
const { doc, ref } = this;
|
||||
this.embedTask = this.embedder.embedIntoContext(doc.context, ref);
|
||||
}
|
||||
await this.embedTask;
|
||||
|
||||
// We clear `this.embedder` so that the indirectly referenced image data
|
||||
// can be garbage collected, thus avoiding a memory leak.
|
||||
// See https://github.com/Hopding/pdf-lib/pull/1032/files.
|
||||
this.embedder = undefined;
|
||||
}
|
||||
}
|
||||
82
node_modules/pdf-lib/src/api/PDFJavaScript.ts
generated
vendored
Normal file
82
node_modules/pdf-lib/src/api/PDFJavaScript.ts
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
import Embeddable from 'src/api/Embeddable';
|
||||
import PDFDocument from 'src/api/PDFDocument';
|
||||
import JavaScriptEmbedder from 'src/core/embedders/JavaScriptEmbedder';
|
||||
import { PDFName, PDFArray, PDFDict, PDFHexString, PDFRef } from 'src/core';
|
||||
|
||||
/**
|
||||
* Represents JavaScript that has been embedded in a [[PDFDocument]].
|
||||
*/
|
||||
export default class PDFJavaScript implements Embeddable {
|
||||
/**
|
||||
* > **NOTE:** You probably don't want to call this method directly. Instead,
|
||||
* > consider using the [[PDFDocument.addJavaScript]] method, which will
|
||||
* create instances of [[PDFJavaScript]] for you.
|
||||
*
|
||||
* Create an instance of [[PDFJavaScript]] from an existing ref and script
|
||||
*
|
||||
* @param ref The unique reference for this script.
|
||||
* @param doc The document to which the script will belong.
|
||||
* @param embedder The embedder that will be used to embed the script.
|
||||
*/
|
||||
static of = (ref: PDFRef, doc: PDFDocument, embedder: JavaScriptEmbedder) =>
|
||||
new PDFJavaScript(ref, doc, embedder);
|
||||
|
||||
/** The unique reference assigned to this embedded script within the document. */
|
||||
readonly ref: PDFRef;
|
||||
|
||||
/** The document to which this embedded script belongs. */
|
||||
readonly doc: PDFDocument;
|
||||
|
||||
private alreadyEmbedded = false;
|
||||
private readonly embedder: JavaScriptEmbedder;
|
||||
|
||||
private constructor(
|
||||
ref: PDFRef,
|
||||
doc: PDFDocument,
|
||||
embedder: JavaScriptEmbedder,
|
||||
) {
|
||||
this.ref = ref;
|
||||
this.doc = doc;
|
||||
this.embedder = embedder;
|
||||
}
|
||||
|
||||
/**
|
||||
* > **NOTE:** You probably don't need to call this method directly. The
|
||||
* > [[PDFDocument.save]] and [[PDFDocument.saveAsBase64]] methods will
|
||||
* > automatically ensure all JavaScripts get embedded.
|
||||
*
|
||||
* Embed this JavaScript in its document.
|
||||
*
|
||||
* @returns Resolves when the embedding is complete.
|
||||
*/
|
||||
async embed(): Promise<void> {
|
||||
if (!this.alreadyEmbedded) {
|
||||
const { catalog, context } = this.doc;
|
||||
|
||||
const ref = await this.embedder.embedIntoContext(
|
||||
this.doc.context,
|
||||
this.ref,
|
||||
);
|
||||
|
||||
if (!catalog.has(PDFName.of('Names'))) {
|
||||
catalog.set(PDFName.of('Names'), context.obj({}));
|
||||
}
|
||||
const Names = catalog.lookup(PDFName.of('Names'), PDFDict);
|
||||
|
||||
if (!Names.has(PDFName.of('JavaScript'))) {
|
||||
Names.set(PDFName.of('JavaScript'), context.obj({}));
|
||||
}
|
||||
const Javascript = Names.lookup(PDFName.of('JavaScript'), PDFDict);
|
||||
|
||||
if (!Javascript.has(PDFName.of('Names'))) {
|
||||
Javascript.set(PDFName.of('Names'), context.obj([]));
|
||||
}
|
||||
const JSNames = Javascript.lookup(PDFName.of('Names'), PDFArray);
|
||||
|
||||
JSNames.push(PDFHexString.fromText(this.embedder.scriptName));
|
||||
JSNames.push(ref);
|
||||
|
||||
this.alreadyEmbedded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
1621
node_modules/pdf-lib/src/api/PDFPage.ts
generated
vendored
Normal file
1621
node_modules/pdf-lib/src/api/PDFPage.ts
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
158
node_modules/pdf-lib/src/api/PDFPageOptions.ts
generated
vendored
Normal file
158
node_modules/pdf-lib/src/api/PDFPageOptions.ts
generated
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
import { Color } from 'src/api/colors';
|
||||
import PDFFont from 'src/api/PDFFont';
|
||||
import { Rotation } from 'src/api/rotations';
|
||||
import { LineCapStyle } from 'src/api/operators';
|
||||
|
||||
export enum BlendMode {
|
||||
Normal = 'Normal',
|
||||
Multiply = 'Multiply',
|
||||
Screen = 'Screen',
|
||||
Overlay = 'Overlay',
|
||||
Darken = 'Darken',
|
||||
Lighten = 'Lighten',
|
||||
ColorDodge = 'ColorDodge',
|
||||
ColorBurn = 'ColorBurn',
|
||||
HardLight = 'HardLight',
|
||||
SoftLight = 'SoftLight',
|
||||
Difference = 'Difference',
|
||||
Exclusion = 'Exclusion',
|
||||
}
|
||||
|
||||
export interface PDFPageDrawTextOptions {
|
||||
color?: Color;
|
||||
opacity?: number;
|
||||
blendMode?: BlendMode;
|
||||
font?: PDFFont;
|
||||
size?: number;
|
||||
rotate?: Rotation;
|
||||
xSkew?: Rotation;
|
||||
ySkew?: Rotation;
|
||||
x?: number;
|
||||
y?: number;
|
||||
lineHeight?: number;
|
||||
maxWidth?: number;
|
||||
wordBreaks?: string[];
|
||||
}
|
||||
|
||||
export interface PDFPageDrawImageOptions {
|
||||
x?: number;
|
||||
y?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
rotate?: Rotation;
|
||||
xSkew?: Rotation;
|
||||
ySkew?: Rotation;
|
||||
opacity?: number;
|
||||
blendMode?: BlendMode;
|
||||
}
|
||||
|
||||
export interface PDFPageDrawPageOptions {
|
||||
x?: number;
|
||||
y?: number;
|
||||
xScale?: number;
|
||||
yScale?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
rotate?: Rotation;
|
||||
xSkew?: Rotation;
|
||||
ySkew?: Rotation;
|
||||
opacity?: number;
|
||||
blendMode?: BlendMode;
|
||||
}
|
||||
|
||||
export interface PDFPageDrawSVGOptions {
|
||||
x?: number;
|
||||
y?: number;
|
||||
scale?: number;
|
||||
rotate?: Rotation;
|
||||
borderWidth?: number;
|
||||
color?: Color;
|
||||
opacity?: number;
|
||||
borderColor?: Color;
|
||||
borderOpacity?: number;
|
||||
borderDashArray?: number[];
|
||||
borderDashPhase?: number;
|
||||
borderLineCap?: LineCapStyle;
|
||||
blendMode?: BlendMode;
|
||||
}
|
||||
|
||||
export interface PDFPageDrawLineOptions {
|
||||
start: { x: number; y: number };
|
||||
end: { x: number; y: number };
|
||||
thickness?: number;
|
||||
color?: Color;
|
||||
opacity?: number;
|
||||
lineCap?: LineCapStyle;
|
||||
dashArray?: number[];
|
||||
dashPhase?: number;
|
||||
blendMode?: BlendMode;
|
||||
}
|
||||
|
||||
export interface PDFPageDrawRectangleOptions {
|
||||
x?: number;
|
||||
y?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
rotate?: Rotation;
|
||||
xSkew?: Rotation;
|
||||
ySkew?: Rotation;
|
||||
borderWidth?: number;
|
||||
color?: Color;
|
||||
opacity?: number;
|
||||
borderColor?: Color;
|
||||
borderOpacity?: number;
|
||||
borderDashArray?: number[];
|
||||
borderDashPhase?: number;
|
||||
borderLineCap?: LineCapStyle;
|
||||
blendMode?: BlendMode;
|
||||
}
|
||||
|
||||
export interface PDFPageDrawSquareOptions {
|
||||
x?: number;
|
||||
y?: number;
|
||||
size?: number;
|
||||
rotate?: Rotation;
|
||||
xSkew?: Rotation;
|
||||
ySkew?: Rotation;
|
||||
borderWidth?: number;
|
||||
color?: Color;
|
||||
opacity?: number;
|
||||
borderColor?: Color;
|
||||
borderOpacity?: number;
|
||||
borderDashArray?: number[];
|
||||
borderDashPhase?: number;
|
||||
borderLineCap?: LineCapStyle;
|
||||
blendMode?: BlendMode;
|
||||
}
|
||||
|
||||
export interface PDFPageDrawEllipseOptions {
|
||||
x?: number;
|
||||
y?: number;
|
||||
xScale?: number;
|
||||
yScale?: number;
|
||||
rotate?: Rotation;
|
||||
color?: Color;
|
||||
opacity?: number;
|
||||
borderColor?: Color;
|
||||
borderOpacity?: number;
|
||||
borderWidth?: number;
|
||||
borderDashArray?: number[];
|
||||
borderDashPhase?: number;
|
||||
borderLineCap?: LineCapStyle;
|
||||
blendMode?: BlendMode;
|
||||
}
|
||||
|
||||
export interface PDFPageDrawCircleOptions {
|
||||
x?: number;
|
||||
y?: number;
|
||||
size?: number;
|
||||
color?: Color;
|
||||
opacity?: number;
|
||||
borderColor?: Color;
|
||||
borderOpacity?: number;
|
||||
borderWidth?: number;
|
||||
borderDashArray?: number[];
|
||||
borderDashPhase?: number;
|
||||
borderLineCap?: LineCapStyle;
|
||||
blendMode?: BlendMode;
|
||||
}
|
||||
16
node_modules/pdf-lib/src/api/StandardFonts.ts
generated
vendored
Normal file
16
node_modules/pdf-lib/src/api/StandardFonts.ts
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
export enum StandardFonts {
|
||||
Courier = 'Courier',
|
||||
CourierBold = 'Courier-Bold',
|
||||
CourierOblique = 'Courier-Oblique',
|
||||
CourierBoldOblique = 'Courier-BoldOblique',
|
||||
Helvetica = 'Helvetica',
|
||||
HelveticaBold = 'Helvetica-Bold',
|
||||
HelveticaOblique = 'Helvetica-Oblique',
|
||||
HelveticaBoldOblique = 'Helvetica-BoldOblique',
|
||||
TimesRoman = 'Times-Roman',
|
||||
TimesRomanBold = 'Times-Bold',
|
||||
TimesRomanItalic = 'Times-Italic',
|
||||
TimesRomanBoldItalic = 'Times-BoldItalic',
|
||||
Symbol = 'Symbol',
|
||||
ZapfDingbats = 'ZapfDingbats',
|
||||
}
|
||||
104
node_modules/pdf-lib/src/api/colors.ts
generated
vendored
Normal file
104
node_modules/pdf-lib/src/api/colors.ts
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
import {
|
||||
setFillingCmykColor,
|
||||
setFillingGrayscaleColor,
|
||||
setFillingRgbColor,
|
||||
setStrokingCmykColor,
|
||||
setStrokingGrayscaleColor,
|
||||
setStrokingRgbColor,
|
||||
} from 'src/api/operators';
|
||||
import { assertRange, error } from 'src/utils';
|
||||
|
||||
export enum ColorTypes {
|
||||
Grayscale = 'Grayscale',
|
||||
RGB = 'RGB',
|
||||
CMYK = 'CMYK',
|
||||
}
|
||||
|
||||
export interface Grayscale {
|
||||
type: ColorTypes.Grayscale;
|
||||
gray: number;
|
||||
}
|
||||
|
||||
export interface RGB {
|
||||
type: ColorTypes.RGB;
|
||||
red: number;
|
||||
green: number;
|
||||
blue: number;
|
||||
}
|
||||
|
||||
export interface CMYK {
|
||||
type: ColorTypes.CMYK;
|
||||
cyan: number;
|
||||
magenta: number;
|
||||
yellow: number;
|
||||
key: number;
|
||||
}
|
||||
|
||||
export type Color = Grayscale | RGB | CMYK;
|
||||
|
||||
export const grayscale = (gray: number): Grayscale => {
|
||||
assertRange(gray, 'gray', 0.0, 1.0);
|
||||
return { type: ColorTypes.Grayscale, gray };
|
||||
};
|
||||
|
||||
export const rgb = (red: number, green: number, blue: number): RGB => {
|
||||
assertRange(red, 'red', 0, 1);
|
||||
assertRange(green, 'green', 0, 1);
|
||||
assertRange(blue, 'blue', 0, 1);
|
||||
return { type: ColorTypes.RGB, red, green, blue };
|
||||
};
|
||||
|
||||
export const cmyk = (
|
||||
cyan: number,
|
||||
magenta: number,
|
||||
yellow: number,
|
||||
key: number,
|
||||
): CMYK => {
|
||||
assertRange(cyan, 'cyan', 0, 1);
|
||||
assertRange(magenta, 'magenta', 0, 1);
|
||||
assertRange(yellow, 'yellow', 0, 1);
|
||||
assertRange(key, 'key', 0, 1);
|
||||
return { type: ColorTypes.CMYK, cyan, magenta, yellow, key };
|
||||
};
|
||||
|
||||
const { Grayscale, RGB, CMYK } = ColorTypes;
|
||||
|
||||
// prettier-ignore
|
||||
export const setFillingColor = (color: Color) =>
|
||||
color.type === Grayscale ? setFillingGrayscaleColor(color.gray)
|
||||
: color.type === RGB ? setFillingRgbColor(color.red, color.green, color.blue)
|
||||
: color.type === CMYK ? setFillingCmykColor(color.cyan, color.magenta, color.yellow, color.key)
|
||||
: error(`Invalid color: ${JSON.stringify(color)}`);
|
||||
|
||||
// prettier-ignore
|
||||
export const setStrokingColor = (color: Color) =>
|
||||
color.type === Grayscale ? setStrokingGrayscaleColor(color.gray)
|
||||
: color.type === RGB ? setStrokingRgbColor(color.red, color.green, color.blue)
|
||||
: color.type === CMYK ? setStrokingCmykColor(color.cyan, color.magenta, color.yellow, color.key)
|
||||
: error(`Invalid color: ${JSON.stringify(color)}`);
|
||||
|
||||
// prettier-ignore
|
||||
export const componentsToColor = (comps?: number[], scale = 1) => (
|
||||
comps?.length === 1 ? grayscale(
|
||||
comps[0] * scale,
|
||||
)
|
||||
: comps?.length === 3 ? rgb(
|
||||
comps[0] * scale,
|
||||
comps[1] * scale,
|
||||
comps[2] * scale,
|
||||
)
|
||||
: comps?.length === 4 ? cmyk(
|
||||
comps[0] * scale,
|
||||
comps[1] * scale,
|
||||
comps[2] * scale,
|
||||
comps[3] * scale,
|
||||
)
|
||||
: undefined
|
||||
);
|
||||
|
||||
// prettier-ignore
|
||||
export const colorToComponents = (color: Color) =>
|
||||
color.type === Grayscale ? [color.gray]
|
||||
: color.type === RGB ? [color.red, color.green, color.blue]
|
||||
: color.type === CMYK ? [color.cyan, color.magenta, color.yellow, color.key]
|
||||
: error(`Invalid color: ${JSON.stringify(color)}`);
|
||||
111
node_modules/pdf-lib/src/api/errors.ts
generated
vendored
Normal file
111
node_modules/pdf-lib/src/api/errors.ts
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
// tslint:disable: max-classes-per-file
|
||||
|
||||
// TODO: Include link to documentation with example
|
||||
export class EncryptedPDFError extends Error {
|
||||
constructor() {
|
||||
const msg =
|
||||
'Input document to `PDFDocument.load` is encrypted. You can use `PDFDocument.load(..., { ignoreEncryption: true })` if you wish to load the document anyways.';
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Include link to documentation with example
|
||||
export class FontkitNotRegisteredError extends Error {
|
||||
constructor() {
|
||||
const msg =
|
||||
'Input to `PDFDocument.embedFont` was a custom font, but no `fontkit` instance was found. You must register a `fontkit` instance with `PDFDocument.registerFontkit(...)` before embedding custom fonts.';
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Include link to documentation with example
|
||||
export class ForeignPageError extends Error {
|
||||
constructor() {
|
||||
const msg =
|
||||
'A `page` passed to `PDFDocument.addPage` or `PDFDocument.insertPage` was from a different (foreign) PDF document. If you want to copy pages from one PDFDocument to another, you must use `PDFDocument.copyPages(...)` to copy the pages before adding or inserting them.';
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Include link to documentation with example
|
||||
export class RemovePageFromEmptyDocumentError extends Error {
|
||||
constructor() {
|
||||
const msg =
|
||||
'PDFDocument has no pages so `PDFDocument.removePage` cannot be called';
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class NoSuchFieldError extends Error {
|
||||
constructor(name: string) {
|
||||
const msg = `PDFDocument has no form field with the name "${name}"`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class UnexpectedFieldTypeError extends Error {
|
||||
constructor(name: string, expected: any, actual: any) {
|
||||
const expectedType = expected?.name;
|
||||
const actualType = actual?.constructor?.name ?? actual;
|
||||
const msg =
|
||||
`Expected field "${name}" to be of type ${expectedType}, ` +
|
||||
`but it is actually of type ${actualType}`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class MissingOnValueCheckError extends Error {
|
||||
constructor(onValue: any) {
|
||||
const msg = `Failed to select check box due to missing onValue: "${onValue}"`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class FieldAlreadyExistsError extends Error {
|
||||
constructor(name: string) {
|
||||
const msg = `A field already exists with the specified name: "${name}"`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidFieldNamePartError extends Error {
|
||||
constructor(namePart: string) {
|
||||
const msg = `Field name contains invalid component: "${namePart}"`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class FieldExistsAsNonTerminalError extends Error {
|
||||
constructor(name: string) {
|
||||
const msg = `A non-terminal field already exists with the specified name: "${name}"`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class RichTextFieldReadError extends Error {
|
||||
constructor(fieldName: string) {
|
||||
const msg = `Reading rich text fields is not supported: Attempted to read rich text field: ${fieldName}`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class CombedTextLayoutError extends Error {
|
||||
constructor(lineLength: number, cellCount: number) {
|
||||
const msg = `Failed to layout combed text as lineLength=${lineLength} is greater than cellCount=${cellCount}`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExceededMaxLengthError extends Error {
|
||||
constructor(textLength: number, maxLength: number, name: string) {
|
||||
const msg = `Attempted to set text with length=${textLength} for TextField with maxLength=${maxLength} and name=${name}`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidMaxLengthError extends Error {
|
||||
constructor(textLength: number, maxLength: number, name: string) {
|
||||
const msg = `Attempted to set maxLength=${maxLength}, which is less than ${textLength}, the length of this field's current value (name=${name})`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
267
node_modules/pdf-lib/src/api/form/PDFButton.ts
generated
vendored
Normal file
267
node_modules/pdf-lib/src/api/form/PDFButton.ts
generated
vendored
Normal file
@@ -0,0 +1,267 @@
|
||||
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 { ImageAlignment } from 'src/api/image/alignment';
|
||||
import {
|
||||
AppearanceProviderFor,
|
||||
normalizeAppearance,
|
||||
defaultButtonAppearanceProvider,
|
||||
} from 'src/api/form/appearances';
|
||||
import PDFField, {
|
||||
FieldAppearanceOptions,
|
||||
assertFieldAppearanceOptions,
|
||||
} from 'src/api/form/PDFField';
|
||||
import { rgb } from 'src/api/colors';
|
||||
import { degrees } from 'src/api/rotations';
|
||||
|
||||
import {
|
||||
PDFRef,
|
||||
PDFStream,
|
||||
PDFAcroPushButton,
|
||||
PDFWidgetAnnotation,
|
||||
} from 'src/core';
|
||||
import { assertIs, assertOrUndefined, assertPositive } from 'src/utils';
|
||||
|
||||
/**
|
||||
* Represents a button field of a [[PDFForm]].
|
||||
*
|
||||
* [[PDFButton]] fields are interactive controls that users can click with their
|
||||
* mouse. This type of [[PDFField]] is not stateful. The purpose of a button
|
||||
* is to perform an action when the user clicks on it, such as opening a print
|
||||
* modal or resetting the form. Buttons are typically rectangular in shape and
|
||||
* have a text label describing the action that they perform when clicked.
|
||||
*/
|
||||
export default class PDFButton extends PDFField {
|
||||
/**
|
||||
* > **NOTE:** You probably don't want to call this method directly. Instead,
|
||||
* > consider using the [[PDFForm.getButton]] method, which will create an
|
||||
* > instance of [[PDFButton]] for you.
|
||||
*
|
||||
* Create an instance of [[PDFButton]] from an existing acroPushButton and ref
|
||||
*
|
||||
* @param acroPushButton The underlying `PDFAcroPushButton` for this button.
|
||||
* @param ref The unique reference for this button.
|
||||
* @param doc The document to which this button will belong.
|
||||
*/
|
||||
static of = (
|
||||
acroPushButton: PDFAcroPushButton,
|
||||
ref: PDFRef,
|
||||
doc: PDFDocument,
|
||||
) => new PDFButton(acroPushButton, ref, doc);
|
||||
|
||||
/** The low-level PDFAcroPushButton wrapped by this button. */
|
||||
readonly acroField: PDFAcroPushButton;
|
||||
|
||||
private constructor(
|
||||
acroPushButton: PDFAcroPushButton,
|
||||
ref: PDFRef,
|
||||
doc: PDFDocument,
|
||||
) {
|
||||
super(acroPushButton, ref, doc);
|
||||
|
||||
assertIs(acroPushButton, 'acroButton', [
|
||||
[PDFAcroPushButton, 'PDFAcroPushButton'],
|
||||
]);
|
||||
|
||||
this.acroField = acroPushButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an image inside the bounds of this button's widgets. For example:
|
||||
* ```js
|
||||
* const pngImage = await pdfDoc.embedPng(...)
|
||||
* const button = form.getButton('some.button.field')
|
||||
* button.setImage(pngImage, ImageAlignment.Center)
|
||||
* ```
|
||||
* This will update the appearances streams for each of this button's widgets.
|
||||
* @param image The image that should be displayed.
|
||||
* @param alignment The alignment of the image.
|
||||
*/
|
||||
setImage(image: PDFImage, alignment = ImageAlignment.Center) {
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the font size for this field. Larger font sizes will result in larger
|
||||
* text being displayed when PDF readers render this button. Font sizes may
|
||||
* be integer or floating point numbers. Supplying a negative font size will
|
||||
* cause this method to throw an error.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* const button = form.getButton('some.button.field')
|
||||
* button.setFontSize(4)
|
||||
* button.setFontSize(15.7)
|
||||
* ```
|
||||
*
|
||||
* > This method depends upon the existence of a default appearance
|
||||
* > (`/DA`) string. If this field does not have a default appearance string,
|
||||
* > or that string does not contain a font size (via the `Tf` operator),
|
||||
* > then this method will throw an error.
|
||||
*
|
||||
* @param fontSize The font size to be used when rendering text in this field.
|
||||
*/
|
||||
setFontSize(fontSize: number) {
|
||||
assertPositive(fontSize, 'fontSize');
|
||||
this.acroField.setFontSize(fontSize);
|
||||
this.markAsDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show this button on the specified page with the given text. For example:
|
||||
* ```js
|
||||
* const ubuntuFont = await pdfDoc.embedFont(ubuntuFontBytes)
|
||||
* const page = pdfDoc.addPage()
|
||||
*
|
||||
* const form = pdfDoc.getForm()
|
||||
* const button = form.createButton('some.button.field')
|
||||
*
|
||||
* button.addToPage('Do Stuff', page, {
|
||||
* x: 50,
|
||||
* y: 75,
|
||||
* width: 200,
|
||||
* height: 100,
|
||||
* textColor: rgb(1, 0, 0),
|
||||
* backgroundColor: rgb(0, 1, 0),
|
||||
* borderColor: rgb(0, 0, 1),
|
||||
* borderWidth: 2,
|
||||
* rotate: degrees(90),
|
||||
* font: ubuntuFont,
|
||||
* })
|
||||
* ```
|
||||
* This will create a new widget for this button field.
|
||||
* @param text The text to be displayed for this button widget.
|
||||
* @param page The page to which this button widget should be added.
|
||||
* @param options The options to be used when adding this button widget.
|
||||
*/
|
||||
addToPage(
|
||||
// TODO: This needs to be optional, e.g. for image buttons
|
||||
text: string,
|
||||
page: PDFPage,
|
||||
options?: FieldAppearanceOptions,
|
||||
) {
|
||||
assertOrUndefined(text, 'text', ['string']);
|
||||
assertOrUndefined(page, 'page', [[PDFPage, 'PDFPage']]);
|
||||
assertFieldAppearanceOptions(options);
|
||||
|
||||
// Create a widget for this button
|
||||
const widget = this.createWidget({
|
||||
x: (options?.x ?? 0) - (options?.borderWidth ?? 0) / 2,
|
||||
y: (options?.y ?? 0) - (options?.borderWidth ?? 0) / 2,
|
||||
width: options?.width ?? 100,
|
||||
height: options?.height ?? 50,
|
||||
textColor: options?.textColor ?? rgb(0, 0, 0),
|
||||
backgroundColor: options?.backgroundColor ?? rgb(0.75, 0.75, 0.75),
|
||||
borderColor: options?.borderColor,
|
||||
borderWidth: options?.borderWidth ?? 0,
|
||||
rotate: options?.rotate ?? degrees(0),
|
||||
caption: text,
|
||||
hidden: options?.hidden,
|
||||
page: page.ref,
|
||||
});
|
||||
const widgetRef = this.doc.context.register(widget.dict);
|
||||
|
||||
// Add widget to this field
|
||||
this.acroField.addWidget(widgetRef);
|
||||
|
||||
// Set appearance streams for widget
|
||||
const font = options?.font ?? this.doc.getForm().getDefaultFont();
|
||||
this.updateWidgetAppearance(widget, font);
|
||||
|
||||
// Add widget to the given page
|
||||
page.node.addAnnot(widgetRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if this button has been marked as dirty, or if any of this
|
||||
* button's widgets do not have an appearance stream. For example:
|
||||
* ```js
|
||||
* const button = form.getButton('some.button.field')
|
||||
* if (button.needsAppearancesUpdate()) console.log('Needs update')
|
||||
* ```
|
||||
* @returns Whether or not this button needs an appearance update.
|
||||
*/
|
||||
needsAppearancesUpdate(): boolean {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the appearance streams for each of this button's widgets using
|
||||
* the default appearance provider for buttons. For example:
|
||||
* ```js
|
||||
* const helvetica = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||
* const button = form.getButton('some.button.field')
|
||||
* button.defaultUpdateAppearances(helvetica)
|
||||
* ```
|
||||
* @param font The font to be used for creating the appearance streams.
|
||||
*/
|
||||
defaultUpdateAppearances(font: PDFFont) {
|
||||
assertIs(font, 'font', [[PDFFont, 'PDFFont']]);
|
||||
this.updateAppearances(font);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the appearance streams for each of this button's widgets using
|
||||
* the given appearance provider. If no `provider` is passed, the default
|
||||
* appearance provider for buttons will be used. For example:
|
||||
* ```js
|
||||
* const helvetica = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||
* const button = form.getButton('some.button.field')
|
||||
* button.updateAppearances(helvetica, (field, widget, font) => {
|
||||
* ...
|
||||
* return {
|
||||
* normal: drawButton(...),
|
||||
* down: drawButton(...),
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
* @param font The font to be used for creating the appearance streams.
|
||||
* @param provider Optionally, the appearance provider to be used for
|
||||
* generating the contents of the appearance streams.
|
||||
*/
|
||||
updateAppearances(
|
||||
font: PDFFont,
|
||||
provider?: AppearanceProviderFor<PDFButton>,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private updateWidgetAppearance(
|
||||
widget: PDFWidgetAnnotation,
|
||||
font: PDFFont,
|
||||
provider?: AppearanceProviderFor<PDFButton>,
|
||||
) {
|
||||
const apProvider = provider ?? defaultButtonAppearanceProvider;
|
||||
const appearances = normalizeAppearance(apProvider(this, widget, font));
|
||||
this.updateWidgetAppearanceWithFont(widget, font, appearances);
|
||||
}
|
||||
}
|
||||
262
node_modules/pdf-lib/src/api/form/PDFCheckBox.ts
generated
vendored
Normal file
262
node_modules/pdf-lib/src/api/form/PDFCheckBox.ts
generated
vendored
Normal file
@@ -0,0 +1,262 @@
|
||||
import PDFDocument from 'src/api/PDFDocument';
|
||||
import PDFPage from 'src/api/PDFPage';
|
||||
import {
|
||||
AppearanceProviderFor,
|
||||
normalizeAppearance,
|
||||
defaultCheckBoxAppearanceProvider,
|
||||
} from 'src/api/form/appearances';
|
||||
import { rgb } from 'src/api/colors';
|
||||
import { degrees } from 'src/api/rotations';
|
||||
import PDFField, {
|
||||
FieldAppearanceOptions,
|
||||
assertFieldAppearanceOptions,
|
||||
} from 'src/api/form/PDFField';
|
||||
|
||||
import {
|
||||
PDFName,
|
||||
PDFRef,
|
||||
PDFDict,
|
||||
PDFAcroCheckBox,
|
||||
PDFWidgetAnnotation,
|
||||
} from 'src/core';
|
||||
import { assertIs, assertOrUndefined } from 'src/utils';
|
||||
|
||||
/**
|
||||
* Represents a check box field of a [[PDFForm]].
|
||||
*
|
||||
* [[PDFCheckBox]] fields are interactive boxes that users can click with their
|
||||
* mouse. This type of [[PDFField]] has two states: `on` and `off`. The purpose
|
||||
* of a check box is to enable users to select from one or more options, where
|
||||
* each option is represented by a single check box. Check boxes are typically
|
||||
* square in shape and display a check mark when they are in the `on` state.
|
||||
*/
|
||||
export default class PDFCheckBox extends PDFField {
|
||||
/**
|
||||
* > **NOTE:** You probably don't want to call this method directly. Instead,
|
||||
* > consider using the [[PDFForm.getCheckBox]] method, which will create an
|
||||
* > instance of [[PDFCheckBox]] for you.
|
||||
*
|
||||
* Create an instance of [[PDFCheckBox]] from an existing acroCheckBox and ref
|
||||
*
|
||||
* @param acroCheckBox The underlying `PDFAcroCheckBox` for this check box.
|
||||
* @param ref The unique reference for this check box.
|
||||
* @param doc The document to which this check box will belong.
|
||||
*/
|
||||
static of = (acroCheckBox: PDFAcroCheckBox, ref: PDFRef, doc: PDFDocument) =>
|
||||
new PDFCheckBox(acroCheckBox, ref, doc);
|
||||
|
||||
/** The low-level PDFAcroCheckBox wrapped by this check box. */
|
||||
readonly acroField: PDFAcroCheckBox;
|
||||
|
||||
private constructor(
|
||||
acroCheckBox: PDFAcroCheckBox,
|
||||
ref: PDFRef,
|
||||
doc: PDFDocument,
|
||||
) {
|
||||
super(acroCheckBox, ref, doc);
|
||||
|
||||
assertIs(acroCheckBox, 'acroCheckBox', [
|
||||
[PDFAcroCheckBox, 'PDFAcroCheckBox'],
|
||||
]);
|
||||
|
||||
this.acroField = acroCheckBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this check box. This operation is analogous to a human user clicking
|
||||
* a check box to fill it in a PDF reader. This method will update the
|
||||
* underlying state of the check box field to indicate it has been selected.
|
||||
* PDF libraries and readers will be able to extract this value from the
|
||||
* saved document and determine that it was selected.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* const checkBox = form.getCheckBox('some.checkBox.field')
|
||||
* checkBox.check()
|
||||
* ```
|
||||
*
|
||||
* This method will mark this check box as dirty, causing its appearance
|
||||
* streams to be updated when either [[PDFDocument.save]] or
|
||||
* [[PDFForm.updateFieldAppearances]] is called. The updated appearance
|
||||
* streams will display a check mark inside the widgets of this check box
|
||||
* field.
|
||||
*/
|
||||
check() {
|
||||
const onValue = this.acroField.getOnValue() ?? PDFName.of('Yes');
|
||||
this.markAsDirty();
|
||||
this.acroField.setValue(onValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears this check box. This operation is analogous to a human user clicking
|
||||
* a check box to unmark it in a PDF reader. This method will update the
|
||||
* underlying state of the check box field to indicate it has been deselected.
|
||||
* PDF libraries and readers will be able to extract this value from the
|
||||
* saved document and determine that it was not selected.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* const checkBox = form.getCheckBox('some.checkBox.field')
|
||||
* checkBox.uncheck()
|
||||
* ```
|
||||
*
|
||||
* This method will mark this check box as dirty. See [[PDFCheckBox.check]]
|
||||
* for more details about what this means.
|
||||
*/
|
||||
uncheck() {
|
||||
this.markAsDirty();
|
||||
this.acroField.setValue(PDFName.of('Off'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if this check box is selected (either by a human user via
|
||||
* a PDF reader, or else programmatically via software). For example:
|
||||
* ```js
|
||||
* const checkBox = form.getCheckBox('some.checkBox.field')
|
||||
* if (checkBox.isChecked()) console.log('check box is selected')
|
||||
* ```
|
||||
* @returns Whether or not this check box is selected.
|
||||
*/
|
||||
isChecked(): boolean {
|
||||
const onValue = this.acroField.getOnValue();
|
||||
return !!onValue && onValue === this.acroField.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show this check box on the specified page. For example:
|
||||
* ```js
|
||||
* const helvetica = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||
* const page = pdfDoc.addPage()
|
||||
*
|
||||
* const form = pdfDoc.getForm()
|
||||
* const checkBox = form.createCheckBox('some.checkBox.field')
|
||||
*
|
||||
* checkBox.addToPage(page, {
|
||||
* x: 50,
|
||||
* y: 75,
|
||||
* width: 25,
|
||||
* height: 25,
|
||||
* textColor: rgb(1, 0, 0),
|
||||
* backgroundColor: rgb(0, 1, 0),
|
||||
* borderColor: rgb(0, 0, 1),
|
||||
* borderWidth: 2,
|
||||
* rotate: degrees(90),
|
||||
* })
|
||||
* ```
|
||||
* This will create a new widget for this check box field.
|
||||
* @param page The page to which this check box widget should be added.
|
||||
* @param options The options to be used when adding this check box widget.
|
||||
*/
|
||||
addToPage(page: PDFPage, options?: FieldAppearanceOptions) {
|
||||
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;
|
||||
|
||||
// Create a widget for this check box
|
||||
const widget = this.createWidget({
|
||||
x: options.x ?? 0,
|
||||
y: options.y ?? 0,
|
||||
width: options.width ?? 50,
|
||||
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);
|
||||
|
||||
// Add widget to this field
|
||||
this.acroField.addWidget(widgetRef);
|
||||
|
||||
// Set appearance streams for widget
|
||||
widget.setAppearanceState(PDFName.of('Off'));
|
||||
this.updateWidgetAppearance(widget, PDFName.of('Yes'));
|
||||
|
||||
// Add widget to the given page
|
||||
page.node.addAnnot(widgetRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if any of this check box's widgets do not have an
|
||||
* appearance stream for its current state. For example:
|
||||
* ```js
|
||||
* const checkBox = form.getCheckBox('some.checkBox.field')
|
||||
* if (checkBox.needsAppearancesUpdate()) console.log('Needs update')
|
||||
* ```
|
||||
* @returns Whether or not this check box needs an appearance update.
|
||||
*/
|
||||
needsAppearancesUpdate(): boolean {
|
||||
const widgets = this.acroField.getWidgets();
|
||||
for (let idx = 0, len = widgets.length; idx < len; idx++) {
|
||||
const widget = widgets[idx];
|
||||
const state = widget.getAppearanceState();
|
||||
const normal = widget.getAppearances()?.normal;
|
||||
|
||||
if (!(normal instanceof PDFDict)) return true;
|
||||
if (state && !normal.has(state)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the appearance streams for each of this check box's widgets using
|
||||
* the default appearance provider for check boxes. For example:
|
||||
* ```js
|
||||
* const checkBox = form.getCheckBox('some.checkBox.field')
|
||||
* checkBox.defaultUpdateAppearances()
|
||||
* ```
|
||||
*/
|
||||
defaultUpdateAppearances() {
|
||||
this.updateAppearances();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the appearance streams for each of this check box's widgets using
|
||||
* the given appearance provider. If no `provider` is passed, the default
|
||||
* appearance provider for check boxs will be used. For example:
|
||||
* ```js
|
||||
* const checkBox = form.getCheckBox('some.checkBox.field')
|
||||
* checkBox.updateAppearances((field, widget) => {
|
||||
* ...
|
||||
* return {
|
||||
* normal: { on: drawCheckBox(...), off: drawCheckBox(...) },
|
||||
* down: { on: drawCheckBox(...), off: drawCheckBox(...) },
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
* @param provider Optionally, the appearance provider to be used for
|
||||
* generating the contents of the appearance streams.
|
||||
*/
|
||||
updateAppearances(provider?: AppearanceProviderFor<PDFCheckBox>) {
|
||||
assertOrUndefined(provider, 'provider', [Function]);
|
||||
|
||||
const widgets = this.acroField.getWidgets();
|
||||
for (let idx = 0, len = widgets.length; idx < len; idx++) {
|
||||
const widget = widgets[idx];
|
||||
const onValue = widget.getOnValue() ?? PDFName.of('Yes');
|
||||
if (!onValue) continue;
|
||||
this.updateWidgetAppearance(widget, onValue, provider);
|
||||
}
|
||||
this.markAsClean();
|
||||
}
|
||||
|
||||
private updateWidgetAppearance(
|
||||
widget: PDFWidgetAnnotation,
|
||||
onValue: PDFName,
|
||||
provider?: AppearanceProviderFor<PDFCheckBox>,
|
||||
) {
|
||||
const apProvider = provider ?? defaultCheckBoxAppearanceProvider;
|
||||
const appearances = normalizeAppearance(apProvider(this, widget));
|
||||
this.updateOnOffWidgetAppearance(widget, onValue, appearances);
|
||||
}
|
||||
}
|
||||
652
node_modules/pdf-lib/src/api/form/PDFDropdown.ts
generated
vendored
Normal file
652
node_modules/pdf-lib/src/api/form/PDFDropdown.ts
generated
vendored
Normal file
@@ -0,0 +1,652 @@
|
||||
import PDFDocument from 'src/api/PDFDocument';
|
||||
import PDFPage from 'src/api/PDFPage';
|
||||
import PDFFont from 'src/api/PDFFont';
|
||||
import PDFField, {
|
||||
FieldAppearanceOptions,
|
||||
assertFieldAppearanceOptions,
|
||||
} from 'src/api/form/PDFField';
|
||||
import {
|
||||
AppearanceProviderFor,
|
||||
normalizeAppearance,
|
||||
defaultDropdownAppearanceProvider,
|
||||
} from 'src/api/form/appearances';
|
||||
import { rgb } from 'src/api/colors';
|
||||
import { degrees } from 'src/api/rotations';
|
||||
|
||||
import {
|
||||
PDFHexString,
|
||||
PDFRef,
|
||||
PDFString,
|
||||
PDFStream,
|
||||
PDFWidgetAnnotation,
|
||||
PDFAcroComboBox,
|
||||
AcroChoiceFlags,
|
||||
} from 'src/core';
|
||||
import { assertIs, assertOrUndefined, assertPositive } from 'src/utils';
|
||||
|
||||
/**
|
||||
* Represents a dropdown field of a [[PDFForm]].
|
||||
*
|
||||
* [[PDFDropdown]] fields are interactive text boxes that display a single
|
||||
* element (the currently selected value). The purpose of a dropdown is to
|
||||
* enable users to select a single option from a set of possible options. Users
|
||||
* can click on a dropdown to view the full list of options it provides.
|
||||
* Clicking on an option in the list will cause it to be selected and displayed
|
||||
* in the dropdown's text box. Some dropdowns allow users to enter text
|
||||
* directly into the box from their keyboard, rather than only being allowed to
|
||||
* choose an option from the list (see [[PDFDropdown.isEditable]]).
|
||||
*/
|
||||
export default class PDFDropdown extends PDFField {
|
||||
/**
|
||||
* > **NOTE:** You probably don't want to call this method directly. Instead,
|
||||
* > consider using the [[PDFForm.getDropdown]] method, which will create an
|
||||
* > instance of [[PDFDropdown]] for you.
|
||||
*
|
||||
* Create an instance of [[PDFDropdown]] from an existing acroComboBox and ref
|
||||
*
|
||||
* @param acroComboBox The underlying `PDFAcroComboBox` for this dropdown.
|
||||
* @param ref The unique reference for this dropdown.
|
||||
* @param doc The document to which this dropdown will belong.
|
||||
*/
|
||||
static of = (acroComboBox: PDFAcroComboBox, ref: PDFRef, doc: PDFDocument) =>
|
||||
new PDFDropdown(acroComboBox, ref, doc);
|
||||
|
||||
/** The low-level PDFAcroComboBox wrapped by this dropdown. */
|
||||
readonly acroField: PDFAcroComboBox;
|
||||
|
||||
private constructor(
|
||||
acroComboBox: PDFAcroComboBox,
|
||||
ref: PDFRef,
|
||||
doc: PDFDocument,
|
||||
) {
|
||||
super(acroComboBox, ref, doc);
|
||||
|
||||
assertIs(acroComboBox, 'acroComboBox', [
|
||||
[PDFAcroComboBox, 'PDFAcroComboBox'],
|
||||
]);
|
||||
|
||||
this.acroField = acroComboBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of available options for this dropdown. These options will be
|
||||
* displayed to users who click on this dropdown in a PDF reader.
|
||||
* For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* const options = dropdown.getOptions()
|
||||
* console.log('Dropdown options:', options)
|
||||
* ```
|
||||
* @returns The options for this dropdown.
|
||||
*/
|
||||
getOptions(): string[] {
|
||||
const rawOptions = this.acroField.getOptions();
|
||||
|
||||
const options = new Array<string>(rawOptions.length);
|
||||
for (let idx = 0, len = options.length; idx < len; idx++) {
|
||||
const { display, value } = rawOptions[idx];
|
||||
options[idx] = (display ?? value).decodeText();
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected options for this dropdown. These are the values that were
|
||||
* selected by a human user via a PDF reader, or programatically via
|
||||
* software.
|
||||
* For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* const selections = dropdown.getSelected()
|
||||
* console.log('Dropdown selections:', selections)
|
||||
* ```
|
||||
* > **NOTE:** Note that PDF readers only display one selected option when
|
||||
* > rendering dropdowns. However, the PDF specification does allow for
|
||||
* > multiple values to be selected in a dropdown. As such, the `pdf-lib`
|
||||
* > API supports this. However, in most cases the array returned by this
|
||||
* > method will contain only a single element (or no elements).
|
||||
* @returns The selected options in this dropdown.
|
||||
*/
|
||||
getSelected(): string[] {
|
||||
const values = this.acroField.getValues();
|
||||
|
||||
const selected = new Array<string>(values.length);
|
||||
for (let idx = 0, len = values.length; idx < len; idx++) {
|
||||
selected[idx] = values[idx].decodeText();
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of options that are available for this dropdown. These are
|
||||
* the values that will be available for users to select when they view this
|
||||
* dropdown in a PDF reader. Note that preexisting options for this dropdown
|
||||
* will be removed. Only the values passed as `options` will be available to
|
||||
* select.
|
||||
* For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('planets.dropdown')
|
||||
* dropdown.setOptions(['Earth', 'Mars', 'Pluto', 'Venus'])
|
||||
* ```
|
||||
* @param options The options that should be available in this dropdown.
|
||||
*/
|
||||
setOptions(options: string[]) {
|
||||
assertIs(options, 'options', [Array]);
|
||||
|
||||
const optionObjects = new Array<{ value: PDFHexString }>(options.length);
|
||||
for (let idx = 0, len = options.length; idx < len; idx++) {
|
||||
optionObjects[idx] = { value: PDFHexString.fromText(options[idx]) };
|
||||
}
|
||||
this.acroField.setOptions(optionObjects);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to the list of options that are available for this dropdown. Users
|
||||
* will be able to select these values in a PDF reader. In addition to the
|
||||
* values passed as `options`, any preexisting options for this dropdown will
|
||||
* still be available for users to select.
|
||||
* For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('rockets.dropdown')
|
||||
* dropdown.addOptions(['Saturn IV', 'Falcon Heavy'])
|
||||
* ```
|
||||
* @param options New options that should be available in this dropdown.
|
||||
*/
|
||||
addOptions(options: string | string[]) {
|
||||
assertIs(options, 'options', ['string', Array]);
|
||||
|
||||
const optionsArr = Array.isArray(options) ? options : [options];
|
||||
|
||||
const existingOptions: {
|
||||
value: PDFString | PDFHexString;
|
||||
display?: PDFString | PDFHexString;
|
||||
}[] = this.acroField.getOptions();
|
||||
|
||||
const newOptions = new Array<{ value: PDFHexString }>(optionsArr.length);
|
||||
for (let idx = 0, len = optionsArr.length; idx < len; idx++) {
|
||||
newOptions[idx] = { value: PDFHexString.fromText(optionsArr[idx]) };
|
||||
}
|
||||
|
||||
this.acroField.setOptions(existingOptions.concat(newOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Select one or more values for this dropdown. This operation is analogous
|
||||
* to a human user opening the dropdown in a PDF reader and clicking on a
|
||||
* value to select it. This method will update the underlying state of the
|
||||
* dropdown to indicate which values have been selected. PDF libraries and
|
||||
* readers will be able to extract these values from the saved document and
|
||||
* determine which values were selected.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('best.superhero.dropdown')
|
||||
* dropdown.select('One Punch Man')
|
||||
* ```
|
||||
*
|
||||
* This method will mark this dropdown as dirty, causing its appearance
|
||||
* streams to be updated when either [[PDFDocument.save]] or
|
||||
* [[PDFForm.updateFieldAppearances]] is called. The updated streams will
|
||||
* display the selected option inside the widgets of this dropdown.
|
||||
*
|
||||
* **IMPORTANT:** The default font used to update appearance streams is
|
||||
* [[StandardFonts.Helvetica]]. Note that this is a WinAnsi font. This means
|
||||
* that encoding errors will be thrown if the selected option for this field
|
||||
* contains characters outside the WinAnsi character set (the latin alphabet).
|
||||
*
|
||||
* Embedding a custom font and passing it to
|
||||
* [[PDFForm.updateFieldAppearances]] or [[PDFDropdown.updateAppearances]]
|
||||
* allows you to generate appearance streams with characters outside the
|
||||
* latin alphabet (assuming the custom font supports them).
|
||||
*
|
||||
* Selecting an option that does not exist in this dropdown's option list
|
||||
* (see [[PDFDropdown.getOptions]]) will enable editing on this dropdown
|
||||
* (see [[PDFDropdown.enableEditing]]).
|
||||
*
|
||||
* > **NOTE:** PDF readers only display one selected option when rendering
|
||||
* > dropdowns. However, the PDF specification does allow for multiple values
|
||||
* > to be selected in a dropdown. As such, the `pdf-lib` API supports this.
|
||||
* > However, it is not recommended to select more than one value with this
|
||||
* > method, as only one will be visible. [[PDFOptionList]] fields are better
|
||||
* > suited for displaying multiple selected values.
|
||||
*
|
||||
* @param options The options to be selected.
|
||||
* @param merge Whether or not existing selections should be preserved.
|
||||
*/
|
||||
select(options: string | string[], merge = false) {
|
||||
assertIs(options, 'options', ['string', Array]);
|
||||
assertIs(merge, 'merge', ['boolean']);
|
||||
|
||||
const optionsArr = Array.isArray(options) ? options : [options];
|
||||
|
||||
const validOptions = this.getOptions();
|
||||
const hasCustomOption = optionsArr.find(
|
||||
(option) => !validOptions.includes(option),
|
||||
);
|
||||
if (hasCustomOption) this.enableEditing();
|
||||
|
||||
this.markAsDirty();
|
||||
|
||||
if (optionsArr.length > 1 || (optionsArr.length === 1 && merge)) {
|
||||
this.enableMultiselect();
|
||||
}
|
||||
|
||||
const values = new Array<PDFHexString>(optionsArr.length);
|
||||
for (let idx = 0, len = optionsArr.length; idx < len; idx++) {
|
||||
values[idx] = PDFHexString.fromText(optionsArr[idx]);
|
||||
}
|
||||
|
||||
if (merge) {
|
||||
const existingValues = this.acroField.getValues();
|
||||
this.acroField.setValues(existingValues.concat(values));
|
||||
} else {
|
||||
this.acroField.setValues(values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all selected values for this dropdown. This operation is equivalent
|
||||
* to selecting an empty list. This method will update the underlying state
|
||||
* of the dropdown to indicate that no values have been selected.
|
||||
* For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* dropdown.clear()
|
||||
* ```
|
||||
* This method will mark this text field as dirty. See [[PDFDropdown.select]]
|
||||
* for more details about what this means.
|
||||
*/
|
||||
clear() {
|
||||
this.markAsDirty();
|
||||
this.acroField.setValues([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the font size for this field. Larger font sizes will result in larger
|
||||
* text being displayed when PDF readers render this dropdown. Font sizes may
|
||||
* be integer or floating point numbers. Supplying a negative font size will
|
||||
* cause this method to throw an error.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* dropdown.setFontSize(4)
|
||||
* dropdown.setFontSize(15.7)
|
||||
* ```
|
||||
*
|
||||
* > This method depends upon the existence of a default appearance
|
||||
* > (`/DA`) string. If this field does not have a default appearance string,
|
||||
* > or that string does not contain a font size (via the `Tf` operator),
|
||||
* > then this method will throw an error.
|
||||
*
|
||||
* @param fontSize The font size to be used when rendering text in this field.
|
||||
*/
|
||||
setFontSize(fontSize: number) {
|
||||
assertPositive(fontSize, 'fontSize');
|
||||
this.acroField.setFontSize(fontSize);
|
||||
this.markAsDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if users are allowed to edit the selected value of this
|
||||
* dropdown directly and are not constrained by the list of available
|
||||
* options. See [[PDFDropdown.enableEditing]] and
|
||||
* [[PDFDropdown.disableEditing]]. For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* if (dropdown.isEditable()) console.log('Editing is enabled')
|
||||
* ```
|
||||
* @returns Whether or not this dropdown is editable.
|
||||
*/
|
||||
isEditable(): boolean {
|
||||
return this.acroField.hasFlag(AcroChoiceFlags.Edit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow users to edit the selected value of this dropdown in PDF readers
|
||||
* with their keyboard. This means that the selected value of this dropdown
|
||||
* will not be constrained by the list of available options. However, if this
|
||||
* dropdown has any available options, users will still be allowed to select
|
||||
* from that list.
|
||||
* For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* dropdown.enableEditing()
|
||||
* ```
|
||||
*/
|
||||
enableEditing() {
|
||||
this.acroField.setFlagTo(AcroChoiceFlags.Edit, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not allow users to edit the selected value of this dropdown in PDF
|
||||
* readers with their keyboard. This will constrain the selected value of
|
||||
* this dropdown to the list of available options. Users will only be able
|
||||
* to select an option from that list.
|
||||
* For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* dropdown.disableEditing()
|
||||
* ```
|
||||
*/
|
||||
disableEditing() {
|
||||
this.acroField.setFlagTo(AcroChoiceFlags.Edit, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the option list of this dropdown is always displayed
|
||||
* in alphabetical order, irrespective of the order in which the options
|
||||
* were added to the dropdown. See [[PDFDropdown.enableSorting]] and
|
||||
* [[PDFDropdown.disableSorting]]. For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* if (dropdown.isSorted()) console.log('Sorting is enabled')
|
||||
* ```
|
||||
* @returns Whether or not this dropdown's options are sorted.
|
||||
*/
|
||||
isSorted(): boolean {
|
||||
return this.acroField.hasFlag(AcroChoiceFlags.Sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Always display the option list of this dropdown in alphabetical order,
|
||||
* irrespective of the order in which the options were added to this dropdown.
|
||||
* For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* dropdown.enableSorting()
|
||||
* ```
|
||||
*/
|
||||
enableSorting() {
|
||||
this.acroField.setFlagTo(AcroChoiceFlags.Sort, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not always display the option list of this dropdown in alphabetical
|
||||
* order. Instead, display the options in whichever order they were added
|
||||
* to the list. For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* dropdown.disableSorting()
|
||||
* ```
|
||||
*/
|
||||
disableSorting() {
|
||||
this.acroField.setFlagTo(AcroChoiceFlags.Sort, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if multiple options can be selected from this dropdown's
|
||||
* option list. See [[PDFDropdown.enableMultiselect]] and
|
||||
* [[PDFDropdown.disableMultiselect]]. For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* if (dropdown.isMultiselect()) console.log('Multiselect is enabled')
|
||||
* ```
|
||||
* @returns Whether or not multiple options can be selected.
|
||||
*/
|
||||
isMultiselect(): boolean {
|
||||
return this.acroField.hasFlag(AcroChoiceFlags.MultiSelect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow users to select more than one option from this dropdown's option
|
||||
* list. For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* dropdown.enableMultiselect()
|
||||
* ```
|
||||
*/
|
||||
enableMultiselect() {
|
||||
this.acroField.setFlagTo(AcroChoiceFlags.MultiSelect, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not allow users to select more than one option from this dropdown's
|
||||
* option list. For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* dropdown.disableMultiselect()
|
||||
* ```
|
||||
*/
|
||||
disableMultiselect() {
|
||||
this.acroField.setFlagTo(AcroChoiceFlags.MultiSelect, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the selected option should be spell checked by PDF
|
||||
* readers. Spell checking will only be performed if this dropdown allows
|
||||
* editing (see [[PDFDropdown.isEditable]]). See
|
||||
* [[PDFDropdown.enableSpellChecking]] and
|
||||
* [[PDFDropdown.disableSpellChecking]]. For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* if (dropdown.isSpellChecked()) console.log('Spell checking is enabled')
|
||||
* ```
|
||||
* @returns Whether or not this dropdown can be spell checked.
|
||||
*/
|
||||
isSpellChecked(): boolean {
|
||||
return !this.acroField.hasFlag(AcroChoiceFlags.DoNotSpellCheck);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow PDF readers to spell check the selected option of this dropdown.
|
||||
* For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* dropdown.enableSpellChecking()
|
||||
* ```
|
||||
*/
|
||||
enableSpellChecking() {
|
||||
this.acroField.setFlagTo(AcroChoiceFlags.DoNotSpellCheck, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not allow PDF readers to spell check the selected option of this
|
||||
* dropdown. For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* dropdown.disableSpellChecking()
|
||||
* ```
|
||||
*/
|
||||
disableSpellChecking() {
|
||||
this.acroField.setFlagTo(AcroChoiceFlags.DoNotSpellCheck, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the option selected by a user is stored, or "committed",
|
||||
* when the user clicks the option. The alternative is that the user's
|
||||
* selection is stored when the user leaves this dropdown field (by clicking
|
||||
* outside of it - on another field, for example). See
|
||||
* [[PDFDropdown.enableSelectOnClick]] and
|
||||
* [[PDFDropdown.disableSelectOnClick]]. For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* if (dropdown.isSelectOnClick()) console.log('Select on click is enabled')
|
||||
* ```
|
||||
* @returns Whether or not options are selected immediately after they are
|
||||
* clicked.
|
||||
*/
|
||||
isSelectOnClick(): boolean {
|
||||
return this.acroField.hasFlag(AcroChoiceFlags.CommitOnSelChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the option selected by a user immediately after the user clicks the
|
||||
* option. Do not wait for the user to leave this dropdown field (by clicking
|
||||
* outside of it - on another field, for example). For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* dropdown.enableSelectOnClick()
|
||||
* ```
|
||||
*/
|
||||
enableSelectOnClick() {
|
||||
this.acroField.setFlagTo(AcroChoiceFlags.CommitOnSelChange, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait to store the option selected by a user until they leave this dropdown
|
||||
* field (by clicking outside of it - on another field, for example).
|
||||
* For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* dropdown.disableSelectOnClick()
|
||||
* ```
|
||||
*/
|
||||
disableSelectOnClick() {
|
||||
this.acroField.setFlagTo(AcroChoiceFlags.CommitOnSelChange, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show this dropdown on the specified page. For example:
|
||||
* ```js
|
||||
* const ubuntuFont = await pdfDoc.embedFont(ubuntuFontBytes)
|
||||
* const page = pdfDoc.addPage()
|
||||
*
|
||||
* const form = pdfDoc.getForm()
|
||||
* const dropdown = form.createDropdown('best.gundam')
|
||||
* dropdown.setOptions(['Exia', 'Dynames'])
|
||||
* dropdown.select('Exia')
|
||||
*
|
||||
* dropdown.addToPage(page, {
|
||||
* x: 50,
|
||||
* y: 75,
|
||||
* width: 200,
|
||||
* height: 100,
|
||||
* textColor: rgb(1, 0, 0),
|
||||
* backgroundColor: rgb(0, 1, 0),
|
||||
* borderColor: rgb(0, 0, 1),
|
||||
* borderWidth: 2,
|
||||
* rotate: degrees(90),
|
||||
* font: ubuntuFont,
|
||||
* })
|
||||
* ```
|
||||
* This will create a new widget for this dropdown field.
|
||||
* @param page The page to which this dropdown widget should be added.
|
||||
* @param options The options to be used when adding this dropdown widget.
|
||||
*/
|
||||
addToPage(page: PDFPage, options?: FieldAppearanceOptions) {
|
||||
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;
|
||||
|
||||
// Create a widget for this dropdown
|
||||
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);
|
||||
|
||||
// Add widget to this field
|
||||
this.acroField.addWidget(widgetRef);
|
||||
|
||||
// Set appearance streams for widget
|
||||
const font = options.font ?? this.doc.getForm().getDefaultFont();
|
||||
this.updateWidgetAppearance(widget, font);
|
||||
|
||||
// Add widget to the given page
|
||||
page.node.addAnnot(widgetRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if this dropdown has been marked as dirty, or if any of
|
||||
* this dropdown's widgets do not have an appearance stream. For example:
|
||||
* ```js
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* if (dropdown.needsAppearancesUpdate()) console.log('Needs update')
|
||||
* ```
|
||||
* @returns Whether or not this dropdown needs an appearance update.
|
||||
*/
|
||||
needsAppearancesUpdate(): boolean {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the appearance streams for each of this dropdown's widgets using
|
||||
* the default appearance provider for dropdowns. For example:
|
||||
* ```js
|
||||
* const helvetica = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* dropdown.defaultUpdateAppearances(helvetica)
|
||||
* ```
|
||||
* @param font The font to be used for creating the appearance streams.
|
||||
*/
|
||||
defaultUpdateAppearances(font: PDFFont) {
|
||||
assertIs(font, 'font', [[PDFFont, 'PDFFont']]);
|
||||
this.updateAppearances(font);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the appearance streams for each of this dropdown's widgets using
|
||||
* the given appearance provider. If no `provider` is passed, the default
|
||||
* appearance provider for dropdowns will be used. For example:
|
||||
* ```js
|
||||
* const helvetica = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||
* const dropdown = form.getDropdown('some.dropdown.field')
|
||||
* dropdown.updateAppearances(helvetica, (field, widget, font) => {
|
||||
* ...
|
||||
* return drawTextField(...)
|
||||
* })
|
||||
* ```
|
||||
* @param font The font to be used for creating the appearance streams.
|
||||
* @param provider Optionally, the appearance provider to be used for
|
||||
* generating the contents of the appearance streams.
|
||||
*/
|
||||
updateAppearances(
|
||||
font: PDFFont,
|
||||
provider?: AppearanceProviderFor<PDFDropdown>,
|
||||
) {
|
||||
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();
|
||||
}
|
||||
|
||||
// getOption(index: number): string {}
|
||||
// getSelectedIndices(): number[] {}
|
||||
// removeOptions(option: string | string[]) {}
|
||||
// removeIndices(option: number[]) {}
|
||||
// deselect(options: string | string[]) {}
|
||||
// deselectIndices(optionIndices: number[]) {}
|
||||
|
||||
private updateWidgetAppearance(
|
||||
widget: PDFWidgetAnnotation,
|
||||
font: PDFFont,
|
||||
provider?: AppearanceProviderFor<PDFDropdown>,
|
||||
) {
|
||||
const apProvider = provider ?? defaultDropdownAppearanceProvider;
|
||||
const appearances = normalizeAppearance(apProvider(this, widget, font));
|
||||
this.updateWidgetAppearanceWithFont(widget, font, appearances);
|
||||
}
|
||||
}
|
||||
521
node_modules/pdf-lib/src/api/form/PDFField.ts
generated
vendored
Normal file
521
node_modules/pdf-lib/src/api/form/PDFField.ts
generated
vendored
Normal file
@@ -0,0 +1,521 @@
|
||||
import PDFDocument from 'src/api/PDFDocument';
|
||||
import PDFFont from 'src/api/PDFFont';
|
||||
import { AppearanceMapping } from 'src/api/form/appearances';
|
||||
import { Color, colorToComponents, setFillingColor } from 'src/api/colors';
|
||||
import {
|
||||
Rotation,
|
||||
toDegrees,
|
||||
rotateRectangle,
|
||||
reduceRotation,
|
||||
adjustDimsForRotation,
|
||||
degrees,
|
||||
} from 'src/api/rotations';
|
||||
|
||||
import {
|
||||
PDFRef,
|
||||
PDFWidgetAnnotation,
|
||||
PDFOperator,
|
||||
PDFName,
|
||||
PDFDict,
|
||||
MethodNotImplementedError,
|
||||
AcroFieldFlags,
|
||||
PDFAcroTerminal,
|
||||
AnnotationFlags,
|
||||
} from 'src/core';
|
||||
import { assertIs, assertMultiple, assertOrUndefined } from 'src/utils';
|
||||
import { ImageAlignment } from '../image';
|
||||
import PDFImage from '../PDFImage';
|
||||
import { drawImage, rotateInPlace } from '../operations';
|
||||
|
||||
export interface FieldAppearanceOptions {
|
||||
x?: number;
|
||||
y?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
textColor?: Color;
|
||||
backgroundColor?: Color;
|
||||
borderColor?: Color;
|
||||
borderWidth?: number;
|
||||
rotate?: Rotation;
|
||||
font?: PDFFont;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
export const assertFieldAppearanceOptions = (
|
||||
options?: FieldAppearanceOptions,
|
||||
) => {
|
||||
assertOrUndefined(options?.x, 'options.x', ['number']);
|
||||
assertOrUndefined(options?.y, 'options.y', ['number']);
|
||||
assertOrUndefined(options?.width, 'options.width', ['number']);
|
||||
assertOrUndefined(options?.height, 'options.height', ['number']);
|
||||
assertOrUndefined(options?.textColor, 'options.textColor', [
|
||||
[Object, 'Color'],
|
||||
]);
|
||||
assertOrUndefined(options?.backgroundColor, 'options.backgroundColor', [
|
||||
[Object, 'Color'],
|
||||
]);
|
||||
assertOrUndefined(options?.borderColor, 'options.borderColor', [
|
||||
[Object, 'Color'],
|
||||
]);
|
||||
assertOrUndefined(options?.borderWidth, 'options.borderWidth', ['number']);
|
||||
assertOrUndefined(options?.rotate, 'options.rotate', [[Object, 'Rotation']]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a field of a [[PDFForm]].
|
||||
*
|
||||
* This class is effectively abstract. All fields in a [[PDFForm]] will
|
||||
* actually be an instance of a subclass of this class.
|
||||
*
|
||||
* Note that each field in a PDF is represented by a single field object.
|
||||
* However, a given field object may be rendered at multiple locations within
|
||||
* the document (across one or more pages). The rendering of a field is
|
||||
* controlled by its widgets. Each widget causes its field to be displayed at a
|
||||
* particular location in the document.
|
||||
*
|
||||
* Most of the time each field in a PDF has only a single widget, and thus is
|
||||
* only rendered once. However, if a field is rendered multiple times, it will
|
||||
* have multiple widgets - one for each location it is rendered.
|
||||
*
|
||||
* This abstraction of field objects and widgets is defined in the PDF
|
||||
* specification and dictates how PDF files store fields and where they are
|
||||
* to be rendered.
|
||||
*/
|
||||
export default class PDFField {
|
||||
/** The low-level PDFAcroTerminal wrapped by this field. */
|
||||
readonly acroField: PDFAcroTerminal;
|
||||
|
||||
/** The unique reference assigned to this field within the document. */
|
||||
readonly ref: PDFRef;
|
||||
|
||||
/** The document to which this field belongs. */
|
||||
readonly doc: PDFDocument;
|
||||
|
||||
protected constructor(
|
||||
acroField: PDFAcroTerminal,
|
||||
ref: PDFRef,
|
||||
doc: PDFDocument,
|
||||
) {
|
||||
assertIs(acroField, 'acroField', [[PDFAcroTerminal, 'PDFAcroTerminal']]);
|
||||
assertIs(ref, 'ref', [[PDFRef, 'PDFRef']]);
|
||||
assertIs(doc, 'doc', [[PDFDocument, 'PDFDocument']]);
|
||||
|
||||
this.acroField = acroField;
|
||||
this.ref = ref;
|
||||
this.doc = doc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified name of this field. For example:
|
||||
* ```js
|
||||
* const fields = form.getFields()
|
||||
* fields.forEach(field => {
|
||||
* const name = field.getName()
|
||||
* console.log('Field name:', name)
|
||||
* })
|
||||
* ```
|
||||
* Note that PDF fields are structured as a tree. Each field is the
|
||||
* descendent of a series of ancestor nodes all the way up to the form node,
|
||||
* which is always the root of the tree. Each node in the tree (except for
|
||||
* the form node) has a partial name. Partial names can be composed of any
|
||||
* unicode characters except a period (`.`). The fully qualified name of a
|
||||
* field is composed of the partial names of all its ancestors joined
|
||||
* with periods. This means that splitting the fully qualified name on
|
||||
* periods and taking the last element of the resulting array will give you
|
||||
* the partial name of a specific field.
|
||||
* @returns The fully qualified name of this field.
|
||||
*/
|
||||
getName(): string {
|
||||
return this.acroField.getFullyQualifiedName() ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if this field is read only. This means that PDF readers
|
||||
* will not allow users to interact with the field or change its value. See
|
||||
* [[PDFField.enableReadOnly]] and [[PDFField.disableReadOnly]].
|
||||
* For example:
|
||||
* ```js
|
||||
* const field = form.getField('some.field')
|
||||
* if (field.isReadOnly()) console.log('Read only is enabled')
|
||||
* ```
|
||||
* @returns Whether or not this is a read only field.
|
||||
*/
|
||||
isReadOnly(): boolean {
|
||||
return this.acroField.hasFlag(AcroFieldFlags.ReadOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent PDF readers from allowing users to interact with this field or
|
||||
* change its value. The field will not respond to mouse or keyboard input.
|
||||
* For example:
|
||||
* ```js
|
||||
* const field = form.getField('some.field')
|
||||
* field.enableReadOnly()
|
||||
* ```
|
||||
* Useful for fields whose values are computed, imported from a database, or
|
||||
* prefilled by software before being displayed to the user.
|
||||
*/
|
||||
enableReadOnly() {
|
||||
this.acroField.setFlagTo(AcroFieldFlags.ReadOnly, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow users to interact with this field and change its value in PDF
|
||||
* readers via mouse and keyboard input. For example:
|
||||
* ```js
|
||||
* const field = form.getField('some.field')
|
||||
* field.disableReadOnly()
|
||||
* ```
|
||||
*/
|
||||
disableReadOnly() {
|
||||
this.acroField.setFlagTo(AcroFieldFlags.ReadOnly, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if this field must have a value when the form is submitted.
|
||||
* See [[PDFField.enableRequired]] and [[PDFField.disableRequired]].
|
||||
* For example:
|
||||
* ```js
|
||||
* const field = form.getField('some.field')
|
||||
* if (field.isRequired()) console.log('Field is required')
|
||||
* ```
|
||||
* @returns Whether or not this field is required.
|
||||
*/
|
||||
isRequired(): boolean {
|
||||
return this.acroField.hasFlag(AcroFieldFlags.Required);
|
||||
}
|
||||
|
||||
/**
|
||||
* Require this field to have a value when the form is submitted.
|
||||
* For example:
|
||||
* ```js
|
||||
* const field = form.getField('some.field')
|
||||
* field.enableRequired()
|
||||
* ```
|
||||
*/
|
||||
enableRequired() {
|
||||
this.acroField.setFlagTo(AcroFieldFlags.Required, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not require this field to have a value when the form is submitted.
|
||||
* For example:
|
||||
* ```js
|
||||
* const field = form.getField('some.field')
|
||||
* field.disableRequired()
|
||||
* ```
|
||||
*/
|
||||
disableRequired() {
|
||||
this.acroField.setFlagTo(AcroFieldFlags.Required, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if this field's value should be exported when the form is
|
||||
* submitted. See [[PDFField.enableExporting]] and
|
||||
* [[PDFField.disableExporting]].
|
||||
* For example:
|
||||
* ```js
|
||||
* const field = form.getField('some.field')
|
||||
* if (field.isExported()) console.log('Exporting is enabled')
|
||||
* ```
|
||||
* @returns Whether or not this field's value should be exported.
|
||||
*/
|
||||
isExported(): boolean {
|
||||
return !this.acroField.hasFlag(AcroFieldFlags.NoExport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that this field's value should be exported when the form is
|
||||
* submitted in a PDF reader. For example:
|
||||
* ```js
|
||||
* const field = form.getField('some.field')
|
||||
* field.enableExporting()
|
||||
* ```
|
||||
*/
|
||||
enableExporting() {
|
||||
this.acroField.setFlagTo(AcroFieldFlags.NoExport, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that this field's value should **not** be exported when the form
|
||||
* is submitted in a PDF reader. For example:
|
||||
* ```js
|
||||
* const field = form.getField('some.field')
|
||||
* field.disableExporting()
|
||||
* ```
|
||||
*/
|
||||
disableExporting() {
|
||||
this.acroField.setFlagTo(AcroFieldFlags.NoExport, true);
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
needsAppearancesUpdate(): boolean {
|
||||
throw new MethodNotImplementedError(
|
||||
this.constructor.name,
|
||||
'needsAppearancesUpdate',
|
||||
);
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
defaultUpdateAppearances(_font: PDFFont) {
|
||||
throw new MethodNotImplementedError(
|
||||
this.constructor.name,
|
||||
'defaultUpdateAppearances',
|
||||
);
|
||||
}
|
||||
|
||||
protected markAsDirty() {
|
||||
this.doc.getForm().markFieldAsDirty(this.ref);
|
||||
}
|
||||
|
||||
protected markAsClean() {
|
||||
this.doc.getForm().markFieldAsClean(this.ref);
|
||||
}
|
||||
|
||||
protected isDirty(): boolean {
|
||||
return this.doc.getForm().fieldIsDirty(this.ref);
|
||||
}
|
||||
|
||||
protected createWidget(options: {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
textColor?: Color;
|
||||
backgroundColor?: Color;
|
||||
borderColor?: Color;
|
||||
borderWidth: number;
|
||||
rotate: Rotation;
|
||||
caption?: string;
|
||||
hidden?: boolean;
|
||||
page?: PDFRef;
|
||||
}): PDFWidgetAnnotation {
|
||||
const textColor = options.textColor;
|
||||
const backgroundColor = options.backgroundColor;
|
||||
const borderColor = options.borderColor;
|
||||
const borderWidth = options.borderWidth;
|
||||
const degreesAngle = toDegrees(options.rotate);
|
||||
const caption = options.caption;
|
||||
const x = options.x;
|
||||
const y = options.y;
|
||||
const width = options.width + borderWidth;
|
||||
const height = options.height + borderWidth;
|
||||
const hidden = Boolean(options.hidden);
|
||||
const pageRef = options.page;
|
||||
|
||||
assertMultiple(degreesAngle, 'degreesAngle', 90);
|
||||
|
||||
// Create a widget for this field
|
||||
const widget = PDFWidgetAnnotation.create(this.doc.context, this.ref);
|
||||
|
||||
// Set widget properties
|
||||
const rect = rotateRectangle(
|
||||
{ x, y, width, height },
|
||||
borderWidth,
|
||||
degreesAngle,
|
||||
);
|
||||
widget.setRectangle(rect);
|
||||
|
||||
if (pageRef) widget.setP(pageRef);
|
||||
|
||||
const ac = widget.getOrCreateAppearanceCharacteristics();
|
||||
if (backgroundColor) {
|
||||
ac.setBackgroundColor(colorToComponents(backgroundColor));
|
||||
}
|
||||
ac.setRotation(degreesAngle);
|
||||
if (caption) ac.setCaptions({ normal: caption });
|
||||
if (borderColor) ac.setBorderColor(colorToComponents(borderColor));
|
||||
|
||||
const bs = widget.getOrCreateBorderStyle();
|
||||
if (borderWidth !== undefined) bs.setWidth(borderWidth);
|
||||
|
||||
widget.setFlagTo(AnnotationFlags.Print, true);
|
||||
widget.setFlagTo(AnnotationFlags.Hidden, hidden);
|
||||
widget.setFlagTo(AnnotationFlags.Invisible, false);
|
||||
|
||||
// Set acrofield properties
|
||||
if (textColor) {
|
||||
const da = this.acroField.getDefaultAppearance() ?? '';
|
||||
const newDa = da + '\n' + setFillingColor(textColor).toString();
|
||||
this.acroField.setDefaultAppearance(newDa);
|
||||
}
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
protected updateWidgetAppearanceWithFont(
|
||||
widget: PDFWidgetAnnotation,
|
||||
font: PDFFont,
|
||||
{ normal, rollover, down }: AppearanceMapping<PDFOperator[]>,
|
||||
) {
|
||||
this.updateWidgetAppearances(widget, {
|
||||
normal: this.createAppearanceStream(widget, normal, font),
|
||||
rollover: rollover && this.createAppearanceStream(widget, rollover, font),
|
||||
down: down && this.createAppearanceStream(widget, down, font),
|
||||
});
|
||||
}
|
||||
|
||||
protected updateOnOffWidgetAppearance(
|
||||
widget: PDFWidgetAnnotation,
|
||||
onValue: PDFName,
|
||||
{
|
||||
normal,
|
||||
rollover,
|
||||
down,
|
||||
}: AppearanceMapping<{ on: PDFOperator[]; off: PDFOperator[] }>,
|
||||
) {
|
||||
this.updateWidgetAppearances(widget, {
|
||||
normal: this.createAppearanceDict(widget, normal, onValue),
|
||||
rollover:
|
||||
rollover && this.createAppearanceDict(widget, rollover, onValue),
|
||||
down: down && this.createAppearanceDict(widget, down, onValue),
|
||||
});
|
||||
}
|
||||
|
||||
protected updateWidgetAppearances(
|
||||
widget: PDFWidgetAnnotation,
|
||||
{ normal, rollover, down }: AppearanceMapping<PDFRef | PDFDict>,
|
||||
) {
|
||||
widget.setNormalAppearance(normal);
|
||||
|
||||
if (rollover) {
|
||||
widget.setRolloverAppearance(rollover);
|
||||
} else {
|
||||
widget.removeRolloverAppearance();
|
||||
}
|
||||
|
||||
if (down) {
|
||||
widget.setDownAppearance(down);
|
||||
} else {
|
||||
widget.removeDownAppearance();
|
||||
}
|
||||
}
|
||||
|
||||
// // TODO: Do we need to do this...?
|
||||
// private foo(font: PDFFont, dict: PDFDict) {
|
||||
// if (!dict.lookup(PDFName.of('DR'))) {
|
||||
// dict.set(PDFName.of('DR'), dict.context.obj({}));
|
||||
// }
|
||||
// const DR = dict.lookup(PDFName.of('DR'), PDFDict);
|
||||
|
||||
// if (!DR.lookup(PDFName.of('Font'))) {
|
||||
// DR.set(PDFName.of('Font'), dict.context.obj({}));
|
||||
// }
|
||||
// const Font = DR.lookup(PDFName.of('Font'), PDFDict);
|
||||
|
||||
// Font.set(PDFName.of(font.name), font.ref);
|
||||
// }
|
||||
|
||||
private createAppearanceStream(
|
||||
widget: PDFWidgetAnnotation,
|
||||
appearance: PDFOperator[],
|
||||
font?: PDFFont,
|
||||
): PDFRef {
|
||||
const { context } = this.acroField.dict;
|
||||
const { width, height } = widget.getRectangle();
|
||||
|
||||
// TODO: Do we need to do this...?
|
||||
// if (font) {
|
||||
// this.foo(font, widget.dict);
|
||||
// this.foo(font, this.doc.getForm().acroForm.dict);
|
||||
// }
|
||||
// END TODO
|
||||
|
||||
const Resources = font && { Font: { [font.name]: font.ref } };
|
||||
const stream = context.formXObject(appearance, {
|
||||
Resources,
|
||||
BBox: context.obj([0, 0, width, height]),
|
||||
Matrix: context.obj([1, 0, 0, 1, 0, 0]),
|
||||
});
|
||||
const streamRef = context.register(stream);
|
||||
|
||||
return streamRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a FormXObject of the supplied image and add it to context.
|
||||
* The FormXObject size is calculated based on the widget (including
|
||||
* the alignment).
|
||||
* @param widget The widget that should display the image.
|
||||
* @param alignment The alignment of the image.
|
||||
* @param image The image that should be displayed.
|
||||
* @returns The ref for the FormXObject that was added to the context.
|
||||
*/
|
||||
protected createImageAppearanceStream(
|
||||
widget: PDFWidgetAnnotation,
|
||||
image: PDFImage,
|
||||
alignment: ImageAlignment,
|
||||
): PDFRef {
|
||||
// NOTE: This implementation doesn't handle image borders.
|
||||
// NOTE: Acrobat seems to resize the image (maybe even skewing its aspect
|
||||
// ratio) to fit perfectly within the widget's rectangle. This method
|
||||
// does not currently do that. Should there be an option for that?
|
||||
|
||||
const { context } = this.acroField.dict;
|
||||
|
||||
const rectangle = widget.getRectangle();
|
||||
const ap = widget.getAppearanceCharacteristics();
|
||||
const bs = widget.getBorderStyle();
|
||||
|
||||
const borderWidth = bs?.getWidth() ?? 0;
|
||||
const rotation = reduceRotation(ap?.getRotation());
|
||||
|
||||
const rotate = rotateInPlace({ ...rectangle, rotation });
|
||||
|
||||
const adj = adjustDimsForRotation(rectangle, rotation);
|
||||
const imageDims = image.scaleToFit(
|
||||
adj.width - borderWidth * 2,
|
||||
adj.height - borderWidth * 2,
|
||||
);
|
||||
|
||||
// Support borders on images and maybe other properties
|
||||
const options = {
|
||||
x: borderWidth,
|
||||
y: borderWidth,
|
||||
width: imageDims.width,
|
||||
height: imageDims.height,
|
||||
//
|
||||
rotate: degrees(0),
|
||||
xSkew: degrees(0),
|
||||
ySkew: degrees(0),
|
||||
};
|
||||
|
||||
if (alignment === ImageAlignment.Center) {
|
||||
options.x += (adj.width - borderWidth * 2) / 2 - imageDims.width / 2;
|
||||
options.y += (adj.height - borderWidth * 2) / 2 - imageDims.height / 2;
|
||||
} else if (alignment === ImageAlignment.Right) {
|
||||
options.x = adj.width - borderWidth - imageDims.width;
|
||||
options.y = adj.height - borderWidth - imageDims.height;
|
||||
}
|
||||
|
||||
const imageName = this.doc.context.addRandomSuffix('Image', 10);
|
||||
const appearance = [...rotate, ...drawImage(imageName, options)];
|
||||
////////////
|
||||
|
||||
const Resources = { XObject: { [imageName]: image.ref } };
|
||||
const stream = context.formXObject(appearance, {
|
||||
Resources,
|
||||
BBox: context.obj([0, 0, rectangle.width, rectangle.height]),
|
||||
Matrix: context.obj([1, 0, 0, 1, 0, 0]),
|
||||
});
|
||||
|
||||
return context.register(stream);
|
||||
}
|
||||
|
||||
private createAppearanceDict(
|
||||
widget: PDFWidgetAnnotation,
|
||||
appearance: { on: PDFOperator[]; off: PDFOperator[] },
|
||||
onValue: PDFName,
|
||||
): PDFDict {
|
||||
const { context } = this.acroField.dict;
|
||||
|
||||
const onStreamRef = this.createAppearanceStream(widget, appearance.on);
|
||||
const offStreamRef = this.createAppearanceStream(widget, appearance.off);
|
||||
|
||||
const appearanceDict = context.obj({});
|
||||
appearanceDict.set(onValue, onStreamRef);
|
||||
appearanceDict.set(PDFName.of('Off'), offStreamRef);
|
||||
|
||||
return appearanceDict;
|
||||
}
|
||||
}
|
||||
852
node_modules/pdf-lib/src/api/form/PDFForm.ts
generated
vendored
Normal file
852
node_modules/pdf-lib/src/api/form/PDFForm.ts
generated
vendored
Normal file
@@ -0,0 +1,852 @@
|
||||
import PDFDocument from 'src/api/PDFDocument';
|
||||
import PDFPage from 'src/api/PDFPage';
|
||||
import PDFField from 'src/api/form/PDFField';
|
||||
import PDFButton from 'src/api/form/PDFButton';
|
||||
import PDFCheckBox from 'src/api/form/PDFCheckBox';
|
||||
import PDFDropdown from 'src/api/form/PDFDropdown';
|
||||
import PDFOptionList from 'src/api/form/PDFOptionList';
|
||||
import PDFRadioGroup from 'src/api/form/PDFRadioGroup';
|
||||
import PDFSignature from 'src/api/form/PDFSignature';
|
||||
import PDFTextField from 'src/api/form/PDFTextField';
|
||||
import {
|
||||
NoSuchFieldError,
|
||||
UnexpectedFieldTypeError,
|
||||
FieldAlreadyExistsError,
|
||||
InvalidFieldNamePartError,
|
||||
} from 'src/api/errors';
|
||||
import PDFFont from 'src/api/PDFFont';
|
||||
import { StandardFonts } from 'src/api/StandardFonts';
|
||||
import { rotateInPlace } from 'src/api/operations';
|
||||
import {
|
||||
drawObject,
|
||||
popGraphicsState,
|
||||
pushGraphicsState,
|
||||
translate,
|
||||
} from 'src/api/operators';
|
||||
import {
|
||||
PDFAcroForm,
|
||||
PDFAcroField,
|
||||
PDFAcroCheckBox,
|
||||
PDFAcroComboBox,
|
||||
PDFAcroListBox,
|
||||
PDFAcroRadioButton,
|
||||
PDFAcroSignature,
|
||||
PDFAcroText,
|
||||
PDFAcroPushButton,
|
||||
PDFAcroNonTerminal,
|
||||
PDFDict,
|
||||
PDFOperator,
|
||||
PDFRef,
|
||||
createPDFAcroFields,
|
||||
PDFName,
|
||||
PDFWidgetAnnotation,
|
||||
} from 'src/core';
|
||||
import { assertIs, Cache, assertOrUndefined } from 'src/utils';
|
||||
|
||||
export interface FlattenOptions {
|
||||
updateFieldAppearances: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the interactive form of a [[PDFDocument]].
|
||||
*
|
||||
* Interactive forms (sometimes called _AcroForms_) are collections of fields
|
||||
* designed to gather information from a user. A PDF document may contains any
|
||||
* number of fields that appear on various pages, all of which make up a single,
|
||||
* global interactive form spanning the entire document. This means that
|
||||
* instances of [[PDFDocument]] shall contain at most one [[PDFForm]].
|
||||
*
|
||||
* The fields of an interactive form are represented by [[PDFField]] instances.
|
||||
*/
|
||||
export default class PDFForm {
|
||||
/**
|
||||
* > **NOTE:** You probably don't want to call this method directly. Instead,
|
||||
* > consider using the [[PDFDocument.getForm]] method, which will create an
|
||||
* > instance of [[PDFForm]] for you.
|
||||
*
|
||||
* Create an instance of [[PDFForm]] from an existing acroForm and embedder
|
||||
*
|
||||
* @param acroForm The underlying `PDFAcroForm` for this form.
|
||||
* @param doc The document to which the form will belong.
|
||||
*/
|
||||
static of = (acroForm: PDFAcroForm, doc: PDFDocument) =>
|
||||
new PDFForm(acroForm, doc);
|
||||
|
||||
/** The low-level PDFAcroForm wrapped by this form. */
|
||||
readonly acroForm: PDFAcroForm;
|
||||
|
||||
/** The document to which this form belongs. */
|
||||
readonly doc: PDFDocument;
|
||||
|
||||
private readonly dirtyFields: Set<PDFRef>;
|
||||
private readonly defaultFontCache: Cache<PDFFont>;
|
||||
|
||||
private constructor(acroForm: PDFAcroForm, doc: PDFDocument) {
|
||||
assertIs(acroForm, 'acroForm', [[PDFAcroForm, 'PDFAcroForm']]);
|
||||
assertIs(doc, 'doc', [[PDFDocument, 'PDFDocument']]);
|
||||
|
||||
this.acroForm = acroForm;
|
||||
this.doc = doc;
|
||||
|
||||
this.dirtyFields = new Set();
|
||||
this.defaultFontCache = Cache.populatedBy(this.embedDefaultFont);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if this [[PDFForm]] has XFA data. Most PDFs with form
|
||||
* fields do not use XFA as it is not widely supported by PDF readers.
|
||||
*
|
||||
* > `pdf-lib` does not support creation, modification, or reading of XFA
|
||||
* > fields.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* const form = pdfDoc.getForm()
|
||||
* if (form.hasXFA()) console.log('PDF has XFA data')
|
||||
* ```
|
||||
* @returns Whether or not this form has XFA data.
|
||||
*/
|
||||
hasXFA(): boolean {
|
||||
return this.acroForm.dict.has(PDFName.of('XFA'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect the XFA data from this [[PDFForm]] (if any exists). This will
|
||||
* force readers to fallback to standard fields if the [[PDFDocument]]
|
||||
* contains any. For example:
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* const form = pdfDoc.getForm()
|
||||
* form.deleteXFA()
|
||||
* ```
|
||||
*/
|
||||
deleteXFA(): void {
|
||||
this.acroForm.dict.delete(PDFName.of('XFA'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all fields contained in this [[PDFForm]]. For example:
|
||||
* ```js
|
||||
* const form = pdfDoc.getForm()
|
||||
* const fields = form.getFields()
|
||||
* fields.forEach(field => {
|
||||
* const type = field.constructor.name
|
||||
* const name = field.getName()
|
||||
* console.log(`${type}: ${name}`)
|
||||
* })
|
||||
* ```
|
||||
* @returns An array of all fields in this form.
|
||||
*/
|
||||
getFields(): PDFField[] {
|
||||
const allFields = this.acroForm.getAllFields();
|
||||
|
||||
const fields: PDFField[] = [];
|
||||
for (let idx = 0, len = allFields.length; idx < len; idx++) {
|
||||
const [acroField, ref] = allFields[idx];
|
||||
const field = convertToPDFField(acroField, ref, this.doc);
|
||||
if (field) fields.push(field);
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the field in this [[PDFForm]] with the given name. For example:
|
||||
* ```js
|
||||
* const form = pdfDoc.getForm()
|
||||
* const field = form.getFieldMaybe('Page1.Foo.Bar[0]')
|
||||
* if (field) console.log('Field exists!')
|
||||
* ```
|
||||
* @param name A fully qualified field name.
|
||||
* @returns The field with the specified name, if one exists.
|
||||
*/
|
||||
getFieldMaybe(name: string): PDFField | undefined {
|
||||
assertIs(name, 'name', ['string']);
|
||||
const fields = this.getFields();
|
||||
for (let idx = 0, len = fields.length; idx < len; idx++) {
|
||||
const field = fields[idx];
|
||||
if (field.getName() === name) return field;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the field in this [[PDFForm]] with the given name. For example:
|
||||
* ```js
|
||||
* const form = pdfDoc.getForm()
|
||||
* const field = form.getField('Page1.Foo.Bar[0]')
|
||||
* ```
|
||||
* If no field exists with the provided name, an error will be thrown.
|
||||
* @param name A fully qualified field name.
|
||||
* @returns The field with the specified name.
|
||||
*/
|
||||
getField(name: string): PDFField {
|
||||
assertIs(name, 'name', ['string']);
|
||||
const field = this.getFieldMaybe(name);
|
||||
if (field) return field;
|
||||
throw new NoSuchFieldError(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the button field in this [[PDFForm]] with the given name. For example:
|
||||
* ```js
|
||||
* const form = pdfDoc.getForm()
|
||||
* const button = form.getButton('Page1.Foo.Button[0]')
|
||||
* ```
|
||||
* An error will be thrown if no field exists with the provided name, or if
|
||||
* the field exists but is not a button.
|
||||
* @param name A fully qualified button name.
|
||||
* @returns The button with the specified name.
|
||||
*/
|
||||
getButton(name: string): PDFButton {
|
||||
assertIs(name, 'name', ['string']);
|
||||
const field = this.getField(name);
|
||||
if (field instanceof PDFButton) return field;
|
||||
throw new UnexpectedFieldTypeError(name, PDFButton, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the check box field in this [[PDFForm]] with the given name.
|
||||
* For example:
|
||||
* ```js
|
||||
* const form = pdfDoc.getForm()
|
||||
* const checkBox = form.getCheckBox('Page1.Foo.CheckBox[0]')
|
||||
* checkBox.check()
|
||||
* ```
|
||||
* An error will be thrown if no field exists with the provided name, or if
|
||||
* the field exists but is not a check box.
|
||||
* @param name A fully qualified check box name.
|
||||
* @returns The check box with the specified name.
|
||||
*/
|
||||
getCheckBox(name: string): PDFCheckBox {
|
||||
assertIs(name, 'name', ['string']);
|
||||
const field = this.getField(name);
|
||||
if (field instanceof PDFCheckBox) return field;
|
||||
throw new UnexpectedFieldTypeError(name, PDFCheckBox, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the dropdown field in this [[PDFForm]] with the given name.
|
||||
* For example:
|
||||
* ```js
|
||||
* const form = pdfDoc.getForm()
|
||||
* const dropdown = form.getDropdown('Page1.Foo.Dropdown[0]')
|
||||
* const options = dropdown.getOptions()
|
||||
* dropdown.select(options[0])
|
||||
* ```
|
||||
* An error will be thrown if no field exists with the provided name, or if
|
||||
* the field exists but is not a dropdown.
|
||||
* @param name A fully qualified dropdown name.
|
||||
* @returns The dropdown with the specified name.
|
||||
*/
|
||||
getDropdown(name: string): PDFDropdown {
|
||||
assertIs(name, 'name', ['string']);
|
||||
const field = this.getField(name);
|
||||
if (field instanceof PDFDropdown) return field;
|
||||
throw new UnexpectedFieldTypeError(name, PDFDropdown, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the option list field in this [[PDFForm]] with the given name.
|
||||
* For example:
|
||||
* ```js
|
||||
* const form = pdfDoc.getForm()
|
||||
* const optionList = form.getOptionList('Page1.Foo.OptionList[0]')
|
||||
* const options = optionList.getOptions()
|
||||
* optionList.select(options[0])
|
||||
* ```
|
||||
* An error will be thrown if no field exists with the provided name, or if
|
||||
* the field exists but is not an option list.
|
||||
* @param name A fully qualified option list name.
|
||||
* @returns The option list with the specified name.
|
||||
*/
|
||||
getOptionList(name: string): PDFOptionList {
|
||||
assertIs(name, 'name', ['string']);
|
||||
const field = this.getField(name);
|
||||
if (field instanceof PDFOptionList) return field;
|
||||
throw new UnexpectedFieldTypeError(name, PDFOptionList, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the radio group field in this [[PDFForm]] with the given name.
|
||||
* For example:
|
||||
* ```js
|
||||
* const form = pdfDoc.getForm()
|
||||
* const radioGroup = form.getRadioGroup('Page1.Foo.RadioGroup[0]')
|
||||
* const options = radioGroup.getOptions()
|
||||
* radioGroup.select(options[0])
|
||||
* ```
|
||||
* An error will be thrown if no field exists with the provided name, or if
|
||||
* the field exists but is not a radio group.
|
||||
* @param name A fully qualified radio group name.
|
||||
* @returns The radio group with the specified name.
|
||||
*/
|
||||
getRadioGroup(name: string): PDFRadioGroup {
|
||||
assertIs(name, 'name', ['string']);
|
||||
const field = this.getField(name);
|
||||
if (field instanceof PDFRadioGroup) return field;
|
||||
throw new UnexpectedFieldTypeError(name, PDFRadioGroup, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the signature field in this [[PDFForm]] with the given name.
|
||||
* For example:
|
||||
* ```js
|
||||
* const form = pdfDoc.getForm()
|
||||
* const signature = form.getSignature('Page1.Foo.Signature[0]')
|
||||
* ```
|
||||
* An error will be thrown if no field exists with the provided name, or if
|
||||
* the field exists but is not a signature.
|
||||
* @param name A fully qualified signature name.
|
||||
* @returns The signature with the specified name.
|
||||
*/
|
||||
getSignature(name: string): PDFSignature {
|
||||
assertIs(name, 'name', ['string']);
|
||||
const field = this.getField(name);
|
||||
if (field instanceof PDFSignature) return field;
|
||||
throw new UnexpectedFieldTypeError(name, PDFSignature, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text field in this [[PDFForm]] with the given name.
|
||||
* For example:
|
||||
* ```js
|
||||
* const form = pdfDoc.getForm()
|
||||
* const textField = form.getTextField('Page1.Foo.TextField[0]')
|
||||
* textField.setText('Are you designed to act or to be acted upon?')
|
||||
* ```
|
||||
* An error will be thrown if no field exists with the provided name, or if
|
||||
* the field exists but is not a text field.
|
||||
* @param name A fully qualified text field name.
|
||||
* @returns The text field with the specified name.
|
||||
*/
|
||||
getTextField(name: string): PDFTextField {
|
||||
assertIs(name, 'name', ['string']);
|
||||
const field = this.getField(name);
|
||||
if (field instanceof PDFTextField) return field;
|
||||
throw new UnexpectedFieldTypeError(name, PDFTextField, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new button field in this [[PDFForm]] with the given name.
|
||||
* For example:
|
||||
* ```js
|
||||
* const font = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||
* const page = pdfDoc.addPage()
|
||||
*
|
||||
* const form = pdfDoc.getForm()
|
||||
* const button = form.createButton('cool.new.button')
|
||||
*
|
||||
* button.addToPage('Do Stuff', font, page)
|
||||
* ```
|
||||
* An error will be thrown if a field already exists with the provided name.
|
||||
* @param name The fully qualified name for the new button.
|
||||
* @returns The new button field.
|
||||
*/
|
||||
createButton(name: string): PDFButton {
|
||||
assertIs(name, 'name', ['string']);
|
||||
|
||||
const nameParts = splitFieldName(name);
|
||||
const parent = this.findOrCreateNonTerminals(nameParts.nonTerminal);
|
||||
|
||||
const button = PDFAcroPushButton.create(this.doc.context);
|
||||
button.setPartialName(nameParts.terminal);
|
||||
|
||||
addFieldToParent(parent, [button, button.ref], nameParts.terminal);
|
||||
|
||||
return PDFButton.of(button, button.ref, this.doc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new check box field in this [[PDFForm]] with the given name.
|
||||
* For example:
|
||||
* ```js
|
||||
* const font = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||
* const page = pdfDoc.addPage()
|
||||
*
|
||||
* const form = pdfDoc.getForm()
|
||||
* const checkBox = form.createCheckBox('cool.new.checkBox')
|
||||
*
|
||||
* checkBox.addToPage(page)
|
||||
* ```
|
||||
* An error will be thrown if a field already exists with the provided name.
|
||||
* @param name The fully qualified name for the new check box.
|
||||
* @returns The new check box field.
|
||||
*/
|
||||
createCheckBox(name: string): PDFCheckBox {
|
||||
assertIs(name, 'name', ['string']);
|
||||
|
||||
const nameParts = splitFieldName(name);
|
||||
const parent = this.findOrCreateNonTerminals(nameParts.nonTerminal);
|
||||
|
||||
const checkBox = PDFAcroCheckBox.create(this.doc.context);
|
||||
checkBox.setPartialName(nameParts.terminal);
|
||||
|
||||
addFieldToParent(parent, [checkBox, checkBox.ref], nameParts.terminal);
|
||||
|
||||
return PDFCheckBox.of(checkBox, checkBox.ref, this.doc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new dropdown field in this [[PDFForm]] with the given name.
|
||||
* For example:
|
||||
* ```js
|
||||
* const font = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||
* const page = pdfDoc.addPage()
|
||||
*
|
||||
* const form = pdfDoc.getForm()
|
||||
* const dropdown = form.createDropdown('cool.new.dropdown')
|
||||
*
|
||||
* dropdown.addToPage(font, page)
|
||||
* ```
|
||||
* An error will be thrown if a field already exists with the provided name.
|
||||
* @param name The fully qualified name for the new dropdown.
|
||||
* @returns The new dropdown field.
|
||||
*/
|
||||
createDropdown(name: string): PDFDropdown {
|
||||
assertIs(name, 'name', ['string']);
|
||||
|
||||
const nameParts = splitFieldName(name);
|
||||
const parent = this.findOrCreateNonTerminals(nameParts.nonTerminal);
|
||||
|
||||
const comboBox = PDFAcroComboBox.create(this.doc.context);
|
||||
comboBox.setPartialName(nameParts.terminal);
|
||||
|
||||
addFieldToParent(parent, [comboBox, comboBox.ref], nameParts.terminal);
|
||||
|
||||
return PDFDropdown.of(comboBox, comboBox.ref, this.doc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new option list field in this [[PDFForm]] with the given name.
|
||||
* For example:
|
||||
* ```js
|
||||
* const font = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||
* const page = pdfDoc.addPage()
|
||||
*
|
||||
* const form = pdfDoc.getForm()
|
||||
* const optionList = form.createOptionList('cool.new.optionList')
|
||||
*
|
||||
* optionList.addToPage(font, page)
|
||||
* ```
|
||||
* An error will be thrown if a field already exists with the provided name.
|
||||
* @param name The fully qualified name for the new option list.
|
||||
* @returns The new option list field.
|
||||
*/
|
||||
createOptionList(name: string): PDFOptionList {
|
||||
assertIs(name, 'name', ['string']);
|
||||
|
||||
const nameParts = splitFieldName(name);
|
||||
const parent = this.findOrCreateNonTerminals(nameParts.nonTerminal);
|
||||
|
||||
const listBox = PDFAcroListBox.create(this.doc.context);
|
||||
listBox.setPartialName(nameParts.terminal);
|
||||
|
||||
addFieldToParent(parent, [listBox, listBox.ref], nameParts.terminal);
|
||||
|
||||
return PDFOptionList.of(listBox, listBox.ref, this.doc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new radio group field in this [[PDFForm]] with the given name.
|
||||
* For example:
|
||||
* ```js
|
||||
* const font = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||
* const page = pdfDoc.addPage()
|
||||
*
|
||||
* const form = pdfDoc.getForm()
|
||||
* const radioGroup = form.createRadioGroup('cool.new.radioGroup')
|
||||
*
|
||||
* radioGroup.addOptionToPage('is-dog', page, { y: 0 })
|
||||
* radioGroup.addOptionToPage('is-cat', page, { y: 75 })
|
||||
* ```
|
||||
* An error will be thrown if a field already exists with the provided name.
|
||||
* @param name The fully qualified name for the new radio group.
|
||||
* @returns The new radio group field.
|
||||
*/
|
||||
createRadioGroup(name: string): PDFRadioGroup {
|
||||
assertIs(name, 'name', ['string']);
|
||||
const nameParts = splitFieldName(name);
|
||||
|
||||
const parent = this.findOrCreateNonTerminals(nameParts.nonTerminal);
|
||||
|
||||
const radioButton = PDFAcroRadioButton.create(this.doc.context);
|
||||
radioButton.setPartialName(nameParts.terminal);
|
||||
|
||||
addFieldToParent(
|
||||
parent,
|
||||
[radioButton, radioButton.ref],
|
||||
nameParts.terminal,
|
||||
);
|
||||
|
||||
return PDFRadioGroup.of(radioButton, radioButton.ref, this.doc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new text field in this [[PDFForm]] with the given name.
|
||||
* For example:
|
||||
* ```js
|
||||
* const font = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||
* const page = pdfDoc.addPage()
|
||||
*
|
||||
* const form = pdfDoc.getForm()
|
||||
* const textField = form.createTextField('cool.new.textField')
|
||||
*
|
||||
* textField.addToPage(font, page)
|
||||
* ```
|
||||
* An error will be thrown if a field already exists with the provided name.
|
||||
* @param name The fully qualified name for the new radio group.
|
||||
* @returns The new radio group field.
|
||||
*/
|
||||
createTextField(name: string): PDFTextField {
|
||||
assertIs(name, 'name', ['string']);
|
||||
const nameParts = splitFieldName(name);
|
||||
|
||||
const parent = this.findOrCreateNonTerminals(nameParts.nonTerminal);
|
||||
|
||||
const text = PDFAcroText.create(this.doc.context);
|
||||
text.setPartialName(nameParts.terminal);
|
||||
|
||||
addFieldToParent(parent, [text, text.ref], nameParts.terminal);
|
||||
|
||||
return PDFTextField.of(text, text.ref, this.doc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten all fields in this [[PDFForm]].
|
||||
*
|
||||
* Flattening a form field will take the current appearance for each of that
|
||||
* field's widgets and make them part of their page's content stream. All form
|
||||
* fields and annotations associated are then removed. Note that once a form
|
||||
* has been flattened its fields can no longer be accessed or edited.
|
||||
*
|
||||
* This operation is often used after filling form fields to ensure a
|
||||
* consistent appearance across different PDF readers and/or printers.
|
||||
* Another common use case is to copy a template document with form fields
|
||||
* into another document. In this scenario you would load the template
|
||||
* document, fill its fields, flatten it, and then copy its pages into the
|
||||
* recipient document - the filled fields will be copied over.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* const form = pdfDoc.getForm();
|
||||
* form.flatten();
|
||||
* ```
|
||||
*/
|
||||
flatten(options: FlattenOptions = { updateFieldAppearances: true }) {
|
||||
if (options.updateFieldAppearances) {
|
||||
this.updateFieldAppearances();
|
||||
}
|
||||
|
||||
const fields = this.getFields();
|
||||
|
||||
for (let i = 0, lenFields = fields.length; i < lenFields; i++) {
|
||||
const field = fields[i];
|
||||
const widgets = field.acroField.getWidgets();
|
||||
|
||||
for (let j = 0, lenWidgets = widgets.length; j < lenWidgets; j++) {
|
||||
const widget = widgets[j];
|
||||
const page = this.findWidgetPage(widget);
|
||||
const widgetRef = this.findWidgetAppearanceRef(field, widget);
|
||||
|
||||
const xObjectKey = page.node.newXObject('FlatWidget', widgetRef);
|
||||
|
||||
const rectangle = widget.getRectangle();
|
||||
const operators = [
|
||||
pushGraphicsState(),
|
||||
translate(rectangle.x, rectangle.y),
|
||||
...rotateInPlace({ ...rectangle, rotation: 0 }),
|
||||
drawObject(xObjectKey),
|
||||
popGraphicsState(),
|
||||
].filter(Boolean) as PDFOperator[];
|
||||
|
||||
page.pushOperators(...operators);
|
||||
}
|
||||
|
||||
this.removeField(field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a field from this [[PDFForm]].
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* const form = pdfDoc.getForm();
|
||||
* const ageField = form.getFields().find(x => x.getName() === 'Age');
|
||||
* form.removeField(ageField);
|
||||
* ```
|
||||
*/
|
||||
removeField(field: PDFField) {
|
||||
const widgets = field.acroField.getWidgets();
|
||||
const pages: Set<PDFPage> = new Set();
|
||||
|
||||
for (let i = 0, len = widgets.length; i < len; i++) {
|
||||
const widget = widgets[i];
|
||||
const widgetRef = this.findWidgetAppearanceRef(field, widget);
|
||||
|
||||
const page = this.findWidgetPage(widget);
|
||||
pages.add(page);
|
||||
|
||||
page.node.removeAnnot(widgetRef);
|
||||
}
|
||||
|
||||
pages.forEach((page) => page.node.removeAnnot(field.ref));
|
||||
this.acroForm.removeField(field.acroField);
|
||||
const fieldKids = field.acroField.normalizedEntries().Kids;
|
||||
const kidsCount = fieldKids.size();
|
||||
for (let childIndex = 0; childIndex < kidsCount; childIndex++) {
|
||||
const child = fieldKids.get(childIndex);
|
||||
if (child instanceof PDFRef) {
|
||||
this.doc.context.delete(child);
|
||||
}
|
||||
}
|
||||
this.doc.context.delete(field.ref);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the appearance streams for all widgets of all fields in this
|
||||
* [[PDFForm]]. Appearance streams will only be created for a widget if it
|
||||
* does not have any existing appearance streams, or the field's value has
|
||||
* changed (e.g. by calling [[PDFTextField.setText]] or
|
||||
* [[PDFDropdown.select]]).
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* const courier = await pdfDoc.embedFont(StandardFonts.Courier)
|
||||
* const form = pdfDoc.getForm()
|
||||
* form.updateFieldAppearances(courier)
|
||||
* ```
|
||||
*
|
||||
* **IMPORTANT:** The default value for the `font` parameter is
|
||||
* [[StandardFonts.Helvetica]]. Note that this is a WinAnsi font. This means
|
||||
* that encoding errors will be thrown if any fields contain text with
|
||||
* characters outside the WinAnsi character set (the latin alphabet).
|
||||
*
|
||||
* Embedding a custom font and passing that as the `font`
|
||||
* parameter allows you to generate appearance streams with non WinAnsi
|
||||
* characters (assuming your custom font supports them).
|
||||
*
|
||||
* > **NOTE:** The [[PDFDocument.save]] method will call this method to
|
||||
* > update appearances automatically if a form was accessed via the
|
||||
* > [[PDFDocument.getForm]] method prior to saving.
|
||||
*
|
||||
* @param font Optionally, the font to use when creating new appearances.
|
||||
*/
|
||||
updateFieldAppearances(font?: PDFFont) {
|
||||
assertOrUndefined(font, 'font', [[PDFFont, 'PDFFont']]);
|
||||
|
||||
font = font ?? this.getDefaultFont();
|
||||
|
||||
const fields = this.getFields();
|
||||
|
||||
for (let idx = 0, len = fields.length; idx < len; idx++) {
|
||||
const field = fields[idx];
|
||||
if (field.needsAppearancesUpdate()) {
|
||||
field.defaultUpdateAppearances(font);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a field as dirty. This will cause its appearance streams to be
|
||||
* updated by [[PDFForm.updateFieldAppearances]].
|
||||
* ```js
|
||||
* const form = pdfDoc.getForm()
|
||||
* const field = form.getField('foo.bar')
|
||||
* form.markFieldAsDirty(field.ref)
|
||||
* ```
|
||||
* @param fieldRef The reference to the field that should be marked.
|
||||
*/
|
||||
markFieldAsDirty(fieldRef: PDFRef) {
|
||||
assertOrUndefined(fieldRef, 'fieldRef', [[PDFRef, 'PDFRef']]);
|
||||
this.dirtyFields.add(fieldRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a field as dirty. This will cause its appearance streams to not be
|
||||
* updated by [[PDFForm.updateFieldAppearances]].
|
||||
* ```js
|
||||
* const form = pdfDoc.getForm()
|
||||
* const field = form.getField('foo.bar')
|
||||
* form.markFieldAsClean(field.ref)
|
||||
* ```
|
||||
* @param fieldRef The reference to the field that should be marked.
|
||||
*/
|
||||
markFieldAsClean(fieldRef: PDFRef) {
|
||||
assertOrUndefined(fieldRef, 'fieldRef', [[PDFRef, 'PDFRef']]);
|
||||
this.dirtyFields.delete(fieldRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` is the specified field has been marked as dirty.
|
||||
* ```js
|
||||
* const form = pdfDoc.getForm()
|
||||
* const field = form.getField('foo.bar')
|
||||
* if (form.fieldIsDirty(field.ref)) console.log('Field is dirty')
|
||||
* ```
|
||||
* @param fieldRef The reference to the field that should be checked.
|
||||
* @returns Whether or not the specified field is dirty.
|
||||
*/
|
||||
fieldIsDirty(fieldRef: PDFRef): boolean {
|
||||
assertOrUndefined(fieldRef, 'fieldRef', [[PDFRef, 'PDFRef']]);
|
||||
return this.dirtyFields.has(fieldRef);
|
||||
}
|
||||
|
||||
getDefaultFont() {
|
||||
return this.defaultFontCache.access();
|
||||
}
|
||||
|
||||
private findWidgetPage(widget: PDFWidgetAnnotation): PDFPage {
|
||||
const pageRef = widget.P();
|
||||
let page = this.doc.getPages().find((x) => x.ref === pageRef);
|
||||
if (page === undefined) {
|
||||
const widgetRef = this.doc.context.getObjectRef(widget.dict);
|
||||
if (widgetRef === undefined) {
|
||||
throw new Error('Could not find PDFRef for PDFObject');
|
||||
}
|
||||
|
||||
page = this.doc.findPageForAnnotationRef(widgetRef);
|
||||
|
||||
if (page === undefined) {
|
||||
throw new Error(`Could not find page for PDFRef ${widgetRef}`);
|
||||
}
|
||||
}
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
private findWidgetAppearanceRef(
|
||||
field: PDFField,
|
||||
widget: PDFWidgetAnnotation,
|
||||
): PDFRef {
|
||||
let refOrDict = widget.getNormalAppearance();
|
||||
|
||||
if (
|
||||
refOrDict instanceof PDFDict &&
|
||||
(field instanceof PDFCheckBox || field instanceof PDFRadioGroup)
|
||||
) {
|
||||
const value = field.acroField.getValue();
|
||||
const ref = refOrDict.get(value) ?? refOrDict.get(PDFName.of('Off'));
|
||||
|
||||
if (ref instanceof PDFRef) {
|
||||
refOrDict = ref;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(refOrDict instanceof PDFRef)) {
|
||||
const name = field.getName();
|
||||
throw new Error(`Failed to extract appearance ref for: ${name}`);
|
||||
}
|
||||
|
||||
return refOrDict;
|
||||
}
|
||||
|
||||
private findOrCreateNonTerminals(partialNames: string[]) {
|
||||
let nonTerminal: [PDFAcroForm] | [PDFAcroNonTerminal, PDFRef] = [
|
||||
this.acroForm,
|
||||
];
|
||||
for (let idx = 0, len = partialNames.length; idx < len; idx++) {
|
||||
const namePart = partialNames[idx];
|
||||
if (!namePart) throw new InvalidFieldNamePartError(namePart);
|
||||
const [parent, parentRef] = nonTerminal;
|
||||
const res = this.findNonTerminal(namePart, parent);
|
||||
|
||||
if (res) {
|
||||
nonTerminal = res;
|
||||
} else {
|
||||
const node = PDFAcroNonTerminal.create(this.doc.context);
|
||||
node.setPartialName(namePart);
|
||||
node.setParent(parentRef);
|
||||
const nodeRef = this.doc.context.register(node.dict);
|
||||
parent.addField(nodeRef);
|
||||
nonTerminal = [node, nodeRef];
|
||||
}
|
||||
}
|
||||
return nonTerminal;
|
||||
}
|
||||
|
||||
private findNonTerminal(
|
||||
partialName: string,
|
||||
parent: PDFAcroForm | PDFAcroNonTerminal,
|
||||
): [PDFAcroNonTerminal, PDFRef] | undefined {
|
||||
const fields =
|
||||
parent instanceof PDFAcroForm
|
||||
? this.acroForm.getFields()
|
||||
: createPDFAcroFields(parent.Kids());
|
||||
|
||||
for (let idx = 0, len = fields.length; idx < len; idx++) {
|
||||
const [field, ref] = fields[idx];
|
||||
if (field.getPartialName() === partialName) {
|
||||
if (field instanceof PDFAcroNonTerminal) return [field, ref];
|
||||
throw new FieldAlreadyExistsError(partialName);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private embedDefaultFont = (): PDFFont =>
|
||||
this.doc.embedStandardFont(StandardFonts.Helvetica);
|
||||
}
|
||||
|
||||
const convertToPDFField = (
|
||||
field: PDFAcroField,
|
||||
ref: PDFRef,
|
||||
doc: PDFDocument,
|
||||
): PDFField | undefined => {
|
||||
if (field instanceof PDFAcroPushButton) return PDFButton.of(field, ref, doc);
|
||||
if (field instanceof PDFAcroCheckBox) return PDFCheckBox.of(field, ref, doc);
|
||||
if (field instanceof PDFAcroComboBox) return PDFDropdown.of(field, ref, doc);
|
||||
if (field instanceof PDFAcroListBox) return PDFOptionList.of(field, ref, doc);
|
||||
if (field instanceof PDFAcroText) return PDFTextField.of(field, ref, doc);
|
||||
if (field instanceof PDFAcroRadioButton) {
|
||||
return PDFRadioGroup.of(field, ref, doc);
|
||||
}
|
||||
if (field instanceof PDFAcroSignature) {
|
||||
return PDFSignature.of(field, ref, doc);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const splitFieldName = (fullyQualifiedName: string) => {
|
||||
if (fullyQualifiedName.length === 0) {
|
||||
throw new Error('PDF field names must not be empty strings');
|
||||
}
|
||||
|
||||
const parts = fullyQualifiedName.split('.');
|
||||
|
||||
for (let idx = 0, len = parts.length; idx < len; idx++) {
|
||||
if (parts[idx] === '') {
|
||||
throw new Error(
|
||||
`Periods in PDF field names must be separated by at least one character: "${fullyQualifiedName}"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (parts.length === 1) return { nonTerminal: [], terminal: parts[0] };
|
||||
|
||||
return {
|
||||
nonTerminal: parts.slice(0, parts.length - 1),
|
||||
terminal: parts[parts.length - 1],
|
||||
};
|
||||
};
|
||||
|
||||
const addFieldToParent = (
|
||||
[parent, parentRef]: [PDFAcroForm] | [PDFAcroNonTerminal, PDFRef],
|
||||
[field, fieldRef]: [PDFAcroField, PDFRef],
|
||||
partialName: string,
|
||||
) => {
|
||||
const entries = parent.normalizedEntries();
|
||||
const fields = createPDFAcroFields(
|
||||
'Kids' in entries ? entries.Kids : entries.Fields,
|
||||
);
|
||||
for (let idx = 0, len = fields.length; idx < len; idx++) {
|
||||
if (fields[idx][0].getPartialName() === partialName) {
|
||||
throw new FieldAlreadyExistsError(partialName);
|
||||
}
|
||||
}
|
||||
parent.addField(fieldRef);
|
||||
field.setParent(parentRef);
|
||||
};
|
||||
571
node_modules/pdf-lib/src/api/form/PDFOptionList.ts
generated
vendored
Normal file
571
node_modules/pdf-lib/src/api/form/PDFOptionList.ts
generated
vendored
Normal file
@@ -0,0 +1,571 @@
|
||||
import PDFDocument from 'src/api/PDFDocument';
|
||||
import PDFPage from 'src/api/PDFPage';
|
||||
import PDFFont from 'src/api/PDFFont';
|
||||
import PDFField, {
|
||||
FieldAppearanceOptions,
|
||||
assertFieldAppearanceOptions,
|
||||
} from 'src/api/form/PDFField';
|
||||
import {
|
||||
AppearanceProviderFor,
|
||||
normalizeAppearance,
|
||||
defaultOptionListAppearanceProvider,
|
||||
} from 'src/api/form/appearances';
|
||||
import { rgb } from 'src/api/colors';
|
||||
import { degrees } from 'src/api/rotations';
|
||||
|
||||
import {
|
||||
PDFRef,
|
||||
PDFHexString,
|
||||
PDFString,
|
||||
PDFStream,
|
||||
PDFAcroListBox,
|
||||
AcroChoiceFlags,
|
||||
PDFWidgetAnnotation,
|
||||
} from 'src/core';
|
||||
import {
|
||||
assertIs,
|
||||
assertIsSubset,
|
||||
assertOrUndefined,
|
||||
assertPositive,
|
||||
} from 'src/utils';
|
||||
|
||||
/**
|
||||
* Represents an option list field of a [[PDFForm]].
|
||||
*
|
||||
* [[PDFOptionList]] fields are interactive lists of options. The purpose of an
|
||||
* option list is to enable users to select one or more options from a set of
|
||||
* possible options. Users are able to see the full set of options without
|
||||
* first having to click on the field (though scrolling may be necessary).
|
||||
* Clicking an option in the list will cause it to be selected and displayed
|
||||
* with a highlighted background. Some option lists allow users to select
|
||||
* more than one option (see [[PDFOptionList.isMultiselect]]).
|
||||
*/
|
||||
export default class PDFOptionList extends PDFField {
|
||||
/**
|
||||
* > **NOTE:** You probably don't want to call this method directly. Instead,
|
||||
* > consider using the [[PDFForm.getOptionList]] method, which will create
|
||||
* > an instance of [[PDFOptionList]] for you.
|
||||
*
|
||||
* Create an instance of [[PDFOptionList]] from an existing acroListBox and
|
||||
* ref
|
||||
*
|
||||
* @param acroComboBox The underlying `PDFAcroListBox` for this option list.
|
||||
* @param ref The unique reference for this option list.
|
||||
* @param doc The document to which this option list will belong.
|
||||
*/
|
||||
static of = (acroListBox: PDFAcroListBox, ref: PDFRef, doc: PDFDocument) =>
|
||||
new PDFOptionList(acroListBox, ref, doc);
|
||||
|
||||
/** The low-level PDFAcroListBox wrapped by this option list. */
|
||||
readonly acroField: PDFAcroListBox;
|
||||
|
||||
private constructor(
|
||||
acroListBox: PDFAcroListBox,
|
||||
ref: PDFRef,
|
||||
doc: PDFDocument,
|
||||
) {
|
||||
super(acroListBox, ref, doc);
|
||||
|
||||
assertIs(acroListBox, 'acroListBox', [[PDFAcroListBox, 'PDFAcroListBox']]);
|
||||
|
||||
this.acroField = acroListBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of available options for this option list. These options will
|
||||
* be displayed to users who view this option list in a PDF reader.
|
||||
* For example:
|
||||
* ```js
|
||||
* const optionList = form.getOptionList('some.optionList.field')
|
||||
* const options = optionList.getOptions()
|
||||
* console.log('Option List options:', options)
|
||||
* ```
|
||||
* @returns The options for this option list.
|
||||
*/
|
||||
getOptions(): string[] {
|
||||
const rawOptions = this.acroField.getOptions();
|
||||
|
||||
const options = new Array<string>(rawOptions.length);
|
||||
for (let idx = 0, len = options.length; idx < len; idx++) {
|
||||
const { display, value } = rawOptions[idx];
|
||||
options[idx] = (display ?? value).decodeText();
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected options for this option list. These are the values that
|
||||
* were selected by a human user via a PDF reader, or programatically via
|
||||
* software.
|
||||
* For example:
|
||||
* ```js
|
||||
* const optionList = form.getOptionList('some.optionList.field')
|
||||
* const selections = optionList.getSelected()
|
||||
* console.log('Option List selections:', selections)
|
||||
* ```
|
||||
* @returns The selected options for this option list.
|
||||
*/
|
||||
getSelected(): string[] {
|
||||
const values = this.acroField.getValues();
|
||||
|
||||
const selected = new Array<string>(values.length);
|
||||
for (let idx = 0, len = values.length; idx < len; idx++) {
|
||||
selected[idx] = values[idx].decodeText();
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of options that are available for this option list. These are
|
||||
* the values that will be available for users to select when they view this
|
||||
* option list in a PDF reader. Note that preexisting options for this
|
||||
* option list will be removed. Only the values passed as `options` will be
|
||||
* available to select.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* const optionList = form.getOptionList('planets.optionList')
|
||||
* optionList.setOptions(['Earth', 'Mars', 'Pluto', 'Venus'])
|
||||
* ```
|
||||
*
|
||||
* This method will mark this option list as dirty, causing its appearance
|
||||
* streams to be updated when either [[PDFDocument.save]] or
|
||||
* [[PDFForm.updateFieldAppearances]] is called. The updated streams will
|
||||
* display the options this field contains inside the widgets of this text
|
||||
* field (with selected options highlighted).
|
||||
*
|
||||
* **IMPORTANT:** The default font used to update appearance streams is
|
||||
* [[StandardFonts.Helvetica]]. Note that this is a WinAnsi font. This means
|
||||
* that encoding errors will be thrown if this field contains any options
|
||||
* with characters outside the WinAnsi character set (the latin alphabet).
|
||||
*
|
||||
* Embedding a custom font and passing it to
|
||||
* [[PDFForm.updateFieldAppearances]] or [[PDFOptionList.updateAppearances]]
|
||||
* allows you to generate appearance streams with characters outside the
|
||||
* latin alphabet (assuming the custom font supports them).
|
||||
*
|
||||
* @param options The options that should be available in this option list.
|
||||
*/
|
||||
setOptions(options: string[]) {
|
||||
assertIs(options, 'options', [Array]);
|
||||
|
||||
this.markAsDirty();
|
||||
const optionObjects = new Array<{ value: PDFHexString }>(options.length);
|
||||
for (let idx = 0, len = options.length; idx < len; idx++) {
|
||||
optionObjects[idx] = { value: PDFHexString.fromText(options[idx]) };
|
||||
}
|
||||
this.acroField.setOptions(optionObjects);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to the list of options that are available for this option list. Users
|
||||
* will be able to select these values in a PDF reader. In addition to the
|
||||
* values passed as `options`, any preexisting options for this option list
|
||||
* will still be available for users to select.
|
||||
* For example:
|
||||
* ```js
|
||||
* const optionList = form.getOptionList('rockets.optionList')
|
||||
* optionList.addOptions(['Saturn IV', 'Falcon Heavy'])
|
||||
* ```
|
||||
* This method will mark this option list as dirty. See
|
||||
* [[PDFOptionList.setOptions]] for more details about what this means.
|
||||
* @param options New options that should be available in this option list.
|
||||
*/
|
||||
addOptions(options: string | string[]) {
|
||||
assertIs(options, 'options', ['string', Array]);
|
||||
|
||||
this.markAsDirty();
|
||||
|
||||
const optionsArr = Array.isArray(options) ? options : [options];
|
||||
|
||||
const existingOptions: {
|
||||
value: PDFString | PDFHexString;
|
||||
display?: PDFString | PDFHexString;
|
||||
}[] = this.acroField.getOptions();
|
||||
|
||||
const newOptions = new Array<{ value: PDFHexString }>(optionsArr.length);
|
||||
for (let idx = 0, len = optionsArr.length; idx < len; idx++) {
|
||||
newOptions[idx] = { value: PDFHexString.fromText(optionsArr[idx]) };
|
||||
}
|
||||
|
||||
this.acroField.setOptions(existingOptions.concat(newOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Select one or more values for this option list. This operation is analogous
|
||||
* to a human user opening the option list in a PDF reader and clicking on one
|
||||
* or more values to select them. This method will update the underlying state
|
||||
* of the option list to indicate which values have been selected. PDF
|
||||
* libraries and readers will be able to extract these values from the saved
|
||||
* document and determine which values were selected.
|
||||
* For example:
|
||||
* ```js
|
||||
* const optionList = form.getOptionList('best.superheroes.optionList')
|
||||
* optionList.select(['One Punch Man', 'Iron Man'])
|
||||
* ```
|
||||
* This method will mark this option list as dirty. See
|
||||
* [[PDFOptionList.setOptions]] for more details about what this means.
|
||||
* @param options The options to be selected.
|
||||
* @param merge Whether or not existing selections should be preserved.
|
||||
*/
|
||||
select(options: string | string[], merge = false) {
|
||||
assertIs(options, 'options', ['string', Array]);
|
||||
assertIs(merge, 'merge', ['boolean']);
|
||||
|
||||
const optionsArr = Array.isArray(options) ? options : [options];
|
||||
|
||||
const validOptions = this.getOptions();
|
||||
assertIsSubset(optionsArr, 'option', validOptions);
|
||||
|
||||
this.markAsDirty();
|
||||
|
||||
if (optionsArr.length > 1 || (optionsArr.length === 1 && merge)) {
|
||||
this.enableMultiselect();
|
||||
}
|
||||
|
||||
const values = new Array<PDFHexString>(optionsArr.length);
|
||||
for (let idx = 0, len = optionsArr.length; idx < len; idx++) {
|
||||
values[idx] = PDFHexString.fromText(optionsArr[idx]);
|
||||
}
|
||||
|
||||
if (merge) {
|
||||
const existingValues = this.acroField.getValues();
|
||||
this.acroField.setValues(existingValues.concat(values));
|
||||
} else {
|
||||
this.acroField.setValues(values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all selected values for this option list. This operation is
|
||||
* equivalent to selecting an empty list. This method will update the
|
||||
* underlying state of the option list to indicate that no values have been
|
||||
* selected.
|
||||
* For example:
|
||||
* ```js
|
||||
* const optionList = form.getOptionList('some.optionList.field')
|
||||
* optionList.clear()
|
||||
* ```
|
||||
* This method will mark this option list as dirty. See
|
||||
* [[PDFOptionList.setOptions]] for more details about what this means.
|
||||
*/
|
||||
clear() {
|
||||
this.markAsDirty();
|
||||
this.acroField.setValues([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the font size for the text in this field. There needs to be a
|
||||
* default appearance string (DA) set with a font value specified
|
||||
* for this to work. For example:
|
||||
* ```js
|
||||
* const optionList = form.getOptionList('some.optionList.field')
|
||||
* optionList.setFontSize(4);
|
||||
* ```
|
||||
* @param fontSize The font size to set the font to.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Set the font size for this field. Larger font sizes will result in larger
|
||||
* text being displayed when PDF readers render this option list. Font sizes
|
||||
* may be integer or floating point numbers. Supplying a negative font size
|
||||
* will cause this method to throw an error.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* const optionList = form.getOptionList('some.optionList.field')
|
||||
* optionList.setFontSize(4)
|
||||
* optionList.setFontSize(15.7)
|
||||
* ```
|
||||
*
|
||||
* > This method depends upon the existence of a default appearance
|
||||
* > (`/DA`) string. If this field does not have a default appearance string,
|
||||
* > or that string does not contain a font size (via the `Tf` operator),
|
||||
* > then this method will throw an error.
|
||||
*
|
||||
* @param fontSize The font size to be used when rendering text in this field.
|
||||
*/
|
||||
setFontSize(fontSize: number) {
|
||||
assertPositive(fontSize, 'fontSize');
|
||||
this.acroField.setFontSize(fontSize);
|
||||
this.markAsDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the options of this option list are always displayed
|
||||
* in alphabetical order, irrespective of the order in which the options
|
||||
* were added to the option list. See [[PDFOptionList.enableSorting]] and
|
||||
* [[PDFOptionList.disableSorting]]. For example:
|
||||
* ```js
|
||||
* const optionList = form.getOptionList('some.optionList.field')
|
||||
* if (optionList.isSorted()) console.log('Sorting is enabled')
|
||||
* ```
|
||||
* @returns Whether or not this option list is sorted.
|
||||
*/
|
||||
isSorted(): boolean {
|
||||
return this.acroField.hasFlag(AcroChoiceFlags.Sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Always display the options of this option list in alphabetical order,
|
||||
* irrespective of the order in which the options were added to this option
|
||||
* list.
|
||||
* For example:
|
||||
* ```js
|
||||
* const optionList = form.getOptionList('some.optionList.field')
|
||||
* optionList.enableSorting()
|
||||
* ```
|
||||
*/
|
||||
enableSorting() {
|
||||
this.acroField.setFlagTo(AcroChoiceFlags.Sort, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not always display the options of this option list in alphabetical
|
||||
* order. Instead, display the options in whichever order they were added
|
||||
* to this option list. For example:
|
||||
* ```js
|
||||
* const optionList = form.getOptionList('some.optionList.field')
|
||||
* optionList.disableSorting()
|
||||
* ```
|
||||
*/
|
||||
disableSorting() {
|
||||
this.acroField.setFlagTo(AcroChoiceFlags.Sort, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if multiple options can be selected from this option list.
|
||||
* See [[PDFOptionList.enableMultiselect]] and
|
||||
* [[PDFOptionList.disableMultiselect]]. For example:
|
||||
* ```js
|
||||
* const optionList = form.getOptionList('some.optionList.field')
|
||||
* if (optionList.isMultiselect()) console.log('Multiselect is enabled')
|
||||
* ```
|
||||
* @returns Whether or not multiple options can be selected.
|
||||
*/
|
||||
isMultiselect(): boolean {
|
||||
return this.acroField.hasFlag(AcroChoiceFlags.MultiSelect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow users to select more than one option from this option list.
|
||||
* For example:
|
||||
* ```js
|
||||
* const optionList = form.getOptionList('some.optionList.field')
|
||||
* optionList.enableMultiselect()
|
||||
* ```
|
||||
*/
|
||||
enableMultiselect() {
|
||||
this.acroField.setFlagTo(AcroChoiceFlags.MultiSelect, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not allow users to select more than one option from this option list.
|
||||
* For example:
|
||||
* ```js
|
||||
* const optionList = form.getOptionList('some.optionList.field')
|
||||
* optionList.disableMultiselect()
|
||||
* ```
|
||||
*/
|
||||
disableMultiselect() {
|
||||
this.acroField.setFlagTo(AcroChoiceFlags.MultiSelect, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the option selected by a user is stored, or "committed",
|
||||
* when the user clicks the option. The alternative is that the user's
|
||||
* selection is stored when the user leaves this option list field (by
|
||||
* clicking outside of it - on another field, for example). See
|
||||
* [[PDFOptionList.enableSelectOnClick]] and
|
||||
* [[PDFOptionList.disableSelectOnClick]]. For example:
|
||||
* ```js
|
||||
* const optionList = form.getOptionList('some.optionList.field')
|
||||
* if (optionList.isSelectOnClick()) console.log('Select on click is enabled')
|
||||
* ```
|
||||
* @returns Whether or not options are selected immediately after they are
|
||||
* clicked.
|
||||
*/
|
||||
isSelectOnClick(): boolean {
|
||||
return this.acroField.hasFlag(AcroChoiceFlags.CommitOnSelChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the option selected by a user immediately after the user clicks the
|
||||
* option. Do not wait for the user to leave this option list field (by
|
||||
* clicking outside of it - on another field, for example). For example:
|
||||
* ```js
|
||||
* const optionList = form.getOptionList('some.optionList.field')
|
||||
* optionList.enableSelectOnClick()
|
||||
* ```
|
||||
*/
|
||||
enableSelectOnClick() {
|
||||
this.acroField.setFlagTo(AcroChoiceFlags.CommitOnSelChange, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait to store the option selected by a user until they leave this option
|
||||
* list field (by clicking outside of it - on another field, for example).
|
||||
* For example:
|
||||
* ```js
|
||||
* const optionList = form.getOptionList('some.optionList.field')
|
||||
* optionList.disableSelectOnClick()
|
||||
* ```
|
||||
*/
|
||||
disableSelectOnClick() {
|
||||
this.acroField.setFlagTo(AcroChoiceFlags.CommitOnSelChange, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show this option list on the specified page. For example:
|
||||
* ```js
|
||||
* const ubuntuFont = await pdfDoc.embedFont(ubuntuFontBytes)
|
||||
* const page = pdfDoc.addPage()
|
||||
*
|
||||
* const form = pdfDoc.getForm()
|
||||
* const optionList = form.createOptionList('best.gundams')
|
||||
* optionList.setOptions(['Exia', 'Dynames', 'Kyrios', 'Virtue'])
|
||||
* optionList.select(['Exia', 'Virtue'])
|
||||
*
|
||||
* optionList.addToPage(page, {
|
||||
* x: 50,
|
||||
* y: 75,
|
||||
* width: 200,
|
||||
* height: 100,
|
||||
* textColor: rgb(1, 0, 0),
|
||||
* backgroundColor: rgb(0, 1, 0),
|
||||
* borderColor: rgb(0, 0, 1),
|
||||
* borderWidth: 2,
|
||||
* rotate: degrees(90),
|
||||
* font: ubuntuFont,
|
||||
* })
|
||||
* ```
|
||||
* This will create a new widget for this option list field.
|
||||
* @param page The page to which this option list widget should be added.
|
||||
* @param options The options to be used when adding this option list widget.
|
||||
*/
|
||||
addToPage(page: PDFPage, options?: FieldAppearanceOptions) {
|
||||
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;
|
||||
|
||||
// Create a widget for this option list
|
||||
const widget = this.createWidget({
|
||||
x: options.x ?? 0,
|
||||
y: options.y ?? 0,
|
||||
width: options.width ?? 200,
|
||||
height: options.height ?? 100,
|
||||
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);
|
||||
|
||||
// Add widget to this field
|
||||
this.acroField.addWidget(widgetRef);
|
||||
|
||||
// Set appearance streams for widget
|
||||
const font = options.font ?? this.doc.getForm().getDefaultFont();
|
||||
this.updateWidgetAppearance(widget, font);
|
||||
|
||||
// Add widget to the given page
|
||||
page.node.addAnnot(widgetRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if this option list has been marked as dirty, or if any of
|
||||
* this option list's widgets do not have an appearance stream. For example:
|
||||
* ```js
|
||||
* const optionList = form.getOptionList('some.optionList.field')
|
||||
* if (optionList.needsAppearancesUpdate()) console.log('Needs update')
|
||||
* ```
|
||||
* @returns Whether or not this option list needs an appearance update.
|
||||
*/
|
||||
needsAppearancesUpdate(): boolean {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the appearance streams for each of this option list's widgets using
|
||||
* the default appearance provider for option lists. For example:
|
||||
* ```js
|
||||
* const helvetica = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||
* const optionList = form.getOptionList('some.optionList.field')
|
||||
* optionList.defaultUpdateAppearances(helvetica)
|
||||
* ```
|
||||
* @param font The font to be used for creating the appearance streams.
|
||||
*/
|
||||
defaultUpdateAppearances(font: PDFFont) {
|
||||
assertIs(font, 'font', [[PDFFont, 'PDFFont']]);
|
||||
this.updateAppearances(font);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the appearance streams for each of this option list's widgets using
|
||||
* the given appearance provider. If no `provider` is passed, the default
|
||||
* appearance provider for option lists will be used. For example:
|
||||
* ```js
|
||||
* const helvetica = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||
* const optionList = form.getOptionList('some.optionList.field')
|
||||
* optionList.updateAppearances(helvetica, (field, widget, font) => {
|
||||
* ...
|
||||
* return drawOptionList(...)
|
||||
* })
|
||||
* ```
|
||||
* @param font The font to be used for creating the appearance streams.
|
||||
* @param provider Optionally, the appearance provider to be used for
|
||||
* generating the contents of the appearance streams.
|
||||
*/
|
||||
updateAppearances(
|
||||
font: PDFFont,
|
||||
provider?: AppearanceProviderFor<PDFOptionList>,
|
||||
) {
|
||||
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();
|
||||
}
|
||||
|
||||
// getOption(index: number): string {}
|
||||
// getSelectedIndices(): number[] {}
|
||||
// removeOptions(option: string | string[]) {}
|
||||
// removeIndices(option: number[]) {}
|
||||
// deselect(options: string | string[]) {}
|
||||
// deselectIndices(optionIndices: number[]) {}
|
||||
|
||||
private updateWidgetAppearance(
|
||||
widget: PDFWidgetAnnotation,
|
||||
font: PDFFont,
|
||||
provider?: AppearanceProviderFor<PDFOptionList>,
|
||||
) {
|
||||
const apProvider = provider ?? defaultOptionListAppearanceProvider;
|
||||
const appearances = normalizeAppearance(apProvider(this, widget, font));
|
||||
this.updateWidgetAppearanceWithFont(widget, font, appearances);
|
||||
}
|
||||
}
|
||||
471
node_modules/pdf-lib/src/api/form/PDFRadioGroup.ts
generated
vendored
Normal file
471
node_modules/pdf-lib/src/api/form/PDFRadioGroup.ts
generated
vendored
Normal file
@@ -0,0 +1,471 @@
|
||||
import PDFDocument from 'src/api/PDFDocument';
|
||||
import PDFPage from 'src/api/PDFPage';
|
||||
import PDFField, {
|
||||
FieldAppearanceOptions,
|
||||
assertFieldAppearanceOptions,
|
||||
} from 'src/api/form/PDFField';
|
||||
import {
|
||||
AppearanceProviderFor,
|
||||
normalizeAppearance,
|
||||
defaultRadioGroupAppearanceProvider,
|
||||
} from 'src/api/form/appearances';
|
||||
import { rgb } from 'src/api/colors';
|
||||
import { degrees } from 'src/api/rotations';
|
||||
|
||||
import {
|
||||
PDFName,
|
||||
PDFRef,
|
||||
PDFHexString,
|
||||
PDFDict,
|
||||
PDFWidgetAnnotation,
|
||||
PDFAcroRadioButton,
|
||||
AcroButtonFlags,
|
||||
} from 'src/core';
|
||||
import { assertIs, assertOrUndefined, assertIsOneOf } from 'src/utils';
|
||||
|
||||
/**
|
||||
* Represents a radio group field of a [[PDFForm]].
|
||||
*
|
||||
* [[PDFRadioGroup]] fields are collections of radio buttons. The purpose of a
|
||||
* radio group is to enable users to select one option from a set of mutually
|
||||
* exclusive choices. Each choice in a radio group is represented by a radio
|
||||
* button. Radio buttons each have two states: `on` and `off`. At most one
|
||||
* radio button in a group may be in the `on` state at any time. Users can
|
||||
* click on a radio button to select it (and thereby automatically deselect any
|
||||
* other radio button that might have already been selected). Some radio
|
||||
* groups allow users to toggle a selected radio button `off` by clicking on
|
||||
* it (see [[PDFRadioGroup.isOffToggleable]]).
|
||||
*
|
||||
* Note that some radio groups allow multiple radio buttons to be in the `on`
|
||||
* state at the same type **if** they represent the same underlying value (see
|
||||
* [[PDFRadioGroup.isMutuallyExclusive]]).
|
||||
*/
|
||||
export default class PDFRadioGroup extends PDFField {
|
||||
/**
|
||||
* > **NOTE:** You probably don't want to call this method directly. Instead,
|
||||
* > consider using the [[PDFForm.getOptionList]] method, which will create an
|
||||
* > instance of [[PDFOptionList]] for you.
|
||||
*
|
||||
* Create an instance of [[PDFOptionList]] from an existing acroRadioButton
|
||||
* and ref
|
||||
*
|
||||
* @param acroRadioButton The underlying `PDFAcroRadioButton` for this
|
||||
* radio group.
|
||||
* @param ref The unique reference for this radio group.
|
||||
* @param doc The document to which this radio group will belong.
|
||||
*/
|
||||
static of = (
|
||||
acroRadioButton: PDFAcroRadioButton,
|
||||
ref: PDFRef,
|
||||
doc: PDFDocument,
|
||||
) => new PDFRadioGroup(acroRadioButton, ref, doc);
|
||||
|
||||
/** The low-level PDFAcroRadioButton wrapped by this radio group. */
|
||||
readonly acroField: PDFAcroRadioButton;
|
||||
|
||||
private constructor(
|
||||
acroRadioButton: PDFAcroRadioButton,
|
||||
ref: PDFRef,
|
||||
doc: PDFDocument,
|
||||
) {
|
||||
super(acroRadioButton, ref, doc);
|
||||
|
||||
assertIs(acroRadioButton, 'acroRadioButton', [
|
||||
[PDFAcroRadioButton, 'PDFAcroRadioButton'],
|
||||
]);
|
||||
|
||||
this.acroField = acroRadioButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of available options for this radio group. Each option is
|
||||
* represented by a radio button. These radio buttons are displayed at
|
||||
* various locations in the document, potentially on different pages (though
|
||||
* typically they are stacked horizontally or vertically on the same page).
|
||||
* For example:
|
||||
* ```js
|
||||
* const radioGroup = form.getRadioGroup('some.radioGroup.field')
|
||||
* const options = radioGroup.getOptions()
|
||||
* console.log('Radio Group options:', options)
|
||||
* ```
|
||||
* @returns The options for this radio group.
|
||||
*/
|
||||
getOptions(): string[] {
|
||||
const exportValues = this.acroField.getExportValues();
|
||||
if (exportValues) {
|
||||
const exportOptions = new Array<string>(exportValues.length);
|
||||
for (let idx = 0, len = exportValues.length; idx < len; idx++) {
|
||||
exportOptions[idx] = exportValues[idx].decodeText();
|
||||
}
|
||||
return exportOptions;
|
||||
}
|
||||
|
||||
const onValues = this.acroField.getOnValues();
|
||||
const onOptions = new Array<string>(onValues.length);
|
||||
for (let idx = 0, len = onOptions.length; idx < len; idx++) {
|
||||
onOptions[idx] = onValues[idx].decodeText();
|
||||
}
|
||||
return onOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected option for this radio group. The selected option is
|
||||
* represented by the radio button in this group that is turned on. At most
|
||||
* one radio button in a group can be selected. If no buttons in this group
|
||||
* are selected, `undefined` is returned.
|
||||
* For example:
|
||||
* ```js
|
||||
* const radioGroup = form.getRadioGroup('some.radioGroup.field')
|
||||
* const selected = radioGroup.getSelected()
|
||||
* console.log('Selected radio button:', selected)
|
||||
* ```
|
||||
* @returns The selected option for this radio group.
|
||||
*/
|
||||
getSelected(): string | undefined {
|
||||
const value = this.acroField.getValue();
|
||||
if (value === PDFName.of('Off')) return undefined;
|
||||
const exportValues = this.acroField.getExportValues();
|
||||
if (exportValues) {
|
||||
const onValues = this.acroField.getOnValues();
|
||||
for (let idx = 0, len = onValues.length; idx < len; idx++) {
|
||||
if (onValues[idx] === value) return exportValues[idx].decodeText();
|
||||
}
|
||||
}
|
||||
return value.decodeText();
|
||||
}
|
||||
|
||||
// // TODO: Figure out why this seems to crash Acrobat. Maybe it's because we
|
||||
// // aren't removing the widget reference from the page's Annots?
|
||||
// removeOption(option: string) {
|
||||
// assertIs(option, 'option', ['string']);
|
||||
// // TODO: Assert is valid `option`!
|
||||
|
||||
// const onValues = this.acroField.getOnValues();
|
||||
// const exportValues = this.acroField.getExportValues();
|
||||
// if (exportValues) {
|
||||
// for (let idx = 0, len = exportValues.length; idx < len; idx++) {
|
||||
// if (exportValues[idx].decodeText() === option) {
|
||||
// this.acroField.removeWidget(idx);
|
||||
// this.acroField.removeExportValue(idx);
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// for (let idx = 0, len = onValues.length; idx < len; idx++) {
|
||||
// const value = onValues[idx];
|
||||
// if (value.decodeText() === option) {
|
||||
// this.acroField.removeWidget(idx);
|
||||
// this.acroField.removeExportValue(idx);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Select an option for this radio group. This operation is analogous to a
|
||||
* human user clicking one of the radio buttons in this group via a PDF
|
||||
* reader to toggle it on. This method will update the underlying state of
|
||||
* the radio group to indicate which option has been selected. PDF libraries
|
||||
* and readers will be able to extract this value from the saved document and
|
||||
* determine which option was selected.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* const radioGroup = form.getRadioGroup('best.superhero.radioGroup')
|
||||
* radioGroup.select('One Punch Man')
|
||||
* ```
|
||||
*
|
||||
* This method will mark this radio group as dirty, causing its appearance
|
||||
* streams to be updated when either [[PDFDocument.save]] or
|
||||
* [[PDFForm.updateFieldAppearances]] is called. The updated appearance
|
||||
* streams will display a dot inside the widget of this check box field
|
||||
* that represents the selected option.
|
||||
*
|
||||
* @param option The option to be selected.
|
||||
*/
|
||||
select(option: string) {
|
||||
assertIs(option, 'option', ['string']);
|
||||
|
||||
const validOptions = this.getOptions();
|
||||
assertIsOneOf(option, 'option', validOptions);
|
||||
|
||||
this.markAsDirty();
|
||||
|
||||
const onValues = this.acroField.getOnValues();
|
||||
const exportValues = this.acroField.getExportValues();
|
||||
if (exportValues) {
|
||||
for (let idx = 0, len = exportValues.length; idx < len; idx++) {
|
||||
if (exportValues[idx].decodeText() === option) {
|
||||
this.acroField.setValue(onValues[idx]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let idx = 0, len = onValues.length; idx < len; idx++) {
|
||||
const value = onValues[idx];
|
||||
if (value.decodeText() === option) this.acroField.setValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any selected option for this dropdown. This will result in all
|
||||
* radio buttons in this group being toggled off. This method will update
|
||||
* the underlying state of the dropdown to indicate that no radio buttons
|
||||
* have been selected.
|
||||
* For example:
|
||||
* ```js
|
||||
* const radioGroup = form.getRadioGroup('some.radioGroup.field')
|
||||
* radioGroup.clear()
|
||||
* ```
|
||||
* This method will mark this radio group as dirty. See
|
||||
* [[PDFRadioGroup.select]] for more details about what this means.
|
||||
*/
|
||||
clear() {
|
||||
this.markAsDirty();
|
||||
this.acroField.setValue(PDFName.of('Off'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if users can click on radio buttons in this group to toggle
|
||||
* them off. The alternative is that once a user clicks on a radio button
|
||||
* to select it, the only way to deselect it is by selecting on another radio
|
||||
* button in the group. See [[PDFRadioGroup.enableOffToggling]] and
|
||||
* [[PDFRadioGroup.disableOffToggling]]. For example:
|
||||
* ```js
|
||||
* const radioGroup = form.getRadioGroup('some.radioGroup.field')
|
||||
* if (radioGroup.isOffToggleable()) console.log('Off toggling is enabled')
|
||||
* ```
|
||||
*/
|
||||
isOffToggleable() {
|
||||
return !this.acroField.hasFlag(AcroButtonFlags.NoToggleToOff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow users to click on selected radio buttons in this group to toggle
|
||||
* them off. For example:
|
||||
* ```js
|
||||
* const radioGroup = form.getRadioGroup('some.radioGroup.field')
|
||||
* radioGroup.enableOffToggling()
|
||||
* ```
|
||||
* > **NOTE:** This feature is documented in the PDF specification
|
||||
* > (Table 226). However, most PDF readers do not respect this option and
|
||||
* > prevent users from toggling radio buttons off even when it is enabled.
|
||||
* > At the time of this writing (9/6/2020) Mac's Preview software did
|
||||
* > respect the option. Adobe Acrobat, Foxit Reader, and Google Chrome did
|
||||
* > not.
|
||||
*/
|
||||
enableOffToggling() {
|
||||
this.acroField.setFlagTo(AcroButtonFlags.NoToggleToOff, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent users from clicking on selected radio buttons in this group to
|
||||
* toggle them off. Clicking on a selected radio button will have no effect.
|
||||
* The only way to deselect a selected radio button is to click on a
|
||||
* different radio button in the group. For example:
|
||||
* ```js
|
||||
* const radioGroup = form.getRadioGroup('some.radioGroup.field')
|
||||
* radioGroup.disableOffToggling()
|
||||
* ```
|
||||
*/
|
||||
disableOffToggling() {
|
||||
this.acroField.setFlagTo(AcroButtonFlags.NoToggleToOff, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the radio buttons in this group are mutually exclusive.
|
||||
* This means that when the user selects a radio button, only that specific
|
||||
* button will be turned on. Even if other radio buttons in the group
|
||||
* represent the same value, they will not be enabled. The alternative to
|
||||
* this is that clicking a radio button will select that button along with
|
||||
* any other radio buttons in the group that share the same value. See
|
||||
* [[PDFRadioGroup.enableMutualExclusion]] and
|
||||
* [[PDFRadioGroup.disableMutualExclusion]].
|
||||
* For example:
|
||||
* ```js
|
||||
* const radioGroup = form.getRadioGroup('some.radioGroup.field')
|
||||
* if (radioGroup.isMutuallyExclusive()) console.log('Mutual exclusion is enabled')
|
||||
* ```
|
||||
*/
|
||||
isMutuallyExclusive() {
|
||||
return !this.acroField.hasFlag(AcroButtonFlags.RadiosInUnison);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the user clicks a radio button in this group it will be selected. In
|
||||
* addition, any other radio buttons in this group that share the same
|
||||
* underlying value will also be selected. For example:
|
||||
* ```js
|
||||
* const radioGroup = form.getRadioGroup('some.radioGroup.field')
|
||||
* radioGroup.enableMutualExclusion()
|
||||
* ```
|
||||
* Note that this option must be enabled prior to adding options to the
|
||||
* radio group. It does not currently apply retroactively to existing
|
||||
* radio buttons in the group.
|
||||
*/
|
||||
enableMutualExclusion() {
|
||||
this.acroField.setFlagTo(AcroButtonFlags.RadiosInUnison, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the user clicks a radio button in this group only it will be selected.
|
||||
* No other radio buttons in the group will be selected, even if they share
|
||||
* the same underlying value. For example:
|
||||
* ```js
|
||||
* const radioGroup = form.getRadioGroup('some.radioGroup.field')
|
||||
* radioGroup.disableMutualExclusion()
|
||||
* ```
|
||||
* Note that this option must be disabled prior to adding options to the
|
||||
* radio group. It does not currently apply retroactively to existing
|
||||
* radio buttons in the group.
|
||||
*/
|
||||
disableMutualExclusion() {
|
||||
this.acroField.setFlagTo(AcroButtonFlags.RadiosInUnison, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new radio button to this group on the specified page. For example:
|
||||
* ```js
|
||||
* const page = pdfDoc.addPage()
|
||||
*
|
||||
* const form = pdfDoc.getForm()
|
||||
* const radioGroup = form.createRadioGroup('best.gundam')
|
||||
*
|
||||
* const options = {
|
||||
* x: 50,
|
||||
* width: 25,
|
||||
* height: 25,
|
||||
* textColor: rgb(1, 0, 0),
|
||||
* backgroundColor: rgb(0, 1, 0),
|
||||
* borderColor: rgb(0, 0, 1),
|
||||
* borderWidth: 2,
|
||||
* rotate: degrees(90),
|
||||
* }
|
||||
*
|
||||
* radioGroup.addOptionToPage('Exia', page, { ...options, y: 50 })
|
||||
* radioGroup.addOptionToPage('Dynames', page, { ...options, y: 110 })
|
||||
* ```
|
||||
* This will create a new radio button widget for this radio group field.
|
||||
* @param option The option that the radio button widget represents.
|
||||
* @param page The page to which the radio button widget should be added.
|
||||
* @param options The options to be used when adding the radio button widget.
|
||||
*/
|
||||
addOptionToPage(
|
||||
option: string,
|
||||
page: PDFPage,
|
||||
options?: FieldAppearanceOptions,
|
||||
) {
|
||||
assertIs(option, 'option', ['string']);
|
||||
assertIs(page, 'page', [[PDFPage, 'PDFPage']]);
|
||||
assertFieldAppearanceOptions(options);
|
||||
|
||||
// Create a widget for this radio button
|
||||
const widget = this.createWidget({
|
||||
x: options?.x ?? 0,
|
||||
y: options?.y ?? 0,
|
||||
width: options?.width ?? 50,
|
||||
height: options?.height ?? 50,
|
||||
textColor: options?.textColor ?? rgb(0, 0, 0),
|
||||
backgroundColor: options?.backgroundColor ?? rgb(1, 1, 1),
|
||||
borderColor: options?.borderColor ?? rgb(0, 0, 0),
|
||||
borderWidth: options?.borderWidth ?? 1,
|
||||
rotate: options?.rotate ?? degrees(0),
|
||||
hidden: options?.hidden,
|
||||
page: page.ref,
|
||||
});
|
||||
const widgetRef = this.doc.context.register(widget.dict);
|
||||
|
||||
// Add widget to this field
|
||||
const apStateValue = this.acroField.addWidgetWithOpt(
|
||||
widgetRef,
|
||||
PDFHexString.fromText(option),
|
||||
!this.isMutuallyExclusive(),
|
||||
);
|
||||
|
||||
// Set appearance streams for widget
|
||||
widget.setAppearanceState(PDFName.of('Off'));
|
||||
this.updateWidgetAppearance(widget, apStateValue);
|
||||
|
||||
// Add widget to the given page
|
||||
page.node.addAnnot(widgetRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if any of this group's radio button widgets do not have an
|
||||
* appearance stream for their current state. For example:
|
||||
* ```js
|
||||
* const radioGroup = form.getRadioGroup('some.radioGroup.field')
|
||||
* if (radioGroup.needsAppearancesUpdate()) console.log('Needs update')
|
||||
* ```
|
||||
* @returns Whether or not this radio group needs an appearance update.
|
||||
*/
|
||||
needsAppearancesUpdate(): boolean {
|
||||
const widgets = this.acroField.getWidgets();
|
||||
for (let idx = 0, len = widgets.length; idx < len; idx++) {
|
||||
const widget = widgets[idx];
|
||||
const state = widget.getAppearanceState();
|
||||
const normal = widget.getAppearances()?.normal;
|
||||
|
||||
if (!(normal instanceof PDFDict)) return true;
|
||||
if (state && !normal.has(state)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the appearance streams for each of this group's radio button widgets
|
||||
* using the default appearance provider for radio groups. For example:
|
||||
* ```js
|
||||
* const radioGroup = form.getRadioGroup('some.radioGroup.field')
|
||||
* radioGroup.defaultUpdateAppearances()
|
||||
* ```
|
||||
*/
|
||||
defaultUpdateAppearances() {
|
||||
this.updateAppearances();
|
||||
}
|
||||
|
||||
// rg.updateAppearances((field: any, widget: any) => {
|
||||
// assert(field === rg);
|
||||
// assert(widget instanceof PDFWidgetAnnotation);
|
||||
// return { on: [...rectangle, ...circle], off: [...rectangle, ...circle] };
|
||||
// });
|
||||
|
||||
/**
|
||||
* Update the appearance streams for each of this group's radio button widgets
|
||||
* using the given appearance provider. If no `provider` is passed, the
|
||||
* default appearance provider for radio groups will be used. For example:
|
||||
* ```js
|
||||
* const radioGroup = form.getRadioGroup('some.radioGroup.field')
|
||||
* radioGroup.updateAppearances((field, widget) => {
|
||||
* ...
|
||||
* return {
|
||||
* normal: { on: drawRadioButton(...), off: drawRadioButton(...) },
|
||||
* down: { on: drawRadioButton(...), off: drawRadioButton(...) },
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
* @param provider Optionally, the appearance provider to be used for
|
||||
* generating the contents of the appearance streams.
|
||||
*/
|
||||
updateAppearances(provider?: AppearanceProviderFor<PDFRadioGroup>) {
|
||||
assertOrUndefined(provider, 'provider', [Function]);
|
||||
|
||||
const widgets = this.acroField.getWidgets();
|
||||
for (let idx = 0, len = widgets.length; idx < len; idx++) {
|
||||
const widget = widgets[idx];
|
||||
const onValue = widget.getOnValue();
|
||||
if (!onValue) continue;
|
||||
this.updateWidgetAppearance(widget, onValue, provider);
|
||||
}
|
||||
}
|
||||
|
||||
private updateWidgetAppearance(
|
||||
widget: PDFWidgetAnnotation,
|
||||
onValue: PDFName,
|
||||
provider?: AppearanceProviderFor<PDFRadioGroup>,
|
||||
) {
|
||||
const apProvider = provider ?? defaultRadioGroupAppearanceProvider;
|
||||
const appearances = normalizeAppearance(apProvider(this, widget));
|
||||
this.updateOnOffWidgetAppearance(widget, onValue, appearances);
|
||||
}
|
||||
}
|
||||
53
node_modules/pdf-lib/src/api/form/PDFSignature.ts
generated
vendored
Normal file
53
node_modules/pdf-lib/src/api/form/PDFSignature.ts
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
import PDFDocument from 'src/api/PDFDocument';
|
||||
import PDFField from 'src/api/form/PDFField';
|
||||
|
||||
import { PDFRef, PDFAcroSignature } from 'src/core';
|
||||
import { assertIs } from 'src/utils';
|
||||
|
||||
/**
|
||||
* Represents a signature field of a [[PDFForm]].
|
||||
*
|
||||
* [[PDFSignature]] fields are digital signatures. `pdf-lib` does not
|
||||
* currently provide any specialized APIs for creating digital signatures or
|
||||
* reading the contents of existing digital signatures.
|
||||
*/
|
||||
export default class PDFSignature extends PDFField {
|
||||
/**
|
||||
* > **NOTE:** You probably don't want to call this method directly. Instead,
|
||||
* > consider using the [[PDFForm.getSignature]] method, which will create an
|
||||
* > instance of [[PDFSignature]] for you.
|
||||
*
|
||||
* Create an instance of [[PDFSignature]] from an existing acroSignature and
|
||||
* ref
|
||||
*
|
||||
* @param acroSignature The underlying `PDFAcroSignature` for this signature.
|
||||
* @param ref The unique reference for this signature.
|
||||
* @param doc The document to which this signature will belong.
|
||||
*/
|
||||
static of = (
|
||||
acroSignature: PDFAcroSignature,
|
||||
ref: PDFRef,
|
||||
doc: PDFDocument,
|
||||
) => new PDFSignature(acroSignature, ref, doc);
|
||||
|
||||
/** The low-level PDFAcroSignature wrapped by this signature. */
|
||||
readonly acroField: PDFAcroSignature;
|
||||
|
||||
private constructor(
|
||||
acroSignature: PDFAcroSignature,
|
||||
ref: PDFRef,
|
||||
doc: PDFDocument,
|
||||
) {
|
||||
super(acroSignature, ref, doc);
|
||||
|
||||
assertIs(acroSignature, 'acroSignature', [
|
||||
[PDFAcroSignature, 'PDFAcroSignature'],
|
||||
]);
|
||||
|
||||
this.acroField = acroSignature;
|
||||
}
|
||||
|
||||
needsAppearancesUpdate() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
826
node_modules/pdf-lib/src/api/form/PDFTextField.ts
generated
vendored
Normal file
826
node_modules/pdf-lib/src/api/form/PDFTextField.ts
generated
vendored
Normal file
@@ -0,0 +1,826 @@
|
||||
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 {
|
||||
/**
|
||||
* > **NOTE:** You probably don't want to call this method directly. Instead,
|
||||
* > consider using the [[PDFForm.getTextField]] method, which will create an
|
||||
* > instance of [[PDFTextField]] for you.
|
||||
*
|
||||
* Create an instance of [[PDFTextField]] from an existing acroText and ref
|
||||
*
|
||||
* @param acroText The underlying `PDFAcroText` for this text field.
|
||||
* @param ref The unique reference for this text field.
|
||||
* @param doc The document to which this text field will belong.
|
||||
*/
|
||||
static of = (acroText: PDFAcroText, ref: PDFRef, doc: PDFDocument) =>
|
||||
new PDFTextField(acroText, ref, doc);
|
||||
|
||||
/** The low-level PDFAcroText wrapped by this text field. */
|
||||
readonly acroField: PDFAcroText;
|
||||
|
||||
private constructor(acroText: PDFAcroText, ref: PDFRef, doc: PDFDocument) {
|
||||
super(acroText, ref, doc);
|
||||
|
||||
assertIs(acroText, 'acroText', [[PDFAcroText, 'PDFAcroText']]);
|
||||
|
||||
this.acroField = acroText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text that this field contains. This text is visible to users who
|
||||
* view this field in a PDF reader.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* const textField = form.getTextField('some.text.field')
|
||||
* const text = textField.getText()
|
||||
* console.log('Text field contents:', text)
|
||||
* ```
|
||||
*
|
||||
* Note that if this text field contains no underlying value, `undefined`
|
||||
* will be returned. Text fields may also contain an underlying value that
|
||||
* is simply an empty string (`''`). This detail is largely irrelevant for
|
||||
* most applications. In general, you'll want to treat both cases the same
|
||||
* way and simply consider the text field to be empty. In either case, the
|
||||
* text field will appear empty to users when viewed in a PDF reader.
|
||||
*
|
||||
* An error will be thrown if this is a rich text field. `pdf-lib` does not
|
||||
* support reading 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/
|
||||
*
|
||||
* @returns The text contained in this text field.
|
||||
*/
|
||||
getText(): string | undefined {
|
||||
const value = this.acroField.getValue();
|
||||
if (!value && this.isRichFormatted()) {
|
||||
throw new RichTextFieldReadError(this.getName());
|
||||
}
|
||||
return value?.decodeText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text for this field. This operation is analogous to a human user
|
||||
* clicking on the text field in a PDF reader and typing in text via their
|
||||
* keyboard. This method will update the underlying state of the text field
|
||||
* to indicate what text has been set. PDF libraries and readers will be able
|
||||
* to extract these values from the saved document and determine what text
|
||||
* was set.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* const textField = form.getTextField('best.superhero.text.field')
|
||||
* textField.setText('One Punch Man')
|
||||
* ```
|
||||
*
|
||||
* This method will mark this text field as dirty, causing its appearance
|
||||
* streams to be updated when either [[PDFDocument.save]] or
|
||||
* [[PDFForm.updateFieldAppearances]] is called. The updated streams will
|
||||
* display the text this field contains inside the widgets of this text
|
||||
* field.
|
||||
*
|
||||
* **IMPORTANT:** The default font used to update appearance streams is
|
||||
* [[StandardFonts.Helvetica]]. Note that this is a WinAnsi font. This means
|
||||
* that encoding errors will be thrown if this field contains text outside
|
||||
* the WinAnsi character set (the latin alphabet).
|
||||
*
|
||||
* Embedding a custom font and passing it to
|
||||
* [[PDFForm.updateFieldAppearances]] or [[PDFTextField.updateAppearances]]
|
||||
* allows you to generate appearance streams with characters outside the
|
||||
* latin alphabet (assuming the custom font supports them).
|
||||
*
|
||||
* If this is a rich text field, it will be converted to a standard text
|
||||
* field in order to set the text. `pdf-lib` does not support writing rich
|
||||
* text strings. Nor do most PDF readers and writers. See
|
||||
* [[PDFTextField.getText]] for more information about rich text fields and
|
||||
* their deprecation in PDF 2.0.
|
||||
*
|
||||
* @param text The text this field should contain.
|
||||
*/
|
||||
setText(text: string | undefined) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the alignment for this text field. This value represents the
|
||||
* justification of the text when it is displayed to the user in PDF readers.
|
||||
* There are three possible alignments: left, center, and right. For example:
|
||||
* ```js
|
||||
* const textField = form.getTextField('some.text.field')
|
||||
* const alignment = textField.getAlignment()
|
||||
* if (alignment === TextAlignment.Left) console.log('Text is left justified')
|
||||
* if (alignment === TextAlignment.Center) console.log('Text is centered')
|
||||
* if (alignment === TextAlignment.Right) console.log('Text is right justified')
|
||||
* ```
|
||||
* @returns The alignment of this text field.
|
||||
*/
|
||||
getAlignment(): TextAlignment {
|
||||
const quadding = this.acroField.getQuadding();
|
||||
|
||||
// prettier-ignore
|
||||
return (
|
||||
quadding === 0 ? TextAlignment.Left
|
||||
: quadding === 1 ? TextAlignment.Center
|
||||
: quadding === 2 ? TextAlignment.Right
|
||||
: TextAlignment.Left
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the alignment for this text field. This will determine the
|
||||
* justification of the text when it is displayed to the user in PDF readers.
|
||||
* There are three possible alignments: left, center, and right. For example:
|
||||
* ```js
|
||||
* const textField = form.getTextField('some.text.field')
|
||||
*
|
||||
* // Text will be left justified when displayed
|
||||
* textField.setAlignment(TextAlignment.Left)
|
||||
*
|
||||
* // Text will be centered when displayed
|
||||
* textField.setAlignment(TextAlignment.Center)
|
||||
*
|
||||
* // Text will be right justified when displayed
|
||||
* textField.setAlignment(TextAlignment.Right)
|
||||
* ```
|
||||
* This method will mark this text field as dirty. See
|
||||
* [[PDFTextField.setText]] for more details about what this means.
|
||||
* @param alignment The alignment for this text field.
|
||||
*/
|
||||
setAlignment(alignment: TextAlignment) {
|
||||
assertIsOneOf(alignment, 'alignment', TextAlignment);
|
||||
this.markAsDirty();
|
||||
this.acroField.setQuadding(alignment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum length of this field. This value represents the maximum
|
||||
* number of characters that can be typed into this field by the user. If
|
||||
* this field does not have a maximum length, `undefined` is returned.
|
||||
* For example:
|
||||
* ```js
|
||||
* const textField = form.getTextField('some.text.field')
|
||||
* const maxLength = textField.getMaxLength()
|
||||
* if (maxLength === undefined) console.log('No max length')
|
||||
* else console.log(`Max length is ${maxLength}`)
|
||||
* ```
|
||||
* @returns The maximum number of characters allowed in this field, or
|
||||
* `undefined` if no limit exists.
|
||||
*/
|
||||
getMaxLength(): number | undefined {
|
||||
return this.acroField.getMaxLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum length of this field. This limits the number of characters
|
||||
* that can be typed into this field by the user. This also limits the length
|
||||
* of the string that can be passed to [[PDFTextField.setText]]. This limit
|
||||
* can be removed by passing `undefined` as `maxLength`. For example:
|
||||
* ```js
|
||||
* const textField = form.getTextField('some.text.field')
|
||||
*
|
||||
* // Allow between 0 and 5 characters to be entered
|
||||
* textField.setMaxLength(5)
|
||||
*
|
||||
* // Allow any number of characters to be entered
|
||||
* textField.setMaxLength(undefined)
|
||||
* ```
|
||||
* This method will mark this text field as dirty. See
|
||||
* [[PDFTextField.setText]] for more details about what this means.
|
||||
* @param maxLength The maximum number of characters allowed in this field, or
|
||||
* `undefined` to remove the limit.
|
||||
*/
|
||||
setMaxLength(maxLength?: number) {
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an image inside the bounds of this text field's widgets. For example:
|
||||
* ```js
|
||||
* const pngImage = await pdfDoc.embedPng(...)
|
||||
* const textField = form.getTextField('some.text.field')
|
||||
* textField.setImage(pngImage)
|
||||
* ```
|
||||
* This will update the appearances streams for each of this text field's widgets.
|
||||
* @param image The image that should be displayed.
|
||||
*/
|
||||
setImage(image: PDFImage) {
|
||||
const fieldAlignment = this.getAlignment();
|
||||
|
||||
// prettier-ignore
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the font size for this field. Larger font sizes will result in larger
|
||||
* text being displayed when PDF readers render this text field. Font sizes
|
||||
* may be integer or floating point numbers. Supplying a negative font size
|
||||
* will cause this method to throw an error.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* const textField = form.getTextField('some.text.field')
|
||||
* textField.setFontSize(4)
|
||||
* textField.setFontSize(15.7)
|
||||
* ```
|
||||
*
|
||||
* > This method depends upon the existence of a default appearance
|
||||
* > (`/DA`) string. If this field does not have a default appearance string,
|
||||
* > or that string does not contain a font size (via the `Tf` operator),
|
||||
* > then this method will throw an error.
|
||||
*
|
||||
* @param fontSize The font size to be used when rendering text in this field.
|
||||
*/
|
||||
setFontSize(fontSize: number) {
|
||||
assertPositive(fontSize, 'fontSize');
|
||||
this.acroField.setFontSize(fontSize);
|
||||
this.markAsDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if each line of text is shown on a new line when this
|
||||
* field is displayed in a PDF reader. The alternative is that all lines of
|
||||
* text are merged onto a single line when displayed. See
|
||||
* [[PDFTextField.enableMultiline]] and [[PDFTextField.disableMultiline]].
|
||||
* For example:
|
||||
* ```js
|
||||
* const textField = form.getTextField('some.text.field')
|
||||
* if (textField.isMultiline()) console.log('Multiline is enabled')
|
||||
* ```
|
||||
* @returns Whether or not this is a multiline text field.
|
||||
*/
|
||||
isMultiline(): boolean {
|
||||
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 `true` if this is a password text field. This means that the field
|
||||
* is intended for storing a secure password. See
|
||||
* [[PDFTextField.enablePassword]] and [[PDFTextField.disablePassword]].
|
||||
* For example:
|
||||
* ```js
|
||||
* const textField = form.getTextField('some.text.field')
|
||||
* if (textField.isPassword()) console.log('Password is enabled')
|
||||
* ```
|
||||
* @returns Whether or not this is a password text field.
|
||||
*/
|
||||
isPassword(): boolean {
|
||||
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 `true` if the contents of this text field represent a file path.
|
||||
* See [[PDFTextField.enableFileSelection]] and
|
||||
* [[PDFTextField.disableFileSelection]]. For example:
|
||||
* ```js
|
||||
* const textField = form.getTextField('some.text.field')
|
||||
* if (textField.isFileSelector()) console.log('Is a file selector')
|
||||
* ```
|
||||
* @returns Whether or not this field should contain file paths.
|
||||
*/
|
||||
isFileSelector(): boolean {
|
||||
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 `true` if the text entered in this field should be spell checked
|
||||
* by PDF readers. See [[PDFTextField.enableSpellChecking]] and
|
||||
* [[PDFTextField.disableSpellChecking]]. For example:
|
||||
* ```js
|
||||
* const textField = form.getTextField('some.text.field')
|
||||
* if (textField.isSpellChecked()) console.log('Spell checking is enabled')
|
||||
* ```
|
||||
* @returns Whether or not this field should be spell checked.
|
||||
*/
|
||||
isSpellChecked(): boolean {
|
||||
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 `true` if PDF readers should allow the user to scroll the text
|
||||
* field when its contents do not fit within the field's view bounds. See
|
||||
* [[PDFTextField.enableScrolling]] and [[PDFTextField.disableScrolling]].
|
||||
* For example:
|
||||
* ```js
|
||||
* const textField = form.getTextField('some.text.field')
|
||||
* if (textField.isScrollable()) console.log('Scrolling is enabled')
|
||||
* ```
|
||||
* @returns Whether or not the field is scrollable in PDF readers.
|
||||
*/
|
||||
isScrollable(): boolean {
|
||||
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 `true` if this is a combed text field. This means that the field
|
||||
* is split into `n` equal size cells with one character in each (where `n`
|
||||
* is equal to the max length of the text field). The result is that all
|
||||
* characters in this field are displayed an equal distance apart from one
|
||||
* another. See [[PDFTextField.enableCombing]] and
|
||||
* [[PDFTextField.disableCombing]]. For example:
|
||||
* ```js
|
||||
* const textField = form.getTextField('some.text.field')
|
||||
* if (textField.isCombed()) console.log('Combing is enabled')
|
||||
* ```
|
||||
* Note that in order for a text field to be combed, the following must be
|
||||
* true (in addition to enabling combing):
|
||||
* * It must not be a multiline field (see [[PDFTextField.isMultiline]])
|
||||
* * It must not be a password field (see [[PDFTextField.isPassword]])
|
||||
* * It must not be a file selector field (see [[PDFTextField.isFileSelector]])
|
||||
* * It must have a max length defined (see [[PDFTextField.setMaxLength]])
|
||||
* @returns Whether or not this field is combed.
|
||||
*/
|
||||
isCombed(): boolean {
|
||||
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 `true` if this text field contains rich text. See
|
||||
* [[PDFTextField.enableRichFormatting]] and
|
||||
* [[PDFTextField.disableRichFormatting]]. For example:
|
||||
* ```js
|
||||
* const textField = form.getTextField('some.text.field')
|
||||
* if (textField.isRichFormatted()) console.log('Rich formatting enabled')
|
||||
* ```
|
||||
* @returns Whether or not this field contains rich text.
|
||||
*/
|
||||
isRichFormatted(): boolean {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show this text field on the specified page. For example:
|
||||
* ```js
|
||||
* const ubuntuFont = await pdfDoc.embedFont(ubuntuFontBytes)
|
||||
* const page = pdfDoc.addPage()
|
||||
*
|
||||
* const form = pdfDoc.getForm()
|
||||
* const textField = form.createTextField('best.gundam')
|
||||
* textField.setText('Exia')
|
||||
*
|
||||
* textField.addToPage(page, {
|
||||
* x: 50,
|
||||
* y: 75,
|
||||
* width: 200,
|
||||
* height: 100,
|
||||
* textColor: rgb(1, 0, 0),
|
||||
* backgroundColor: rgb(0, 1, 0),
|
||||
* borderColor: rgb(0, 0, 1),
|
||||
* borderWidth: 2,
|
||||
* rotate: degrees(90),
|
||||
* font: ubuntuFont,
|
||||
* })
|
||||
* ```
|
||||
* This will create a new widget for this text field.
|
||||
* @param page The page to which this text field widget should be added.
|
||||
* @param options The options to be used when adding this text field widget.
|
||||
*/
|
||||
addToPage(page: PDFPage, options?: FieldAppearanceOptions) {
|
||||
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;
|
||||
|
||||
// Create a widget for this text field
|
||||
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);
|
||||
|
||||
// Add widget to this field
|
||||
this.acroField.addWidget(widgetRef);
|
||||
|
||||
// Set appearance streams for widget
|
||||
const font = options.font ?? this.doc.getForm().getDefaultFont();
|
||||
this.updateWidgetAppearance(widget, font);
|
||||
|
||||
// Add widget to the given page
|
||||
page.node.addAnnot(widgetRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if this text field has been marked as dirty, or if any of
|
||||
* this text field's widgets do not have an appearance stream. For example:
|
||||
* ```js
|
||||
* const textField = form.getTextField('some.text.field')
|
||||
* if (textField.needsAppearancesUpdate()) console.log('Needs update')
|
||||
* ```
|
||||
* @returns Whether or not this text field needs an appearance update.
|
||||
*/
|
||||
needsAppearancesUpdate(): boolean {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the appearance streams for each of this text field's widgets using
|
||||
* the default appearance provider for text fields. For example:
|
||||
* ```js
|
||||
* const helvetica = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||
* const textField = form.getTextField('some.text.field')
|
||||
* textField.defaultUpdateAppearances(helvetica)
|
||||
* ```
|
||||
* @param font The font to be used for creating the appearance streams.
|
||||
*/
|
||||
defaultUpdateAppearances(font: PDFFont) {
|
||||
assertIs(font, 'font', [[PDFFont, 'PDFFont']]);
|
||||
this.updateAppearances(font);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the appearance streams for each of this text field's widgets using
|
||||
* the given appearance provider. If no `provider` is passed, the default
|
||||
* appearance provider for text fields will be used. For example:
|
||||
* ```js
|
||||
* const helvetica = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||
* const textField = form.getTextField('some.text.field')
|
||||
* textField.updateAppearances(helvetica, (field, widget, font) => {
|
||||
* ...
|
||||
* return drawTextField(...)
|
||||
* })
|
||||
* ```
|
||||
* @param font The font to be used for creating the appearance streams.
|
||||
* @param provider Optionally, the appearance provider to be used for
|
||||
* generating the contents of the appearance streams.
|
||||
*/
|
||||
updateAppearances(
|
||||
font: PDFFont,
|
||||
provider?: AppearanceProviderFor<PDFTextField>,
|
||||
) {
|
||||
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: PDFWidgetAnnotation,
|
||||
font: PDFFont,
|
||||
provider?: AppearanceProviderFor<PDFTextField>,
|
||||
) {
|
||||
const apProvider = provider ?? defaultTextFieldAppearanceProvider;
|
||||
const appearances = normalizeAppearance(apProvider(this, widget, font));
|
||||
this.updateWidgetAppearanceWithFont(widget, font, appearances);
|
||||
}
|
||||
}
|
||||
665
node_modules/pdf-lib/src/api/form/appearances.ts
generated
vendored
Normal file
665
node_modules/pdf-lib/src/api/form/appearances.ts
generated
vendored
Normal file
@@ -0,0 +1,665 @@
|
||||
import { PDFOperator, PDFWidgetAnnotation } from 'src/core';
|
||||
import PDFFont from 'src/api/PDFFont';
|
||||
import PDFButton from 'src/api/form/PDFButton';
|
||||
import PDFCheckBox from 'src/api/form/PDFCheckBox';
|
||||
import PDFDropdown from 'src/api/form/PDFDropdown';
|
||||
import PDFField from 'src/api/form/PDFField';
|
||||
import PDFOptionList from 'src/api/form/PDFOptionList';
|
||||
import PDFRadioGroup from 'src/api/form/PDFRadioGroup';
|
||||
import PDFSignature from 'src/api/form/PDFSignature';
|
||||
import PDFTextField from 'src/api/form/PDFTextField';
|
||||
import {
|
||||
drawCheckBox,
|
||||
rotateInPlace,
|
||||
drawRadioButton,
|
||||
drawButton,
|
||||
drawTextField,
|
||||
drawOptionList,
|
||||
} from 'src/api/operations';
|
||||
import {
|
||||
rgb,
|
||||
componentsToColor,
|
||||
setFillingColor,
|
||||
grayscale,
|
||||
cmyk,
|
||||
Color,
|
||||
} from 'src/api/colors';
|
||||
import { reduceRotation, adjustDimsForRotation } from 'src/api/rotations';
|
||||
import {
|
||||
layoutMultilineText,
|
||||
layoutCombedText,
|
||||
TextPosition,
|
||||
layoutSinglelineText,
|
||||
} from 'src/api/text/layout';
|
||||
import { TextAlignment } from 'src/api/text/alignment';
|
||||
import { setFontAndSize } from 'src/api/operators';
|
||||
import { findLastMatch } from 'src/utils';
|
||||
|
||||
/*********************** Appearance Provider Types ****************************/
|
||||
|
||||
type CheckBoxAppearanceProvider = (
|
||||
checkBox: PDFCheckBox,
|
||||
widget: PDFWidgetAnnotation,
|
||||
) => AppearanceOrMapping<{
|
||||
on: PDFOperator[];
|
||||
off: PDFOperator[];
|
||||
}>;
|
||||
|
||||
type RadioGroupAppearanceProvider = (
|
||||
radioGroup: PDFRadioGroup,
|
||||
widget: PDFWidgetAnnotation,
|
||||
) => AppearanceOrMapping<{
|
||||
on: PDFOperator[];
|
||||
off: PDFOperator[];
|
||||
}>;
|
||||
|
||||
type ButtonAppearanceProvider = (
|
||||
button: PDFButton,
|
||||
widget: PDFWidgetAnnotation,
|
||||
font: PDFFont,
|
||||
) => AppearanceOrMapping<PDFOperator[]>;
|
||||
|
||||
type DropdownAppearanceProvider = (
|
||||
dropdown: PDFDropdown,
|
||||
widget: PDFWidgetAnnotation,
|
||||
font: PDFFont,
|
||||
) => AppearanceOrMapping<PDFOperator[]>;
|
||||
|
||||
type OptionListAppearanceProvider = (
|
||||
optionList: PDFOptionList,
|
||||
widget: PDFWidgetAnnotation,
|
||||
font: PDFFont,
|
||||
) => AppearanceOrMapping<PDFOperator[]>;
|
||||
|
||||
type TextFieldAppearanceProvider = (
|
||||
textField: PDFTextField,
|
||||
widget: PDFWidgetAnnotation,
|
||||
font: PDFFont,
|
||||
) => AppearanceOrMapping<PDFOperator[]>;
|
||||
|
||||
type SignatureAppearanceProvider = (
|
||||
signature: PDFSignature,
|
||||
widget: PDFWidgetAnnotation,
|
||||
font: PDFFont,
|
||||
) => AppearanceOrMapping<PDFOperator[]>;
|
||||
|
||||
/******************* Appearance Provider Utility Types ************************/
|
||||
|
||||
export type AppearanceMapping<T> = { normal: T; rollover?: T; down?: T };
|
||||
|
||||
type AppearanceOrMapping<T> = T | AppearanceMapping<T>;
|
||||
|
||||
// prettier-ignore
|
||||
export type AppearanceProviderFor<T extends PDFField> =
|
||||
T extends PDFCheckBox ? CheckBoxAppearanceProvider
|
||||
: T extends PDFRadioGroup ? RadioGroupAppearanceProvider
|
||||
: T extends PDFButton ? ButtonAppearanceProvider
|
||||
: T extends PDFDropdown ? DropdownAppearanceProvider
|
||||
: T extends PDFOptionList ? OptionListAppearanceProvider
|
||||
: T extends PDFTextField ? TextFieldAppearanceProvider
|
||||
: T extends PDFSignature ? SignatureAppearanceProvider
|
||||
: never;
|
||||
|
||||
/********************* Appearance Provider Functions **************************/
|
||||
|
||||
export const normalizeAppearance = <T>(
|
||||
appearance: T | AppearanceMapping<T>,
|
||||
): AppearanceMapping<T> => {
|
||||
if ('normal' in appearance) return appearance;
|
||||
return { normal: appearance };
|
||||
};
|
||||
|
||||
// Examples:
|
||||
// `/Helv 12 Tf` -> ['/Helv 12 Tf', 'Helv', '12']
|
||||
// `/HeBo 8.00 Tf` -> ['/HeBo 8 Tf', 'HeBo', '8.00']
|
||||
const tfRegex = /\/([^\0\t\n\f\r\ ]+)[\0\t\n\f\r\ ]+(\d*\.\d+|\d+)[\0\t\n\f\r\ ]+Tf/;
|
||||
|
||||
const getDefaultFontSize = (field: {
|
||||
getDefaultAppearance(): string | undefined;
|
||||
}) => {
|
||||
const da = field.getDefaultAppearance() ?? '';
|
||||
const daMatch = findLastMatch(da, tfRegex).match ?? [];
|
||||
const defaultFontSize = Number(daMatch[2]);
|
||||
return isFinite(defaultFontSize) ? defaultFontSize : undefined;
|
||||
};
|
||||
|
||||
// Examples:
|
||||
// `0.3 g` -> ['0.3', 'g']
|
||||
// `0.3 1 .3 rg` -> ['0.3', '1', '.3', 'rg']
|
||||
// `0.3 1 .3 0 k` -> ['0.3', '1', '.3', '0', 'k']
|
||||
const colorRegex = /(\d*\.\d+|\d+)[\0\t\n\f\r\ ]*(\d*\.\d+|\d+)?[\0\t\n\f\r\ ]*(\d*\.\d+|\d+)?[\0\t\n\f\r\ ]*(\d*\.\d+|\d+)?[\0\t\n\f\r\ ]+(g|rg|k)/;
|
||||
|
||||
const getDefaultColor = (field: {
|
||||
getDefaultAppearance(): string | undefined;
|
||||
}) => {
|
||||
const da = field.getDefaultAppearance() ?? '';
|
||||
const daMatch = findLastMatch(da, colorRegex).match;
|
||||
|
||||
const [, c1, c2, c3, c4, colorSpace] = daMatch ?? [];
|
||||
|
||||
if (colorSpace === 'g' && c1) {
|
||||
return grayscale(Number(c1));
|
||||
}
|
||||
if (colorSpace === 'rg' && c1 && c2 && c3) {
|
||||
return rgb(Number(c1), Number(c2), Number(c3));
|
||||
}
|
||||
if (colorSpace === 'k' && c1 && c2 && c3 && c4) {
|
||||
return cmyk(Number(c1), Number(c2), Number(c3), Number(c4));
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const updateDefaultAppearance = (
|
||||
field: { setDefaultAppearance(appearance: string): void },
|
||||
color: Color,
|
||||
font?: PDFFont,
|
||||
fontSize: number = 0,
|
||||
) => {
|
||||
const da = [
|
||||
setFillingColor(color).toString(),
|
||||
setFontAndSize(font?.name ?? 'dummy__noop', fontSize).toString(),
|
||||
].join('\n');
|
||||
field.setDefaultAppearance(da);
|
||||
};
|
||||
|
||||
export const defaultCheckBoxAppearanceProvider: AppearanceProviderFor<PDFCheckBox> = (
|
||||
checkBox,
|
||||
widget,
|
||||
) => {
|
||||
// The `/DA` entry can be at the widget or field level - so we handle both
|
||||
const widgetColor = getDefaultColor(widget);
|
||||
const fieldColor = getDefaultColor(checkBox.acroField);
|
||||
|
||||
const rectangle = widget.getRectangle();
|
||||
const ap = widget.getAppearanceCharacteristics();
|
||||
const bs = widget.getBorderStyle();
|
||||
|
||||
const borderWidth = bs?.getWidth() ?? 0;
|
||||
const rotation = reduceRotation(ap?.getRotation());
|
||||
const { width, height } = adjustDimsForRotation(rectangle, rotation);
|
||||
|
||||
const rotate = rotateInPlace({ ...rectangle, rotation });
|
||||
|
||||
const black = rgb(0, 0, 0);
|
||||
const borderColor = componentsToColor(ap?.getBorderColor()) ?? black;
|
||||
const normalBackgroundColor = componentsToColor(ap?.getBackgroundColor());
|
||||
const downBackgroundColor = componentsToColor(ap?.getBackgroundColor(), 0.8);
|
||||
|
||||
// Update color
|
||||
const textColor = widgetColor ?? fieldColor ?? black;
|
||||
if (widgetColor) {
|
||||
updateDefaultAppearance(widget, textColor);
|
||||
} else {
|
||||
updateDefaultAppearance(checkBox.acroField, textColor);
|
||||
}
|
||||
|
||||
const options = {
|
||||
x: 0 + borderWidth / 2,
|
||||
y: 0 + borderWidth / 2,
|
||||
width: width - borderWidth,
|
||||
height: height - borderWidth,
|
||||
thickness: 1.5,
|
||||
borderWidth,
|
||||
borderColor,
|
||||
markColor: textColor,
|
||||
};
|
||||
|
||||
return {
|
||||
normal: {
|
||||
on: [
|
||||
...rotate,
|
||||
...drawCheckBox({
|
||||
...options,
|
||||
color: normalBackgroundColor,
|
||||
filled: true,
|
||||
}),
|
||||
],
|
||||
off: [
|
||||
...rotate,
|
||||
...drawCheckBox({
|
||||
...options,
|
||||
color: normalBackgroundColor,
|
||||
filled: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
down: {
|
||||
on: [
|
||||
...rotate,
|
||||
...drawCheckBox({
|
||||
...options,
|
||||
color: downBackgroundColor,
|
||||
filled: true,
|
||||
}),
|
||||
],
|
||||
off: [
|
||||
...rotate,
|
||||
...drawCheckBox({
|
||||
...options,
|
||||
color: downBackgroundColor,
|
||||
filled: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const defaultRadioGroupAppearanceProvider: AppearanceProviderFor<PDFRadioGroup> = (
|
||||
radioGroup,
|
||||
widget,
|
||||
) => {
|
||||
// The `/DA` entry can be at the widget or field level - so we handle both
|
||||
const widgetColor = getDefaultColor(widget);
|
||||
const fieldColor = getDefaultColor(radioGroup.acroField);
|
||||
|
||||
const rectangle = widget.getRectangle();
|
||||
const ap = widget.getAppearanceCharacteristics();
|
||||
const bs = widget.getBorderStyle();
|
||||
|
||||
const borderWidth = bs?.getWidth() ?? 0;
|
||||
const rotation = reduceRotation(ap?.getRotation());
|
||||
const { width, height } = adjustDimsForRotation(rectangle, rotation);
|
||||
|
||||
const rotate = rotateInPlace({ ...rectangle, rotation });
|
||||
|
||||
const black = rgb(0, 0, 0);
|
||||
const borderColor = componentsToColor(ap?.getBorderColor()) ?? black;
|
||||
const normalBackgroundColor = componentsToColor(ap?.getBackgroundColor());
|
||||
const downBackgroundColor = componentsToColor(ap?.getBackgroundColor(), 0.8);
|
||||
|
||||
// Update color
|
||||
const textColor = widgetColor ?? fieldColor ?? black;
|
||||
if (widgetColor) {
|
||||
updateDefaultAppearance(widget, textColor);
|
||||
} else {
|
||||
updateDefaultAppearance(radioGroup.acroField, textColor);
|
||||
}
|
||||
|
||||
const options = {
|
||||
x: width / 2,
|
||||
y: height / 2,
|
||||
width: width - borderWidth,
|
||||
height: height - borderWidth,
|
||||
borderWidth,
|
||||
borderColor,
|
||||
dotColor: textColor,
|
||||
};
|
||||
|
||||
return {
|
||||
normal: {
|
||||
on: [
|
||||
...rotate,
|
||||
...drawRadioButton({
|
||||
...options,
|
||||
color: normalBackgroundColor,
|
||||
filled: true,
|
||||
}),
|
||||
],
|
||||
off: [
|
||||
...rotate,
|
||||
...drawRadioButton({
|
||||
...options,
|
||||
color: normalBackgroundColor,
|
||||
filled: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
down: {
|
||||
on: [
|
||||
...rotate,
|
||||
...drawRadioButton({
|
||||
...options,
|
||||
color: downBackgroundColor,
|
||||
filled: true,
|
||||
}),
|
||||
],
|
||||
off: [
|
||||
...rotate,
|
||||
...drawRadioButton({
|
||||
...options,
|
||||
color: downBackgroundColor,
|
||||
filled: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const defaultButtonAppearanceProvider: AppearanceProviderFor<PDFButton> = (
|
||||
button,
|
||||
widget,
|
||||
font,
|
||||
) => {
|
||||
// The `/DA` entry can be at the widget or field level - so we handle both
|
||||
const widgetColor = getDefaultColor(widget);
|
||||
const fieldColor = getDefaultColor(button.acroField);
|
||||
const widgetFontSize = getDefaultFontSize(widget);
|
||||
const fieldFontSize = getDefaultFontSize(button.acroField);
|
||||
|
||||
const rectangle = widget.getRectangle();
|
||||
const ap = widget.getAppearanceCharacteristics();
|
||||
const bs = widget.getBorderStyle();
|
||||
const captions = ap?.getCaptions();
|
||||
const normalText = captions?.normal ?? '';
|
||||
const downText = captions?.down ?? normalText ?? '';
|
||||
|
||||
const borderWidth = bs?.getWidth() ?? 0;
|
||||
const rotation = reduceRotation(ap?.getRotation());
|
||||
const { width, height } = adjustDimsForRotation(rectangle, rotation);
|
||||
|
||||
const rotate = rotateInPlace({ ...rectangle, rotation });
|
||||
|
||||
const black = rgb(0, 0, 0);
|
||||
|
||||
const borderColor = componentsToColor(ap?.getBorderColor());
|
||||
const normalBackgroundColor = componentsToColor(ap?.getBackgroundColor());
|
||||
const downBackgroundColor = componentsToColor(ap?.getBackgroundColor(), 0.8);
|
||||
|
||||
const bounds = {
|
||||
x: borderWidth,
|
||||
y: borderWidth,
|
||||
width: width - borderWidth * 2,
|
||||
height: height - borderWidth * 2,
|
||||
};
|
||||
const normalLayout = layoutSinglelineText(normalText, {
|
||||
alignment: TextAlignment.Center,
|
||||
fontSize: widgetFontSize ?? fieldFontSize,
|
||||
font,
|
||||
bounds,
|
||||
});
|
||||
const downLayout = layoutSinglelineText(downText, {
|
||||
alignment: TextAlignment.Center,
|
||||
fontSize: widgetFontSize ?? fieldFontSize,
|
||||
font,
|
||||
bounds,
|
||||
});
|
||||
|
||||
// Update font size and color
|
||||
const fontSize = Math.min(normalLayout.fontSize, downLayout.fontSize);
|
||||
const textColor = widgetColor ?? fieldColor ?? black;
|
||||
if (widgetColor || widgetFontSize !== undefined) {
|
||||
updateDefaultAppearance(widget, textColor, font, fontSize);
|
||||
} else {
|
||||
updateDefaultAppearance(button.acroField, textColor, font, fontSize);
|
||||
}
|
||||
|
||||
const options = {
|
||||
x: 0 + borderWidth / 2,
|
||||
y: 0 + borderWidth / 2,
|
||||
width: width - borderWidth,
|
||||
height: height - borderWidth,
|
||||
borderWidth,
|
||||
borderColor,
|
||||
textColor,
|
||||
font: font.name,
|
||||
fontSize,
|
||||
};
|
||||
|
||||
return {
|
||||
normal: [
|
||||
...rotate,
|
||||
...drawButton({
|
||||
...options,
|
||||
color: normalBackgroundColor,
|
||||
textLines: [normalLayout.line],
|
||||
}),
|
||||
],
|
||||
down: [
|
||||
...rotate,
|
||||
...drawButton({
|
||||
...options,
|
||||
color: downBackgroundColor,
|
||||
textLines: [downLayout.line],
|
||||
}),
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
export const defaultTextFieldAppearanceProvider: AppearanceProviderFor<PDFTextField> = (
|
||||
textField,
|
||||
widget,
|
||||
font,
|
||||
) => {
|
||||
// The `/DA` entry can be at the widget or field level - so we handle both
|
||||
const widgetColor = getDefaultColor(widget);
|
||||
const fieldColor = getDefaultColor(textField.acroField);
|
||||
const widgetFontSize = getDefaultFontSize(widget);
|
||||
const fieldFontSize = getDefaultFontSize(textField.acroField);
|
||||
|
||||
const rectangle = widget.getRectangle();
|
||||
const ap = widget.getAppearanceCharacteristics();
|
||||
const bs = widget.getBorderStyle();
|
||||
const text = textField.getText() ?? '';
|
||||
|
||||
const borderWidth = bs?.getWidth() ?? 0;
|
||||
const rotation = reduceRotation(ap?.getRotation());
|
||||
const { width, height } = adjustDimsForRotation(rectangle, rotation);
|
||||
|
||||
const rotate = rotateInPlace({ ...rectangle, rotation });
|
||||
|
||||
const black = rgb(0, 0, 0);
|
||||
|
||||
const borderColor = componentsToColor(ap?.getBorderColor());
|
||||
const normalBackgroundColor = componentsToColor(ap?.getBackgroundColor());
|
||||
|
||||
let textLines: TextPosition[];
|
||||
let fontSize: number;
|
||||
|
||||
const padding = textField.isCombed() ? 0 : 1;
|
||||
const bounds = {
|
||||
x: borderWidth + padding,
|
||||
y: borderWidth + padding,
|
||||
width: width - (borderWidth + padding) * 2,
|
||||
height: height - (borderWidth + padding) * 2,
|
||||
};
|
||||
if (textField.isMultiline()) {
|
||||
const layout = layoutMultilineText(text, {
|
||||
alignment: textField.getAlignment(),
|
||||
fontSize: widgetFontSize ?? fieldFontSize,
|
||||
font,
|
||||
bounds,
|
||||
});
|
||||
textLines = layout.lines;
|
||||
fontSize = layout.fontSize;
|
||||
} else if (textField.isCombed()) {
|
||||
const layout = layoutCombedText(text, {
|
||||
fontSize: widgetFontSize ?? fieldFontSize,
|
||||
font,
|
||||
bounds,
|
||||
cellCount: textField.getMaxLength() ?? 0,
|
||||
});
|
||||
textLines = layout.cells;
|
||||
fontSize = layout.fontSize;
|
||||
} else {
|
||||
const layout = layoutSinglelineText(text, {
|
||||
alignment: textField.getAlignment(),
|
||||
fontSize: widgetFontSize ?? fieldFontSize,
|
||||
font,
|
||||
bounds,
|
||||
});
|
||||
textLines = [layout.line];
|
||||
fontSize = layout.fontSize;
|
||||
}
|
||||
|
||||
// Update font size and color
|
||||
const textColor = widgetColor ?? fieldColor ?? black;
|
||||
if (widgetColor || widgetFontSize !== undefined) {
|
||||
updateDefaultAppearance(widget, textColor, font, fontSize);
|
||||
} else {
|
||||
updateDefaultAppearance(textField.acroField, textColor, font, fontSize);
|
||||
}
|
||||
|
||||
const options = {
|
||||
x: 0 + borderWidth / 2,
|
||||
y: 0 + borderWidth / 2,
|
||||
width: width - borderWidth,
|
||||
height: height - borderWidth,
|
||||
borderWidth: borderWidth ?? 0,
|
||||
borderColor,
|
||||
textColor,
|
||||
font: font.name,
|
||||
fontSize,
|
||||
color: normalBackgroundColor,
|
||||
textLines,
|
||||
padding,
|
||||
};
|
||||
|
||||
return [...rotate, ...drawTextField(options)];
|
||||
};
|
||||
|
||||
export const defaultDropdownAppearanceProvider: AppearanceProviderFor<PDFDropdown> = (
|
||||
dropdown,
|
||||
widget,
|
||||
font,
|
||||
) => {
|
||||
// The `/DA` entry can be at the widget or field level - so we handle both
|
||||
const widgetColor = getDefaultColor(widget);
|
||||
const fieldColor = getDefaultColor(dropdown.acroField);
|
||||
const widgetFontSize = getDefaultFontSize(widget);
|
||||
const fieldFontSize = getDefaultFontSize(dropdown.acroField);
|
||||
|
||||
const rectangle = widget.getRectangle();
|
||||
const ap = widget.getAppearanceCharacteristics();
|
||||
const bs = widget.getBorderStyle();
|
||||
const text = dropdown.getSelected()[0] ?? '';
|
||||
|
||||
const borderWidth = bs?.getWidth() ?? 0;
|
||||
const rotation = reduceRotation(ap?.getRotation());
|
||||
const { width, height } = adjustDimsForRotation(rectangle, rotation);
|
||||
|
||||
const rotate = rotateInPlace({ ...rectangle, rotation });
|
||||
|
||||
const black = rgb(0, 0, 0);
|
||||
|
||||
const borderColor = componentsToColor(ap?.getBorderColor());
|
||||
const normalBackgroundColor = componentsToColor(ap?.getBackgroundColor());
|
||||
|
||||
const padding = 1;
|
||||
const bounds = {
|
||||
x: borderWidth + padding,
|
||||
y: borderWidth + padding,
|
||||
width: width - (borderWidth + padding) * 2,
|
||||
height: height - (borderWidth + padding) * 2,
|
||||
};
|
||||
const { line, fontSize } = layoutSinglelineText(text, {
|
||||
alignment: TextAlignment.Left,
|
||||
fontSize: widgetFontSize ?? fieldFontSize,
|
||||
font,
|
||||
bounds,
|
||||
});
|
||||
|
||||
// Update font size and color
|
||||
const textColor = widgetColor ?? fieldColor ?? black;
|
||||
if (widgetColor || widgetFontSize !== undefined) {
|
||||
updateDefaultAppearance(widget, textColor, font, fontSize);
|
||||
} else {
|
||||
updateDefaultAppearance(dropdown.acroField, textColor, font, fontSize);
|
||||
}
|
||||
|
||||
const options = {
|
||||
x: 0 + borderWidth / 2,
|
||||
y: 0 + borderWidth / 2,
|
||||
width: width - borderWidth,
|
||||
height: height - borderWidth,
|
||||
borderWidth: borderWidth ?? 0,
|
||||
borderColor,
|
||||
textColor,
|
||||
font: font.name,
|
||||
fontSize,
|
||||
color: normalBackgroundColor,
|
||||
textLines: [line],
|
||||
padding,
|
||||
};
|
||||
|
||||
return [...rotate, ...drawTextField(options)];
|
||||
};
|
||||
|
||||
export const defaultOptionListAppearanceProvider: AppearanceProviderFor<PDFOptionList> = (
|
||||
optionList,
|
||||
widget,
|
||||
font,
|
||||
) => {
|
||||
// The `/DA` entry can be at the widget or field level - so we handle both
|
||||
const widgetColor = getDefaultColor(widget);
|
||||
const fieldColor = getDefaultColor(optionList.acroField);
|
||||
const widgetFontSize = getDefaultFontSize(widget);
|
||||
const fieldFontSize = getDefaultFontSize(optionList.acroField);
|
||||
|
||||
const rectangle = widget.getRectangle();
|
||||
const ap = widget.getAppearanceCharacteristics();
|
||||
const bs = widget.getBorderStyle();
|
||||
|
||||
const borderWidth = bs?.getWidth() ?? 0;
|
||||
const rotation = reduceRotation(ap?.getRotation());
|
||||
const { width, height } = adjustDimsForRotation(rectangle, rotation);
|
||||
|
||||
const rotate = rotateInPlace({ ...rectangle, rotation });
|
||||
|
||||
const black = rgb(0, 0, 0);
|
||||
|
||||
const borderColor = componentsToColor(ap?.getBorderColor());
|
||||
const normalBackgroundColor = componentsToColor(ap?.getBackgroundColor());
|
||||
|
||||
const options = optionList.getOptions();
|
||||
const selected = optionList.getSelected();
|
||||
|
||||
if (optionList.isSorted()) options.sort();
|
||||
|
||||
let text = '';
|
||||
for (let idx = 0, len = options.length; idx < len; idx++) {
|
||||
text += options[idx];
|
||||
if (idx < len - 1) text += '\n';
|
||||
}
|
||||
|
||||
const padding = 1;
|
||||
const bounds = {
|
||||
x: borderWidth + padding,
|
||||
y: borderWidth + padding,
|
||||
width: width - (borderWidth + padding) * 2,
|
||||
height: height - (borderWidth + padding) * 2,
|
||||
};
|
||||
const { lines, fontSize, lineHeight } = layoutMultilineText(text, {
|
||||
alignment: TextAlignment.Left,
|
||||
fontSize: widgetFontSize ?? fieldFontSize,
|
||||
font,
|
||||
bounds,
|
||||
});
|
||||
|
||||
const selectedLines: number[] = [];
|
||||
for (let idx = 0, len = lines.length; idx < len; idx++) {
|
||||
const line = lines[idx];
|
||||
if (selected.includes(line.text)) selectedLines.push(idx);
|
||||
}
|
||||
|
||||
const blue = rgb(153 / 255, 193 / 255, 218 / 255);
|
||||
|
||||
// Update font size and color
|
||||
const textColor = widgetColor ?? fieldColor ?? black;
|
||||
if (widgetColor || widgetFontSize !== undefined) {
|
||||
updateDefaultAppearance(widget, textColor, font, fontSize);
|
||||
} else {
|
||||
updateDefaultAppearance(optionList.acroField, textColor, font, fontSize);
|
||||
}
|
||||
|
||||
return [
|
||||
...rotate,
|
||||
...drawOptionList({
|
||||
x: 0 + borderWidth / 2,
|
||||
y: 0 + borderWidth / 2,
|
||||
width: width - borderWidth,
|
||||
height: height - borderWidth,
|
||||
borderWidth: borderWidth ?? 0,
|
||||
borderColor,
|
||||
textColor,
|
||||
font: font.name,
|
||||
fontSize,
|
||||
color: normalBackgroundColor,
|
||||
textLines: lines,
|
||||
lineHeight,
|
||||
selectedColor: blue,
|
||||
selectedLines,
|
||||
padding,
|
||||
}),
|
||||
];
|
||||
};
|
||||
10
node_modules/pdf-lib/src/api/form/index.ts
generated
vendored
Normal file
10
node_modules/pdf-lib/src/api/form/index.ts
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
export * from 'src/api/form/appearances';
|
||||
export { default as PDFButton } from 'src/api/form/PDFButton';
|
||||
export { default as PDFCheckBox } from 'src/api/form/PDFCheckBox';
|
||||
export { default as PDFDropdown } from 'src/api/form/PDFDropdown';
|
||||
export { default as PDFField } from 'src/api/form/PDFField';
|
||||
export { default as PDFForm } from 'src/api/form/PDFForm';
|
||||
export { default as PDFOptionList } from 'src/api/form/PDFOptionList';
|
||||
export { default as PDFRadioGroup } from 'src/api/form/PDFRadioGroup';
|
||||
export { default as PDFSignature } from 'src/api/form/PDFSignature';
|
||||
export { default as PDFTextField } from 'src/api/form/PDFTextField';
|
||||
5
node_modules/pdf-lib/src/api/image/alignment.ts
generated
vendored
Normal file
5
node_modules/pdf-lib/src/api/image/alignment.ts
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum ImageAlignment {
|
||||
Left = 0,
|
||||
Center = 1,
|
||||
Right = 2,
|
||||
}
|
||||
1
node_modules/pdf-lib/src/api/image/index.ts
generated
vendored
Normal file
1
node_modules/pdf-lib/src/api/image/index.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from 'src/api/image/alignment';
|
||||
20
node_modules/pdf-lib/src/api/index.ts
generated
vendored
Normal file
20
node_modules/pdf-lib/src/api/index.ts
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
export * from 'src/api/form';
|
||||
export * from 'src/api/text';
|
||||
export * from 'src/api/colors';
|
||||
export * from 'src/api/errors';
|
||||
export * from 'src/api/image';
|
||||
export * from 'src/api/objects';
|
||||
export * from 'src/api/operations';
|
||||
export * from 'src/api/operators';
|
||||
export * from 'src/api/rotations';
|
||||
export * from 'src/api/sizes';
|
||||
export * from 'src/api/PDFPageOptions';
|
||||
export * from 'src/api/PDFDocumentOptions';
|
||||
export * from 'src/api/StandardFonts';
|
||||
export { default as PDFDocument } from 'src/api/PDFDocument';
|
||||
export { default as PDFFont } from 'src/api/PDFFont';
|
||||
export { default as PDFImage } from 'src/api/PDFImage';
|
||||
export { default as PDFPage } from 'src/api/PDFPage';
|
||||
export { default as PDFEmbeddedPage } from 'src/api/PDFEmbeddedPage';
|
||||
export { default as PDFJavaScript } from 'src/api/PDFJavaScript';
|
||||
export { default as Embeddable } from 'src/api/Embeddable';
|
||||
10
node_modules/pdf-lib/src/api/objects.ts
generated
vendored
Normal file
10
node_modules/pdf-lib/src/api/objects.ts
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import { PDFName, PDFNumber } from 'src/core';
|
||||
|
||||
export const asPDFName = (name: string | PDFName) =>
|
||||
name instanceof PDFName ? name : PDFName.of(name);
|
||||
|
||||
export const asPDFNumber = (num: number | PDFNumber) =>
|
||||
num instanceof PDFNumber ? num : PDFNumber.of(num);
|
||||
|
||||
export const asNumber = (num: number | PDFNumber) =>
|
||||
num instanceof PDFNumber ? num.asNumber() : num;
|
||||
800
node_modules/pdf-lib/src/api/operations.ts
generated
vendored
Normal file
800
node_modules/pdf-lib/src/api/operations.ts
generated
vendored
Normal file
@@ -0,0 +1,800 @@
|
||||
import { Color, setFillingColor, setStrokingColor } from 'src/api/colors';
|
||||
import {
|
||||
beginText,
|
||||
closePath,
|
||||
drawObject,
|
||||
endText,
|
||||
fill,
|
||||
fillAndStroke,
|
||||
lineTo,
|
||||
moveTo,
|
||||
nextLine,
|
||||
popGraphicsState,
|
||||
pushGraphicsState,
|
||||
rotateAndSkewTextRadiansAndTranslate,
|
||||
rotateRadians,
|
||||
scale,
|
||||
setFontAndSize,
|
||||
setLineHeight,
|
||||
setLineWidth,
|
||||
showText,
|
||||
skewRadians,
|
||||
stroke,
|
||||
translate,
|
||||
LineCapStyle,
|
||||
setLineCap,
|
||||
rotateDegrees,
|
||||
setGraphicsState,
|
||||
setDashPattern,
|
||||
beginMarkedContent,
|
||||
endMarkedContent,
|
||||
clip,
|
||||
endPath,
|
||||
appendBezierCurve,
|
||||
} from 'src/api/operators';
|
||||
import { Rotation, degrees, toRadians } from 'src/api/rotations';
|
||||
import { svgPathToOperators } from 'src/api/svgPath';
|
||||
import { PDFHexString, PDFName, PDFNumber, PDFOperator } from 'src/core';
|
||||
import { asNumber } from 'src/api/objects';
|
||||
|
||||
export interface DrawTextOptions {
|
||||
color: Color;
|
||||
font: string | PDFName;
|
||||
size: number | PDFNumber;
|
||||
rotate: Rotation;
|
||||
xSkew: Rotation;
|
||||
ySkew: Rotation;
|
||||
x: number | PDFNumber;
|
||||
y: number | PDFNumber;
|
||||
graphicsState?: string | PDFName;
|
||||
}
|
||||
|
||||
export const drawText = (
|
||||
line: PDFHexString,
|
||||
options: DrawTextOptions,
|
||||
): PDFOperator[] =>
|
||||
[
|
||||
pushGraphicsState(),
|
||||
options.graphicsState && setGraphicsState(options.graphicsState),
|
||||
beginText(),
|
||||
setFillingColor(options.color),
|
||||
setFontAndSize(options.font, options.size),
|
||||
rotateAndSkewTextRadiansAndTranslate(
|
||||
toRadians(options.rotate),
|
||||
toRadians(options.xSkew),
|
||||
toRadians(options.ySkew),
|
||||
options.x,
|
||||
options.y,
|
||||
),
|
||||
showText(line),
|
||||
endText(),
|
||||
popGraphicsState(),
|
||||
].filter(Boolean) as PDFOperator[];
|
||||
|
||||
export interface DrawLinesOfTextOptions extends DrawTextOptions {
|
||||
lineHeight: number | PDFNumber;
|
||||
}
|
||||
|
||||
export const drawLinesOfText = (
|
||||
lines: PDFHexString[],
|
||||
options: DrawLinesOfTextOptions,
|
||||
): PDFOperator[] => {
|
||||
const operators = [
|
||||
pushGraphicsState(),
|
||||
options.graphicsState && setGraphicsState(options.graphicsState),
|
||||
beginText(),
|
||||
setFillingColor(options.color),
|
||||
setFontAndSize(options.font, options.size),
|
||||
setLineHeight(options.lineHeight),
|
||||
rotateAndSkewTextRadiansAndTranslate(
|
||||
toRadians(options.rotate),
|
||||
toRadians(options.xSkew),
|
||||
toRadians(options.ySkew),
|
||||
options.x,
|
||||
options.y,
|
||||
),
|
||||
].filter(Boolean) as PDFOperator[];
|
||||
|
||||
for (let idx = 0, len = lines.length; idx < len; idx++) {
|
||||
operators.push(showText(lines[idx]), nextLine());
|
||||
}
|
||||
|
||||
operators.push(endText(), popGraphicsState());
|
||||
return operators;
|
||||
};
|
||||
|
||||
export const drawImage = (
|
||||
name: string | PDFName,
|
||||
options: {
|
||||
x: number | PDFNumber;
|
||||
y: number | PDFNumber;
|
||||
width: number | PDFNumber;
|
||||
height: number | PDFNumber;
|
||||
rotate: Rotation;
|
||||
xSkew: Rotation;
|
||||
ySkew: Rotation;
|
||||
graphicsState?: string | PDFName;
|
||||
},
|
||||
): PDFOperator[] =>
|
||||
[
|
||||
pushGraphicsState(),
|
||||
options.graphicsState && setGraphicsState(options.graphicsState),
|
||||
translate(options.x, options.y),
|
||||
rotateRadians(toRadians(options.rotate)),
|
||||
scale(options.width, options.height),
|
||||
skewRadians(toRadians(options.xSkew), toRadians(options.ySkew)),
|
||||
drawObject(name),
|
||||
popGraphicsState(),
|
||||
].filter(Boolean) as PDFOperator[];
|
||||
|
||||
export const drawPage = (
|
||||
name: string | PDFName,
|
||||
options: {
|
||||
x: number | PDFNumber;
|
||||
y: number | PDFNumber;
|
||||
xScale: number | PDFNumber;
|
||||
yScale: number | PDFNumber;
|
||||
rotate: Rotation;
|
||||
xSkew: Rotation;
|
||||
ySkew: Rotation;
|
||||
graphicsState?: string | PDFName;
|
||||
},
|
||||
): PDFOperator[] =>
|
||||
[
|
||||
pushGraphicsState(),
|
||||
options.graphicsState && setGraphicsState(options.graphicsState),
|
||||
translate(options.x, options.y),
|
||||
rotateRadians(toRadians(options.rotate)),
|
||||
scale(options.xScale, options.yScale),
|
||||
skewRadians(toRadians(options.xSkew), toRadians(options.ySkew)),
|
||||
drawObject(name),
|
||||
popGraphicsState(),
|
||||
].filter(Boolean) as PDFOperator[];
|
||||
|
||||
export const drawLine = (options: {
|
||||
start: { x: number | PDFNumber; y: number | PDFNumber };
|
||||
end: { x: number | PDFNumber; y: number | PDFNumber };
|
||||
thickness: number | PDFNumber;
|
||||
color: Color | undefined;
|
||||
dashArray?: (number | PDFNumber)[];
|
||||
dashPhase?: number | PDFNumber;
|
||||
lineCap?: LineCapStyle;
|
||||
graphicsState?: string | PDFName;
|
||||
}) =>
|
||||
[
|
||||
pushGraphicsState(),
|
||||
options.graphicsState && setGraphicsState(options.graphicsState),
|
||||
options.color && setStrokingColor(options.color),
|
||||
setLineWidth(options.thickness),
|
||||
setDashPattern(options.dashArray ?? [], options.dashPhase ?? 0),
|
||||
moveTo(options.start.x, options.start.y),
|
||||
options.lineCap && setLineCap(options.lineCap),
|
||||
moveTo(options.start.x, options.start.y),
|
||||
lineTo(options.end.x, options.end.y),
|
||||
stroke(),
|
||||
popGraphicsState(),
|
||||
].filter(Boolean) as PDFOperator[];
|
||||
|
||||
export const drawRectangle = (options: {
|
||||
x: number | PDFNumber;
|
||||
y: number | PDFNumber;
|
||||
width: number | PDFNumber;
|
||||
height: number | PDFNumber;
|
||||
borderWidth: number | PDFNumber;
|
||||
color: Color | undefined;
|
||||
borderColor: Color | undefined;
|
||||
rotate: Rotation;
|
||||
xSkew: Rotation;
|
||||
ySkew: Rotation;
|
||||
borderLineCap?: LineCapStyle;
|
||||
borderDashArray?: (number | PDFNumber)[];
|
||||
borderDashPhase?: number | PDFNumber;
|
||||
graphicsState?: string | PDFName;
|
||||
}) =>
|
||||
[
|
||||
pushGraphicsState(),
|
||||
options.graphicsState && setGraphicsState(options.graphicsState),
|
||||
options.color && setFillingColor(options.color),
|
||||
options.borderColor && setStrokingColor(options.borderColor),
|
||||
setLineWidth(options.borderWidth),
|
||||
options.borderLineCap && setLineCap(options.borderLineCap),
|
||||
setDashPattern(options.borderDashArray ?? [], options.borderDashPhase ?? 0),
|
||||
translate(options.x, options.y),
|
||||
rotateRadians(toRadians(options.rotate)),
|
||||
skewRadians(toRadians(options.xSkew), toRadians(options.ySkew)),
|
||||
moveTo(0, 0),
|
||||
lineTo(0, options.height),
|
||||
lineTo(options.width, options.height),
|
||||
lineTo(options.width, 0),
|
||||
closePath(),
|
||||
|
||||
// prettier-ignore
|
||||
options.color && options.borderWidth ? fillAndStroke()
|
||||
: options.color ? fill()
|
||||
: options.borderColor ? stroke()
|
||||
: closePath(),
|
||||
|
||||
popGraphicsState(),
|
||||
].filter(Boolean) as PDFOperator[];
|
||||
|
||||
const KAPPA = 4.0 * ((Math.sqrt(2) - 1.0) / 3.0);
|
||||
|
||||
/** @deprecated */
|
||||
export const drawEllipsePath = (config: {
|
||||
x: number | PDFNumber;
|
||||
y: number | PDFNumber;
|
||||
xScale: number | PDFNumber;
|
||||
yScale: number | PDFNumber;
|
||||
}): PDFOperator[] => {
|
||||
let x = asNumber(config.x);
|
||||
let y = asNumber(config.y);
|
||||
const xScale = asNumber(config.xScale);
|
||||
const yScale = asNumber(config.yScale);
|
||||
|
||||
x -= xScale;
|
||||
y -= yScale;
|
||||
|
||||
const ox = xScale * KAPPA;
|
||||
const oy = yScale * KAPPA;
|
||||
const xe = x + xScale * 2;
|
||||
const ye = y + yScale * 2;
|
||||
const xm = x + xScale;
|
||||
const ym = y + yScale;
|
||||
|
||||
return [
|
||||
pushGraphicsState(),
|
||||
moveTo(x, ym),
|
||||
appendBezierCurve(x, ym - oy, xm - ox, y, xm, y),
|
||||
appendBezierCurve(xm + ox, y, xe, ym - oy, xe, ym),
|
||||
appendBezierCurve(xe, ym + oy, xm + ox, ye, xm, ye),
|
||||
appendBezierCurve(xm - ox, ye, x, ym + oy, x, ym),
|
||||
popGraphicsState(),
|
||||
];
|
||||
};
|
||||
|
||||
const drawEllipseCurves = (config: {
|
||||
x: number | PDFNumber;
|
||||
y: number | PDFNumber;
|
||||
xScale: number | PDFNumber;
|
||||
yScale: number | PDFNumber;
|
||||
rotate: Rotation;
|
||||
}): PDFOperator[] => {
|
||||
const centerX = asNumber(config.x);
|
||||
const centerY = asNumber(config.y);
|
||||
const xScale = asNumber(config.xScale);
|
||||
const yScale = asNumber(config.yScale);
|
||||
|
||||
const x = -xScale;
|
||||
const y = -yScale;
|
||||
|
||||
const ox = xScale * KAPPA;
|
||||
const oy = yScale * KAPPA;
|
||||
const xe = x + xScale * 2;
|
||||
const ye = y + yScale * 2;
|
||||
const xm = x + xScale;
|
||||
const ym = y + yScale;
|
||||
|
||||
return [
|
||||
translate(centerX, centerY),
|
||||
rotateRadians(toRadians(config.rotate)),
|
||||
moveTo(x, ym),
|
||||
appendBezierCurve(x, ym - oy, xm - ox, y, xm, y),
|
||||
appendBezierCurve(xm + ox, y, xe, ym - oy, xe, ym),
|
||||
appendBezierCurve(xe, ym + oy, xm + ox, ye, xm, ye),
|
||||
appendBezierCurve(xm - ox, ye, x, ym + oy, x, ym),
|
||||
];
|
||||
};
|
||||
|
||||
export const drawEllipse = (options: {
|
||||
x: number | PDFNumber;
|
||||
y: number | PDFNumber;
|
||||
xScale: number | PDFNumber;
|
||||
yScale: number | PDFNumber;
|
||||
rotate?: Rotation;
|
||||
color: Color | undefined;
|
||||
borderColor: Color | undefined;
|
||||
borderWidth: number | PDFNumber;
|
||||
borderDashArray?: (number | PDFNumber)[];
|
||||
borderDashPhase?: number | PDFNumber;
|
||||
graphicsState?: string | PDFName;
|
||||
borderLineCap?: LineCapStyle;
|
||||
}) =>
|
||||
[
|
||||
pushGraphicsState(),
|
||||
options.graphicsState && setGraphicsState(options.graphicsState),
|
||||
options.color && setFillingColor(options.color),
|
||||
options.borderColor && setStrokingColor(options.borderColor),
|
||||
setLineWidth(options.borderWidth),
|
||||
options.borderLineCap && setLineCap(options.borderLineCap),
|
||||
setDashPattern(options.borderDashArray ?? [], options.borderDashPhase ?? 0),
|
||||
|
||||
// The `drawEllipsePath` branch is only here for backwards compatibility.
|
||||
// See https://github.com/Hopding/pdf-lib/pull/511#issuecomment-667685655.
|
||||
...(options.rotate === undefined
|
||||
? drawEllipsePath({
|
||||
x: options.x,
|
||||
y: options.y,
|
||||
xScale: options.xScale,
|
||||
yScale: options.yScale,
|
||||
})
|
||||
: drawEllipseCurves({
|
||||
x: options.x,
|
||||
y: options.y,
|
||||
xScale: options.xScale,
|
||||
yScale: options.yScale,
|
||||
rotate: options.rotate ?? degrees(0),
|
||||
})),
|
||||
|
||||
// prettier-ignore
|
||||
options.color && options.borderWidth ? fillAndStroke()
|
||||
: options.color ? fill()
|
||||
: options.borderColor ? stroke()
|
||||
: closePath(),
|
||||
|
||||
popGraphicsState(),
|
||||
].filter(Boolean) as PDFOperator[];
|
||||
|
||||
export const drawSvgPath = (
|
||||
path: string,
|
||||
options: {
|
||||
x: number | PDFNumber;
|
||||
y: number | PDFNumber;
|
||||
rotate?: Rotation;
|
||||
scale: number | PDFNumber | undefined;
|
||||
color: Color | undefined;
|
||||
borderColor: Color | undefined;
|
||||
borderWidth: number | PDFNumber;
|
||||
borderDashArray?: (number | PDFNumber)[];
|
||||
borderDashPhase?: number | PDFNumber;
|
||||
borderLineCap?: LineCapStyle;
|
||||
graphicsState?: string | PDFName;
|
||||
},
|
||||
) =>
|
||||
[
|
||||
pushGraphicsState(),
|
||||
options.graphicsState && setGraphicsState(options.graphicsState),
|
||||
|
||||
translate(options.x, options.y),
|
||||
rotateRadians(toRadians(options.rotate ?? degrees(0))),
|
||||
|
||||
// SVG path Y axis is opposite pdf-lib's
|
||||
options.scale ? scale(options.scale, -options.scale) : scale(1, -1),
|
||||
|
||||
options.color && setFillingColor(options.color),
|
||||
options.borderColor && setStrokingColor(options.borderColor),
|
||||
options.borderWidth && setLineWidth(options.borderWidth),
|
||||
options.borderLineCap && setLineCap(options.borderLineCap),
|
||||
|
||||
setDashPattern(options.borderDashArray ?? [], options.borderDashPhase ?? 0),
|
||||
|
||||
...svgPathToOperators(path),
|
||||
|
||||
// prettier-ignore
|
||||
options.color && options.borderWidth ? fillAndStroke()
|
||||
: options.color ? fill()
|
||||
: options.borderColor ? stroke()
|
||||
: closePath(),
|
||||
|
||||
popGraphicsState(),
|
||||
].filter(Boolean) as PDFOperator[];
|
||||
|
||||
export const drawCheckMark = (options: {
|
||||
x: number | PDFNumber;
|
||||
y: number | PDFNumber;
|
||||
size: number | PDFNumber;
|
||||
thickness: number | PDFNumber;
|
||||
color: Color | undefined;
|
||||
}) => {
|
||||
const size = asNumber(options.size);
|
||||
|
||||
/*********************** Define Check Mark Points ***************************/
|
||||
// A check mark is defined by three points in some coordinate space. Here, we
|
||||
// define these points in a unit coordinate system, where the range of the x
|
||||
// and y axis are both [-1, 1].
|
||||
//
|
||||
// Note that we do not hard code `p1y` in case we wish to change the
|
||||
// size/shape of the check mark in the future. We want the check mark to
|
||||
// always form a right angle. This means that the dot product between (p1-p2)
|
||||
// and (p3-p2) should be zero:
|
||||
//
|
||||
// (p1x-p2x) * (p3x-p2x) + (p1y-p2y) * (p3y-p2y) = 0
|
||||
//
|
||||
// We can now rejigger this equation to solve for `p1y`:
|
||||
//
|
||||
// (p1y-p2y) * (p3y-p2y) = -((p1x-p2x) * (p3x-p2x))
|
||||
// (p1y-p2y) = -((p1x-p2x) * (p3x-p2x)) / (p3y-p2y)
|
||||
// p1y = -((p1x-p2x) * (p3x-p2x)) / (p3y-p2y) + p2y
|
||||
//
|
||||
// Thanks to my friend Joel Walker (https://github.com/JWalker1995) for
|
||||
// devising the above equation and unit coordinate system approach!
|
||||
|
||||
// (x, y) coords of the check mark's bottommost point
|
||||
const p2x = -1 + 0.75;
|
||||
const p2y = -1 + 0.51;
|
||||
|
||||
// (x, y) coords of the check mark's topmost point
|
||||
const p3y = 1 - 0.525;
|
||||
const p3x = 1 - 0.31;
|
||||
|
||||
// (x, y) coords of the check mark's center (vertically) point
|
||||
const p1x = -1 + 0.325;
|
||||
const p1y = -((p1x - p2x) * (p3x - p2x)) / (p3y - p2y) + p2y;
|
||||
/****************************************************************************/
|
||||
|
||||
return [
|
||||
pushGraphicsState(),
|
||||
options.color && setStrokingColor(options.color),
|
||||
setLineWidth(options.thickness),
|
||||
|
||||
translate(options.x, options.y),
|
||||
moveTo(p1x * size, p1y * size),
|
||||
lineTo(p2x * size, p2y * size),
|
||||
lineTo(p3x * size, p3y * size),
|
||||
|
||||
stroke(),
|
||||
popGraphicsState(),
|
||||
].filter(Boolean) as PDFOperator[];
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
export const rotateInPlace = (options: {
|
||||
width: number | PDFNumber;
|
||||
height: number | PDFNumber;
|
||||
rotation: 0 | 90 | 180 | 270;
|
||||
}) =>
|
||||
options.rotation === 0 ? [
|
||||
translate(0, 0),
|
||||
rotateDegrees(0)
|
||||
]
|
||||
: options.rotation === 90 ? [
|
||||
translate(options.width, 0),
|
||||
rotateDegrees(90)
|
||||
]
|
||||
: options.rotation === 180 ? [
|
||||
translate(options.width, options.height),
|
||||
rotateDegrees(180)
|
||||
]
|
||||
: options.rotation === 270 ? [
|
||||
translate(0, options.height),
|
||||
rotateDegrees(270)
|
||||
]
|
||||
: []; // Invalid rotation - noop
|
||||
|
||||
export const drawCheckBox = (options: {
|
||||
x: number | PDFNumber;
|
||||
y: number | PDFNumber;
|
||||
width: number | PDFNumber;
|
||||
height: number | PDFNumber;
|
||||
thickness: number | PDFNumber;
|
||||
borderWidth: number | PDFNumber;
|
||||
markColor: Color | undefined;
|
||||
color: Color | undefined;
|
||||
borderColor: Color | undefined;
|
||||
filled: boolean;
|
||||
}) => {
|
||||
const outline = drawRectangle({
|
||||
x: options.x,
|
||||
y: options.y,
|
||||
width: options.width,
|
||||
height: options.height,
|
||||
borderWidth: options.borderWidth,
|
||||
color: options.color,
|
||||
borderColor: options.borderColor,
|
||||
rotate: degrees(0),
|
||||
xSkew: degrees(0),
|
||||
ySkew: degrees(0),
|
||||
});
|
||||
|
||||
if (!options.filled) return outline;
|
||||
|
||||
const width = asNumber(options.width);
|
||||
const height = asNumber(options.height);
|
||||
|
||||
const checkMarkSize = Math.min(width, height) / 2;
|
||||
|
||||
const checkMark = drawCheckMark({
|
||||
x: width / 2,
|
||||
y: height / 2,
|
||||
size: checkMarkSize,
|
||||
thickness: options.thickness,
|
||||
color: options.markColor,
|
||||
});
|
||||
|
||||
return [pushGraphicsState(), ...outline, ...checkMark, popGraphicsState()];
|
||||
};
|
||||
|
||||
export const drawRadioButton = (options: {
|
||||
x: number | PDFNumber;
|
||||
y: number | PDFNumber;
|
||||
width: number | PDFNumber;
|
||||
height: number | PDFNumber;
|
||||
borderWidth: number | PDFNumber;
|
||||
dotColor: Color | undefined;
|
||||
color: Color | undefined;
|
||||
borderColor: Color | undefined;
|
||||
filled: boolean;
|
||||
}) => {
|
||||
const width = asNumber(options.width);
|
||||
const height = asNumber(options.height);
|
||||
|
||||
const outlineScale = Math.min(width, height) / 2;
|
||||
|
||||
const outline = drawEllipse({
|
||||
x: options.x,
|
||||
y: options.y,
|
||||
xScale: outlineScale,
|
||||
yScale: outlineScale,
|
||||
color: options.color,
|
||||
borderColor: options.borderColor,
|
||||
borderWidth: options.borderWidth,
|
||||
});
|
||||
|
||||
if (!options.filled) return outline;
|
||||
|
||||
const dot = drawEllipse({
|
||||
x: options.x,
|
||||
y: options.y,
|
||||
xScale: outlineScale * 0.45,
|
||||
yScale: outlineScale * 0.45,
|
||||
color: options.dotColor,
|
||||
borderColor: undefined,
|
||||
borderWidth: 0,
|
||||
});
|
||||
|
||||
return [pushGraphicsState(), ...outline, ...dot, popGraphicsState()];
|
||||
};
|
||||
|
||||
export const drawButton = (options: {
|
||||
x: number | PDFNumber;
|
||||
y: number | PDFNumber;
|
||||
width: number | PDFNumber;
|
||||
height: number | PDFNumber;
|
||||
borderWidth: number | PDFNumber;
|
||||
color: Color | undefined;
|
||||
borderColor: Color | undefined;
|
||||
textLines: { encoded: PDFHexString; x: number; y: number }[];
|
||||
textColor: Color;
|
||||
font: string | PDFName;
|
||||
fontSize: number | PDFNumber;
|
||||
}) => {
|
||||
const x = asNumber(options.x);
|
||||
const y = asNumber(options.y);
|
||||
const width = asNumber(options.width);
|
||||
const height = asNumber(options.height);
|
||||
|
||||
const background = drawRectangle({
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
borderWidth: options.borderWidth,
|
||||
color: options.color,
|
||||
borderColor: options.borderColor,
|
||||
rotate: degrees(0),
|
||||
xSkew: degrees(0),
|
||||
ySkew: degrees(0),
|
||||
});
|
||||
|
||||
const lines = drawTextLines(options.textLines, {
|
||||
color: options.textColor,
|
||||
font: options.font,
|
||||
size: options.fontSize,
|
||||
rotate: degrees(0),
|
||||
xSkew: degrees(0),
|
||||
ySkew: degrees(0),
|
||||
});
|
||||
|
||||
return [pushGraphicsState(), ...background, ...lines, popGraphicsState()];
|
||||
};
|
||||
|
||||
export interface DrawTextLinesOptions {
|
||||
color: Color;
|
||||
font: string | PDFName;
|
||||
size: number | PDFNumber;
|
||||
rotate: Rotation;
|
||||
xSkew: Rotation;
|
||||
ySkew: Rotation;
|
||||
}
|
||||
|
||||
export const drawTextLines = (
|
||||
lines: { encoded: PDFHexString; x: number; y: number }[],
|
||||
options: DrawTextLinesOptions,
|
||||
): PDFOperator[] => {
|
||||
const operators = [
|
||||
beginText(),
|
||||
setFillingColor(options.color),
|
||||
setFontAndSize(options.font, options.size),
|
||||
];
|
||||
|
||||
for (let idx = 0, len = lines.length; idx < len; idx++) {
|
||||
const { encoded, x, y } = lines[idx];
|
||||
operators.push(
|
||||
rotateAndSkewTextRadiansAndTranslate(
|
||||
toRadians(options.rotate),
|
||||
toRadians(options.xSkew),
|
||||
toRadians(options.ySkew),
|
||||
x,
|
||||
y,
|
||||
),
|
||||
showText(encoded),
|
||||
);
|
||||
}
|
||||
|
||||
operators.push(endText());
|
||||
|
||||
return operators;
|
||||
};
|
||||
|
||||
export const drawTextField = (options: {
|
||||
x: number | PDFNumber;
|
||||
y: number | PDFNumber;
|
||||
width: number | PDFNumber;
|
||||
height: number | PDFNumber;
|
||||
borderWidth: number | PDFNumber;
|
||||
color: Color | undefined;
|
||||
borderColor: Color | undefined;
|
||||
textLines: { encoded: PDFHexString; x: number; y: number }[];
|
||||
textColor: Color;
|
||||
font: string | PDFName;
|
||||
fontSize: number | PDFNumber;
|
||||
padding: number | PDFNumber;
|
||||
}) => {
|
||||
const x = asNumber(options.x);
|
||||
const y = asNumber(options.y);
|
||||
const width = asNumber(options.width);
|
||||
const height = asNumber(options.height);
|
||||
const borderWidth = asNumber(options.borderWidth);
|
||||
const padding = asNumber(options.padding);
|
||||
|
||||
const clipX = x + borderWidth / 2 + padding;
|
||||
const clipY = y + borderWidth / 2 + padding;
|
||||
const clipWidth = width - (borderWidth / 2 + padding) * 2;
|
||||
const clipHeight = height - (borderWidth / 2 + padding) * 2;
|
||||
|
||||
const clippingArea = [
|
||||
moveTo(clipX, clipY),
|
||||
lineTo(clipX, clipY + clipHeight),
|
||||
lineTo(clipX + clipWidth, clipY + clipHeight),
|
||||
lineTo(clipX + clipWidth, clipY),
|
||||
closePath(),
|
||||
clip(),
|
||||
endPath(),
|
||||
];
|
||||
|
||||
const background = drawRectangle({
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
borderWidth: options.borderWidth,
|
||||
color: options.color,
|
||||
borderColor: options.borderColor,
|
||||
rotate: degrees(0),
|
||||
xSkew: degrees(0),
|
||||
ySkew: degrees(0),
|
||||
});
|
||||
|
||||
const lines = drawTextLines(options.textLines, {
|
||||
color: options.textColor,
|
||||
font: options.font,
|
||||
size: options.fontSize,
|
||||
rotate: degrees(0),
|
||||
xSkew: degrees(0),
|
||||
ySkew: degrees(0),
|
||||
});
|
||||
|
||||
const markedContent = [
|
||||
beginMarkedContent('Tx'),
|
||||
pushGraphicsState(),
|
||||
...lines,
|
||||
popGraphicsState(),
|
||||
endMarkedContent(),
|
||||
];
|
||||
|
||||
return [
|
||||
pushGraphicsState(),
|
||||
...background,
|
||||
...clippingArea,
|
||||
...markedContent,
|
||||
popGraphicsState(),
|
||||
];
|
||||
};
|
||||
|
||||
export const drawOptionList = (options: {
|
||||
x: number | PDFNumber;
|
||||
y: number | PDFNumber;
|
||||
width: number | PDFNumber;
|
||||
height: number | PDFNumber;
|
||||
borderWidth: number | PDFNumber;
|
||||
color: Color | undefined;
|
||||
borderColor: Color | undefined;
|
||||
textLines: { encoded: PDFHexString; x: number; y: number; height: number }[];
|
||||
textColor: Color;
|
||||
font: string | PDFName;
|
||||
fontSize: number | PDFNumber;
|
||||
lineHeight: number | PDFNumber;
|
||||
selectedLines: number[];
|
||||
selectedColor: Color;
|
||||
padding: number | PDFNumber;
|
||||
}) => {
|
||||
const x = asNumber(options.x);
|
||||
const y = asNumber(options.y);
|
||||
const width = asNumber(options.width);
|
||||
const height = asNumber(options.height);
|
||||
const lineHeight = asNumber(options.lineHeight);
|
||||
const borderWidth = asNumber(options.borderWidth);
|
||||
const padding = asNumber(options.padding);
|
||||
|
||||
const clipX = x + borderWidth / 2 + padding;
|
||||
const clipY = y + borderWidth / 2 + padding;
|
||||
const clipWidth = width - (borderWidth / 2 + padding) * 2;
|
||||
const clipHeight = height - (borderWidth / 2 + padding) * 2;
|
||||
|
||||
const clippingArea = [
|
||||
moveTo(clipX, clipY),
|
||||
lineTo(clipX, clipY + clipHeight),
|
||||
lineTo(clipX + clipWidth, clipY + clipHeight),
|
||||
lineTo(clipX + clipWidth, clipY),
|
||||
closePath(),
|
||||
clip(),
|
||||
endPath(),
|
||||
];
|
||||
|
||||
const background = drawRectangle({
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
borderWidth: options.borderWidth,
|
||||
color: options.color,
|
||||
borderColor: options.borderColor,
|
||||
rotate: degrees(0),
|
||||
xSkew: degrees(0),
|
||||
ySkew: degrees(0),
|
||||
});
|
||||
|
||||
const highlights: PDFOperator[] = [];
|
||||
for (let idx = 0, len = options.selectedLines.length; idx < len; idx++) {
|
||||
const line = options.textLines[options.selectedLines[idx]];
|
||||
highlights.push(
|
||||
...drawRectangle({
|
||||
x: line.x - padding,
|
||||
y: line.y - (lineHeight - line.height) / 2,
|
||||
width: width - borderWidth,
|
||||
height: line.height + (lineHeight - line.height) / 2,
|
||||
borderWidth: 0,
|
||||
color: options.selectedColor,
|
||||
borderColor: undefined,
|
||||
rotate: degrees(0),
|
||||
xSkew: degrees(0),
|
||||
ySkew: degrees(0),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const lines = drawTextLines(options.textLines, {
|
||||
color: options.textColor,
|
||||
font: options.font,
|
||||
size: options.fontSize,
|
||||
rotate: degrees(0),
|
||||
xSkew: degrees(0),
|
||||
ySkew: degrees(0),
|
||||
});
|
||||
|
||||
const markedContent = [
|
||||
beginMarkedContent('Tx'),
|
||||
pushGraphicsState(),
|
||||
...lines,
|
||||
popGraphicsState(),
|
||||
endMarkedContent(),
|
||||
];
|
||||
|
||||
return [
|
||||
pushGraphicsState(),
|
||||
...background,
|
||||
...highlights,
|
||||
...clippingArea,
|
||||
...markedContent,
|
||||
popGraphicsState(),
|
||||
];
|
||||
};
|
||||
360
node_modules/pdf-lib/src/api/operators.ts
generated
vendored
Normal file
360
node_modules/pdf-lib/src/api/operators.ts
generated
vendored
Normal file
@@ -0,0 +1,360 @@
|
||||
import { asNumber, asPDFName, asPDFNumber } from 'src/api/objects';
|
||||
import { degreesToRadians } from 'src/api/rotations';
|
||||
import {
|
||||
PDFHexString,
|
||||
PDFName,
|
||||
PDFNumber,
|
||||
PDFOperator,
|
||||
PDFOperatorNames as Ops,
|
||||
} from 'src/core';
|
||||
|
||||
/* ==================== Clipping Path Operators ==================== */
|
||||
|
||||
export const clip = () => PDFOperator.of(Ops.ClipNonZero);
|
||||
export const clipEvenOdd = () => PDFOperator.of(Ops.ClipEvenOdd);
|
||||
|
||||
/* ==================== Graphics State Operators ==================== */
|
||||
|
||||
const { cos, sin, tan } = Math;
|
||||
|
||||
export const concatTransformationMatrix = (
|
||||
a: number | PDFNumber,
|
||||
b: number | PDFNumber,
|
||||
c: number | PDFNumber,
|
||||
d: number | PDFNumber,
|
||||
e: number | PDFNumber,
|
||||
f: number | PDFNumber,
|
||||
) =>
|
||||
PDFOperator.of(Ops.ConcatTransformationMatrix, [
|
||||
asPDFNumber(a),
|
||||
asPDFNumber(b),
|
||||
asPDFNumber(c),
|
||||
asPDFNumber(d),
|
||||
asPDFNumber(e),
|
||||
asPDFNumber(f),
|
||||
]);
|
||||
|
||||
export const translate = (xPos: number | PDFNumber, yPos: number | PDFNumber) =>
|
||||
concatTransformationMatrix(1, 0, 0, 1, xPos, yPos);
|
||||
|
||||
export const scale = (xPos: number | PDFNumber, yPos: number | PDFNumber) =>
|
||||
concatTransformationMatrix(xPos, 0, 0, yPos, 0, 0);
|
||||
|
||||
export const rotateRadians = (angle: number | PDFNumber) =>
|
||||
concatTransformationMatrix(
|
||||
cos(asNumber(angle)),
|
||||
sin(asNumber(angle)),
|
||||
-sin(asNumber(angle)),
|
||||
cos(asNumber(angle)),
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
export const rotateDegrees = (angle: number | PDFNumber) =>
|
||||
rotateRadians(degreesToRadians(asNumber(angle)));
|
||||
|
||||
export const skewRadians = (
|
||||
xSkewAngle: number | PDFNumber,
|
||||
ySkewAngle: number | PDFNumber,
|
||||
) =>
|
||||
concatTransformationMatrix(
|
||||
1,
|
||||
tan(asNumber(xSkewAngle)),
|
||||
tan(asNumber(ySkewAngle)),
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
export const skewDegrees = (
|
||||
xSkewAngle: number | PDFNumber,
|
||||
ySkewAngle: number | PDFNumber,
|
||||
) =>
|
||||
skewRadians(
|
||||
degreesToRadians(asNumber(xSkewAngle)),
|
||||
degreesToRadians(asNumber(ySkewAngle)),
|
||||
);
|
||||
|
||||
export const setDashPattern = (
|
||||
dashArray: (number | PDFNumber)[],
|
||||
dashPhase: number | PDFNumber,
|
||||
) =>
|
||||
PDFOperator.of(Ops.SetLineDashPattern, [
|
||||
`[${dashArray.map(asPDFNumber).join(' ')}]`,
|
||||
asPDFNumber(dashPhase),
|
||||
]);
|
||||
|
||||
export const restoreDashPattern = () => setDashPattern([], 0);
|
||||
|
||||
export enum LineCapStyle {
|
||||
Butt = 0,
|
||||
Round = 1,
|
||||
Projecting = 2,
|
||||
}
|
||||
|
||||
export const setLineCap = (style: LineCapStyle) =>
|
||||
PDFOperator.of(Ops.SetLineCapStyle, [asPDFNumber(style)]);
|
||||
|
||||
export enum LineJoinStyle {
|
||||
Miter = 0,
|
||||
Round = 1,
|
||||
Bevel = 2,
|
||||
}
|
||||
|
||||
export const setLineJoin = (style: LineJoinStyle) =>
|
||||
PDFOperator.of(Ops.SetLineJoinStyle, [asPDFNumber(style)]);
|
||||
|
||||
export const setGraphicsState = (state: string | PDFName) =>
|
||||
PDFOperator.of(Ops.SetGraphicsStateParams, [asPDFName(state)]);
|
||||
|
||||
export const pushGraphicsState = () => PDFOperator.of(Ops.PushGraphicsState);
|
||||
|
||||
export const popGraphicsState = () => PDFOperator.of(Ops.PopGraphicsState);
|
||||
|
||||
export const setLineWidth = (width: number | PDFNumber) =>
|
||||
PDFOperator.of(Ops.SetLineWidth, [asPDFNumber(width)]);
|
||||
|
||||
/* ==================== Path Construction Operators ==================== */
|
||||
|
||||
export const appendBezierCurve = (
|
||||
x1: number | PDFNumber,
|
||||
y1: number | PDFNumber,
|
||||
x2: number | PDFNumber,
|
||||
y2: number | PDFNumber,
|
||||
x3: number | PDFNumber,
|
||||
y3: number | PDFNumber,
|
||||
) =>
|
||||
PDFOperator.of(Ops.AppendBezierCurve, [
|
||||
asPDFNumber(x1),
|
||||
asPDFNumber(y1),
|
||||
asPDFNumber(x2),
|
||||
asPDFNumber(y2),
|
||||
asPDFNumber(x3),
|
||||
asPDFNumber(y3),
|
||||
]);
|
||||
|
||||
export const appendQuadraticCurve = (
|
||||
x1: number | PDFNumber,
|
||||
y1: number | PDFNumber,
|
||||
x2: number | PDFNumber,
|
||||
y2: number | PDFNumber,
|
||||
) =>
|
||||
PDFOperator.of(Ops.CurveToReplicateInitialPoint, [
|
||||
asPDFNumber(x1),
|
||||
asPDFNumber(y1),
|
||||
asPDFNumber(x2),
|
||||
asPDFNumber(y2),
|
||||
]);
|
||||
|
||||
export const closePath = () => PDFOperator.of(Ops.ClosePath);
|
||||
|
||||
export const moveTo = (xPos: number | PDFNumber, yPos: number | PDFNumber) =>
|
||||
PDFOperator.of(Ops.MoveTo, [asPDFNumber(xPos), asPDFNumber(yPos)]);
|
||||
|
||||
export const lineTo = (xPos: number | PDFNumber, yPos: number | PDFNumber) =>
|
||||
PDFOperator.of(Ops.LineTo, [asPDFNumber(xPos), asPDFNumber(yPos)]);
|
||||
|
||||
/**
|
||||
* @param xPos x coordinate for the lower left corner of the rectangle
|
||||
* @param yPos y coordinate for the lower left corner of the rectangle
|
||||
* @param width width of the rectangle
|
||||
* @param height height of the rectangle
|
||||
*/
|
||||
export const rectangle = (
|
||||
xPos: number | PDFNumber,
|
||||
yPos: number | PDFNumber,
|
||||
width: number | PDFNumber,
|
||||
height: number | PDFNumber,
|
||||
) =>
|
||||
PDFOperator.of(Ops.AppendRectangle, [
|
||||
asPDFNumber(xPos),
|
||||
asPDFNumber(yPos),
|
||||
asPDFNumber(width),
|
||||
asPDFNumber(height),
|
||||
]);
|
||||
|
||||
/**
|
||||
* @param xPos x coordinate for the lower left corner of the square
|
||||
* @param yPos y coordinate for the lower left corner of the square
|
||||
* @param size width and height of the square
|
||||
*/
|
||||
export const square = (xPos: number, yPos: number, size: number) =>
|
||||
rectangle(xPos, yPos, size, size);
|
||||
|
||||
/* ==================== Path Painting Operators ==================== */
|
||||
|
||||
export const stroke = () => PDFOperator.of(Ops.StrokePath);
|
||||
|
||||
export const fill = () => PDFOperator.of(Ops.FillNonZero);
|
||||
|
||||
export const fillAndStroke = () => PDFOperator.of(Ops.FillNonZeroAndStroke);
|
||||
|
||||
export const endPath = () => PDFOperator.of(Ops.EndPath);
|
||||
|
||||
/* ==================== Text Positioning Operators ==================== */
|
||||
|
||||
export const nextLine = () => PDFOperator.of(Ops.NextLine);
|
||||
|
||||
export const moveText = (x: number | PDFNumber, y: number | PDFNumber) =>
|
||||
PDFOperator.of(Ops.MoveText, [asPDFNumber(x), asPDFNumber(y)]);
|
||||
|
||||
/* ==================== Text Showing Operators ==================== */
|
||||
|
||||
export const showText = (text: PDFHexString) =>
|
||||
PDFOperator.of(Ops.ShowText, [text]);
|
||||
|
||||
/* ==================== Text State Operators ==================== */
|
||||
|
||||
export const beginText = () => PDFOperator.of(Ops.BeginText);
|
||||
export const endText = () => PDFOperator.of(Ops.EndText);
|
||||
|
||||
export const setFontAndSize = (
|
||||
name: string | PDFName,
|
||||
size: number | PDFNumber,
|
||||
) => PDFOperator.of(Ops.SetFontAndSize, [asPDFName(name), asPDFNumber(size)]);
|
||||
|
||||
export const setCharacterSpacing = (spacing: number | PDFNumber) =>
|
||||
PDFOperator.of(Ops.SetCharacterSpacing, [asPDFNumber(spacing)]);
|
||||
|
||||
export const setWordSpacing = (spacing: number | PDFNumber) =>
|
||||
PDFOperator.of(Ops.SetWordSpacing, [asPDFNumber(spacing)]);
|
||||
|
||||
/** @param squeeze horizontal character spacing */
|
||||
export const setCharacterSqueeze = (squeeze: number | PDFNumber) =>
|
||||
PDFOperator.of(Ops.SetTextHorizontalScaling, [asPDFNumber(squeeze)]);
|
||||
|
||||
export const setLineHeight = (lineHeight: number | PDFNumber) =>
|
||||
PDFOperator.of(Ops.SetTextLineHeight, [asPDFNumber(lineHeight)]);
|
||||
|
||||
export const setTextRise = (rise: number | PDFNumber) =>
|
||||
PDFOperator.of(Ops.SetTextRise, [asPDFNumber(rise)]);
|
||||
|
||||
export enum TextRenderingMode {
|
||||
Fill = 0,
|
||||
Outline = 1,
|
||||
FillAndOutline = 2,
|
||||
Invisible = 3,
|
||||
FillAndClip = 4,
|
||||
OutlineAndClip = 5,
|
||||
FillAndOutlineAndClip = 6,
|
||||
Clip = 7,
|
||||
}
|
||||
|
||||
export const setTextRenderingMode = (mode: TextRenderingMode) =>
|
||||
PDFOperator.of(Ops.SetTextRenderingMode, [asPDFNumber(mode)]);
|
||||
|
||||
export const setTextMatrix = (
|
||||
a: number | PDFNumber,
|
||||
b: number | PDFNumber,
|
||||
c: number | PDFNumber,
|
||||
d: number | PDFNumber,
|
||||
e: number | PDFNumber,
|
||||
f: number | PDFNumber,
|
||||
) =>
|
||||
PDFOperator.of(Ops.SetTextMatrix, [
|
||||
asPDFNumber(a),
|
||||
asPDFNumber(b),
|
||||
asPDFNumber(c),
|
||||
asPDFNumber(d),
|
||||
asPDFNumber(e),
|
||||
asPDFNumber(f),
|
||||
]);
|
||||
|
||||
export const rotateAndSkewTextRadiansAndTranslate = (
|
||||
rotationAngle: number | PDFNumber,
|
||||
xSkewAngle: number | PDFNumber,
|
||||
ySkewAngle: number | PDFNumber,
|
||||
x: number | PDFNumber,
|
||||
y: number | PDFNumber,
|
||||
) =>
|
||||
setTextMatrix(
|
||||
cos(asNumber(rotationAngle)),
|
||||
sin(asNumber(rotationAngle)) + tan(asNumber(xSkewAngle)),
|
||||
-sin(asNumber(rotationAngle)) + tan(asNumber(ySkewAngle)),
|
||||
cos(asNumber(rotationAngle)),
|
||||
x,
|
||||
y,
|
||||
);
|
||||
|
||||
export const rotateAndSkewTextDegreesAndTranslate = (
|
||||
rotationAngle: number | PDFNumber,
|
||||
xSkewAngle: number | PDFNumber,
|
||||
ySkewAngle: number | PDFNumber,
|
||||
x: number | PDFNumber,
|
||||
y: number | PDFNumber,
|
||||
) =>
|
||||
rotateAndSkewTextRadiansAndTranslate(
|
||||
degreesToRadians(asNumber(rotationAngle)),
|
||||
degreesToRadians(asNumber(xSkewAngle)),
|
||||
degreesToRadians(asNumber(ySkewAngle)),
|
||||
x,
|
||||
y,
|
||||
);
|
||||
|
||||
/* ==================== XObject Operator ==================== */
|
||||
|
||||
export const drawObject = (name: string | PDFName) =>
|
||||
PDFOperator.of(Ops.DrawObject, [asPDFName(name)]);
|
||||
|
||||
/* ==================== Color Operators ==================== */
|
||||
|
||||
export const setFillingGrayscaleColor = (gray: number | PDFNumber) =>
|
||||
PDFOperator.of(Ops.NonStrokingColorGray, [asPDFNumber(gray)]);
|
||||
|
||||
export const setStrokingGrayscaleColor = (gray: number | PDFNumber) =>
|
||||
PDFOperator.of(Ops.StrokingColorGray, [asPDFNumber(gray)]);
|
||||
|
||||
export const setFillingRgbColor = (
|
||||
red: number | PDFNumber,
|
||||
green: number | PDFNumber,
|
||||
blue: number | PDFNumber,
|
||||
) =>
|
||||
PDFOperator.of(Ops.NonStrokingColorRgb, [
|
||||
asPDFNumber(red),
|
||||
asPDFNumber(green),
|
||||
asPDFNumber(blue),
|
||||
]);
|
||||
|
||||
export const setStrokingRgbColor = (
|
||||
red: number | PDFNumber,
|
||||
green: number | PDFNumber,
|
||||
blue: number | PDFNumber,
|
||||
) =>
|
||||
PDFOperator.of(Ops.StrokingColorRgb, [
|
||||
asPDFNumber(red),
|
||||
asPDFNumber(green),
|
||||
asPDFNumber(blue),
|
||||
]);
|
||||
|
||||
export const setFillingCmykColor = (
|
||||
cyan: number | PDFNumber,
|
||||
magenta: number | PDFNumber,
|
||||
yellow: number | PDFNumber,
|
||||
key: number | PDFNumber,
|
||||
) =>
|
||||
PDFOperator.of(Ops.NonStrokingColorCmyk, [
|
||||
asPDFNumber(cyan),
|
||||
asPDFNumber(magenta),
|
||||
asPDFNumber(yellow),
|
||||
asPDFNumber(key),
|
||||
]);
|
||||
|
||||
export const setStrokingCmykColor = (
|
||||
cyan: number | PDFNumber,
|
||||
magenta: number | PDFNumber,
|
||||
yellow: number | PDFNumber,
|
||||
key: number | PDFNumber,
|
||||
) =>
|
||||
PDFOperator.of(Ops.StrokingColorCmyk, [
|
||||
asPDFNumber(cyan),
|
||||
asPDFNumber(magenta),
|
||||
asPDFNumber(yellow),
|
||||
asPDFNumber(key),
|
||||
]);
|
||||
|
||||
/* ==================== Marked Content Operators ==================== */
|
||||
|
||||
export const beginMarkedContent = (tag: string | PDFName) =>
|
||||
PDFOperator.of(Ops.BeginMarkedContent, [asPDFName(tag)]);
|
||||
|
||||
export const endMarkedContent = () => PDFOperator.of(Ops.EndMarkedContent);
|
||||
87
node_modules/pdf-lib/src/api/rotations.ts
generated
vendored
Normal file
87
node_modules/pdf-lib/src/api/rotations.ts
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
import { assertIs, error } from 'src/utils';
|
||||
|
||||
export enum RotationTypes {
|
||||
Degrees = 'degrees',
|
||||
Radians = 'radians',
|
||||
}
|
||||
|
||||
export interface Radians {
|
||||
type: RotationTypes.Radians;
|
||||
angle: number;
|
||||
}
|
||||
|
||||
export interface Degrees {
|
||||
type: RotationTypes.Degrees;
|
||||
angle: number;
|
||||
}
|
||||
|
||||
export type Rotation = Radians | Degrees;
|
||||
|
||||
export const radians = (radianAngle: number): Radians => {
|
||||
assertIs(radianAngle, 'radianAngle', ['number']);
|
||||
return { type: RotationTypes.Radians, angle: radianAngle };
|
||||
};
|
||||
|
||||
export const degrees = (degreeAngle: number): Degrees => {
|
||||
assertIs(degreeAngle, 'degreeAngle', ['number']);
|
||||
return { type: RotationTypes.Degrees, angle: degreeAngle };
|
||||
};
|
||||
|
||||
const { Radians, Degrees } = RotationTypes;
|
||||
|
||||
export const degreesToRadians = (degree: number) => (degree * Math.PI) / 180;
|
||||
export const radiansToDegrees = (radian: number) => (radian * 180) / Math.PI;
|
||||
|
||||
// prettier-ignore
|
||||
export const toRadians = (rotation: Rotation) =>
|
||||
rotation.type === Radians ? rotation.angle
|
||||
: rotation.type === Degrees ? degreesToRadians(rotation.angle)
|
||||
: error(`Invalid rotation: ${JSON.stringify(rotation)}`);
|
||||
|
||||
// prettier-ignore
|
||||
export const toDegrees = (rotation: Rotation) =>
|
||||
rotation.type === Radians ? radiansToDegrees(rotation.angle)
|
||||
: rotation.type === Degrees ? rotation.angle
|
||||
: error(`Invalid rotation: ${JSON.stringify(rotation)}`);
|
||||
|
||||
export const reduceRotation = (degreeAngle = 0) => {
|
||||
const quadrants = (degreeAngle / 90) % 4;
|
||||
if (quadrants === 0) return 0;
|
||||
if (quadrants === 1) return 90;
|
||||
if (quadrants === 2) return 180;
|
||||
if (quadrants === 3) return 270;
|
||||
return 0; // `degreeAngle` is not a multiple of 90
|
||||
};
|
||||
|
||||
export const adjustDimsForRotation = (
|
||||
dims: { width: number; height: number },
|
||||
degreeAngle = 0,
|
||||
) => {
|
||||
const rotation = reduceRotation(degreeAngle);
|
||||
return rotation === 90 || rotation === 270
|
||||
? { width: dims.height, height: dims.width }
|
||||
: { width: dims.width, height: dims.height };
|
||||
};
|
||||
|
||||
export const rotateRectangle = (
|
||||
rectangle: {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
},
|
||||
borderWidth = 0,
|
||||
degreeAngle = 0,
|
||||
) => {
|
||||
const { x, y, width: w, height: h } = rectangle;
|
||||
|
||||
const r = reduceRotation(degreeAngle);
|
||||
const b = borderWidth / 2;
|
||||
|
||||
// prettier-ignore
|
||||
if (r === 0) return { x: x - b, y: y - b, width: w, height: h };
|
||||
else if (r === 90) return { x: x - h + b, y: y - b, width: h, height: w };
|
||||
else if (r === 180) return { x: x - w + b, y: y - h + b, width: w, height: h };
|
||||
else if (r === 270) return { x: x - b, y: y - w + b, width: h, height: w };
|
||||
else return { x: x - b, y: y - b, width: w, height: h };
|
||||
};
|
||||
52
node_modules/pdf-lib/src/api/sizes.ts
generated
vendored
Normal file
52
node_modules/pdf-lib/src/api/sizes.ts
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
export const PageSizes = {
|
||||
'4A0': [4767.87, 6740.79] as [number, number],
|
||||
'2A0': [3370.39, 4767.87] as [number, number],
|
||||
A0: [2383.94, 3370.39] as [number, number],
|
||||
A1: [1683.78, 2383.94] as [number, number],
|
||||
A2: [1190.55, 1683.78] as [number, number],
|
||||
A3: [841.89, 1190.55] as [number, number],
|
||||
A4: [595.28, 841.89] as [number, number],
|
||||
A5: [419.53, 595.28] as [number, number],
|
||||
A6: [297.64, 419.53] as [number, number],
|
||||
A7: [209.76, 297.64] as [number, number],
|
||||
A8: [147.4, 209.76] as [number, number],
|
||||
A9: [104.88, 147.4] as [number, number],
|
||||
A10: [73.7, 104.88] as [number, number],
|
||||
B0: [2834.65, 4008.19] as [number, number],
|
||||
B1: [2004.09, 2834.65] as [number, number],
|
||||
B2: [1417.32, 2004.09] as [number, number],
|
||||
B3: [1000.63, 1417.32] as [number, number],
|
||||
B4: [708.66, 1000.63] as [number, number],
|
||||
B5: [498.9, 708.66] as [number, number],
|
||||
B6: [354.33, 498.9] as [number, number],
|
||||
B7: [249.45, 354.33] as [number, number],
|
||||
B8: [175.75, 249.45] as [number, number],
|
||||
B9: [124.72, 175.75] as [number, number],
|
||||
B10: [87.87, 124.72] as [number, number],
|
||||
C0: [2599.37, 3676.54] as [number, number],
|
||||
C1: [1836.85, 2599.37] as [number, number],
|
||||
C2: [1298.27, 1836.85] as [number, number],
|
||||
C3: [918.43, 1298.27] as [number, number],
|
||||
C4: [649.13, 918.43] as [number, number],
|
||||
C5: [459.21, 649.13] as [number, number],
|
||||
C6: [323.15, 459.21] as [number, number],
|
||||
C7: [229.61, 323.15] as [number, number],
|
||||
C8: [161.57, 229.61] as [number, number],
|
||||
C9: [113.39, 161.57] as [number, number],
|
||||
C10: [79.37, 113.39] as [number, number],
|
||||
RA0: [2437.8, 3458.27] as [number, number],
|
||||
RA1: [1729.13, 2437.8] as [number, number],
|
||||
RA2: [1218.9, 1729.13] as [number, number],
|
||||
RA3: [864.57, 1218.9] as [number, number],
|
||||
RA4: [609.45, 864.57] as [number, number],
|
||||
SRA0: [2551.18, 3628.35] as [number, number],
|
||||
SRA1: [1814.17, 2551.18] as [number, number],
|
||||
SRA2: [1275.59, 1814.17] as [number, number],
|
||||
SRA3: [907.09, 1275.59] as [number, number],
|
||||
SRA4: [637.8, 907.09] as [number, number],
|
||||
Executive: [521.86, 756.0] as [number, number],
|
||||
Folio: [612.0, 936.0] as [number, number],
|
||||
Legal: [612.0, 1008.0] as [number, number],
|
||||
Letter: [612.0, 792.0] as [number, number],
|
||||
Tabloid: [792.0, 1224.0] as [number, number],
|
||||
};
|
||||
489
node_modules/pdf-lib/src/api/svgPath.ts
generated
vendored
Normal file
489
node_modules/pdf-lib/src/api/svgPath.ts
generated
vendored
Normal file
@@ -0,0 +1,489 @@
|
||||
// Originated from pdfkit Copyright (c) 2014 Devon Govett
|
||||
// https://github.com/foliojs/pdfkit/blob/1e62e6ffe24b378eb890df507a47610f4c4a7b24/lib/path.js
|
||||
// MIT LICENSE
|
||||
// Updated for pdf-lib & TypeScript by Jeremy Messenger
|
||||
|
||||
import {
|
||||
appendBezierCurve,
|
||||
appendQuadraticCurve,
|
||||
closePath,
|
||||
lineTo,
|
||||
moveTo,
|
||||
} from 'src/api/operators';
|
||||
import { PDFOperator } from 'src/core';
|
||||
|
||||
let cx: number = 0;
|
||||
let cy: number = 0;
|
||||
let px: number | null = 0;
|
||||
let py: number | null = 0;
|
||||
let sx: number = 0;
|
||||
let sy: number = 0;
|
||||
|
||||
const parameters = new Map<string, number>([
|
||||
['A', 7],
|
||||
['a', 7],
|
||||
['C', 6],
|
||||
['c', 6],
|
||||
['H', 1],
|
||||
['h', 1],
|
||||
['L', 2],
|
||||
['l', 2],
|
||||
['M', 2],
|
||||
['m', 2],
|
||||
['Q', 4],
|
||||
['q', 4],
|
||||
['S', 4],
|
||||
['s', 4],
|
||||
['T', 2],
|
||||
['t', 2],
|
||||
['V', 1],
|
||||
['v', 1],
|
||||
['Z', 0],
|
||||
['z', 0],
|
||||
]);
|
||||
|
||||
interface Cmd {
|
||||
cmd?: string;
|
||||
args: number[];
|
||||
}
|
||||
|
||||
const parse = (path: string) => {
|
||||
let cmd;
|
||||
const ret: Cmd[] = [];
|
||||
let args: number[] = [];
|
||||
let curArg = '';
|
||||
let foundDecimal = false;
|
||||
let params = 0;
|
||||
|
||||
for (const c of path) {
|
||||
if (parameters.has(c)) {
|
||||
params = parameters.get(c)!;
|
||||
if (cmd) {
|
||||
// save existing command
|
||||
if (curArg.length > 0) {
|
||||
args[args.length] = +curArg;
|
||||
}
|
||||
ret[ret.length] = { cmd, args };
|
||||
|
||||
args = [];
|
||||
curArg = '';
|
||||
foundDecimal = false;
|
||||
}
|
||||
|
||||
cmd = c;
|
||||
} else if (
|
||||
[' ', ','].includes(c) ||
|
||||
(c === '-' && curArg.length > 0 && curArg[curArg.length - 1] !== 'e') ||
|
||||
(c === '.' && foundDecimal)
|
||||
) {
|
||||
if (curArg.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (args.length === params) {
|
||||
// handle reused commands
|
||||
ret[ret.length] = { cmd, args };
|
||||
args = [+curArg];
|
||||
|
||||
// handle assumed commands
|
||||
if (cmd === 'M') {
|
||||
cmd = 'L';
|
||||
}
|
||||
if (cmd === 'm') {
|
||||
cmd = 'l';
|
||||
}
|
||||
} else {
|
||||
args[args.length] = +curArg;
|
||||
}
|
||||
|
||||
foundDecimal = c === '.';
|
||||
|
||||
// fix for negative numbers or repeated decimals with no delimeter between commands
|
||||
curArg = ['-', '.'].includes(c) ? c : '';
|
||||
} else {
|
||||
curArg += c;
|
||||
if (c === '.') {
|
||||
foundDecimal = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add the last command
|
||||
if (curArg.length > 0) {
|
||||
if (args.length === params) {
|
||||
// handle reused commands
|
||||
ret[ret.length] = { cmd, args };
|
||||
args = [+curArg];
|
||||
|
||||
// handle assumed commands
|
||||
if (cmd === 'M') {
|
||||
cmd = 'L';
|
||||
}
|
||||
if (cmd === 'm') {
|
||||
cmd = 'l';
|
||||
}
|
||||
} else {
|
||||
args[args.length] = +curArg;
|
||||
}
|
||||
}
|
||||
|
||||
ret[ret.length] = { cmd, args };
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
const apply = (commands: Cmd[]) => {
|
||||
// current point, control point, and subpath starting point
|
||||
cx = cy = px = py = sx = sy = 0;
|
||||
|
||||
// run the commands
|
||||
let cmds: PDFOperator[] = [];
|
||||
for (let i = 0; i < commands.length; i++) {
|
||||
const c = commands[i];
|
||||
if (c.cmd && typeof runners[c.cmd] === 'function') {
|
||||
const cmd = runners[c.cmd](c.args);
|
||||
if (Array.isArray(cmd)) {
|
||||
cmds = cmds.concat(cmd);
|
||||
} else {
|
||||
cmds.push(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
return cmds;
|
||||
};
|
||||
|
||||
interface CmdToOperatorsMap {
|
||||
[cmd: string]: (a: number[]) => PDFOperator | PDFOperator[];
|
||||
}
|
||||
|
||||
const runners: CmdToOperatorsMap = {
|
||||
M(a) {
|
||||
cx = a[0];
|
||||
cy = a[1];
|
||||
px = py = null;
|
||||
sx = cx;
|
||||
sy = cy;
|
||||
return moveTo(cx, cy);
|
||||
},
|
||||
|
||||
m(a) {
|
||||
cx += a[0];
|
||||
cy += a[1];
|
||||
px = py = null;
|
||||
sx = cx;
|
||||
sy = cy;
|
||||
return moveTo(cx, cy);
|
||||
},
|
||||
|
||||
C(a) {
|
||||
cx = a[4];
|
||||
cy = a[5];
|
||||
px = a[2];
|
||||
py = a[3];
|
||||
return appendBezierCurve(a[0], a[1], a[2], a[3], a[4], a[5]);
|
||||
},
|
||||
|
||||
c(a) {
|
||||
const cmd = appendBezierCurve(
|
||||
a[0] + cx,
|
||||
a[1] + cy,
|
||||
a[2] + cx,
|
||||
a[3] + cy,
|
||||
a[4] + cx,
|
||||
a[5] + cy,
|
||||
);
|
||||
px = cx + a[2];
|
||||
py = cy + a[3];
|
||||
cx += a[4];
|
||||
cy += a[5];
|
||||
return cmd;
|
||||
},
|
||||
|
||||
S(a) {
|
||||
if (px === null || py === null) {
|
||||
px = cx;
|
||||
py = cy;
|
||||
}
|
||||
|
||||
const cmd = appendBezierCurve(
|
||||
cx - (px - cx),
|
||||
cy - (py - cy),
|
||||
a[0],
|
||||
a[1],
|
||||
a[2],
|
||||
a[3],
|
||||
);
|
||||
px = a[0];
|
||||
py = a[1];
|
||||
cx = a[2];
|
||||
cy = a[3];
|
||||
return cmd;
|
||||
},
|
||||
|
||||
s(a) {
|
||||
if (px === null || py === null) {
|
||||
px = cx;
|
||||
py = cy;
|
||||
}
|
||||
|
||||
const cmd = appendBezierCurve(
|
||||
cx - (px - cx),
|
||||
cy - (py - cy),
|
||||
cx + a[0],
|
||||
cy + a[1],
|
||||
cx + a[2],
|
||||
cy + a[3],
|
||||
);
|
||||
px = cx + a[0];
|
||||
py = cy + a[1];
|
||||
cx += a[2];
|
||||
cy += a[3];
|
||||
return cmd;
|
||||
},
|
||||
|
||||
Q(a) {
|
||||
px = a[0];
|
||||
py = a[1];
|
||||
cx = a[2];
|
||||
cy = a[3];
|
||||
return appendQuadraticCurve(a[0], a[1], cx, cy);
|
||||
},
|
||||
|
||||
q(a) {
|
||||
const cmd = appendQuadraticCurve(
|
||||
a[0] + cx,
|
||||
a[1] + cy,
|
||||
a[2] + cx,
|
||||
a[3] + cy,
|
||||
);
|
||||
px = cx + a[0];
|
||||
py = cy + a[1];
|
||||
cx += a[2];
|
||||
cy += a[3];
|
||||
return cmd;
|
||||
},
|
||||
|
||||
T(a) {
|
||||
if (px === null || py === null) {
|
||||
px = cx;
|
||||
py = cy;
|
||||
} else {
|
||||
px = cx - (px - cx);
|
||||
py = cy - (py - cy);
|
||||
}
|
||||
|
||||
const cmd = appendQuadraticCurve(px, py, a[0], a[1]);
|
||||
px = cx - (px - cx);
|
||||
py = cy - (py - cy);
|
||||
cx = a[0];
|
||||
cy = a[1];
|
||||
return cmd;
|
||||
},
|
||||
|
||||
t(a) {
|
||||
if (px === null || py === null) {
|
||||
px = cx;
|
||||
py = cy;
|
||||
} else {
|
||||
px = cx - (px - cx);
|
||||
py = cy - (py - cy);
|
||||
}
|
||||
|
||||
const cmd = appendQuadraticCurve(px, py, cx + a[0], cy + a[1]);
|
||||
cx += a[0];
|
||||
cy += a[1];
|
||||
return cmd;
|
||||
},
|
||||
|
||||
A(a) {
|
||||
const cmds = solveArc(cx, cy, a);
|
||||
cx = a[5];
|
||||
cy = a[6];
|
||||
return cmds;
|
||||
},
|
||||
|
||||
a(a) {
|
||||
a[5] += cx;
|
||||
a[6] += cy;
|
||||
const cmds = solveArc(cx, cy, a);
|
||||
cx = a[5];
|
||||
cy = a[6];
|
||||
return cmds;
|
||||
},
|
||||
|
||||
L(a) {
|
||||
cx = a[0];
|
||||
cy = a[1];
|
||||
px = py = null;
|
||||
return lineTo(cx, cy);
|
||||
},
|
||||
|
||||
l(a) {
|
||||
cx += a[0];
|
||||
cy += a[1];
|
||||
px = py = null;
|
||||
return lineTo(cx, cy);
|
||||
},
|
||||
|
||||
H(a) {
|
||||
cx = a[0];
|
||||
px = py = null;
|
||||
return lineTo(cx, cy);
|
||||
},
|
||||
|
||||
h(a) {
|
||||
cx += a[0];
|
||||
px = py = null;
|
||||
return lineTo(cx, cy);
|
||||
},
|
||||
|
||||
V(a) {
|
||||
cy = a[0];
|
||||
px = py = null;
|
||||
return lineTo(cx, cy);
|
||||
},
|
||||
|
||||
v(a) {
|
||||
cy += a[0];
|
||||
px = py = null;
|
||||
return lineTo(cx, cy);
|
||||
},
|
||||
|
||||
Z() {
|
||||
const cmd = closePath();
|
||||
cx = sx;
|
||||
cy = sy;
|
||||
return cmd;
|
||||
},
|
||||
|
||||
z() {
|
||||
const cmd = closePath();
|
||||
cx = sx;
|
||||
cy = sy;
|
||||
return cmd;
|
||||
},
|
||||
};
|
||||
|
||||
const solveArc = (x: number, y: number, coords: number[]) => {
|
||||
const [rx, ry, rot, large, sweep, ex, ey] = coords;
|
||||
const segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y);
|
||||
|
||||
const cmds: PDFOperator[] = [];
|
||||
for (const seg of segs) {
|
||||
const bez = segmentToBezier(...seg);
|
||||
cmds.push(appendBezierCurve(...bez));
|
||||
}
|
||||
return cmds;
|
||||
};
|
||||
|
||||
type Segment = [number, number, number, number, number, number, number, number];
|
||||
type Bezier = [number, number, number, number, number, number];
|
||||
|
||||
// from Inkscape svgtopdf, thanks!
|
||||
const arcToSegments = (
|
||||
x: number,
|
||||
y: number,
|
||||
rx: number,
|
||||
ry: number,
|
||||
large: number,
|
||||
sweep: number,
|
||||
rotateX: number,
|
||||
ox: number,
|
||||
oy: number,
|
||||
) => {
|
||||
const th = rotateX * (Math.PI / 180);
|
||||
const sinTh = Math.sin(th);
|
||||
const cosTh = Math.cos(th);
|
||||
rx = Math.abs(rx);
|
||||
ry = Math.abs(ry);
|
||||
px = cosTh * (ox - x) * 0.5 + sinTh * (oy - y) * 0.5;
|
||||
py = cosTh * (oy - y) * 0.5 - sinTh * (ox - x) * 0.5;
|
||||
let pl = (px * px) / (rx * rx) + (py * py) / (ry * ry);
|
||||
if (pl > 1) {
|
||||
pl = Math.sqrt(pl);
|
||||
rx *= pl;
|
||||
ry *= pl;
|
||||
}
|
||||
|
||||
const a00 = cosTh / rx;
|
||||
const a01 = sinTh / rx;
|
||||
const a10 = -sinTh / ry;
|
||||
const a11 = cosTh / ry;
|
||||
const x0 = a00 * ox + a01 * oy;
|
||||
const y0 = a10 * ox + a11 * oy;
|
||||
const x1 = a00 * x + a01 * y;
|
||||
const y1 = a10 * x + a11 * y;
|
||||
|
||||
const d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
|
||||
let sfactorSq = 1 / d - 0.25;
|
||||
if (sfactorSq < 0) {
|
||||
sfactorSq = 0;
|
||||
}
|
||||
let sfactor = Math.sqrt(sfactorSq);
|
||||
if (sweep === large) {
|
||||
sfactor = -sfactor;
|
||||
}
|
||||
|
||||
const xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
|
||||
const yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
|
||||
|
||||
const th0 = Math.atan2(y0 - yc, x0 - xc);
|
||||
const th1 = Math.atan2(y1 - yc, x1 - xc);
|
||||
|
||||
let thArc = th1 - th0;
|
||||
if (thArc < 0 && sweep === 1) {
|
||||
thArc += 2 * Math.PI;
|
||||
} else if (thArc > 0 && sweep === 0) {
|
||||
thArc -= 2 * Math.PI;
|
||||
}
|
||||
|
||||
const segments = Math.ceil(Math.abs(thArc / (Math.PI * 0.5 + 0.001)));
|
||||
const result: Segment[] = [];
|
||||
|
||||
for (let i = 0; i < segments; i++) {
|
||||
const th2 = th0 + (i * thArc) / segments;
|
||||
const th3 = th0 + ((i + 1) * thArc) / segments;
|
||||
result[i] = [xc, yc, th2, th3, rx, ry, sinTh, cosTh];
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const segmentToBezier = (
|
||||
cx1: number,
|
||||
cy1: number,
|
||||
th0: number,
|
||||
th1: number,
|
||||
rx: number,
|
||||
ry: number,
|
||||
sinTh: number,
|
||||
cosTh: number,
|
||||
) => {
|
||||
const a00 = cosTh * rx;
|
||||
const a01 = -sinTh * ry;
|
||||
const a10 = sinTh * rx;
|
||||
const a11 = cosTh * ry;
|
||||
|
||||
const thHalf = 0.5 * (th1 - th0);
|
||||
const t =
|
||||
((8 / 3) * Math.sin(thHalf * 0.5) * Math.sin(thHalf * 0.5)) /
|
||||
Math.sin(thHalf);
|
||||
const x1 = cx1 + Math.cos(th0) - t * Math.sin(th0);
|
||||
const y1 = cy1 + Math.sin(th0) + t * Math.cos(th0);
|
||||
const x3 = cx1 + Math.cos(th1);
|
||||
const y3 = cy1 + Math.sin(th1);
|
||||
const x2 = x3 + t * Math.sin(th1);
|
||||
const y2 = y3 - t * Math.cos(th1);
|
||||
|
||||
const result: Bezier = [
|
||||
a00 * x1 + a01 * y1,
|
||||
a10 * x1 + a11 * y1,
|
||||
a00 * x2 + a01 * y2,
|
||||
a10 * x2 + a11 * y2,
|
||||
a00 * x3 + a01 * y3,
|
||||
a10 * x3 + a11 * y3,
|
||||
];
|
||||
return result;
|
||||
};
|
||||
|
||||
export const svgPathToOperators = (path: string) => apply(parse(path));
|
||||
5
node_modules/pdf-lib/src/api/text/alignment.ts
generated
vendored
Normal file
5
node_modules/pdf-lib/src/api/text/alignment.ts
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum TextAlignment {
|
||||
Left = 0,
|
||||
Center = 1,
|
||||
Right = 2,
|
||||
}
|
||||
2
node_modules/pdf-lib/src/api/text/index.ts
generated
vendored
Normal file
2
node_modules/pdf-lib/src/api/text/index.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from 'src/api/text/alignment';
|
||||
export * from 'src/api/text/layout';
|
||||
343
node_modules/pdf-lib/src/api/text/layout.ts
generated
vendored
Normal file
343
node_modules/pdf-lib/src/api/text/layout.ts
generated
vendored
Normal file
@@ -0,0 +1,343 @@
|
||||
import PDFFont from 'src/api/PDFFont';
|
||||
import { CombedTextLayoutError } from 'src/api/errors';
|
||||
import { TextAlignment } from 'src/api/text/alignment';
|
||||
|
||||
import { PDFHexString } from 'src/core';
|
||||
import {
|
||||
cleanText,
|
||||
lineSplit,
|
||||
mergeLines,
|
||||
charAtIndex,
|
||||
charSplit,
|
||||
} from 'src/utils';
|
||||
|
||||
export interface TextPosition {
|
||||
text: string;
|
||||
encoded: PDFHexString;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface LayoutBounds {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
const MIN_FONT_SIZE = 4;
|
||||
const MAX_FONT_SIZE = 500;
|
||||
|
||||
const computeFontSize = (
|
||||
lines: string[],
|
||||
font: PDFFont,
|
||||
bounds: LayoutBounds,
|
||||
multiline: boolean = false,
|
||||
) => {
|
||||
let fontSize = MIN_FONT_SIZE;
|
||||
|
||||
while (fontSize < MAX_FONT_SIZE) {
|
||||
let linesUsed = 0;
|
||||
|
||||
for (
|
||||
let lineIdx = 0, lineLen = lines.length;
|
||||
lineIdx < lineLen;
|
||||
lineIdx++
|
||||
) {
|
||||
linesUsed += 1;
|
||||
|
||||
const line = lines[lineIdx];
|
||||
const words = line.split(' ');
|
||||
|
||||
// Layout the words using the current `fontSize`, line wrapping
|
||||
// whenever we reach the end of the current line.
|
||||
let spaceInLineRemaining = bounds.width;
|
||||
for (let idx = 0, len = words.length; idx < len; idx++) {
|
||||
const isLastWord = idx === len - 1;
|
||||
const word = isLastWord ? words[idx] : words[idx] + ' ';
|
||||
const widthOfWord = font.widthOfTextAtSize(word, fontSize);
|
||||
spaceInLineRemaining -= widthOfWord;
|
||||
if (spaceInLineRemaining <= 0) {
|
||||
linesUsed += 1;
|
||||
spaceInLineRemaining = bounds.width - widthOfWord;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return if we exceeded the allowed width
|
||||
if (!multiline && linesUsed > lines.length) return fontSize - 1;
|
||||
|
||||
const height = font.heightAtSize(fontSize);
|
||||
const lineHeight = height + height * 0.2;
|
||||
const totalHeight = lineHeight * linesUsed;
|
||||
|
||||
// Return if we exceeded the allowed height
|
||||
if (totalHeight > Math.abs(bounds.height)) return fontSize - 1;
|
||||
|
||||
fontSize += 1;
|
||||
}
|
||||
|
||||
return fontSize;
|
||||
};
|
||||
|
||||
const computeCombedFontSize = (
|
||||
line: string,
|
||||
font: PDFFont,
|
||||
bounds: LayoutBounds,
|
||||
cellCount: number,
|
||||
) => {
|
||||
const cellWidth = bounds.width / cellCount;
|
||||
const cellHeight = bounds.height;
|
||||
|
||||
let fontSize = MIN_FONT_SIZE;
|
||||
|
||||
const chars = charSplit(line);
|
||||
while (fontSize < MAX_FONT_SIZE) {
|
||||
for (let idx = 0, len = chars.length; idx < len; idx++) {
|
||||
const c = chars[idx];
|
||||
const tooLong = font.widthOfTextAtSize(c, fontSize) > cellWidth * 0.75;
|
||||
if (tooLong) return fontSize - 1;
|
||||
}
|
||||
|
||||
const height = font.heightAtSize(fontSize, { descender: false });
|
||||
if (height > cellHeight) return fontSize - 1;
|
||||
|
||||
fontSize += 1;
|
||||
}
|
||||
|
||||
return fontSize;
|
||||
};
|
||||
|
||||
export interface LayoutTextOptions {
|
||||
alignment: TextAlignment;
|
||||
fontSize?: number;
|
||||
font: PDFFont;
|
||||
bounds: LayoutBounds;
|
||||
}
|
||||
|
||||
export interface MultilineTextLayout {
|
||||
bounds: LayoutBounds;
|
||||
lines: TextPosition[];
|
||||
fontSize: number;
|
||||
lineHeight: number;
|
||||
}
|
||||
|
||||
const lastIndexOfWhitespace = (line: string) => {
|
||||
for (let idx = line.length; idx > 0; idx--) {
|
||||
if (/\s/.test(line[idx])) return idx;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const splitOutLines = (
|
||||
input: string,
|
||||
maxWidth: number,
|
||||
font: PDFFont,
|
||||
fontSize: number,
|
||||
) => {
|
||||
let lastWhitespaceIdx = input.length;
|
||||
while (lastWhitespaceIdx > 0) {
|
||||
const line = input.substring(0, lastWhitespaceIdx);
|
||||
const encoded = font.encodeText(line);
|
||||
const width = font.widthOfTextAtSize(line, fontSize);
|
||||
if (width < maxWidth) {
|
||||
const remainder = input.substring(lastWhitespaceIdx) || undefined;
|
||||
return { line, encoded, width, remainder };
|
||||
}
|
||||
lastWhitespaceIdx = lastIndexOfWhitespace(line) ?? 0;
|
||||
}
|
||||
|
||||
// We were unable to split the input enough to get a chunk that would fit
|
||||
// within the specified `maxWidth` so we'll just return everything
|
||||
return {
|
||||
line: input,
|
||||
encoded: font.encodeText(input),
|
||||
width: font.widthOfTextAtSize(input, fontSize),
|
||||
remainder: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
export const layoutMultilineText = (
|
||||
text: string,
|
||||
{ alignment, fontSize, font, bounds }: LayoutTextOptions,
|
||||
): MultilineTextLayout => {
|
||||
const lines = lineSplit(cleanText(text));
|
||||
|
||||
if (fontSize === undefined || fontSize === 0) {
|
||||
fontSize = computeFontSize(lines, font, bounds, true);
|
||||
}
|
||||
const height = font.heightAtSize(fontSize);
|
||||
const lineHeight = height + height * 0.2;
|
||||
|
||||
const textLines: TextPosition[] = [];
|
||||
|
||||
let minX = bounds.x;
|
||||
let minY = bounds.y;
|
||||
let maxX = bounds.x + bounds.width;
|
||||
let maxY = bounds.y + bounds.height;
|
||||
|
||||
let y = bounds.y + bounds.height;
|
||||
for (let idx = 0, len = lines.length; idx < len; idx++) {
|
||||
let prevRemainder: string | undefined = lines[idx];
|
||||
while (prevRemainder !== undefined) {
|
||||
const { line, encoded, width, remainder } = splitOutLines(
|
||||
prevRemainder,
|
||||
bounds.width,
|
||||
font,
|
||||
fontSize,
|
||||
);
|
||||
|
||||
// prettier-ignore
|
||||
const x = (
|
||||
alignment === TextAlignment.Left ? bounds.x
|
||||
: alignment === TextAlignment.Center ? bounds.x + (bounds.width / 2) - (width / 2)
|
||||
: alignment === TextAlignment.Right ? bounds.x + bounds.width - width
|
||||
: bounds.x
|
||||
);
|
||||
|
||||
y -= lineHeight;
|
||||
|
||||
if (x < minX) minX = x;
|
||||
if (y < minY) minY = y;
|
||||
if (x + width > maxX) maxX = x + width;
|
||||
if (y + height > maxY) maxY = y + height;
|
||||
|
||||
textLines.push({ text: line, encoded, width, height, x, y });
|
||||
|
||||
// Only trim lines that we had to split ourselves. So we won't trim lines
|
||||
// that the user provided themselves with whitespace.
|
||||
prevRemainder = remainder?.trim();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
fontSize,
|
||||
lineHeight,
|
||||
lines: textLines,
|
||||
bounds: {
|
||||
x: minX,
|
||||
y: minY,
|
||||
width: maxX - minX,
|
||||
height: maxY - minY,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export interface LayoutCombedTextOptions {
|
||||
fontSize?: number;
|
||||
font: PDFFont;
|
||||
bounds: LayoutBounds;
|
||||
cellCount: number;
|
||||
}
|
||||
|
||||
export interface CombedTextLayout {
|
||||
bounds: LayoutBounds;
|
||||
cells: TextPosition[];
|
||||
fontSize: number;
|
||||
}
|
||||
|
||||
export const layoutCombedText = (
|
||||
text: string,
|
||||
{ fontSize, font, bounds, cellCount }: LayoutCombedTextOptions,
|
||||
): CombedTextLayout => {
|
||||
const line = mergeLines(cleanText(text));
|
||||
|
||||
if (line.length > cellCount) {
|
||||
throw new CombedTextLayoutError(line.length, cellCount);
|
||||
}
|
||||
|
||||
if (fontSize === undefined || fontSize === 0) {
|
||||
fontSize = computeCombedFontSize(line, font, bounds, cellCount);
|
||||
}
|
||||
|
||||
const cellWidth = bounds.width / cellCount;
|
||||
|
||||
const height = font.heightAtSize(fontSize, { descender: false });
|
||||
const y = bounds.y + (bounds.height / 2 - height / 2);
|
||||
|
||||
const cells: TextPosition[] = [];
|
||||
|
||||
let minX = bounds.x;
|
||||
let minY = bounds.y;
|
||||
let maxX = bounds.x + bounds.width;
|
||||
let maxY = bounds.y + bounds.height;
|
||||
|
||||
let cellOffset = 0;
|
||||
let charOffset = 0;
|
||||
while (cellOffset < cellCount) {
|
||||
const [char, charLength] = charAtIndex(line, charOffset);
|
||||
|
||||
const encoded = font.encodeText(char);
|
||||
const width = font.widthOfTextAtSize(char, fontSize);
|
||||
|
||||
const cellCenter = bounds.x + (cellWidth * cellOffset + cellWidth / 2);
|
||||
const x = cellCenter - width / 2;
|
||||
|
||||
if (x < minX) minX = x;
|
||||
if (y < minY) minY = y;
|
||||
if (x + width > maxX) maxX = x + width;
|
||||
if (y + height > maxY) maxY = y + height;
|
||||
|
||||
cells.push({ text: line, encoded, width, height, x, y });
|
||||
|
||||
cellOffset += 1;
|
||||
charOffset += charLength;
|
||||
}
|
||||
|
||||
return {
|
||||
fontSize,
|
||||
cells,
|
||||
bounds: {
|
||||
x: minX,
|
||||
y: minY,
|
||||
width: maxX - minX,
|
||||
height: maxY - minY,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export interface LayoutSinglelineTextOptions {
|
||||
alignment: TextAlignment;
|
||||
fontSize?: number;
|
||||
font: PDFFont;
|
||||
bounds: LayoutBounds;
|
||||
}
|
||||
|
||||
export interface SinglelineTextLayout {
|
||||
bounds: LayoutBounds;
|
||||
line: TextPosition;
|
||||
fontSize: number;
|
||||
}
|
||||
|
||||
export const layoutSinglelineText = (
|
||||
text: string,
|
||||
{ alignment, fontSize, font, bounds }: LayoutSinglelineTextOptions,
|
||||
): SinglelineTextLayout => {
|
||||
const line = mergeLines(cleanText(text));
|
||||
|
||||
if (fontSize === undefined || fontSize === 0) {
|
||||
fontSize = computeFontSize([line], font, bounds);
|
||||
}
|
||||
|
||||
const encoded = font.encodeText(line);
|
||||
const width = font.widthOfTextAtSize(line, fontSize);
|
||||
const height = font.heightAtSize(fontSize, { descender: false });
|
||||
|
||||
// prettier-ignore
|
||||
const x = (
|
||||
alignment === TextAlignment.Left ? bounds.x
|
||||
: alignment === TextAlignment.Center ? bounds.x + (bounds.width / 2) - (width / 2)
|
||||
: alignment === TextAlignment.Right ? bounds.x + bounds.width - width
|
||||
: bounds.x
|
||||
);
|
||||
|
||||
const y = bounds.y + (bounds.height / 2 - height / 2);
|
||||
|
||||
return {
|
||||
fontSize,
|
||||
line: { text: line, encoded, width, height, x, y },
|
||||
bounds: { x, y, width, height },
|
||||
};
|
||||
};
|
||||
299
node_modules/pdf-lib/src/core/PDFContext.ts
generated
vendored
Normal file
299
node_modules/pdf-lib/src/core/PDFContext.ts
generated
vendored
Normal file
@@ -0,0 +1,299 @@
|
||||
import pako from 'pako';
|
||||
|
||||
import PDFHeader from 'src/core/document/PDFHeader';
|
||||
import { UnexpectedObjectTypeError } from 'src/core/errors';
|
||||
import PDFArray from 'src/core/objects/PDFArray';
|
||||
import PDFBool from 'src/core/objects/PDFBool';
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFHexString from 'src/core/objects/PDFHexString';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFNull from 'src/core/objects/PDFNull';
|
||||
import PDFNumber from 'src/core/objects/PDFNumber';
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
import PDFRawStream from 'src/core/objects/PDFRawStream';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFStream from 'src/core/objects/PDFStream';
|
||||
import PDFString from 'src/core/objects/PDFString';
|
||||
import PDFOperator from 'src/core/operators/PDFOperator';
|
||||
import Ops from 'src/core/operators/PDFOperatorNames';
|
||||
import PDFContentStream from 'src/core/structures/PDFContentStream';
|
||||
import { typedArrayFor } from 'src/utils';
|
||||
import { SimpleRNG } from 'src/utils/rng';
|
||||
|
||||
type LookupKey = PDFRef | PDFObject | undefined;
|
||||
|
||||
interface LiteralObject {
|
||||
[name: string]: Literal | PDFObject;
|
||||
}
|
||||
|
||||
interface LiteralArray {
|
||||
[index: number]: Literal | PDFObject;
|
||||
}
|
||||
|
||||
type Literal =
|
||||
| LiteralObject
|
||||
| LiteralArray
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
| undefined;
|
||||
|
||||
const byAscendingObjectNumber = (
|
||||
[a]: [PDFRef, PDFObject],
|
||||
[b]: [PDFRef, PDFObject],
|
||||
) => a.objectNumber - b.objectNumber;
|
||||
|
||||
class PDFContext {
|
||||
static create = () => new PDFContext();
|
||||
|
||||
largestObjectNumber: number;
|
||||
header: PDFHeader;
|
||||
trailerInfo: {
|
||||
Root?: PDFObject;
|
||||
Encrypt?: PDFObject;
|
||||
Info?: PDFObject;
|
||||
ID?: PDFObject;
|
||||
};
|
||||
rng: SimpleRNG;
|
||||
|
||||
private readonly indirectObjects: Map<PDFRef, PDFObject>;
|
||||
|
||||
private pushGraphicsStateContentStreamRef?: PDFRef;
|
||||
private popGraphicsStateContentStreamRef?: PDFRef;
|
||||
|
||||
private constructor() {
|
||||
this.largestObjectNumber = 0;
|
||||
this.header = PDFHeader.forVersion(1, 7);
|
||||
this.trailerInfo = {};
|
||||
|
||||
this.indirectObjects = new Map();
|
||||
this.rng = SimpleRNG.withSeed(1);
|
||||
}
|
||||
|
||||
assign(ref: PDFRef, object: PDFObject): void {
|
||||
this.indirectObjects.set(ref, object);
|
||||
if (ref.objectNumber > this.largestObjectNumber) {
|
||||
this.largestObjectNumber = ref.objectNumber;
|
||||
}
|
||||
}
|
||||
|
||||
nextRef(): PDFRef {
|
||||
this.largestObjectNumber += 1;
|
||||
return PDFRef.of(this.largestObjectNumber);
|
||||
}
|
||||
|
||||
register(object: PDFObject): PDFRef {
|
||||
const ref = this.nextRef();
|
||||
this.assign(ref, object);
|
||||
return ref;
|
||||
}
|
||||
|
||||
delete(ref: PDFRef): boolean {
|
||||
return this.indirectObjects.delete(ref);
|
||||
}
|
||||
|
||||
lookupMaybe(ref: LookupKey, type: typeof PDFArray): PDFArray | undefined;
|
||||
lookupMaybe(ref: LookupKey, type: typeof PDFBool): PDFBool | undefined;
|
||||
lookupMaybe(ref: LookupKey, type: typeof PDFDict): PDFDict | undefined;
|
||||
lookupMaybe(
|
||||
ref: LookupKey,
|
||||
type: typeof PDFHexString,
|
||||
): PDFHexString | undefined;
|
||||
lookupMaybe(ref: LookupKey, type: typeof PDFName): PDFName | undefined;
|
||||
lookupMaybe(ref: LookupKey, type: typeof PDFNull): typeof PDFNull | undefined;
|
||||
lookupMaybe(ref: LookupKey, type: typeof PDFNumber): PDFNumber | undefined;
|
||||
lookupMaybe(ref: LookupKey, type: typeof PDFStream): PDFStream | undefined;
|
||||
lookupMaybe(ref: LookupKey, type: typeof PDFRef): PDFRef | undefined;
|
||||
lookupMaybe(ref: LookupKey, type: typeof PDFString): PDFString | undefined;
|
||||
lookupMaybe(
|
||||
ref: LookupKey,
|
||||
type1: typeof PDFString,
|
||||
type2: typeof PDFHexString,
|
||||
): PDFString | PDFHexString | undefined;
|
||||
|
||||
lookupMaybe(ref: LookupKey, ...types: any[]) {
|
||||
// TODO: `preservePDFNull` is for backwards compatibility. Should be
|
||||
// removed in next breaking API change.
|
||||
const preservePDFNull = types.includes(PDFNull);
|
||||
|
||||
const result = ref instanceof PDFRef ? this.indirectObjects.get(ref) : ref;
|
||||
|
||||
if (!result || (result === PDFNull && !preservePDFNull)) return undefined;
|
||||
|
||||
for (let idx = 0, len = types.length; idx < len; idx++) {
|
||||
const type = types[idx];
|
||||
if (type === PDFNull) {
|
||||
if (result === PDFNull) return result;
|
||||
} else {
|
||||
if (result instanceof type) return result;
|
||||
}
|
||||
}
|
||||
throw new UnexpectedObjectTypeError(types, result);
|
||||
}
|
||||
|
||||
lookup(ref: LookupKey): PDFObject | undefined;
|
||||
lookup(ref: LookupKey, type: typeof PDFArray): PDFArray;
|
||||
lookup(ref: LookupKey, type: typeof PDFBool): PDFBool;
|
||||
lookup(ref: LookupKey, type: typeof PDFDict): PDFDict;
|
||||
lookup(ref: LookupKey, type: typeof PDFHexString): PDFHexString;
|
||||
lookup(ref: LookupKey, type: typeof PDFName): PDFName;
|
||||
lookup(ref: LookupKey, type: typeof PDFNull): typeof PDFNull;
|
||||
lookup(ref: LookupKey, type: typeof PDFNumber): PDFNumber;
|
||||
lookup(ref: LookupKey, type: typeof PDFStream): PDFStream;
|
||||
lookup(ref: LookupKey, type: typeof PDFRef): PDFRef;
|
||||
lookup(ref: LookupKey, type: typeof PDFString): PDFString;
|
||||
lookup(
|
||||
ref: LookupKey,
|
||||
type1: typeof PDFString,
|
||||
type2: typeof PDFHexString,
|
||||
): PDFString | PDFHexString;
|
||||
|
||||
lookup(ref: LookupKey, ...types: any[]) {
|
||||
const result = ref instanceof PDFRef ? this.indirectObjects.get(ref) : ref;
|
||||
|
||||
if (types.length === 0) return result;
|
||||
|
||||
for (let idx = 0, len = types.length; idx < len; idx++) {
|
||||
const type = types[idx];
|
||||
if (type === PDFNull) {
|
||||
if (result === PDFNull) return result;
|
||||
} else {
|
||||
if (result instanceof type) return result;
|
||||
}
|
||||
}
|
||||
|
||||
throw new UnexpectedObjectTypeError(types, result);
|
||||
}
|
||||
|
||||
getObjectRef(pdfObject: PDFObject): PDFRef | undefined {
|
||||
const entries = Array.from(this.indirectObjects.entries());
|
||||
for (let idx = 0, len = entries.length; idx < len; idx++) {
|
||||
const [ref, object] = entries[idx];
|
||||
if (object === pdfObject) {
|
||||
return ref;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
enumerateIndirectObjects(): [PDFRef, PDFObject][] {
|
||||
return Array.from(this.indirectObjects.entries()).sort(
|
||||
byAscendingObjectNumber,
|
||||
);
|
||||
}
|
||||
|
||||
obj(literal: null | undefined): typeof PDFNull;
|
||||
obj(literal: string): PDFName;
|
||||
obj(literal: number): PDFNumber;
|
||||
obj(literal: boolean): PDFBool;
|
||||
obj(literal: LiteralObject): PDFDict;
|
||||
obj(literal: LiteralArray): PDFArray;
|
||||
|
||||
obj(literal: Literal) {
|
||||
if (literal instanceof PDFObject) {
|
||||
return literal;
|
||||
} else if (literal === null || literal === undefined) {
|
||||
return PDFNull;
|
||||
} else if (typeof literal === 'string') {
|
||||
return PDFName.of(literal);
|
||||
} else if (typeof literal === 'number') {
|
||||
return PDFNumber.of(literal);
|
||||
} else if (typeof literal === 'boolean') {
|
||||
return literal ? PDFBool.True : PDFBool.False;
|
||||
} else if (Array.isArray(literal)) {
|
||||
const array = PDFArray.withContext(this);
|
||||
for (let idx = 0, len = literal.length; idx < len; idx++) {
|
||||
array.push(this.obj(literal[idx]));
|
||||
}
|
||||
return array;
|
||||
} else {
|
||||
const dict = PDFDict.withContext(this);
|
||||
const keys = Object.keys(literal);
|
||||
for (let idx = 0, len = keys.length; idx < len; idx++) {
|
||||
const key = keys[idx];
|
||||
const value = (literal as LiteralObject)[key] as any;
|
||||
if (value !== undefined) dict.set(PDFName.of(key), this.obj(value));
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
|
||||
stream(
|
||||
contents: string | Uint8Array,
|
||||
dict: LiteralObject = {},
|
||||
): PDFRawStream {
|
||||
return PDFRawStream.of(this.obj(dict), typedArrayFor(contents));
|
||||
}
|
||||
|
||||
flateStream(
|
||||
contents: string | Uint8Array,
|
||||
dict: LiteralObject = {},
|
||||
): PDFRawStream {
|
||||
return this.stream(pako.deflate(typedArrayFor(contents)), {
|
||||
...dict,
|
||||
Filter: 'FlateDecode',
|
||||
});
|
||||
}
|
||||
|
||||
contentStream(
|
||||
operators: PDFOperator[],
|
||||
dict: LiteralObject = {},
|
||||
): PDFContentStream {
|
||||
return PDFContentStream.of(this.obj(dict), operators);
|
||||
}
|
||||
|
||||
formXObject(
|
||||
operators: PDFOperator[],
|
||||
dict: LiteralObject = {},
|
||||
): PDFContentStream {
|
||||
return this.contentStream(operators, {
|
||||
BBox: this.obj([0, 0, 0, 0]),
|
||||
Matrix: this.obj([1, 0, 0, 1, 0, 0]),
|
||||
...dict,
|
||||
Type: 'XObject',
|
||||
Subtype: 'Form',
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Reference to PDFContentStream that contains a single PDFOperator: `q`.
|
||||
* Used by [[PDFPageLeaf]] instances to ensure that when content streams are
|
||||
* added to a modified PDF, they start in the default, unchanged graphics
|
||||
* state.
|
||||
*/
|
||||
getPushGraphicsStateContentStream(): PDFRef {
|
||||
if (this.pushGraphicsStateContentStreamRef) {
|
||||
return this.pushGraphicsStateContentStreamRef;
|
||||
}
|
||||
const dict = this.obj({});
|
||||
const op = PDFOperator.of(Ops.PushGraphicsState);
|
||||
const stream = PDFContentStream.of(dict, [op]);
|
||||
this.pushGraphicsStateContentStreamRef = this.register(stream);
|
||||
return this.pushGraphicsStateContentStreamRef;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reference to PDFContentStream that contains a single PDFOperator: `Q`.
|
||||
* Used by [[PDFPageLeaf]] instances to ensure that when content streams are
|
||||
* added to a modified PDF, they start in the default, unchanged graphics
|
||||
* state.
|
||||
*/
|
||||
getPopGraphicsStateContentStream(): PDFRef {
|
||||
if (this.popGraphicsStateContentStreamRef) {
|
||||
return this.popGraphicsStateContentStreamRef;
|
||||
}
|
||||
const dict = this.obj({});
|
||||
const op = PDFOperator.of(Ops.PopGraphicsState);
|
||||
const stream = PDFContentStream.of(dict, [op]);
|
||||
this.popGraphicsStateContentStreamRef = this.register(stream);
|
||||
return this.popGraphicsStateContentStreamRef;
|
||||
}
|
||||
|
||||
addRandomSuffix(prefix: string, suffixLength = 4): string {
|
||||
return `${prefix}-${Math.floor(this.rng.nextInt() * 10 ** suffixLength)}`;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFContext;
|
||||
143
node_modules/pdf-lib/src/core/PDFObjectCopier.ts
generated
vendored
Normal file
143
node_modules/pdf-lib/src/core/PDFObjectCopier.ts
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
import PDFArray from 'src/core/objects/PDFArray';
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFStream from 'src/core/objects/PDFStream';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import PDFPageLeaf from 'src/core/structures/PDFPageLeaf';
|
||||
|
||||
/**
|
||||
* PDFObjectCopier copies PDFObjects from a src context to a dest context.
|
||||
* The primary use case for this is to copy pages between PDFs.
|
||||
*
|
||||
* _Copying_ an object with a PDFObjectCopier is different from _cloning_ an
|
||||
* object with its [[PDFObject.clone]] method:
|
||||
*
|
||||
* ```
|
||||
* const src: PDFContext = ...
|
||||
* const dest: PDFContext = ...
|
||||
* const originalObject: PDFObject = ...
|
||||
* const copiedObject = PDFObjectCopier.for(src, dest).copy(originalObject);
|
||||
* const clonedObject = originalObject.clone();
|
||||
* ```
|
||||
*
|
||||
* Copying an object is equivalent to cloning it and then copying over any other
|
||||
* objects that it references. Note that only dictionaries, arrays, and streams
|
||||
* (or structures build from them) can contain indirect references to other
|
||||
* objects. Copying a PDFObject that is not a dictionary, array, or stream is
|
||||
* supported, but is equivalent to cloning it.
|
||||
*/
|
||||
class PDFObjectCopier {
|
||||
static for = (src: PDFContext, dest: PDFContext) =>
|
||||
new PDFObjectCopier(src, dest);
|
||||
|
||||
private readonly src: PDFContext;
|
||||
private readonly dest: PDFContext;
|
||||
private readonly traversedObjects = new Map<PDFObject, PDFObject>();
|
||||
|
||||
private constructor(src: PDFContext, dest: PDFContext) {
|
||||
this.src = src;
|
||||
this.dest = dest;
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
copy = <T extends PDFObject>(object: T): T => (
|
||||
object instanceof PDFPageLeaf ? this.copyPDFPage(object)
|
||||
: object instanceof PDFDict ? this.copyPDFDict(object)
|
||||
: object instanceof PDFArray ? this.copyPDFArray(object)
|
||||
: object instanceof PDFStream ? this.copyPDFStream(object)
|
||||
: object instanceof PDFRef ? this.copyPDFIndirectObject(object)
|
||||
: object.clone()
|
||||
) as T;
|
||||
|
||||
private copyPDFPage = (originalPage: PDFPageLeaf): PDFPageLeaf => {
|
||||
const clonedPage = originalPage.clone();
|
||||
|
||||
// Move any entries that the originalPage is inheriting from its parent
|
||||
// tree nodes directly into originalPage so they are preserved during
|
||||
// the copy.
|
||||
const { InheritableEntries } = PDFPageLeaf;
|
||||
for (let idx = 0, len = InheritableEntries.length; idx < len; idx++) {
|
||||
const key = PDFName.of(InheritableEntries[idx]);
|
||||
const value = clonedPage.getInheritableAttribute(key)!;
|
||||
if (!clonedPage.get(key) && value) clonedPage.set(key, value);
|
||||
}
|
||||
|
||||
// Remove the parent reference to prevent the whole donor document's page
|
||||
// tree from being copied when we only need a single page.
|
||||
clonedPage.delete(PDFName.of('Parent'));
|
||||
|
||||
return this.copyPDFDict(clonedPage) as PDFPageLeaf;
|
||||
};
|
||||
|
||||
private copyPDFDict = (originalDict: PDFDict): PDFDict => {
|
||||
if (this.traversedObjects.has(originalDict)) {
|
||||
return this.traversedObjects.get(originalDict) as PDFDict;
|
||||
}
|
||||
|
||||
const clonedDict = originalDict.clone(this.dest);
|
||||
this.traversedObjects.set(originalDict, clonedDict);
|
||||
|
||||
const entries = originalDict.entries();
|
||||
|
||||
for (let idx = 0, len = entries.length; idx < len; idx++) {
|
||||
const [key, value] = entries[idx];
|
||||
clonedDict.set(key, this.copy(value));
|
||||
}
|
||||
|
||||
return clonedDict;
|
||||
};
|
||||
|
||||
private copyPDFArray = (originalArray: PDFArray): PDFArray => {
|
||||
if (this.traversedObjects.has(originalArray)) {
|
||||
return this.traversedObjects.get(originalArray) as PDFArray;
|
||||
}
|
||||
|
||||
const clonedArray = originalArray.clone(this.dest);
|
||||
this.traversedObjects.set(originalArray, clonedArray);
|
||||
|
||||
for (let idx = 0, len = originalArray.size(); idx < len; idx++) {
|
||||
const value = originalArray.get(idx);
|
||||
clonedArray.set(idx, this.copy(value));
|
||||
}
|
||||
|
||||
return clonedArray;
|
||||
};
|
||||
|
||||
private copyPDFStream = (originalStream: PDFStream): PDFStream => {
|
||||
if (this.traversedObjects.has(originalStream)) {
|
||||
return this.traversedObjects.get(originalStream) as PDFStream;
|
||||
}
|
||||
|
||||
const clonedStream = originalStream.clone(this.dest);
|
||||
this.traversedObjects.set(originalStream, clonedStream);
|
||||
|
||||
const entries = originalStream.dict.entries();
|
||||
for (let idx = 0, len = entries.length; idx < len; idx++) {
|
||||
const [key, value] = entries[idx];
|
||||
clonedStream.dict.set(key, this.copy(value));
|
||||
}
|
||||
|
||||
return clonedStream;
|
||||
};
|
||||
|
||||
private copyPDFIndirectObject = (ref: PDFRef): PDFRef => {
|
||||
const alreadyMapped = this.traversedObjects.has(ref);
|
||||
|
||||
if (!alreadyMapped) {
|
||||
const newRef = this.dest.nextRef();
|
||||
this.traversedObjects.set(ref, newRef);
|
||||
|
||||
const dereferencedValue = this.src.lookup(ref);
|
||||
if (dereferencedValue) {
|
||||
const cloned = this.copy(dereferencedValue);
|
||||
this.dest.assign(newRef, cloned);
|
||||
}
|
||||
}
|
||||
|
||||
return this.traversedObjects.get(ref) as PDFRef;
|
||||
};
|
||||
}
|
||||
|
||||
export default PDFObjectCopier;
|
||||
114
node_modules/pdf-lib/src/core/acroform/PDFAcroButton.ts
generated
vendored
Normal file
114
node_modules/pdf-lib/src/core/acroform/PDFAcroButton.ts
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
import PDFString from 'src/core/objects/PDFString';
|
||||
import PDFHexString from 'src/core/objects/PDFHexString';
|
||||
import PDFArray from 'src/core/objects/PDFArray';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFAcroTerminal from 'src/core/acroform/PDFAcroTerminal';
|
||||
import { IndexOutOfBoundsError } from 'src/core/errors';
|
||||
|
||||
class PDFAcroButton extends PDFAcroTerminal {
|
||||
Opt(): PDFString | PDFHexString | PDFArray | undefined {
|
||||
return this.dict.lookupMaybe(
|
||||
PDFName.of('Opt'),
|
||||
PDFString,
|
||||
PDFHexString,
|
||||
PDFArray,
|
||||
);
|
||||
}
|
||||
|
||||
setOpt(opt: PDFObject[]) {
|
||||
this.dict.set(PDFName.of('Opt'), this.dict.context.obj(opt));
|
||||
}
|
||||
|
||||
getExportValues(): (PDFString | PDFHexString)[] | undefined {
|
||||
const opt = this.Opt();
|
||||
|
||||
if (!opt) return undefined;
|
||||
|
||||
if (opt instanceof PDFString || opt instanceof PDFHexString) {
|
||||
return [opt];
|
||||
}
|
||||
|
||||
const values: (PDFString | PDFHexString)[] = [];
|
||||
for (let idx = 0, len = opt.size(); idx < len; idx++) {
|
||||
const value = opt.lookup(idx);
|
||||
if (value instanceof PDFString || value instanceof PDFHexString) {
|
||||
values.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
removeExportValue(idx: number) {
|
||||
const opt = this.Opt();
|
||||
|
||||
if (!opt) return;
|
||||
|
||||
if (opt instanceof PDFString || opt instanceof PDFHexString) {
|
||||
if (idx !== 0) throw new IndexOutOfBoundsError(idx, 0, 0);
|
||||
this.setOpt([]);
|
||||
} else {
|
||||
if (idx < 0 || idx > opt.size()) {
|
||||
throw new IndexOutOfBoundsError(idx, 0, opt.size());
|
||||
}
|
||||
opt.remove(idx);
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce use use of /Opt even if it isn't strictly necessary
|
||||
normalizeExportValues() {
|
||||
const exportValues = this.getExportValues() ?? [];
|
||||
|
||||
const Opt: (PDFString | PDFHexString)[] = [];
|
||||
|
||||
const widgets = this.getWidgets();
|
||||
for (let idx = 0, len = widgets.length; idx < len; idx++) {
|
||||
const widget = widgets[idx];
|
||||
const exportVal =
|
||||
exportValues[idx] ??
|
||||
PDFHexString.fromText(widget.getOnValue()?.decodeText() ?? '');
|
||||
Opt.push(exportVal);
|
||||
}
|
||||
|
||||
this.setOpt(Opt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reuses existing opt if one exists with the same value (assuming
|
||||
* `useExistingIdx` is `true`). Returns index of existing (or new) opt.
|
||||
*/
|
||||
addOpt(opt: PDFHexString | PDFString, useExistingOptIdx: boolean): number {
|
||||
this.normalizeExportValues();
|
||||
|
||||
const optText = opt.decodeText();
|
||||
|
||||
let existingIdx: number | undefined;
|
||||
if (useExistingOptIdx) {
|
||||
const exportValues = this.getExportValues() ?? [];
|
||||
for (let idx = 0, len = exportValues.length; idx < len; idx++) {
|
||||
const exportVal = exportValues[idx];
|
||||
if (exportVal.decodeText() === optText) existingIdx = idx;
|
||||
}
|
||||
}
|
||||
|
||||
const Opt = this.Opt() as PDFArray;
|
||||
Opt.push(opt);
|
||||
|
||||
return existingIdx ?? Opt.size() - 1;
|
||||
}
|
||||
|
||||
addWidgetWithOpt(
|
||||
widget: PDFRef,
|
||||
opt: PDFHexString | PDFString,
|
||||
useExistingOptIdx: boolean,
|
||||
) {
|
||||
const optIdx = this.addOpt(opt, useExistingOptIdx);
|
||||
const apStateValue = PDFName.of(String(optIdx));
|
||||
this.addWidget(widget);
|
||||
return apStateValue;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFAcroButton;
|
||||
49
node_modules/pdf-lib/src/core/acroform/PDFAcroCheckBox.ts
generated
vendored
Normal file
49
node_modules/pdf-lib/src/core/acroform/PDFAcroCheckBox.ts
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFAcroButton from 'src/core/acroform/PDFAcroButton';
|
||||
import { InvalidAcroFieldValueError } from 'src/core/errors';
|
||||
|
||||
class PDFAcroCheckBox extends PDFAcroButton {
|
||||
static fromDict = (dict: PDFDict, ref: PDFRef) =>
|
||||
new PDFAcroCheckBox(dict, ref);
|
||||
|
||||
static create = (context: PDFContext) => {
|
||||
const dict = context.obj({
|
||||
FT: 'Btn',
|
||||
Kids: [],
|
||||
});
|
||||
const ref = context.register(dict);
|
||||
return new PDFAcroCheckBox(dict, ref);
|
||||
};
|
||||
|
||||
setValue(value: PDFName) {
|
||||
const onValue = this.getOnValue() ?? PDFName.of('Yes');
|
||||
if (value !== onValue && value !== PDFName.of('Off')) {
|
||||
throw new InvalidAcroFieldValueError();
|
||||
}
|
||||
|
||||
this.dict.set(PDFName.of('V'), value);
|
||||
|
||||
const widgets = this.getWidgets();
|
||||
for (let idx = 0, len = widgets.length; idx < len; idx++) {
|
||||
const widget = widgets[idx];
|
||||
const state = widget.getOnValue() === value ? value : PDFName.of('Off');
|
||||
widget.setAppearanceState(state);
|
||||
}
|
||||
}
|
||||
|
||||
getValue(): PDFName {
|
||||
const v = this.V();
|
||||
if (v instanceof PDFName) return v;
|
||||
return PDFName.of('Off');
|
||||
}
|
||||
|
||||
getOnValue(): PDFName | undefined {
|
||||
const [widget] = this.getWidgets();
|
||||
return widget?.getOnValue();
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFAcroCheckBox;
|
||||
153
node_modules/pdf-lib/src/core/acroform/PDFAcroChoice.ts
generated
vendored
Normal file
153
node_modules/pdf-lib/src/core/acroform/PDFAcroChoice.ts
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
import PDFAcroTerminal from 'src/core/acroform/PDFAcroTerminal';
|
||||
import PDFHexString from 'src/core/objects/PDFHexString';
|
||||
import PDFString from 'src/core/objects/PDFString';
|
||||
import PDFArray from 'src/core/objects/PDFArray';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import { AcroChoiceFlags } from 'src/core/acroform/flags';
|
||||
import {
|
||||
InvalidAcroFieldValueError,
|
||||
MultiSelectValueError,
|
||||
} from 'src/core/errors';
|
||||
|
||||
class PDFAcroChoice extends PDFAcroTerminal {
|
||||
setValues(values: (PDFString | PDFHexString)[]) {
|
||||
if (
|
||||
this.hasFlag(AcroChoiceFlags.Combo) &&
|
||||
!this.hasFlag(AcroChoiceFlags.Edit) &&
|
||||
!this.valuesAreValid(values)
|
||||
) {
|
||||
throw new InvalidAcroFieldValueError();
|
||||
}
|
||||
|
||||
if (values.length === 0) {
|
||||
this.dict.delete(PDFName.of('V'));
|
||||
}
|
||||
if (values.length === 1) {
|
||||
this.dict.set(PDFName.of('V'), values[0]);
|
||||
}
|
||||
if (values.length > 1) {
|
||||
if (!this.hasFlag(AcroChoiceFlags.MultiSelect)) {
|
||||
throw new MultiSelectValueError();
|
||||
}
|
||||
this.dict.set(PDFName.of('V'), this.dict.context.obj(values));
|
||||
}
|
||||
|
||||
this.updateSelectedIndices(values);
|
||||
}
|
||||
|
||||
valuesAreValid(values: (PDFString | PDFHexString)[]): boolean {
|
||||
const options = this.getOptions();
|
||||
for (let idx = 0, len = values.length; idx < len; idx++) {
|
||||
const val = values[idx].decodeText();
|
||||
if (!options.find((o) => val === (o.display || o.value).decodeText())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
updateSelectedIndices(values: (PDFString | PDFHexString)[]) {
|
||||
if (values.length > 1) {
|
||||
const indices = new Array<number>(values.length);
|
||||
const options = this.getOptions();
|
||||
for (let idx = 0, len = values.length; idx < len; idx++) {
|
||||
const val = values[idx].decodeText();
|
||||
indices[idx] = options.findIndex(
|
||||
(o) => val === (o.display || o.value).decodeText(),
|
||||
);
|
||||
}
|
||||
this.dict.set(PDFName.of('I'), this.dict.context.obj(indices.sort()));
|
||||
} else {
|
||||
this.dict.delete(PDFName.of('I'));
|
||||
}
|
||||
}
|
||||
|
||||
getValues(): (PDFString | PDFHexString)[] {
|
||||
const v = this.V();
|
||||
|
||||
if (v instanceof PDFString || v instanceof PDFHexString) return [v];
|
||||
|
||||
if (v instanceof PDFArray) {
|
||||
const values: (PDFString | PDFHexString)[] = [];
|
||||
|
||||
for (let idx = 0, len = v.size(); idx < len; idx++) {
|
||||
const value = v.lookup(idx);
|
||||
if (value instanceof PDFString || value instanceof PDFHexString) {
|
||||
values.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
Opt(): PDFArray | PDFString | PDFHexString | undefined {
|
||||
return this.dict.lookupMaybe(
|
||||
PDFName.of('Opt'),
|
||||
PDFString,
|
||||
PDFHexString,
|
||||
PDFArray,
|
||||
);
|
||||
}
|
||||
|
||||
setOptions(
|
||||
options: {
|
||||
value: PDFString | PDFHexString;
|
||||
display?: PDFString | PDFHexString;
|
||||
}[],
|
||||
) {
|
||||
const newOpt = new Array<PDFArray>(options.length);
|
||||
for (let idx = 0, len = options.length; idx < len; idx++) {
|
||||
const { value, display } = options[idx];
|
||||
newOpt[idx] = this.dict.context.obj([value, display || value]);
|
||||
}
|
||||
this.dict.set(PDFName.of('Opt'), this.dict.context.obj(newOpt));
|
||||
}
|
||||
|
||||
getOptions(): {
|
||||
value: PDFString | PDFHexString;
|
||||
display: PDFString | PDFHexString;
|
||||
}[] {
|
||||
const Opt = this.Opt();
|
||||
|
||||
// Not supposed to happen - Opt _should_ always be `PDFArray | undefined`
|
||||
if (Opt instanceof PDFString || Opt instanceof PDFHexString) {
|
||||
return [{ value: Opt, display: Opt }];
|
||||
}
|
||||
|
||||
if (Opt instanceof PDFArray) {
|
||||
const res: {
|
||||
value: PDFString | PDFHexString;
|
||||
display: PDFString | PDFHexString;
|
||||
}[] = [];
|
||||
|
||||
for (let idx = 0, len = Opt.size(); idx < len; idx++) {
|
||||
const item = Opt.lookup(idx);
|
||||
|
||||
// If `item` is a string, use that as both the export and text value
|
||||
if (item instanceof PDFString || item instanceof PDFHexString) {
|
||||
res.push({ value: item, display: item });
|
||||
}
|
||||
|
||||
// If `item` is an array of one, treat it the same as just a string,
|
||||
// if it's an array of two then `item[0]` is the export value and
|
||||
// `item[1]` is the text value
|
||||
if (item instanceof PDFArray) {
|
||||
if (item.size() > 0) {
|
||||
const first = item.lookup(0, PDFString, PDFHexString);
|
||||
const second = item.lookupMaybe(1, PDFString, PDFHexString);
|
||||
res.push({ value: first, display: second || first });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFAcroChoice;
|
||||
22
node_modules/pdf-lib/src/core/acroform/PDFAcroComboBox.ts
generated
vendored
Normal file
22
node_modules/pdf-lib/src/core/acroform/PDFAcroComboBox.ts
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFAcroChoice from 'src/core/acroform/PDFAcroChoice';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import { AcroChoiceFlags } from 'src/core/acroform/flags';
|
||||
|
||||
class PDFAcroComboBox extends PDFAcroChoice {
|
||||
static fromDict = (dict: PDFDict, ref: PDFRef) =>
|
||||
new PDFAcroComboBox(dict, ref);
|
||||
|
||||
static create = (context: PDFContext) => {
|
||||
const dict = context.obj({
|
||||
FT: 'Ch',
|
||||
Ff: AcroChoiceFlags.Combo,
|
||||
Kids: [],
|
||||
});
|
||||
const ref = context.register(dict);
|
||||
return new PDFAcroComboBox(dict, ref);
|
||||
};
|
||||
}
|
||||
|
||||
export default PDFAcroComboBox;
|
||||
167
node_modules/pdf-lib/src/core/acroform/PDFAcroField.ts
generated
vendored
Normal file
167
node_modules/pdf-lib/src/core/acroform/PDFAcroField.ts
generated
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFString from 'src/core/objects/PDFString';
|
||||
import PDFHexString from 'src/core/objects/PDFHexString';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
import PDFNumber from 'src/core/objects/PDFNumber';
|
||||
import PDFArray from 'src/core/objects/PDFArray';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import { findLastMatch } from 'src/utils';
|
||||
import { MissingDAEntryError, MissingTfOperatorError } from 'src/core/errors';
|
||||
|
||||
// Examples:
|
||||
// `/Helv 12 Tf` -> ['Helv', '12']
|
||||
// `/HeBo 8.00 Tf` -> ['HeBo', '8.00']
|
||||
// `/HeBo Tf` -> ['HeBo', undefined]
|
||||
const tfRegex = /\/([^\0\t\n\f\r\ ]+)[\0\t\n\f\r\ ]*(\d*\.\d+|\d+)?[\0\t\n\f\r\ ]+Tf/;
|
||||
|
||||
class PDFAcroField {
|
||||
readonly dict: PDFDict;
|
||||
readonly ref: PDFRef;
|
||||
|
||||
protected constructor(dict: PDFDict, ref: PDFRef) {
|
||||
this.dict = dict;
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
T(): PDFString | PDFHexString | undefined {
|
||||
return this.dict.lookupMaybe(PDFName.of('T'), PDFString, PDFHexString);
|
||||
}
|
||||
|
||||
Ff(): PDFNumber | undefined {
|
||||
const numberOrRef = this.getInheritableAttribute(PDFName.of('Ff'));
|
||||
return this.dict.context.lookupMaybe(numberOrRef, PDFNumber);
|
||||
}
|
||||
|
||||
V(): PDFObject | undefined {
|
||||
const valueOrRef = this.getInheritableAttribute(PDFName.of('V'));
|
||||
return this.dict.context.lookup(valueOrRef);
|
||||
}
|
||||
|
||||
Kids(): PDFArray | undefined {
|
||||
return this.dict.lookupMaybe(PDFName.of('Kids'), PDFArray);
|
||||
}
|
||||
|
||||
// Parent(): PDFDict | undefined {
|
||||
// return this.dict.lookupMaybe(PDFName.of('Parent'), PDFDict);
|
||||
// }
|
||||
|
||||
DA(): PDFString | PDFHexString | undefined {
|
||||
const da = this.dict.lookup(PDFName.of('DA'));
|
||||
if (da instanceof PDFString || da instanceof PDFHexString) return da;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
setKids(kids: PDFObject[]) {
|
||||
this.dict.set(PDFName.of('Kids'), this.dict.context.obj(kids));
|
||||
}
|
||||
|
||||
getParent(): PDFAcroField | undefined {
|
||||
// const parent = this.Parent();
|
||||
// if (!parent) return undefined;
|
||||
// return new PDFAcroField(parent);
|
||||
|
||||
const parentRef = this.dict.get(PDFName.of('Parent'));
|
||||
if (parentRef instanceof PDFRef) {
|
||||
const parent = this.dict.lookup(PDFName.of('Parent'), PDFDict);
|
||||
return new PDFAcroField(parent, parentRef);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
setParent(parent: PDFRef | undefined) {
|
||||
if (!parent) this.dict.delete(PDFName.of('Parent'));
|
||||
else this.dict.set(PDFName.of('Parent'), parent);
|
||||
}
|
||||
|
||||
getFullyQualifiedName(): string | undefined {
|
||||
const parent = this.getParent();
|
||||
if (!parent) return this.getPartialName();
|
||||
return `${parent.getFullyQualifiedName()}.${this.getPartialName()}`;
|
||||
}
|
||||
|
||||
getPartialName(): string | undefined {
|
||||
return this.T()?.decodeText();
|
||||
}
|
||||
|
||||
setPartialName(partialName: string | undefined) {
|
||||
if (!partialName) this.dict.delete(PDFName.of('T'));
|
||||
else this.dict.set(PDFName.of('T'), PDFHexString.fromText(partialName));
|
||||
}
|
||||
|
||||
setDefaultAppearance(appearance: string) {
|
||||
this.dict.set(PDFName.of('DA'), PDFString.of(appearance));
|
||||
}
|
||||
|
||||
getDefaultAppearance(): string | undefined {
|
||||
const DA = this.DA();
|
||||
|
||||
if (DA instanceof PDFHexString) {
|
||||
return DA.decodeText();
|
||||
}
|
||||
|
||||
return DA?.asString();
|
||||
}
|
||||
|
||||
setFontSize(fontSize: number) {
|
||||
const name = this.getFullyQualifiedName() ?? '';
|
||||
|
||||
const da = this.getDefaultAppearance();
|
||||
if (!da) throw new MissingDAEntryError(name);
|
||||
|
||||
const daMatch = findLastMatch(da, tfRegex);
|
||||
if (!daMatch.match) throw new MissingTfOperatorError(name);
|
||||
|
||||
const daStart = da.slice(0, daMatch.pos - daMatch.match[0].length);
|
||||
const daEnd = daMatch.pos <= da.length ? da.slice(daMatch.pos) : '';
|
||||
const fontName = daMatch.match[1];
|
||||
const modifiedDa = `${daStart} /${fontName} ${fontSize} Tf ${daEnd}`;
|
||||
|
||||
this.setDefaultAppearance(modifiedDa);
|
||||
}
|
||||
|
||||
getFlags(): number {
|
||||
return this.Ff()?.asNumber() ?? 0;
|
||||
}
|
||||
|
||||
setFlags(flags: number) {
|
||||
this.dict.set(PDFName.of('Ff'), PDFNumber.of(flags));
|
||||
}
|
||||
|
||||
hasFlag(flag: number): boolean {
|
||||
const flags = this.getFlags();
|
||||
return (flags & flag) !== 0;
|
||||
}
|
||||
|
||||
setFlag(flag: number) {
|
||||
const flags = this.getFlags();
|
||||
this.setFlags(flags | flag);
|
||||
}
|
||||
|
||||
clearFlag(flag: number) {
|
||||
const flags = this.getFlags();
|
||||
this.setFlags(flags & ~flag);
|
||||
}
|
||||
|
||||
setFlagTo(flag: number, enable: boolean) {
|
||||
if (enable) this.setFlag(flag);
|
||||
else this.clearFlag(flag);
|
||||
}
|
||||
|
||||
getInheritableAttribute(name: PDFName): PDFObject | undefined {
|
||||
let attribute: PDFObject | undefined;
|
||||
this.ascend((node) => {
|
||||
if (!attribute) attribute = node.dict.get(name);
|
||||
});
|
||||
return attribute;
|
||||
}
|
||||
|
||||
ascend(visitor: (node: PDFAcroField) => any): void {
|
||||
visitor(this);
|
||||
const parent = this.getParent();
|
||||
if (parent) parent.ascend(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFAcroField;
|
||||
102
node_modules/pdf-lib/src/core/acroform/PDFAcroForm.ts
generated
vendored
Normal file
102
node_modules/pdf-lib/src/core/acroform/PDFAcroForm.ts
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFArray from 'src/core/objects/PDFArray';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFAcroField from 'src/core/acroform/PDFAcroField';
|
||||
import PDFAcroNonTerminal from 'src/core/acroform/PDFAcroNonTerminal';
|
||||
import {
|
||||
createPDFAcroField,
|
||||
createPDFAcroFields,
|
||||
} from 'src/core/acroform/utils';
|
||||
|
||||
class PDFAcroForm {
|
||||
readonly dict: PDFDict;
|
||||
|
||||
static fromDict = (dict: PDFDict) => new PDFAcroForm(dict);
|
||||
|
||||
static create = (context: PDFContext) => {
|
||||
const dict = context.obj({ Fields: [] });
|
||||
return new PDFAcroForm(dict);
|
||||
};
|
||||
|
||||
private constructor(dict: PDFDict) {
|
||||
this.dict = dict;
|
||||
}
|
||||
|
||||
Fields(): PDFArray | undefined {
|
||||
const fields = this.dict.lookup(PDFName.of('Fields'));
|
||||
if (fields instanceof PDFArray) return fields;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getFields(): [PDFAcroField, PDFRef][] {
|
||||
const { Fields } = this.normalizedEntries();
|
||||
|
||||
const fields = new Array(Fields.size());
|
||||
for (let idx = 0, len = Fields.size(); idx < len; idx++) {
|
||||
const ref = Fields.get(idx) as PDFRef;
|
||||
const dict = Fields.lookup(idx, PDFDict);
|
||||
fields[idx] = [createPDFAcroField(dict, ref), ref];
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
getAllFields(): [PDFAcroField, PDFRef][] {
|
||||
const allFields: [PDFAcroField, PDFRef][] = [];
|
||||
|
||||
const pushFields = (fields?: [PDFAcroField, PDFRef][]) => {
|
||||
if (!fields) return;
|
||||
for (let idx = 0, len = fields.length; idx < len; idx++) {
|
||||
const field = fields[idx];
|
||||
allFields.push(field);
|
||||
const [fieldModel] = field;
|
||||
if (fieldModel instanceof PDFAcroNonTerminal) {
|
||||
pushFields(createPDFAcroFields(fieldModel.Kids()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pushFields(this.getFields());
|
||||
|
||||
return allFields;
|
||||
}
|
||||
|
||||
addField(field: PDFRef) {
|
||||
const { Fields } = this.normalizedEntries();
|
||||
Fields?.push(field);
|
||||
}
|
||||
|
||||
removeField(field: PDFAcroField): void {
|
||||
const parent = field.getParent();
|
||||
const fields =
|
||||
parent === undefined ? this.normalizedEntries().Fields : parent.Kids();
|
||||
|
||||
const index = fields?.indexOf(field.ref);
|
||||
if (fields === undefined || index === undefined) {
|
||||
throw new Error(
|
||||
`Tried to remove inexistent field ${field.getFullyQualifiedName()}`,
|
||||
);
|
||||
}
|
||||
|
||||
fields.remove(index);
|
||||
|
||||
if (parent !== undefined && fields.size() === 0) {
|
||||
this.removeField(parent);
|
||||
}
|
||||
}
|
||||
|
||||
normalizedEntries() {
|
||||
let Fields = this.Fields();
|
||||
|
||||
if (!Fields) {
|
||||
Fields = this.dict.context.obj([]);
|
||||
this.dict.set(PDFName.of('Fields'), Fields);
|
||||
}
|
||||
|
||||
return { Fields };
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFAcroForm;
|
||||
20
node_modules/pdf-lib/src/core/acroform/PDFAcroListBox.ts
generated
vendored
Normal file
20
node_modules/pdf-lib/src/core/acroform/PDFAcroListBox.ts
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFAcroChoice from 'src/core/acroform/PDFAcroChoice';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
|
||||
class PDFAcroListBox extends PDFAcroChoice {
|
||||
static fromDict = (dict: PDFDict, ref: PDFRef) =>
|
||||
new PDFAcroListBox(dict, ref);
|
||||
|
||||
static create = (context: PDFContext) => {
|
||||
const dict = context.obj({
|
||||
FT: 'Ch',
|
||||
Kids: [],
|
||||
});
|
||||
const ref = context.register(dict);
|
||||
return new PDFAcroListBox(dict, ref);
|
||||
};
|
||||
}
|
||||
|
||||
export default PDFAcroListBox;
|
||||
34
node_modules/pdf-lib/src/core/acroform/PDFAcroNonTerminal.ts
generated
vendored
Normal file
34
node_modules/pdf-lib/src/core/acroform/PDFAcroNonTerminal.ts
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import PDFAcroField from 'src/core/acroform/PDFAcroField';
|
||||
|
||||
class PDFAcroNonTerminal extends PDFAcroField {
|
||||
static fromDict = (dict: PDFDict, ref: PDFRef) =>
|
||||
new PDFAcroNonTerminal(dict, ref);
|
||||
|
||||
static create = (context: PDFContext) => {
|
||||
const dict = context.obj({});
|
||||
const ref = context.register(dict);
|
||||
return new PDFAcroNonTerminal(dict, ref);
|
||||
};
|
||||
|
||||
addField(field: PDFRef) {
|
||||
const { Kids } = this.normalizedEntries();
|
||||
Kids?.push(field);
|
||||
}
|
||||
|
||||
normalizedEntries() {
|
||||
let Kids = this.Kids();
|
||||
|
||||
if (!Kids) {
|
||||
Kids = this.dict.context.obj([]);
|
||||
this.dict.set(PDFName.of('Kids'), Kids);
|
||||
}
|
||||
|
||||
return { Kids };
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFAcroNonTerminal;
|
||||
22
node_modules/pdf-lib/src/core/acroform/PDFAcroPushButton.ts
generated
vendored
Normal file
22
node_modules/pdf-lib/src/core/acroform/PDFAcroPushButton.ts
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFAcroButton from 'src/core/acroform/PDFAcroButton';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import { AcroButtonFlags } from 'src/core/acroform/flags';
|
||||
|
||||
class PDFAcroPushButton extends PDFAcroButton {
|
||||
static fromDict = (dict: PDFDict, ref: PDFRef) =>
|
||||
new PDFAcroPushButton(dict, ref);
|
||||
|
||||
static create = (context: PDFContext) => {
|
||||
const dict = context.obj({
|
||||
FT: 'Btn',
|
||||
Ff: AcroButtonFlags.PushButton,
|
||||
Kids: [],
|
||||
});
|
||||
const ref = context.register(dict);
|
||||
return new PDFAcroPushButton(dict, ref);
|
||||
};
|
||||
}
|
||||
|
||||
export default PDFAcroPushButton;
|
||||
58
node_modules/pdf-lib/src/core/acroform/PDFAcroRadioButton.ts
generated
vendored
Normal file
58
node_modules/pdf-lib/src/core/acroform/PDFAcroRadioButton.ts
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFAcroButton from 'src/core/acroform/PDFAcroButton';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import { AcroButtonFlags } from 'src/core/acroform/flags';
|
||||
import { InvalidAcroFieldValueError } from 'src/core/errors';
|
||||
|
||||
class PDFAcroRadioButton extends PDFAcroButton {
|
||||
static fromDict = (dict: PDFDict, ref: PDFRef) =>
|
||||
new PDFAcroRadioButton(dict, ref);
|
||||
|
||||
static create = (context: PDFContext) => {
|
||||
const dict = context.obj({
|
||||
FT: 'Btn',
|
||||
Ff: AcroButtonFlags.Radio,
|
||||
Kids: [],
|
||||
});
|
||||
const ref = context.register(dict);
|
||||
return new PDFAcroRadioButton(dict, ref);
|
||||
};
|
||||
|
||||
setValue(value: PDFName) {
|
||||
const onValues = this.getOnValues();
|
||||
if (!onValues.includes(value) && value !== PDFName.of('Off')) {
|
||||
throw new InvalidAcroFieldValueError();
|
||||
}
|
||||
|
||||
this.dict.set(PDFName.of('V'), value);
|
||||
|
||||
const widgets = this.getWidgets();
|
||||
for (let idx = 0, len = widgets.length; idx < len; idx++) {
|
||||
const widget = widgets[idx];
|
||||
const state = widget.getOnValue() === value ? value : PDFName.of('Off');
|
||||
widget.setAppearanceState(state);
|
||||
}
|
||||
}
|
||||
|
||||
getValue(): PDFName {
|
||||
const v = this.V();
|
||||
if (v instanceof PDFName) return v;
|
||||
return PDFName.of('Off');
|
||||
}
|
||||
|
||||
getOnValues(): PDFName[] {
|
||||
const widgets = this.getWidgets();
|
||||
|
||||
const onValues: PDFName[] = [];
|
||||
for (let idx = 0, len = widgets.length; idx < len; idx++) {
|
||||
const onValue = widgets[idx].getOnValue();
|
||||
if (onValue) onValues.push(onValue);
|
||||
}
|
||||
|
||||
return onValues;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFAcroRadioButton;
|
||||
10
node_modules/pdf-lib/src/core/acroform/PDFAcroSignature.ts
generated
vendored
Normal file
10
node_modules/pdf-lib/src/core/acroform/PDFAcroSignature.ts
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFAcroTerminal from 'src/core/acroform/PDFAcroTerminal';
|
||||
|
||||
class PDFAcroSignature extends PDFAcroTerminal {
|
||||
static fromDict = (dict: PDFDict, ref: PDFRef) =>
|
||||
new PDFAcroSignature(dict, ref);
|
||||
}
|
||||
|
||||
export default PDFAcroSignature;
|
||||
71
node_modules/pdf-lib/src/core/acroform/PDFAcroTerminal.ts
generated
vendored
Normal file
71
node_modules/pdf-lib/src/core/acroform/PDFAcroTerminal.ts
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFAcroField from 'src/core/acroform/PDFAcroField';
|
||||
import PDFWidgetAnnotation from 'src/core/annotation/PDFWidgetAnnotation';
|
||||
import { IndexOutOfBoundsError } from 'src/core/errors';
|
||||
|
||||
class PDFAcroTerminal extends PDFAcroField {
|
||||
static fromDict = (dict: PDFDict, ref: PDFRef) =>
|
||||
new PDFAcroTerminal(dict, ref);
|
||||
|
||||
FT(): PDFName {
|
||||
const nameOrRef = this.getInheritableAttribute(PDFName.of('FT'));
|
||||
return this.dict.context.lookup(nameOrRef, PDFName);
|
||||
}
|
||||
|
||||
getWidgets(): PDFWidgetAnnotation[] {
|
||||
const kidDicts = this.Kids();
|
||||
|
||||
// This field is itself a widget
|
||||
if (!kidDicts) return [PDFWidgetAnnotation.fromDict(this.dict)];
|
||||
|
||||
// This field's kids are its widgets
|
||||
const widgets = new Array<PDFWidgetAnnotation>(kidDicts.size());
|
||||
for (let idx = 0, len = kidDicts.size(); idx < len; idx++) {
|
||||
const dict = kidDicts.lookup(idx, PDFDict);
|
||||
widgets[idx] = PDFWidgetAnnotation.fromDict(dict);
|
||||
}
|
||||
|
||||
return widgets;
|
||||
}
|
||||
|
||||
addWidget(ref: PDFRef) {
|
||||
const { Kids } = this.normalizedEntries();
|
||||
Kids.push(ref);
|
||||
}
|
||||
|
||||
removeWidget(idx: number) {
|
||||
const kidDicts = this.Kids();
|
||||
|
||||
if (!kidDicts) {
|
||||
// This field is itself a widget
|
||||
if (idx !== 0) throw new IndexOutOfBoundsError(idx, 0, 0);
|
||||
this.setKids([]);
|
||||
} else {
|
||||
// This field's kids are its widgets
|
||||
if (idx < 0 || idx > kidDicts.size()) {
|
||||
throw new IndexOutOfBoundsError(idx, 0, kidDicts.size());
|
||||
}
|
||||
kidDicts.remove(idx);
|
||||
}
|
||||
}
|
||||
|
||||
normalizedEntries() {
|
||||
let Kids = this.Kids();
|
||||
|
||||
// If this field is itself a widget (because it was only rendered once in
|
||||
// the document, so the field and widget properties were merged) then we
|
||||
// add itself to the `Kids` array. The alternative would be to try
|
||||
// splitting apart the widget properties and creating a separate object
|
||||
// for them.
|
||||
if (!Kids) {
|
||||
Kids = this.dict.context.obj([this.ref]);
|
||||
this.dict.set(PDFName.of('Kids'), Kids);
|
||||
}
|
||||
|
||||
return { Kids };
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFAcroTerminal;
|
||||
76
node_modules/pdf-lib/src/core/acroform/PDFAcroText.ts
generated
vendored
Normal file
76
node_modules/pdf-lib/src/core/acroform/PDFAcroText.ts
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFNumber from 'src/core/objects/PDFNumber';
|
||||
import PDFString from 'src/core/objects/PDFString';
|
||||
import PDFHexString from 'src/core/objects/PDFHexString';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFAcroTerminal from 'src/core/acroform/PDFAcroTerminal';
|
||||
|
||||
class PDFAcroText extends PDFAcroTerminal {
|
||||
static fromDict = (dict: PDFDict, ref: PDFRef) => new PDFAcroText(dict, ref);
|
||||
|
||||
static create = (context: PDFContext) => {
|
||||
const dict = context.obj({
|
||||
FT: 'Tx',
|
||||
Kids: [],
|
||||
});
|
||||
const ref = context.register(dict);
|
||||
return new PDFAcroText(dict, ref);
|
||||
};
|
||||
|
||||
MaxLen(): PDFNumber | undefined {
|
||||
const maxLen = this.dict.lookup(PDFName.of('MaxLen'));
|
||||
if (maxLen instanceof PDFNumber) return maxLen;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Q(): PDFNumber | undefined {
|
||||
const q = this.dict.lookup(PDFName.of('Q'));
|
||||
if (q instanceof PDFNumber) return q;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
setMaxLength(maxLength: number) {
|
||||
this.dict.set(PDFName.of('MaxLen'), PDFNumber.of(maxLength));
|
||||
}
|
||||
|
||||
removeMaxLength() {
|
||||
this.dict.delete(PDFName.of('MaxLen'));
|
||||
}
|
||||
|
||||
getMaxLength(): number | undefined {
|
||||
return this.MaxLen()?.asNumber();
|
||||
}
|
||||
|
||||
setQuadding(quadding: 0 | 1 | 2) {
|
||||
this.dict.set(PDFName.of('Q'), PDFNumber.of(quadding));
|
||||
}
|
||||
|
||||
getQuadding(): number | undefined {
|
||||
return this.Q()?.asNumber();
|
||||
}
|
||||
|
||||
setValue(value: PDFHexString | PDFString) {
|
||||
this.dict.set(PDFName.of('V'), value);
|
||||
|
||||
// const widgets = this.getWidgets();
|
||||
// for (let idx = 0, len = widgets.length; idx < len; idx++) {
|
||||
// const widget = widgets[idx];
|
||||
// const state = widget.getOnValue() === value ? value : PDFName.of('Off');
|
||||
// widget.setAppearanceState(state);
|
||||
// }
|
||||
}
|
||||
|
||||
removeValue() {
|
||||
this.dict.delete(PDFName.of('V'));
|
||||
}
|
||||
|
||||
getValue(): PDFString | PDFHexString | undefined {
|
||||
const v = this.V();
|
||||
if (v instanceof PDFString || v instanceof PDFHexString) return v;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFAcroText;
|
||||
162
node_modules/pdf-lib/src/core/acroform/flags.ts
generated
vendored
Normal file
162
node_modules/pdf-lib/src/core/acroform/flags.ts
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
const flag = (bitIndex: number) => 1 << bitIndex;
|
||||
|
||||
/** From PDF spec table 221 */
|
||||
export enum AcroFieldFlags {
|
||||
/**
|
||||
* If set, the user may not change the value of the field. Any associated
|
||||
* widget annotations will not interact with the user; that is, they will not
|
||||
* respond to mouse clicks or change their appearance in response to mouse
|
||||
* motions. This flag is useful for fields whose values are computed or
|
||||
* imported from a database.
|
||||
*/
|
||||
ReadOnly = flag(1 - 1),
|
||||
|
||||
/**
|
||||
* If set, the field shall have a value at the time it is exported by a
|
||||
* submit-form action (see 12.7.5.2, "Submit-Form Action").
|
||||
*/
|
||||
Required = flag(2 - 1),
|
||||
|
||||
/**
|
||||
* If set, the field shall not be exported by a submit-form action
|
||||
* (see 12.7.5.2, "Submit-Form Action").
|
||||
*/
|
||||
NoExport = flag(3 - 1),
|
||||
}
|
||||
|
||||
/** From PDF spec table 226 */
|
||||
export enum AcroButtonFlags {
|
||||
/**
|
||||
* (Radio buttons only) If set, exactly one radio button shall be selected at
|
||||
* all times; selecting the currently selected button has no effect. If clear,
|
||||
* clicking the selected button deselects it, leaving no button selected.
|
||||
*/
|
||||
NoToggleToOff = flag(15 - 1),
|
||||
|
||||
/**
|
||||
* If set, the field is a set of radio buttons; if clear, the field is a check
|
||||
* box. This flag may be set only if the Pushbutton flag is clear.
|
||||
*/
|
||||
Radio = flag(16 - 1),
|
||||
|
||||
/**
|
||||
* If set, the field is a pushbutton that does not retain a permanent value.
|
||||
*/
|
||||
PushButton = flag(17 - 1),
|
||||
|
||||
/**
|
||||
* If set, a group of radio buttons within a radio button field that use the
|
||||
* same value for the on state will turn on and off in unison; that is if one
|
||||
* is checked, they are all checked. If clear, the buttons are mutually
|
||||
* exclusive (the same behavior as HTML radio buttons).
|
||||
*/
|
||||
RadiosInUnison = flag(26 - 1),
|
||||
}
|
||||
|
||||
/** From PDF spec table 228 */
|
||||
export enum AcroTextFlags {
|
||||
/**
|
||||
* If set, the field may contain multiple lines of text; if clear, the field's
|
||||
* text shall be restricted to a single line.
|
||||
*/
|
||||
Multiline = flag(13 - 1),
|
||||
|
||||
/**
|
||||
* If set, the field is intended for entering a secure password that should
|
||||
* not be echoed visibly to the screen. Characters typed from the keyboard
|
||||
* shall instead be echoed in some unreadable form, such as asterisks or
|
||||
* bullet characters.
|
||||
* > NOTE To protect password confidentiality, readers should never store
|
||||
* > the value of the text field in the PDF file if this flag is set.
|
||||
*/
|
||||
Password = flag(14 - 1),
|
||||
|
||||
/**
|
||||
* If set, the text entered in the field represents the pathname of a file
|
||||
* whose contents shall be submitted as the value of the field.
|
||||
*/
|
||||
FileSelect = flag(21 - 1),
|
||||
|
||||
/**
|
||||
* If set, text entered in the field shall not be spell-checked.
|
||||
*/
|
||||
DoNotSpellCheck = flag(23 - 1),
|
||||
|
||||
/**
|
||||
* If set, the field shall not scroll (horizontally for single-line fields,
|
||||
* vertically for multiple-line fields) to accommodate more text than fits
|
||||
* within its annotation rectangle. Once the field is full, no further text
|
||||
* shall be accepted for interactive form filling; for non-interactive form
|
||||
* filling, the filler should take care not to add more character than will
|
||||
* visibly fit in the defined area.
|
||||
*/
|
||||
DoNotScroll = flag(24 - 1),
|
||||
|
||||
/**
|
||||
* May be set only if the MaxLen entry is present in the text field dictionary
|
||||
* (see Table 229) and if the Multiline, Password, and FileSelect flags are
|
||||
* clear. If set, the field shall be automatically divided into as many
|
||||
* equally spaced positions, or combs, as the value of MaxLen, and the text
|
||||
* is laid out into those combs.
|
||||
*/
|
||||
Comb = flag(25 - 1),
|
||||
|
||||
/**
|
||||
* If set, the value of this field shall be a rich text string
|
||||
* (see 12.7.3.4, "Rich Text Strings"). If the field has a value, the RV
|
||||
* entry of the field dictionary (Table 222) shall specify the rich text
|
||||
* string.
|
||||
*/
|
||||
RichText = flag(26 - 1),
|
||||
}
|
||||
|
||||
/** From PDF spec table 230 */
|
||||
export enum AcroChoiceFlags {
|
||||
/**
|
||||
* If set, the field is a combo box; if clear, the field is a list box.
|
||||
*/
|
||||
Combo = flag(18 - 1),
|
||||
|
||||
/**
|
||||
* If set, the combo box shall include an editable text box as well as a
|
||||
* drop-down list; if clear, it shall include only a drop-down list. This
|
||||
* flag shall be used only if the Combo flag is set.
|
||||
*/
|
||||
Edit = flag(19 - 1),
|
||||
|
||||
/**
|
||||
* If set, the field's option items shall be sorted alphabetically. This flag
|
||||
* is intended for use by writers, not by readers. Conforming readers shall
|
||||
* display the options in the order in which they occur in the Opt array
|
||||
* (see Table 231).
|
||||
*/
|
||||
Sort = flag(20 - 1),
|
||||
|
||||
/**
|
||||
* If set, more than one of the field's option items may be selected
|
||||
* simultaneously; if clear, at most one item shall be selected.
|
||||
*/
|
||||
MultiSelect = flag(22 - 1),
|
||||
|
||||
/**
|
||||
* If set, text entered in the field shall not be spell-checked. This flag
|
||||
* shall not be used unless the Combo and Edit flags are both set.
|
||||
*/
|
||||
DoNotSpellCheck = flag(23 - 1),
|
||||
|
||||
/**
|
||||
* If set, the new value shall be committed as soon as a selection is made
|
||||
* (commonly with the pointing device). In this case, supplying a value for
|
||||
* a field involves three actions: selecting the field for fill-in,
|
||||
* selecting a choice for the fill-in value, and leaving that field, which
|
||||
* finalizes or "commits" the data choice and triggers any actions associated
|
||||
* with the entry or changing of this data. If this flag is on, then
|
||||
* processing does not wait for leaving the field action to occur, but
|
||||
* immediately proceeds to the third step.
|
||||
*
|
||||
* This option enables applications to perform an action once a selection is
|
||||
* made, without requiring the user to exit the field. If clear, the new
|
||||
* value is not committed until the user exits the field.
|
||||
*/
|
||||
CommitOnSelChange = flag(27 - 1),
|
||||
}
|
||||
15
node_modules/pdf-lib/src/core/acroform/index.ts
generated
vendored
Normal file
15
node_modules/pdf-lib/src/core/acroform/index.ts
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
export { default as PDFAcroButton } from 'src/core/acroform/PDFAcroButton';
|
||||
export { default as PDFAcroCheckBox } from 'src/core/acroform/PDFAcroCheckBox';
|
||||
export { default as PDFAcroChoice } from 'src/core/acroform/PDFAcroChoice';
|
||||
export { default as PDFAcroComboBox } from 'src/core/acroform/PDFAcroComboBox';
|
||||
export { default as PDFAcroField } from 'src/core/acroform/PDFAcroField';
|
||||
export { default as PDFAcroForm } from 'src/core/acroform/PDFAcroForm';
|
||||
export { default as PDFAcroListBox } from 'src/core/acroform/PDFAcroListBox';
|
||||
export { default as PDFAcroNonTerminal } from 'src/core/acroform/PDFAcroNonTerminal';
|
||||
export { default as PDFAcroPushButton } from 'src/core/acroform/PDFAcroPushButton';
|
||||
export { default as PDFAcroRadioButton } from 'src/core/acroform/PDFAcroRadioButton';
|
||||
export { default as PDFAcroSignature } from 'src/core/acroform/PDFAcroSignature';
|
||||
export { default as PDFAcroTerminal } from 'src/core/acroform/PDFAcroTerminal';
|
||||
export { default as PDFAcroText } from 'src/core/acroform/PDFAcroText';
|
||||
export * from 'src/core/acroform/flags';
|
||||
export * from 'src/core/acroform/utils';
|
||||
135
node_modules/pdf-lib/src/core/acroform/utils.ts
generated
vendored
Normal file
135
node_modules/pdf-lib/src/core/acroform/utils.ts
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
import PDFNumber from 'src/core/objects/PDFNumber';
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFArray from 'src/core/objects/PDFArray';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
|
||||
import PDFAcroField from 'src/core/acroform/PDFAcroField';
|
||||
import PDFAcroTerminal from 'src/core/acroform/PDFAcroTerminal';
|
||||
import PDFAcroNonTerminal from 'src/core/acroform/PDFAcroNonTerminal';
|
||||
import PDFAcroButton from 'src/core/acroform/PDFAcroButton';
|
||||
import PDFAcroSignature from 'src/core/acroform/PDFAcroSignature';
|
||||
import PDFAcroChoice from 'src/core/acroform/PDFAcroChoice';
|
||||
import PDFAcroText from 'src/core/acroform/PDFAcroText';
|
||||
import PDFAcroPushButton from 'src/core/acroform/PDFAcroPushButton';
|
||||
import PDFAcroRadioButton from 'src/core/acroform/PDFAcroRadioButton';
|
||||
import PDFAcroCheckBox from 'src/core/acroform/PDFAcroCheckBox';
|
||||
import PDFAcroComboBox from 'src/core/acroform/PDFAcroComboBox';
|
||||
import PDFAcroListBox from 'src/core/acroform/PDFAcroListBox';
|
||||
import { AcroButtonFlags, AcroChoiceFlags } from 'src/core/acroform/flags';
|
||||
|
||||
export const createPDFAcroFields = (
|
||||
kidDicts?: PDFArray,
|
||||
): [PDFAcroField, PDFRef][] => {
|
||||
if (!kidDicts) return [];
|
||||
|
||||
const kids: [PDFAcroField, PDFRef][] = [];
|
||||
for (let idx = 0, len = kidDicts.size(); idx < len; idx++) {
|
||||
const ref = kidDicts.get(idx);
|
||||
const dict = kidDicts.lookup(idx);
|
||||
// if (dict instanceof PDFDict) kids.push(PDFAcroField.fromDict(dict));
|
||||
if (ref instanceof PDFRef && dict instanceof PDFDict) {
|
||||
kids.push([createPDFAcroField(dict, ref), ref]);
|
||||
}
|
||||
}
|
||||
|
||||
return kids;
|
||||
};
|
||||
|
||||
export const createPDFAcroField = (
|
||||
dict: PDFDict,
|
||||
ref: PDFRef,
|
||||
): PDFAcroField => {
|
||||
const isNonTerminal = isNonTerminalAcroField(dict);
|
||||
if (isNonTerminal) return PDFAcroNonTerminal.fromDict(dict, ref);
|
||||
return createPDFAcroTerminal(dict, ref);
|
||||
};
|
||||
|
||||
// TODO: Maybe just check if the dict is *not* a widget? That might be better.
|
||||
|
||||
// According to the PDF spec:
|
||||
//
|
||||
// > A field's children in the hierarchy may also include widget annotations
|
||||
// > that define its appearance on the page. A field that has children that
|
||||
// > are fields is called a non-terminal field. A field that does not have
|
||||
// > children that are fields is called a terminal field.
|
||||
//
|
||||
// The spec is not entirely clear about how to determine whether a given
|
||||
// dictionary represents an acrofield or a widget annotation. So we will assume
|
||||
// that a dictionary is an acrofield if it is a member of the `/Kids` array
|
||||
// and it contains a `/T` entry (widgets do not have `/T` entries). This isn't
|
||||
// a bullet proof solution, because the `/T` entry is technically defined as
|
||||
// optional for acrofields by the PDF spec. But in practice all acrofields seem
|
||||
// to have a `/T` entry defined.
|
||||
const isNonTerminalAcroField = (dict: PDFDict): boolean => {
|
||||
const kids = dict.lookup(PDFName.of('Kids'));
|
||||
|
||||
if (kids instanceof PDFArray) {
|
||||
for (let idx = 0, len = kids.size(); idx < len; idx++) {
|
||||
const kid = kids.lookup(idx);
|
||||
const kidIsField = kid instanceof PDFDict && kid.has(PDFName.of('T'));
|
||||
if (kidIsField) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const createPDFAcroTerminal = (dict: PDFDict, ref: PDFRef): PDFAcroTerminal => {
|
||||
const ftNameOrRef = getInheritableAttribute(dict, PDFName.of('FT'));
|
||||
const type = dict.context.lookup(ftNameOrRef, PDFName);
|
||||
|
||||
if (type === PDFName.of('Btn')) return createPDFAcroButton(dict, ref);
|
||||
if (type === PDFName.of('Ch')) return createPDFAcroChoice(dict, ref);
|
||||
if (type === PDFName.of('Tx')) return PDFAcroText.fromDict(dict, ref);
|
||||
if (type === PDFName.of('Sig')) return PDFAcroSignature.fromDict(dict, ref);
|
||||
|
||||
// We should never reach this line. But there are a lot of weird PDFs out
|
||||
// there. So, just to be safe, we'll try to handle things gracefully instead
|
||||
// of throwing an error.
|
||||
return PDFAcroTerminal.fromDict(dict, ref);
|
||||
};
|
||||
|
||||
const createPDFAcroButton = (dict: PDFDict, ref: PDFRef): PDFAcroButton => {
|
||||
const ffNumberOrRef = getInheritableAttribute(dict, PDFName.of('Ff'));
|
||||
const ffNumber = dict.context.lookupMaybe(ffNumberOrRef, PDFNumber);
|
||||
const flags = ffNumber?.asNumber() ?? 0;
|
||||
|
||||
if (flagIsSet(flags, AcroButtonFlags.PushButton)) {
|
||||
return PDFAcroPushButton.fromDict(dict, ref);
|
||||
} else if (flagIsSet(flags, AcroButtonFlags.Radio)) {
|
||||
return PDFAcroRadioButton.fromDict(dict, ref);
|
||||
} else {
|
||||
return PDFAcroCheckBox.fromDict(dict, ref);
|
||||
}
|
||||
};
|
||||
|
||||
const createPDFAcroChoice = (dict: PDFDict, ref: PDFRef): PDFAcroChoice => {
|
||||
const ffNumberOrRef = getInheritableAttribute(dict, PDFName.of('Ff'));
|
||||
const ffNumber = dict.context.lookupMaybe(ffNumberOrRef, PDFNumber);
|
||||
const flags = ffNumber?.asNumber() ?? 0;
|
||||
|
||||
if (flagIsSet(flags, AcroChoiceFlags.Combo)) {
|
||||
return PDFAcroComboBox.fromDict(dict, ref);
|
||||
} else {
|
||||
return PDFAcroListBox.fromDict(dict, ref);
|
||||
}
|
||||
};
|
||||
|
||||
const flagIsSet = (flags: number, flag: number): boolean =>
|
||||
(flags & flag) !== 0;
|
||||
|
||||
const getInheritableAttribute = (startNode: PDFDict, name: PDFName) => {
|
||||
let attribute: PDFObject | undefined;
|
||||
ascend(startNode, (node) => {
|
||||
if (!attribute) attribute = node.get(name);
|
||||
});
|
||||
return attribute;
|
||||
};
|
||||
|
||||
const ascend = (startNode: PDFDict, visitor: (node: PDFDict) => any) => {
|
||||
visitor(startNode);
|
||||
const Parent = startNode.lookupMaybe(PDFName.of('Parent'), PDFDict);
|
||||
if (Parent) ascend(Parent, visitor);
|
||||
};
|
||||
133
node_modules/pdf-lib/src/core/annotation/AppearanceCharacteristics.ts
generated
vendored
Normal file
133
node_modules/pdf-lib/src/core/annotation/AppearanceCharacteristics.ts
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFNumber from 'src/core/objects/PDFNumber';
|
||||
import PDFArray from 'src/core/objects/PDFArray';
|
||||
import PDFHexString from 'src/core/objects/PDFHexString';
|
||||
import PDFString from 'src/core/objects/PDFString';
|
||||
|
||||
class AppearanceCharacteristics {
|
||||
readonly dict: PDFDict;
|
||||
|
||||
static fromDict = (dict: PDFDict): AppearanceCharacteristics =>
|
||||
new AppearanceCharacteristics(dict);
|
||||
|
||||
protected constructor(dict: PDFDict) {
|
||||
this.dict = dict;
|
||||
}
|
||||
|
||||
R(): PDFNumber | undefined {
|
||||
const R = this.dict.lookup(PDFName.of('R'));
|
||||
if (R instanceof PDFNumber) return R;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
BC(): PDFArray | undefined {
|
||||
const BC = this.dict.lookup(PDFName.of('BC'));
|
||||
if (BC instanceof PDFArray) return BC;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
BG(): PDFArray | undefined {
|
||||
const BG = this.dict.lookup(PDFName.of('BG'));
|
||||
if (BG instanceof PDFArray) return BG;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
CA(): PDFHexString | PDFString | undefined {
|
||||
const CA = this.dict.lookup(PDFName.of('CA'));
|
||||
if (CA instanceof PDFHexString || CA instanceof PDFString) return CA;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
RC(): PDFHexString | PDFString | undefined {
|
||||
const RC = this.dict.lookup(PDFName.of('RC'));
|
||||
if (RC instanceof PDFHexString || RC instanceof PDFString) return RC;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
AC(): PDFHexString | PDFString | undefined {
|
||||
const AC = this.dict.lookup(PDFName.of('AC'));
|
||||
if (AC instanceof PDFHexString || AC instanceof PDFString) return AC;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getRotation(): number | undefined {
|
||||
return this.R()?.asNumber();
|
||||
}
|
||||
|
||||
getBorderColor(): number[] | undefined {
|
||||
const BC = this.BC();
|
||||
|
||||
if (!BC) return undefined;
|
||||
|
||||
const components: number[] = [];
|
||||
for (let idx = 0, len = BC?.size(); idx < len; idx++) {
|
||||
const component = BC.get(idx);
|
||||
if (component instanceof PDFNumber) components.push(component.asNumber());
|
||||
}
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
getBackgroundColor(): number[] | undefined {
|
||||
const BG = this.BG();
|
||||
|
||||
if (!BG) return undefined;
|
||||
|
||||
const components: number[] = [];
|
||||
for (let idx = 0, len = BG?.size(); idx < len; idx++) {
|
||||
const component = BG.get(idx);
|
||||
if (component instanceof PDFNumber) components.push(component.asNumber());
|
||||
}
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
getCaptions(): { normal?: string; rollover?: string; down?: string } {
|
||||
const CA = this.CA();
|
||||
const RC = this.RC();
|
||||
const AC = this.AC();
|
||||
|
||||
return {
|
||||
normal: CA?.decodeText(),
|
||||
rollover: RC?.decodeText(),
|
||||
down: AC?.decodeText(),
|
||||
};
|
||||
}
|
||||
|
||||
setRotation(rotation: number) {
|
||||
const R = this.dict.context.obj(rotation);
|
||||
this.dict.set(PDFName.of('R'), R);
|
||||
}
|
||||
|
||||
setBorderColor(color: number[]) {
|
||||
const BC = this.dict.context.obj(color);
|
||||
this.dict.set(PDFName.of('BC'), BC);
|
||||
}
|
||||
|
||||
setBackgroundColor(color: number[]) {
|
||||
const BG = this.dict.context.obj(color);
|
||||
this.dict.set(PDFName.of('BG'), BG);
|
||||
}
|
||||
|
||||
setCaptions(captions: { normal: string; rollover?: string; down?: string }) {
|
||||
const CA = PDFHexString.fromText(captions.normal);
|
||||
this.dict.set(PDFName.of('CA'), CA);
|
||||
|
||||
if (captions.rollover) {
|
||||
const RC = PDFHexString.fromText(captions.rollover);
|
||||
this.dict.set(PDFName.of('RC'), RC);
|
||||
} else {
|
||||
this.dict.delete(PDFName.of('RC'));
|
||||
}
|
||||
|
||||
if (captions.down) {
|
||||
const AC = PDFHexString.fromText(captions.down);
|
||||
this.dict.set(PDFName.of('AC'), AC);
|
||||
} else {
|
||||
this.dict.delete(PDFName.of('AC'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default AppearanceCharacteristics;
|
||||
31
node_modules/pdf-lib/src/core/annotation/BorderStyle.ts
generated
vendored
Normal file
31
node_modules/pdf-lib/src/core/annotation/BorderStyle.ts
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFNumber from 'src/core/objects/PDFNumber';
|
||||
|
||||
// TODO: Also handle the `/S` and `/D` entries
|
||||
class BorderStyle {
|
||||
readonly dict: PDFDict;
|
||||
|
||||
static fromDict = (dict: PDFDict): BorderStyle => new BorderStyle(dict);
|
||||
|
||||
protected constructor(dict: PDFDict) {
|
||||
this.dict = dict;
|
||||
}
|
||||
|
||||
W(): PDFNumber | undefined {
|
||||
const W = this.dict.lookup(PDFName.of('W'));
|
||||
if (W instanceof PDFNumber) return W;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getWidth(): number | undefined {
|
||||
return this.W()?.asNumber() ?? 1;
|
||||
}
|
||||
|
||||
setWidth(width: number) {
|
||||
const W = this.dict.context.obj(width);
|
||||
this.dict.set(PDFName.of('W'), W);
|
||||
}
|
||||
}
|
||||
|
||||
export default BorderStyle;
|
||||
148
node_modules/pdf-lib/src/core/annotation/PDFAnnotation.ts
generated
vendored
Normal file
148
node_modules/pdf-lib/src/core/annotation/PDFAnnotation.ts
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFStream from 'src/core/objects/PDFStream';
|
||||
import PDFArray from 'src/core/objects/PDFArray';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFNumber from 'src/core/objects/PDFNumber';
|
||||
|
||||
class PDFAnnotation {
|
||||
readonly dict: PDFDict;
|
||||
|
||||
static fromDict = (dict: PDFDict): PDFAnnotation => new PDFAnnotation(dict);
|
||||
|
||||
protected constructor(dict: PDFDict) {
|
||||
this.dict = dict;
|
||||
}
|
||||
|
||||
// This is technically required by the PDF spec
|
||||
Rect(): PDFArray | undefined {
|
||||
return this.dict.lookup(PDFName.of('Rect'), PDFArray);
|
||||
}
|
||||
|
||||
AP(): PDFDict | undefined {
|
||||
return this.dict.lookupMaybe(PDFName.of('AP'), PDFDict);
|
||||
}
|
||||
|
||||
F(): PDFNumber | undefined {
|
||||
const numberOrRef = this.dict.lookup(PDFName.of('F'));
|
||||
return this.dict.context.lookupMaybe(numberOrRef, PDFNumber);
|
||||
}
|
||||
|
||||
getRectangle(): { x: number; y: number; width: number; height: number } {
|
||||
const Rect = this.Rect();
|
||||
return Rect?.asRectangle() ?? { x: 0, y: 0, width: 0, height: 0 };
|
||||
}
|
||||
|
||||
setRectangle(rect: { x: number; y: number; width: number; height: number }) {
|
||||
const { x, y, width, height } = rect;
|
||||
const Rect = this.dict.context.obj([x, y, x + width, y + height]);
|
||||
this.dict.set(PDFName.of('Rect'), Rect);
|
||||
}
|
||||
|
||||
getAppearanceState(): PDFName | undefined {
|
||||
const AS = this.dict.lookup(PDFName.of('AS'));
|
||||
if (AS instanceof PDFName) return AS;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
setAppearanceState(state: PDFName) {
|
||||
this.dict.set(PDFName.of('AS'), state);
|
||||
}
|
||||
|
||||
setAppearances(appearances: PDFDict) {
|
||||
this.dict.set(PDFName.of('AP'), appearances);
|
||||
}
|
||||
|
||||
ensureAP(): PDFDict {
|
||||
let AP = this.AP();
|
||||
if (!AP) {
|
||||
AP = this.dict.context.obj({});
|
||||
this.dict.set(PDFName.of('AP'), AP);
|
||||
}
|
||||
return AP;
|
||||
}
|
||||
|
||||
getNormalAppearance(): PDFRef | PDFDict {
|
||||
const AP = this.ensureAP();
|
||||
const N = AP.get(PDFName.of('N'));
|
||||
if (N instanceof PDFRef || N instanceof PDFDict) return N;
|
||||
|
||||
throw new Error(`Unexpected N type: ${N?.constructor.name}`);
|
||||
}
|
||||
|
||||
/** @param appearance A PDFDict or PDFStream (direct or ref) */
|
||||
setNormalAppearance(appearance: PDFRef | PDFDict) {
|
||||
const AP = this.ensureAP();
|
||||
AP.set(PDFName.of('N'), appearance);
|
||||
}
|
||||
|
||||
/** @param appearance A PDFDict or PDFStream (direct or ref) */
|
||||
setRolloverAppearance(appearance: PDFRef | PDFDict) {
|
||||
const AP = this.ensureAP();
|
||||
AP.set(PDFName.of('R'), appearance);
|
||||
}
|
||||
|
||||
/** @param appearance A PDFDict or PDFStream (direct or ref) */
|
||||
setDownAppearance(appearance: PDFRef | PDFDict) {
|
||||
const AP = this.ensureAP();
|
||||
AP.set(PDFName.of('D'), appearance);
|
||||
}
|
||||
|
||||
removeRolloverAppearance() {
|
||||
const AP = this.AP();
|
||||
AP?.delete(PDFName.of('R'));
|
||||
}
|
||||
|
||||
removeDownAppearance() {
|
||||
const AP = this.AP();
|
||||
AP?.delete(PDFName.of('D'));
|
||||
}
|
||||
|
||||
getAppearances():
|
||||
| {
|
||||
normal: PDFStream | PDFDict;
|
||||
rollover?: PDFStream | PDFDict;
|
||||
down?: PDFStream | PDFDict;
|
||||
}
|
||||
| undefined {
|
||||
const AP = this.AP();
|
||||
|
||||
if (!AP) return undefined;
|
||||
|
||||
const N = AP.lookup(PDFName.of('N'), PDFDict, PDFStream);
|
||||
const R = AP.lookupMaybe(PDFName.of('R'), PDFDict, PDFStream);
|
||||
const D = AP.lookupMaybe(PDFName.of('D'), PDFDict, PDFStream);
|
||||
|
||||
return { normal: N, rollover: R, down: D };
|
||||
}
|
||||
|
||||
getFlags(): number {
|
||||
return this.F()?.asNumber() ?? 0;
|
||||
}
|
||||
|
||||
setFlags(flags: number) {
|
||||
this.dict.set(PDFName.of('F'), PDFNumber.of(flags));
|
||||
}
|
||||
|
||||
hasFlag(flag: number): boolean {
|
||||
const flags = this.getFlags();
|
||||
return (flags & flag) !== 0;
|
||||
}
|
||||
|
||||
setFlag(flag: number) {
|
||||
const flags = this.getFlags();
|
||||
this.setFlags(flags | flag);
|
||||
}
|
||||
|
||||
clearFlag(flag: number) {
|
||||
const flags = this.getFlags();
|
||||
this.setFlags(flags & ~flag);
|
||||
}
|
||||
|
||||
setFlagTo(flag: number, enable: boolean) {
|
||||
if (enable) this.setFlag(flag);
|
||||
else this.clearFlag(flag);
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFAnnotation;
|
||||
112
node_modules/pdf-lib/src/core/annotation/PDFWidgetAnnotation.ts
generated
vendored
Normal file
112
node_modules/pdf-lib/src/core/annotation/PDFWidgetAnnotation.ts
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFString from 'src/core/objects/PDFString';
|
||||
import PDFHexString from 'src/core/objects/PDFHexString';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import BorderStyle from 'src/core/annotation/BorderStyle';
|
||||
import PDFAnnotation from 'src/core/annotation/PDFAnnotation';
|
||||
import AppearanceCharacteristics from 'src/core/annotation/AppearanceCharacteristics';
|
||||
|
||||
class PDFWidgetAnnotation extends PDFAnnotation {
|
||||
static fromDict = (dict: PDFDict): PDFWidgetAnnotation =>
|
||||
new PDFWidgetAnnotation(dict);
|
||||
|
||||
static create = (context: PDFContext, parent: PDFRef) => {
|
||||
const dict = context.obj({
|
||||
Type: 'Annot',
|
||||
Subtype: 'Widget',
|
||||
Rect: [0, 0, 0, 0],
|
||||
Parent: parent,
|
||||
});
|
||||
return new PDFWidgetAnnotation(dict);
|
||||
};
|
||||
|
||||
MK(): PDFDict | undefined {
|
||||
const MK = this.dict.lookup(PDFName.of('MK'));
|
||||
if (MK instanceof PDFDict) return MK;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
BS(): PDFDict | undefined {
|
||||
const BS = this.dict.lookup(PDFName.of('BS'));
|
||||
if (BS instanceof PDFDict) return BS;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
DA(): PDFString | PDFHexString | undefined {
|
||||
const da = this.dict.lookup(PDFName.of('DA'));
|
||||
if (da instanceof PDFString || da instanceof PDFHexString) return da;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
P(): PDFRef | undefined {
|
||||
const P = this.dict.get(PDFName.of('P'));
|
||||
if (P instanceof PDFRef) return P;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
setP(page: PDFRef) {
|
||||
this.dict.set(PDFName.of('P'), page);
|
||||
}
|
||||
|
||||
setDefaultAppearance(appearance: string) {
|
||||
this.dict.set(PDFName.of('DA'), PDFString.of(appearance));
|
||||
}
|
||||
|
||||
getDefaultAppearance(): string | undefined {
|
||||
const DA = this.DA();
|
||||
|
||||
if (DA instanceof PDFHexString) {
|
||||
return DA.decodeText();
|
||||
}
|
||||
|
||||
return DA?.asString();
|
||||
}
|
||||
|
||||
getAppearanceCharacteristics(): AppearanceCharacteristics | undefined {
|
||||
const MK = this.MK();
|
||||
if (MK) return AppearanceCharacteristics.fromDict(MK);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getOrCreateAppearanceCharacteristics(): AppearanceCharacteristics {
|
||||
const MK = this.MK();
|
||||
if (MK) return AppearanceCharacteristics.fromDict(MK);
|
||||
|
||||
const ac = AppearanceCharacteristics.fromDict(this.dict.context.obj({}));
|
||||
this.dict.set(PDFName.of('MK'), ac.dict);
|
||||
return ac;
|
||||
}
|
||||
|
||||
getBorderStyle(): BorderStyle | undefined {
|
||||
const BS = this.BS();
|
||||
if (BS) return BorderStyle.fromDict(BS);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getOrCreateBorderStyle(): BorderStyle {
|
||||
const BS = this.BS();
|
||||
if (BS) return BorderStyle.fromDict(BS);
|
||||
|
||||
const bs = BorderStyle.fromDict(this.dict.context.obj({}));
|
||||
this.dict.set(PDFName.of('BS'), bs.dict);
|
||||
return bs;
|
||||
}
|
||||
|
||||
getOnValue(): PDFName | undefined {
|
||||
const normal = this.getAppearances()?.normal;
|
||||
|
||||
if (normal instanceof PDFDict) {
|
||||
const keys = normal.keys();
|
||||
for (let idx = 0, len = keys.length; idx < len; idx++) {
|
||||
const key = keys[idx];
|
||||
if (key !== PDFName.of('Off')) return key;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFWidgetAnnotation;
|
||||
90
node_modules/pdf-lib/src/core/annotation/flags.ts
generated
vendored
Normal file
90
node_modules/pdf-lib/src/core/annotation/flags.ts
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
const flag = (bitIndex: number) => 1 << bitIndex;
|
||||
|
||||
/** From PDF spec table 165 */
|
||||
export enum AnnotationFlags {
|
||||
/**
|
||||
* If set, do not display the annotation if it does not belong to one of the
|
||||
* standard annotation types and no annotation handler is available. If clear,
|
||||
* display such an unknown annotation using an appearance stream specified by
|
||||
* its appearance dictionary, if any.
|
||||
*/
|
||||
Invisible = flag(1 - 1),
|
||||
|
||||
/**
|
||||
* If set, do not display or print the annotation or allow it to interact with
|
||||
* the user, regardless of its annotation type or whether an annotation
|
||||
* handler is available.
|
||||
*
|
||||
* In cases where screen space is limited, the ability to hide and show
|
||||
* annotations selectively can be used in combination with appearance streams
|
||||
* to display auxiliary pop-up information similar in function to online help
|
||||
* systems.
|
||||
*/
|
||||
Hidden = flag(2 - 1),
|
||||
|
||||
/**
|
||||
* If set, print the annotation when the page is printed. If clear, never
|
||||
* print the annotation, regardless of whether it is displayed on the screen.
|
||||
*
|
||||
* This can be useful for annotations representing interactive pushbuttons,
|
||||
* which would serve no meaningful purpose on the printed page.
|
||||
*/
|
||||
Print = flag(3 - 1),
|
||||
|
||||
/**
|
||||
* If set, do not scale the annotation’s appearance to match the magnification
|
||||
* of the page. The location of the annotation on the page (defined by the
|
||||
* upper-left corner of its annotation rectangle) shall remain fixed,
|
||||
* regardless of the page magnification.
|
||||
*/
|
||||
NoZoom = flag(4 - 1),
|
||||
|
||||
/**
|
||||
* If set, do not rotate the annotation’s appearance to match the rotation of
|
||||
* the page. The upper-left corner of the annotation rectangle shall remain in
|
||||
* a fixed location on the page, regardless of the page rotation.
|
||||
*/
|
||||
NoRotate = flag(5 - 1),
|
||||
|
||||
/**
|
||||
* If set, do not display the annotation on the screen or allow it to interact
|
||||
* with the user. The annotation may be printed (depending on the setting of
|
||||
* the Print flag) but should be considered hidden for purposes of on-screen
|
||||
* display and user interaction.
|
||||
*/
|
||||
NoView = flag(6 - 1),
|
||||
|
||||
/**
|
||||
* If set, do not allow the annotation to interact with the user. The
|
||||
* annotation may be displayed or printed (depending on the settings of the
|
||||
* NoView and Print flags) but should not respond to mouse clicks or change
|
||||
* its appearance in response to mouse motions.
|
||||
*
|
||||
* This flag shall be ignored for widget annotations; its function is
|
||||
* subsumed by the ReadOnly flag of the associated form field.
|
||||
*/
|
||||
ReadOnly = flag(7 - 1),
|
||||
|
||||
/**
|
||||
* If set, do not allow the annotation to be deleted or its properties
|
||||
* (including position and size) to be modified by the user. However, this
|
||||
* flag does not restrict changes to the annotation’s contents, such as the
|
||||
* value of a form field.
|
||||
*/
|
||||
Locked = flag(8 - 1),
|
||||
|
||||
/**
|
||||
* If set, invert the interpretation of the NoView flag for certain events.
|
||||
*
|
||||
* A typical use is to have an annotation that appears only when a mouse
|
||||
* cursor is held over it.
|
||||
*/
|
||||
ToggleNoView = flag(9 - 1),
|
||||
|
||||
/**
|
||||
* If set, do not allow the contents of the annotation to be modified by the
|
||||
* user. This flag does not restrict deletion of the annotation or changes to
|
||||
* other annotation properties, such as position and size.
|
||||
*/
|
||||
LockedContents = flag(10 - 1),
|
||||
}
|
||||
4
node_modules/pdf-lib/src/core/annotation/index.ts
generated
vendored
Normal file
4
node_modules/pdf-lib/src/core/annotation/index.ts
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as PDFAnnotation } from 'src/core/annotation/PDFAnnotation';
|
||||
export { default as PDFWidgetAnnotation } from 'src/core/annotation/PDFWidgetAnnotation';
|
||||
export { default as AppearanceCharacteristics } from 'src/core/annotation/AppearanceCharacteristics';
|
||||
export * from 'src/core/annotation/flags';
|
||||
173
node_modules/pdf-lib/src/core/document/PDFCrossRefSection.ts
generated
vendored
Normal file
173
node_modules/pdf-lib/src/core/document/PDFCrossRefSection.ts
generated
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import CharCodes from 'src/core/syntax/CharCodes';
|
||||
import { copyStringIntoBuffer, padStart } from 'src/utils';
|
||||
|
||||
export interface Entry {
|
||||
ref: PDFRef;
|
||||
offset: number;
|
||||
deleted: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entries should be added using the [[addEntry]] and [[addDeletedEntry]]
|
||||
* methods **in order of ascending object number**.
|
||||
*/
|
||||
class PDFCrossRefSection {
|
||||
static create = () =>
|
||||
new PDFCrossRefSection({
|
||||
ref: PDFRef.of(0, 65535),
|
||||
offset: 0,
|
||||
deleted: true,
|
||||
});
|
||||
|
||||
static createEmpty = () => new PDFCrossRefSection();
|
||||
|
||||
private subsections: Entry[][];
|
||||
private chunkIdx: number;
|
||||
private chunkLength: number;
|
||||
|
||||
private constructor(firstEntry: Entry | void) {
|
||||
this.subsections = firstEntry ? [[firstEntry]] : [];
|
||||
this.chunkIdx = 0;
|
||||
this.chunkLength = firstEntry ? 1 : 0;
|
||||
}
|
||||
|
||||
addEntry(ref: PDFRef, offset: number): void {
|
||||
this.append({ ref, offset, deleted: false });
|
||||
}
|
||||
|
||||
addDeletedEntry(ref: PDFRef, nextFreeObjectNumber: number): void {
|
||||
this.append({ ref, offset: nextFreeObjectNumber, deleted: true });
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
let section = `xref\n`;
|
||||
|
||||
for (
|
||||
let rangeIdx = 0, rangeLen = this.subsections.length;
|
||||
rangeIdx < rangeLen;
|
||||
rangeIdx++
|
||||
) {
|
||||
const range = this.subsections[rangeIdx];
|
||||
section += `${range[0].ref.objectNumber} ${range.length}\n`;
|
||||
for (
|
||||
let entryIdx = 0, entryLen = range.length;
|
||||
entryIdx < entryLen;
|
||||
entryIdx++
|
||||
) {
|
||||
const entry = range[entryIdx];
|
||||
section += padStart(String(entry.offset), 10, '0');
|
||||
section += ' ';
|
||||
section += padStart(String(entry.ref.generationNumber), 5, '0');
|
||||
section += ' ';
|
||||
section += entry.deleted ? 'f' : 'n';
|
||||
section += ' \n';
|
||||
}
|
||||
}
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
sizeInBytes(): number {
|
||||
let size = 5;
|
||||
for (let idx = 0, len = this.subsections.length; idx < len; idx++) {
|
||||
const subsection = this.subsections[idx];
|
||||
const subsectionLength = subsection.length;
|
||||
const [firstEntry] = subsection;
|
||||
size += 2;
|
||||
size += String(firstEntry.ref.objectNumber).length;
|
||||
size += String(subsectionLength).length;
|
||||
size += 20 * subsectionLength;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
copyBytesInto(buffer: Uint8Array, offset: number): number {
|
||||
const initialOffset = offset;
|
||||
|
||||
buffer[offset++] = CharCodes.x;
|
||||
buffer[offset++] = CharCodes.r;
|
||||
buffer[offset++] = CharCodes.e;
|
||||
buffer[offset++] = CharCodes.f;
|
||||
buffer[offset++] = CharCodes.Newline;
|
||||
|
||||
offset += this.copySubsectionsIntoBuffer(this.subsections, buffer, offset);
|
||||
|
||||
return offset - initialOffset;
|
||||
}
|
||||
|
||||
private copySubsectionsIntoBuffer(
|
||||
subsections: Entry[][],
|
||||
buffer: Uint8Array,
|
||||
offset: number,
|
||||
): number {
|
||||
const initialOffset = offset;
|
||||
const length = subsections.length;
|
||||
|
||||
for (let idx = 0; idx < length; idx++) {
|
||||
const subsection = this.subsections[idx];
|
||||
|
||||
const firstObjectNumber = String(subsection[0].ref.objectNumber);
|
||||
offset += copyStringIntoBuffer(firstObjectNumber, buffer, offset);
|
||||
buffer[offset++] = CharCodes.Space;
|
||||
|
||||
const rangeLength = String(subsection.length);
|
||||
offset += copyStringIntoBuffer(rangeLength, buffer, offset);
|
||||
buffer[offset++] = CharCodes.Newline;
|
||||
|
||||
offset += this.copyEntriesIntoBuffer(subsection, buffer, offset);
|
||||
}
|
||||
|
||||
return offset - initialOffset;
|
||||
}
|
||||
|
||||
private copyEntriesIntoBuffer(
|
||||
entries: Entry[],
|
||||
buffer: Uint8Array,
|
||||
offset: number,
|
||||
): number {
|
||||
const length = entries.length;
|
||||
|
||||
for (let idx = 0; idx < length; idx++) {
|
||||
const entry = entries[idx];
|
||||
|
||||
const entryOffset = padStart(String(entry.offset), 10, '0');
|
||||
offset += copyStringIntoBuffer(entryOffset, buffer, offset);
|
||||
buffer[offset++] = CharCodes.Space;
|
||||
|
||||
const entryGen = padStart(String(entry.ref.generationNumber), 5, '0');
|
||||
offset += copyStringIntoBuffer(entryGen, buffer, offset);
|
||||
buffer[offset++] = CharCodes.Space;
|
||||
|
||||
buffer[offset++] = entry.deleted ? CharCodes.f : CharCodes.n;
|
||||
|
||||
buffer[offset++] = CharCodes.Space;
|
||||
buffer[offset++] = CharCodes.Newline;
|
||||
}
|
||||
|
||||
return 20 * length;
|
||||
}
|
||||
|
||||
private append(currEntry: Entry): void {
|
||||
if (this.chunkLength === 0) {
|
||||
this.subsections.push([currEntry]);
|
||||
this.chunkIdx = 0;
|
||||
this.chunkLength = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
const chunk = this.subsections[this.chunkIdx];
|
||||
const prevEntry = chunk[this.chunkLength - 1];
|
||||
|
||||
if (currEntry.ref.objectNumber - prevEntry.ref.objectNumber > 1) {
|
||||
this.subsections.push([currEntry]);
|
||||
this.chunkIdx += 1;
|
||||
this.chunkLength = 1;
|
||||
} else {
|
||||
chunk.push(currEntry);
|
||||
this.chunkLength += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFCrossRefSection;
|
||||
49
node_modules/pdf-lib/src/core/document/PDFHeader.ts
generated
vendored
Normal file
49
node_modules/pdf-lib/src/core/document/PDFHeader.ts
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
import CharCodes from 'src/core/syntax/CharCodes';
|
||||
import { charFromCode, copyStringIntoBuffer } from 'src/utils';
|
||||
|
||||
class PDFHeader {
|
||||
static forVersion = (major: number, minor: number) =>
|
||||
new PDFHeader(major, minor);
|
||||
|
||||
private readonly major: string;
|
||||
private readonly minor: string;
|
||||
|
||||
private constructor(major: number, minor: number) {
|
||||
this.major = String(major);
|
||||
this.minor = String(minor);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
const bc = charFromCode(129);
|
||||
return `%PDF-${this.major}.${this.minor}\n%${bc}${bc}${bc}${bc}`;
|
||||
}
|
||||
|
||||
sizeInBytes(): number {
|
||||
return 12 + this.major.length + this.minor.length;
|
||||
}
|
||||
|
||||
copyBytesInto(buffer: Uint8Array, offset: number): number {
|
||||
const initialOffset = offset;
|
||||
|
||||
buffer[offset++] = CharCodes.Percent;
|
||||
buffer[offset++] = CharCodes.P;
|
||||
buffer[offset++] = CharCodes.D;
|
||||
buffer[offset++] = CharCodes.F;
|
||||
buffer[offset++] = CharCodes.Dash;
|
||||
|
||||
offset += copyStringIntoBuffer(this.major, buffer, offset);
|
||||
buffer[offset++] = CharCodes.Period;
|
||||
offset += copyStringIntoBuffer(this.minor, buffer, offset);
|
||||
buffer[offset++] = CharCodes.Newline;
|
||||
|
||||
buffer[offset++] = CharCodes.Percent;
|
||||
buffer[offset++] = 129;
|
||||
buffer[offset++] = 129;
|
||||
buffer[offset++] = 129;
|
||||
buffer[offset++] = 129;
|
||||
|
||||
return offset - initialOffset;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFHeader;
|
||||
49
node_modules/pdf-lib/src/core/document/PDFTrailer.ts
generated
vendored
Normal file
49
node_modules/pdf-lib/src/core/document/PDFTrailer.ts
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
import CharCodes from 'src/core/syntax/CharCodes';
|
||||
import { copyStringIntoBuffer } from 'src/utils';
|
||||
|
||||
class PDFTrailer {
|
||||
static forLastCrossRefSectionOffset = (offset: number) =>
|
||||
new PDFTrailer(offset);
|
||||
|
||||
private readonly lastXRefOffset: string;
|
||||
|
||||
private constructor(lastXRefOffset: number) {
|
||||
this.lastXRefOffset = String(lastXRefOffset);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `startxref\n${this.lastXRefOffset}\n%%EOF`;
|
||||
}
|
||||
|
||||
sizeInBytes(): number {
|
||||
return 16 + this.lastXRefOffset.length;
|
||||
}
|
||||
|
||||
copyBytesInto(buffer: Uint8Array, offset: number): number {
|
||||
const initialOffset = offset;
|
||||
|
||||
buffer[offset++] = CharCodes.s;
|
||||
buffer[offset++] = CharCodes.t;
|
||||
buffer[offset++] = CharCodes.a;
|
||||
buffer[offset++] = CharCodes.r;
|
||||
buffer[offset++] = CharCodes.t;
|
||||
buffer[offset++] = CharCodes.x;
|
||||
buffer[offset++] = CharCodes.r;
|
||||
buffer[offset++] = CharCodes.e;
|
||||
buffer[offset++] = CharCodes.f;
|
||||
buffer[offset++] = CharCodes.Newline;
|
||||
|
||||
offset += copyStringIntoBuffer(this.lastXRefOffset, buffer, offset);
|
||||
|
||||
buffer[offset++] = CharCodes.Newline;
|
||||
buffer[offset++] = CharCodes.Percent;
|
||||
buffer[offset++] = CharCodes.Percent;
|
||||
buffer[offset++] = CharCodes.E;
|
||||
buffer[offset++] = CharCodes.O;
|
||||
buffer[offset++] = CharCodes.F;
|
||||
|
||||
return offset - initialOffset;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFTrailer;
|
||||
39
node_modules/pdf-lib/src/core/document/PDFTrailerDict.ts
generated
vendored
Normal file
39
node_modules/pdf-lib/src/core/document/PDFTrailerDict.ts
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import CharCodes from 'src/core/syntax/CharCodes';
|
||||
|
||||
class PDFTrailerDict {
|
||||
static of = (dict: PDFDict) => new PDFTrailerDict(dict);
|
||||
|
||||
readonly dict: PDFDict;
|
||||
|
||||
private constructor(dict: PDFDict) {
|
||||
this.dict = dict;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `trailer\n${this.dict.toString()}`;
|
||||
}
|
||||
|
||||
sizeInBytes(): number {
|
||||
return 8 + this.dict.sizeInBytes();
|
||||
}
|
||||
|
||||
copyBytesInto(buffer: Uint8Array, offset: number): number {
|
||||
const initialOffset = offset;
|
||||
|
||||
buffer[offset++] = CharCodes.t;
|
||||
buffer[offset++] = CharCodes.r;
|
||||
buffer[offset++] = CharCodes.a;
|
||||
buffer[offset++] = CharCodes.i;
|
||||
buffer[offset++] = CharCodes.l;
|
||||
buffer[offset++] = CharCodes.e;
|
||||
buffer[offset++] = CharCodes.r;
|
||||
buffer[offset++] = CharCodes.Newline;
|
||||
|
||||
offset += this.dict.copyBytesInto(buffer, offset);
|
||||
|
||||
return offset - initialOffset;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFTrailerDict;
|
||||
70
node_modules/pdf-lib/src/core/embedders/CMap.ts
generated
vendored
Normal file
70
node_modules/pdf-lib/src/core/embedders/CMap.ts
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
import { Glyph } from 'src/types/fontkit';
|
||||
|
||||
import { toHexString, toHexStringOfMinLength } from 'src/utils';
|
||||
import {
|
||||
hasSurrogates,
|
||||
highSurrogate,
|
||||
isWithinBMP,
|
||||
lowSurrogate,
|
||||
} from 'src/utils/unicode';
|
||||
|
||||
/** [fontId, codePoint] */
|
||||
type BfChar = [string, string];
|
||||
|
||||
/** `glyphs` should be an array of unique glyphs */
|
||||
export const createCmap = (glyphs: Glyph[], glyphId: (g?: Glyph) => number) => {
|
||||
const bfChars: BfChar[] = new Array(glyphs.length);
|
||||
for (let idx = 0, len = glyphs.length; idx < len; idx++) {
|
||||
const glyph = glyphs[idx];
|
||||
const id = cmapHexFormat(cmapHexString(glyphId(glyph)));
|
||||
const unicode = cmapHexFormat(...glyph.codePoints.map(cmapCodePointFormat));
|
||||
bfChars[idx] = [id, unicode];
|
||||
}
|
||||
|
||||
return fillCmapTemplate(bfChars);
|
||||
};
|
||||
|
||||
/* =============================== Templates ================================ */
|
||||
|
||||
const fillCmapTemplate = (bfChars: BfChar[]) => `\
|
||||
/CIDInit /ProcSet findresource begin
|
||||
12 dict begin
|
||||
begincmap
|
||||
/CIDSystemInfo <<
|
||||
/Registry (Adobe)
|
||||
/Ordering (UCS)
|
||||
/Supplement 0
|
||||
>> def
|
||||
/CMapName /Adobe-Identity-UCS def
|
||||
/CMapType 2 def
|
||||
1 begincodespacerange
|
||||
<0000><ffff>
|
||||
endcodespacerange
|
||||
${bfChars.length} beginbfchar
|
||||
${bfChars.map(([glyphId, codePoint]) => `${glyphId} ${codePoint}`).join('\n')}
|
||||
endbfchar
|
||||
endcmap
|
||||
CMapName currentdict /CMap defineresource pop
|
||||
end
|
||||
end\
|
||||
`;
|
||||
|
||||
/* =============================== Utilities ================================ */
|
||||
|
||||
const cmapHexFormat = (...values: string[]) => `<${values.join('')}>`;
|
||||
|
||||
const cmapHexString = (value: number) => toHexStringOfMinLength(value, 4);
|
||||
|
||||
const cmapCodePointFormat = (codePoint: number) => {
|
||||
if (isWithinBMP(codePoint)) return cmapHexString(codePoint);
|
||||
|
||||
if (hasSurrogates(codePoint)) {
|
||||
const hs = highSurrogate(codePoint);
|
||||
const ls = lowSurrogate(codePoint);
|
||||
return `${cmapHexString(hs)}${cmapHexString(ls)}`;
|
||||
}
|
||||
|
||||
const hex = toHexString(codePoint);
|
||||
const msg = `0x${hex} is not a valid UTF-8 or UTF-16 codepoint.`;
|
||||
throw new Error(msg);
|
||||
};
|
||||
249
node_modules/pdf-lib/src/core/embedders/CustomFontEmbedder.ts
generated
vendored
Normal file
249
node_modules/pdf-lib/src/core/embedders/CustomFontEmbedder.ts
generated
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
import { Font, Fontkit, Glyph, TypeFeatures } from 'src/types/fontkit';
|
||||
|
||||
import { createCmap } from 'src/core/embedders/CMap';
|
||||
import { deriveFontFlags } from 'src/core/embedders/FontFlags';
|
||||
import PDFHexString from 'src/core/objects/PDFHexString';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFString from 'src/core/objects/PDFString';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import {
|
||||
byAscendingId,
|
||||
Cache,
|
||||
sortedUniq,
|
||||
toHexStringOfMinLength,
|
||||
} from 'src/utils';
|
||||
|
||||
/**
|
||||
* A note of thanks to the developers of https://github.com/foliojs/pdfkit, as
|
||||
* this class borrows from:
|
||||
* https://github.com/devongovett/pdfkit/blob/e71edab0dd4657b5a767804ba86c94c58d01fbca/lib/image/jpeg.coffee
|
||||
*/
|
||||
class CustomFontEmbedder {
|
||||
static async for(
|
||||
fontkit: Fontkit,
|
||||
fontData: Uint8Array,
|
||||
customName?: string,
|
||||
fontFeatures?: TypeFeatures,
|
||||
) {
|
||||
const font = await fontkit.create(fontData);
|
||||
return new CustomFontEmbedder(font, fontData, customName, fontFeatures);
|
||||
}
|
||||
|
||||
readonly font: Font;
|
||||
readonly scale: number;
|
||||
readonly fontData: Uint8Array;
|
||||
readonly fontName: string;
|
||||
readonly customName: string | undefined;
|
||||
readonly fontFeatures: TypeFeatures | undefined;
|
||||
|
||||
protected baseFontName: string;
|
||||
protected glyphCache: Cache<Glyph[]>;
|
||||
|
||||
protected constructor(
|
||||
font: Font,
|
||||
fontData: Uint8Array,
|
||||
customName?: string,
|
||||
fontFeatures?: TypeFeatures,
|
||||
) {
|
||||
this.font = font;
|
||||
this.scale = 1000 / this.font.unitsPerEm;
|
||||
this.fontData = fontData;
|
||||
this.fontName = this.font.postscriptName || 'Font';
|
||||
this.customName = customName;
|
||||
this.fontFeatures = fontFeatures;
|
||||
|
||||
this.baseFontName = '';
|
||||
this.glyphCache = Cache.populatedBy(this.allGlyphsInFontSortedById);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the JavaScript string into this font. (JavaScript encodes strings in
|
||||
* Unicode, but embedded fonts use their own custom encodings)
|
||||
*/
|
||||
encodeText(text: string): PDFHexString {
|
||||
const { glyphs } = this.font.layout(text, this.fontFeatures);
|
||||
const hexCodes = new Array(glyphs.length);
|
||||
for (let idx = 0, len = glyphs.length; idx < len; idx++) {
|
||||
hexCodes[idx] = toHexStringOfMinLength(glyphs[idx].id, 4);
|
||||
}
|
||||
return PDFHexString.of(hexCodes.join(''));
|
||||
}
|
||||
|
||||
// The advanceWidth takes into account kerning automatically, so we don't
|
||||
// have to do that manually like we do for the standard fonts.
|
||||
widthOfTextAtSize(text: string, size: number): number {
|
||||
const { glyphs } = this.font.layout(text, this.fontFeatures);
|
||||
let totalWidth = 0;
|
||||
for (let idx = 0, len = glyphs.length; idx < len; idx++) {
|
||||
totalWidth += glyphs[idx].advanceWidth * this.scale;
|
||||
}
|
||||
const scale = size / 1000;
|
||||
return totalWidth * scale;
|
||||
}
|
||||
|
||||
heightOfFontAtSize(
|
||||
size: number,
|
||||
options: { descender?: boolean } = {},
|
||||
): number {
|
||||
const { descender = true } = options;
|
||||
|
||||
const { ascent, descent, bbox } = this.font;
|
||||
const yTop = (ascent || bbox.maxY) * this.scale;
|
||||
const yBottom = (descent || bbox.minY) * this.scale;
|
||||
|
||||
let height = yTop - yBottom;
|
||||
if (!descender) height -= Math.abs(descent) || 0;
|
||||
|
||||
return (height / 1000) * size;
|
||||
}
|
||||
|
||||
sizeOfFontAtHeight(height: number): number {
|
||||
const { ascent, descent, bbox } = this.font;
|
||||
const yTop = (ascent || bbox.maxY) * this.scale;
|
||||
const yBottom = (descent || bbox.minY) * this.scale;
|
||||
return (1000 * height) / (yTop - yBottom);
|
||||
}
|
||||
|
||||
embedIntoContext(context: PDFContext, ref?: PDFRef): Promise<PDFRef> {
|
||||
this.baseFontName =
|
||||
this.customName || context.addRandomSuffix(this.fontName);
|
||||
return this.embedFontDict(context, ref);
|
||||
}
|
||||
|
||||
protected async embedFontDict(
|
||||
context: PDFContext,
|
||||
ref?: PDFRef,
|
||||
): Promise<PDFRef> {
|
||||
const cidFontDictRef = await this.embedCIDFontDict(context);
|
||||
const unicodeCMapRef = this.embedUnicodeCmap(context);
|
||||
|
||||
const fontDict = context.obj({
|
||||
Type: 'Font',
|
||||
Subtype: 'Type0',
|
||||
BaseFont: this.baseFontName,
|
||||
Encoding: 'Identity-H',
|
||||
DescendantFonts: [cidFontDictRef],
|
||||
ToUnicode: unicodeCMapRef,
|
||||
});
|
||||
|
||||
if (ref) {
|
||||
context.assign(ref, fontDict);
|
||||
return ref;
|
||||
} else {
|
||||
return context.register(fontDict);
|
||||
}
|
||||
}
|
||||
|
||||
protected isCFF(): boolean {
|
||||
return this.font.cff;
|
||||
}
|
||||
|
||||
protected async embedCIDFontDict(context: PDFContext): Promise<PDFRef> {
|
||||
const fontDescriptorRef = await this.embedFontDescriptor(context);
|
||||
|
||||
const cidFontDict = context.obj({
|
||||
Type: 'Font',
|
||||
Subtype: this.isCFF() ? 'CIDFontType0' : 'CIDFontType2',
|
||||
CIDToGIDMap: 'Identity',
|
||||
BaseFont: this.baseFontName,
|
||||
CIDSystemInfo: {
|
||||
Registry: PDFString.of('Adobe'),
|
||||
Ordering: PDFString.of('Identity'),
|
||||
Supplement: 0,
|
||||
},
|
||||
FontDescriptor: fontDescriptorRef,
|
||||
W: this.computeWidths(),
|
||||
});
|
||||
|
||||
return context.register(cidFontDict);
|
||||
}
|
||||
|
||||
protected async embedFontDescriptor(context: PDFContext): Promise<PDFRef> {
|
||||
const fontStreamRef = await this.embedFontStream(context);
|
||||
|
||||
const { scale } = this;
|
||||
const { italicAngle, ascent, descent, capHeight, xHeight } = this.font;
|
||||
const { minX, minY, maxX, maxY } = this.font.bbox;
|
||||
|
||||
const fontDescriptor = context.obj({
|
||||
Type: 'FontDescriptor',
|
||||
FontName: this.baseFontName,
|
||||
Flags: deriveFontFlags(this.font),
|
||||
FontBBox: [minX * scale, minY * scale, maxX * scale, maxY * scale],
|
||||
ItalicAngle: italicAngle,
|
||||
Ascent: ascent * scale,
|
||||
Descent: descent * scale,
|
||||
CapHeight: (capHeight || ascent) * scale,
|
||||
XHeight: (xHeight || 0) * scale,
|
||||
|
||||
// Not sure how to compute/find this, nor is anybody else really:
|
||||
// https://stackoverflow.com/questions/35485179/stemv-value-of-the-truetype-font
|
||||
StemV: 0,
|
||||
|
||||
[this.isCFF() ? 'FontFile3' : 'FontFile2']: fontStreamRef,
|
||||
});
|
||||
|
||||
return context.register(fontDescriptor);
|
||||
}
|
||||
|
||||
protected async serializeFont(): Promise<Uint8Array> {
|
||||
return this.fontData;
|
||||
}
|
||||
|
||||
protected async embedFontStream(context: PDFContext): Promise<PDFRef> {
|
||||
const fontStream = context.flateStream(await this.serializeFont(), {
|
||||
Subtype: this.isCFF() ? 'CIDFontType0C' : undefined,
|
||||
});
|
||||
return context.register(fontStream);
|
||||
}
|
||||
|
||||
protected embedUnicodeCmap(context: PDFContext): PDFRef {
|
||||
const cmap = createCmap(this.glyphCache.access(), this.glyphId.bind(this));
|
||||
const cmapStream = context.flateStream(cmap);
|
||||
return context.register(cmapStream);
|
||||
}
|
||||
|
||||
protected glyphId(glyph?: Glyph): number {
|
||||
return glyph ? glyph.id : -1;
|
||||
}
|
||||
|
||||
protected computeWidths(): (number | number[])[] {
|
||||
const glyphs = this.glyphCache.access();
|
||||
|
||||
const widths: (number | number[])[] = [];
|
||||
let currSection: number[] = [];
|
||||
|
||||
for (let idx = 0, len = glyphs.length; idx < len; idx++) {
|
||||
const currGlyph = glyphs[idx];
|
||||
const prevGlyph = glyphs[idx - 1];
|
||||
|
||||
const currGlyphId = this.glyphId(currGlyph);
|
||||
const prevGlyphId = this.glyphId(prevGlyph);
|
||||
|
||||
if (idx === 0) {
|
||||
widths.push(currGlyphId);
|
||||
} else if (currGlyphId - prevGlyphId !== 1) {
|
||||
widths.push(currSection);
|
||||
widths.push(currGlyphId);
|
||||
currSection = [];
|
||||
}
|
||||
|
||||
currSection.push(currGlyph.advanceWidth * this.scale);
|
||||
}
|
||||
|
||||
widths.push(currSection);
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
private allGlyphsInFontSortedById = (): Glyph[] => {
|
||||
const glyphs: Glyph[] = new Array(this.font.characterSet.length);
|
||||
for (let idx = 0, len = glyphs.length; idx < len; idx++) {
|
||||
const codePoint = this.font.characterSet[idx];
|
||||
glyphs[idx] = this.font.glyphForCodePoint(codePoint);
|
||||
}
|
||||
return sortedUniq(glyphs.sort(byAscendingId), (g) => g.id);
|
||||
};
|
||||
}
|
||||
|
||||
export default CustomFontEmbedder;
|
||||
84
node_modules/pdf-lib/src/core/embedders/CustomFontSubsetEmbedder.ts
generated
vendored
Normal file
84
node_modules/pdf-lib/src/core/embedders/CustomFontSubsetEmbedder.ts
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Font, Fontkit, Glyph, Subset, TypeFeatures } from 'src/types/fontkit';
|
||||
|
||||
import CustomFontEmbedder from 'src/core/embedders/CustomFontEmbedder';
|
||||
import PDFHexString from 'src/core/objects/PDFHexString';
|
||||
import { Cache, mergeUint8Arrays, toHexStringOfMinLength } from 'src/utils';
|
||||
|
||||
/**
|
||||
* A note of thanks to the developers of https://github.com/foliojs/pdfkit, as
|
||||
* this class borrows from:
|
||||
* https://github.com/devongovett/pdfkit/blob/e71edab0dd4657b5a767804ba86c94c58d01fbca/lib/image/jpeg.coffee
|
||||
*/
|
||||
class CustomFontSubsetEmbedder extends CustomFontEmbedder {
|
||||
static async for(
|
||||
fontkit: Fontkit,
|
||||
fontData: Uint8Array,
|
||||
customFontName?: string,
|
||||
fontFeatures?: TypeFeatures,
|
||||
) {
|
||||
const font = await fontkit.create(fontData);
|
||||
return new CustomFontSubsetEmbedder(
|
||||
font,
|
||||
fontData,
|
||||
customFontName,
|
||||
fontFeatures,
|
||||
);
|
||||
}
|
||||
|
||||
private readonly subset: Subset;
|
||||
private readonly glyphs: Glyph[];
|
||||
private readonly glyphIdMap: Map<number, number>;
|
||||
|
||||
private constructor(
|
||||
font: Font,
|
||||
fontData: Uint8Array,
|
||||
customFontName?: string,
|
||||
fontFeatures?: TypeFeatures,
|
||||
) {
|
||||
super(font, fontData, customFontName, fontFeatures);
|
||||
|
||||
this.subset = this.font.createSubset();
|
||||
this.glyphs = [];
|
||||
this.glyphCache = Cache.populatedBy(() => this.glyphs);
|
||||
this.glyphIdMap = new Map();
|
||||
}
|
||||
|
||||
encodeText(text: string): PDFHexString {
|
||||
const { glyphs } = this.font.layout(text, this.fontFeatures);
|
||||
const hexCodes = new Array(glyphs.length);
|
||||
|
||||
for (let idx = 0, len = glyphs.length; idx < len; idx++) {
|
||||
const glyph = glyphs[idx];
|
||||
const subsetGlyphId = this.subset.includeGlyph(glyph);
|
||||
|
||||
this.glyphs[subsetGlyphId - 1] = glyph;
|
||||
this.glyphIdMap.set(glyph.id, subsetGlyphId);
|
||||
|
||||
hexCodes[idx] = toHexStringOfMinLength(subsetGlyphId, 4);
|
||||
}
|
||||
|
||||
this.glyphCache.invalidate();
|
||||
return PDFHexString.of(hexCodes.join(''));
|
||||
}
|
||||
|
||||
protected isCFF(): boolean {
|
||||
return (this.subset as any).cff;
|
||||
}
|
||||
|
||||
protected glyphId(glyph?: Glyph): number {
|
||||
return glyph ? this.glyphIdMap.get(glyph.id)! : -1;
|
||||
}
|
||||
|
||||
protected serializeFont(): Promise<Uint8Array> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const parts: Uint8Array[] = [];
|
||||
this.subset
|
||||
.encodeStream()
|
||||
.on('data', (bytes) => parts.push(bytes))
|
||||
.on('end', () => resolve(mergeUint8Arrays(parts)))
|
||||
.on('error' as any, (err) => reject(err));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default CustomFontSubsetEmbedder;
|
||||
95
node_modules/pdf-lib/src/core/embedders/FileEmbedder.ts
generated
vendored
Normal file
95
node_modules/pdf-lib/src/core/embedders/FileEmbedder.ts
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
import PDFString from 'src/core/objects/PDFString';
|
||||
import PDFHexString from 'src/core/objects/PDFHexString';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
|
||||
/**
|
||||
* From the PDF-A3 specification, section **3.1. Requirements - General**.
|
||||
* See:
|
||||
* * https://www.pdfa.org/wp-content/uploads/2018/10/PDF20_AN002-AF.pdf
|
||||
*/
|
||||
export enum AFRelationship {
|
||||
Source = 'Source',
|
||||
Data = 'Data',
|
||||
Alternative = 'Alternative',
|
||||
Supplement = 'Supplement',
|
||||
EncryptedPayload = 'EncryptedPayload',
|
||||
FormData = 'EncryptedPayload',
|
||||
Schema = 'Schema',
|
||||
Unspecified = 'Unspecified',
|
||||
}
|
||||
|
||||
export interface EmbeddedFileOptions {
|
||||
mimeType?: string;
|
||||
description?: string;
|
||||
creationDate?: Date;
|
||||
modificationDate?: Date;
|
||||
afRelationship?: AFRelationship;
|
||||
}
|
||||
|
||||
class FileEmbedder {
|
||||
static for(
|
||||
bytes: Uint8Array,
|
||||
fileName: string,
|
||||
options: EmbeddedFileOptions = {},
|
||||
) {
|
||||
return new FileEmbedder(bytes, fileName, options);
|
||||
}
|
||||
|
||||
private readonly fileData: Uint8Array;
|
||||
readonly fileName: string;
|
||||
readonly options: EmbeddedFileOptions;
|
||||
|
||||
private constructor(
|
||||
fileData: Uint8Array,
|
||||
fileName: string,
|
||||
options: EmbeddedFileOptions = {},
|
||||
) {
|
||||
this.fileData = fileData;
|
||||
this.fileName = fileName;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
async embedIntoContext(context: PDFContext, ref?: PDFRef): Promise<PDFRef> {
|
||||
const {
|
||||
mimeType,
|
||||
description,
|
||||
creationDate,
|
||||
modificationDate,
|
||||
afRelationship,
|
||||
} = this.options;
|
||||
|
||||
const embeddedFileStream = context.flateStream(this.fileData, {
|
||||
Type: 'EmbeddedFile',
|
||||
Subtype: mimeType ?? undefined,
|
||||
Params: {
|
||||
Size: this.fileData.length,
|
||||
CreationDate: creationDate
|
||||
? PDFString.fromDate(creationDate)
|
||||
: undefined,
|
||||
ModDate: modificationDate
|
||||
? PDFString.fromDate(modificationDate)
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
const embeddedFileStreamRef = context.register(embeddedFileStream);
|
||||
|
||||
const fileSpecDict = context.obj({
|
||||
Type: 'Filespec',
|
||||
F: PDFString.of(this.fileName), // TODO: Assert that this is plain ASCII
|
||||
UF: PDFHexString.fromText(this.fileName),
|
||||
EF: { F: embeddedFileStreamRef },
|
||||
Desc: description ? PDFHexString.fromText(description) : undefined,
|
||||
AFRelationship: afRelationship ?? undefined,
|
||||
});
|
||||
|
||||
if (ref) {
|
||||
context.assign(ref, fileSpecDict);
|
||||
return ref;
|
||||
} else {
|
||||
return context.register(fileSpecDict);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default FileEmbedder;
|
||||
45
node_modules/pdf-lib/src/core/embedders/FontFlags.ts
generated
vendored
Normal file
45
node_modules/pdf-lib/src/core/embedders/FontFlags.ts
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Font } from 'src/types/fontkit';
|
||||
|
||||
export interface FontFlagOptions {
|
||||
fixedPitch?: boolean;
|
||||
serif?: boolean;
|
||||
symbolic?: boolean;
|
||||
script?: boolean;
|
||||
nonsymbolic?: boolean;
|
||||
italic?: boolean;
|
||||
allCap?: boolean;
|
||||
smallCap?: boolean;
|
||||
forceBold?: boolean;
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
const makeFontFlags = (options: FontFlagOptions) => {
|
||||
let flags = 0;
|
||||
|
||||
const flipBit = (bit: number) => { flags |= (1 << (bit - 1)); };
|
||||
|
||||
if (options.fixedPitch) flipBit(1);
|
||||
if (options.serif) flipBit(2);
|
||||
if (options.symbolic) flipBit(3);
|
||||
if (options.script) flipBit(4);
|
||||
if (options.nonsymbolic) flipBit(6);
|
||||
if (options.italic) flipBit(7);
|
||||
if (options.allCap) flipBit(17);
|
||||
if (options.smallCap) flipBit(18);
|
||||
if (options.forceBold) flipBit(19);
|
||||
|
||||
return flags;
|
||||
};
|
||||
|
||||
// From: https://github.com/foliojs/pdfkit/blob/83f5f7243172a017adcf6a7faa5547c55982c57b/lib/font/embedded.js#L123-L129
|
||||
export const deriveFontFlags = (font: Font): number => {
|
||||
const familyClass = font['OS/2'] ? font['OS/2'].sFamilyClass : 0;
|
||||
const flags = makeFontFlags({
|
||||
fixedPitch: font.post.isFixedPitch,
|
||||
serif: 1 <= familyClass && familyClass <= 7,
|
||||
symbolic: true, // Assume the font uses non-latin characters
|
||||
script: familyClass === 10,
|
||||
italic: font.head.macStyle.italic,
|
||||
});
|
||||
return flags;
|
||||
};
|
||||
34
node_modules/pdf-lib/src/core/embedders/JavaScriptEmbedder.ts
generated
vendored
Normal file
34
node_modules/pdf-lib/src/core/embedders/JavaScriptEmbedder.ts
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
import PDFHexString from 'src/core/objects/PDFHexString';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
|
||||
class JavaScriptEmbedder {
|
||||
static for(script: string, scriptName: string) {
|
||||
return new JavaScriptEmbedder(script, scriptName);
|
||||
}
|
||||
|
||||
private readonly script: string;
|
||||
readonly scriptName: string;
|
||||
|
||||
private constructor(script: string, scriptName: string) {
|
||||
this.script = script;
|
||||
this.scriptName = scriptName;
|
||||
}
|
||||
|
||||
async embedIntoContext(context: PDFContext, ref?: PDFRef): Promise<PDFRef> {
|
||||
const jsActionDict = context.obj({
|
||||
Type: 'Action',
|
||||
S: 'JavaScript',
|
||||
JS: PDFHexString.fromText(this.script),
|
||||
});
|
||||
|
||||
if (ref) {
|
||||
context.assign(ref, jsActionDict);
|
||||
return ref;
|
||||
} else {
|
||||
return context.register(jsActionDict);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default JavaScriptEmbedder;
|
||||
127
node_modules/pdf-lib/src/core/embedders/JpegEmbedder.ts
generated
vendored
Normal file
127
node_modules/pdf-lib/src/core/embedders/JpegEmbedder.ts
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
|
||||
// prettier-ignore
|
||||
const MARKERS = [
|
||||
0xffc0, 0xffc1, 0xffc2,
|
||||
0xffc3, 0xffc5, 0xffc6,
|
||||
0xffc7, 0xffc8, 0xffc9,
|
||||
0xffca, 0xffcb, 0xffcc,
|
||||
0xffcd, 0xffce, 0xffcf,
|
||||
];
|
||||
|
||||
enum ColorSpace {
|
||||
DeviceGray = 'DeviceGray',
|
||||
DeviceRGB = 'DeviceRGB',
|
||||
DeviceCMYK = 'DeviceCMYK',
|
||||
}
|
||||
|
||||
const ChannelToColorSpace: { [idx: number]: ColorSpace | undefined } = {
|
||||
1: ColorSpace.DeviceGray,
|
||||
3: ColorSpace.DeviceRGB,
|
||||
4: ColorSpace.DeviceCMYK,
|
||||
};
|
||||
|
||||
/**
|
||||
* A note of thanks to the developers of https://github.com/foliojs/pdfkit, as
|
||||
* this class borrows from:
|
||||
* https://github.com/foliojs/pdfkit/blob/a6af76467ce06bd6a2af4aa7271ccac9ff152a7d/lib/image/jpeg.js
|
||||
*/
|
||||
class JpegEmbedder {
|
||||
static async for(imageData: Uint8Array) {
|
||||
const dataView = new DataView(imageData.buffer);
|
||||
|
||||
const soi = dataView.getUint16(0);
|
||||
if (soi !== 0xffd8) throw new Error('SOI not found in JPEG');
|
||||
|
||||
let pos = 2;
|
||||
let marker: number;
|
||||
|
||||
while (pos < dataView.byteLength) {
|
||||
marker = dataView.getUint16(pos);
|
||||
pos += 2;
|
||||
if (MARKERS.includes(marker)) break;
|
||||
pos += dataView.getUint16(pos);
|
||||
}
|
||||
|
||||
if (!MARKERS.includes(marker!)) throw new Error('Invalid JPEG');
|
||||
pos += 2;
|
||||
|
||||
const bitsPerComponent = dataView.getUint8(pos++);
|
||||
const height = dataView.getUint16(pos);
|
||||
pos += 2;
|
||||
|
||||
const width = dataView.getUint16(pos);
|
||||
pos += 2;
|
||||
|
||||
const channelByte = dataView.getUint8(pos++);
|
||||
const channelName = ChannelToColorSpace[channelByte];
|
||||
|
||||
if (!channelName) throw new Error('Unknown JPEG channel.');
|
||||
|
||||
const colorSpace = channelName;
|
||||
|
||||
return new JpegEmbedder(
|
||||
imageData,
|
||||
bitsPerComponent,
|
||||
width,
|
||||
height,
|
||||
colorSpace,
|
||||
);
|
||||
}
|
||||
|
||||
readonly bitsPerComponent: number;
|
||||
readonly height: number;
|
||||
readonly width: number;
|
||||
readonly colorSpace: ColorSpace;
|
||||
|
||||
private readonly imageData: Uint8Array;
|
||||
|
||||
private constructor(
|
||||
imageData: Uint8Array,
|
||||
bitsPerComponent: number,
|
||||
width: number,
|
||||
height: number,
|
||||
colorSpace: ColorSpace,
|
||||
) {
|
||||
this.imageData = imageData;
|
||||
this.bitsPerComponent = bitsPerComponent;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.colorSpace = colorSpace;
|
||||
}
|
||||
|
||||
async embedIntoContext(context: PDFContext, ref?: PDFRef): Promise<PDFRef> {
|
||||
const xObject = context.stream(this.imageData, {
|
||||
Type: 'XObject',
|
||||
Subtype: 'Image',
|
||||
BitsPerComponent: this.bitsPerComponent,
|
||||
Width: this.width,
|
||||
Height: this.height,
|
||||
ColorSpace: this.colorSpace,
|
||||
Filter: 'DCTDecode',
|
||||
|
||||
// CMYK JPEG streams in PDF are typically stored complemented,
|
||||
// with 1 as 'off' and 0 as 'on' (PDF 32000-1:2008, 8.6.4.4).
|
||||
//
|
||||
// Standalone CMYK JPEG (usually exported by Photoshop) are
|
||||
// stored inverse, with 0 as 'off' and 1 as 'on', like RGB.
|
||||
//
|
||||
// Applying a swap here as a hedge that most bytes passing
|
||||
// through this method will benefit from it.
|
||||
Decode:
|
||||
this.colorSpace === ColorSpace.DeviceCMYK
|
||||
? [1, 0, 1, 0, 1, 0, 1, 0]
|
||||
: undefined,
|
||||
});
|
||||
|
||||
if (ref) {
|
||||
context.assign(ref, xObject);
|
||||
return ref;
|
||||
} else {
|
||||
return context.register(xObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default JpegEmbedder;
|
||||
141
node_modules/pdf-lib/src/core/embedders/PDFPageEmbedder.ts
generated
vendored
Normal file
141
node_modules/pdf-lib/src/core/embedders/PDFPageEmbedder.ts
generated
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
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 /** The left of the bounding box */;
|
||||
bottom: number /** The bottom of the bounding box */;
|
||||
right: number /** The right of the bounding box */;
|
||||
top: number /** The top of the bounding box */;
|
||||
}
|
||||
|
||||
const fullPageBoundingBox = (page: PDFPageLeaf) => {
|
||||
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 };
|
||||
};
|
||||
|
||||
// Returns the identity matrix, modified to position the content of the given
|
||||
// bounding box at (0, 0).
|
||||
const boundingBoxAdjustedMatrix = (
|
||||
bb: PageBoundingBox,
|
||||
): TransformationMatrix => [1, 0, 0, 1, -bb.left, -bb.bottom];
|
||||
|
||||
class PDFPageEmbedder {
|
||||
static async for(
|
||||
page: PDFPageLeaf,
|
||||
boundingBox?: PageBoundingBox,
|
||||
transformationMatrix?: 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: PDFPageLeaf,
|
||||
boundingBox?: PageBoundingBox,
|
||||
transformationMatrix?: 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: PDFContext, ref?: PDFRef): Promise<PDFRef> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// `contents` is an array of streams which are merged to include them in the XObject.
|
||||
// This methods extracts each stream and joins them with a newline character.
|
||||
private decodeContents(contents: PDFArray) {
|
||||
const newline = Uint8Array.of(CharCodes.Newline);
|
||||
const decodedContents: Uint8Array[] = [];
|
||||
|
||||
for (let idx = 0, len = contents.size(); idx < len; idx++) {
|
||||
const stream = contents.lookup(idx, PDFStream);
|
||||
|
||||
let content: Uint8Array;
|
||||
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;
|
||||
69
node_modules/pdf-lib/src/core/embedders/PngEmbedder.ts
generated
vendored
Normal file
69
node_modules/pdf-lib/src/core/embedders/PngEmbedder.ts
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import { PNG } from 'src/utils/png';
|
||||
|
||||
/**
|
||||
* A note of thanks to the developers of https://github.com/foliojs/pdfkit, as
|
||||
* this class borrows from:
|
||||
* https://github.com/devongovett/pdfkit/blob/e71edab0dd4657b5a767804ba86c94c58d01fbca/lib/image/png.coffee
|
||||
*/
|
||||
class PngEmbedder {
|
||||
static async for(imageData: Uint8Array) {
|
||||
const png = PNG.load(imageData);
|
||||
return new PngEmbedder(png);
|
||||
}
|
||||
|
||||
readonly bitsPerComponent: number;
|
||||
readonly height: number;
|
||||
readonly width: number;
|
||||
readonly colorSpace: 'DeviceRGB';
|
||||
|
||||
private readonly image: PNG;
|
||||
|
||||
private constructor(png: PNG) {
|
||||
this.image = png;
|
||||
this.bitsPerComponent = png.bitsPerComponent;
|
||||
this.width = png.width;
|
||||
this.height = png.height;
|
||||
this.colorSpace = 'DeviceRGB';
|
||||
}
|
||||
|
||||
async embedIntoContext(context: PDFContext, ref?: PDFRef): Promise<PDFRef> {
|
||||
const SMask = this.embedAlphaChannel(context);
|
||||
|
||||
const xObject = context.flateStream(this.image.rgbChannel, {
|
||||
Type: 'XObject',
|
||||
Subtype: 'Image',
|
||||
BitsPerComponent: this.image.bitsPerComponent,
|
||||
Width: this.image.width,
|
||||
Height: this.image.height,
|
||||
ColorSpace: this.colorSpace,
|
||||
SMask,
|
||||
});
|
||||
|
||||
if (ref) {
|
||||
context.assign(ref, xObject);
|
||||
return ref;
|
||||
} else {
|
||||
return context.register(xObject);
|
||||
}
|
||||
}
|
||||
|
||||
private embedAlphaChannel(context: PDFContext): PDFRef | undefined {
|
||||
if (!this.image.alphaChannel) return undefined;
|
||||
|
||||
const xObject = context.flateStream(this.image.alphaChannel, {
|
||||
Type: 'XObject',
|
||||
Subtype: 'Image',
|
||||
Height: this.image.height,
|
||||
Width: this.image.width,
|
||||
BitsPerComponent: this.image.bitsPerComponent,
|
||||
ColorSpace: 'DeviceGray',
|
||||
Decode: [0, 1],
|
||||
});
|
||||
|
||||
return context.register(xObject);
|
||||
}
|
||||
}
|
||||
|
||||
export default PngEmbedder;
|
||||
130
node_modules/pdf-lib/src/core/embedders/StandardFontEmbedder.ts
generated
vendored
Normal file
130
node_modules/pdf-lib/src/core/embedders/StandardFontEmbedder.ts
generated
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
import {
|
||||
Encodings,
|
||||
Font,
|
||||
FontNames,
|
||||
EncodingType,
|
||||
} from '@pdf-lib/standard-fonts';
|
||||
|
||||
import PDFHexString from 'src/core/objects/PDFHexString';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import { toCodePoint, toHexString } from 'src/utils';
|
||||
|
||||
export interface Glyph {
|
||||
code: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A note of thanks to the developers of https://github.com/foliojs/pdfkit, as
|
||||
* this class borrows from:
|
||||
* https://github.com/foliojs/pdfkit/blob/f91bdd61c164a72ea06be1a43dc0a412afc3925f/lib/font/afm.coffee
|
||||
*/
|
||||
class StandardFontEmbedder {
|
||||
static for = (fontName: FontNames, customName?: string) =>
|
||||
new StandardFontEmbedder(fontName, customName);
|
||||
|
||||
readonly font: Font;
|
||||
readonly encoding: EncodingType;
|
||||
readonly fontName: string;
|
||||
readonly customName: string | undefined;
|
||||
|
||||
private constructor(fontName: FontNames, customName?: string) {
|
||||
// prettier-ignore
|
||||
this.encoding = (
|
||||
fontName === FontNames.ZapfDingbats ? Encodings.ZapfDingbats
|
||||
: fontName === FontNames.Symbol ? Encodings.Symbol
|
||||
: Encodings.WinAnsi
|
||||
);
|
||||
this.font = Font.load(fontName);
|
||||
this.fontName = this.font.FontName;
|
||||
this.customName = customName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the JavaScript string into this font. (JavaScript encodes strings in
|
||||
* Unicode, but standard fonts use either WinAnsi, ZapfDingbats, or Symbol
|
||||
* encodings)
|
||||
*/
|
||||
encodeText(text: string): PDFHexString {
|
||||
const glyphs = this.encodeTextAsGlyphs(text);
|
||||
const hexCodes = new Array(glyphs.length);
|
||||
for (let idx = 0, len = glyphs.length; idx < len; idx++) {
|
||||
hexCodes[idx] = toHexString(glyphs[idx].code);
|
||||
}
|
||||
return PDFHexString.of(hexCodes.join(''));
|
||||
}
|
||||
|
||||
widthOfTextAtSize(text: string, size: number): number {
|
||||
const glyphs = this.encodeTextAsGlyphs(text);
|
||||
let totalWidth = 0;
|
||||
|
||||
for (let idx = 0, len = glyphs.length; idx < len; idx++) {
|
||||
const left = glyphs[idx].name;
|
||||
const right = (glyphs[idx + 1] || {}).name;
|
||||
const kernAmount = this.font.getXAxisKerningForPair(left, right) || 0;
|
||||
totalWidth += this.widthOfGlyph(left) + kernAmount;
|
||||
}
|
||||
|
||||
const scale = size / 1000;
|
||||
return totalWidth * scale;
|
||||
}
|
||||
|
||||
heightOfFontAtSize(
|
||||
size: number,
|
||||
options: { descender?: boolean } = {},
|
||||
): number {
|
||||
const { descender = true } = options;
|
||||
|
||||
const { Ascender, Descender, FontBBox } = this.font;
|
||||
const yTop = Ascender || FontBBox[3];
|
||||
const yBottom = Descender || FontBBox[1];
|
||||
|
||||
let height = yTop - yBottom;
|
||||
if (!descender) height += Descender || 0;
|
||||
|
||||
return (height / 1000) * size;
|
||||
}
|
||||
|
||||
sizeOfFontAtHeight(height: number): number {
|
||||
const { Ascender, Descender, FontBBox } = this.font;
|
||||
const yTop = Ascender || FontBBox[3];
|
||||
const yBottom = Descender || FontBBox[1];
|
||||
return (1000 * height) / (yTop - yBottom);
|
||||
}
|
||||
|
||||
embedIntoContext(context: PDFContext, ref?: PDFRef): PDFRef {
|
||||
const fontDict = context.obj({
|
||||
Type: 'Font',
|
||||
Subtype: 'Type1',
|
||||
BaseFont: this.customName || this.fontName,
|
||||
|
||||
Encoding:
|
||||
this.encoding === Encodings.WinAnsi ? 'WinAnsiEncoding' : undefined,
|
||||
});
|
||||
|
||||
if (ref) {
|
||||
context.assign(ref, fontDict);
|
||||
return ref;
|
||||
} else {
|
||||
return context.register(fontDict);
|
||||
}
|
||||
}
|
||||
|
||||
private widthOfGlyph(glyphName: string): number {
|
||||
// Default to 250 if font doesn't specify a width
|
||||
return this.font.getWidthOfGlyph(glyphName) || 250;
|
||||
}
|
||||
|
||||
private encodeTextAsGlyphs(text: string): Glyph[] {
|
||||
const codePoints = Array.from(text);
|
||||
const glyphs: Glyph[] = new Array(codePoints.length);
|
||||
for (let idx = 0, len = codePoints.length; idx < len; idx++) {
|
||||
const codePoint = toCodePoint(codePoints[idx])!;
|
||||
glyphs[idx] = this.encoding.encodeUnicodeCodePoint(codePoint);
|
||||
}
|
||||
return glyphs;
|
||||
}
|
||||
}
|
||||
|
||||
export default StandardFontEmbedder;
|
||||
221
node_modules/pdf-lib/src/core/errors.ts
generated
vendored
Normal file
221
node_modules/pdf-lib/src/core/errors.ts
generated
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
// tslint:disable: max-classes-per-file
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
import { arrayAsString } from 'src/utils';
|
||||
|
||||
export class MethodNotImplementedError extends Error {
|
||||
constructor(className: string, methodName: string) {
|
||||
const msg = `Method ${className}.${methodName}() not implemented`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class PrivateConstructorError extends Error {
|
||||
constructor(className: string) {
|
||||
const msg = `Cannot construct ${className} - it has a private constructor`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class UnexpectedObjectTypeError extends Error {
|
||||
constructor(expected: any | any[], actual: any) {
|
||||
const name = (t: any) => t?.name ?? t?.constructor?.name;
|
||||
|
||||
const expectedTypes = Array.isArray(expected)
|
||||
? expected.map(name)
|
||||
: [name(expected)];
|
||||
|
||||
const msg =
|
||||
`Expected instance of ${expectedTypes.join(' or ')}, ` +
|
||||
`but got instance of ${actual ? name(actual) : actual}`;
|
||||
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class UnsupportedEncodingError extends Error {
|
||||
constructor(encoding: string) {
|
||||
const msg = `${encoding} stream encoding not supported`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class ReparseError extends Error {
|
||||
constructor(className: string, methodName: string) {
|
||||
const msg = `Cannot call ${className}.${methodName}() more than once`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class MissingCatalogError extends Error {
|
||||
constructor(ref?: PDFObject) {
|
||||
const msg = `Missing catalog (ref=${ref})`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class MissingPageContentsEmbeddingError extends Error {
|
||||
constructor() {
|
||||
const msg = `Can't embed page with missing Contents`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class UnrecognizedStreamTypeError extends Error {
|
||||
constructor(stream: any) {
|
||||
const streamType = stream?.contructor?.name ?? stream?.name ?? stream;
|
||||
const msg = `Unrecognized stream type: ${streamType}`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class PageEmbeddingMismatchedContextError extends Error {
|
||||
constructor() {
|
||||
const msg = `Found mismatched contexts while embedding pages. All pages in the array passed to \`PDFDocument.embedPages()\` must be from the same document.`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class PDFArrayIsNotRectangleError extends Error {
|
||||
constructor(size: number) {
|
||||
const msg = `Attempted to convert PDFArray with ${size} elements to rectangle, but must have exactly 4 elements.`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidPDFDateStringError extends Error {
|
||||
constructor(value: string) {
|
||||
const msg = `Attempted to convert "${value}" to a date, but it does not match the PDF date string format.`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidTargetIndexError extends Error {
|
||||
constructor(targetIndex: number, Count: number) {
|
||||
const msg = `Invalid targetIndex specified: targetIndex=${targetIndex} must be less than Count=${Count}`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class CorruptPageTreeError extends Error {
|
||||
constructor(targetIndex: number, operation: string) {
|
||||
const msg = `Failed to ${operation} at targetIndex=${targetIndex} due to corrupt page tree: It is likely that one or more 'Count' entries are invalid`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class IndexOutOfBoundsError extends Error {
|
||||
constructor(index: number, min: number, max: number) {
|
||||
const msg = `index should be at least ${min} and at most ${max}, but was actually ${index}`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidAcroFieldValueError extends Error {
|
||||
constructor() {
|
||||
const msg = `Attempted to set invalid field value`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class MultiSelectValueError extends Error {
|
||||
constructor() {
|
||||
const msg = `Attempted to select multiple values for single-select field`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class MissingDAEntryError extends Error {
|
||||
constructor(fieldName: string) {
|
||||
const msg = `No /DA (default appearance) entry found for field: ${fieldName}`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class MissingTfOperatorError extends Error {
|
||||
constructor(fieldName: string) {
|
||||
const msg = `No Tf operator found for DA of field: ${fieldName}`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/***** Parser Errors ******/
|
||||
|
||||
export interface Position {
|
||||
line: number;
|
||||
column: number;
|
||||
offset: number;
|
||||
}
|
||||
|
||||
export class NumberParsingError extends Error {
|
||||
constructor(pos: Position, value: string) {
|
||||
const msg =
|
||||
`Failed to parse number ` +
|
||||
`(line:${pos.line} col:${pos.column} offset=${pos.offset}): "${value}"`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class PDFParsingError extends Error {
|
||||
constructor(pos: Position, details: string) {
|
||||
const msg =
|
||||
`Failed to parse PDF document ` +
|
||||
`(line:${pos.line} col:${pos.column} offset=${pos.offset}): ${details}`;
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class NextByteAssertionError extends PDFParsingError {
|
||||
constructor(pos: Position, expectedByte: number, actualByte: number) {
|
||||
const msg = `Expected next byte to be ${expectedByte} but it was actually ${actualByte}`;
|
||||
super(pos, msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class PDFObjectParsingError extends PDFParsingError {
|
||||
constructor(pos: Position, byte: number) {
|
||||
const msg = `Failed to parse PDF object starting with the following byte: ${byte}`;
|
||||
super(pos, msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class PDFInvalidObjectParsingError extends PDFParsingError {
|
||||
constructor(pos: Position) {
|
||||
const msg = `Failed to parse invalid PDF object`;
|
||||
super(pos, msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class PDFStreamParsingError extends PDFParsingError {
|
||||
constructor(pos: Position) {
|
||||
const msg = `Failed to parse PDF stream`;
|
||||
super(pos, msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class UnbalancedParenthesisError extends PDFParsingError {
|
||||
constructor(pos: Position) {
|
||||
const msg = `Failed to parse PDF literal string due to unbalanced parenthesis`;
|
||||
super(pos, msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class StalledParserError extends PDFParsingError {
|
||||
constructor(pos: Position) {
|
||||
const msg = `Parser stalled`;
|
||||
super(pos, msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class MissingPDFHeaderError extends PDFParsingError {
|
||||
constructor(pos: Position) {
|
||||
const msg = `No PDF header found`;
|
||||
super(pos, msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class MissingKeywordError extends PDFParsingError {
|
||||
constructor(pos: Position, keyword: number[]) {
|
||||
const msg = `Did not find expected keyword '${arrayAsString(keyword)}'`;
|
||||
super(pos, msg);
|
||||
}
|
||||
}
|
||||
69
node_modules/pdf-lib/src/core/index.ts
generated
vendored
Normal file
69
node_modules/pdf-lib/src/core/index.ts
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
export * from 'src/core/errors';
|
||||
export { default as CharCodes } from 'src/core/syntax/CharCodes';
|
||||
|
||||
export { default as PDFContext } from 'src/core/PDFContext';
|
||||
export { default as PDFObjectCopier } from 'src/core/PDFObjectCopier';
|
||||
export { default as PDFWriter } from 'src/core/writers/PDFWriter';
|
||||
export { default as PDFStreamWriter } from 'src/core/writers/PDFStreamWriter';
|
||||
|
||||
export { default as PDFHeader } from 'src/core/document/PDFHeader';
|
||||
export { default as PDFTrailer } from 'src/core/document/PDFTrailer';
|
||||
export { default as PDFTrailerDict } from 'src/core/document/PDFTrailerDict';
|
||||
export { default as PDFCrossRefSection } from 'src/core/document/PDFCrossRefSection';
|
||||
|
||||
export { default as StandardFontEmbedder } from 'src/core/embedders/StandardFontEmbedder';
|
||||
export { default as CustomFontEmbedder } from 'src/core/embedders/CustomFontEmbedder';
|
||||
export { default as CustomFontSubsetEmbedder } from 'src/core/embedders/CustomFontSubsetEmbedder';
|
||||
export {
|
||||
default as FileEmbedder,
|
||||
AFRelationship,
|
||||
} from 'src/core/embedders/FileEmbedder';
|
||||
export { default as JpegEmbedder } from 'src/core/embedders/JpegEmbedder';
|
||||
export { default as PngEmbedder } from 'src/core/embedders/PngEmbedder';
|
||||
export {
|
||||
default as PDFPageEmbedder,
|
||||
PageBoundingBox,
|
||||
} from 'src/core/embedders/PDFPageEmbedder';
|
||||
|
||||
export {
|
||||
default as ViewerPreferences,
|
||||
NonFullScreenPageMode,
|
||||
ReadingDirection,
|
||||
PrintScaling,
|
||||
Duplex,
|
||||
} from 'src/core/interactive/ViewerPreferences';
|
||||
|
||||
export { default as PDFObject } from 'src/core/objects/PDFObject';
|
||||
export { default as PDFBool } from 'src/core/objects/PDFBool';
|
||||
export { default as PDFNumber } from 'src/core/objects/PDFNumber';
|
||||
export { default as PDFString } from 'src/core/objects/PDFString';
|
||||
export { default as PDFHexString } from 'src/core/objects/PDFHexString';
|
||||
export { default as PDFName } from 'src/core/objects/PDFName';
|
||||
export { default as PDFNull } from 'src/core/objects/PDFNull';
|
||||
export { default as PDFArray } from 'src/core/objects/PDFArray';
|
||||
export { default as PDFDict } from 'src/core/objects/PDFDict';
|
||||
export { default as PDFRef } from 'src/core/objects/PDFRef';
|
||||
export { default as PDFInvalidObject } from 'src/core/objects/PDFInvalidObject';
|
||||
export { default as PDFStream } from 'src/core/objects/PDFStream';
|
||||
export { default as PDFRawStream } from 'src/core/objects/PDFRawStream';
|
||||
|
||||
export { default as PDFCatalog } from 'src/core/structures/PDFCatalog';
|
||||
export { default as PDFContentStream } from 'src/core/structures/PDFContentStream';
|
||||
export { default as PDFCrossRefStream } from 'src/core/structures/PDFCrossRefStream';
|
||||
export { default as PDFObjectStream } from 'src/core/structures/PDFObjectStream';
|
||||
export { default as PDFPageTree } from 'src/core/structures/PDFPageTree';
|
||||
export { default as PDFPageLeaf } from 'src/core/structures/PDFPageLeaf';
|
||||
export { default as PDFFlateStream } from 'src/core/structures/PDFFlateStream';
|
||||
|
||||
export { default as PDFOperator } from 'src/core/operators/PDFOperator';
|
||||
export { default as PDFOperatorNames } from 'src/core/operators/PDFOperatorNames';
|
||||
|
||||
export { default as PDFObjectParser } from 'src/core/parser/PDFObjectParser';
|
||||
export { default as PDFObjectStreamParser } from 'src/core/parser/PDFObjectStreamParser';
|
||||
export { default as PDFParser } from 'src/core/parser/PDFParser';
|
||||
export { default as PDFXRefStreamParser } from 'src/core/parser/PDFXRefStreamParser';
|
||||
|
||||
export { decodePDFRawStream } from 'src/core/streams/decode';
|
||||
|
||||
export * from 'src/core/annotation';
|
||||
export * from 'src/core/acroform';
|
||||
579
node_modules/pdf-lib/src/core/interactive/ViewerPreferences.ts
generated
vendored
Normal file
579
node_modules/pdf-lib/src/core/interactive/ViewerPreferences.ts
generated
vendored
Normal file
@@ -0,0 +1,579 @@
|
||||
import PDFArray from 'src/core/objects/PDFArray';
|
||||
import PDFBool from 'src/core/objects/PDFBool';
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFNumber from 'src/core/objects/PDFNumber';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import {
|
||||
assertEachIs,
|
||||
assertInteger,
|
||||
assertIsOneOf,
|
||||
assertRange,
|
||||
} from 'src/utils';
|
||||
|
||||
const asEnum = <T extends string | number, U extends { [key: string]: T }>(
|
||||
rawValue: T | undefined,
|
||||
enumType: U,
|
||||
): U[keyof U] | undefined => {
|
||||
if (rawValue === undefined) return undefined;
|
||||
return enumType[rawValue];
|
||||
};
|
||||
|
||||
export enum NonFullScreenPageMode {
|
||||
/**
|
||||
* After exiting FullScreen mode, neither the document outline nor thumbnail
|
||||
* images should be visible.
|
||||
*/
|
||||
UseNone = 'UseNone',
|
||||
|
||||
/** After exiting FullScreen mode, the document outline should be visible. */
|
||||
UseOutlines = 'UseOutlines',
|
||||
|
||||
/** After exiting FullScreen mode, thumbnail images should be visible. */
|
||||
UseThumbs = 'UseThumbs',
|
||||
|
||||
/**
|
||||
* After exiting FullScreen mode, the optional content group panel should be
|
||||
* visible.
|
||||
*/
|
||||
UseOC = 'UseOC',
|
||||
}
|
||||
|
||||
export enum ReadingDirection {
|
||||
/** The predominant reading order is Left to Right. */
|
||||
L2R = 'L2R',
|
||||
|
||||
/**
|
||||
* The predominant reading order is Right to left (including vertical writing
|
||||
* systems, such as Chinese, Japanese and Korean).
|
||||
*/
|
||||
R2L = 'R2L',
|
||||
}
|
||||
|
||||
export enum PrintScaling {
|
||||
/** No page scaling. */
|
||||
None = 'None',
|
||||
|
||||
/* Use the PDF reader's default print scaling. */
|
||||
AppDefault = 'AppDefault',
|
||||
}
|
||||
|
||||
export enum Duplex {
|
||||
/** The PDF reader should print single-sided. */
|
||||
Simplex = 'Simplex',
|
||||
|
||||
/**
|
||||
* The PDF reader should print double sided and flip on the short edge of the
|
||||
* sheet.
|
||||
*/
|
||||
DuplexFlipShortEdge = 'DuplexFlipShortEdge',
|
||||
|
||||
/**
|
||||
* The PDF reader should print double sided and flip on the long edge of the
|
||||
* sheet.
|
||||
*/
|
||||
DuplexFlipLongEdge = 'DuplexFlipLongEdge',
|
||||
}
|
||||
|
||||
type BoolViewerPrefKey =
|
||||
| 'HideToolbar'
|
||||
| 'HideMenubar'
|
||||
| 'HideWindowUI'
|
||||
| 'FitWindow'
|
||||
| 'CenterWindow'
|
||||
| 'DisplayDocTitle'
|
||||
| 'PickTrayByPDFSize';
|
||||
type NameViewerPrefKey =
|
||||
| 'NonFullScreenPageMode'
|
||||
| 'Direction'
|
||||
| 'PrintScaling'
|
||||
| 'Duplex';
|
||||
|
||||
interface PageRange {
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
class ViewerPreferences {
|
||||
/** @ignore */
|
||||
readonly dict: PDFDict;
|
||||
|
||||
/** @ignore */
|
||||
static fromDict = (dict: PDFDict): ViewerPreferences =>
|
||||
new ViewerPreferences(dict);
|
||||
|
||||
/** @ignore */
|
||||
static create = (context: PDFContext) => {
|
||||
const dict = context.obj({});
|
||||
return new ViewerPreferences(dict);
|
||||
};
|
||||
|
||||
/** @ignore */
|
||||
protected constructor(dict: PDFDict) {
|
||||
this.dict = dict;
|
||||
}
|
||||
|
||||
protected lookupBool(key: BoolViewerPrefKey): PDFBool | undefined {
|
||||
const returnObj = this.dict.lookup(PDFName.of(key));
|
||||
if (returnObj instanceof PDFBool) return returnObj;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected lookupName(key: NameViewerPrefKey): PDFName | undefined {
|
||||
const returnObj = this.dict.lookup(PDFName.of(key));
|
||||
if (returnObj instanceof PDFName) return returnObj;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
HideToolbar(): PDFBool | undefined {
|
||||
return this.lookupBool('HideToolbar');
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
HideMenubar(): PDFBool | undefined {
|
||||
return this.lookupBool('HideMenubar');
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
HideWindowUI(): PDFBool | undefined {
|
||||
return this.lookupBool('HideWindowUI');
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
FitWindow(): PDFBool | undefined {
|
||||
return this.lookupBool('FitWindow');
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
CenterWindow(): PDFBool | undefined {
|
||||
return this.lookupBool('CenterWindow');
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
DisplayDocTitle(): PDFBool | undefined {
|
||||
return this.lookupBool('DisplayDocTitle');
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
NonFullScreenPageMode(): PDFName | undefined {
|
||||
return this.lookupName('NonFullScreenPageMode');
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
Direction(): PDFName | undefined {
|
||||
return this.lookupName('Direction');
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
PrintScaling(): PDFName | undefined {
|
||||
return this.lookupName('PrintScaling');
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
Duplex(): PDFName | undefined {
|
||||
return this.lookupName('Duplex');
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
PickTrayByPDFSize(): PDFBool | undefined {
|
||||
return this.lookupBool('PickTrayByPDFSize');
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
PrintPageRange(): PDFArray | undefined {
|
||||
const PrintPageRange = this.dict.lookup(PDFName.of('PrintPageRange'));
|
||||
if (PrintPageRange instanceof PDFArray) return PrintPageRange;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
NumCopies(): PDFNumber | undefined {
|
||||
const NumCopies = this.dict.lookup(PDFName.of('NumCopies'));
|
||||
if (NumCopies instanceof PDFNumber) return NumCopies;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if PDF readers should hide the toolbar menus when displaying
|
||||
* this document.
|
||||
* @returns Whether or not toolbars should be hidden.
|
||||
*/
|
||||
getHideToolbar(): boolean {
|
||||
return this.HideToolbar()?.asBoolean() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if PDF readers should hide the menu bar when displaying this
|
||||
* document.
|
||||
* @returns Whether or not the menu bar should be hidden.
|
||||
*/
|
||||
getHideMenubar(): boolean {
|
||||
return this.HideMenubar()?.asBoolean() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if PDF readers should hide the user interface elements in
|
||||
* the document's window (such as scroll bars and navigation controls),
|
||||
* leaving only the document's contents displayed.
|
||||
* @returns Whether or not user interface elements should be hidden.
|
||||
*/
|
||||
getHideWindowUI(): boolean {
|
||||
return this.HideWindowUI()?.asBoolean() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if PDF readers should resize the document's window to fit
|
||||
* the size of the first displayed page.
|
||||
* @returns Whether or not the window should be resized to fit.
|
||||
*/
|
||||
getFitWindow(): boolean {
|
||||
return this.FitWindow()?.asBoolean() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if PDF readers should position the document's window in the
|
||||
* center of the screen.
|
||||
* @returns Whether or not to center the document window.
|
||||
*/
|
||||
getCenterWindow(): boolean {
|
||||
return this.CenterWindow()?.asBoolean() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the window's title bar should display the document
|
||||
* `Title`, taken from the document metadata (see [[PDFDocument.getTitle]]).
|
||||
* Returns `false` if the title bar should instead display the filename of the
|
||||
* PDF file.
|
||||
* @returns Whether to display the document title.
|
||||
*/
|
||||
getDisplayDocTitle(): boolean {
|
||||
return this.DisplayDocTitle()?.asBoolean() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the page mode, which tells the PDF reader how to display the
|
||||
* document after exiting full-screen mode.
|
||||
* @returns The page mode after exiting full-screen mode.
|
||||
*/
|
||||
getNonFullScreenPageMode(): NonFullScreenPageMode {
|
||||
const mode = this.NonFullScreenPageMode()?.decodeText();
|
||||
return asEnum(mode, NonFullScreenPageMode) ?? NonFullScreenPageMode.UseNone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the predominant reading order for text.
|
||||
* @returns The text reading order.
|
||||
*/
|
||||
getReadingDirection(): ReadingDirection {
|
||||
const direction = this.Direction()?.decodeText();
|
||||
return asEnum(direction, ReadingDirection) ?? ReadingDirection.L2R;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the page scaling option that the PDF reader should select when the
|
||||
* print dialog is displayed.
|
||||
* @returns The page scaling option.
|
||||
*/
|
||||
getPrintScaling(): PrintScaling {
|
||||
const scaling = this.PrintScaling()?.decodeText();
|
||||
return asEnum(scaling, PrintScaling) ?? PrintScaling.AppDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paper handling option that should be used when printing the
|
||||
* file from the print dialog.
|
||||
* @returns The paper handling option.
|
||||
*/
|
||||
getDuplex(): Duplex | undefined {
|
||||
const duplex = this.Duplex()?.decodeText();
|
||||
return asEnum(duplex, Duplex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the PDF page size should be used to select the input
|
||||
* paper tray.
|
||||
* @returns Whether or not the PDF page size should be used to select the
|
||||
* input paper tray.
|
||||
*/
|
||||
getPickTrayByPDFSize(): boolean | undefined {
|
||||
return this.PickTrayByPDFSize()?.asBoolean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of page number ranges, which are the values used to
|
||||
* initialize the print dialog box when the file is printed. Each range
|
||||
* specifies the first (`start`) and last (`end`) pages in a sub-range of
|
||||
* pages to be printed. The first page of the PDF file is denoted by 0.
|
||||
* For example:
|
||||
* ```js
|
||||
* const viewerPrefs = pdfDoc.catalog.getOrCreateViewerPreferences()
|
||||
* const includesPage3 = viewerPrefs
|
||||
* .getPrintRanges()
|
||||
* .some(pr => pr.start =< 2 && pr.end >= 2)
|
||||
* if (includesPage3) console.log('printRange includes page 3')
|
||||
* ```
|
||||
* @returns An array of objects, each with the properties `start` and `end`,
|
||||
* denoting page indices. If not, specified an empty array is
|
||||
* returned.
|
||||
*/
|
||||
getPrintPageRange(): PageRange[] {
|
||||
const rng = this.PrintPageRange();
|
||||
if (!rng) return [];
|
||||
|
||||
const pageRanges: PageRange[] = [];
|
||||
for (let i = 0; i < rng.size(); i += 2) {
|
||||
// Despite the spec clearly stating that "The first page of the PDF file
|
||||
// shall be donoted by 1", several test PDFs (spec 1.7) created in
|
||||
// Acrobat XI 11.0 and also read with Reader DC 2020.013 indicate this is
|
||||
// actually a 0 based index.
|
||||
const start = rng.lookup(i, PDFNumber).asNumber();
|
||||
const end = rng.lookup(i + 1, PDFNumber).asNumber();
|
||||
pageRanges.push({ start, end });
|
||||
}
|
||||
|
||||
return pageRanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of copies to be printed when the print dialog is opened
|
||||
* for this document.
|
||||
* @returns The default number of copies to be printed.
|
||||
*/
|
||||
getNumCopies(): number {
|
||||
return this.NumCopies()?.asNumber() ?? 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose whether the PDF reader's toolbars should be hidden while the
|
||||
* document is active.
|
||||
* @param hideToolbar `true` if the toolbar should be hidden.
|
||||
*/
|
||||
setHideToolbar(hideToolbar: boolean) {
|
||||
const HideToolbar = this.dict.context.obj(hideToolbar);
|
||||
this.dict.set(PDFName.of('HideToolbar'), HideToolbar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose whether the PDF reader's menu bar should be hidden while the
|
||||
* document is active.
|
||||
* @param hideMenubar `true` if the menu bar should be hidden.
|
||||
*/
|
||||
setHideMenubar(hideMenubar: boolean) {
|
||||
const HideMenubar = this.dict.context.obj(hideMenubar);
|
||||
this.dict.set(PDFName.of('HideMenubar'), HideMenubar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose whether the PDF reader should hide user interface elements in the
|
||||
* document's window (such as scroll bars and navigation controls), leaving
|
||||
* only the document's contents displayed.
|
||||
* @param hideWindowUI `true` if the user interface elements should be hidden.
|
||||
*/
|
||||
setHideWindowUI(hideWindowUI: boolean) {
|
||||
const HideWindowUI = this.dict.context.obj(hideWindowUI);
|
||||
this.dict.set(PDFName.of('HideWindowUI'), HideWindowUI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose whether the PDF reader should resize the document's window to fit
|
||||
* the size of the first displayed page.
|
||||
* @param fitWindow `true` if the window should be resized.
|
||||
*/
|
||||
setFitWindow(fitWindow: boolean) {
|
||||
const FitWindow = this.dict.context.obj(fitWindow);
|
||||
this.dict.set(PDFName.of('FitWindow'), FitWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose whether the PDF reader should position the document's window in the
|
||||
* center of the screen.
|
||||
* @param centerWindow `true` if the window should be centered.
|
||||
*/
|
||||
setCenterWindow(centerWindow: boolean) {
|
||||
const CenterWindow = this.dict.context.obj(centerWindow);
|
||||
this.dict.set(PDFName.of('CenterWindow'), CenterWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose whether the window's title bar should display the document `Title`
|
||||
* taken from the document metadata (see [[PDFDocument.setTitle]]). If
|
||||
* `false`, the title bar should instead display the PDF filename.
|
||||
* @param displayTitle `true` if the document title should be displayed.
|
||||
*/
|
||||
setDisplayDocTitle(displayTitle: boolean) {
|
||||
const DisplayDocTitle = this.dict.context.obj(displayTitle);
|
||||
this.dict.set(PDFName.of('DisplayDocTitle'), DisplayDocTitle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose how the PDF reader should display the document upon exiting
|
||||
* full-screen mode. This entry is meaningful only if the value of the
|
||||
* `PageMode` entry in the document's [[PDFCatalog]] is `FullScreen`.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* import { PDFDocument, NonFullScreenPageMode, PDFName } from 'pdf-lib'
|
||||
*
|
||||
* const pdfDoc = await PDFDocument.create()
|
||||
*
|
||||
* // Set the PageMode
|
||||
* pdfDoc.catalog.set(PDFName.of('PageMode'),PDFName.of('FullScreen'))
|
||||
*
|
||||
* // Set what happens when full-screen is closed
|
||||
* const viewerPrefs = pdfDoc.catalog.getOrCreateViewerPreferences()
|
||||
* viewerPrefs.setNonFullScreenPageMode(NonFullScreenPageMode.UseOutlines)
|
||||
* ```
|
||||
*
|
||||
* @param nonFullScreenPageMode How the document should be displayed upon
|
||||
* exiting full screen mode.
|
||||
*/
|
||||
setNonFullScreenPageMode(nonFullScreenPageMode: NonFullScreenPageMode) {
|
||||
assertIsOneOf(
|
||||
nonFullScreenPageMode,
|
||||
'nonFullScreenPageMode',
|
||||
NonFullScreenPageMode,
|
||||
);
|
||||
const mode = PDFName.of(nonFullScreenPageMode);
|
||||
this.dict.set(PDFName.of('NonFullScreenPageMode'), mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose the predominant reading order for text.
|
||||
*
|
||||
* This entry has no direct effect on the document's contents or page
|
||||
* numbering, but may be used to determine the relative positioning of pages
|
||||
* when displayed side by side or printed n-up.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* import { PDFDocument, ReadingDirection } from 'pdf-lib'
|
||||
*
|
||||
* const pdfDoc = await PDFDocument.create()
|
||||
* const viewerPrefs = pdfDoc.catalog.getOrCreateViewerPreferences()
|
||||
* viewerPrefs.setReadingDirection(ReadingDirection.R2L)
|
||||
* ```
|
||||
*
|
||||
* @param readingDirection The reading order for text.
|
||||
*/
|
||||
setReadingDirection(readingDirection: ReadingDirection) {
|
||||
assertIsOneOf(readingDirection, 'readingDirection', ReadingDirection);
|
||||
const direction = PDFName.of(readingDirection);
|
||||
this.dict.set(PDFName.of('Direction'), direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose the page scaling option that should be selected when a print dialog
|
||||
* is displayed for this document.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* import { PDFDocument, PrintScaling } from 'pdf-lib'
|
||||
*
|
||||
* const pdfDoc = await PDFDocument.create()
|
||||
* const viewerPrefs = pdfDoc.catalog.getOrCreateViewerPreferences()
|
||||
* viewerPrefs.setPrintScaling(PrintScaling.None)
|
||||
* ```
|
||||
*
|
||||
* @param printScaling The print scaling option.
|
||||
*/
|
||||
setPrintScaling(printScaling: PrintScaling) {
|
||||
assertIsOneOf(printScaling, 'printScaling', PrintScaling);
|
||||
const scaling = PDFName.of(printScaling);
|
||||
this.dict.set(PDFName.of('PrintScaling'), scaling);
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose the paper handling option that should be selected by default in the
|
||||
* print dialog.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* import { PDFDocument, Duplex } from 'pdf-lib'
|
||||
*
|
||||
* const pdfDoc = await PDFDocument.create()
|
||||
* const viewerPrefs = pdfDoc.catalog.getOrCreateViewerPreferences()
|
||||
* viewerPrefs.setDuplex(Duplex.DuplexFlipShortEdge)
|
||||
* ```
|
||||
*
|
||||
* @param duplex The double or single sided printing option.
|
||||
*/
|
||||
setDuplex(duplex: Duplex) {
|
||||
assertIsOneOf(duplex, 'duplex', Duplex);
|
||||
const dup = PDFName.of(duplex);
|
||||
this.dict.set(PDFName.of('Duplex'), dup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose whether the PDF document's page size should be used to select the
|
||||
* input paper tray when printing. This setting influences only the preset
|
||||
* values used to populate the print dialog presented by a PDF reader.
|
||||
*
|
||||
* If PickTrayByPDFSize is true, the check box in the print dialog associated
|
||||
* with input paper tray should be checked. This setting has no effect on
|
||||
* operating systems that do not provide the ability to pick the input tray
|
||||
* by size.
|
||||
*
|
||||
* @param pickTrayByPDFSize `true` if the document's page size should be used
|
||||
* to select the input paper tray.
|
||||
*/
|
||||
setPickTrayByPDFSize(pickTrayByPDFSize: boolean) {
|
||||
const PickTrayByPDFSize = this.dict.context.obj(pickTrayByPDFSize);
|
||||
this.dict.set(PDFName.of('PickTrayByPDFSize'), PickTrayByPDFSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose the page numbers used to initialize the print dialog box when the
|
||||
* file is printed. The first page of the PDF file is denoted by 0.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* import { PDFDocument } from 'pdf-lib'
|
||||
*
|
||||
* const pdfDoc = await PDFDocument.create()
|
||||
* const viewerPrefs = pdfDoc.catalog.getOrCreateViewerPreferences()
|
||||
*
|
||||
* // We can set the default print range to only the first page
|
||||
* viewerPrefs.setPrintPageRange({ start: 0, end: 0 })
|
||||
*
|
||||
* // Or we can supply noncontiguous ranges (e.g. pages 1, 3, and 5-7)
|
||||
* viewerPrefs.setPrintPageRange([
|
||||
* { start: 0, end: 0 },
|
||||
* { start: 2, end: 2 },
|
||||
* { start: 4, end: 6 },
|
||||
* ])
|
||||
* ```
|
||||
*
|
||||
* @param printPageRange An object or array of objects, each with the
|
||||
* properties `start` and `end`, denoting a range of
|
||||
* page indices.
|
||||
*/
|
||||
setPrintPageRange(printPageRange: PageRange[] | PageRange) {
|
||||
if (!Array.isArray(printPageRange)) printPageRange = [printPageRange];
|
||||
|
||||
const flatRange: number[] = [];
|
||||
for (let idx = 0, len = printPageRange.length; idx < len; idx++) {
|
||||
flatRange.push(printPageRange[idx].start);
|
||||
flatRange.push(printPageRange[idx].end);
|
||||
}
|
||||
|
||||
assertEachIs(flatRange, 'printPageRange', ['number']);
|
||||
|
||||
const pageRanges = this.dict.context.obj(flatRange);
|
||||
this.dict.set(PDFName.of('PrintPageRange'), pageRanges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose the default number of copies to be printed when the print dialog is
|
||||
* opened for this file.
|
||||
* @param numCopies The default number of copies.
|
||||
*/
|
||||
setNumCopies(numCopies: number) {
|
||||
assertRange(numCopies, 'numCopies', 1, Number.MAX_VALUE);
|
||||
assertInteger(numCopies, 'numCopies');
|
||||
const NumCopies = this.dict.context.obj(numCopies);
|
||||
this.dict.set(PDFName.of('NumCopies'), NumCopies);
|
||||
}
|
||||
}
|
||||
|
||||
export default ViewerPreferences;
|
||||
185
node_modules/pdf-lib/src/core/objects/PDFArray.ts
generated
vendored
Normal file
185
node_modules/pdf-lib/src/core/objects/PDFArray.ts
generated
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
import PDFBool from 'src/core/objects/PDFBool';
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFHexString from 'src/core/objects/PDFHexString';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFNull from 'src/core/objects/PDFNull';
|
||||
import PDFNumber from 'src/core/objects/PDFNumber';
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFStream from 'src/core/objects/PDFStream';
|
||||
import PDFString from 'src/core/objects/PDFString';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import CharCodes from 'src/core/syntax/CharCodes';
|
||||
import { PDFArrayIsNotRectangleError } from 'src/core/errors';
|
||||
import PDFRawStream from 'src/core/objects/PDFRawStream';
|
||||
|
||||
class PDFArray extends PDFObject {
|
||||
static withContext = (context: PDFContext) => new PDFArray(context);
|
||||
|
||||
private readonly array: PDFObject[];
|
||||
private readonly context: PDFContext;
|
||||
|
||||
private constructor(context: PDFContext) {
|
||||
super();
|
||||
this.array = [];
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
size(): number {
|
||||
return this.array.length;
|
||||
}
|
||||
|
||||
push(object: PDFObject): void {
|
||||
this.array.push(object);
|
||||
}
|
||||
|
||||
insert(index: number, object: PDFObject): void {
|
||||
this.array.splice(index, 0, object);
|
||||
}
|
||||
|
||||
indexOf(object: PDFObject): number | undefined {
|
||||
const index = this.array.indexOf(object);
|
||||
return index === -1 ? undefined : index;
|
||||
}
|
||||
|
||||
remove(index: number): void {
|
||||
this.array.splice(index, 1);
|
||||
}
|
||||
|
||||
set(idx: number, object: PDFObject): void {
|
||||
this.array[idx] = object;
|
||||
}
|
||||
|
||||
get(index: number): PDFObject {
|
||||
return this.array[index];
|
||||
}
|
||||
|
||||
lookupMaybe(index: number, type: typeof PDFArray): PDFArray | undefined;
|
||||
lookupMaybe(index: number, type: typeof PDFBool): PDFBool | undefined;
|
||||
lookupMaybe(index: number, type: typeof PDFDict): PDFDict | undefined;
|
||||
lookupMaybe(
|
||||
index: number,
|
||||
type: typeof PDFHexString,
|
||||
): PDFHexString | undefined;
|
||||
lookupMaybe(index: number, type: typeof PDFName): PDFName | undefined;
|
||||
lookupMaybe(index: number, type: typeof PDFNull): typeof PDFNull | undefined;
|
||||
lookupMaybe(index: number, type: typeof PDFNumber): PDFNumber | undefined;
|
||||
lookupMaybe(index: number, type: typeof PDFStream): PDFStream | undefined;
|
||||
lookupMaybe(
|
||||
index: number,
|
||||
type: typeof PDFRawStream,
|
||||
): PDFRawStream | undefined;
|
||||
lookupMaybe(index: number, type: typeof PDFRef): PDFRef | undefined;
|
||||
lookupMaybe(index: number, type: typeof PDFString): PDFString | undefined;
|
||||
lookupMaybe(
|
||||
index: number,
|
||||
type1: typeof PDFString,
|
||||
type2: typeof PDFHexString,
|
||||
): PDFString | PDFHexString | undefined;
|
||||
|
||||
lookupMaybe(index: number, ...types: any[]) {
|
||||
return this.context.lookupMaybe(
|
||||
this.get(index),
|
||||
// @ts-ignore
|
||||
...types,
|
||||
) as any;
|
||||
}
|
||||
|
||||
lookup(index: number): PDFObject | undefined;
|
||||
lookup(index: number, type: typeof PDFArray): PDFArray;
|
||||
lookup(index: number, type: typeof PDFBool): PDFBool;
|
||||
lookup(index: number, type: typeof PDFDict): PDFDict;
|
||||
lookup(index: number, type: typeof PDFHexString): PDFHexString;
|
||||
lookup(index: number, type: typeof PDFName): PDFName;
|
||||
lookup(index: number, type: typeof PDFNull): typeof PDFNull;
|
||||
lookup(index: number, type: typeof PDFNumber): PDFNumber;
|
||||
lookup(index: number, type: typeof PDFStream): PDFStream;
|
||||
lookup(index: number, type: typeof PDFRawStream): PDFRawStream;
|
||||
lookup(index: number, type: typeof PDFRef): PDFRef;
|
||||
lookup(index: number, type: typeof PDFString): PDFString;
|
||||
lookup(
|
||||
index: number,
|
||||
type1: typeof PDFString,
|
||||
type2: typeof PDFHexString,
|
||||
): PDFString | PDFHexString;
|
||||
|
||||
lookup(index: number, ...types: any[]) {
|
||||
return this.context.lookup(
|
||||
this.get(index),
|
||||
// @ts-ignore
|
||||
...types,
|
||||
) as any;
|
||||
}
|
||||
|
||||
asRectangle(): { x: number; y: number; width: number; height: number } {
|
||||
if (this.size() !== 4) throw new PDFArrayIsNotRectangleError(this.size());
|
||||
|
||||
const lowerLeftX = this.lookup(0, PDFNumber).asNumber();
|
||||
const lowerLeftY = this.lookup(1, PDFNumber).asNumber();
|
||||
const upperRightX = this.lookup(2, PDFNumber).asNumber();
|
||||
const upperRightY = this.lookup(3, PDFNumber).asNumber();
|
||||
|
||||
const x = lowerLeftX;
|
||||
const y = lowerLeftY;
|
||||
const width = upperRightX - lowerLeftX;
|
||||
const height = upperRightY - lowerLeftY;
|
||||
|
||||
return { x, y, width, height };
|
||||
}
|
||||
|
||||
asArray(): PDFObject[] {
|
||||
return this.array.slice();
|
||||
}
|
||||
|
||||
clone(context?: PDFContext): PDFArray {
|
||||
const clone = PDFArray.withContext(context || this.context);
|
||||
for (let idx = 0, len = this.size(); idx < len; idx++) {
|
||||
clone.push(this.array[idx]);
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
let arrayString = '[ ';
|
||||
for (let idx = 0, len = this.size(); idx < len; idx++) {
|
||||
arrayString += this.get(idx).toString();
|
||||
arrayString += ' ';
|
||||
}
|
||||
arrayString += ']';
|
||||
return arrayString;
|
||||
}
|
||||
|
||||
sizeInBytes(): number {
|
||||
let size = 3;
|
||||
for (let idx = 0, len = this.size(); idx < len; idx++) {
|
||||
size += this.get(idx).sizeInBytes() + 1;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
copyBytesInto(buffer: Uint8Array, offset: number): number {
|
||||
const initialOffset = offset;
|
||||
|
||||
buffer[offset++] = CharCodes.LeftSquareBracket;
|
||||
buffer[offset++] = CharCodes.Space;
|
||||
for (let idx = 0, len = this.size(); idx < len; idx++) {
|
||||
offset += this.get(idx).copyBytesInto(buffer, offset);
|
||||
buffer[offset++] = CharCodes.Space;
|
||||
}
|
||||
buffer[offset++] = CharCodes.RightSquareBracket;
|
||||
|
||||
return offset - initialOffset;
|
||||
}
|
||||
|
||||
scalePDFNumbers(x: number, y: number): void {
|
||||
for (let idx = 0, len = this.size(); idx < len; idx++) {
|
||||
const el = this.lookup(idx);
|
||||
if (el instanceof PDFNumber) {
|
||||
const factor = idx % 2 === 0 ? x : y;
|
||||
this.set(idx, PDFNumber.of(el.asNumber() * factor));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFArray;
|
||||
53
node_modules/pdf-lib/src/core/objects/PDFBool.ts
generated
vendored
Normal file
53
node_modules/pdf-lib/src/core/objects/PDFBool.ts
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
import { PrivateConstructorError } from 'src/core/errors';
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
import CharCodes from 'src/core/syntax/CharCodes';
|
||||
|
||||
const ENFORCER = {};
|
||||
|
||||
class PDFBool extends PDFObject {
|
||||
static readonly True = new PDFBool(ENFORCER, true);
|
||||
static readonly False = new PDFBool(ENFORCER, false);
|
||||
|
||||
private readonly value: boolean;
|
||||
|
||||
private constructor(enforcer: any, value: boolean) {
|
||||
if (enforcer !== ENFORCER) throw new PrivateConstructorError('PDFBool');
|
||||
super();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
asBoolean(): boolean {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
clone(): PDFBool {
|
||||
return this;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return String(this.value);
|
||||
}
|
||||
|
||||
sizeInBytes(): number {
|
||||
return this.value ? 4 : 5;
|
||||
}
|
||||
|
||||
copyBytesInto(buffer: Uint8Array, offset: number): number {
|
||||
if (this.value) {
|
||||
buffer[offset++] = CharCodes.t;
|
||||
buffer[offset++] = CharCodes.r;
|
||||
buffer[offset++] = CharCodes.u;
|
||||
buffer[offset++] = CharCodes.e;
|
||||
return 4;
|
||||
} else {
|
||||
buffer[offset++] = CharCodes.f;
|
||||
buffer[offset++] = CharCodes.a;
|
||||
buffer[offset++] = CharCodes.l;
|
||||
buffer[offset++] = CharCodes.s;
|
||||
buffer[offset++] = CharCodes.e;
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFBool;
|
||||
226
node_modules/pdf-lib/src/core/objects/PDFDict.ts
generated
vendored
Normal file
226
node_modules/pdf-lib/src/core/objects/PDFDict.ts
generated
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
import PDFArray from 'src/core/objects/PDFArray';
|
||||
import PDFBool from 'src/core/objects/PDFBool';
|
||||
import PDFHexString from 'src/core/objects/PDFHexString';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFNull from 'src/core/objects/PDFNull';
|
||||
import PDFNumber from 'src/core/objects/PDFNumber';
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFStream from 'src/core/objects/PDFStream';
|
||||
import PDFString from 'src/core/objects/PDFString';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import CharCodes from 'src/core/syntax/CharCodes';
|
||||
|
||||
export type DictMap = Map<PDFName, PDFObject>;
|
||||
|
||||
class PDFDict extends PDFObject {
|
||||
static withContext = (context: PDFContext) => new PDFDict(new Map(), context);
|
||||
|
||||
static fromMapWithContext = (map: DictMap, context: PDFContext) =>
|
||||
new PDFDict(map, context);
|
||||
|
||||
readonly context: PDFContext;
|
||||
|
||||
private readonly dict: DictMap;
|
||||
|
||||
protected constructor(map: DictMap, context: PDFContext) {
|
||||
super();
|
||||
this.dict = map;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
keys(): PDFName[] {
|
||||
return Array.from(this.dict.keys());
|
||||
}
|
||||
|
||||
values(): PDFObject[] {
|
||||
return Array.from(this.dict.values());
|
||||
}
|
||||
|
||||
entries(): [PDFName, PDFObject][] {
|
||||
return Array.from(this.dict.entries());
|
||||
}
|
||||
|
||||
set(key: PDFName, value: PDFObject): void {
|
||||
this.dict.set(key, value);
|
||||
}
|
||||
|
||||
get(
|
||||
key: PDFName,
|
||||
// TODO: `preservePDFNull` is for backwards compatibility. Should be
|
||||
// removed in next breaking API change.
|
||||
preservePDFNull = false,
|
||||
): PDFObject | undefined {
|
||||
const value = this.dict.get(key);
|
||||
if (value === PDFNull && !preservePDFNull) return undefined;
|
||||
return value;
|
||||
}
|
||||
|
||||
has(key: PDFName): boolean {
|
||||
const value = this.dict.get(key);
|
||||
return value !== undefined && value !== PDFNull;
|
||||
}
|
||||
|
||||
lookupMaybe(key: PDFName, type: typeof PDFArray): PDFArray | undefined;
|
||||
lookupMaybe(key: PDFName, type: typeof PDFBool): PDFBool | undefined;
|
||||
lookupMaybe(key: PDFName, type: typeof PDFDict): PDFDict | undefined;
|
||||
lookupMaybe(
|
||||
key: PDFName,
|
||||
type: typeof PDFHexString,
|
||||
): PDFHexString | undefined;
|
||||
lookupMaybe(key: PDFName, type: typeof PDFName): PDFName | undefined;
|
||||
lookupMaybe(key: PDFName, type: typeof PDFNull): typeof PDFNull | undefined;
|
||||
lookupMaybe(key: PDFName, type: typeof PDFNumber): PDFNumber | undefined;
|
||||
lookupMaybe(key: PDFName, type: typeof PDFStream): PDFStream | undefined;
|
||||
lookupMaybe(key: PDFName, type: typeof PDFRef): PDFRef | undefined;
|
||||
lookupMaybe(key: PDFName, type: typeof PDFString): PDFString | undefined;
|
||||
lookupMaybe(
|
||||
ref: PDFName,
|
||||
type1: typeof PDFString,
|
||||
type2: typeof PDFHexString,
|
||||
): PDFString | PDFHexString | undefined;
|
||||
lookupMaybe(
|
||||
ref: PDFName,
|
||||
type1: typeof PDFDict,
|
||||
type2: typeof PDFStream,
|
||||
): PDFDict | PDFStream | undefined;
|
||||
lookupMaybe(
|
||||
ref: PDFName,
|
||||
type1: typeof PDFString,
|
||||
type2: typeof PDFHexString,
|
||||
type3: typeof PDFArray,
|
||||
): PDFString | PDFHexString | PDFArray | undefined;
|
||||
|
||||
lookupMaybe(key: PDFName, ...types: any[]) {
|
||||
// TODO: `preservePDFNull` is for backwards compatibility. Should be
|
||||
// removed in next breaking API change.
|
||||
const preservePDFNull = types.includes(PDFNull);
|
||||
|
||||
const value = this.context.lookupMaybe(
|
||||
this.get(key, preservePDFNull),
|
||||
// @ts-ignore
|
||||
...types,
|
||||
) as any;
|
||||
|
||||
if (value === PDFNull && !preservePDFNull) return undefined;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
lookup(key: PDFName): PDFObject | undefined;
|
||||
lookup(key: PDFName, type: typeof PDFArray): PDFArray;
|
||||
lookup(key: PDFName, type: typeof PDFBool): PDFBool;
|
||||
lookup(key: PDFName, type: typeof PDFDict): PDFDict;
|
||||
lookup(key: PDFName, type: typeof PDFHexString): PDFHexString;
|
||||
lookup(key: PDFName, type: typeof PDFName): PDFName;
|
||||
lookup(key: PDFName, type: typeof PDFNull): typeof PDFNull;
|
||||
lookup(key: PDFName, type: typeof PDFNumber): PDFNumber;
|
||||
lookup(key: PDFName, type: typeof PDFStream): PDFStream;
|
||||
lookup(key: PDFName, type: typeof PDFRef): PDFRef;
|
||||
lookup(key: PDFName, type: typeof PDFString): PDFString;
|
||||
lookup(
|
||||
ref: PDFName,
|
||||
type1: typeof PDFString,
|
||||
type2: typeof PDFHexString,
|
||||
): PDFString | PDFHexString;
|
||||
lookup(
|
||||
ref: PDFName,
|
||||
type1: typeof PDFDict,
|
||||
type2: typeof PDFStream,
|
||||
): PDFDict | PDFStream;
|
||||
lookup(
|
||||
ref: PDFName,
|
||||
type1: typeof PDFString,
|
||||
type2: typeof PDFHexString,
|
||||
type3: typeof PDFArray,
|
||||
): PDFString | PDFHexString | PDFArray;
|
||||
|
||||
lookup(key: PDFName, ...types: any[]) {
|
||||
// TODO: `preservePDFNull` is for backwards compatibility. Should be
|
||||
// removed in next breaking API change.
|
||||
const preservePDFNull = types.includes(PDFNull);
|
||||
|
||||
const value = this.context.lookup(
|
||||
this.get(key, preservePDFNull),
|
||||
// @ts-ignore
|
||||
...types,
|
||||
) as any;
|
||||
|
||||
if (value === PDFNull && !preservePDFNull) return undefined;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
delete(key: PDFName): boolean {
|
||||
return this.dict.delete(key);
|
||||
}
|
||||
|
||||
asMap(): Map<PDFName, PDFObject> {
|
||||
return new Map(this.dict);
|
||||
}
|
||||
|
||||
/** Generate a random key that doesn't exist in current key set */
|
||||
uniqueKey(tag = ''): PDFName {
|
||||
const existingKeys = this.keys();
|
||||
let key = PDFName.of(this.context.addRandomSuffix(tag, 10));
|
||||
while (existingKeys.includes(key)) {
|
||||
key = PDFName.of(this.context.addRandomSuffix(tag, 10));
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
clone(context?: PDFContext): PDFDict {
|
||||
const clone = PDFDict.withContext(context || this.context);
|
||||
const entries = this.entries();
|
||||
for (let idx = 0, len = entries.length; idx < len; idx++) {
|
||||
const [key, value] = entries[idx];
|
||||
clone.set(key, value);
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
let dictString = '<<\n';
|
||||
const entries = this.entries();
|
||||
for (let idx = 0, len = entries.length; idx < len; idx++) {
|
||||
const [key, value] = entries[idx];
|
||||
dictString += key.toString() + ' ' + value.toString() + '\n';
|
||||
}
|
||||
dictString += '>>';
|
||||
return dictString;
|
||||
}
|
||||
|
||||
sizeInBytes(): number {
|
||||
let size = 5;
|
||||
const entries = this.entries();
|
||||
for (let idx = 0, len = entries.length; idx < len; idx++) {
|
||||
const [key, value] = entries[idx];
|
||||
size += key.sizeInBytes() + value.sizeInBytes() + 2;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
copyBytesInto(buffer: Uint8Array, offset: number): number {
|
||||
const initialOffset = offset;
|
||||
|
||||
buffer[offset++] = CharCodes.LessThan;
|
||||
buffer[offset++] = CharCodes.LessThan;
|
||||
buffer[offset++] = CharCodes.Newline;
|
||||
|
||||
const entries = this.entries();
|
||||
for (let idx = 0, len = entries.length; idx < len; idx++) {
|
||||
const [key, value] = entries[idx];
|
||||
offset += key.copyBytesInto(buffer, offset);
|
||||
buffer[offset++] = CharCodes.Space;
|
||||
offset += value.copyBytesInto(buffer, offset);
|
||||
buffer[offset++] = CharCodes.Newline;
|
||||
}
|
||||
|
||||
buffer[offset++] = CharCodes.GreaterThan;
|
||||
buffer[offset++] = CharCodes.GreaterThan;
|
||||
|
||||
return offset - initialOffset;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFDict;
|
||||
94
node_modules/pdf-lib/src/core/objects/PDFHexString.ts
generated
vendored
Normal file
94
node_modules/pdf-lib/src/core/objects/PDFHexString.ts
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
import CharCodes from 'src/core/syntax/CharCodes';
|
||||
import {
|
||||
copyStringIntoBuffer,
|
||||
toHexStringOfMinLength,
|
||||
utf16Decode,
|
||||
utf16Encode,
|
||||
pdfDocEncodingDecode,
|
||||
parseDate,
|
||||
hasUtf16BOM,
|
||||
} from 'src/utils';
|
||||
import { InvalidPDFDateStringError } from 'src/core/errors';
|
||||
|
||||
class PDFHexString extends PDFObject {
|
||||
static of = (value: string) => new PDFHexString(value);
|
||||
|
||||
static fromText = (value: string) => {
|
||||
const encoded = utf16Encode(value);
|
||||
|
||||
let hex = '';
|
||||
for (let idx = 0, len = encoded.length; idx < len; idx++) {
|
||||
hex += toHexStringOfMinLength(encoded[idx], 4);
|
||||
}
|
||||
|
||||
return new PDFHexString(hex);
|
||||
};
|
||||
|
||||
private readonly value: string;
|
||||
|
||||
constructor(value: string) {
|
||||
super();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
asBytes(): Uint8Array {
|
||||
// Append a zero if the number of digits is odd. See PDF spec 7.3.4.3
|
||||
const hex = this.value + (this.value.length % 2 === 1 ? '0' : '');
|
||||
const hexLength = hex.length;
|
||||
|
||||
const bytes = new Uint8Array(hex.length / 2);
|
||||
|
||||
let hexOffset = 0;
|
||||
let bytesOffset = 0;
|
||||
|
||||
// Interpret each pair of hex digits as a single byte
|
||||
while (hexOffset < hexLength) {
|
||||
const byte = parseInt(hex.substring(hexOffset, hexOffset + 2), 16);
|
||||
bytes[bytesOffset] = byte;
|
||||
|
||||
hexOffset += 2;
|
||||
bytesOffset += 1;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
decodeText(): string {
|
||||
const bytes = this.asBytes();
|
||||
if (hasUtf16BOM(bytes)) return utf16Decode(bytes);
|
||||
return pdfDocEncodingDecode(bytes);
|
||||
}
|
||||
|
||||
decodeDate(): Date {
|
||||
const text = this.decodeText();
|
||||
const date = parseDate(text);
|
||||
if (!date) throw new InvalidPDFDateStringError(text);
|
||||
return date;
|
||||
}
|
||||
|
||||
asString(): string {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
clone(): PDFHexString {
|
||||
return PDFHexString.of(this.value);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `<${this.value}>`;
|
||||
}
|
||||
|
||||
sizeInBytes(): number {
|
||||
return this.value.length + 2;
|
||||
}
|
||||
|
||||
copyBytesInto(buffer: Uint8Array, offset: number): number {
|
||||
buffer[offset++] = CharCodes.LessThan;
|
||||
offset += copyStringIntoBuffer(this.value, buffer, offset);
|
||||
buffer[offset++] = CharCodes.GreaterThan;
|
||||
return this.value.length + 2;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFHexString;
|
||||
34
node_modules/pdf-lib/src/core/objects/PDFInvalidObject.ts
generated
vendored
Normal file
34
node_modules/pdf-lib/src/core/objects/PDFInvalidObject.ts
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
|
||||
class PDFInvalidObject extends PDFObject {
|
||||
static of = (data: Uint8Array) => new PDFInvalidObject(data);
|
||||
|
||||
private readonly data: Uint8Array;
|
||||
|
||||
private constructor(data: Uint8Array) {
|
||||
super();
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
clone(): PDFInvalidObject {
|
||||
return PDFInvalidObject.of(this.data.slice());
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `PDFInvalidObject(${this.data.length} bytes)`;
|
||||
}
|
||||
|
||||
sizeInBytes(): number {
|
||||
return this.data.length;
|
||||
}
|
||||
|
||||
copyBytesInto(buffer: Uint8Array, offset: number): number {
|
||||
const length = this.data.length;
|
||||
for (let idx = 0; idx < length; idx++) {
|
||||
buffer[offset++] = this.data[idx];
|
||||
}
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFInvalidObject;
|
||||
159
node_modules/pdf-lib/src/core/objects/PDFName.ts
generated
vendored
Normal file
159
node_modules/pdf-lib/src/core/objects/PDFName.ts
generated
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
import { PrivateConstructorError } from 'src/core/errors';
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
import CharCodes from 'src/core/syntax/CharCodes';
|
||||
import { IsIrregular } from 'src/core/syntax/Irregular';
|
||||
import {
|
||||
charFromHexCode,
|
||||
copyStringIntoBuffer,
|
||||
toCharCode,
|
||||
toHexString,
|
||||
} from 'src/utils';
|
||||
|
||||
const decodeName = (name: string) =>
|
||||
name.replace(/#([\dABCDEF]{2})/g, (_, hex) => charFromHexCode(hex));
|
||||
|
||||
const isRegularChar = (charCode: number) =>
|
||||
charCode >= CharCodes.ExclamationPoint &&
|
||||
charCode <= CharCodes.Tilde &&
|
||||
!IsIrregular[charCode];
|
||||
|
||||
const ENFORCER = {};
|
||||
const pool = new Map<string, PDFName>();
|
||||
|
||||
class PDFName extends PDFObject {
|
||||
static of = (name: string): PDFName => {
|
||||
const decodedValue = decodeName(name);
|
||||
|
||||
let instance = pool.get(decodedValue);
|
||||
if (!instance) {
|
||||
instance = new PDFName(ENFORCER, decodedValue);
|
||||
pool.set(decodedValue, instance);
|
||||
}
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
/* tslint:disable member-ordering */
|
||||
static readonly Length = PDFName.of('Length');
|
||||
static readonly FlateDecode = PDFName.of('FlateDecode');
|
||||
static readonly Resources = PDFName.of('Resources');
|
||||
static readonly Font = PDFName.of('Font');
|
||||
static readonly XObject = PDFName.of('XObject');
|
||||
static readonly ExtGState = PDFName.of('ExtGState');
|
||||
static readonly Contents = PDFName.of('Contents');
|
||||
static readonly Type = PDFName.of('Type');
|
||||
static readonly Parent = PDFName.of('Parent');
|
||||
static readonly MediaBox = PDFName.of('MediaBox');
|
||||
static readonly Page = PDFName.of('Page');
|
||||
static readonly Annots = PDFName.of('Annots');
|
||||
static readonly TrimBox = PDFName.of('TrimBox');
|
||||
static readonly ArtBox = PDFName.of('ArtBox');
|
||||
static readonly BleedBox = PDFName.of('BleedBox');
|
||||
static readonly CropBox = PDFName.of('CropBox');
|
||||
static readonly Rotate = PDFName.of('Rotate');
|
||||
static readonly Title = PDFName.of('Title');
|
||||
static readonly Author = PDFName.of('Author');
|
||||
static readonly Subject = PDFName.of('Subject');
|
||||
static readonly Creator = PDFName.of('Creator');
|
||||
static readonly Keywords = PDFName.of('Keywords');
|
||||
static readonly Producer = PDFName.of('Producer');
|
||||
static readonly CreationDate = PDFName.of('CreationDate');
|
||||
static readonly ModDate = PDFName.of('ModDate');
|
||||
/* tslint:enable member-ordering */
|
||||
|
||||
private readonly encodedName: string;
|
||||
|
||||
private constructor(enforcer: any, name: string) {
|
||||
if (enforcer !== ENFORCER) throw new PrivateConstructorError('PDFName');
|
||||
super();
|
||||
|
||||
let encodedName = '/';
|
||||
for (let idx = 0, len = name.length; idx < len; idx++) {
|
||||
const character = name[idx];
|
||||
const code = toCharCode(character);
|
||||
encodedName += isRegularChar(code) ? character : `#${toHexString(code)}`;
|
||||
}
|
||||
|
||||
this.encodedName = encodedName;
|
||||
}
|
||||
|
||||
asBytes(): Uint8Array {
|
||||
const bytes: number[] = [];
|
||||
|
||||
let hex = '';
|
||||
let escaped = false;
|
||||
|
||||
const pushByte = (byte?: number) => {
|
||||
if (byte !== undefined) bytes.push(byte);
|
||||
escaped = false;
|
||||
};
|
||||
|
||||
for (let idx = 1, len = this.encodedName.length; idx < len; idx++) {
|
||||
const char = this.encodedName[idx];
|
||||
const byte = toCharCode(char);
|
||||
const nextChar = this.encodedName[idx + 1];
|
||||
if (!escaped) {
|
||||
if (byte === CharCodes.Hash) escaped = true;
|
||||
else pushByte(byte);
|
||||
} else {
|
||||
if (
|
||||
(byte >= CharCodes.Zero && byte <= CharCodes.Nine) ||
|
||||
(byte >= CharCodes.a && byte <= CharCodes.f) ||
|
||||
(byte >= CharCodes.A && byte <= CharCodes.F)
|
||||
) {
|
||||
hex += char;
|
||||
if (
|
||||
hex.length === 2 ||
|
||||
!(
|
||||
(nextChar >= '0' && nextChar <= '9') ||
|
||||
(nextChar >= 'a' && nextChar <= 'f') ||
|
||||
(nextChar >= 'A' && nextChar <= 'F')
|
||||
)
|
||||
) {
|
||||
pushByte(parseInt(hex, 16));
|
||||
hex = '';
|
||||
}
|
||||
} else {
|
||||
pushByte(byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Uint8Array(bytes);
|
||||
}
|
||||
|
||||
// TODO: This should probably use `utf8Decode()`
|
||||
// TODO: Polyfill Array.from?
|
||||
decodeText(): string {
|
||||
const bytes = this.asBytes();
|
||||
return String.fromCharCode(...Array.from(bytes));
|
||||
}
|
||||
|
||||
asString(): string {
|
||||
return this.encodedName;
|
||||
}
|
||||
|
||||
/** @deprecated in favor of [[PDFName.asString]] */
|
||||
value(): string {
|
||||
return this.encodedName;
|
||||
}
|
||||
|
||||
clone(): PDFName {
|
||||
return this;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.encodedName;
|
||||
}
|
||||
|
||||
sizeInBytes(): number {
|
||||
return this.encodedName.length;
|
||||
}
|
||||
|
||||
copyBytesInto(buffer: Uint8Array, offset: number): number {
|
||||
offset += copyStringIntoBuffer(this.encodedName, buffer, offset);
|
||||
return this.encodedName.length;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFName;
|
||||
30
node_modules/pdf-lib/src/core/objects/PDFNull.ts
generated
vendored
Normal file
30
node_modules/pdf-lib/src/core/objects/PDFNull.ts
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
import CharCodes from 'src/core/syntax/CharCodes';
|
||||
|
||||
class PDFNull extends PDFObject {
|
||||
asNull(): null {
|
||||
return null;
|
||||
}
|
||||
|
||||
clone(): PDFNull {
|
||||
return this;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
sizeInBytes(): number {
|
||||
return 4;
|
||||
}
|
||||
|
||||
copyBytesInto(buffer: Uint8Array, offset: number): number {
|
||||
buffer[offset++] = CharCodes.n;
|
||||
buffer[offset++] = CharCodes.u;
|
||||
buffer[offset++] = CharCodes.l;
|
||||
buffer[offset++] = CharCodes.l;
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
export default new PDFNull();
|
||||
44
node_modules/pdf-lib/src/core/objects/PDFNumber.ts
generated
vendored
Normal file
44
node_modules/pdf-lib/src/core/objects/PDFNumber.ts
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
import { copyStringIntoBuffer, numberToString } from 'src/utils/index';
|
||||
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
|
||||
class PDFNumber extends PDFObject {
|
||||
static of = (value: number) => new PDFNumber(value);
|
||||
|
||||
private readonly numberValue: number;
|
||||
private readonly stringValue: string;
|
||||
|
||||
private constructor(value: number) {
|
||||
super();
|
||||
this.numberValue = value;
|
||||
this.stringValue = numberToString(value);
|
||||
}
|
||||
|
||||
asNumber(): number {
|
||||
return this.numberValue;
|
||||
}
|
||||
|
||||
/** @deprecated in favor of [[PDFNumber.asNumber]] */
|
||||
value(): number {
|
||||
return this.numberValue;
|
||||
}
|
||||
|
||||
clone(): PDFNumber {
|
||||
return PDFNumber.of(this.numberValue);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.stringValue;
|
||||
}
|
||||
|
||||
sizeInBytes(): number {
|
||||
return this.stringValue.length;
|
||||
}
|
||||
|
||||
copyBytesInto(buffer: Uint8Array, offset: number): number {
|
||||
offset += copyStringIntoBuffer(this.stringValue, buffer, offset);
|
||||
return this.stringValue.length;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFNumber;
|
||||
22
node_modules/pdf-lib/src/core/objects/PDFObject.ts
generated
vendored
Normal file
22
node_modules/pdf-lib/src/core/objects/PDFObject.ts
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import { MethodNotImplementedError } from 'src/core/errors';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
|
||||
class PDFObject {
|
||||
clone(_context?: PDFContext): PDFObject {
|
||||
throw new MethodNotImplementedError(this.constructor.name, 'clone');
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
throw new MethodNotImplementedError(this.constructor.name, 'toString');
|
||||
}
|
||||
|
||||
sizeInBytes(): number {
|
||||
throw new MethodNotImplementedError(this.constructor.name, 'sizeInBytes');
|
||||
}
|
||||
|
||||
copyBytesInto(_buffer: Uint8Array, _offset: number): number {
|
||||
throw new MethodNotImplementedError(this.constructor.name, 'copyBytesInto');
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFObject;
|
||||
38
node_modules/pdf-lib/src/core/objects/PDFRawStream.ts
generated
vendored
Normal file
38
node_modules/pdf-lib/src/core/objects/PDFRawStream.ts
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFStream from 'src/core/objects/PDFStream';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import { arrayAsString } from 'src/utils';
|
||||
|
||||
class PDFRawStream extends PDFStream {
|
||||
static of = (dict: PDFDict, contents: Uint8Array) =>
|
||||
new PDFRawStream(dict, contents);
|
||||
|
||||
readonly contents: Uint8Array;
|
||||
|
||||
private constructor(dict: PDFDict, contents: Uint8Array) {
|
||||
super(dict);
|
||||
this.contents = contents;
|
||||
}
|
||||
|
||||
asUint8Array(): Uint8Array {
|
||||
return this.contents.slice();
|
||||
}
|
||||
|
||||
clone(context?: PDFContext): PDFRawStream {
|
||||
return PDFRawStream.of(this.dict.clone(context), this.contents.slice());
|
||||
}
|
||||
|
||||
getContentsString(): string {
|
||||
return arrayAsString(this.contents);
|
||||
}
|
||||
|
||||
getContents(): Uint8Array {
|
||||
return this.contents;
|
||||
}
|
||||
|
||||
getContentsSize(): number {
|
||||
return this.contents.length;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFRawStream;
|
||||
55
node_modules/pdf-lib/src/core/objects/PDFRef.ts
generated
vendored
Normal file
55
node_modules/pdf-lib/src/core/objects/PDFRef.ts
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
import { PrivateConstructorError } from 'src/core/errors';
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
import { copyStringIntoBuffer } from 'src/utils';
|
||||
|
||||
const ENFORCER = {};
|
||||
const pool = new Map<string, PDFRef>();
|
||||
|
||||
class PDFRef extends PDFObject {
|
||||
static of = (objectNumber: number, generationNumber = 0) => {
|
||||
const tag = `${objectNumber} ${generationNumber} R`;
|
||||
|
||||
let instance = pool.get(tag);
|
||||
if (!instance) {
|
||||
instance = new PDFRef(ENFORCER, objectNumber, generationNumber);
|
||||
pool.set(tag, instance);
|
||||
}
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
readonly objectNumber: number;
|
||||
readonly generationNumber: number;
|
||||
readonly tag: string;
|
||||
|
||||
private constructor(
|
||||
enforcer: any,
|
||||
objectNumber: number,
|
||||
generationNumber: number,
|
||||
) {
|
||||
if (enforcer !== ENFORCER) throw new PrivateConstructorError('PDFRef');
|
||||
super();
|
||||
this.objectNumber = objectNumber;
|
||||
this.generationNumber = generationNumber;
|
||||
this.tag = `${objectNumber} ${generationNumber} R`;
|
||||
}
|
||||
|
||||
clone(): PDFRef {
|
||||
return this;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.tag;
|
||||
}
|
||||
|
||||
sizeInBytes(): number {
|
||||
return this.tag.length;
|
||||
}
|
||||
|
||||
copyBytesInto(buffer: Uint8Array, offset: number): number {
|
||||
offset += copyStringIntoBuffer(this.tag, buffer, offset);
|
||||
return this.tag.length;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFRef;
|
||||
93
node_modules/pdf-lib/src/core/objects/PDFStream.ts
generated
vendored
Normal file
93
node_modules/pdf-lib/src/core/objects/PDFStream.ts
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
import { MethodNotImplementedError } from 'src/core/errors';
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFNumber from 'src/core/objects/PDFNumber';
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import CharCodes from 'src/core/syntax/CharCodes';
|
||||
|
||||
class PDFStream extends PDFObject {
|
||||
readonly dict: PDFDict;
|
||||
|
||||
constructor(dict: PDFDict) {
|
||||
super();
|
||||
this.dict = dict;
|
||||
}
|
||||
|
||||
clone(_context?: PDFContext): PDFStream {
|
||||
throw new MethodNotImplementedError(this.constructor.name, 'clone');
|
||||
}
|
||||
|
||||
getContentsString(): string {
|
||||
throw new MethodNotImplementedError(
|
||||
this.constructor.name,
|
||||
'getContentsString',
|
||||
);
|
||||
}
|
||||
|
||||
getContents(): Uint8Array {
|
||||
throw new MethodNotImplementedError(this.constructor.name, 'getContents');
|
||||
}
|
||||
|
||||
getContentsSize(): number {
|
||||
throw new MethodNotImplementedError(
|
||||
this.constructor.name,
|
||||
'getContentsSize',
|
||||
);
|
||||
}
|
||||
|
||||
updateDict(): void {
|
||||
const contentsSize = this.getContentsSize();
|
||||
this.dict.set(PDFName.Length, PDFNumber.of(contentsSize));
|
||||
}
|
||||
|
||||
sizeInBytes(): number {
|
||||
this.updateDict();
|
||||
return this.dict.sizeInBytes() + this.getContentsSize() + 18;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
this.updateDict();
|
||||
let streamString = this.dict.toString();
|
||||
streamString += '\nstream\n';
|
||||
streamString += this.getContentsString();
|
||||
streamString += '\nendstream';
|
||||
return streamString;
|
||||
}
|
||||
|
||||
copyBytesInto(buffer: Uint8Array, offset: number): number {
|
||||
this.updateDict();
|
||||
const initialOffset = offset;
|
||||
|
||||
offset += this.dict.copyBytesInto(buffer, offset);
|
||||
buffer[offset++] = CharCodes.Newline;
|
||||
|
||||
buffer[offset++] = CharCodes.s;
|
||||
buffer[offset++] = CharCodes.t;
|
||||
buffer[offset++] = CharCodes.r;
|
||||
buffer[offset++] = CharCodes.e;
|
||||
buffer[offset++] = CharCodes.a;
|
||||
buffer[offset++] = CharCodes.m;
|
||||
buffer[offset++] = CharCodes.Newline;
|
||||
|
||||
const contents = this.getContents();
|
||||
for (let idx = 0, len = contents.length; idx < len; idx++) {
|
||||
buffer[offset++] = contents[idx];
|
||||
}
|
||||
|
||||
buffer[offset++] = CharCodes.Newline;
|
||||
buffer[offset++] = CharCodes.e;
|
||||
buffer[offset++] = CharCodes.n;
|
||||
buffer[offset++] = CharCodes.d;
|
||||
buffer[offset++] = CharCodes.s;
|
||||
buffer[offset++] = CharCodes.t;
|
||||
buffer[offset++] = CharCodes.r;
|
||||
buffer[offset++] = CharCodes.e;
|
||||
buffer[offset++] = CharCodes.a;
|
||||
buffer[offset++] = CharCodes.m;
|
||||
|
||||
return offset - initialOffset;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFStream;
|
||||
118
node_modules/pdf-lib/src/core/objects/PDFString.ts
generated
vendored
Normal file
118
node_modules/pdf-lib/src/core/objects/PDFString.ts
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
import CharCodes from 'src/core/syntax/CharCodes';
|
||||
import {
|
||||
copyStringIntoBuffer,
|
||||
padStart,
|
||||
utf16Decode,
|
||||
pdfDocEncodingDecode,
|
||||
toCharCode,
|
||||
parseDate,
|
||||
hasUtf16BOM,
|
||||
} from 'src/utils';
|
||||
import { InvalidPDFDateStringError } from 'src/core/errors';
|
||||
|
||||
class PDFString extends PDFObject {
|
||||
// The PDF spec allows newlines and parens to appear directly within a literal
|
||||
// string. These character _may_ be escaped. But they do not _have_ to be. So
|
||||
// for simplicity, we will not bother escaping them.
|
||||
static of = (value: string) => new PDFString(value);
|
||||
|
||||
static fromDate = (date: Date) => {
|
||||
const year = padStart(String(date.getUTCFullYear()), 4, '0');
|
||||
const month = padStart(String(date.getUTCMonth() + 1), 2, '0');
|
||||
const day = padStart(String(date.getUTCDate()), 2, '0');
|
||||
const hours = padStart(String(date.getUTCHours()), 2, '0');
|
||||
const mins = padStart(String(date.getUTCMinutes()), 2, '0');
|
||||
const secs = padStart(String(date.getUTCSeconds()), 2, '0');
|
||||
return new PDFString(`D:${year}${month}${day}${hours}${mins}${secs}Z`);
|
||||
};
|
||||
|
||||
private readonly value: string;
|
||||
|
||||
private constructor(value: string) {
|
||||
super();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
asBytes(): Uint8Array {
|
||||
const bytes: number[] = [];
|
||||
|
||||
let octal = '';
|
||||
let escaped = false;
|
||||
|
||||
const pushByte = (byte?: number) => {
|
||||
if (byte !== undefined) bytes.push(byte);
|
||||
escaped = false;
|
||||
};
|
||||
|
||||
for (let idx = 0, len = this.value.length; idx < len; idx++) {
|
||||
const char = this.value[idx];
|
||||
const byte = toCharCode(char);
|
||||
const nextChar = this.value[idx + 1];
|
||||
if (!escaped) {
|
||||
if (byte === CharCodes.BackSlash) escaped = true;
|
||||
else pushByte(byte);
|
||||
} else {
|
||||
if (byte === CharCodes.Newline) pushByte();
|
||||
else if (byte === CharCodes.CarriageReturn) pushByte();
|
||||
else if (byte === CharCodes.n) pushByte(CharCodes.Newline);
|
||||
else if (byte === CharCodes.r) pushByte(CharCodes.CarriageReturn);
|
||||
else if (byte === CharCodes.t) pushByte(CharCodes.Tab);
|
||||
else if (byte === CharCodes.b) pushByte(CharCodes.Backspace);
|
||||
else if (byte === CharCodes.f) pushByte(CharCodes.FormFeed);
|
||||
else if (byte === CharCodes.LeftParen) pushByte(CharCodes.LeftParen);
|
||||
else if (byte === CharCodes.RightParen) pushByte(CharCodes.RightParen);
|
||||
else if (byte === CharCodes.Backspace) pushByte(CharCodes.BackSlash);
|
||||
else if (byte >= CharCodes.Zero && byte <= CharCodes.Seven) {
|
||||
octal += char;
|
||||
if (octal.length === 3 || !(nextChar >= '0' && nextChar <= '7')) {
|
||||
pushByte(parseInt(octal, 8));
|
||||
octal = '';
|
||||
}
|
||||
} else {
|
||||
pushByte(byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Uint8Array(bytes);
|
||||
}
|
||||
|
||||
decodeText(): string {
|
||||
const bytes = this.asBytes();
|
||||
if (hasUtf16BOM(bytes)) return utf16Decode(bytes);
|
||||
return pdfDocEncodingDecode(bytes);
|
||||
}
|
||||
|
||||
decodeDate(): Date {
|
||||
const text = this.decodeText();
|
||||
const date = parseDate(text);
|
||||
if (!date) throw new InvalidPDFDateStringError(text);
|
||||
return date;
|
||||
}
|
||||
|
||||
asString(): string {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
clone(): PDFString {
|
||||
return PDFString.of(this.value);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `(${this.value})`;
|
||||
}
|
||||
|
||||
sizeInBytes(): number {
|
||||
return this.value.length + 2;
|
||||
}
|
||||
|
||||
copyBytesInto(buffer: Uint8Array, offset: number): number {
|
||||
buffer[offset++] = CharCodes.LeftParen;
|
||||
offset += copyStringIntoBuffer(this.value, buffer, offset);
|
||||
buffer[offset++] = CharCodes.RightParen;
|
||||
return this.value.length + 2;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFString;
|
||||
79
node_modules/pdf-lib/src/core/operators/PDFOperator.ts
generated
vendored
Normal file
79
node_modules/pdf-lib/src/core/operators/PDFOperator.ts
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
import PDFArray from 'src/core/objects/PDFArray';
|
||||
import PDFHexString from 'src/core/objects/PDFHexString';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFNumber from 'src/core/objects/PDFNumber';
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
import PDFString from 'src/core/objects/PDFString';
|
||||
import PDFOperatorNames from 'src/core/operators/PDFOperatorNames';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import CharCodes from 'src/core/syntax/CharCodes';
|
||||
import { copyStringIntoBuffer } from 'src/utils';
|
||||
|
||||
export type PDFOperatorArg =
|
||||
| string
|
||||
| PDFName
|
||||
| PDFArray
|
||||
| PDFNumber
|
||||
| PDFString
|
||||
| PDFHexString;
|
||||
|
||||
class PDFOperator {
|
||||
static of = (name: PDFOperatorNames, args?: PDFOperatorArg[]) =>
|
||||
new PDFOperator(name, args);
|
||||
|
||||
private readonly name: PDFOperatorNames;
|
||||
private readonly args: PDFOperatorArg[];
|
||||
|
||||
private constructor(name: PDFOperatorNames, args?: PDFOperatorArg[]) {
|
||||
this.name = name;
|
||||
this.args = args || [];
|
||||
}
|
||||
|
||||
clone(context?: PDFContext): PDFOperator {
|
||||
const args = new Array(this.args.length);
|
||||
for (let idx = 0, len = args.length; idx < len; idx++) {
|
||||
const arg = this.args[idx];
|
||||
args[idx] = arg instanceof PDFObject ? arg.clone(context) : arg;
|
||||
}
|
||||
return PDFOperator.of(this.name, args);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
let value = '';
|
||||
for (let idx = 0, len = this.args.length; idx < len; idx++) {
|
||||
value += String(this.args[idx]) + ' ';
|
||||
}
|
||||
value += this.name;
|
||||
return value;
|
||||
}
|
||||
|
||||
sizeInBytes(): number {
|
||||
let size = 0;
|
||||
for (let idx = 0, len = this.args.length; idx < len; idx++) {
|
||||
const arg = this.args[idx];
|
||||
size += (arg instanceof PDFObject ? arg.sizeInBytes() : arg.length) + 1;
|
||||
}
|
||||
size += this.name.length;
|
||||
return size;
|
||||
}
|
||||
|
||||
copyBytesInto(buffer: Uint8Array, offset: number): number {
|
||||
const initialOffset = offset;
|
||||
|
||||
for (let idx = 0, len = this.args.length; idx < len; idx++) {
|
||||
const arg = this.args[idx];
|
||||
if (arg instanceof PDFObject) {
|
||||
offset += arg.copyBytesInto(buffer, offset);
|
||||
} else {
|
||||
offset += copyStringIntoBuffer(arg, buffer, offset);
|
||||
}
|
||||
buffer[offset++] = CharCodes.Space;
|
||||
}
|
||||
|
||||
offset += copyStringIntoBuffer(this.name, buffer, offset);
|
||||
|
||||
return offset - initialOffset;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFOperator;
|
||||
92
node_modules/pdf-lib/src/core/operators/PDFOperatorNames.ts
generated
vendored
Normal file
92
node_modules/pdf-lib/src/core/operators/PDFOperatorNames.ts
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
enum PDFOperatorNames {
|
||||
// Non Stroking Color Operators
|
||||
NonStrokingColor = 'sc',
|
||||
NonStrokingColorN = 'scn',
|
||||
NonStrokingColorRgb = 'rg',
|
||||
NonStrokingColorGray = 'g',
|
||||
NonStrokingColorCmyk = 'k',
|
||||
NonStrokingColorspace = 'cs',
|
||||
|
||||
// Stroking Color Operators
|
||||
StrokingColor = 'SC',
|
||||
StrokingColorN = 'SCN',
|
||||
StrokingColorRgb = 'RG',
|
||||
StrokingColorGray = 'G',
|
||||
StrokingColorCmyk = 'K',
|
||||
StrokingColorspace = 'CS',
|
||||
|
||||
// Marked Content Operators
|
||||
BeginMarkedContentSequence = 'BDC',
|
||||
BeginMarkedContent = 'BMC',
|
||||
EndMarkedContent = 'EMC',
|
||||
MarkedContentPointWithProps = 'DP',
|
||||
MarkedContentPoint = 'MP',
|
||||
DrawObject = 'Do',
|
||||
|
||||
// Graphics State Operators
|
||||
ConcatTransformationMatrix = 'cm',
|
||||
PopGraphicsState = 'Q',
|
||||
PushGraphicsState = 'q',
|
||||
SetFlatness = 'i',
|
||||
SetGraphicsStateParams = 'gs',
|
||||
SetLineCapStyle = 'J',
|
||||
SetLineDashPattern = 'd',
|
||||
SetLineJoinStyle = 'j',
|
||||
SetLineMiterLimit = 'M',
|
||||
SetLineWidth = 'w',
|
||||
SetTextMatrix = 'Tm',
|
||||
SetRenderingIntent = 'ri',
|
||||
|
||||
// Graphics Operators
|
||||
AppendRectangle = 're',
|
||||
BeginInlineImage = 'BI',
|
||||
BeginInlineImageData = 'ID',
|
||||
EndInlineImage = 'EI',
|
||||
ClipEvenOdd = 'W*',
|
||||
ClipNonZero = 'W',
|
||||
CloseAndStroke = 's',
|
||||
CloseFillEvenOddAndStroke = 'b*',
|
||||
CloseFillNonZeroAndStroke = 'b',
|
||||
ClosePath = 'h',
|
||||
AppendBezierCurve = 'c',
|
||||
CurveToReplicateFinalPoint = 'y',
|
||||
CurveToReplicateInitialPoint = 'v',
|
||||
EndPath = 'n',
|
||||
FillEvenOddAndStroke = 'B*',
|
||||
FillEvenOdd = 'f*',
|
||||
FillNonZeroAndStroke = 'B',
|
||||
FillNonZero = 'f',
|
||||
LegacyFillNonZero = 'F',
|
||||
LineTo = 'l',
|
||||
MoveTo = 'm',
|
||||
ShadingFill = 'sh',
|
||||
StrokePath = 'S',
|
||||
|
||||
// Text Operators
|
||||
BeginText = 'BT',
|
||||
EndText = 'ET',
|
||||
MoveText = 'Td',
|
||||
MoveTextSetLeading = 'TD',
|
||||
NextLine = 'T*',
|
||||
SetCharacterSpacing = 'Tc',
|
||||
SetFontAndSize = 'Tf',
|
||||
SetTextHorizontalScaling = 'Tz',
|
||||
SetTextLineHeight = 'TL',
|
||||
SetTextRenderingMode = 'Tr',
|
||||
SetTextRise = 'Ts',
|
||||
SetWordSpacing = 'Tw',
|
||||
ShowText = 'Tj',
|
||||
ShowTextAdjusted = 'TJ',
|
||||
ShowTextLine = "'", // tslint:disable-line quotemark
|
||||
ShowTextLineAndSpace = '"',
|
||||
|
||||
// Type3 Font Operators
|
||||
Type3D0 = 'd0',
|
||||
Type3D1 = 'd1',
|
||||
|
||||
// Compatibility Section Operators
|
||||
BeginCompatibilitySection = 'BX',
|
||||
EndCompatibilitySection = 'EX',
|
||||
}
|
||||
|
||||
export default PDFOperatorNames;
|
||||
119
node_modules/pdf-lib/src/core/parser/BaseParser.ts
generated
vendored
Normal file
119
node_modules/pdf-lib/src/core/parser/BaseParser.ts
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
import { NumberParsingError } from 'src/core/errors';
|
||||
import ByteStream from 'src/core/parser/ByteStream';
|
||||
import CharCodes from 'src/core/syntax/CharCodes';
|
||||
import { IsDigit, IsNumeric } from 'src/core/syntax/Numeric';
|
||||
import { IsWhitespace } from 'src/core/syntax/Whitespace';
|
||||
import { charFromCode } from 'src/utils';
|
||||
|
||||
const { Newline, CarriageReturn } = CharCodes;
|
||||
|
||||
// TODO: Throw error if eof is reached before finishing object parse...
|
||||
class BaseParser {
|
||||
protected readonly bytes: ByteStream;
|
||||
protected readonly capNumbers: boolean;
|
||||
|
||||
constructor(bytes: ByteStream, capNumbers = false) {
|
||||
this.bytes = bytes;
|
||||
this.capNumbers = capNumbers;
|
||||
}
|
||||
|
||||
protected parseRawInt(): number {
|
||||
let value = '';
|
||||
|
||||
while (!this.bytes.done()) {
|
||||
const byte = this.bytes.peek();
|
||||
if (!IsDigit[byte]) break;
|
||||
value += charFromCode(this.bytes.next());
|
||||
}
|
||||
|
||||
const numberValue = Number(value);
|
||||
|
||||
if (!value || !isFinite(numberValue)) {
|
||||
throw new NumberParsingError(this.bytes.position(), value);
|
||||
}
|
||||
|
||||
return numberValue;
|
||||
}
|
||||
|
||||
// TODO: Maybe handle exponential format?
|
||||
// TODO: Compare performance of string concatenation to charFromCode(...bytes)
|
||||
protected parseRawNumber(): number {
|
||||
let value = '';
|
||||
|
||||
// Parse integer-part, the leading (+ | - | . | 0-9)
|
||||
while (!this.bytes.done()) {
|
||||
const byte = this.bytes.peek();
|
||||
if (!IsNumeric[byte]) break;
|
||||
value += charFromCode(this.bytes.next());
|
||||
if (byte === CharCodes.Period) break;
|
||||
}
|
||||
|
||||
// Parse decimal-part, the trailing (0-9)
|
||||
while (!this.bytes.done()) {
|
||||
const byte = this.bytes.peek();
|
||||
if (!IsDigit[byte]) break;
|
||||
value += charFromCode(this.bytes.next());
|
||||
}
|
||||
|
||||
const numberValue = Number(value);
|
||||
|
||||
if (!value || !isFinite(numberValue)) {
|
||||
throw new NumberParsingError(this.bytes.position(), value);
|
||||
}
|
||||
|
||||
if (numberValue > Number.MAX_SAFE_INTEGER) {
|
||||
if (this.capNumbers) {
|
||||
const msg = `Parsed number that is too large for some PDF readers: ${value}, using Number.MAX_SAFE_INTEGER instead.`;
|
||||
console.warn(msg);
|
||||
return Number.MAX_SAFE_INTEGER;
|
||||
} else {
|
||||
const msg = `Parsed number that is too large for some PDF readers: ${value}, not capping.`;
|
||||
console.warn(msg);
|
||||
}
|
||||
}
|
||||
|
||||
return numberValue;
|
||||
}
|
||||
|
||||
protected skipWhitespace(): void {
|
||||
while (!this.bytes.done() && IsWhitespace[this.bytes.peek()]) {
|
||||
this.bytes.next();
|
||||
}
|
||||
}
|
||||
|
||||
protected skipLine(): void {
|
||||
while (!this.bytes.done()) {
|
||||
const byte = this.bytes.peek();
|
||||
if (byte === Newline || byte === CarriageReturn) return;
|
||||
this.bytes.next();
|
||||
}
|
||||
}
|
||||
|
||||
protected skipComment(): boolean {
|
||||
if (this.bytes.peek() !== CharCodes.Percent) return false;
|
||||
while (!this.bytes.done()) {
|
||||
const byte = this.bytes.peek();
|
||||
if (byte === Newline || byte === CarriageReturn) return true;
|
||||
this.bytes.next();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected skipWhitespaceAndComments(): void {
|
||||
this.skipWhitespace();
|
||||
while (this.skipComment()) this.skipWhitespace();
|
||||
}
|
||||
|
||||
protected matchKeyword(keyword: number[]): boolean {
|
||||
const initialOffset = this.bytes.offset();
|
||||
for (let idx = 0, len = keyword.length; idx < len; idx++) {
|
||||
if (this.bytes.done() || this.bytes.next() !== keyword[idx]) {
|
||||
this.bytes.moveTo(initialOffset);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseParser;
|
||||
76
node_modules/pdf-lib/src/core/parser/ByteStream.ts
generated
vendored
Normal file
76
node_modules/pdf-lib/src/core/parser/ByteStream.ts
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
import { NextByteAssertionError } from 'src/core/errors';
|
||||
import PDFRawStream from 'src/core/objects/PDFRawStream';
|
||||
import { decodePDFRawStream } from 'src/core/streams/decode';
|
||||
import CharCodes from 'src/core/syntax/CharCodes';
|
||||
|
||||
// TODO: See how line/col tracking affects performance
|
||||
class ByteStream {
|
||||
static of = (bytes: Uint8Array) => new ByteStream(bytes);
|
||||
|
||||
static fromPDFRawStream = (rawStream: PDFRawStream) =>
|
||||
ByteStream.of(decodePDFRawStream(rawStream).decode());
|
||||
|
||||
private readonly bytes: Uint8Array;
|
||||
private readonly length: number;
|
||||
|
||||
private idx = 0;
|
||||
private line = 0;
|
||||
private column = 0;
|
||||
|
||||
constructor(bytes: Uint8Array) {
|
||||
this.bytes = bytes;
|
||||
this.length = this.bytes.length;
|
||||
}
|
||||
|
||||
moveTo(offset: number): void {
|
||||
this.idx = offset;
|
||||
}
|
||||
|
||||
next(): number {
|
||||
const byte = this.bytes[this.idx++];
|
||||
if (byte === CharCodes.Newline) {
|
||||
this.line += 1;
|
||||
this.column = 0;
|
||||
} else {
|
||||
this.column += 1;
|
||||
}
|
||||
return byte;
|
||||
}
|
||||
|
||||
assertNext(expected: number): number {
|
||||
if (this.peek() !== expected) {
|
||||
throw new NextByteAssertionError(this.position(), expected, this.peek());
|
||||
}
|
||||
return this.next();
|
||||
}
|
||||
|
||||
peek(): number {
|
||||
return this.bytes[this.idx];
|
||||
}
|
||||
|
||||
peekAhead(steps: number) {
|
||||
return this.bytes[this.idx + steps];
|
||||
}
|
||||
|
||||
peekAt(offset: number) {
|
||||
return this.bytes[offset];
|
||||
}
|
||||
|
||||
done(): boolean {
|
||||
return this.idx >= this.length;
|
||||
}
|
||||
|
||||
offset(): number {
|
||||
return this.idx;
|
||||
}
|
||||
|
||||
slice(start: number, end: number): Uint8Array {
|
||||
return this.bytes.slice(start, end);
|
||||
}
|
||||
|
||||
position(): { line: number; column: number; offset: number } {
|
||||
return { line: this.line, column: this.column, offset: this.idx };
|
||||
}
|
||||
}
|
||||
|
||||
export default ByteStream;
|
||||
274
node_modules/pdf-lib/src/core/parser/PDFObjectParser.ts
generated
vendored
Normal file
274
node_modules/pdf-lib/src/core/parser/PDFObjectParser.ts
generated
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
import {
|
||||
PDFObjectParsingError,
|
||||
PDFStreamParsingError,
|
||||
Position,
|
||||
UnbalancedParenthesisError,
|
||||
} from 'src/core/errors';
|
||||
import PDFArray from 'src/core/objects/PDFArray';
|
||||
import PDFBool from 'src/core/objects/PDFBool';
|
||||
import PDFDict, { DictMap } from 'src/core/objects/PDFDict';
|
||||
import PDFHexString from 'src/core/objects/PDFHexString';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFNull from 'src/core/objects/PDFNull';
|
||||
import PDFNumber from 'src/core/objects/PDFNumber';
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
import PDFRawStream from 'src/core/objects/PDFRawStream';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import PDFStream from 'src/core/objects/PDFStream';
|
||||
import PDFString from 'src/core/objects/PDFString';
|
||||
import BaseParser from 'src/core/parser/BaseParser';
|
||||
import ByteStream from 'src/core/parser/ByteStream';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import PDFCatalog from 'src/core/structures/PDFCatalog';
|
||||
import PDFPageLeaf from 'src/core/structures/PDFPageLeaf';
|
||||
import PDFPageTree from 'src/core/structures/PDFPageTree';
|
||||
import CharCodes from 'src/core/syntax/CharCodes';
|
||||
import { IsDelimiter } from 'src/core/syntax/Delimiters';
|
||||
import { Keywords } from 'src/core/syntax/Keywords';
|
||||
import { IsDigit, IsNumeric } from 'src/core/syntax/Numeric';
|
||||
import { IsWhitespace } from 'src/core/syntax/Whitespace';
|
||||
import { charFromCode } from 'src/utils';
|
||||
|
||||
// TODO: Throw error if eof is reached before finishing object parse...
|
||||
class PDFObjectParser extends BaseParser {
|
||||
static forBytes = (
|
||||
bytes: Uint8Array,
|
||||
context: PDFContext,
|
||||
capNumbers?: boolean,
|
||||
) => new PDFObjectParser(ByteStream.of(bytes), context, capNumbers);
|
||||
|
||||
static forByteStream = (
|
||||
byteStream: ByteStream,
|
||||
context: PDFContext,
|
||||
capNumbers = false,
|
||||
) => new PDFObjectParser(byteStream, context, capNumbers);
|
||||
|
||||
protected readonly context: PDFContext;
|
||||
|
||||
constructor(byteStream: ByteStream, context: PDFContext, capNumbers = false) {
|
||||
super(byteStream, capNumbers);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
// TODO: Is it possible to reduce duplicate parsing for ref lookaheads?
|
||||
parseObject(): PDFObject {
|
||||
this.skipWhitespaceAndComments();
|
||||
|
||||
if (this.matchKeyword(Keywords.true)) return PDFBool.True;
|
||||
if (this.matchKeyword(Keywords.false)) return PDFBool.False;
|
||||
if (this.matchKeyword(Keywords.null)) return PDFNull;
|
||||
|
||||
const byte = this.bytes.peek();
|
||||
|
||||
if (
|
||||
byte === CharCodes.LessThan &&
|
||||
this.bytes.peekAhead(1) === CharCodes.LessThan
|
||||
) {
|
||||
return this.parseDictOrStream();
|
||||
}
|
||||
if (byte === CharCodes.LessThan) return this.parseHexString();
|
||||
if (byte === CharCodes.LeftParen) return this.parseString();
|
||||
if (byte === CharCodes.ForwardSlash) return this.parseName();
|
||||
if (byte === CharCodes.LeftSquareBracket) return this.parseArray();
|
||||
if (IsNumeric[byte]) return this.parseNumberOrRef();
|
||||
|
||||
throw new PDFObjectParsingError(this.bytes.position(), byte);
|
||||
}
|
||||
|
||||
protected parseNumberOrRef(): PDFNumber | PDFRef {
|
||||
const firstNum = this.parseRawNumber();
|
||||
this.skipWhitespaceAndComments();
|
||||
|
||||
const lookaheadStart = this.bytes.offset();
|
||||
if (IsDigit[this.bytes.peek()]) {
|
||||
const secondNum = this.parseRawNumber();
|
||||
this.skipWhitespaceAndComments();
|
||||
if (this.bytes.peek() === CharCodes.R) {
|
||||
this.bytes.assertNext(CharCodes.R);
|
||||
return PDFRef.of(firstNum, secondNum);
|
||||
}
|
||||
}
|
||||
|
||||
this.bytes.moveTo(lookaheadStart);
|
||||
return PDFNumber.of(firstNum);
|
||||
}
|
||||
|
||||
// TODO: Maybe update PDFHexString.of() logic to remove whitespace and validate input?
|
||||
protected parseHexString(): PDFHexString {
|
||||
let value = '';
|
||||
|
||||
this.bytes.assertNext(CharCodes.LessThan);
|
||||
while (!this.bytes.done() && this.bytes.peek() !== CharCodes.GreaterThan) {
|
||||
value += charFromCode(this.bytes.next());
|
||||
}
|
||||
this.bytes.assertNext(CharCodes.GreaterThan);
|
||||
|
||||
return PDFHexString.of(value);
|
||||
}
|
||||
|
||||
protected parseString(): PDFString {
|
||||
let nestingLvl = 0;
|
||||
let isEscaped = false;
|
||||
let value = '';
|
||||
|
||||
while (!this.bytes.done()) {
|
||||
const byte = this.bytes.next();
|
||||
value += charFromCode(byte);
|
||||
|
||||
// Check for unescaped parenthesis
|
||||
if (!isEscaped) {
|
||||
if (byte === CharCodes.LeftParen) nestingLvl += 1;
|
||||
if (byte === CharCodes.RightParen) nestingLvl -= 1;
|
||||
}
|
||||
|
||||
// Track whether current character is being escaped or not
|
||||
if (byte === CharCodes.BackSlash) {
|
||||
isEscaped = !isEscaped;
|
||||
} else if (isEscaped) {
|
||||
isEscaped = false;
|
||||
}
|
||||
|
||||
// Once (if) the unescaped parenthesis balance out, return their contents
|
||||
if (nestingLvl === 0) {
|
||||
// Remove the outer parens so they aren't part of the contents
|
||||
return PDFString.of(value.substring(1, value.length - 1));
|
||||
}
|
||||
}
|
||||
|
||||
throw new UnbalancedParenthesisError(this.bytes.position());
|
||||
}
|
||||
|
||||
// TODO: Compare performance of string concatenation to charFromCode(...bytes)
|
||||
// TODO: Maybe preallocate small Uint8Array if can use charFromCode?
|
||||
protected parseName(): PDFName {
|
||||
this.bytes.assertNext(CharCodes.ForwardSlash);
|
||||
|
||||
let name = '';
|
||||
while (!this.bytes.done()) {
|
||||
const byte = this.bytes.peek();
|
||||
if (IsWhitespace[byte] || IsDelimiter[byte]) break;
|
||||
name += charFromCode(byte);
|
||||
this.bytes.next();
|
||||
}
|
||||
|
||||
return PDFName.of(name);
|
||||
}
|
||||
|
||||
protected parseArray(): PDFArray {
|
||||
this.bytes.assertNext(CharCodes.LeftSquareBracket);
|
||||
this.skipWhitespaceAndComments();
|
||||
|
||||
const pdfArray = PDFArray.withContext(this.context);
|
||||
while (this.bytes.peek() !== CharCodes.RightSquareBracket) {
|
||||
const element = this.parseObject();
|
||||
pdfArray.push(element);
|
||||
this.skipWhitespaceAndComments();
|
||||
}
|
||||
this.bytes.assertNext(CharCodes.RightSquareBracket);
|
||||
return pdfArray;
|
||||
}
|
||||
|
||||
protected parseDict(): PDFDict {
|
||||
this.bytes.assertNext(CharCodes.LessThan);
|
||||
this.bytes.assertNext(CharCodes.LessThan);
|
||||
this.skipWhitespaceAndComments();
|
||||
|
||||
const dict: DictMap = new Map();
|
||||
|
||||
while (
|
||||
!this.bytes.done() &&
|
||||
this.bytes.peek() !== CharCodes.GreaterThan &&
|
||||
this.bytes.peekAhead(1) !== CharCodes.GreaterThan
|
||||
) {
|
||||
const key = this.parseName();
|
||||
const value = this.parseObject();
|
||||
dict.set(key, value);
|
||||
this.skipWhitespaceAndComments();
|
||||
}
|
||||
|
||||
this.skipWhitespaceAndComments();
|
||||
this.bytes.assertNext(CharCodes.GreaterThan);
|
||||
this.bytes.assertNext(CharCodes.GreaterThan);
|
||||
|
||||
const Type = dict.get(PDFName.of('Type'));
|
||||
|
||||
if (Type === PDFName.of('Catalog')) {
|
||||
return PDFCatalog.fromMapWithContext(dict, this.context);
|
||||
} else if (Type === PDFName.of('Pages')) {
|
||||
return PDFPageTree.fromMapWithContext(dict, this.context);
|
||||
} else if (Type === PDFName.of('Page')) {
|
||||
return PDFPageLeaf.fromMapWithContext(dict, this.context);
|
||||
} else {
|
||||
return PDFDict.fromMapWithContext(dict, this.context);
|
||||
}
|
||||
}
|
||||
|
||||
protected parseDictOrStream(): PDFDict | PDFStream {
|
||||
const startPos = this.bytes.position();
|
||||
|
||||
const dict = this.parseDict();
|
||||
|
||||
this.skipWhitespaceAndComments();
|
||||
|
||||
if (
|
||||
!this.matchKeyword(Keywords.streamEOF1) &&
|
||||
!this.matchKeyword(Keywords.streamEOF2) &&
|
||||
!this.matchKeyword(Keywords.streamEOF3) &&
|
||||
!this.matchKeyword(Keywords.streamEOF4) &&
|
||||
!this.matchKeyword(Keywords.stream)
|
||||
) {
|
||||
return dict;
|
||||
}
|
||||
|
||||
const start = this.bytes.offset();
|
||||
let end: number;
|
||||
|
||||
const Length = dict.get(PDFName.of('Length'));
|
||||
if (Length instanceof PDFNumber) {
|
||||
end = start + Length.asNumber();
|
||||
this.bytes.moveTo(end);
|
||||
this.skipWhitespaceAndComments();
|
||||
if (!this.matchKeyword(Keywords.endstream)) {
|
||||
this.bytes.moveTo(start);
|
||||
end = this.findEndOfStreamFallback(startPos);
|
||||
}
|
||||
} else {
|
||||
end = this.findEndOfStreamFallback(startPos);
|
||||
}
|
||||
|
||||
const contents = this.bytes.slice(start, end);
|
||||
|
||||
return PDFRawStream.of(dict, contents);
|
||||
}
|
||||
|
||||
protected findEndOfStreamFallback(startPos: Position) {
|
||||
// Move to end of stream, while handling nested streams
|
||||
let nestingLvl = 1;
|
||||
let end = this.bytes.offset();
|
||||
|
||||
while (!this.bytes.done()) {
|
||||
end = this.bytes.offset();
|
||||
|
||||
if (this.matchKeyword(Keywords.stream)) {
|
||||
nestingLvl += 1;
|
||||
} else if (
|
||||
this.matchKeyword(Keywords.EOF1endstream) ||
|
||||
this.matchKeyword(Keywords.EOF2endstream) ||
|
||||
this.matchKeyword(Keywords.EOF3endstream) ||
|
||||
this.matchKeyword(Keywords.endstream)
|
||||
) {
|
||||
nestingLvl -= 1;
|
||||
} else {
|
||||
this.bytes.next();
|
||||
}
|
||||
|
||||
if (nestingLvl === 0) break;
|
||||
}
|
||||
|
||||
if (nestingLvl !== 0) throw new PDFStreamParsingError(startPos);
|
||||
|
||||
return end;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFObjectParser;
|
||||
67
node_modules/pdf-lib/src/core/parser/PDFObjectStreamParser.ts
generated
vendored
Normal file
67
node_modules/pdf-lib/src/core/parser/PDFObjectStreamParser.ts
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
import { ReparseError } from 'src/core/errors';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFNumber from 'src/core/objects/PDFNumber';
|
||||
import PDFRawStream from 'src/core/objects/PDFRawStream';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import ByteStream from 'src/core/parser/ByteStream';
|
||||
import PDFObjectParser from 'src/core/parser/PDFObjectParser';
|
||||
import { waitForTick } from 'src/utils';
|
||||
|
||||
class PDFObjectStreamParser extends PDFObjectParser {
|
||||
static forStream = (
|
||||
rawStream: PDFRawStream,
|
||||
shouldWaitForTick?: () => boolean,
|
||||
) => new PDFObjectStreamParser(rawStream, shouldWaitForTick);
|
||||
|
||||
private alreadyParsed: boolean;
|
||||
private readonly shouldWaitForTick: () => boolean;
|
||||
private readonly firstOffset: number;
|
||||
private readonly objectCount: number;
|
||||
|
||||
constructor(rawStream: PDFRawStream, shouldWaitForTick?: () => boolean) {
|
||||
super(ByteStream.fromPDFRawStream(rawStream), rawStream.dict.context);
|
||||
|
||||
const { dict } = rawStream;
|
||||
|
||||
this.alreadyParsed = false;
|
||||
this.shouldWaitForTick = shouldWaitForTick || (() => false);
|
||||
this.firstOffset = dict.lookup(PDFName.of('First'), PDFNumber).asNumber();
|
||||
this.objectCount = dict.lookup(PDFName.of('N'), PDFNumber).asNumber();
|
||||
}
|
||||
|
||||
async parseIntoContext(): Promise<void> {
|
||||
if (this.alreadyParsed) {
|
||||
throw new ReparseError('PDFObjectStreamParser', 'parseIntoContext');
|
||||
}
|
||||
this.alreadyParsed = true;
|
||||
|
||||
const offsetsAndObjectNumbers = this.parseOffsetsAndObjectNumbers();
|
||||
for (let idx = 0, len = offsetsAndObjectNumbers.length; idx < len; idx++) {
|
||||
const { objectNumber, offset } = offsetsAndObjectNumbers[idx];
|
||||
this.bytes.moveTo(this.firstOffset + offset);
|
||||
const object = this.parseObject();
|
||||
const ref = PDFRef.of(objectNumber, 0);
|
||||
this.context.assign(ref, object);
|
||||
if (this.shouldWaitForTick()) await waitForTick();
|
||||
}
|
||||
}
|
||||
|
||||
private parseOffsetsAndObjectNumbers(): {
|
||||
objectNumber: number;
|
||||
offset: number;
|
||||
}[] {
|
||||
const offsetsAndObjectNumbers = [];
|
||||
for (let idx = 0, len = this.objectCount; idx < len; idx++) {
|
||||
this.skipWhitespaceAndComments();
|
||||
const objectNumber = this.parseRawInt();
|
||||
|
||||
this.skipWhitespaceAndComments();
|
||||
const offset = this.parseRawInt();
|
||||
|
||||
offsetsAndObjectNumbers.push({ objectNumber, offset });
|
||||
}
|
||||
return offsetsAndObjectNumbers;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFObjectStreamParser;
|
||||
364
node_modules/pdf-lib/src/core/parser/PDFParser.ts
generated
vendored
Normal file
364
node_modules/pdf-lib/src/core/parser/PDFParser.ts
generated
vendored
Normal file
@@ -0,0 +1,364 @@
|
||||
import PDFCrossRefSection from 'src/core/document/PDFCrossRefSection';
|
||||
import PDFHeader from 'src/core/document/PDFHeader';
|
||||
import PDFTrailer from 'src/core/document/PDFTrailer';
|
||||
import {
|
||||
MissingKeywordError,
|
||||
MissingPDFHeaderError,
|
||||
PDFInvalidObjectParsingError,
|
||||
ReparseError,
|
||||
StalledParserError,
|
||||
} from 'src/core/errors';
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFInvalidObject from 'src/core/objects/PDFInvalidObject';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFObject from 'src/core/objects/PDFObject';
|
||||
import PDFRawStream from 'src/core/objects/PDFRawStream';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import ByteStream from 'src/core/parser/ByteStream';
|
||||
import PDFObjectParser from 'src/core/parser/PDFObjectParser';
|
||||
import PDFObjectStreamParser from 'src/core/parser/PDFObjectStreamParser';
|
||||
import PDFXRefStreamParser from 'src/core/parser/PDFXRefStreamParser';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
import CharCodes from 'src/core/syntax/CharCodes';
|
||||
import { Keywords } from 'src/core/syntax/Keywords';
|
||||
import { IsDigit } from 'src/core/syntax/Numeric';
|
||||
import { waitForTick } from 'src/utils';
|
||||
|
||||
class PDFParser extends PDFObjectParser {
|
||||
static forBytesWithOptions = (
|
||||
pdfBytes: Uint8Array,
|
||||
objectsPerTick?: number,
|
||||
throwOnInvalidObject?: boolean,
|
||||
capNumbers?: boolean,
|
||||
) =>
|
||||
new PDFParser(pdfBytes, objectsPerTick, throwOnInvalidObject, capNumbers);
|
||||
|
||||
private readonly objectsPerTick: number;
|
||||
private readonly throwOnInvalidObject: boolean;
|
||||
private alreadyParsed = false;
|
||||
private parsedObjects = 0;
|
||||
|
||||
constructor(
|
||||
pdfBytes: Uint8Array,
|
||||
objectsPerTick = Infinity,
|
||||
throwOnInvalidObject = false,
|
||||
capNumbers = false,
|
||||
) {
|
||||
super(ByteStream.of(pdfBytes), PDFContext.create(), capNumbers);
|
||||
this.objectsPerTick = objectsPerTick;
|
||||
this.throwOnInvalidObject = throwOnInvalidObject;
|
||||
}
|
||||
|
||||
async parseDocument(): Promise<PDFContext> {
|
||||
if (this.alreadyParsed) {
|
||||
throw new ReparseError('PDFParser', 'parseDocument');
|
||||
}
|
||||
this.alreadyParsed = true;
|
||||
|
||||
this.context.header = this.parseHeader();
|
||||
|
||||
let prevOffset;
|
||||
while (!this.bytes.done()) {
|
||||
await this.parseDocumentSection();
|
||||
const offset = this.bytes.offset();
|
||||
if (offset === prevOffset) {
|
||||
throw new StalledParserError(this.bytes.position());
|
||||
}
|
||||
prevOffset = offset;
|
||||
}
|
||||
|
||||
this.maybeRecoverRoot();
|
||||
|
||||
if (this.context.lookup(PDFRef.of(0))) {
|
||||
console.warn('Removing parsed object: 0 0 R');
|
||||
this.context.delete(PDFRef.of(0));
|
||||
}
|
||||
|
||||
return this.context;
|
||||
}
|
||||
|
||||
private maybeRecoverRoot(): void {
|
||||
const isValidCatalog = (obj?: PDFObject) =>
|
||||
obj instanceof PDFDict &&
|
||||
obj.lookup(PDFName.of('Type')) === PDFName.of('Catalog');
|
||||
|
||||
const catalog = this.context.lookup(this.context.trailerInfo.Root);
|
||||
|
||||
if (!isValidCatalog(catalog)) {
|
||||
const indirectObjects = this.context.enumerateIndirectObjects();
|
||||
for (let idx = 0, len = indirectObjects.length; idx < len; idx++) {
|
||||
const [ref, object] = indirectObjects[idx];
|
||||
if (isValidCatalog(object)) {
|
||||
this.context.trailerInfo.Root = ref;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private parseHeader(): PDFHeader {
|
||||
while (!this.bytes.done()) {
|
||||
if (this.matchKeyword(Keywords.header)) {
|
||||
const major = this.parseRawInt();
|
||||
this.bytes.assertNext(CharCodes.Period);
|
||||
const minor = this.parseRawInt();
|
||||
const header = PDFHeader.forVersion(major, minor);
|
||||
this.skipBinaryHeaderComment();
|
||||
return header;
|
||||
}
|
||||
this.bytes.next();
|
||||
}
|
||||
|
||||
throw new MissingPDFHeaderError(this.bytes.position());
|
||||
}
|
||||
|
||||
private parseIndirectObjectHeader(): PDFRef {
|
||||
this.skipWhitespaceAndComments();
|
||||
const objectNumber = this.parseRawInt();
|
||||
|
||||
this.skipWhitespaceAndComments();
|
||||
const generationNumber = this.parseRawInt();
|
||||
|
||||
this.skipWhitespaceAndComments();
|
||||
if (!this.matchKeyword(Keywords.obj)) {
|
||||
throw new MissingKeywordError(this.bytes.position(), Keywords.obj);
|
||||
}
|
||||
|
||||
return PDFRef.of(objectNumber, generationNumber);
|
||||
}
|
||||
|
||||
private matchIndirectObjectHeader(): boolean {
|
||||
const initialOffset = this.bytes.offset();
|
||||
try {
|
||||
this.parseIndirectObjectHeader();
|
||||
return true;
|
||||
} catch (e) {
|
||||
this.bytes.moveTo(initialOffset);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private shouldWaitForTick = () => {
|
||||
this.parsedObjects += 1;
|
||||
return this.parsedObjects % this.objectsPerTick === 0;
|
||||
};
|
||||
|
||||
private async parseIndirectObject(): Promise<PDFRef> {
|
||||
const ref = this.parseIndirectObjectHeader();
|
||||
|
||||
this.skipWhitespaceAndComments();
|
||||
const object = this.parseObject();
|
||||
|
||||
this.skipWhitespaceAndComments();
|
||||
// if (!this.matchKeyword(Keywords.endobj)) {
|
||||
// throw new MissingKeywordError(this.bytes.position(), Keywords.endobj);
|
||||
// }
|
||||
|
||||
// TODO: Log a warning if this fails...
|
||||
this.matchKeyword(Keywords.endobj);
|
||||
|
||||
if (
|
||||
object instanceof PDFRawStream &&
|
||||
object.dict.lookup(PDFName.of('Type')) === PDFName.of('ObjStm')
|
||||
) {
|
||||
await PDFObjectStreamParser.forStream(
|
||||
object,
|
||||
this.shouldWaitForTick,
|
||||
).parseIntoContext();
|
||||
} else if (
|
||||
object instanceof PDFRawStream &&
|
||||
object.dict.lookup(PDFName.of('Type')) === PDFName.of('XRef')
|
||||
) {
|
||||
PDFXRefStreamParser.forStream(object).parseIntoContext();
|
||||
} else {
|
||||
this.context.assign(ref, object);
|
||||
}
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
// TODO: Improve and clean this up
|
||||
private tryToParseInvalidIndirectObject() {
|
||||
const startPos = this.bytes.position();
|
||||
|
||||
const msg = `Trying to parse invalid object: ${JSON.stringify(startPos)})`;
|
||||
if (this.throwOnInvalidObject) throw new Error(msg);
|
||||
console.warn(msg);
|
||||
|
||||
const ref = this.parseIndirectObjectHeader();
|
||||
|
||||
console.warn(`Invalid object ref: ${ref}`);
|
||||
|
||||
this.skipWhitespaceAndComments();
|
||||
const start = this.bytes.offset();
|
||||
|
||||
let failed = true;
|
||||
while (!this.bytes.done()) {
|
||||
if (this.matchKeyword(Keywords.endobj)) {
|
||||
failed = false;
|
||||
}
|
||||
if (!failed) break;
|
||||
this.bytes.next();
|
||||
}
|
||||
|
||||
if (failed) throw new PDFInvalidObjectParsingError(startPos);
|
||||
|
||||
const end = this.bytes.offset() - Keywords.endobj.length;
|
||||
|
||||
const object = PDFInvalidObject.of(this.bytes.slice(start, end));
|
||||
this.context.assign(ref, object);
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
private async parseIndirectObjects(): Promise<void> {
|
||||
this.skipWhitespaceAndComments();
|
||||
|
||||
while (!this.bytes.done() && IsDigit[this.bytes.peek()]) {
|
||||
const initialOffset = this.bytes.offset();
|
||||
|
||||
try {
|
||||
await this.parseIndirectObject();
|
||||
} catch (e) {
|
||||
// TODO: Add tracing/logging mechanism to track when this happens!
|
||||
this.bytes.moveTo(initialOffset);
|
||||
this.tryToParseInvalidIndirectObject();
|
||||
}
|
||||
this.skipWhitespaceAndComments();
|
||||
|
||||
// TODO: Can this be done only when needed, to avoid harming performance?
|
||||
this.skipJibberish();
|
||||
|
||||
if (this.shouldWaitForTick()) await waitForTick();
|
||||
}
|
||||
}
|
||||
|
||||
private maybeParseCrossRefSection(): PDFCrossRefSection | void {
|
||||
this.skipWhitespaceAndComments();
|
||||
if (!this.matchKeyword(Keywords.xref)) return;
|
||||
this.skipWhitespaceAndComments();
|
||||
|
||||
let objectNumber = -1;
|
||||
const xref = PDFCrossRefSection.createEmpty();
|
||||
|
||||
while (!this.bytes.done() && IsDigit[this.bytes.peek()]) {
|
||||
const firstInt = this.parseRawInt();
|
||||
this.skipWhitespaceAndComments();
|
||||
|
||||
const secondInt = this.parseRawInt();
|
||||
this.skipWhitespaceAndComments();
|
||||
|
||||
const byte = this.bytes.peek();
|
||||
if (byte === CharCodes.n || byte === CharCodes.f) {
|
||||
const ref = PDFRef.of(objectNumber, secondInt);
|
||||
if (this.bytes.next() === CharCodes.n) {
|
||||
xref.addEntry(ref, firstInt);
|
||||
} else {
|
||||
// this.context.delete(ref);
|
||||
xref.addDeletedEntry(ref, firstInt);
|
||||
}
|
||||
objectNumber += 1;
|
||||
} else {
|
||||
objectNumber = firstInt;
|
||||
}
|
||||
this.skipWhitespaceAndComments();
|
||||
}
|
||||
|
||||
return xref;
|
||||
}
|
||||
|
||||
private maybeParseTrailerDict(): void {
|
||||
this.skipWhitespaceAndComments();
|
||||
if (!this.matchKeyword(Keywords.trailer)) return;
|
||||
this.skipWhitespaceAndComments();
|
||||
|
||||
const dict = this.parseDict();
|
||||
|
||||
const { context } = this;
|
||||
context.trailerInfo = {
|
||||
Root: dict.get(PDFName.of('Root')) || context.trailerInfo.Root,
|
||||
Encrypt: dict.get(PDFName.of('Encrypt')) || context.trailerInfo.Encrypt,
|
||||
Info: dict.get(PDFName.of('Info')) || context.trailerInfo.Info,
|
||||
ID: dict.get(PDFName.of('ID')) || context.trailerInfo.ID,
|
||||
};
|
||||
}
|
||||
|
||||
private maybeParseTrailer(): PDFTrailer | void {
|
||||
this.skipWhitespaceAndComments();
|
||||
if (!this.matchKeyword(Keywords.startxref)) return;
|
||||
this.skipWhitespaceAndComments();
|
||||
|
||||
const offset = this.parseRawInt();
|
||||
|
||||
this.skipWhitespace();
|
||||
this.matchKeyword(Keywords.eof);
|
||||
this.skipWhitespaceAndComments();
|
||||
this.matchKeyword(Keywords.eof);
|
||||
this.skipWhitespaceAndComments();
|
||||
|
||||
return PDFTrailer.forLastCrossRefSectionOffset(offset);
|
||||
}
|
||||
|
||||
private async parseDocumentSection(): Promise<void> {
|
||||
await this.parseIndirectObjects();
|
||||
this.maybeParseCrossRefSection();
|
||||
this.maybeParseTrailerDict();
|
||||
this.maybeParseTrailer();
|
||||
|
||||
// TODO: Can this be done only when needed, to avoid harming performance?
|
||||
this.skipJibberish();
|
||||
}
|
||||
|
||||
/**
|
||||
* This operation is not necessary for valid PDF files. But some invalid PDFs
|
||||
* contain jibberish in between indirect objects. This method is designed to
|
||||
* skip past that jibberish, should it exist, until it reaches the next
|
||||
* indirect object header, an xref table section, or the file trailer.
|
||||
*/
|
||||
private skipJibberish(): void {
|
||||
this.skipWhitespaceAndComments();
|
||||
while (!this.bytes.done()) {
|
||||
const initialOffset = this.bytes.offset();
|
||||
const byte = this.bytes.peek();
|
||||
const isAlphaNumeric = byte >= CharCodes.Space && byte <= CharCodes.Tilde;
|
||||
if (isAlphaNumeric) {
|
||||
if (
|
||||
this.matchKeyword(Keywords.xref) ||
|
||||
this.matchKeyword(Keywords.trailer) ||
|
||||
this.matchKeyword(Keywords.startxref) ||
|
||||
this.matchIndirectObjectHeader()
|
||||
) {
|
||||
this.bytes.moveTo(initialOffset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.bytes.next();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips the binary comment following a PDF header. The specification
|
||||
* defines this binary comment (section 7.5.2 File Header) as a sequence of 4
|
||||
* or more bytes that are 128 or greater, and which are preceded by a "%".
|
||||
*
|
||||
* This would imply that to strip out this binary comment, we could check for
|
||||
* a sequence of bytes starting with "%", and remove all subsequent bytes that
|
||||
* are 128 or greater. This works for many documents that properly comply with
|
||||
* the spec. But in the wild, there are PDFs that omit the leading "%", and
|
||||
* include bytes that are less than 128 (e.g. 0 or 1). So in order to parse
|
||||
* these headers correctly, we just throw out all bytes leading up to the
|
||||
* first indirect object header.
|
||||
*/
|
||||
private skipBinaryHeaderComment(): void {
|
||||
this.skipWhitespaceAndComments();
|
||||
try {
|
||||
const initialOffset = this.bytes.offset();
|
||||
this.parseIndirectObjectHeader();
|
||||
this.bytes.moveTo(initialOffset);
|
||||
} catch (e) {
|
||||
this.bytes.next();
|
||||
this.skipWhitespaceAndComments();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFParser;
|
||||
130
node_modules/pdf-lib/src/core/parser/PDFXRefStreamParser.ts
generated
vendored
Normal file
130
node_modules/pdf-lib/src/core/parser/PDFXRefStreamParser.ts
generated
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
import { ReparseError } from 'src/core/errors';
|
||||
import PDFArray from 'src/core/objects/PDFArray';
|
||||
import PDFDict from 'src/core/objects/PDFDict';
|
||||
import PDFName from 'src/core/objects/PDFName';
|
||||
import PDFNumber from 'src/core/objects/PDFNumber';
|
||||
import PDFRawStream from 'src/core/objects/PDFRawStream';
|
||||
import PDFRef from 'src/core/objects/PDFRef';
|
||||
import ByteStream from 'src/core/parser/ByteStream';
|
||||
import PDFContext from 'src/core/PDFContext';
|
||||
|
||||
export interface Entry {
|
||||
ref: PDFRef;
|
||||
offset: number;
|
||||
deleted: boolean;
|
||||
inObjectStream: boolean;
|
||||
}
|
||||
|
||||
class PDFXRefStreamParser {
|
||||
static forStream = (rawStream: PDFRawStream) =>
|
||||
new PDFXRefStreamParser(rawStream);
|
||||
|
||||
private alreadyParsed: boolean;
|
||||
|
||||
private readonly dict: PDFDict;
|
||||
private readonly context: PDFContext;
|
||||
private readonly bytes: ByteStream;
|
||||
private readonly subsections: {
|
||||
firstObjectNumber: number;
|
||||
length: number;
|
||||
}[];
|
||||
private readonly byteWidths: [number, number, number];
|
||||
|
||||
constructor(rawStream: PDFRawStream) {
|
||||
this.alreadyParsed = false;
|
||||
|
||||
this.dict = rawStream.dict;
|
||||
this.bytes = ByteStream.fromPDFRawStream(rawStream);
|
||||
this.context = this.dict.context;
|
||||
|
||||
const Size = this.dict.lookup(PDFName.of('Size'), PDFNumber);
|
||||
|
||||
const Index = this.dict.lookup(PDFName.of('Index'));
|
||||
if (Index instanceof PDFArray) {
|
||||
this.subsections = [];
|
||||
for (let idx = 0, len = Index.size(); idx < len; idx += 2) {
|
||||
const firstObjectNumber = Index.lookup(idx + 0, PDFNumber).asNumber();
|
||||
const length = Index.lookup(idx + 1, PDFNumber).asNumber();
|
||||
this.subsections.push({ firstObjectNumber, length });
|
||||
}
|
||||
} else {
|
||||
this.subsections = [{ firstObjectNumber: 0, length: Size.asNumber() }];
|
||||
}
|
||||
|
||||
const W = this.dict.lookup(PDFName.of('W'), PDFArray);
|
||||
this.byteWidths = [-1, -1, -1];
|
||||
for (let idx = 0, len = W.size(); idx < len; idx++) {
|
||||
this.byteWidths[idx] = W.lookup(idx, PDFNumber).asNumber();
|
||||
}
|
||||
}
|
||||
|
||||
parseIntoContext(): Entry[] {
|
||||
if (this.alreadyParsed) {
|
||||
throw new ReparseError('PDFXRefStreamParser', 'parseIntoContext');
|
||||
}
|
||||
this.alreadyParsed = true;
|
||||
|
||||
this.context.trailerInfo = {
|
||||
Root: this.dict.get(PDFName.of('Root')),
|
||||
Encrypt: this.dict.get(PDFName.of('Encrypt')),
|
||||
Info: this.dict.get(PDFName.of('Info')),
|
||||
ID: this.dict.get(PDFName.of('ID')),
|
||||
};
|
||||
|
||||
const entries = this.parseEntries();
|
||||
|
||||
// for (let idx = 0, len = entries.length; idx < len; idx++) {
|
||||
// const entry = entries[idx];
|
||||
// if (entry.deleted) this.context.delete(entry.ref);
|
||||
// }
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private parseEntries(): Entry[] {
|
||||
const entries = [];
|
||||
const [typeFieldWidth, offsetFieldWidth, genFieldWidth] = this.byteWidths;
|
||||
|
||||
for (
|
||||
let subsectionIdx = 0, subsectionLen = this.subsections.length;
|
||||
subsectionIdx < subsectionLen;
|
||||
subsectionIdx++
|
||||
) {
|
||||
const { firstObjectNumber, length } = this.subsections[subsectionIdx];
|
||||
|
||||
for (let objIdx = 0; objIdx < length; objIdx++) {
|
||||
let type = 0;
|
||||
for (let idx = 0, len = typeFieldWidth; idx < len; idx++) {
|
||||
type = (type << 8) | this.bytes.next();
|
||||
}
|
||||
|
||||
let offset = 0;
|
||||
for (let idx = 0, len = offsetFieldWidth; idx < len; idx++) {
|
||||
offset = (offset << 8) | this.bytes.next();
|
||||
}
|
||||
|
||||
let generationNumber = 0;
|
||||
for (let idx = 0, len = genFieldWidth; idx < len; idx++) {
|
||||
generationNumber = (generationNumber << 8) | this.bytes.next();
|
||||
}
|
||||
|
||||
// When the `type` field is absent, it defaults to 1
|
||||
if (typeFieldWidth === 0) type = 1;
|
||||
|
||||
const objectNumber = firstObjectNumber + objIdx;
|
||||
const entry = {
|
||||
ref: PDFRef.of(objectNumber, generationNumber),
|
||||
offset,
|
||||
deleted: type === 0,
|
||||
inObjectStream: type === 2,
|
||||
};
|
||||
|
||||
entries.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFXRefStreamParser;
|
||||
98
node_modules/pdf-lib/src/core/streams/Ascii85Stream.ts
generated
vendored
Normal file
98
node_modules/pdf-lib/src/core/streams/Ascii85Stream.ts
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* The Ascii85Stream class contained in this file is a TypeScript port of the
|
||||
* JavaScript Ascii85Stream class in Mozilla's pdf.js project, made available
|
||||
* under the Apache 2.0 open source license.
|
||||
*/
|
||||
|
||||
import DecodeStream from 'src/core/streams/DecodeStream';
|
||||
import { StreamType } from 'src/core/streams/Stream';
|
||||
|
||||
const isSpace = (ch: number) =>
|
||||
ch === 0x20 || ch === 0x09 || ch === 0x0d || ch === 0x0a;
|
||||
|
||||
class Ascii85Stream extends DecodeStream {
|
||||
private stream: StreamType;
|
||||
private input: Uint8Array;
|
||||
|
||||
constructor(stream: StreamType, maybeLength?: number) {
|
||||
super(maybeLength);
|
||||
|
||||
this.stream = stream;
|
||||
this.input = new Uint8Array(5);
|
||||
|
||||
// Most streams increase in size when decoded, but Ascii85 streams
|
||||
// typically shrink by ~20%.
|
||||
if (maybeLength) {
|
||||
maybeLength = 0.8 * maybeLength;
|
||||
}
|
||||
}
|
||||
|
||||
protected readBlock() {
|
||||
const TILDA_CHAR = 0x7e; // '~'
|
||||
const Z_LOWER_CHAR = 0x7a; // 'z'
|
||||
const EOF = -1;
|
||||
|
||||
const stream = this.stream;
|
||||
|
||||
let c = stream.getByte();
|
||||
while (isSpace(c)) {
|
||||
c = stream.getByte();
|
||||
}
|
||||
|
||||
if (c === EOF || c === TILDA_CHAR) {
|
||||
this.eof = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const bufferLength = this.bufferLength;
|
||||
let buffer;
|
||||
let i;
|
||||
|
||||
// special code for z
|
||||
if (c === Z_LOWER_CHAR) {
|
||||
buffer = this.ensureBuffer(bufferLength + 4);
|
||||
for (i = 0; i < 4; ++i) {
|
||||
buffer[bufferLength + i] = 0;
|
||||
}
|
||||
this.bufferLength += 4;
|
||||
} else {
|
||||
const input = this.input;
|
||||
input[0] = c;
|
||||
for (i = 1; i < 5; ++i) {
|
||||
c = stream.getByte();
|
||||
while (isSpace(c)) {
|
||||
c = stream.getByte();
|
||||
}
|
||||
|
||||
input[i] = c;
|
||||
|
||||
if (c === EOF || c === TILDA_CHAR) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
buffer = this.ensureBuffer(bufferLength + i - 1);
|
||||
this.bufferLength += i - 1;
|
||||
|
||||
// partial ending;
|
||||
if (i < 5) {
|
||||
for (; i < 5; ++i) {
|
||||
input[i] = 0x21 + 84;
|
||||
}
|
||||
this.eof = true;
|
||||
}
|
||||
let t = 0;
|
||||
for (i = 0; i < 5; ++i) {
|
||||
t = t * 85 + (input[i] - 0x21);
|
||||
}
|
||||
|
||||
for (i = 3; i >= 0; --i) {
|
||||
buffer[bufferLength + i] = t & 0xff;
|
||||
t >>= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Ascii85Stream;
|
||||
77
node_modules/pdf-lib/src/core/streams/AsciiHexStream.ts
generated
vendored
Normal file
77
node_modules/pdf-lib/src/core/streams/AsciiHexStream.ts
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* The AsciiHexStream class contained in this file is a TypeScript port of the
|
||||
* JavaScript AsciiHexStream class in Mozilla's pdf.js project, made available
|
||||
* under the Apache 2.0 open source license.
|
||||
*/
|
||||
|
||||
import DecodeStream from 'src/core/streams/DecodeStream';
|
||||
import { StreamType } from 'src/core/streams/Stream';
|
||||
|
||||
class AsciiHexStream extends DecodeStream {
|
||||
private stream: StreamType;
|
||||
private firstDigit: number;
|
||||
|
||||
constructor(stream: StreamType, maybeLength?: number) {
|
||||
super(maybeLength);
|
||||
|
||||
this.stream = stream;
|
||||
|
||||
this.firstDigit = -1;
|
||||
|
||||
// Most streams increase in size when decoded, but AsciiHex streams shrink
|
||||
// by 50%.
|
||||
if (maybeLength) {
|
||||
maybeLength = 0.5 * maybeLength;
|
||||
}
|
||||
}
|
||||
|
||||
protected readBlock() {
|
||||
const UPSTREAM_BLOCK_SIZE = 8000;
|
||||
const bytes = this.stream.getBytes(UPSTREAM_BLOCK_SIZE);
|
||||
if (!bytes.length) {
|
||||
this.eof = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const maxDecodeLength = (bytes.length + 1) >> 1;
|
||||
const buffer = this.ensureBuffer(this.bufferLength + maxDecodeLength);
|
||||
let bufferLength = this.bufferLength;
|
||||
|
||||
let firstDigit = this.firstDigit;
|
||||
for (let i = 0, ii = bytes.length; i < ii; i++) {
|
||||
const ch = bytes[i];
|
||||
let digit;
|
||||
if (ch >= 0x30 && ch <= 0x39) {
|
||||
// '0'-'9'
|
||||
digit = ch & 0x0f;
|
||||
} else if ((ch >= 0x41 && ch <= 0x46) || (ch >= 0x61 && ch <= 0x66)) {
|
||||
// 'A'-'Z', 'a'-'z'
|
||||
digit = (ch & 0x0f) + 9;
|
||||
} else if (ch === 0x3e) {
|
||||
// '>'
|
||||
this.eof = true;
|
||||
break;
|
||||
} else {
|
||||
// probably whitespace
|
||||
continue; // ignoring
|
||||
}
|
||||
if (firstDigit < 0) {
|
||||
firstDigit = digit;
|
||||
} else {
|
||||
buffer[bufferLength++] = (firstDigit << 4) | digit;
|
||||
firstDigit = -1;
|
||||
}
|
||||
}
|
||||
if (firstDigit >= 0 && this.eof) {
|
||||
// incomplete byte
|
||||
buffer[bufferLength++] = firstDigit << 4;
|
||||
firstDigit = -1;
|
||||
}
|
||||
this.firstDigit = firstDigit;
|
||||
this.bufferLength = bufferLength;
|
||||
}
|
||||
}
|
||||
|
||||
export default AsciiHexStream;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user