diff --git a/CHANGELOG.md b/CHANGELOG.md
index b4225c1..3082546 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Fix warning on the Domains tab of PDF exports
- Fix errors when generating a pdf with software license/installation tabs
- Incomplete columns when exporting Users or Groups
+- Fix content missing from PDF export when a table contains a large image
## [4.1.6] - 2026-10-06
diff --git a/inc/simplepdf.class.php b/inc/simplepdf.class.php
index b30456f..e4d7f95 100644
--- a/inc/simplepdf.class.php
+++ b/inc/simplepdf.class.php
@@ -393,19 +393,25 @@ public function displayText($name, $content = '', $minline = 3, $maxline = 100)
// The sanitizer encodes some characters in attributes (e.g. "=" => "=", "+" => "+"), which TCPDF
// does not decode: document URLs are no longer recognized and base64 data URIs get corrupted.
// Images are therefore resolved here from their decoded "src" and passed to TCPDF as validated data streams.
- $content = preg_replace_callback(
+ // Resolved images are replaced by placeholders until the end of the processing: their (large) base64 data
+ // would otherwise make the table regex hit "pcre.backtrack_limit" and the whole content would be lost.
+ $images = [];
+ $placeholder_token = 'pdfimg' . bin2hex(random_bytes(8)) . '_';
+ $content = preg_replace_callback(
'/(]*>\s*)?(]*>)(\s*<\/a>)?/i',
- function (array $match): string {
+ function (array $match) use (&$images, $placeholder_token): string {
$img_tag = $this->buildPdfImageTag($match[2]);
if ($img_tag === null) {
return $match[0];
}
+ $placeholder = $placeholder_token . count($images);
+ $images[$placeholder] = $img_tag;
if ($match[1] !== '' && ($match[3] ?? '') !== '') {
// Remove the link wrapping the image
- return $img_tag;
+ return $placeholder;
}
- return $match[1] . $img_tag . ($match[3] ?? '');
+ return $match[1] . $placeholder . ($match[3] ?? '');
},
$content,
);
@@ -429,7 +435,18 @@ function (array $match): string {
}
}
- $this->displayInternal(240, 0.5, self::LEFT, $minline * 5, [$formatted_content]);
+ // Put back the resolved images
+ $formatted_content = strtr($formatted_content, $images);
+
+ // TCPDF also matches tables with a lazy regex and silently drops the whole content when it fails:
+ // raise "pcre.backtrack_limit" to fit the content size (large embedded images) while rendering it.
+ $backtrack_limit = ini_get('pcre.backtrack_limit');
+ ini_set('pcre.backtrack_limit', (string) max((int) $backtrack_limit, 2 * strlen($formatted_content)));
+ try {
+ $this->displayInternal(240, 0.5, self::LEFT, $minline * 5, [$formatted_content]);
+ } finally {
+ ini_set('pcre.backtrack_limit', $backtrack_limit);
+ }
/* Restore */
[$this->cols, $this->colsx, $this->colsw, $this->align, ] = $save;
@@ -447,10 +464,10 @@ function (array $match): string {
*/
private function buildPdfImageTag(string $img_tag): ?string
{
- if (!preg_match('/\bsrc\s*=\s*(["\'])(.*?)\1/is', $img_tag, $src_match)) {
+ if (!preg_match('/\bsrc\s*=\s*(?|"([^"]*)"|\'([^\']*)\')/i', $img_tag, $src_match)) {
return null;
}
- $src = html_entity_decode($src_match[2], ENT_QUOTES | ENT_HTML5, 'UTF-8');
+ $src = html_entity_decode($src_match[1], ENT_QUOTES | ENT_HTML5, 'UTF-8');
$data = null;
if (preg_match('/^data:image\/[a-z0-9.+-]+;base64,(.*)$/is', $src, $data_match)) {
diff --git a/tests/Units/SimplePdfTest.php b/tests/Units/SimplePdfTest.php
index ba4d21d..e93b486 100644
--- a/tests/Units/SimplePdfTest.php
+++ b/tests/Units/SimplePdfTest.php
@@ -36,6 +36,7 @@
use Document;
use Glpi\Tests\DbTestCase;
+use PHPUnit\Framework\Attributes\DataProvider;
use PluginPdfSimplePDF;
use Psr\Log\LogLevel;
use ReflectionMethod;
@@ -181,6 +182,53 @@ public function testDisplayTextDropsCorruptedBase64Image(): void
$this->assertStringNotContainsString('/Subtype /Image', $output);
}
+ /**
+ * Test a large document image inside a table
+ * Its base64 data made the table regex hit `pcre.backtrack_limit`, and the whole content vanished from the PDF
+ */
+ public function testDisplayTextEmbedsLargeDocumentImageInTable(): void
+ {
+ $content = $this->getLargePngContent();
+ $this->assertGreaterThan(1_000_000, strlen($content));
+
+ $docid = $this->createDocumentFile('PNG', 'png', $content);
+ $url = '/front/document.send.php?docid=' . $docid;
+
+ $pdf = new PluginPdfSimplePDF();
+ $pdf->newPage();
+ $pdf->displayText(
+ 'Description',
+ '
before %s after
']; + yield 'inside a table' => ['| before %s after |