Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 137 additions & 1 deletion packages/pdfrx/assets/pdfium_worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,7 @@ function loadPagesProgressively(params) {
}

/**
*
*
* @param {{docHandle: number, pageIndices: number[]|undefined, currentPagesCount: number}} params
* @returns {{pages: PdfPage[], missingFonts: FontQueries}}
*/
Expand Down Expand Up @@ -2116,6 +2116,119 @@ function _setImageObjPixels(pageHandle, imageObj, pixels, pixelWidth, pixelHeigh
}
}

/**
* @param {{
* docHandle: number,
* pageIndex: number,
* text: string,
* fontSize: number,
* x: number,
* y: number,
* anchorX: number,
* anchorY: number,
* rotation: number,
* textColor: number,
* fontName: string,
* }} params
*/
function insertText(params) {
const {
docHandle,
pageIndex,
text,
fontSize,
x,
y,
anchorX,
anchorY,
rotation,
textColor,
fontName,
} = params;

let textUtf16 = StringUtils.allocateUTF16(text);
let fontNameUtf8 = StringUtils.allocateUTF8(fontName);
let pageHandle = 0;
let fontHandle = 0;
try {
pageHandle = Pdfium.wasmExports.FPDF_LoadPage(docHandle, pageIndex);
if (!pageHandle) {
throw new Error(`Failed to load page ${pageIndex} from document ${docHandle}`);
}

fontHandle = Pdfium.wasmExports.FPDFText_LoadStandardFont(docHandle, fontNameUtf8);
if (!fontHandle) {
const error = Pdfium.wasmExports.FPDF_GetLastError();
throw new Error(`FPDFText_LoadStandardFont failed (${_getErrorMessage(error)})`);
}

const textHandle = Pdfium.wasmExports.FPDFPageObj_CreateTextObj(docHandle, fontHandle, fontSize);
if (!textHandle) {
const error = Pdfium.wasmExports.FPDF_GetLastError();
throw new Error(`FPDFPageObj_CreateTextObj failed (${_getErrorMessage(error)})`);
}

if (!Pdfium.wasmExports.FPDFText_SetText(textHandle, textUtf16)) {
const error = Pdfium.wasmExports.FPDF_GetLastError();
throw new Error(`FPDFText_SetText failed (${_getErrorMessage(error)})`);
}

const a = (textColor >> 24) & 0xFF;
const r = (textColor >> 16) & 0xFF;
const g = (textColor >> 8) & 0xFF;
const b = textColor & 0xFF;

if (Pdfium.wasmExports.FPDFPageObj_SetFillColor(textHandle, r, g, b, a) == 0) {
throw PdfException('FPDFPageObj_SetFillColor failed.');
}


let ax, ay;

const boundsSize = 64;
const boundsWrite = Pdfium.wasmExports.malloc(boundsSize);
try {
if (Pdfium.wasmExports.FPDFPageObj_GetBounds(textHandle, boundsWrite, boundsWrite + 4, boundsWrite + 8, boundsWrite + 12) == 0) {
throw PdfException('could not determine text bounds');
}
const boundsView = new Float32Array(Pdfium.memory.buffer, boundsWrite, 4);

ax = (boundsView[2] - boundsView[0]) * anchorX;
ay = (boundsView[3] - boundsView[1]) * anchorY;
} finally {
Pdfium.wasmExports.free(boundsWrite);
}

const radians = rotation * (Math.PI / 180.0);
const cosR = Math.cos(radians);
const sinR = Math.sin(radians);

Pdfium.wasmExports.FPDFPageObj_Transform(
textHandle,
cosR,
sinR,
-sinR,
cosR,
x - ax * cosR + ay * sinR,
y - ax * sinR - ay * cosR,
);

Pdfium.wasmExports.FPDFPage_InsertObject(pageHandle, textHandle);
if (!Pdfium.wasmExports.FPDFPage_GenerateContent(pageHandle)) {
const error = Pdfium.wasmExports.FPDF_GetLastError();
throw new Error(`FPDFPage_InsertObject failed (${_getErrorMessage(error)})`);
}
} finally {
if (fontHandle) {
Pdfium.wasmExports.FPDFFont_Close(fontHandle);
}
Pdfium.wasmExports.FPDF_ClosePage(pageHandle);
StringUtils.freeUTF8(fontNameUtf8);
StringUtils.freeUTF16(textUtf16);
}
return { message: 'Text inserted' };
}

/**
* Functions that can be called from the main thread
*/
Expand All @@ -2138,6 +2251,7 @@ const functions = {
clearAllFontData,
assemble,
encodePdf,
insertText,
};

/**
Expand Down Expand Up @@ -2418,11 +2532,33 @@ class StringUtils {
this.stringToUtf8Bytes(str, new Uint8Array(Pdfium.memory.buffer, ptr, size));
return ptr;
}
/**
* Allocate memory for UTF-16 string
* @param {string} str
* @returns {number} Pointer to allocated buffer that contains UTF-16 string. The buffer should be released by calling [freeUTF16].
*/
static allocateUTF16(str) {
if (str == null) return 0;

const size = str.length * 2 + 1;
const ptr = Pdfium.wasmExports.malloc(size);
const view = new DataView(Pdfium.memory.buffer, ptr, size);
for (let i = 0; i < str.length; i++) view.setUint16(i * 2, str.charCodeAt(i), true);
view.setUint8(str.length * 2, 0);
return ptr;
}
/**
* Release memory allocated for UTF-8 string
* @param {number} ptr Pointer to allocated buffer
*/
static freeUTF8(ptr) {
Pdfium.wasmExports.free(ptr);
}
/**
* Release memory allocated for UTF-16 string
* @param {number} ptr Pointer to allocated buffer
*/
static freeUTF16(ptr) {
Pdfium.wasmExports.free(ptr);
}
}
32 changes: 32 additions & 0 deletions packages/pdfrx/lib/src/wasm/pdfrx_wasm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,38 @@ class _PdfPageWasm extends PdfPage {

return PdfImageWeb(width: width, height: height, pixels: pixels);
}

