Examples / edit_text / activex_cpp

Edit Text

A complete, runnable activex_cpp program — 126 lines, shipped in your download.

Demonstrates Text

Same example, other languages: c cpp cpp_linux csharp python vbnet

examples/activex_cpp/edit_text/edit_text.cpp 126 lines
// ============================================================================
//  edit_text -- C++ ActiveX (COM) port of
//  examples/delphi/edit_text/edit_text.dpr, cross-checked against
//  examples/activex/edit_text/edit_text.vbs
//
//  Imports a PDF, then uses the content parser (Psr* exports) to find every
//  occurrence of a string and replace it in place.
//
//  STATUS: COMPILES (x64 + x86), FAILS AT RUNTIME -- same root cause its
//  VBScript sibling records as "*** NOT RUNNABLE ***", reached one call later.
//
//  C++ solves the half that stopped VBScript: TTextSelection and TContent are
//  packed in/out buffers that a script host cannot build, and RecVar<T> is
//  exactly that buffer, so PsrParsePage's TContent out-param now marshals
//  correctly where VBScript could not even express it.
//
//  What remains is a marshalling gap no caller can code around: the OPTIONAL
//  pointer parameters (PsrCreateParserContext's Parms, PsrParsePage's Funcs and
//  Parms, PsrFindText's Area and Last) all take the TVarLocks.InPtr path, and
//  Lock() REJECTS Empty/Null -- "this parameter needs a packed numeric array".
//  So there is no way to say "nil" for them. Passing the integer 0 (what the
//  VBScript port does) yields a pointer to a single zero byte, which the engine
//  then reads as a TFltRect/TOptimizeParams and rejects: "engine call failed".
//  A zeroed record is not equivalent either -- an all-zero Area means an EMPTY
//  search rectangle, not "the whole page".
//
//  The fix belongs in the wrapper: these params need to accept Empty as a nil
//  pointer. That is a per-parameter recipe in tools/gen_axidl.py (the same
//  mechanism rec_out uses), NOT a blanket change to Lock() -- its Empty guard
//  was added deliberately, because a nil buffer paired with a separate element
//  count is an access violation rather than a clean error.
// ============================================================================
#import "..\\..\\wrappers\\activex\\LumasPdfAX.tlb" no_namespace named_guids

#include <windows.h>

#include "axcommon.h"

#include <cstdio>
#include <string>

static const char* IN_FILE = "E:\\LUMASPDFSDK\\sample_multipage.pdf";
// cpfEnableTextSelection / stDefault / rtfDefault / ofDefault are all real
// enumerations in the type library, so they arrive with the #import -- unlike
// the table tf* flags, which are a Cardinal bitmask and have to be declared.

int main() {
    ChdirToExe();
    ComInit com;
    if (!com.ok()) return 1;
    int rc = 0;
    try {
        ILumasPDFPtr pdf;
        if (FAILED(pdf.CreateInstance(L"LumasPdf.PDF"))) _com_issue_error(E_FAIL);
        // NOT RaiseExceptions here. PsrFindText returning FALSE is how the
        // search loop ENDS -- "no more matches" is the normal exit, not an
        // error. With exceptions on, that false return throws before the loop
        // can break, and the whole example dies with "engine call failed" after
        // replacing everything successfully. The Delphi original uses an error
        // callback, where a false return is just a false return.
        pdf->RaiseExceptions = VARIANT_FALSE;

        std::string outFile = OutFile();

        pdf->CreateNewPDFA(B(""));
        pdf->SetImportFlags(ifImportAll | ifImportAsPage);

        if (pdf->OpenImportFileA(B(IN_FILE), ptOpen, B("")) < 0) {
            std::printf("Input file not found: %s\n", IN_FILE);
            return 1;
        }
        pdf->ImportPDFFile(1, 1, 1);
        pdf->CloseImportFile();

        // The optional PVoid/record parameters take the InPtr path, whose Lock()
        // REJECTS Empty/Null outright ("this parameter needs a packed numeric
        // array"). There is therefore no way to express a nil pointer for them;
        // the integer 0 is what the VBScript sibling passes, which Lock turns
        // into a pointer to a single zero byte.
        _variant_t nil((long)0);
        long ctx = pdf->PsrCreateParserContext(ofDefault, nil);
        _bstr_t searchText = B("PDF");
        _bstr_t replaceText = B("XDF");

        long pages = pdf->GetPageCount();
        long replaced = 0;
        for (long i = 1; i <= pages; ++i) {
            // PsrParsePage's AValue is a TContent RECORD out-param (16 bytes on
            // x64), not a scalar -- RecFromVar rejects anything else outright.
            RecVar<TContent> content;
            if (pdf->PsrParsePage(ctx, 0, nil, i, cpfEnableTextSelection, nil,
                                  content.Addr())) {
                // `last` seeds the search position; the first pass starts at nil.
                RecVar<TTextSelection> sel;
                bool haveLast = false;
                for (;;) {
                    RecVar<TTextSelection> hit;
                    VARIANT_BOOL found = pdf->PsrFindText(
                        ctx, nil, stDefault, haveLast ? sel.Var() : nil,
                        searchText, (long)searchText.length(), hit.Addr());
                    if (!found) break;
                    pdf->PsrReplaceSelText(ctx, rtfDefault, hit.Addr(), replaceText,
                                           (long)replaceText.length());
                    *sel.Ptr() = *hit.Ptr();
                    haveLast = true;
                    ++replaced;
                }
                pdf->PsrWriteToPage(ctx, ofDefault, nil);
            }
        }
        _variant_t ctxVar((long)ctx);
        pdf->PsrDeleteParserContext(&ctxVar);

        if (pdf->HaveOpenDoc())
            if (!pdf->OpenOutputFileA(B(outFile))) return 1;
        if (pdf->CloseFile())
            std::printf("OK: %s (%ld replacement(s) over %ld page(s))\n",
                        outFile.c_str(), replaced, pages);
    } catch (const _com_error& e) {
        const _bstr_t d = e.Description();
        std::fprintf(stderr, "%ws\n", d.length() ? (const wchar_t*)d : L"COM error");
        rc = 1;
    }
    return rc;
}

This file is in the SDK at examples/activex_cpp/edit_text/edit_text.cpp. The build fails if this page and that file ever differ.