Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tFPDF compatibility #27

Closed
JanSlabon opened this issue Oct 17, 2017 · 27 comments
Closed

Add tFPDF compatibility #27

JanSlabon opened this issue Oct 17, 2017 · 27 comments

Comments

@JanSlabon
Copy link
Member

JanSlabon commented Oct 17, 2017

Currently FPDI v2 cannot be used with tFPDF.

Also overwrite the Output() method to be compatible with FPDF 1.81.

@drjayvee
Copy link

drjayvee commented Jun 4, 2018

I'm adding this to make the issue more verbose. From the tFPDF documentation:

This class is a modified version of FPDF that adds UTF-8 support. Moreover, it embeds only the necessary parts of the fonts that are used in the document, making the file size much smaller than if the whole fonts were embedded. These features were originally developed for the mPDF project.

I would argue that UTF-8 support is an essential feature for a modern PDF library, so I hope that FPDI adds this feature.

The latest version of tFPDF is 1.25 and is based on FPDF 1.7. FPDF's latest version is 1.81. The changes made by tFPDF are substantial, so adding compatability is most likely not as simple as applying the diff between tFPDF and FPDF 1.7 to FPDF 1.81. (I'd be happy to try, but I have no knowledge of pdf / ttf, nor are there any unit tests to guide me.)

@JanSlabon
Copy link
Member Author

@drjayvee thanks for triggering this issue!

After all this is not a big deal but for an official implementation we need to offer a sync repository of tFPDF through packagist. I will see if we are going to set this up in the near future.

Until that: Use this simple class which brings back FPDI functionalities (not FpdfTpl) to tFPDF 1.25. Feedback is welcome!

@drjayvee
Copy link

drjayvee commented Jun 6, 2018

I was looking at the 2.0 version because we want to be ready for PHP 7.2 in the near future. We're currently using FPDI v1.4, which works fine with PHP <= 7.1, but breaks in 7.2*. Under the hood though, (t)FPDF is in dire need of modernization.

Actually, I was secretly hoping that Setasign was working towards unifying all the different PDF libs to create the one true PHP PDF library. Currently, we've got FPDI extends FPDF_TPL extends tFPDF, which is a fork of an older version of FPDF... it's a mess! One fork supports UTF-8, another one supports templates, yet another implements rounded corners, sub/sup text, etc.

From what you're saying about offering a sync repo of tFPDF, it sounds like you wish to keep FPDF_TPL / tFPDF under the hood. Is that correct? Are there plans to refactor, modernize and combine the base libraries?

I'd be happy to help, but like I said, I'm not at all familiar with the internals.

* I got an error about use of each, but now that I've looked at the code, there's only one call in FPDI v1.4. I'll take a look at newer versions.

@JanSlabon
Copy link
Member Author

hoping that Setasign was working towards [...] to create the one true PHP PDF library.

That's really in our minds and on our roadmap. Last puzzles are licensing, financing and man power/time.

From what you're saying about offering a sync repo of tFPDF

We simply need this to have it in our development process via composer (require-dev).

[...]it sounds like you wish to keep FPDF_TPL / tFPDF under the hood. Is that correct?

The features of FpdfTpl are still available in FPDI 2. But not in the given class of my previous reply for tFPDF. I omit this due to the class structure which collides here again.

Are there plans to refactor, modernize and combine the base libraries?

No. We would create a complete new state of the art library.

Currently my collegue is working on a kind of proxy class which is compatible to the interface of FPDF/FPDI but uses SetaPDF in the back, which comes with font subsetting (simliar to tFPDF but much faster and more stable/tested). If you're a SetaPDF customer this thing could be a temporary solution for you, too.

@drjayvee
Copy link

drjayvee commented Jun 6, 2018

Great to hear you're working on a completely modernized library! Hals- & Beinbruch ;p

I guess we'll just wait until FPDI includes templating and UTF-8 before we upgrade to FPDI 2.x.

Thanks a lot for taking the time to reply.

@JanSlabon
Copy link
Member Author

This version does not work for you because of the missing features of FPDF_TPL/FpdfTpl?

We're actually not working on it but have it in mind or on our roadmap.

@drjayvee
Copy link

drjayvee commented Jun 6, 2018

