imagick overlaying text on pdf letter - php

I have pdf designed letter. Now using php I would like to fetch the address and put it on pdf letter and generate another pdf file with that address dynamically.
How can I do this.

using imagick / imagickdraw (ext: php-imagick) It's a pain to setup under windows but if you're running linux it's pretty quick and easy.
$Imagick = new Imagick();
$Imagick->setResolution(300,300);
$Imagick->readImage('my.pdf[' . $page_number . ']');
$width = $Imagick->getImageWidth();
$height = $Imagick->getImageHeight();
$height *= (float) (2550 / $width);
$Imagick->scaleImage(2550, $height);
if (0 != $rotation)
$Imagick->rotateImage(new ImagickPixel(), $rotation);
$scaled_ratio = (float)$Imagick->getImageWidth() / 850;
// put white boxes on image
$ImagickDraw = new ImagickDraw();
$ImagickDraw->setFillColor('#FFFFFF');
$ImagickDraw->rectangle($x1, $y1, $x2, $y2);
$Imagick->drawImage($ImagickDraw);
// put text in white box (really on canvas that has already been modified)
$ImagickDraw = new ImagickDraw();
/* Font properties for text */
$ImagickDraw->setFont('times');
$ImagickDraw->setFontSize(42); // 10 * 300/72 = 42
$ImagickDraw->setFillColor(new ImagickPixel('#000000'));
$ImagickDraw->setStrokeAntialias(true);
$ImagickDraw->setTextAntialias(true);
// add text to canvas (pdf page)
$Imagick->annotateImage(
$ImagickDraw,
$x1 + 4, // 1 * 300/72 = 4
$y1 + 42, // 10 * 300/72 = 42
0,
$the_text // do not use html.. strip tags and replace <br> with \n if you got the text rom an editable div. (which is what I'm doing)
);
$Imagick->writeImage($filename);
I actually use ghostscript to merge the pdfs (individual pages written to a temp directory) into a single pdf. The only problem I've seen is pages seem faded where I've used $Imagick->annotateImage() or $Imagick->drawImage(). I'm figuring that out right now which is why I found this question.
I guess It's a half answer but I hope it helps someone.
--- addition via edit 4/6/2012 ---
Found a way around the PDF image fading.
$Imagick->setImageFormat("jpg");
$Imagick->writeImage('whatever.jpg');
$Imagick = new Imagick();
$Imagick->setResolution(300,300);
$Imagick->readImage('whatever.jpg');
--- another addition via edit 5/1/2012 ---
Found a way around greyscale from pdf to tif looking awful.
Just one command.
Ghostscript PDF -> TIFF conversion is awful for me, people rave about it, I alone look sullen
$Imagick->blackThresholdImage('grey');
--- end of edit 5/1/2012 ---
$Imagick->setImageFormat("pdf");
$Imagick->writeImage($filename);

It's expensive for a license, but PDFlib is designed for such things - opening a template .pdf file and adding new items dynamically to produce an output pdf. There's other free PDF manipulation libraries such as TCPDF which can probably do the same thing.

I used fpdf with fpdi. It worked fine with me. I almost overlayed thousands of file without any problem.

Related

pdflib - PHP: Inserting PDI object with background color as a single object/group

