I have created an FPDF PDF document that puts an image on label paper which has 9 rows of labels (PHP code below)
There is no space/gap between each row of labels, and the next row of labels start immediately after the previous row.
### THE ISSUE: ###
When the labels are printed, the image displayed on each label moves down slightly from the top of each label, causing the bottom row of labels (9th row) to be significantly different to the 1st couple of rows.
I need to add additional content to each label, and if this issue continues, some of this content is going to get cut off.
I don't understand why this is happening, and can't see anything obvious in the code. Can anyone here spot what I'm doing wrong?
My code.....
use Fpdf\Fpdf as FPDF;
$current_x_position = 0;
$current_y_position = 0;
$total_y_per_page = 9;
$total_x_per_page = 3;
$pdf = new FPDF();
$pdf->SetMargins(0,0);
$pdf->SetAutoPageBreak(false);
$pdf->AddPage();
for($qty = 1; $qty <= 10; $qty++) {
label($current_x_position, $current_y_position, $pdf);
$current_y_position++;
if($current_y_position == $total_y_per_page) {
$current_x_position++;
$current_y_position = 0;
if($current_x_position == $total_x_per_page) {
$current_x_position = 0;
$current_y_position = 0;
$pdf->AddPage('P');
}
}
}
$pdf->Output();
function label($current_x_position, $current_y_position, $pdf) {
$left_margin = 7;
$top_margin = 15;
$label_width = 66;
$label_height = 30;
$current_x_position = $left_margin + ($label_width * $current_x_position);
$current_y_position = $top_margin + ($label_height * $current_y_position);
$pdf->Image('image.png', $current_x_position, $current_y_position+=1, $label_width, 10, 'png');
return;
}
?>
I think the problem stems from how x is being handled. It should be distinct from y. Why? Because x needs to be reset after every 3rd label is printed. Since it is only tested and incremented after 9 rows are printed, it will surely cause slueing. (Maybe that's an old-timey expression, used to indicate a program causes a new line because it is already past the column in which you want to print). Suggest you need to test x after every label print, so it can be reset to 0 when it reaches total_x_per_page (which I think really means total_x_per_row). The x test needs to be independent of the y test; it needs to be before the y test because you the columns will be complete before the rows.
In pseudo-ish code:
print a label
if the row is complete reset x, otherwise increase x
if the page is complete, reset y and x.
My experience with Fpdf is nil. My experience with (fighting) labels goes back decades :)
Related
I'm using TCPDF along with FPDI to create documents in PDF based on a template using a PHP form.
I need a way to ID them. The problem is - most of these documents were already created manually, thus having ID format already specified.
It's basically Name-DD.MM.YYYY-X.pdf and it can't change.
For example, Document-01.01.2014-2, where 2 means it's a second document issued that day.
Is there a way to do that automatically? I'm using a traditional <form> with action set to the PHP TCPDF script and it works flawlessly, but how to specify that if something was already generated today then do $var = $var + 1 and then reset it at midnight?
You could try using the function file_exists(), in order to test if the base name is already generated or not.
If it is, simply create a loop and increment your $var to test iteratively your documents.
Example:
$baseName = "Name-DD.MM.YYYY";
$extension = ".pdf";
$i = 0;
while(1)
{
if($i > 0)
$testName = $baseName."-".$i.$extension;
else
$testName = $baseName.$extension;
if(!file_exists($testName))
break;
$i++;
}
if($i > 0)
$validName = $baseName."-".$i.$extension;
else
$validName = $baseName.$extension;
Hoping my answer would help you,
Venom
I'm using JpGraph to graph some data. When I use the SetScale function to force a y-axis range the output visually overflows the graph area. I would like to have the output cropped to the graph area.
<?php
require_once ('include/jpgraph/jpgraph.php');
require_once ('include/jpgraph/jpgraph_line.php');
$datay1 = array(20,7,16,46,90,5,0,5,95);
// Setup the graph
$graph = new Graph(400,300);
$graph->title->Set('Graph Title');
$graph->title->font_size = 20;
$graph->SetScale("textlin",20,50);
//this version works but does not set the y-axis scale
//$graph->SetScale("textlin");
$p1 = new LinePlot($datay1);
$graph->Add($p1);
// Output line
$graph->Stroke();
?>
This is the current output:
This is the desired output (rendered by Excel):
I got in touch with the company behind JpGraph and they were able to help. The missing option is:
$graph->setClipping(true);
I hope this helps anyone else who may have a similar issue.
$myMaxY = 50; //You need to set Y Max as you want
$length = count($datay1);
for($i = 0; $i < $lenght; $i++)
{
if($datay1[$i] > $myMaxY)
$datay1[$i] = $myMaxY;
}
$graph->SetScale("textlin",20,$myMaxY); //Dont forget to change this
The problem is, I need to write data to several copies of the same imported pdf file, and save it as one pdf. I can write data to one page just fine, but when I try to write to more than one, or even continue text (using SetAutoPageBreak()), it simply stops writing once it hits the next page. Although, if I add an arbitrary loop to write more data, the resulting pdf's number of pages is increased to accommodate the added data, but the pages beyond the first are still blank. I have simplified what I'm trying to do into a smaller example to illustrate the issue:
public function actionSample() {
$pdf = new FPDI();
$pdf->AcceptPageBreak();
$pdf->SetAutoPageBreak(true, 30);
$pagecount = $pdf->setSourceFile('images/sample.pdf');
for ($i = 1; $i <= $pagecount; $i++) {
$pdf->AddPage();
$tplidx = $pdf->ImportPage($i);
$pdf->useTemplate($tplidx, 10, 10, 200);
$s = $pdf->getTemplatesize($tplidx);
$pdf->SetTextColor(32,32,32);
$pdf->SetFontSize(10);
$pdf->SetXY($pdf->getX(), $pdf->getY()+10);
$pdf->Write(2, 'This is not!');
}
$pdf->Output('Sample.pdf', 'D');
}
The sample document has 3 blank pages initially. (I did this to make it easier to see what was being written)
$pdf->AddPage();
you have to just put this piece of code inside for loop near closing braces.
All the best..!!
I'm developing a game which requires a randomly generated map. The map is going to be, effectively, a giant grid containing mostly emptiness with objects scattered about, grouped in regions. The idea is that I first generate these regions, then individual classes for each region type handle the generation of the map within the regions.
What I'm currently trying to do is generate these regions randomly on a grid. The regions will always be rectangles or squares and of any size, and the grid itself could also be of any size (again, either rectangular or square).
So to simplify all this down, I'm trying to generate randomly proportioned rectangles on a grid of arbitrary size. For the record, when I say arbitrary, I mean seriously large - we could be looking at sizes as high as 100,000 x 100,000, potentially. Regions are probably going to be a maximum of 100x100, but again, the script should be able to cope with any size.
Just to throw some more constraints out there, these regions cannot overlap. Ultimately, both the regions, and the objects generated within each region will be put into a MySQL database.
I initially thought that using a 2 dimensional array to represent the entire grid and simply marking spaces as taken within that would be fine, and it was in initial tests for small sized grids. I found for very large grids, Php complained about exceeding the memory limit and would not execute the script.
Another approach I thought about taking but haven't tried is inserting regions into the database as they are created and then running a query on the database to see if an area is empty or not every time it is required. I'm confident I could make this work, but the amount of database queries would be huge. I can reduce the number of tests required by eliminating certain squares that I can know for sure to be unusable via purely mathematical means, but for large grids we're still talking a massive amount of queries. This would make the script really, really time intensive and I can't help but feel that it's unnecessary. I'd prefer to do things another way, if possible.
The last method I thought of is purely mathematical, and would be perfect if I could make it work. I used only two variables - maximum x and maximum y to store the highest value on each axis. When testing to create a region I simply checked if the new region's top left coordinate was lower than both the maximum x and y - if it was, the space could not be used. If either one was higher, there was no issue and the region was created. Some of you might spot the problem here I imagine, but to cut a long story short there's certain situations where this system would judge an empty space to be unusable, so masses of random empty blocks appeared when I ran the script.
So, I'm stuck. I can't help but feel that there's a good mathematical solution to this that uses a fixed number of variables, but I can't see it. Failing that, does anyone know of another way to achieve what I'm looking for?
Have a read through this, maybe it will help. I haven't tested it, but I have commented my intentions so you should be able to come up with something based on it.
//create some globals we will need
$region_id = 1;
$current_x = 0;
$current_y = 0;
$squares = array();
//Create the base grid
$grid = array(GRID_WIDTH);
foreach($grid as $a)
{
$a = array(GRID_HEIGHT);
$a = array_fill(0, count($a), 0);
}
//$grid[0][0] will be the top left square
//Iterates over the grid until it is full
while($current_x != (-1) && $current_y != (-1))
{
//We just need to set these counters back to their minimums
$max_x = $current_x;
$max_y = $current_y;
//Lets see how many spaces we have between current x and end of the row
//$max_x will be the maxiumum newX2
for($x = $current_x; $x<GRID_WIDTH; $x++)
{
if($grid[$x][$current_y]==0)
{
$max_x = $x;
}
}
//Lets create a width between 1 and the max left in the row
$newX1 = $current_x;
$newX2 = rand($current_x, $max_x)
//Now lets see how tall we can make it by going down the y until we hit a square or the end.. do for each x
for($x = $newX1; $x<newX2;$x++)
{
for($y = $current_y; $y<GRID_HEIGHT; $y++)
{
if($grid[$x][$y]==0)
{
//We just need the maxiumum y we can have based on our already determined width
//Since this distance has to be smallest possible distance
//We only let max_y get larger on the first iteration of x
if($y>$max_y && $x==$newX1)
{
$max_y = $y;
}
}
}
}
//lets create a height bettwen 1 and the max
$newY1 = $current_y;
$newY2 = rand($current_y, $max_y);
//create a spacewith these values
occupySpace($newX1, $newX2, $newY1, $newY2, $grid, $region_id);
$squares[] = array($newX1, $newX2, $newY1, $newY2);
//Iterate over the grid looking for an empty space
//First lets set current_x and current_y to -1. We will use this to evalute done
$current_x = -1;
$current_y = -1;
for($x = 0; $x<GRID_WIDTH; $x++)
{
for($y = 0; $y<GRID_HEIGHT; $y++)
{
//Looking for any space that hasn't been occupied, if one is found break both for loops
if($grid[$x][$y]==0)
{
$current_x = $x;
$current_y = $y;
break 2;
}
}
}
}
//fills all spaces in the region with the current region id and increments region id
function occupySpace($x1, $x2, $y1, $y2, &$grid, &$region_id)
{
for($x=$x1; $x<$x2; $x++)
{
for($y=$y1; $y<$y2; $y++)
{
$grid[$x][$y] = $region_id;
}
}
$region_id += 1;
}
?>
Update: This solution didn't originally keep track the 'regions' that were created. Now they exist in array $squares.
PS - If this is going to be a big part of your script/business I would consider writing some classes for Grid and region,
there is no need to save every bit of information into the db. For every rectangle you only need top/left-coordinates + width/height.
then you should be able get the maximum available space for a given coordinate with with one query (e.g. getting minimum-x which is bigger than your-x and also lies within the needed y-space)
Update:
For max-width you can use:
SELECT `x` - :x AS `maxWidth`
FROM `test`
WHERE `x` > :x
AND (`y` > :y OR `y` + `h` > :y)
ORDER BY `y`, `y` + `h`
LIMIT 1
and max-height:
SELECT `y` - :y AS `maxHeight`
FROM `test`
WHERE `y` > :y
AND (`x` > :x OR `x` + `w` > :x)
ORDER BY `x`, `x` + `w`
LIMIT 1
I have a table with 150x150 cells which each have a colored background and a small image/symbol in them. Additionally each cell can have zero or more of it's borders set also. The user can change and add borders and change the cell color and cell. This all works pretty well using jquery and it is just a plain html table.
At the end of the user experience I am making a pdf of this table for them to download. This is a big job and takes a while, which is not my main concern now. jQuery gathers the table data in an array and sends it to php to recreate it as an image using the gd library.
So for each cell I was drawing a rectangle of the correct color on a large image, loading the symbol image and resampling it on to the large image, drawing the borders and imagedestroy the symbol image, it worked but took 1 minute. I changed my strategy to make a small colored rectangke impose the image on it and cache it in an array to be quickly used again. That sped my time up and brought it down to 30ish seconds, but now I am exhausting memory.
I am breaking the table down into 50 cell blocks so each fits on a page, each block is made into an image and saved to disk, the next is made and saved, etc. Each time the gd image is destroyed. Then all the blocks are inserted into the pdf.
So after all that, my question is how do I figure out where the memory is being used so I can try to free it up? I have posted the main function I think is causing the issue below. In my test there are up to 30 different symbols/colors images that are 25pxX25px, these are the images that are cached in an array.
I hope I have provided enough info and my problem is clear enough.
Thank you for your time,
Todd
//make one "block" of stitches returns image file name.
//function makeStitchChartBlock($img, $startX, $startY, $endX, $endY, $caption, $brand){
function makeStitchChartBlock($stitchChartArray, $startX, $startY, $endX, $endY, $caption, $brand,$blockNumber){
global $threadColours;
$stitchCache=array();
$saveTo = 'result'.$blockNumber.'.jpeg';
// calculate size of block
$numRows=($endY-$startY);
$numColumns=($endX-$startX);
$heightOfBlock = $numRows*SYMBOL_SIZE; //in pixels --- used to determine size of image to make for block
$widthOfBlock = $numColumns*SYMBOL_SIZE; //in pixels
//----plus any extra for captions grid lines
$heightOfBlock += (($endY-$startY)+1); //each stitch has a grid line before it and the last one also has on after it
$widthOfBlock += (($endX-$startX)+1);
// create image size of block to put stitches in
$newBlockImage = imagecreatetruecolor($widthOfBlock,$heightOfBlock);
$backStitchColor = imagecolorallocate($newBlockImage, 0, 0, 0);
// insert caption????
//draw grid lines
//$newBlockImage = addGridToImage($newBlockImage);
//keep track of where to start next cell top left
$blockX=0;
$blockY=0;
for($y = $startY; $y < $endY; $y++){ //for each pixel in height, move down 1 "row" each iteration
//echo "<tr>";
for($x = $startX; $x < $endX; $x++){ // "draws" a row (for each y pixel)
//rgb(75, 90, 60)
//x and y are column and row #'s
list($r, $g, $b) = getRGBs($stitchChartArray[$y][$x][0]); //get the rgb values for the cell
$stitchColor = imagecolorallocate($newBlockImage, $r, $g, $b);
//calculate x & y start positons
$stitchStartX=($blockX*SYMBOL_SIZE)+$blockX+1; //account for each previous stitch and the grid line, then add one for new grid line
$stitchStartY=($blockY*SYMBOL_SIZE)+$blockY+1;
$stitchEndX=$stitchStartX+(SYMBOL_SIZE);
$stitchEndY=$stitchStartY+(SYMBOL_SIZE);
/* make a symbol cell image with/without color and save it in the cache */
if(!isset($stitchCache[$r][$g][$b]))
{
//create new image
$stitchCache[$r][$g][$b] = imagecreatetruecolor(SYMBOL_SIZE,SYMBOL_SIZE);
$stitchCacheColor = imagecolorallocate($stitchCache[$r][$g][$b], $r, $g, $b);
//draw colored rectangle
imagefilledrectangle($stitchCache[$r][$g][$b], 0, 0, SYMBOL_SIZE-1, SYMBOL_SIZE-1, $stitchCacheColor);
//add the symbol
$symbolFile=$stitchChartArray[$y][$x][1];
if($symbolFile){
$symbolImage = imagecreatefrompng($symbolFile);
imagecopyresampled ($stitchCache[$r][$g][$b],$symbolImage,0,0,0,0,SYMBOL_SIZE-1,SYMBOL_SIZE-1,imagesx($symbolImage), imagesy($symbolImage) );
imagedestroy($symbolImage);
}
}
//add image from cache to the block image
imagecopyresampled ($newBlockImage,$stitchCache[$r][$g][$b],$stitchStartX, $stitchStartY,0,0,SYMBOL_SIZE,SYMBOL_SIZE,SYMBOL_SIZE,SYMBOL_SIZE);
//add the backstitch lines(borders)
if($stitchChartArray[$y][$x][2]>1) //top
{
imagefilledrectangle($newBlockImage, $stitchStartX, $stitchStartY, $stitchEndX, $stitchStartY+1, $backStitchColor);
}
if($stitchChartArray[$y][$x][3]>1) //right
{
imagefilledrectangle($newBlockImage, $stitchEndX-1, $stitchStartY, $stitchEndX, $stitchEndY, $backStitchColor);
}
if($stitchChartArray[$y][$x][4]>1) //bottom
{
imagefilledrectangle($newBlockImage, $stitchStartX, $stitchEndY-1, $stitchEndX, $stitchEndY, $backStitchColor);
}
if($stitchChartArray[$y][$x][5]>1) //left
{
imagefilledrectangle($newBlockImage, $stitchStartX, $stitchStartY, $stitchStartX+1, $stitchEndY, $backStitchColor);
}
//advance x position
$blockX++;
}
//advance y position
//reset x
$blockX=0;
$blockY++;
}
imagejpeg($newBlockImage, $saveTo);
imagedestroy($newBlockImage);
//dump stitch cache
foreach($stitchCache as $r)
{
foreach($r as $g)
{
foreach($g as $b=>$data)
{
imagedestroy($data);
}
}
}
return $saveTo;
}
I would start (if you haven't already) by getting a good IDE and a debugger as these will be invaluable tools. In this instance you may be able to use a profiler to work out where the memory is being used. Failing that some good ole' manual debugging code, say
$memory = memory_get_usage();
as the first line inside your inner loop. Then when you step through using the debugger you'll be able to see where the memory is ramping up.
btw, using global variables is generally not a good idea. You might want to pass in $threadColours as a parameter or look at other ways of getting that data into the function.