@ironsoftware/ironpdf
Version:
IronPDF for Node
190 lines (179 loc) • 6.43 kB
text/typescript
import {ServiceError} from "@grpc/grpc-js";
import {Access} from "../../access";
import {IronPdfServiceClient} from "../../generated_proto/ironpdfengineproto/IronPdfService";
import {EmptyResultP__Output} from "../../generated_proto/ironpdfengineproto/EmptyResultP";
import {PdfiumGetAnnotationsResultP__Output} from "../../generated_proto/ironpdfengineproto/PdfiumGetAnnotationsResultP";
import {PdfiumWrappedPdfAnnotationP__Output} from "../../generated_proto/ironpdfengineproto/PdfiumWrappedPdfAnnotationP";
import {BookmarkDestinations, LinkAnnotation} from "../../../public/annotation";
import {handleEmptyResultP__Output, handleRemoteException} from "../util";
/**
* URL prefix used to encode an internal hyperlink (goto-page) target inside the
* existing {@code Pdfium_Annotation_AddLinkAnnotation} RPC. Must match the
* {@code InternalLinkPrefixes.InternalLinkPrefix} constant in the C#
* {@code IronPdf.GrpcLayer.InternalPrefixes} / {@code IronPdfServiceHandler}.
*
* <p>The {@code x-} prefix follows RFC 6648 conventions for experimental /
* private URL schemes and cannot collide with any real URL scheme a user would
* construct.</p>
*/
const INTERNAL_LINK_PREFIX = "x-ironpdf-goto-page:";
/**
* Retrieve all heterogeneous annotations (text, free-text, link, ...) on a single page
* via the {@code Pdfium_Annotation_GetAnnotations} unary RPC.
*
* The returned wrapped annotations carry one of the proto sub-types ({@code text},
* {@code freetext}, {@code link}). Callers inspect the {@code annotations} oneof to
* dispatch by sub-type.
*/
export async function getAnnotations(
id: string,
pageIndex: number
): Promise<PdfiumWrappedPdfAnnotationP__Output[]> {
const client: IronPdfServiceClient = await Access.ensureConnection();
return new Promise(
(
resolve: (_: PdfiumWrappedPdfAnnotationP__Output[]) => void,
reject: (errorMsg: string) => void
) => {
client.Pdfium_Annotation_GetAnnotationsRequestP(
{
document: {documentId: id},
pageIndex: pageIndex,
},
(
err: ServiceError | null,
value: PdfiumGetAnnotationsResultP__Output | undefined
) => {
if (err) {
reject(`${err.name}/n${err.message}`);
return;
}
if (!value) {
reject("No response from IronPdfEngine for getAnnotations");
return;
}
if (value.exception) {
handleRemoteException(value.exception, reject);
return;
}
resolve(value.result?.annotations ?? []);
}
);
}
);
}
/**
* Add an internal hyperlink annotation to the document. The link navigates to a
* destination page within the same PDF when clicked.
*
* <p>Reuses the existing {@code Pdfium_Annotation_AddLinkAnnotation} RPC by encoding
* the destination parameters into a special URL format:
* {@code x-ironpdf-goto-page:{destPage},{destType},{left},{right},{top},{bottom},{zoom},{showBorder}}.
* The engine detects this prefix and routes the request to its internal link handler.</p>
*
* <p>Mirrors {@code IronPdf.Engines.Pdfium.GrpcPdfClient.AddInternalLinkAnnotation}
* on the C# side.</p>
*/
export async function addLinkAnnotation(
id: string,
link: LinkAnnotation
): Promise<void> {
if (link.pageIndex < 0) {
throw new Error("Invalid page index when adding link annotation");
}
if (link.destinationPageIndex < 0) {
throw new Error("Invalid destination page index when adding link annotation");
}
const client: IronPdfServiceClient = await Access.ensureConnection();
const destType: BookmarkDestinations = link.destinationType ?? BookmarkDestinations.Page;
const destLeft = link.destinationLeft ?? 0;
const destRight = link.destinationRight ?? 0;
const destTop = link.destinationTop ?? 0;
const destBottom = link.destinationBottom ?? 0;
const destZoom = link.destinationZoom ?? 0;
const showBorderFlag = link.showBorder ? 1 : 0;
// Matches the 8-field encoding produced by C# AddInternalLinkAnnotation:
// {prefix}{destPageIndex},{destType},{destLeft},{destRight},{destTop},{destBottom},{destZoom},{showBorder}
const encodedUrl =
`${INTERNAL_LINK_PREFIX}${link.destinationPageIndex},${destType},` +
`${destLeft},${destRight},${destTop},${destBottom},${destZoom},${showBorderFlag}`;
// Color fallback: the engine parses `color_code` into IronSoftware.Drawing.Color and
// throws on an empty string. Match the C# AddLinkAnnotation behavior by always
// sending a valid color — default to black when the caller hasn't specified one.
const colorCode = link.colorCode && link.colorCode.length > 0 ? link.colorCode : "#000000";
return new Promise(
(resolve: () => void, reject: (errorMsg: string) => void) => {
client.Pdfium_Annotation_AddLinkAnnotation(
{
document: {documentId: id},
name: link.title ?? "",
url: encodedUrl,
pageIndex: link.pageIndex,
rectangle: {
x: link.x,
y: link.y,
width: link.width,
height: link.height,
},
colorCode: colorCode,
Hidden: link.hidden ?? false,
},
(
err: ServiceError | null,
value: EmptyResultP__Output | undefined
) => {
if (err) {
reject(`${err.name}/n${err.message}`);
return;
}
if (!value) {
reject("No response from IronPdfEngine for addLinkAnnotation");
return;
}
handleEmptyResultP__Output(value, reject);
resolve();
}
);
}
);
}
/**
* Retrieve the number of annotations contained on the specified page via the
* {@code Pdfium_Annotation_GetAnnotationCount} unary RPC.
*/
export async function getAnnotationCount(
id: string,
pageIndex: number
): Promise<number> {
const client: IronPdfServiceClient = await Access.ensureConnection();
return new Promise(
(resolve: (_: number) => void, reject: (errorMsg: string) => void) => {
client.Pdfium_Annotation_GetAnnotationCountRequestP(
{
document: {documentId: id},
pageIndex: pageIndex,
},
(
err: ServiceError | null,
value:
| import("../../generated_proto/ironpdfengineproto/IntResultP").IntResultP__Output
| undefined
) => {
if (err) {
reject(`${err.name}/n${err.message}`);
return;
}
if (!value) {
reject("No response from IronPdfEngine for getAnnotationCount");
return;
}
if (value.exception) {
handleRemoteException(value.exception, reject);
return;
}
resolve(value.result ?? 0);
}
);
}
);
}