Skip to content

fix: pdf display with tcpdf - #93

Draft
jdurand-teclib wants to merge 5 commits into
feature/glpi-12.0from
fix/pdf-display-tcpdf-7
Draft

jdurand-teclib wants to merge 5 commits into
feature/glpi-12.0from
fix/pdf-display-tcpdf-7

Conversation

@jdurand-teclib

@jdurand-teclib jdurand-teclib commented Sep 15, 2026 •

Copy link
Copy Markdown

This PR is a bit special:

Context

With TCPDF 7, even if the interface contract is the same, the rendering engine changed a bit and some adjustment were necessary on our side, otherwise pdf display would completely break.

There is however one thing I do not manage to figure out: some contents like the tag column from documents can overflow their cell, where it was perfectly handled by the library before.

Current solution

I used claude to help me fix this particular issue, but I'm not really conviced by the code produced. I believe indeed it can be a bit hard to maintain, but I do not find any other proper way to do it.

The concerned code is in PluginPdfSimplePDF::displayInternal() methods (simplepdf.class.php:297). It concist of 2 methods successively called to manage the line change ourselves, since TCPDF doesn't manage to do it.

image image image

Here is the result we can observe due to these lines, from a computer export that has documents attached:
image

And here is what it does if you do not have these changes:
image

I joined both pdf
computer_with_changes.pdf
computer_without_changes.pdf

Questions to any person reviewing this PR

Question regarding the overflowing test fix

Tip

So my question is the following: Do you think there is a better, more suitable way to fix this ?

I've spent a lot of time in TCPDF documentation and in the mapping file TCPDF wrote to transition from v6 to v7, to try to sport some breaking changes with the writeHTMLCell method we use to render our pdf cell and play with the arguments, because some of them changed a bit, but nothing concluding

https://www.rubydoc.info/gems/rfpdf/1.17.1/TCPDF:writeHTMLCell
https://github.com/tecnickcom/TCPDF/blob/main/MAPPING.md#htmlcss

Question regarding the image folder access rights for TCPDF

Header image was not displaying anymore, because TCPDF is now more restricted in what folders/files it can read. The only solution I found for now is defining the constant K_ALLOWED_PATHS if is not set.
It works, but it is quite fragile because if something loads this constant before simplepdf.class (which is possible because we use the same instance as the core), this line will be ignored and we will have the same issue.

Tip

I'm open to any more consistent solution if you can find some

@Rom1-B Rom1-B left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests

