fix: pdf display with tcpdf - #93
Draft
jdurand-teclib wants to merge 5 commits into
Draft
jdurand-teclib wants to merge 5 commits into
jdurand-teclib wants to merge 5 commits into
Conversation
jdurand-teclib
marked this pull request as draft
September 15, 2026 13:52
jdurand-teclib
requested review from
Lainow,
MyvTsv,
Rom1-B,
RomainLvr and
stonebuzz
September 15, 2026 13:53
Rom1-B
requested changes
Sep 16, 2026
Rom1-B
left a comment
There was a problem hiding this comment.
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));
+}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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
Here is the result we can observe due to these lines, from a computer export that has documents attached:

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

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