We do need template support added by FPDF_TPL (importPage, useTemplate), so Tfpdi won't work for us.

If you could bump up priority for template support, I'd be very grateful. I'd be happy to (beta) test once you have something working which supports templates and UTF-8.

@JanSlabon
Copy link
Member Author

Jeroen, if you are only using importPage() and useTemplate() you're ready to go! The methods beginTemplate() and endTemplate() of FpdfTpl are missing. The main functionalities of FPDI are available! It's only the manual creation of templates which is actually missing.

@drjayvee
Copy link

drjayvee commented Jun 8, 2018

Jan, thanks for pointing that out! This combination works fine for our requirements.

For reference, I'm now using

class PDF extends Tfpdi {

    public function getAsString () {
        return $this->buffer;
    }

    /**
     * Draw rectangle with rouded corners
     *
     * @author Christophe Prugnaud
     * @link   http://www.fpdf.org/en/script/script35.php
     *
     * @param int    $x       x
     * @param int    $y       y
     * @param int    $w       width
     * @param int    $h       height
     * @param int    $r       radius
     * @param string $style
     * @param string $corners numbers of the corners to be rounded: 1, 2, 3, 4 or any combination (1=top left, 2=top right, 3=bottom right, 4=bottom left).
     */
    public function RoundedRect ($x, $y, $w, $h, $r, $style = '', $corners = '1234') {
        $k = $this->k;
        $hp = $this->h;
        if ($style == 'F')
            $op = 'f';
        elseif ($style == 'FD' || $style == 'DF')
            $op = 'B';
        else
            $op = 'S';
        $MyArc = 4 / 3 * (sqrt(2) - 1);
        $this->_out(sprintf('%.2F %.2F m', ($x + $r) * $k, ($hp - $y) * $k));
        $xc = $x + $w - $r;
        $yc = $y + $r;
        $this->_out(sprintf('%.2F %.2F l', $xc * $k, ($hp - $y) * $k));
        if (strpos($corners, '2') === false)
            $this->_out(sprintf('%.2F %.2F l', ($x + $w) * $k, ($hp - $y) * $k));
        else
            $this->_Arc($xc + $r * $MyArc, $yc - $r, $xc + $r, $yc - $r * $MyArc, $xc + $r, $yc);

        $xc = $x + $w - $r;
        $yc = $y + $h - $r;
        $this->_out(sprintf('%.2F %.2F l', ($x + $w) * $k, ($hp - $yc) * $k));
        if (strpos($corners, '3') === false)
            $this->_out(sprintf('%.2F %.2F l', ($x + $w) * $k, ($hp - ($y + $h)) * $k));
        else
            $this->_Arc($xc + $r, $yc + $r * $MyArc, $xc + $r * $MyArc, $yc + $r, $xc, $yc + $r);

        $xc = $x + $r;
        $yc = $y + $h - $r;
        $this->_out(sprintf('%.2F %.2F l', $xc * $k, ($hp - ($y + $h)) * $k));
        if (strpos($corners, '4') === false)
            $this->_out(sprintf('%.2F %.2F l', ($x) * $k, ($hp - ($y + $h)) * $k));
        else
            $this->_Arc($xc - $r * $MyArc, $yc + $r, $xc - $r, $yc + $r * $MyArc, $xc - $r, $yc);

        $xc = $x + $r;
        $yc = $y + $r;
        $this->_out(sprintf('%.2F %.2F l', ($x) * $k, ($hp - $yc) * $k));
        if (strpos($corners, '1') === false) {
            $this->_out(sprintf('%.2F %.2F l', ($x) * $k, ($hp - $y) * $k));
            $this->_out(sprintf('%.2F %.2F l', ($x + $r) * $k, ($hp - $y) * $k));
        } else
            $this->_Arc($xc - $r, $yc - $r * $MyArc, $xc - $r * $MyArc, $yc - $r, $xc, $yc - $r);
        $this->_out($op);
    }

    protected function _Arc ($x1, $y1, $x2, $y2, $x3, $y3) {
        $h = $this->h;
        $this->_out(sprintf('%.2F %.2F %.2F %.2F %.2F %.2F c ', $x1 * $this->k, ($h - $y1) * $this->k,
            $x2 * $this->k, ($h - $y2) * $this->k, $x3 * $this->k, ($h - $y3) * $this->k));
    }
    