diff --git a/.gitignore b/.gitignore
index 57872d0..420fb40 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 /vendor/
+var/
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..b827cdf
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,18 @@
+<phpunit
+    bootstrap="tests/bootstrap.php"
+    colors="true"
+    testdox="true"
+    cacheDirectory="var/phpunit"
+>
+    <source>
+        <include>
+            <directory>src</directory>
+        </include>
+    </source>
+
+    <testsuites>
+        <testsuite name="Tests">
+            <directory suffix="Test.php">tests</directory>
+        </testsuite>
+    </testsuites>
+</phpunit>
diff --git a/tests/SimplePDFTest.php b/tests/SimplePDFTest.php
new file mode 100644
index 0000000..339a3b7
--- /dev/null
+++ b/tests/SimplePDFTest.php
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ *  -------------------------------------------------------------------------
+ *  LICENSE
+ *
+ *  This file is part of PDF plugin for GLPI.
+ *
+ *  PDF is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Affero General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  PDF is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *  GNU Affero General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Affero General Public License
+ *  along with Reports. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author    Nelly Mahu-Lasson, Remi Collet, Teclib
+ * @author    Teclib
+ * @copyright Copyright (c) 2009-2026 PDF plugin team
+ * @license   AGPL License 3.0 or (at your option) any later version
+ * @link      https://github.com/pluginsGLPI/pdf/
+ * @link      http://www.glpi-project.org/
+ * @package   pdf
+ * @since     2009
+ *             http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ *  --------------------------------------------------------------------------
+ */
+
+use Glpi\Tests\GLPITestCase;
+
+class SimplePDFTest extends GLPITestCase
+{
+    private function getStringWidth(PluginPdfSimplePDF $pdf, string $string): float
+    {
+        $property = new ReflectionProperty(PluginPdfSimplePDF::class, 'pdf');
+
+        return $property->getValue($pdf)->GetStringWidth($string);
+    }
+
+    public function testWrapCellContentLeavesHtmlUntouched(): void
+    {
+        $pdf = new PluginPdfSimplePDF();
+        $html = '<b>' . str_repeat('a', 200) . '</b>';
+
+        $this->assertSame($html, $this->callPrivateMethod($pdf, 'wrapCellContent', $html, 10));
+    }
+
+    public function testWrapCellContentLeavesContentUntouchedWhenWidthIsNotPositive(): void
+    {
+        $pdf = new PluginPdfSimplePDF();
+        $msg = str_repeat('a', 200);
+
+        $this->assertSame($msg, $this->callPrivateMethod($pdf, 'wrapCellContent', $msg, 0));
+    }
+
+    public function testBreakWordToFitLeavesWordUntouchedWhenItAlreadyFits(): void
+    {
+        $pdf = new PluginPdfSimplePDF();
+
+        $this->assertSame('short', $this->callPrivateMethod($pdf, 'breakWordToFit', 'short', 100));
+    }
+
+    public function testBreakWordToFitSplitsOnDelimiters(): void
+    {
+        $pdf = new PluginPdfSimplePDF();
+        $word = str_repeat('a', 20) . '/' . str_repeat('b', 20) . '-' . str_repeat('c', 20);
+        $width = $this->getStringWidth($pdf, str_repeat('a', 30));
+
+        $result = $this->callPrivateMethod($pdf, 'breakWordToFit', $word, $width);
+        $chunks = explode(' ', $result);
+
+        $this->assertSame($word, str_replace(' ', '', $result));
+        $this->assertGreaterThan(1, count($chunks));
+        foreach ($chunks as $chunk) {
+            $this->assertLessThanOrEqual($width, $this->getStringWidth($pdf, $chunk));
+        }
+    }
+
+    public function testBreakWordToFitFallsBackToCharacterSplitWithoutDelimiters(): void
+    {
+        $pdf = new PluginPdfSimplePDF();
+        $word = str_repeat('a', 200);
+        $width = $this->getStringWidth($pdf, str_repeat('a', 10));
+
+        $result = $this->callPrivateMethod($pdf, 'breakWordToFit', $word, $width);
+        $chunks = explode(' ', $result);
+
+        $this->assertSame($word, str_replace(' ', '', $result));
+        $this->assertGreaterThan(1, count($chunks));
+        foreach ($chunks as $chunk) {
+            $this->assertNotSame('', $chunk);
+            $this->assertLessThanOrEqual($width, $this->getStringWidth($pdf, $chunk));
+        }
+    }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..f87da9a
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ *  -------------------------------------------------------------------------
+ *  LICENSE
+ *
+ *  This file is part of PDF plugin for GLPI.
+ *
+ *  PDF is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Affero General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  PDF is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *  GNU Affero General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Affero General Public License
+ *  along with Reports. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author    Nelly Mahu-Lasson, Remi Collet, Teclib
+ * @copyright Copyright (c) 2009-2022 PDF plugin team
+ * @license   AGPL License 3.0 or (at your option) any later version
+ * @link      https://github.com/pluginsGLPI/pdf/
+ * @link      http://www.glpi-project.org/
+ * @package   pdf
+ * @since     2009
+ *             http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ *  --------------------------------------------------------------------------
+ */
+
+$current_plugin_folder = basename(dirname(__DIR__));
+
+require __DIR__ . '/../../../tests/bootstrap.php';
+require dirname(__DIR__) . '/vendor/autoload.php';
+
+if (!Plugin::isPluginActive($current_plugin_folder)) {
+    throw new RuntimeException(sprintf('Plugin %s is not active in the test database', $current_plugin_folder));
+}

Comment thread inc/simplepdf.class.php Outdated
Comment thread inc/simplepdf.class.php
jdurand-teclib and others added 4 commits September 30, 2026 09:05
Co-authored-by: Romain B. <8530352+Rom1-B@users.noreply.github.com>
If a word is too long and has no delimiter, it will overflow

Co-authored-by: Romain B. <8530352+Rom1-B@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants