Skip to content

Commit 7ce5e9a

Browse files
committed
Callout annotation
1 parent f335f28 commit 7ce5e9a

14 files changed

Lines changed: 1013 additions & 18 deletions

File tree

packages/engines/src/lib/pdfium/engine.ts

Lines changed: 119 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3349,7 +3349,13 @@ export class PdfiumNative implements IPdfiumExecutor {
33493349
annotation: PdfFreeTextAnnoObject,
33503350
) {
33513351
// Type-specific properties
3352-
if (!this.setBorderStyle(annotationPtr, PdfAnnotationBorderStyle.SOLID, 0)) {
3352+
if (
3353+
!this.setBorderStyle(
3354+
annotationPtr,
3355+
PdfAnnotationBorderStyle.SOLID,
3356+
annotation.strokeWidth ?? 0,
3357+
)
3358+
) {
33533359
return false;
33543360
}
33553361
if (!this.setAnnotationOpacity(annotationPtr, annotation.opacity ?? 1)) {
@@ -3361,21 +3367,42 @@ export class PdfiumNative implements IPdfiumExecutor {
33613367
if (!this.setAnnotationVerticalAlignment(annotationPtr, annotation.verticalAlign)) {
33623368
return false;
33633369
}
3370+
const daColor = annotation.strokeColor ?? annotation.fontColor;
33643371
if (
33653372
!this.setAnnotationDefaultAppearance(
33663373
annotationPtr,
33673374
annotation.fontFamily === PdfStandardFont.Unknown
33683375
? PdfStandardFont.Helvetica
33693376
: annotation.fontFamily,
33703377
annotation.fontSize,
3371-
annotation.fontColor,
3378+
daColor,
33723379
)
33733380
) {
33743381
return false;
33753382
}
3383+
if (annotation.strokeColor && annotation.strokeColor !== annotation.fontColor) {
3384+
this.setAnnotationColor(
3385+
annotationPtr,
3386+
annotation.fontColor,
3387+
PdfAnnotationColorType.TextColor,
3388+
);
3389+
}
33763390
if (annotation.intent && !this.setAnnotIntent(annotationPtr, annotation.intent)) {
33773391
return false;
33783392
}
3393+
if (
3394+
annotation.calloutLine &&
3395+
annotation.calloutLine.length >= 2 &&
3396+
!this.setCalloutLine(doc, page, annotationPtr, annotation.calloutLine)
3397+
) {
3398+
return false;
3399+
}
3400+
if (
3401+
annotation.lineEnding !== undefined &&
3402+
!this.setLineEndings(annotationPtr, PdfAnnotationLineEnding.None, annotation.lineEnding)
3403+
) {
3404+
return false;
3405+
}
33793406
// Prefer color, fall back to deprecated backgroundColor
33803407
const bgColor = annotation.color ?? annotation.backgroundColor;
33813408
if (!bgColor || bgColor === 'transparent') {
@@ -6463,6 +6490,9 @@ export class PdfiumNative implements IPdfiumExecutor {
64636490
* @returns `{ ok, left, top, right, bottom }`
64646491
* • `ok` – `true` when the annotation *has* an /RD entry
64656492
* • the four floats are 0 when `ok` is false
6493+
*
6494+
* Native PDFium exposes /RD as [left, bottom, right, top]. We remap it here
6495+
* to the model's stable { left, top, right, bottom } shape.
64666496
*/
64676497
private getRectangleDifferences(annotationPtr: number): {
64686498
ok: boolean;
@@ -6473,29 +6503,29 @@ export class PdfiumNative implements IPdfiumExecutor {
64736503
} {
64746504
/* tmp storage ─────────────────────────────────────────── */
64756505
const lPtr = this.memoryManager.malloc(4);
6476-
const tPtr = this.memoryManager.malloc(4);
6477-
const rPtr = this.memoryManager.malloc(4);
64786506
const bPtr = this.memoryManager.malloc(4);
6507+
const rPtr = this.memoryManager.malloc(4);
6508+
const tPtr = this.memoryManager.malloc(4);
64796509

64806510
const ok = !!this.pdfiumModule.EPDFAnnot_GetRectangleDifferences(
64816511
annotationPtr,
64826512
lPtr,
6483-
tPtr,
6484-
rPtr,
64856513
bPtr,
6514+
rPtr,
6515+
tPtr,
64866516
);
64876517

64886518
const pdf = this.pdfiumModule.pdfium;
64896519
const left = pdf.getValue(lPtr, 'float');
6490-
const top = pdf.getValue(tPtr, 'float');
6491-
const right = pdf.getValue(rPtr, 'float');
64926520
const bottom = pdf.getValue(bPtr, 'float');
6521+
const right = pdf.getValue(rPtr, 'float');
6522+
const top = pdf.getValue(tPtr, 'float');
64936523

64946524
/* cleanup ─────────────────────────────────────────────── */
64956525
this.memoryManager.free(lPtr);
6496-
this.memoryManager.free(tPtr);
6497-
this.memoryManager.free(rPtr);
64986526
this.memoryManager.free(bPtr);
6527+
this.memoryManager.free(rPtr);
6528+
this.memoryManager.free(tPtr);
64996529

65006530
return { ok, left, top, right, bottom };
65016531
}
@@ -6517,9 +6547,9 @@ export class PdfiumNative implements IPdfiumExecutor {
65176547
return this.pdfiumModule.EPDFAnnot_SetRectangleDifferences(
65186548
annotationPtr,
65196549
rd.left,
6520-
rd.top,
6521-
rd.right,
65226550
rd.bottom,
6551+
rd.right,
6552+
rd.top,
65236553
);
65246554
}
65256555

@@ -7503,11 +7533,16 @@ export class PdfiumNative implements IPdfiumExecutor {
75037533
const defaultStyle = this.getAnnotString(annotationPtr, 'DS');
75047534
const da = this.getAnnotationDefaultAppearance(annotationPtr);
75057535
const bgColor = this.getAnnotationColor(annotationPtr);
7536+
const textColor = this.getAnnotationColor(annotationPtr, PdfAnnotationColorType.TextColor);
7537+
const borderStyle = this.getBorderStyle(annotationPtr);
75067538
const textAlign = this.getAnnotationTextAlignment(annotationPtr);
75077539
const verticalAlign = this.getAnnotationVerticalAlignment(annotationPtr);
75087540
const opacity = this.getAnnotationOpacity(annotationPtr);
75097541
const richContent = this.getAnnotRichContent(annotationPtr);
75107542
const rd = this.getRectangleDifferences(annotationPtr);
7543+
const intent = this.getAnnotIntent(annotationPtr);
7544+
const calloutLine = this.getCalloutLine(doc, page, annotationPtr);
7545+
const lineEndings = this.getLineEndings(annotationPtr);
75117546

75127547
return {
75137548
pageIndex: page.index,
@@ -7516,10 +7551,10 @@ export class PdfiumNative implements IPdfiumExecutor {
75167551
rect,
75177552
fontFamily: da?.fontFamily ?? PdfStandardFont.Unknown,
75187553
fontSize: da?.fontSize ?? 12,
7519-
fontColor: da?.fontColor ?? '#000000',
7554+
fontColor: textColor ?? da?.fontColor ?? '#000000',
75207555
verticalAlign,
7521-
color: bgColor, // fill color (matches shape convention)
7522-
backgroundColor: bgColor, // deprecated alias
7556+
color: bgColor,
7557+
backgroundColor: bgColor,
75237558
opacity,
75247559
textAlign,
75257560
defaultStyle,
@@ -7532,6 +7567,14 @@ export class PdfiumNative implements IPdfiumExecutor {
75327567
bottom: rd.bottom,
75337568
},
75347569
}),
7570+
...(intent && { intent }),
7571+
...(calloutLine && { calloutLine }),
7572+
...(lineEndings && { lineEnding: lineEndings.end }),
7573+
...(borderStyle.width > 0
7574+
? { strokeWidth: borderStyle.width, strokeColor: da?.fontColor ?? '#000000' }
7575+
: intent === 'FreeTextCallout'
7576+
? { strokeWidth: 1, strokeColor: da?.fontColor ?? '#000000' }
7577+
: {}),
75357578
...this.readBaseAnnotationProperties(doc, page, annotationPtr),
75367579
};
75377580
}
@@ -9178,6 +9221,67 @@ export class PdfiumNative implements IPdfiumExecutor {
91789221
return ok;
91799222
}
91809223

9224+
/**
9225+
* Read the callout line points (/CL) from a FreeText annotation.
9226+
* Returns an array of 2 or 3 Position points in device coords, or undefined.
9227+
*
9228+
* @private
9229+
*/
9230+
private getCalloutLine(
9231+
doc: PdfDocumentObject,
9232+
page: PdfPageObject,
9233+
annotationPtr: number,
9234+
): Position[] | undefined {
9235+
const count = this.pdfiumModule.EPDFAnnot_GetCalloutLineCount(annotationPtr);
9236+
if (count === 0) {
9237+
return undefined;
9238+
}
9239+
9240+
const FS_POINTF_SIZE = 8;
9241+
const pointsPtr = this.memoryManager.malloc(count * FS_POINTF_SIZE);
9242+
const result = this.pdfiumModule.EPDFAnnot_GetCalloutLine(annotationPtr, pointsPtr, count);
9243+
if (result === 0) {
9244+
this.memoryManager.free(pointsPtr);
9245+
return undefined;
9246+
}
9247+
9248+
const points: Position[] = [];
9249+
for (let i = 0; i < count; i++) {
9250+
const px = this.pdfiumModule.pdfium.getValue(pointsPtr + i * FS_POINTF_SIZE, 'float');
9251+
const py = this.pdfiumModule.pdfium.getValue(pointsPtr + i * FS_POINTF_SIZE + 4, 'float');
9252+
points.push(this.convertPagePointToDevicePoint(doc, page, { x: px, y: py }));
9253+
}
9254+
this.memoryManager.free(pointsPtr);
9255+
return points;
9256+
}
9257+
9258+
/**
9259+
* Set the callout line points (/CL) on a FreeText annotation.
9260+
* Converts from device coords to page coords before writing.
9261+
*
9262+
* @private
9263+
*/
9264+
private setCalloutLine(
9265+
doc: PdfDocumentObject,
9266+
page: PdfPageObject,
9267+
annotPtr: number,
9268+
points: Position[],
9269+
): boolean {
9270+
const pdf = this.pdfiumModule.pdfium;
9271+
const FS_POINTF_SIZE = 8;
9272+
9273+
const buf = this.memoryManager.malloc(FS_POINTF_SIZE * points.length);
9274+
points.forEach((v, i) => {
9275+
const pagePt = this.convertDevicePointToPagePoint(doc, page, v);
9276+
pdf.setValue(buf + i * FS_POINTF_SIZE + 0, pagePt.x, 'float');
9277+
pdf.setValue(buf + i * FS_POINTF_SIZE + 4, pagePt.y, 'float');
9278+
});
9279+
9280+
const ok = this.pdfiumModule.EPDFAnnot_SetCalloutLine(annotPtr, buf, points.length);
9281+
this.memoryManager.free(buf);
9282+
return ok;
9283+
}
9284+
91819285
/**
91829286
* Read the target of pdf bookmark
91839287
* @param docPtr - pointer to pdf document object

packages/models/src/pdf.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,6 +1243,7 @@ export enum PdfAnnotationColorType {
12431243
Color = 0,
12441244
InteriorColor = 1,
12451245
OverlayColor = 2,
1246+
TextColor = 3,
12461247
}
12471248

12481249
/**
@@ -2314,6 +2315,26 @@ export interface PdfFreeTextAnnoObject extends PdfAnnotationObjectBase {
23142315
* Rectangle Differences (/RD) - inset padding from Rect to the drawn area.
23152316
*/
23162317
rectangleDifferences?: PdfRectDifferences;
2318+
/**
2319+
* Callout line points (PDF /CL array).
2320+
* 2 points for a simple leader line, 3 points for a knee-jointed leader line.
2321+
* Points are in device coordinates (same as rect/vertices).
2322+
* Present only when intent is 'FreeTextCallout'.
2323+
*/
2324+
calloutLine?: Position[];
2325+
/**
2326+
* Line ending style for the callout leader line (PDF /LE).
2327+
* Only meaningful when calloutLine is present.
2328+
*/
2329+
lineEnding?: PdfAnnotationLineEnding;
2330+
/**
2331+
* Border / callout line stroke width (from /BS -> W).
2332+
*/
2333+
strokeWidth?: number;
2334+
/**
2335+
* Border / callout line stroke color (from /DA rg color).
2336+
*/
2337+
strokeColor?: string;
23172338
}
23182339

23192340
/**

packages/pdfium/src/vendor/functions.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ export const functions = {
5454
EPDFAnnot_GetBorderEffect: [['number', 'number'] as const, 'boolean'] as const,
5555
EPDFAnnot_GetBorderStyle: [['number', 'number'] as const, 'number'] as const,
5656
EPDFAnnot_GetButtonExportValue: [['number', 'number', 'number'] as const, 'number'] as const,
57+
EPDFAnnot_GetCalloutLine: [['number', 'number', 'number'] as const, 'number'] as const,
58+
EPDFAnnot_GetCalloutLineCount: [['number'] as const, 'number'] as const,
5759
EPDFAnnot_GetColor: [
5860
['number', 'number', 'number', 'number', 'number'] as const,
5961
'boolean',
@@ -96,6 +98,7 @@ export const functions = {
9698
EPDFAnnot_SetBorderDashPattern: [['number', 'number', 'number'] as const, 'boolean'] as const,
9799
EPDFAnnot_SetBorderEffect: [['number', 'number'] as const, 'boolean'] as const,
98100
EPDFAnnot_SetBorderStyle: [['number', 'number', 'number'] as const, 'boolean'] as const,
101+
EPDFAnnot_SetCalloutLine: [['number', 'number', 'number'] as const, 'boolean'] as const,
99102
EPDFAnnot_SetColor: [
100103
['number', 'number', 'number', 'number', 'number'] as const,
101104
'boolean',

packages/pdfium/src/vendor/pdfium.cjs

Lines changed: 4 additions & 1 deletion
Large diffs are not rendered by default.

packages/pdfium/src/vendor/pdfium.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ var createPdfium = (() => {
6969
'_EPDFAnnot_GetBorderEffect',
7070
'_EPDFAnnot_GetBorderStyle',
7171
'_EPDFAnnot_GetButtonExportValue',
72+
'_EPDFAnnot_GetCalloutLine',
73+
'_EPDFAnnot_GetCalloutLineCount',
7274
'_EPDFAnnot_GetColor',
7375
'_EPDFAnnot_GetDefaultAppearance',
7476
'_EPDFAnnot_GetExtendedRotation',
@@ -96,6 +98,7 @@ var createPdfium = (() => {
9698
'_EPDFAnnot_SetBorderDashPattern',
9799
'_EPDFAnnot_SetBorderEffect',
98100
'_EPDFAnnot_SetBorderStyle',
101+
'_EPDFAnnot_SetCalloutLine',
99102
'_EPDFAnnot_SetColor',
100103
'_EPDFAnnot_SetDefaultAppearance',
101104
'_EPDFAnnot_SetExtendedRotation',
@@ -6399,6 +6402,16 @@ var createPdfium = (() => {
63996402
'EPDFAnnot_ShareFormField',
64006403
3,
64016404
));
6405+
var _EPDFAnnot_GetCalloutLineCount = (Module['_EPDFAnnot_GetCalloutLineCount'] =
6406+
createExportWrapper('EPDFAnnot_GetCalloutLineCount', 1));
6407+
var _EPDFAnnot_GetCalloutLine = (Module['_EPDFAnnot_GetCalloutLine'] = createExportWrapper(
6408+
'EPDFAnnot_GetCalloutLine',
6409+
3,
6410+
));
6411+
var _EPDFAnnot_SetCalloutLine = (Module['_EPDFAnnot_SetCalloutLine'] = createExportWrapper(
6412+
'EPDFAnnot_SetCalloutLine',
6413+
3,
6414+
));
64026415
var _EPDFAnnot_SetFormFieldOptions = (Module['_EPDFAnnot_SetFormFieldOptions'] =
64036416
createExportWrapper('EPDFAnnot_SetFormFieldOptions', 4));
64046417
var _FPDFDoc_GetAttachmentCount = (Module['_FPDFDoc_GetAttachmentCount'] = createExportWrapper(
1.76 KB
Binary file not shown.

0 commit comments

Comments
 (0)