Currently using an older (and recent) version of pdflib (7.0 and 9.2). I'd like a possible solution to work with 7. We have an application where we compose a single PDF file from multiple smaller PDF files. These PDF files may not have an explicit background object set, so they're inserted with transparency active (i.e. the background shines through).
The objects are inserted by using PDI ($p refers to the parent document):
$pdf_doc = PDF_open_pdi_document($p, $file, "");
$image = PDF_open_pdi_page($p, $pdf_doc, 1, "");
PDF_fit_pdi_page($p, $image, $x, $y, $boxsize . " position 50 fitmethod meet");
PDF_close_pdi_page($p, $image);
This works fine as long as the background is as expected (i.e. white). In those cases where someone wants to have a different background (either a different PDF file or a different color), we add a white box with the same boxsize first, then draw the PDF document over it - effectively creating a white background for that single inserted object.
function draw_box($pdf, $offset_x, $offset_y, $width, $height) {
PDF_setcolor($pdf, 'fill', 'cmyk', 0, 0, 0, 0);
PDF_rect($pdf, $offset_x, $offset_y, $width, $height);
PDF_fill($pdf);
}
This works fine for viewing. The problem comes when someone wants to edit the resulting PDF later in Adobe Acrobat or Adobe Illustrator - the box that has been drawn as the background is not grouped together with the rest of the PDF content, making it harder to work with - you have to make sure you're also moving the white box behind the inserted PDF file.
I'd like to work around this without having to insert an explicit background object in all the source PDFs as that is not really a viable strategy because of the number of source PDFs.
I've tried working around the issue by creating a new PDF document, drawing the white box inside this document, then inserting the PDF into this document again. This seems to require writing the pdf to disk and then loading it instead, something I'd like to avoid for performance reasons. The documentation says a "virtual pdf file" can be used, but I haven't been able to find any references to this in the pdflib documentation. The code below barfs when I try to create a PDI document from the in-memory PDF.
$inserted = PDF_new();
PDF_begin_document($inserted, "", '');
$inserted_page = PDF_begin_page_ext($inserted, 20, 20, '');
$pdf_doc = PDF_open_pdi_document($inserted, $file, "");
$image = PDF_open_pdi_page($inserted, $pdf_doc, 1, "");
PDF_fit_pdi_page($inserted, $image, $x, $y, $boxsize . " position 50 fitmethod meet");
PDF_close_pdi_page($inserted, $image);
// then create a PDI document to insert into the parent
// This barfs, since it expects a file.
$new = PDF_open_pdi_document($p, $inserted, "");
I've also tried drawing directly to the PDI document, but this resulted in a segmentation fault. I sadly don't have the code for that attempt available any longer.
So any suggested solutions for either how to get a white background color as the default in the inserted PDF through PDI, or for merging the drawn box with the object inserted by PDI?
The main problem is the following:
This works fine for viewing. The problem comes when someone wants to edit the resulting PDF later in Adobe Acrobat or Adobe Illustrator - the box that has been drawn as the background is not grouped together with the rest of the PDF content, making it harder to work with - you have to make sure you're also moving the white box behind the inserted PDF file
Since PDF is a final format, it is not intended for later editing. Therefore no "groupings" or other logical editing information is available.
So manipulating PDF files will never work reliably as if you had a document format designed for it.
Therefore it depends on the application whether it recognizes PDF elements as a group or not. When using a current Acrobat DC version, I was not able to move an entire imported page as a single object. It offers me several smaller objects to move.
=> I would not recommend editing PDF files.
But from your description, it seems that the Acrobat/Illustrator versions you use treat an XObject as a single object that you can move. If this assumption is correct, you could encapsulate the white rectangle and the PDI page in a template.
This workaround might work for your current versions, but might not work in later versions.
For a detailed introduction to this feature see the PDFlib 9.2 tutorial, chapter 3.2.4 "Templates (Form XObjects)", and its use is also demonstrated in the "repeated content" example in the PDFlib Cookbook. Templates are also available in the outdated PDFlib 7, but have been extended in the last decade.
About PVF: The use of PVF is demonstrated in the starter_pvf example which is included in the PDFlib 7 and 9 download packages. (and available within the PDFlib cookbook "starter_pvf")
In your case you should create the first document in memory and retrieve the data with get_buffer(). For the new document, create a new PVF file with a new name and the contents of get_buffer(). Then open this file with open_pdi_document(). In this case, you do not have any files on disc.
Based on Rainer's answer above, I ended up with the following that works - and ends up with a single object in Illustrator. $pdf is the containing PDF:
function draw_box($pdf, $offset_x, $offset_y, $width, $height)
{
PDF_setcolor($pdf, 'fill', 'cmyk', 0, 0, 0, 0);
PDF_rect($pdf, $offset_x, $offset_y, $width, $height);
PDF_fill($pdf);
}
function insert_pdf_file($pdf, $file, $offset_x, $offset_y, $page = 1)
{
$src = PDF_open_pdi_document($pdf, $file, "");
$page = PDF_open_pdi_page($pdf, $src, $page, "");
// Insert PDF page into current PDF
PDF_fit_pdi_page($pdf, $page, $offset_x, $offset_y, "");
}
function insert_pdf_file_with_background($pdf, $inserted, $offset_x, $offset_y, $width, $height)
{
$grouped = PDF_begin_template_ext($pdf, $width, $height, '');
draw_box($pdf, 0, 0, $width, $height);
insert_pdf_file($pdf, $inserted, 0, 0);
PDF_end_template($pdf);
PDF_fit_image($pdf, $grouped, $offset_x, $offset_y, "");
}
For pdflib 7 I had to move the open_pdi_document and open_pdi_page outside of the template context. The result is still as expected.

