Skip to content

Troubleshoot compat-legacy

Most migration issues fit a small set of patterns. Each entry below lists the symptom, cause, and fix. When you are unsure about a specific method, check /integrations/tcpdf-compat/method-coverage/ and the authoritative in-repo matrix docs/TCPDF_COVERAGE.md.

The process used to stop on a PDF error; now an exception escapes

Section titled “The process used to stop on a PDF error; now an exception escapes”

Symptom. Code that once stopped on a bad render now throws an uncaught RuntimeException, and the request or job reports an error.

Cause. Legacy TCPDF Error() calls die(). The adapter throws RuntimeException instead, by design, so failures are observable.

Fix. Wrap render entry points in try/catch, and map the exception to your error contract. Do not restore die() behavior. See /integrations/tcpdf-compat/production-usage/ § Failure handling.

new \TCPDF() still resolves to the real TCPDF library

Section titled “new \TCPDF() still resolves to the real TCPDF library”

Symptom. You enabled LegacyBootstrap::enableAliases() but the output still looks like legacy TCPDF, or behavior has not changed.

Cause. enableAliases() registers an alias only when no class with that name already exists. If tecnickcom/tcpdf remains autoloadable and its \TCPDF class loads first, the alias is skipped, and your code keeps using legacy TCPDF.

Fix. During migration, use explicit per-file imports (use NextPDF\Compat\Tcpdf\TCPDF;) so each call site is unambiguous. Remove tecnickcom/tcpdf after the audit passes (see /integrations/tcpdf-compat/migration/ Stage 5). Do not run both libraries with global aliases enabled in the same process.

A method “works” but the parameter I passed is ignored

Section titled “A method “works” but the parameter I passed is ignored”

Symptom. A call succeeds and produces a Portable Document Format (PDF) file, but an option you passed (image link, alignment, dots per inch (DPI), bookmark color, …) has no effect.

Cause. The method is in the silent-ignore set. It accepts the parameter for source compatibility, then drops it. This is documented behavior, not a bug; see /integrations/tcpdf-compat/method-coverage/ §2.

Fix. Run a strict-mode audit to find every such call:

examples/troubleshoot-strict.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\Exception\TcpdfNotImplementedException;
use NextPDF\Compat\Tcpdf\TCPDF;
$pdf = new TCPDF();
$pdf->setStrictMode(true);
$pdf->AddPage();
$pdf->SetFont('helvetica', '', 12);
try {
$pdf->Image('logo.png', 10, 10, 50, 0, '', 'https://example.com');
} catch (TcpdfNotImplementedException $e) {
// Message lists every ignored parameter and a migration hint.
echo $e->getMessage(), "\n";
}

Then drop the parameter, or re-express it through the modern API ($pdf->getDocument()), as shown in /integrations/tcpdf-compat/migration/ Stage 4.

Symptom. Code that branches on the return value of MultiCell() (for example, to compute used height or line count) behaves incorrectly.

Cause. The adapter’s MultiCell() returns the compatibility placeholder 1, not the rendered cell count or line count. Write() similarly returns 0.

Fix. Do not branch on these return values. If you need the rendered height, compute it from getStringHeight() / getNumLines(), or move that logic to the modern API.

setPDFVersion('1.4') did not produce a PDF 1.4 file

Section titled “setPDFVersion('1.4') did not produce a PDF 1.4 file”

Symptom. You asked for an older PDF version; the output is still PDF 2.0.

Cause. The adapter always outputs PDF 2.0 (ISO 32000-2). setPDFVersion() is in the not-applicable set; the adapter emits a notice and continues.

Fix. Remove the call. If a downstream consumer requires an older PDF version, resolve that requirement separately; the adapter cannot down-target.

setSignature() did nothing — the PDF is not signed

Section titled “setSignature() did nothing — the PDF is not signed”

Symptom. You called setSignature() with a certificate; the output PDF has no signature.

Cause. The core engine does not implement setSignature() through this adapter. In default mode it is a no-op; in strict mode it throws.

Fix. Signing requires a commercial NextPDF edition and the modern signature API. See /integrations/tcpdf-compat/security-and-operations/ § Digital signatures. Do not expect the legacy setSignature() call to sign anything.

Output() corrupted my HTTP response or worker output

Section titled “Output() corrupted my HTTP response or worker output”

Symptom. PDF bytes appear in a Hypertext Transfer Protocol (HTTP) response, or a worker log is polluted with PDF bytes.

Cause. You used an output destination that writes to the output path (I/D) while you control the response yourself. The adapter does not echo into your buffer the way legacy TCPDF does, but I/D still drive engine output.

Fix. In workers and handlers you control, use Output($path, 'F') to write a file, or Output($name, 'S') to get bytes and emit them yourself. tests/Unit/Compat/Tcpdf/Bridge/OutputBridgeTest.php asserts that destination mapping is case-insensitive and whitespace-trimmed:

CodeReturnsSide effect
SPDF bytes (%PDF…)none
Fempty stringwrites file
Ebase64 MIME bodynone
FI / FDempty stringwrites file, then engine output
I / D / unknownempty stringengine output (inline/download)

Exact-byte PDF assertions fail after switching

Section titled “Exact-byte PDF assertions fail after switching”

Symptom. Snapshot tests comparing raw PDF bytes fail everywhere.

Cause. The engine uses an independent PDF 2.0 implementation. Delegated methods produce compatible visible output, but the bytes differ. This is expected.

Fix. Re-baseline your tests to assert on rendered content (extracted text), structure (page count, page size), or a smoke check (str_starts_with($bytes, '%PDF')). See /integrations/tcpdf-compat/migration/ Stage 4.

A legacy K_* / PDF_* constant has the wrong value

Section titled “A legacy K_* / PDF_* constant has the wrong value”

Symptom. A custom path or default you set through a constant is not taking effect.

Cause. The adapter auto-defines a constant only if it is not already defined, and it does so during first construction. If your define() runs after the first adapter is constructed, the adapter’s default is already in effect.

Fix. Define every custom K_* / PDF_* constant in your bootstrap, before any adapter instance is created. See /integrations/tcpdf-compat/configuration/ § Configuration resolution order.

Symptom. Construction fails or behavior changes unexpectedly after a dependency update.

Cause. The adapter requires nextpdf/core ^3.0. A resolved core version outside that range is unsupported.

Fix. Run composer show nextpdf/core, and pin the engine to ^3.0. See /integrations/tcpdf-compat/install/ § Verify the engine version.

QuestionWhere to look
What does method X actually do here?/integrations/tcpdf-compat/method-coverage/, docs/TCPDF_COVERAGE.md
Which of my calls lose parameters?Strict-mode audit (this page; /integrations/tcpdf-compat/migration/)
Why did the process not stop on error?/integrations/tcpdf-compat/security-and-operations/ § Hardened behaviors
Why is output not signed / not PDF/A?/integrations/tcpdf-compat/security-and-operations/
Alias vs explicit import conflictThis page; /integrations/tcpdf-compat/boot-and-discovery/
  • /integrations/tcpdf-compat/migration/ — the staged migration that prevents most of the above
  • /integrations/tcpdf-compat/method-coverage/ — per-method behavior reference
  • /integrations/tcpdf-compat/boot-and-discovery/ — alias registration and conflict avoidance
  • docs/TCPDF_COVERAGE.md — authoritative coverage matrix