From 498e1473aa1f113dda480a65e327bbc8d09ca424 Mon Sep 17 00:00:00 2001 From: John BOTELLA <68917336+thersane-john@users.noreply.github.com> Date: Tue, 8 Oct 2024 22:44:36 +0200 Subject: [PATCH] Rebuild Cyan PDF MODEL --- .../core/class/commondocgenerator.class.php | 64 +++++ .../modules/propale/doc/pdf_cyan.modules.php | 241 ++++++++---------- 2 files changed, 168 insertions(+), 137 deletions(-) diff --git a/htdocs/core/class/commondocgenerator.class.php b/htdocs/core/class/commondocgenerator.class.php index 43460e9e888..4d55e61a17b 100644 --- a/htdocs/core/class/commondocgenerator.class.php +++ b/htdocs/core/class/commondocgenerator.class.php @@ -158,6 +158,11 @@ abstract class CommonDocGenerator */ public $cols; + /** + * @var array Array of position data + */ + public $afterColsLinePositions; + /** * @var array{fullpath:string} Array with result of doc generation. content is array('fullpath'=>$file) */ @@ -1079,6 +1084,64 @@ abstract class CommonDocGenerator } + /** + * Get position in PDF after col display + * @return false|arrayafterColsLinePositions) || !is_array($this->afterColsLinePositions)) { + return false; + } + + $colId = ''; + $maxPage = $maxY = 0; + foreach ($this->afterColsLinePositions as $colKey => $value) { + if ($value['page'] > $maxPage) { + $colId = $colKey; + $maxPage = $value['page']; + $maxY = $value['y']; // if page is higher we need to reset y to new max page y + } elseif ($value['page'] == $maxPage) { + $maxY = max($value['y'], $maxY); + $colId = $colKey; + } + } + + return [ + 'col' => $colId, + 'y' => $maxY, + 'page' => $maxPage + ]; + } + + /** + * Used for reset afterColsLinePositions var in start of a new pdf draw line loop + * @param float $y the new $y position usually get by TCPDF::GetY() + * @param int $pageNumb the page number to reset at + * @return null + */ + public function resetAfterColsLinePositionsData(float $y, int $pageNumb) + { + $this->afterColsLinePositions = []; + return $this->setAfterColsLinePositionsData('startLine', $y, $pageNumb); + } + + /** + * Used for to set afterColsLinePositions var in a pdf draw line loop + * @param string $colId the column id used as key in $this->cols or an unique id code like startLine or separateLine .... + * @param float $y the $y position usually get by TCPDF::GetY() where print data ended + * @param int $pageNumb the page number where print data ended + * @return void + */ + public function setAfterColsLinePositionsData(string $colId, float $y, int $pageNumb) + { + $this->afterColsLinePositions[$colId] = [ + 'page' => $pageNumb, + 'y' => $y + ]; + } + + /** * uasort callback function to Sort columns fields * @@ -1292,6 +1355,7 @@ abstract class CommonDocGenerator // set cell padding with column content definition $pdf->setCellPaddings(isset($colDef['content']['padding'][3]) ? $colDef['content']['padding'][3] : 0, isset($colDef['content']['padding'][0]) ? $colDef['content']['padding'][0] : 0, isset($colDef['content']['padding'][1]) ? $colDef['content']['padding'][1] : 0, isset($colDef['content']['padding'][2]) ? $colDef['content']['padding'][2] : 0); $pdf->writeHTMLCell($colDef['width'], 2, isset($colDef['xStartPos']) ? $colDef['xStartPos'] : 0, $curY, $columnText, 0, 1, 0, true, $colDef['content']['align']); + $this->setAfterColsLinePositionsData($colKey, $pdf->GetY(), $pdf->getPage()); // restore cell padding $pdf->setCellPaddings($curentCellPaddinds['L'], $curentCellPaddinds['T'], $curentCellPaddinds['R'], $curentCellPaddinds['B']); diff --git a/htdocs/core/modules/propale/doc/pdf_cyan.modules.php b/htdocs/core/modules/propale/doc/pdf_cyan.modules.php index a05f2dc285d..f2f69e53c1c 100644 --- a/htdocs/core/modules/propale/doc/pdf_cyan.modules.php +++ b/htdocs/core/modules/propale/doc/pdf_cyan.modules.php @@ -78,7 +78,6 @@ class pdf_cyan extends ModelePDFPropales */ public $cols; - /** * Constructor * @@ -185,10 +184,7 @@ class pdf_cyan extends ModelePDFPropales $nblines = count($object->lines); - $hidetop = 0; - if (getDolGlobalString('MAIN_PDF_DISABLE_COL_HEAD_TITLE')) { - $hidetop = getDolGlobalString('MAIN_PDF_DISABLE_COL_HEAD_TITLE'); - } + $hidetop = getDolGlobalInt('MAIN_PDF_DISABLE_COL_HEAD_TITLE'); // Loop on each lines to detect if there is at least one image to show $realpatharray = array(); @@ -352,6 +348,10 @@ class pdf_cyan extends ModelePDFPropales $tab_top = 90 + $top_shift; $tab_top_newpage = (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD') ? 42 + $top_shift : 10); + if (!$hidetop && getDolGlobalInt('MAIN_PDF_ENABLE_COL_HEAD_TITLE_REPEAT')) { + // TODO : make this hidden conf the default behavior for each PDF when each PDF managed this new Display + $tab_top_newpage+= $this->tabTitleHeight; + } $nexY = $tab_top; @@ -549,8 +549,25 @@ class pdf_cyan extends ModelePDFPropales // Loop on each lines $pageposbeforeprintlines = $pdf->getPage(); $pagenb = $pageposbeforeprintlines; + for ($i = 0; $i < $nblines; $i++) { + $linePosition = $i + 1; $curY = $nexY; + + // in First Check line page break and add page if needed + if (isset($object->lines[$i]->pagebreak) && $object->lines[$i]->pagebreak) { + // New page + $pdf->AddPage(); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + + $pdf->setPage($pdf->getNumPages()); + $nexY = $tab_top_newpage; + } + + $this->resetAfterColsLinePositionsData($nexY, $pdf->getPage()); + $pdf->SetFont('', '', $default_font_size - 1); // Into loop to work with multipage $pdf->SetTextColor(0, 0, 0); @@ -561,7 +578,7 @@ class pdf_cyan extends ModelePDFPropales } $pdf->setTopMargin($tab_top_newpage); - $pdf->setPageOrientation('', 1, $heightforfooter + $heightforfreetext + $heightforsignature + $heightforinfotot); // The only function to edit the bottom margin of current page to set it. + $pdf->setPageOrientation('', 1, $heightforfooter); // The only function to edit the bottom margin of current page to set it. $pageposbefore = $pdf->getPage(); $curYBefore = $curY; @@ -569,28 +586,17 @@ class pdf_cyan extends ModelePDFPropales $showpricebeforepagebreak = getDolGlobalInt('MAIN_PDF_DATA_ON_FIRST_PAGE'); $posYAfterImage = 0; - $posYAfterDescription = 0; - if ($this->getColumnStatus('position')) { - $this->printStdColumnContent($pdf, $curY, 'position', $i + 1); - } - - $newPageAddedForPhoto = false; if ($this->getColumnStatus('photo')) { // We start with Photo of product line - if (isset($imglinesize['width']) && isset($imglinesize['height']) && ($curY + $imglinesize['height']) > ($this->page_hauteur - ($heightforfooter + $heightforfreetext))) { // If photo too high, we moved completely on new page + if (isset($imglinesize['width']) && isset($imglinesize['height']) && ($curY + $imglinesize['height']) > ($this->page_hauteur - $heightforfooter)) { // If photo too high, we moved completely on new page $pdf->AddPage('', '', true); - $newPageAddedForPhoto = true; if (!empty($tplidx)) { $pdf->useTemplate($tplidx); } $pdf->setPage($pageposbefore + 1); - + $pdf->setPageOrientation('', 1, $heightforfooter); // The only function to edit the bottom margin of current page to set it. $curY = $tab_top_newpage; - - // disable MAIN_PDF_DATA_ON_FIRST_PAGE near page break limit to anticipate auto page break jump before add line due to padding - // (ex: description is on page 2 and price on page 1) - $showpricebeforepagebreak = 0; } $pdf->setPageOrientation('', 0, $heightforfooter + $heightforfreetext); // The only function to edit the bottom margin of current page to set it. @@ -598,85 +604,46 @@ class pdf_cyan extends ModelePDFPropales $pdf->Image($realpatharray[$i], $this->getColumnContentXStart('photo'), $curY + 1, $imglinesize['width'], $imglinesize['height'], '', '', '', 2, 300); // Use 300 dpi // $pdf->Image does not increase value return by getY, so we save it manually $posYAfterImage = $curY + $imglinesize['height']; - } - $pageposafter = $pdf->getPage(); - if ($pageposafter > $pageposbefore) { // There is a pagebreak - $pdf->setPage($pageposbefore+1); + $this->setAfterColsLinePositionsData('photo', $posYAfterImage, $pdf->getPage()); } - - // restore page bottom margin - $pdf->setPageOrientation('', 1, $heightforfooter + $heightforfreetext + $heightforsignature + $heightforinfotot); // The only function to edit the bottom margin of current page to set it. } - $newPageAddedForDescription = false; + if ($this->getColumnStatus('desc')) { - $pdf->startTransaction(); - $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); - $pageposafter = $pdf->getPage(); - - if ($pageposafter > $pageposbefore) { // There is a pagebreak - $pdf->rollbackTransaction(true); - $newPageAddedForDescription = true; - - $pdf->setPageOrientation('', 1, $heightforfooter); // The only function to edit the bottom margin of current page to set it. - - $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); - - $pageposafter = $pdf->getPage(); - $posyafter = $pdf->GetY(); - //var_dump($posyafter); var_dump(($this->page_hauteur - ($heightforfooter+$heightforfreetext+$heightforinfotot))); exit; - if ($posyafter > ($this->page_hauteur - ($heightforfooter + $heightforfreetext + $heightforsignature + $heightforinfotot))) { // There is no space left for total+free text - if ($i == ($nblines - 1)) { // No more lines, and no space left to show total, so we create a new page - $pdf->AddPage('', '', true); - if (!empty($tplidx)) { - $pdf->useTemplate($tplidx); - } - $pdf->setPage($pageposafter + 1); - } - } - } else { // No pagebreak - $pdf->commitTransaction(); - } - $posYAfterDescription = $pdf->GetY(); + $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); } - $nexY = $pdf->GetY(); - - // check if image height is more than description height - if (!$newPageAddedForPhoto && $posYAfterImage) { - $nexY = max($posYAfterImage, $nexY); - } - - $pageposafter = $pdf->getPage(); - + $afterPosData = $this->getMaxAfterColsLinePositionsData(); $pdf->setPage($pageposbefore); $pdf->setTopMargin($this->marge_haute); $pdf->setPageOrientation('', 1, 0); // The only function to edit the bottom margin of current page to set it. + // We suppose that a too long description or photo were moved completely on next page - if ($pageposafter > $pageposbefore && empty($showpricebeforepagebreak)) { - $pdf->setPage($pageposafter); + if ($afterPosData['page'] > $pageposbefore && empty($showpricebeforepagebreak)) { + $pdf->setPage($afterPosData['page']); $curY = $tab_top_newpage; - } else { - $curY = $curYBefore; } $pdf->SetFont('', '', $default_font_size - 1); // We reposition the default font + // Line position + if ($this->getColumnStatus('position')) { + $this->printStdColumnContent($pdf, $curY, 'position', $linePosition); + } + // VAT Rate if ($this->getColumnStatus('vat')) { $vat_rate = pdf_getlinevatrate($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'vat', $vat_rate); - $nexY = max($pdf->GetY(), $nexY); } // Unit price before discount if ($this->getColumnStatus('subprice')) { $up_excl_tax = pdf_getlineupexcltax($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'subprice', $up_excl_tax); - $nexY = max($pdf->GetY(), $nexY); } // Quantity @@ -684,7 +651,6 @@ class pdf_cyan extends ModelePDFPropales if ($this->getColumnStatus('qty')) { $qty = pdf_getlineqty($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'qty', $qty); - $nexY = max($pdf->GetY(), $nexY); } @@ -692,28 +658,24 @@ class pdf_cyan extends ModelePDFPropales if ($this->getColumnStatus('unit')) { $unit = pdf_getlineunit($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'unit', $unit); - $nexY = max($pdf->GetY(), $nexY); } // Discount on line if ($this->getColumnStatus('discount') && $object->lines[$i]->remise_percent) { $remise_percent = pdf_getlineremisepercent($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'discount', $remise_percent); - $nexY = max($pdf->GetY(), $nexY); } // Total excl tax line (HT) if ($this->getColumnStatus('totalexcltax')) { $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); - $nexY = max($pdf->GetY(), $nexY); } // Total with tax line (TTC) if ($this->getColumnStatus('totalincltax')) { $total_incl_tax = pdf_getlinetotalwithtax($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'totalincltax', $total_incl_tax); - $nexY = max($pdf->GetY(), $nexY); } // Extrafields @@ -722,17 +684,19 @@ class pdf_cyan extends ModelePDFPropales if ($this->getColumnStatus($extrafieldColKey)) { $extrafieldValue = $this->getExtrafieldContent($object->lines[$i], $extrafieldColKey, $outputlangs); $this->printStdColumnContent($pdf, $curY, $extrafieldColKey, $extrafieldValue); - $nexY = max($pdf->GetY(), $nexY); + + $this->setAfterColsLinePositionsData('options_'.$extrafieldColKey, $pdf->GetY(), $pdf->getPage()); } } } + $afterPosData = $this->getMaxAfterColsLinePositionsData(); $parameters = array( 'object' => $object, 'i' => $i, 'pdf' => & $pdf, 'curY' => & $curY, - 'nexY' => & $nexY, + 'nexY' => & $afterPosData['y'], // for backward module hook compatibility Y will be accessible by $object->getMaxAfterColsLinePositionsData() 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails ); @@ -768,7 +732,7 @@ class pdf_cyan extends ModelePDFPropales // Retrieve type from database for backward compatibility with old records if ((!isset($localtax1_type) || $localtax1_type == '' || !isset($localtax2_type) || $localtax2_type == '') // if tax type not defined - && (!empty($localtax1_rate) || !empty($localtax2_rate))) { // and there is local tax + && (!empty($localtax1_rate) || !empty($localtax2_rate))) { // and there is local tax $localtaxtmp_array = getLocalTaxesFromRate($vatrate, 0, $object->thirdparty, $mysoc); $localtax1_type = isset($localtaxtmp_array[0]) ? $localtaxtmp_array[0] : ''; $localtax2_type = isset($localtaxtmp_array[2]) ? $localtaxtmp_array[2] : ''; @@ -806,26 +770,13 @@ class pdf_cyan extends ModelePDFPropales $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')] = array('vatrate' => $vatrate, 'vatcode' => $vatcode, 'amount' => $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')]['amount'] + $tvaligne); - // if new pages have been added, we need to check whether they were created by the image display script or the description display script - if ($pageposafter > $pageposbefore && !empty($showpricebeforepagebreak)) { - $pdf->setPage($pageposafter); - if ($pageposafter - $pageposbefore > 2 && $newPageAddedForDescription) { - // photo can't add 2 pages, so it must be description who break others pages - $nexY = $posYAfterDescription; - } elseif ($newPageAddedForPhoto && $newPageAddedForDescription) { - // if break page is detect for photo and description we need to take the bigger Y - $nexY = max($posYAfterImage, $posYAfterDescription); - } elseif ($newPageAddedForPhoto) { - $nexY = $posYAfterImage; - } else { - $nexY = $posYAfterDescription; - } - } + $afterPosData = $this->getMaxAfterColsLinePositionsData(); + $pdf->setPage($afterPosData['page']); + $nexY = $afterPosData['y']; // Add line - if (getDolGlobalString('MAIN_PDF_DASH_BETWEEN_LINES') && $i < ($nblines - 1)) { - $pdf->setPage($pageposafter); + if (getDolGlobalString('MAIN_PDF_DASH_BETWEEN_LINES') && $i < ($nblines - 1) && $afterPosData['y'] < $this->page_hauteur - $heightforfooter - 5) { $pdf->SetLineStyle(array('dash' => '1,1', 'color' => array(80, 80, 80))); //$pdf->SetDrawColor(190,190,200); $pdf->line($this->marge_gauche, $nexY + 1, $this->page_largeur - $this->marge_droite, $nexY + 1); @@ -833,54 +784,70 @@ class pdf_cyan extends ModelePDFPropales } $nexY += 2; // Add space between lines + } - // Detect if some page were added automatically and output _tableau for past pages - while ($pagenb < $pageposafter) { - $pdf->setPage($pagenb); - if ($pagenb == $pageposbeforeprintlines) { - $this->_tableau($pdf, $tab_top, $this->page_hauteur - $tab_top - $heightforfooter, 0, $outputlangs, $hidetop, 1, $object->multicurrency_code, $outputlangsbis); + // Add last page for document footer if there are not enough size left + $afterPosData = $this->getMaxAfterColsLinePositionsData(); + if ($afterPosData['y'] > $this->page_hauteur - ($heightforfooter + $heightforfreetext + $heightforsignature + $heightforinfotot) ) { + $pdf->AddPage(); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + $pagenb++; + $pdf->setPage($pagenb); + } + + // Draw table frames and columns borders + $drawTabNumbPage = $pdf->getNumPages(); + for ($i=$pageposbeforeprintlines; $i<=$drawTabNumbPage; $i++) { + // reset page orientation each loop to override it if it was changed + $pdf->setPageOrientation('', 1, 0); // The only function to edit the bottom margin of current page to set it. + + $pdf->setPage($i); + + $drawTabHideTop = $hidetop; + $drawTabTop = $tab_top_newpage; + $drawTabBottom = $this->page_hauteur - $heightforfooter;; + $hideBottom = 0; // TODO understand why it change to 1 or 0 during process + + if ($i == $pageposbeforeprintlines) { + // first page need to start after notes + $drawTabTop = $tab_top; + } elseif (!$drawTabHideTop) { + if (getDolGlobalInt('MAIN_PDF_ENABLE_COL_HEAD_TITLE_REPEAT')) { + $drawTabTop-= $this->tabTitleHeight; } else { - $this->_tableau($pdf, $tab_top_newpage, $this->page_hauteur - $tab_top_newpage - $heightforfooter, 0, $outputlangs, 1, 1, $object->multicurrency_code, $outputlangsbis); - } - $this->_pagefoot($pdf, $object, $outputlangs, 1); - $pagenb++; - $pdf->setPage($pagenb); - $pdf->setPageOrientation('', 1, 0); // The only function to edit the bottom margin of current page to set it. - if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD')) { - $this->_pagehead($pdf, $object, 0, $outputlangs); - } - if (!empty($tplidx)) { - $pdf->useTemplate($tplidx); + $drawTabHideTop = 1; } } - if (isset($object->lines[$i + 1]->pagebreak) && $object->lines[$i + 1]->pagebreak) { - if ($pagenb == $pageposafter) { - $this->_tableau($pdf, $tab_top, $this->page_hauteur - $tab_top - $heightforfooter, 0, $outputlangs, $hidetop, 1, $object->multicurrency_code, $outputlangsbis); - } else { - $this->_tableau($pdf, $tab_top_newpage, $this->page_hauteur - $tab_top_newpage - $heightforfooter, 0, $outputlangs, 1, 1, $object->multicurrency_code, $outputlangsbis); - } - $this->_pagefoot($pdf, $object, $outputlangs, 1); - // New page - $pdf->AddPage(); - if (!empty($tplidx)) { - $pdf->useTemplate($tplidx); - } - $pagenb++; - if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD')) { - $this->_pagehead($pdf, $object, 0, $outputlangs); - } + // last page need to include document footer + if ($i == $pdf->getNumPages()) { + // remove document footer height to tab bottom position + $drawTabBottom-= $heightforsignature + $heightforfreetext + $heightforinfotot; + } + + $drawTabHeight = $drawTabBottom - $drawTabTop; + $this->_tableau($pdf, $drawTabTop, $drawTabHeight, 0, $outputlangs, $drawTabHideTop, $hideBottom, $object->multicurrency_code, $outputlangsbis); + + $this->_pagefoot($pdf, $object, $outputlangs, 1); + + $pdf->setPage($i); // in case of _pagefoot or _tableau change it + + // reset page orientation each loop to override it if it was changed by _pagefoot or _tableau change it + $pdf->setPageOrientation('', 1, 0); // The only function to edit the bottom margin of current page to set it. + if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD')) { + $this->_pagehead($pdf, $object, 0, $outputlangs); + } + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); } } - // Show square - if ($pagenb == $pageposbeforeprintlines) { - $this->_tableau($pdf, $tab_top, $this->page_hauteur - $tab_top - $heightforinfotot - $heightforfreetext - $heightforsignature - $heightforfooter, 0, $outputlangs, $hidetop, 0, $object->multicurrency_code, $outputlangsbis); - $bottomlasttab = $this->page_hauteur - $heightforinfotot - $heightforfreetext - $heightforsignature - $heightforfooter + 1; - } else { - $this->_tableau($pdf, $tab_top_newpage, $this->page_hauteur - $tab_top_newpage - $heightforinfotot - $heightforfreetext - $heightforsignature - $heightforfooter, 0, $outputlangs, 1, 0, $object->multicurrency_code, $outputlangsbis); - $bottomlasttab = $this->page_hauteur - $heightforinfotot - $heightforfreetext - $heightforsignature - $heightforfooter + 1; - } + + $pdf->setPage($pdf->getNumPages()); + + $bottomlasttab = $this->page_hauteur - $heightforinfotot - $heightforfreetext - $heightforsignature - $heightforfooter + 1; // Display infos area $posy = $this->drawInfoTable($pdf, $object, $bottomlasttab, $outputlangs); @@ -1105,8 +1072,8 @@ class pdf_cyan extends ModelePDFPropales if (!getDolGlobalString('PROPOSAL_PDF_HIDE_PAYMENTMODE')) { // Show payment mode if ($object->mode_reglement_code - && $object->mode_reglement_code != 'CHQ' - && $object->mode_reglement_code != 'VIR') { + && $object->mode_reglement_code != 'CHQ' + && $object->mode_reglement_code != 'VIR') { $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); $pdf->SetXY($this->marge_gauche, $posy); $titre = $outputlangs->transnoentities("PaymentMode").':';