PHP Downsizing or Converting PDF for Live Preview

Problem
Currently working on creating a live PDF generation preview in Laravel PHP using using PDFI + TCPDF so that a user can import a base PDF and embed text on top of it
PDF generation is working fine for all sizes, but large PDFs (e.g. A1 size) generates a 10+ MB file that is too large to serve back to the front end for preview
Looking for the quickest and best method to either optimise and reduce the PDF file size, or resize the actual PDF dimension to serve a minified version for preview only
TLDR
Looking for suggestions (other than those I've tried below), or improvements on what I've tried to create a PDF preview file either through resizing or converting to image file of original large PDF.
What I've Tried So Far
Imagick PDF to Image Conversion - Good output size (31mb > 700kb) but slow (1secs > 10secs)
Converting PDF to image using Imagick before creating a minified PDF using the image was my original idea however I found that Imagick was really slow at reading the PDF blob image (takes about 9 seconds compared to sub 1 second for the PDF generation itself). Code is as follows
// $output === the PDF generated
$downscaleSizeFactor = $this->jsonFile->canvas['downscale_size_factor'] ?? 1;
$previewWidth = $this->size['width'] / $downscaleSizeFactor;
$previewHeight = $this->size['height'] / $downscaleSizeFactor;
$im = new Imagick;
$im->readImageBlob($output); // SLOW HERE!!!
$numPages = $im->getNumberImages();
$pdfPreview = new TCPDF($this->size["orientation"], 'mm', [$previewWidth, $previewHeight], true, 'UTF-8', false);
$pdfPreview->setPrintHeader(false);
$pdfPreview->setPrintFooter(false);
$pdfPreview->SetAutoPageBreak(false, 0);
for($i=0;$i<$numPages;$i++) {
$im->setIteratorIndex($i);
$selectedIm = $im->getImage();
$selectedIm->resizeImage($previewWidth, $previewHeight, imagick::FILTER_LANCZOS, 1, true);
$selectedIm->setImageBackgroundColor('white');
$selectedIm->setImageAlphaChannel(Imagick::ALPHACHANNEL_REMOVE);
$selectedIm->mergeImageLayers(Imagick::LAYERMETHOD_FLATTEN);
$selectedIm->setImageFormat('png');
$selectedIm->setImageCompressionQuality(100);
$imageString = $selectedIm->getImageBlob();
// add a page
$pdfPreview->AddPage();
// set JPEG quality
$pdfPreview->setJPEGQuality(100);
$pdfPreview->Image('#'.$imageString, 0, 0, $previewWidth, $previewHeight);
}
$im->clear();
$im->destroy();
return $pdfPreview->output('', 'S');
Rerun FPDI + TCPDF to generated Minified Version - Bad output size (31mb > 31mb) but extremely fast (1secs > 1.5secs)
Saving the generated PDF to a temporary folder, then using the generated PDF to generate a minified version for preview only. This worked great in terms of speed, however it didn't change the file size at all. Reducing from [600 mm x 800 mm] to [10 mm x 10 mm] did not reduce the file size at all, which is weird. Maybe I missed something if anyone can see. Code as follows
$reducedPdf = new FpdiTcpdfCustom();
$tempPdfFile = storage_path('app/templates/pdf/temp/'.$name.'');
$pageCount = $reducedPdf->setSourceFile($tempPdfFile);
$pageNo = 1;
for ($pageNo; $pageNo <= $pageCount; $pageNo++) {
// Checks if the page is to be skipped
// Import a page from the blank by setting the
$pageId = $reducedPdf->importPage($pageNo);
// Return the size of the imported page
$size = $reducedPdf->getTemplateSize($pageId);
// Remove default header/footer
$reducedPdf->setPrintHeader(false);
$reducedPdf->setPrintFooter(false);
$reducedPdf->SetAutoPageBreak(false, 0);
// Creates the PDF page
$reducedPdf->AddPage($size['orientation'], [10,10]);
$reducedPdf->useTemplate($pageId, 0, 0, 10, 10);
}
return $reducedPdf->output('', 'S');
Using Spatie\PdfToImage to generate Image File - Good output size (31mb > 218kb) but rubbish speed (1secs > 27secs just to convert and save image)
Similar to previous, looking to convert the PDF to an image file, before resizing and embedding the image into a PDF for preview. But it is extremely slow so I gave up on this method before even generating the PDF.
$tempPdf = new \Spatie\PdfToImage\Pdf(storage_path('app/templates/pdf/temp/'.$name));
$tempPdf->setCompressionQuality(10);
$tempPdf->saveImage(storage_path('app/templates/pdf/temp/'));
Suggestions?
Does anyone have suggestion on either improving my attempts, or another way of achieving what I need?

How to put a watermark text across the whole image in diagonal position?

I have connected my Google Drive with my website and im getting images in this format: https://googledrive.com/host/{$GoogleID}. What i want to do is to add a text (not image) watermark to all the images im getting from Google Drive. I already tried with:
http://php.net/manual/en/image.examples.merged-watermark.php
http://phpimageworkshop.com/tutorial/1/adding-watermark.html
Both of them dosent work for me or i cant get them to work i dont know why. I will give an example for an image url: https://googledrive.com/host/0B9eVkF94eohMRlBQVENRWE5mc2c
I have also tried the code from this answer, but it dosent work as well. I guess the problem should be that the files i'm getting from Google Drive are not with the file extention and maybe this cause the problem. This is only my guess...
UPDATE:
i managed to show the photo on the website, but how to put the text in diagonal possition across all the photo like this
try to read image with file_get_contents or fopen and create image from string.
$im = imagecreatefromstring(file_get_contents("image url"));
and then use this example: http://php.net/manual/en/image.examples.merged-watermark.php
Thanks to #Durim Jusaj for helping me out with this one and finally i got the answer after a lot of searching and dealing with a lot of problems i will share my knowledge with u guys. So i will post the code and i will explain what is the code doing.
First thing first. Include this function in your file. This function is finding the center of the image. I havent wrote it i found it in the php docs under the comments so maybe it can be done without it or this function can be modified to be better written.
function imagettftext_cr(&$im, $size, $angle, $x, $y, $color, $fontfile, $text)
{
// retrieve boundingbox
$bbox = imagettfbbox($size, $angle, $fontfile, $text);
// calculate deviation
$dx = ($bbox[2]-$bbox[0])/2.0 - ($bbox[2]-$bbox[4])/2.0; // deviation left-right
$dy = ($bbox[3]-$bbox[1])/2.0 + ($bbox[7]-$bbox[1])/2.0; // deviation top-bottom
// new pivotpoint
$px = $x-$dx;
$py = $y-$dy;
return imagettftext($im, $size, $angle, $px, $py, $color, $fontfile, $text);
}
Load the image you want to apply the watermark to. I'm using Google Drive to extract my photos so thats why im using the file_get_contents, if you are the case like mine then use this, BUT otherwise use what is common and if your picture have extension like .jpg or .png you can use imagecreatefromjpeg or imagecreatefrompng. So it should be something like that $im = imagecreatefromjpeg('photo.jpeg'); for example.
$im = imagecreatefromstring(file_get_contents("https://drive.google.com/uc?id=" . $v['GoogleID']));
After that we need to calculate the position of watermark so we want the watermark to be in the center and to auto adjust its size based on the size of the photo. So on the first like we store all the attributes of the image to array. Second and third lines we calculate where is the center of the image (for me dividing it by 2.2 worked great, you can change this if u want to adjust the position. Last line is the size of the watermark according to the size of the image. This is very important as the watermark will be small or very big if u have images with different sizes.
list($width, $height, $type, $attr) = getimagesize("https://drive.google.com/uc?id=" . $v['GoogleID']);
$width = $width / 2.2;
$height = $height / 2.2;
$size = ($width + $height) / 4;
Set the content-type
header('Content-Type: image/png');
Create the image. I dont know why it should be done twice, but im following the php manual.
$im = imagecreatefromstring(file_get_contents("https://drive.google.com/uc?id=" . $v['GoogleID']));
Create some colors. So, if you want your watermark to be transparent (like mine) you have to use imagecolorallocatealpha, otherwise use imagecolorallocate. Last parameter is the transparency. You can google every function name for more info from the php doc.
$black = imagecolorallocatealpha($im, 0, 0, 0, 100);
The text to draw. Simple as that. Write what you want your text to be.
$text = 'nima.bg';
Replace path by your own font path. Put a font in your server so the code can take it (if you dont have already).
$font = 'fonts/ParsekCyrillic.ttf';
Adding all together. Basically you are creating the final image with the water mark with this function. The magic ;)
imagettftext_cr($im, $size, 20, $width, $height, $black, $font, $text);
Now this is the tricky part! If you want to just display the image without saving it to your server you can simply copy and paste this code, but the website will load very slow if you have more then 2-3 images. So, what i suggest you to do it to save the final images to your server and then display it. I know you will have duplicates, but this is the best choice i think.
ob_start();
imagepng($im);
$image = ob_get_contents();
ob_end_clean();
imagedestroy($im);
echo "<img src='data:image/png;base64," . base64_encode($image) . "'>";
}
OR you can save the images to your server and then display them which is much much faster. The best thing you can do is to process all the images create them with the watermark and then display them (so you have them ready to be show when a visitor visit your website).
imagepng($im, 'photo_stamp.png');
imagedestroy($im);
And the final result for me was that.
UPDATE: As of 2017 you can extract the image using this link 'https://drive.google.com/uc?id=(GoogleID)' or you can just use the 'webContentLink' property of the Google_DriveFile Object (it will give you the same link).