@override
Future<void> insertText({
required String text,
required double fontSize,
double x = 0,
double y = 0,
double anchorX = 0.5,
double anchorY = 0.5,
double rotation = 0,
int textColor = 0xFF000000,
String fontName = 'Helvetica',
}) async {
if (document.isDisposed) return;

await _sendCommand(
'insertText',
parameters: {
'docHandle': document.document['docHandle'],
'pageIndex': pageNumber - 1,
'text': text,
'fontSize': fontSize,
'x': x,
'y': y,
'anchorX': anchorX,
'anchorY': anchorY,
'rotation': rotation,
'textColor': textColor,
'fontName': fontName,
},
);
}
}

class PdfImageWeb extends PdfImage {
Expand Down
13 changes: 13 additions & 0 deletions packages/pdfrx_coregraphics/lib/pdfrx_coregraphics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,19 @@ class _CoreGraphicsPdfPage extends PdfPage {
return const [];
}
}

@override
Future<void> insertText({
required String text,
required double fontSize,
double x = 0,
double y = 0,
double anchorX = 0.5,
double anchorY = 0.5,
double rotation = 0,
int textColor = 0xFF000000,
String fontName = 'Helvetica',
}) => throw PdfException('insertText not implemented for CoreGraphics');
}

class _CoreGraphicsPdfImage implements PdfImage {
Expand Down
80 changes: 80 additions & 0 deletions packages/pdfrx_engine/lib/src/native/pdfrx_pdfium.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,86 @@ class _PdfPagePdfium extends PdfPage {
@override
PdfPageRenderCancellationTokenPdfium createCancellationToken() => PdfPageRenderCancellationTokenPdfium(this);

@override
Future<void> insertText({
required String text,
required double fontSize,
double x = 0,
double y = 0,
double anchorX = 0.5,
double anchorY = 0.5,
double rotation = 0,
int textColor = 0xFF000000,
String fontName = 'Helvetica',
}) async {
if (document.isDisposed || !isLoaded) return;
return await BackgroundWorker.computeWithArena((arena, params) {
final doc = pdfium_bindings.FPDF_DOCUMENT.fromAddress(params.docHandle);
final page = pdfium.FPDF_LoadPage(doc, params.pageNumber - 1);

try {
final font = pdfium.FPDFText_LoadStandardFont(doc, fontName.toUtf8(arena));
if (font == nullptr) {
throw PdfException('FPDFText_LoadStandardFont failed.');
}
try {
final textObj = pdfium.FPDFPageObj_CreateTextObj(doc, font, fontSize);
if (textObj == nullptr) {
throw PdfException('FPDFPageObj_CreateTextObj failed.');
}

if (pdfium.FPDFText_SetText(textObj, text.toNativeUtf16(allocator: arena).cast()) == 0) {
throw PdfException('FPDFText_SetText failed.');
}

final a = (textColor >> 24) & 0xFF;
final r = (textColor >> 16) & 0xFF;
final g = (textColor >> 8) & 0xFF;
final b = textColor & 0xFF;

if (pdfium.FPDFPageObj_SetFillColor(textObj, r, g, b, a) == 0) {
throw PdfException('FPDFPageObj_SetFillColor failed.');
}

final left = arena<Float>();
final bottom = arena<Float>();
final right = arena<Float>();
final top = arena<Float>();

if (pdfium.FPDFPageObj_GetBounds(textObj, left, bottom, right, top) == 0) {
throw PdfException('could not determine text bounds');
}

final ax = (right.value - left.value) * anchorX;
final ay = (top.value - bottom.value) * anchorY;

final radians = rotation * (pi / 180.0);
final cosR = cos(radians);
final sinR = sin(radians);

pdfium.FPDFPageObj_Transform(
textObj,
cosR,
sinR,
-sinR,
cosR,
x - ax * cosR + ay * sinR,
y - ax * sinR - ay * cosR,
);

pdfium.FPDFPage_InsertObject(page, textObj);
if (pdfium.FPDFPage_GenerateContent(page) == 0) {
throw PdfException('FPDFPage_GenerateContent failed.');
}
} finally {
pdfium.FPDFFont_Close(font);
}
} finally {
pdfium.FPDF_ClosePage(page);
}
}, (docHandle: document.document.address, pageNumber: pageNumber, bbLeft: bbLeft, bbBottom: bbBottom));
}

@override
Future<PdfPageRawText?> loadText() async {
if (document.isDisposed || !isLoaded) return null;
Expand Down
20 changes: 20 additions & 0 deletions packages/pdfrx_engine/lib/src/pdf_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,26 @@ abstract class PdfPage {
/// If the page is not loaded yet (progressive loading case only), this function returns null.
Future<PdfPageRawText?> loadText();

/// Insert a text element
///
/// [text] is the text to insert
/// [textColor] is `AARRGGBB` integer color notation
/// [fontSize] is the font size
/// [rotation] is the rotation in degrees
/// [anchorX] and [anchorY] are the anchor point position in the range 0..1
/// [x] and [y] are the position of the anchor point in page coordinates
Future<void> insertText({
required String text,
required double fontSize,
double x = 0,
double y = 0,
double anchorX = 0.5,
double anchorY = 0.5,
double rotation = 0,
int textColor = 0xFF000000,
String fontName = 'Helvetica',
});

/// Load links.
///
/// If [compact] is true, it tries to reduce memory usage by compacting the link data.
Expand Down
Loading