I had no idea that I could not copy content between documents. I thought content should be content, and the worst that might happen would be that it would not fit on the page. I dare say there are probably good reasons for the limitation but it certainly wasn't obvious to me.
Anyway, you're quite right. When I do what you suggest everything works smoothly and consistently in both evaluation and production modes. Thanks very much.
In the hope that it helps others I have included the working code below. (This can replace the function in the demonstration project linked above.)
Alex, in this code I chose to insert all pages at once, do the content copy, and then delete the extra pages. I could have done things one page at a time. Do you know if it matters much which way it is done? (In terms of performance and memory use for large PDFs).
Code: Select all
procedure TPdfCoreApiTestMainForm.MergeTextWithOriginalBtnClick(
Sender: TObject);
var
origfn, textfn, resultfn: string;
origdoc, textdoc: IPXC_Document;
origpage, textpage: IPXC_Page;
textcontent: IPXC_Content;
AuxInst: IAUX_Inst;
origpagebits: IBitSet;
undodata: IPXC_UndoRedoData;
i, j, origpc, textpc: DWORD;
exceptionMask: TFPUExceptionMask; // bug in PDFX
begin
origfn := BasePath + 'Sample_pages.pdf';
textfn := BasePath + 'Sample_pages_text.pdf';
resultfn := BasePath + 'Sample_pages_result.pdf';
origdoc := PdfXInst.OpenDocumentFromFile(PChar(origfn), nil, nil, 0, 0);
if not Assigned(origdoc) then
raise Exception.Create('Failed to open original PDF document.');
textdoc := PdfXInst.OpenDocumentFromFile(PChar(textfn), nil, nil, 0, 0);
if not Assigned(origdoc) then
raise Exception.Create('Failed to open text-only PDF document.');
if origdoc.Pages.Get_Count(origpc) <> 0 then
raise Exception.Create('Failed to get page count from original PDF document.');
if textdoc.Pages.Get_Count(textpc) <> 0 then
raise Exception.Create('Failed to get page count from text-only PDF document.');
// We could check page counts here, or we could just let it catch in the loop.
//if origpc <> textpc then raise Exception.Create('Page count mismatch.');
// I think page-flags 0 will give us everything except bookmarks which seems
// appropriate, although perhaps redundant in this case.
origdoc.Pages.InsertPagesFromDoc(textdoc, origpc, 0, textpc, 0, nil);
j := origpc; // index of text page in origdoc that should match i.
for i := 0 to origpc - 1 do
begin
if origdoc.Pages.Get_Item(i, origpage) <> 0 then
raise Exception.CreateFmt('Failed to load page %d from original document.', [i]);
if origdoc.Pages.Get_Item(j, textpage) <> 0 then
raise Exception.CreateFmt('Failed to load page %d from original document (text for page %d).', [j, i]);
// I'm assuming a weak-clone will be sufficient for this
if textpage.GetContent(CAccessMode_WeakClone, textcontent) <> 0 then
raise Exception.CreateFmt('Failed to obtain content for page %d of original document (text for page %d', [j, i]);
if origpage.PlaceContent(textcontent, PlaceContent_After) <> 0 then
raise Exception.CreateFmt('Failed to place text content on page %d of original document', [i]);
Inc(j);
end;
// Everything seems to work without clearing these, but it seems like a bad
// idea to leave live references to things that should soon be disappearing.
textcontent := nil;
origpage := nil;
textpage := nil;
// Don't forget to delete all those pages we inserted but don't really want.
// There's no need to make this too easy, we're forced to build a bit map to
// indicate which pages we want deleted.
AuxInst := PdfXInst.GetExtension('AUX') as IAUX_Inst;
if not Assigned(AuxInst) then
raise Exception.Create('Failed to load PDF-XChange Auxiliary Extension.');
origpagebits := AuxInst.CreateBitSet(origpc + textpc);
origpagebits.Set_(origpc, textpc, true);
undodata := nil; // needed to match "out" parameter even if not used.
if origdoc.Pages.DeletePages(origpagebits, nil, undodata) <> 0 then
raise Exception.CreateFmt('Failed to delete redundant text pages (%d+) from original document.', [origpc]);
exceptionMask := GetExceptionMask;
SetExceptionMask(exceptionMask + [exZeroDivide, exInvalidOp]);
try
origdoc.WriteToFile(PChar(resultfn), nil, 0);
finally
SetExceptionMask(exceptionMask);
end;
textdoc.Close(0);
origdoc.Close(0);
end;