    /**
     * This function properly handles <sub>, <sup> tags in strings.
     * And prints the string on current x,y coordinates on this pdf.
     *
     * @param    string $string    might contain sub/sup tags
     */
    public function printSubSupString ( $string ) {
        if ( strstr($string, '<sup>') || strstr($string, '<sub>') ) {
            for( $i=0; $i<strlen($string); $i++ ) {
                if ( $string[$i] === '<' ) {
                    if ( substr($string, $i, 5) === '<sub>' ) {
                        $textPart = substr($string, $i+5, strpos($string, '</su', $i)-($i+5) );
                        $this->subSupWrite(5, $textPart, '', 7, -4 );
                        $i = strpos($string, '</su', $i)+5;
                    } elseif ( substr($string, $i, 5) === '<sup>' ) {
                        $textPart = substr($string, $i+5, (strpos($string, '</su', $i)-($i+5)) );
                        $this->subSupWrite(5, $textPart, '', 7, 4 );
                        $i = strpos($string, '</su', $i)+5;
                    }
                } else {
                    $char = mb_substr( $string, $i, 1 );    // ensure $char can contain multibyte chars
                    $this->Write(5, $char );
                }
            }
        } else {
            $this->Write(5, $string );
        }
    }
    
    /**
     * Output sub or sup text
     *
     * @link    http://www.fpdf.org/en/script/script61.php
     * @author    Wirus
     *
     * @param     int        $h                height
     * @param     string     $txt            text
     * @param     string     $link            link
     * @param     int     $subFontSize    fontSize
     * @param     int     $subOffset        offset, >0 for sup, <0 for sub.
     *                                     (4 is suggested as default offset)
     */
    public function subSupWrite ( $h, $txt, $link='', $subFontSize=12, $subOffset=0 ) {
        // resize font
        $subFontSizeold = $this->FontSizePt;
        $this->SetFontSize($subFontSize);
        
        // reposition y
        $subOffset = ((($subFontSize - $subFontSizeold) / $this->k) * 0.3) + ($subOffset / $this->k);
        $subX        = $this->x;
        $subY        = $this->y;
        $this->SetXY( $subX, ($subY - $subOffset) );
    
        //Output text
        $this->Write( $h, $txt, $link );
    
        // restore y position
        $subX        = $this->x;
        $subY        = $this->y;
        $this->SetXY( $subX, ($subY + $subOffset) );
    
        // restore font size
        $this->SetFontSize( $subFontSizeold );
    }
    
    /*
     * ensure $txt is string
     */
    public function Cell($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='') {
        parent::Cell($w, $h, (string)$txt, $border, $ln, $align, $fill, $link);
    }

    function Error($msg) {
        throw new \Exception($msg);
    }
}

I'd love to add these to Fpdi through a PR. Interested?

@JanSlabon
Copy link
Member Author

Thanks for the PR offer but these this has nothing to do with FPDI and should be implemented individually in an extending class. We leave this issue open until tFPDF support is available by default.

@drjayvee
Copy link

drjayvee commented Jun 8, 2018 via email

@MartinMa
Copy link

There is an unofficial composer package of tFPDF.

@JanSlabon I only use importPage() and useTemplate(). So I will try updating to the new version of FPDI using your workaround. Thanks.

Worth mentioning. There seems to be an alternative to tFPDF called TCPDF, which is supported by FPDI by using the class \setasign\Fpdi\TcpdfFpdi. Feature-wise, I don't know the difference between tFPDF and TCPDF, but they both seem to support UTF-8 and font subsetting.

@JanSlabon
Copy link
Member Author

This unofficial repo is far away from the official release.
Feel free to use the POC I mentioned some posts above. There will be a simliar official solution as we have for TCPDF.

@iwasherefirst2
Copy link

iwasherefirst2 commented Jul 17, 2018

@JanSlabon thank you for the workaround. But how do you actually include the tFPDF package? As you already mentioned, there exists no repository of tFPDF through packagist. I have problems with the unoffical tFPDF package mentioned from MartinMa, because for some reason all function names have been renamed, so this won't work with old scripts.

