I am writing in PHP.
What I am trying to do is something like a configurable product in Magento2 or variable product in Woocommerce. I am asking the user to enter attributes to the product, like color, size, etc'.
Think of each attribute as a attribute category which contains attributes inside it, e.g 'color' will have attributes like 'red', 'green', 'blue' and so on. Size will have attributes like 'large', 'small', 'medium' and so on.
Now, I need to make a loop which takes all sizes, colors and other chosen attributes and returns all the possible configurations. At the same time the amount of attributes to loop through isn't predefined since the user can add or remove attributes.
For example if I have this:
Color - - - - - - - - Size - - - - - - - - Shape
Red - - - - - - - - - Large - - - - - - - Square
Green - - - - - - - - Medium - - - - - - - Rounded
Blue - - - - - - - - Small - - - - - - - -
So I will have to have every color with every size and shape:
Red - Large - Square
Red - Large - Rounded
Red - Medium - Square
Red - Medium - Rounded
Red - Small - Square
Red - Small - Rounded
And the same with the other attributes.
What is the best practice to achive it?
You will need recursion for this.
function getCombinations($attributes){
$combinations = [];
buildCombinationChain($attributes,0,[],$combinations);
// encode combinations to desired string format
$result = [];
foreach ($combinations as $combination) {
$result[] = implode(' - ', $combination);
}
return $result;
}
function buildCombinationChain($attributes,$index,$chain,&$output){
if($index>=count($attributes)){
// we have reached the last attribute, stop recursion and push the current chain to the output array
$output[] = $chain;
return;
}
foreach ($attributes[$index] as $attribute) {
$new_chain = $chain; // create a copy of the current chain
$new_chain[] = $attribute; // and add the current attribute to it
buildCombinationChain($attributes,$index+1,$new_chain,$output); // continue recursively on the next attribute group
}
}
$attributes = [
['Red', 'Green', 'Blue'],
['Large', 'Medium', 'Small'],
['Square', 'Rounded']
];
echo json_encode(getCombinations($attributes));
Related
I need to get all the nodes at a certain level in a full binary tree from either the left or right subtree. I currently retrieve the binary tree from the DB as an array, for example:
[1,2,3,4,5,6,7] represents a tree like this:
1
/ \
/ \
2 3
/ \ / \
/ \ / \
4 5 6 7
So what I need to do is basically grab a level of the tree and return it as an array. Something like level(3,"left") -> [4,5] or level(2, "right") -> [3]. I was thinking about creating a BinaryTree object doing it recursively, but I can't think of a way to keep track of the level within the call without having to tag every node with a level or something like that as I'd like to keep the DB as clean as possible. Any ideas?
Edit: I really need all the nodes at a level for the left or right subtree, not the whole tree. I'm displaying a tournament, so I need to split it half and half. If I didn't have to split it, I could probably do this:
function level($array, $level_num) {
return array_slice($array, pow(2, $level_num)-1, pow(2, $level_num));
}
I don't really know how to extend this for getting only the left or right subtree's level array.
// Adjust depth as needed
$depth = 3;
// using bit arithmetic. '<< 3' means multiply by 2 three times.
$start = 1 << $depth-1; // 1 * (2 * 2) because depth is 3
$end = (1 << $depth) -1; // 1 * (2 * 2 * 2) - 1
// if depth=3, this is [4,5,6,7]
$fullLevel = range($start, $end);
print_r($fullLevel);
if($depth > 1):
$leftBranch = array_slice($fullLevel,0,count($fullLevel)/2);
$rightBranch = array_slice($fullLevel,count($fullLevel) / 2);
print_r($leftBranch); // [4,5]
print_r($rightBranch); // [6, 7]
endif;
I upvoted BeetleJuice's answer for using the "Shift Left" bitwise operator << -- it is the perfect building block for this task.
This is as clean as I can make my coding attempt:
Code: (Demo)
function getForkinValues($array,$level,$side='left'){ // left side fork is default
if($level==1) return current($array);
$length=$level>2?1<<($level-2):1; // number of elements to return
$index=(1<<$level-1)-1; // first index to return
if($side=='right') $index+=$length; // shift to correct index for 'right'
if(!isset($array[$index+$length-1])) return 'Error: Insufficient Array Length';
return array_slice($array,$index,$length);
}
$array=['A','B','C','D','E','F','G'];
var_export(getForkinValues($array,3,'right'));
var minCount = 10; // Used as 0, anything below 10 uses will be ignored
User hasMany Attributes
Attribute (id, value, count, worth)
// Count - number of users who associate with this attribute
// Worth - float 0.1 (or a better non-zero number?) to 1
An attribute with count of minCount has worth 1.
The attribute(s) with the highest count has the lowest possible worth.
The lesser used attributes are worth more - lower count -> higher worth.
The difference in worth should be exponential:
Commonly used attributes (high count) - smaller gap between worth
Uncommon attributes (low count) - larger gap between worth
I'll be using php for now, but psuedo code is equally acceptable - I'd rather just understand how to do it.
So what's the problem?
Use the following simple formula:
currentWorth = exp(minCount / currentCount - 1)
where:
exp - exponent of e;
currentCount - current attribute's count;
minCount - least attribute's count.
Brief explanation:
First consider formula without exponent application:
currentWorth = minCount / currentCount
For attribute having minimal count (currentCount = minCount):
currentWorth = minCount / minCount = 1
For attributes having any other count (obviously greater than minCount):
(currentWorth = minCount / currentCount) < 1
Now let's apply exponential law:
currentWorth = exp(minCount / currentCount - 1)
For attribute having minimal count (currentCount = minCount):
currentWorth = exp(minCount / minCount - 1) = exp(1 - 1) = exp(0) = 1
For attributes having any other count (currentCount > minCount):
currentWorth = exp(minCount / currentCount - 1)
Assuming:
t = 1 - minCount / currentCount > 0
We'll have:
(currentWorth = exp(-t) = 1 / exp(t)) < 1
I need to convert the "age" of an item (in days), into an hexadecimal value, where oldest item = max color = D9D8C9, and most recent = min color = FFFFFF.
items beyond age 365 get color D9D8C9.
items beneath age 7 get color FFFFFF.
According to these min and max colors, how can i find the color of any item younger than 365 days and older than 7 days?
Eventually, I'll do it in PHP but pseudocode example is fine.
Essentially, you're looking for a way to arbitrarily map one range onto another (7-365 should be mapped of FFFFFF - D9D8D9).
First things first: converting decimal to hex is quite easy:
$age = mt_rand(1,600);
if ($age > 365) $hex = 'D9D8D9';
elseif ($age < 7) $hex = 'FFFFFF';
else $hex = str_pad(dechex($age), 6, '0', STR_PAD_LEFT);
What I do is simply check if $age is greater then 365, if so, I assign the D9D8D9 constant, if it's less than 7, I assign FFFFFF. In all other cases, I simply convert the number to hexadecimal, and pad the resulting string to 6 chars, using 0, so 255 would become FF, and is padded to 0000FF.
But to map a range on a range, we'll have to find out how a step of 1 in the smallest range scales to the larger one(s). It's a simple "rule of three": calculate the equivalent of 1 in both scales, and multiply. Then apply the same range-bounds and you're there.
The colour-range you're using is FFFFFF through D9D8D9, or to put it in decimals: 16777215 through 14276809. This leaves us with a range of 2500406, versus 365-7 (or 358) days. Each single day, therefore is "worth" 6984.374301676 ((D9D8D9-FFFFFF)/(365-7)) in our colour range.
Put it all together and you have 2 options: calculate the distance from FFFFFF or D9D8D9, but as far as the result is concerned, it doesn't matter which one you choose.
<CrLowBound> - (<value>-<VLowBound>)*<CrStep>
//or
<CrHighBound> - (<VHighBound> - <value>)*<CrStep>
Both simply compute the value in the colour range that corresponds to the given value. CrLowBound and CrHighBound are FFF and D9D8D9 respectively, in much the same way: VLowBound and VHighBound are 6 and 366. CrStep is 6984.374301676. I've explained how I got these values.
$age = mt_rand(1,600);
if ($age > 365) $hex = 'D9D8D9';
elseif ($age < 7) $hex = 'FFFFFF';
else $hex = str_pad(dechex(14276809-(round((366-$dec)*6984.374301676))), 6, '0', STR_PAD_LEFT);
//Or:
$hex = str_pad(dechex(16777215-(round(($dec-6)*6984.374301676))), 6, '0', STR_PAD_LEFT);
This will more evenly spread the range of colours within the D9D8D9 to FFFFFF range: the older the item, the closer the color will be to D9D8D9, the more recent, the closer it is to FFFFFF.
For example, if the age is 117, the "colour" will be 1A6E1D:
//age (max-age)*worth hex
117 == 1732125 == 1a6e1d
Tested with the following code:
function getColour($dec)
{
if ($dec > 365) return 'D9D8D9';
if ($dec < 7) return 'FFFFFF';
return strtoupper(
str_pad(
dechex(14276809-(round((366-$dec)*6984.374301676))),
6,
'0',
STR_PAD_LEFT
)
);
}
$days = range(6,366);
$colours = array();
foreach($days as $day) $colours[$day] = getColour($day);
$out = array_chunk($colours, 8);
foreach($out as $k => $chunk) $out[$k] = implode(' - ', $chunk);
echo implode('<br>', $out);
And got this as output:
FFFFFF - B3964B - B3B193 - B3CCDB - B3E824 - B4036C - B41EB4 - B439FD
B45545 - B4708E - B48BD6 - B4A71E - B4C267 - B4DDAF - B4F8F7 - B51440
B52F88 - B54AD1 - B56619 - B58161 - B59CAA - B5B7F2 - B5D33A - B5EE83
B609CB - B62514 - B6405C - B65BA4 - B676ED - B69235 - B6AD7D - B6C8C6
B6E40E - B6FF57 - B71A9F - B735E7 - B75130 - B76C78 - B787C0 - B7A309
B7BE51 - B7D99A - B7F4E2 - B8102A - B82B73 - B846BB - B86203 - B87D4C
B89894 - B8B3DD - B8CF25 - B8EA6D - B905B6 - B920FE - B93C46 - B9578F
B972D7 - B98E20 - B9A968 - B9C4B0 - B9DFF9 - B9FB41 - BA1689 - BA31D2
BA4D1A - BA6863 - BA83AB - BA9EF3 - BABA3C - BAD584 - BAF0CC - BB0C15
BB275D - BB42A6 - BB5DEE - BB7936 - BB947F - BBAFC7 - BBCB0F - BBE658
BC01A0 - BC1CE9 - BC3831 - BC5379 - BC6EC2 - BC8A0A - BCA552 - BCC09B
BCDBE3 - BCF72C - BD1274 - BD2DBC - BD4905 - BD644D - BD7F95 - BD9ADE
BDB626 - BDD16F - BDECB7 - BE07FF - BE2348 - BE3E90 - BE59D8 - BE7521
BE9069 - BEABB2 - BEC6FA - BEE242 - BEFD8B - BF18D3 - BF341B - BF4F64
BF6AAC - BF85F5 - BFA13D - BFBC85 - BFD7CE - BFF316 - C00E5E - C029A7
C044EF - C06038 - C07B80 - C096C8 - C0B211 - C0CD59 - C0E8A1 - C103EA
C11F32 - C13A7B - C155C3 - C1710B - C18C54 - C1A79C - C1C2E4 - C1DE2D
C1F975 - C214BE - C23006 - C24B4E - C26697 - C281DF - C29D27 - C2B870
C2D3B8 - C2EF01 - C30A49 - C32591 - C340DA - C35C22 - C3776A - C392B3
C3ADFB - C3C944 - C3E48C - C3FFD4 - C41B1D - C43665 - C451AD - C46CF6
C4883E - C4A387 - C4BECF - C4DA17 - C4F560 - C510A8 - C52BF0 - C54739
C56281 - C57DCA - C59912 - C5B45A - C5CFA3 - C5EAEB - C60633 - C6217C
C63CC4 - C6580D - C67355 - C68E9D - C6A9E6 - C6C52E - C6E076 - C6FBBF
C71707 - C7324F - C74D98 - C768E0 - C78429 - C79F71 - C7BAB9 - C7D602
C7F14A - C80C92 - C827DB - C84323 - C85E6C - C879B4 - C894FC - C8B045
C8CB8D - C8E6D5 - C9021E - C91D66 - C938AF - C953F7 - C96F3F - C98A88
C9A5D0 - C9C118 - C9DC61 - C9F7A9 - CA12F2 - CA2E3A - CA4982 - CA64CB
CA8013 - CA9B5B - CAB6A4 - CAD1EC - CAED35 - CB087D - CB23C5 - CB3F0E
CB5A56 - CB759E - CB90E7 - CBAC2F - CBC778 - CBE2C0 - CBFE08 - CC1951
CC3499 - CC4FE1 - CC6B2A - CC8672 - CCA1BB - CCBD03 - CCD84B - CCF394
CD0EDC - CD2A24 - CD456D - CD60B5 - CD7BFE - CD9746 - CDB28E - CDCDD7
CDE91F - CE0467 - CE1FB0 - CE3AF8 - CE5641 - CE7189 - CE8CD1 - CEA81A
CEC362 - CEDEAA - CEF9F3 - CF153B - CF3084 - CF4BCC - CF6714 - CF825D
CF9DA5 - CFB8ED - CFD436 - CFEF7E - D00AC7 - D0260F - D04157 - D05CA0
D077E8 - D09330 - D0AE79 - D0C9C1 - D0E50A - D10052 - D11B9A - D136E3
D1522B - D16D73 - D188BC - D1A404 - D1BF4D - D1DA95 - D1F5DD - D21126
D22C6E - D247B6 - D262FF - D27E47 - D29990 - D2B4D8 - D2D020 - D2EB69
D306B1 - D321F9 - D33D42 - D3588A - D373D3 - D38F1B - D3AA63 - D3C5AC
D3E0F4 - D3FC3C - D41785 - D432CD - D44E16 - D4695E - D484A6 - D49FEF
D4BB37 - D4D67F - D4F1C8 - D50D10 - D52859 - D543A1 - D55EE9 - D57A32
D5957A - D5B0C2 - D5CC0B - D5E753 - D6029C - D61DE4 - D6392C - D65475
D66FBD - D68B05 - D6A64E - D6C196 - D6DCDF - D6F827 - D7136F - D72EB8
D74A00 - D76548 - D78091 - D79BD9 - D7B722 - D7D26A - D7EDB2 - D808FB
D82443 - D83F8B - D85AD4 - D8761C - D89165 - D8ACAD - D8C7F5 - D8E33E
D8FE86 - D919CE - D93517 - D9505F - D96BA8 - D986F0 - D9A238 - D9BD81
D9D8D9
Codepad with 2 versions of this code
I am rendering a waveform in PHP by downsampling it with the lame encoder and then drawing the waveform from the resulting data points. I am currently getting images like this:
What I would like to do is modify my code so that the apparent dynamic range of the waveform is essentially 'compressed'. To produce a waveform that looks more like this:
The equation I am currently using to render the height of each data point is as follows:-
// draw this data point
// relative value based on height of image being generated
// data values can range between 0 and 255
$v = (int) ( $data / 255 * $height );
// don't print flat values on the canvas if not necessary
if (!($v / $height == 0.5 && !$draw_flat))
// draw the line on the image using the $v value and centering it vertically on the canvas
imageline(
$img,
// x1
(int) ($data_point / DETAIL),
// y1: height of the image minus $v as a percentage of the height for the wave amplitude
$height * $wav - $v,
// x2
(int) ($data_point / DETAIL),
// y2: same as y1, but from the bottom of the image
$height * $wav - ($height - $v),
imagecolorallocate($img, $r, $g, $b)
);
With the actual amplitude being defined by the first line of this code:-
$v = (int) ( $data / 255 * $height );
Unfortunately my math skill is poor at best. What I need to do is essentially apply a 'curve' to the value of $v so that when the number input into the equation is lower, the resulting output is higher and as the input number is increased the equation reduces the amplification until finally when the input reaches 255 the output should also be 255. Also the curve should be such so that with an input of 0 the output is also 0.
I apologise if this is not clear but I am finding this question very hard to articulate with my limited math experience.
Perhaps a visual representation would help describe my intent:-
When the value of $v is either 0 or 255 the output of the equation should be exactly the input (0 or 255). However, when the input is a value inbetween, it should follow the resulting output of the curve above. (the above was only a rough drawing to illustrate.)
EDIT:
Based on Alnitiks 'pow' function solution I am now generating waveforms that look like this:-
Using the replacement equation for the $v variable as follows:-
$v = pow($data / 255.0, 0.4) * $height;
I have tried upping the 0.4 value but the result is still not as intended.
EDIT 2:
As requested here is a raw datadump of my $data variable:
Raw Data
This gets passed into the equation to return $v before being used to draw the waveform (you can see what I do to variable $v in the original code I posted above. $height is simple the number of pixels high I have set the image to render.
This data is a comma seperated list of values. I hope this helps. It appears your assertion that the mean value is 128 is correct. So far I have been unable to get my head around your correction for this. I'm afraid it is slightly beyond my current understanding.
With no math skills (and probably useful to have a speedy display):
You have 256 possible values. Create an array that contains the "dynamic" value for each of these values:
$dynamic = array(
0 => 0,
1 => 2,
...
);
That done, you can easily get the dynamic value:
$v = (int) ($dynamic[(int) $data / 255] * $height);
You might lose some precision, but it's probably useful.
Natural dynamic values are generated by the math sine and cosine functions, in PHP this sinDocs (and others linked there).
You can use a loop and that function to prefill the array as well and re-use the array so you have pre-computed values:
$sine = function($v)
{
return sin($v * 0.5 * M_PI);
};
$dynamic = array();
$base = 255;
for ($i = 0; $i <= $base; $i++)
{
$dynamic[$i] = $i/$base;
}
$dynamic = array_map($sine, $dynamic);
I use a variable function here, so you can write multiple and can easily test which one matches your needs.
You need something similar to gamma correction.
For input values x in the range 0.0 -> 1.0, take y = pow(x, n) when n should be in the range 0.2 - 0.7 (ish). Just pick a number that gives the desired curve.
As your values are in the range 0 -> 255 you will need to divide by 255.0, apply the pow function, and then multiply by 255 again, e.g.
$y = 255 * pow($x / 255.0, 0.4);
The pow formula satisfies the criteria that 0 and 1 map to themselves, and smaller values are "amplified" more than larger values.
Here's a graph showing gamma curves for n = 1 / 1.6, 1 / 2, 1 / 2.4 and 1 / 2.8, vs the sin curve (in red):
The lower the value of n, the more "compression" is applied to the low end, so the light blue line is the one with n = 1 / 2.8.
Note how the sin curve is almost linear in the range 0 to 0.5, so provides almost no low end compression at all.
If as I suspect your values are actually centered around 128, then you need to modify the formula somewhat:
$v = ($x - 128.0) / 128.0;
$y = 128 + 127 * sign($v) * pow(abs($v), 0.4);
although I see that the PHP developers have not included a sign function in the PHP library.
Simple downsampling is not going to give you a correct render, as it will leave the original signal with only low frequencies, whilst all frequencies contribute to amplitudes. So you need to build the peak data (min and max for a certain range of values) from the original waveform to visualize it. You shouldn't apply any non-linear functions to your data, as the waveform representation is linear (unlike gamma-compressed images).
I try to create a PDF with multiple pages and need to calculate the height of each individual element (MultiCell) in advance to prepare for a page break. According to the documentation there are a couple of functions out there like GetCharWidth/GetStringWidth to support me in doing it on my own, but besides a potential performance lost I probably will not do it the right anyway. Suggestions to achieve my goal in a more elegant way?
Reference: TCPDF
I GOT it :D!!!!!
Create another pdf2 object
// pdf2 set x margin to pdf1's xmargin, but y margin to zero
// to make sure that pdf2 has identical settings, you can clone the object (after initializing the main pdf object)
$pdf2 = clone $pdf;
pdf2->addpage
pdf2->writeCell
$height = pdf2->getY()
pdf2->deletePage(pdf2->getPage())
pdf1->checkPageBreak($height);
pdf1->writeCell()
W00tness :D
This is an old question, but the current version (as of 7 Dec 2011) of TCPDF has a function called getStringHeight that allows you to calculate the resulting height of a string passed to MultiCell prior to actually calling MultiCell. Then this height can be used for various things, the calculation in the original question, and also for setting row height when making tables etc. Works great.
Just some info in case someone else stumbles across this question looking for a solution to this problem as I did.
While Carvell's answer is great, TCPDF mentions that getStringHeight returns the estimated height. Helpfully the documentation there provides a pretty comprehensive technique for getting the exact height which comes out as $height. As to why they don't use this themselves is a mystery...
// store current object
$pdf->startTransaction();
// store starting values
$start_y = $pdf->GetY();
$start_page = $pdf->getPage();
// call your printing functions with your parameters
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
$pdf->MultiCell($w=0, $h=0, $txt, $border=1, $align='L', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// get the new Y
$end_y = $pdf->GetY();
$end_page = $pdf->getPage();
// calculate height
$height = 0;
if ($end_page == $start_page) {
$height = $end_y - $start_y;
} else {
for ($page=$start_page; $page <= $end_page; ++$page) {
$pdf->setPage($page);
if ($page == $start_page) {
// first page
$height = $pdf->h - $start_y - $pdf->bMargin;
} elseif ($page == $end_page) {
// last page
$height = $end_y - $pdf->tMargin;
} else {
$height = $pdf->h - $pdf->tMargin - $pdf->bMargin;
}
}
}
// restore previous object
$pdf = $pdf->rollbackTransaction();
From my experience, it is nearly impossible to figure out the cell height in advance. It is much easier to use the page break handling functions of TCPDF that sort of tells you in advance if you're heading into a pagebreak. Here is example code:
$yy = $this->pdf->GetY();
$check_pagebreak = $this->pdf->checkPageBreak($height+$padding,$yy,false);
Change false to true to allow for an automatic page break, otherwise, you can handle the logic of the pagebreak, yourself, which is what I ended up doing.
Also, in case you may need this, here's another little tip: Consider using the transaction features to create your document in two passes. The first pass is used to figure out all the heights and cells, pagebreaks, etc. You can also store all your lineheights and lines per page in arrays. ON the second pass, create your document with all the correct information and no need for pagebreak logic (the second pass could be run from a seperate method, for the sake of keeping the code easier to read, and for your sanity).
Use TCPDF Example 20
Calculating MultiCell heights can be a nightmare if the cells/columns end on different pages.
Using transactions or additional pdf objects can make things very slow.
Using functions such as getNumLines() and getStringHeight() to calculate the 'estimated' (see docs) height before the cells are printed do not always work correctly. Especially if the text ends just before or just after the right border of the cell - resulting in rows being printed on top of each other.
I prefer the technique used in Example 20 where the maximum Y value of the different pages are used to calculate the position of the new row.
The example prints only two columns, but I changed its main function to be able to print an array of columns. Obviously you could add more data to the array, such as each column's font, borders, etc.
public function MultiRow($columnsArray) {
$page_start = $this->getPage();
$y_start = $this->GetY();
$pageArray = array();
$yArray = array();
// traverse through array and print one column at a time.
$columnCount = count($columnsArray);
for($i=0; $i<$columnCount; $i++)
{
if($i+1 < $columnCount)
{
// Current column is not the last column in the row.
// After printing, the pointer will be moved down to
// the right-bottom of the column - from where the
// next multiCell in the following loop will use it
// via $this->GetX().
$ln = 2;
}
else
{
// Current column is the last column in the row.
// After printing, the pointer will be moved to new line.
$ln = 1;
}
$this->MultiCell(30, 0, $columnsArray[$i], 1, 'L', 1, $ln,
$this->GetX() ,$y_start, true, 0);
$pageArray[$i] = $this->getPage();
$yArray[$i] = $this->GetY();
// Go to page where the row started - to print the
// next column (if any).
$this->setPage($page_start);
}
// Test if all columns ended on the same page
$samePage = true;
foreach ($pageArray as $val) {
if($val != $pageArray['0'])
{
$samePage = false;
break;
}
}
// Set the new page and row position by case
if($samePage == true)
{
// All columns ended on the same page.
// Get the longest column.
$newY = max($yArray);
}
else
{
// Some columns ended on different pages.
// Get the array-keys (not the values) of all columns that
// ended on the last page.
$endPageKeys = array_keys($pageArray, max($pageArray));
// Get the Y values of all columns that ended on the last page,
// i.e. get the Y values of all columns with keys in $endPageKeys.
$yValues = array();
foreach($endPageKeys as $key)
{
$yValues[] = $yArray[$key];
}
// Get the largest Y value of all columns that ended on
// the last page.
$newY = max($yValues);
}
// Go to the last page and start at its largets Y value
$this->setPage(max($pageArray));
$this->SetXY($this->GetX(),$newY);
}
The post Revisited: Tcpdf – Variable Height Table Rows With MultiCell has a lot of useful information. This is a short extract:
getNumLines() ... actually allows us to determine how many lines a string of text will take up, given a particular width. In effect, it allows us to do what I was using MultiCell to return, without actually drawing anything. This lets us to determined the maximum cell height with one line of code:
$linecount = max($pdf->getNumLines($row['cell1data'], 80),$pdf->getNumLines($row['cell2data'], 80
I tried using Jay's answer and it worked for the intended purpose but for some reason caused my logo to not appear after the first page. I didn't want to do an in depth analysis, but had something to do with the cloning. I then tried the same approach, but using transactions. This produced hundreds of errors.
Then I came up with this rather simple solution using the same object.
/**
* Gets an accurate measurement of a cell's rendered height.
*
* #param float $width the width of the column to be rendered
* #param string $contents the contents to be rendered
*
* #return float
*/
private function getCellHeight(float $width, string $contents): float
{
$view = $this->view;
$currentPage = $view->getPage();
$currentX = $view->GetX();
$currentY = $view->GetY();
$view->AddPage();
$x = $view->GetX();
$start = $view->GetY();
$view->writeHTMLCell($width, 15, $x, $start, $contents, self::INSTANCE_BORDER, 1);
$height = $view->GetY() - $start;
$view->deletePage($view->getPage());
$view->setPage($currentPage);
$view->changePosition($currentX, $currentY);
return $height;
}
As the writeHTMLCell function requires a $h, I use 15, but it can be anything you want, as can the $border value.
The $ln value needs to be set to 1, otherwise the y value resets before the GetY() can get it.
changePosition is my own wrapper for SetXY().