php image manipulation - fade to transparency

does anyone know how to apply fade effect to an image using PHP ? what I am looking for is a way to apply gradient transparency ( i mean : at the top , the image is opaque , which gradually gets more and more transparent , and at the bottom it is completely transparent).
i have been reading up on http://php.net/manual/en/function.imagecolortransparent.php , but did not see anything about applying a gradient effect to an image.
i also read : PHP - Generate transparency (or opacity) gradient using image , but it kinda trailed off without any solution!
I am also open to any other suggestion / libraries that can do this from command line.
Obviously you'll need to work with a png for this effect, but you can convert any png into a jpg using php. The following question I believe covers what you are asking about. Part of the code will have to be removed to clear the image reflection effect.
Can You Get a Transparent Gradient using PHP ImageMagick?
The piece of code which seems to do what you are trying to accomplish is:
$im = new Imagick('image.jpg'); //Reference image location
if (!$im->getImageAlphaChannel()) {
$im->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET);
}
$refl = $im->clone();
$refl->flipImage();
$gradient = new Imagick();
$gradient->newPseudoImage($refl->getImageWidth() + 10, $refl->getImageHeight() + 10, "gradient:transparent-black");

Why is the quality on generated PDFs so low with this code?

I have the following code. It's used to combine various image attachments (and pdfs) into one PDF. For some reason, when I take even a single PDF and put it through the code, the end result comes out looking very bad compared to the original PDF. In addition, I can select text in the source PDF, but in the generated one I cannot.
Any help would be greatly appreciated.
// PDF object
$pdf = new Imagick();
$max_resolution = array('x' => 100, 'y' => 100);
foreach($attachment_ids as $attachment_id) {
$attachment = DAO_Attachment::get($attachment_id);
$file = Storage_Attachments::get($attachment);
// Temporarily store our attachment
$im = new Imagick();
$im->readImageBlob($file);
// We need to reset the iterator otherwise only one page will be rotated
$im->resetIterator();
// Get the resolution
$resolution = $im->getImageResolution();
if($resolution['x'] > $max_resolution['x']) {
$max_resolution['x'] = $resolution['x'];
}
if($resolution['y'] > $max_resolution['y']) {
$max_resolution['y'] = $resolution['y'];
}
$num_pages = $im->getNumberImages();
$rotation = array_shift($rotations);
$degrees = $rotation > 0 ? 360 - $rotation : 0;
$pages = array();
if($degrees > 0) {
// Rotate each page
for($i = 1; $i <= $num_pages; $i++) {
$im->nextImage();
$im->rotateImage(new ImagickPixel(), $degrees);
}
}
// We need to reset the iterator again so all of our pages will be added to the pdf
$im->resetIterator();
// If the image format isn't a pdf, convert it to a png
if($im->getImageFormat !== 'pdf') {
$im->setImageFormat('png');
// Opacity
if(method_exists($im, 'setImageOpacity'))
$im->setImageOpacity(1.0);
}
$im->setImageCompression(imagick::COMPRESSION_LOSSLESSJPEG);
$im->setImageCompressionQuality(100);
$im->stripImage();
// Add the rotated attachment to the PDF
$pdf->addImage($im);
// Free
$im->destroy();
}
// Create a composite
$pdf->setImageFormat('pdf');
// Compress output
$pdf->setImageCompression(imagick::COMPRESSION_LOSSLESSJPEG);
$pdf->setImageCompressionQuality(100);
$pdf->stripImage();
// Set resolution
$pdf->setImageResolution($max_resolution['x'], $max_resolution['y']);
This may be obvious to you already but a low quality image will not result in a high quality pdf. I don't know how good Imagick's pdf generation capabilities are, but it seems from your code you are converting images? You could compare by doing the same thing with TcPDF, though if the image is low quality I doubt you will get better results.
Also, if you have access to higher DPI resolution images than the usual web-optimised format, I recommend you use those to build your PDF instead. The quality will be a lot better.
ImageMagick uses GhostScript to convert PDFs to various raster image formats. GhostScript is quite good at this, but you're hand-cuffing it by scaling the page down to a max of 100x100.
An 8.5x11 (inches) page at 72 dpi, is 612x792 pixels.
Perhaps you meant to restrict DPI rather than resolution? The output still won't scale all that well (vector formats vs pixel formats), but I suspect it would be a big improvement.
It turns out the answer to this is to set the DPI using setResolution(). We do this before using readImageBlob() to get read the file containing our image, as it will change the DPI of the image based on the current resolution (so setting it afterwards won't work).
You could also use some math and use resampleImage() to do it after the fact, but setResolution() seems to be working perfectly for us.

Categories