Your class starts with class Tfpdi extends \tFPDF so I assume you include it with composer autoload? I tried that too, but its not working, see https://stackoverflow.com/questions/51388856/adding-tfpdf-class-with-composer

Looking forward to a solution since I need UTF-8 and I need to import a PDF. Thanks for your help !

@JanSlabon
Copy link
Member Author

You shall never add any foreign package to the vendor folder!

Just create your own library folder and place tFPDF in there and require it when you need it. You may call it "legacy style" but it works, too ;-)

@iwasherefirst2
Copy link

iwasherefirst2 commented Jul 18, 2018

@JanSlabon okay I managed to include the tFPDF package with composer as explained here.

I added your workaround package in the folder vendor/setasign/fpdi/src/. Then I tried to import with that class only one template like this:


  $pdf->AddPage('L');
  $pdf->setSourceFile(storage_path('app/templates/plain_certificate_sh_v12.pdf'));
  $templateId = $pdf->importPage(1);
  $pdf->useTemplate($templateId, ['adjustPageSize' => true]);

But I get the error

Call to undefined method setasign\Fpdi\Tfpdi::setPageFormat()

image

Am I doing anything wrong here?

Edit: Whoups didn't see you answer! Thank you for the quick response! I will put it the folder outside vendor then.

@JanSlabon
Copy link
Member Author

Ok, the POC has some missing features :-)

Change your code to:

$pdf->setSourceFile(storage_path('app/templates/plain_certificate_sh_v12.pdf'));
$templateId = $pdf->importPage(1);
$size = $pdf->getTemplateSize($templateId);
$pdf->AddPage($size['orientation'], $size);
$pdf->useTemplate($templateId);

@iwasherefirst2
Copy link

@JanSlabon Thank you! That worked :)!!!

@JanSlabon
Copy link
Member Author

We just added a clone repository of tFPDF and added support for it in FPDIs "dev-master"-branch. Could you please test it?

We also created a metadata package. So installing it via composer this way:

"require": {
	"setasign/fpdi": "dev-master as 2.1",
	"setasign/fpdi-tfpdf": "^2.1"
}

Feedback is welcome!

@JanSlabon
Copy link
Member Author

Implemented and new release 2.1 will support tFPDF by default.

@MartinMa
Copy link

Thanks a lot! Didn't had the time to test yet. Will try this weekend. The workaround, however, worked perfectly. So I guess this will be just fine, too.

@drjayvee
Copy link

Great, this (setasign\Fpdi\Tfpdf\Fpdi) works out of the box for us!

Does "2.1 will support tFPDF by default" mean users won't have to add an additional dependency (setasign/fpdi-tfpdf)? I would very much encourage that, for reasons that I've outlined above. :)

@JanSlabon
Copy link
Member Author

Does "2.1 will support tFPDF by default" mean users won't have to add an additional dependency (setasign/fpdi-tfpdf)?

No. It's the other way round: Remove the dependency setasign/fpdi but use setasign/fpdi-tfpdf directly to evaluate the dependencies between tFPDF and FPDI. FPDI itself can be used with 3 different things now (FPDF, TCPDF, tFPDF). It wouldn't make sense to setup the dependency in FPDI then.

@drjayvee
Copy link

drjayvee commented Sep 19, 2018 via email

@JanSlabon
Copy link
Member Author

FPDI comes not with a single method which would allow you to write text. Why should anybody think that it supports UTF-8 by default then?

@drjayvee
Copy link

Oh really! :D I guess I'm confused then, sorry about that!

Between tFPDF, FPDI, FPDF_TPL, TCPDF, there are so many names (classes/packages/extensions/???) that I don't know which does what.

I would hope that one day there's a PDF library that has all the functionality (templates, UTF-8 support with font subsetting, etc) incorporated. Maybe that's a pipe dream.

For now, fpdi-tfpdf is exactly what we need, so thanks again for that.

@JanSlabon
Copy link
Member Author

@iwasherefirst2 Feel free to open a new issue and provide all required details so that we are able to reproduce your issue and please do not highjack already closed issues. "setasign/fpdi-tfpdf" is a dependency in your composer.json and for sure not a class name!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants