From 500e064c3efb538e124246f7e100d1e61a2e791a Mon Sep 17 00:00:00 2001 From: Seved Torstendahl Date: Fri, 15 Nov 2024 17:14:09 +0100 Subject: [PATCH 1/5] The Report module is updated to allow the creation of reports with clickable references and better layout of pdf reports. --- app/Report/AbstractRenderer.php | 4 +- app/Report/HtmlRenderer.php | 27 +- app/Report/PdfRenderer.php | 11 +- app/Report/ReportBaseTextbox.php | 18 +- app/Report/ReportHtmlImage.php | 8 +- app/Report/ReportHtmlText.php | 3 + app/Report/ReportHtmlTextbox.php | 86 +- app/Report/ReportParserGenerate.php | 479 +++++++- app/Report/ReportPdfText.php | 40 +- app/Report/ReportPdfTextBox.php | 56 +- resources/xml/reports/bdm_report.xml | 27 +- resources/xml/reports/birth_report.xml | 5 +- resources/xml/reports/change_report.xml | 38 +- resources/xml/reports/death_report.xml | 44 +- resources/xml/reports/fact_sources.xml | 7 +- resources/xml/reports/family_group_report.xml | 159 +-- .../xml/reports/individual_ext_report.xml | 59 +- resources/xml/reports/individual_report.xml | 40 +- resources/xml/reports/marriage_report.xml | 25 +- .../xml/reports/missing_facts_report.xml | 1006 ++++++++++------- resources/xml/reports/occupation_report.xml | 17 +- resources/xml/reports/relative_ext_report.xml | 62 +- 22 files changed, 1522 insertions(+), 699 deletions(-) diff --git a/app/Report/AbstractRenderer.php b/app/Report/AbstractRenderer.php index fb6194d8fbf..14f5e3cd871 100644 --- a/app/Report/AbstractRenderer.php +++ b/app/Report/AbstractRenderer.php @@ -243,7 +243,9 @@ abstract public function createTextBox( string $style, bool $fill, bool $padding, - bool $reseth + bool $reseth, + bool $abs_position, + float $top_position ): ReportBaseTextbox; /** diff --git a/app/Report/HtmlRenderer.php b/app/Report/HtmlRenderer.php index 12b636697db..07b76ee5cb1 100644 --- a/app/Report/HtmlRenderer.php +++ b/app/Report/HtmlRenderer.php @@ -169,7 +169,7 @@ public function run(): void //-- header divider echo '', PHP_EOL; echo '
'; - echo '
'; + echo '
'; foreach ($this->headerElements as $element) { if ($element instanceof ReportBaseElement) { $element->render($this); @@ -196,9 +196,8 @@ public function run(): void } //-- footer echo '
'; - echo ''; - echo '
'; - echo '
'; + echo '
'; + echo '
'; $this->Y = 0; $this->X = 0; $this->maxY = 0; @@ -212,8 +211,12 @@ public function run(): void } } echo '
'; - echo ''; - echo '
'; + if ($this->show_generated_by) { + echo ''; + } + echo '
'; } /** @@ -271,9 +274,11 @@ public function createTextBox( string $style, bool $fill, bool $padding, - bool $reseth + bool $reseth, + bool $abs_position, + float $top_position ): ReportBaseTextbox { - return new ReportHtmlTextbox($width, $height, $border, $bgcolor, $newline, $left, $top, $pagecheck, $style, $fill, $padding, $reseth); + return new ReportHtmlTextbox($width, $height, $border, $bgcolor, $newline, $left, $top, $pagecheck, $style, $fill, $padding, $reseth, $abs_position, $top_position); } /** @@ -524,7 +529,9 @@ public function getStringWidth(string $text): float { $style = $this->getStyle($this->currentStyle); - return mb_strlen($text) * $style['size'] / 2; + // &#x..; is counted as 6 chars, not one. Many such chars will force too early line breaks + $txt2 = str_replace([' ', '╰', ' ', '—'], ["#", "#", "#", "#"], $text); + return mb_strlen($txt2) * ($style['size'] / 2); } /** @@ -715,10 +722,12 @@ public function write(string $text, string $color = '', bool $useclass = true): "\n", '> ', ' <', + "\xA0", ], [ '
', '> ', ' <', + ' ', ], $htmlcode); echo $htmlcode; } diff --git a/app/Report/PdfRenderer.php b/app/Report/PdfRenderer.php index bbe261242b4..f29e59d3e45 100644 --- a/app/Report/PdfRenderer.php +++ b/app/Report/PdfRenderer.php @@ -74,6 +74,7 @@ class PdfRenderer extends AbstractRenderer */ public function header(): void { + $this->tcpdf->AddPage(); foreach ($this->headerElements as $element) { if ($element instanceof ReportBaseElement) { $element->render($this); @@ -363,8 +364,8 @@ public function setup(): void 'UTF-8', self::DISK_CACHE ); - - $this->tcpdf->setMargins($this->left_margin, $this->top_margin, $this->right_margin); + $this->tcpdf->setPrintFooter(true); + $this->tcpdf->setMargins($this->left_margin, $this->top_margin, $this->right_margin); // top-margin? 55.5? header-height? $this->tcpdf->setHeaderMargin($this->header_margin); $this->tcpdf->setFooterMargin($this->footer_margin); $this->tcpdf->setAutoPageBreak(true, $this->bottom_margin); @@ -454,9 +455,11 @@ public function createTextBox( string $style, bool $fill, bool $padding, - bool $reseth + bool $reseth, + bool $abs_position, + float $top_position ): ReportBaseTextbox { - return new ReportPdfTextBox($width, $height, $border, $bgcolor, $newline, $left, $top, $pagecheck, $style, $fill, $padding, $reseth); + return new ReportPdfTextBox($width, $height, $border, $bgcolor, $newline, $left, $top, $pagecheck, $style, $fill, $padding, $reseth, $abs_position, $top_position); } /** diff --git a/app/Report/ReportBaseTextbox.php b/app/Report/ReportBaseTextbox.php index a43478b26d4..50c84692c1c 100644 --- a/app/Report/ReportBaseTextbox.php +++ b/app/Report/ReportBaseTextbox.php @@ -71,6 +71,15 @@ class ReportBaseTextbox extends ReportBaseElement // Resets this box last height after it’s done public bool $reseth; + // position of the TextBox: true=absolute or false=relative: + public bool $abs_position; + + // Value of top position + public float $top_position; + + // Spec position for pdf textbox + public bool $html_only_position; + /** * TextBox - Element - Base * @@ -86,6 +95,8 @@ class ReportBaseTextbox extends ReportBaseElement * @param bool $fill * @param bool $padding * @param bool $reseth + * @param bool $abs_position + * @param float $top_position */ public function __construct( float $width, @@ -99,7 +110,9 @@ public function __construct( string $style, bool $fill, bool $padding, - bool $reseth + bool $reseth, + bool $abs_position, + float $top_position ) { $this->border = $border; $this->bgcolor = $bgcolor; @@ -113,6 +126,9 @@ public function __construct( $this->width = $width; $this->padding = $padding; $this->reseth = $reseth; + $this->abs_position = $abs_position; + $this->top_position = $top_position; + error_log("\ncreateTB base(top,w,h,nl,reseth): ".var_export([$top,$width,$height,$newline,$reseth],true)."\n\n",3,"pdf.log"); } /** diff --git a/app/Report/ReportHtmlImage.php b/app/Report/ReportHtmlImage.php index 513a7de431a..b6e57a05055 100644 --- a/app/Report/ReportHtmlImage.php +++ b/app/Report/ReportHtmlImage.php @@ -62,7 +62,13 @@ public function render($renderer): void echo '\"\"\n
\n"; break; default: - echo '\"\"\n"; + // max-height limit distorts where the height exceeds 40pt when width is set to 30pt. + // Such images should be cropped instead + $maxh = ""; + if ($this->width <= 30) { + $maxh = "max-height:40pt;"; + } + echo '\n'; } $lastpicpage = $renderer->pageNo(); diff --git a/app/Report/ReportHtmlText.php b/app/Report/ReportHtmlText.php index 3f3824405d2..06359d47f38 100644 --- a/app/Report/ReportHtmlText.php +++ b/app/Report/ReportHtmlText.php @@ -98,6 +98,9 @@ public function render($renderer, bool $attrib = true): void public function getHeight($renderer): float { $ct = substr_count($this->text, "\n"); + if (substr($this->text, -1) == "\n") { + $ct -=1; + } if ($ct > 0) { $ct += 1; } diff --git a/app/Report/ReportHtmlTextbox.php b/app/Report/ReportHtmlTextbox.php index 21c0c6a3971..88c1c901bad 100644 --- a/app/Report/ReportHtmlTextbox.php +++ b/app/Report/ReportHtmlTextbox.php @@ -40,9 +40,10 @@ class ReportHtmlTextbox extends ReportBaseTextbox */ public function render($renderer): void { + static $lastBoxYfinal; // checkFootnote $newelements = []; - $lastelement = []; + $lastelement = null; $footnote_element = []; // Element counter $cE = count($this->elements); @@ -58,22 +59,23 @@ public function render($renderer): void } $footnote_element = []; } - if (empty($lastelement)) { + if (!isset($lastelement)) { $lastelement = $element; - } elseif ($element->getStyleName() === $lastelement->getStyleName()) { - // Checking if the Text has the same style + } elseif (($element instanceof ReportBaseText && $lastelement instanceof ReportBaseText) && ($element->getStyleName() === $lastelement->getStyleName())) { $lastelement->addText(str_replace("\n", '
', $element->getValue())); } else { $newelements[] = $lastelement; $lastelement = $element; } + } elseif ($element instanceof ReportHtmlImage) { + $lastelement = $element; } elseif ($element instanceof ReportHtmlFootnote) { // Check if the Footnote has been set with it’s link number $renderer->checkFootnote($element); // Save first the last element if any - if (!empty($lastelement)) { + if (isset($lastelement)) { $newelements[] = $lastelement; - $lastelement = []; + $lastelement = null; } // Save the Footnote with it’s link number as key for sorting later $footnote_element[$element->num] = $element; @@ -86,16 +88,16 @@ public function render($renderer): void } $footnote_element = []; } - if (!empty($lastelement)) { + if (isset($lastelement)) { $newelements[] = $lastelement; - $lastelement = []; + $lastelement = null; } $newelements[] = $element; } } else { - if (!empty($lastelement)) { + if (isset($lastelement)) { $newelements[] = $lastelement; - $lastelement = []; + $lastelement = null; } if (!empty($footnote_element)) { ksort($footnote_element); @@ -107,7 +109,7 @@ public function render($renderer): void $newelements[] = $element; } } - if (!empty($lastelement)) { + if (isset($lastelement)) { $newelements[] = $lastelement; } if (!empty($footnote_element)) { @@ -119,6 +121,11 @@ public function render($renderer): void $this->elements = $newelements; unset($footnote_element, $lastelement, $newelements); + if ($cE>0) + error_log("\nhtmlTB start: [cE,element[1]]=". + var_export([$cE,$this->elements[0]],true)."\n",3,"html_tb.log"); + + $cP = 0; // Class Padding // Used with line breaks and cell height calculation within this box only @@ -132,11 +139,22 @@ public function render($renderer): void $renderer->setX($cX); } // If current position (top) - if ($this->top === ReportBaseElement::CURRENT_POSITION) { - $this->top = $renderer->getY(); - } else { - $renderer->setY($this->top); + $align_Y = false; + $first_Y = $renderer->getY(); + $topstr = ""; + if ($this->abs_position) { + if ($this->top == ReportBaseElement::CURRENT_POSITION) { + $this->top = $first_Y; + } + } + if (!$this->abs_position) { + if ($this->top_position !== ReportBaseElement::CURRENT_POSITION) { + $this->top = $this->top_position; + } + $topstr = "top:" . $this->top . "pt;"; + $align_Y = true; } + $renderer->setY($this->top); // Check the width if set to page wide OR set by xml to larger then page width (margin) if ($this->width === 0.0 || $this->width > $renderer->getRemainingWidth()) { @@ -192,7 +210,8 @@ public function render($renderer): void } // Add up what’s the final height - $cH = $this->height; + //$cH = $this->height; + $cH = 0; // If any element exist if ($cE > 0) { // Check if this is text or some other element, like images @@ -225,7 +244,12 @@ public function render($renderer): void $renderer->addMaxY($this->top + $cH); // Start to print HTML - echo '
top, 'pt;'; + } else { + echo '
alignRTL, ':', $cX, 'pt;'; // Background color @@ -237,12 +261,23 @@ public function render($renderer): void // Use Cell around padding to support RTL also echo 'padding:', $cP, 'pt;'; } + //if ($this->html_only_position) { // allow a small margin between images if text is short + // echo "margin-bottom:5pt;"; + //} // Border setup if ($this->border) { echo ' border:solid black 1pt;'; - echo 'width:', $this->width - 1 - $cP * 2, 'pt;height:', $cH - 1, 'pt;'; + if (!$align_Y) { + echo 'width:', $this->width - 1 - $cP * 2, 'pt;height:', $cH - 1, 'pt;'; + } else { + echo 'width:', $this->width - 1 - $cP * 2, 'pt;height:auto;'; + } // height:',$this->height,'pt;'; //,$topstr; } else { - echo 'width:', $this->width - $cP * 2, 'pt;height:', $cH, 'pt;'; + if (!$align_Y) { + echo 'width:', $this->width - $cP * 2, 'pt;height:', $cH, 'pt;'; + } else { + echo 'width:', $this->width - $cP * 2, 'pt;height:auto;'; + } //height:',$this->height,'pt;'; //,$topstr; } echo '">'; @@ -270,15 +305,24 @@ public function render($renderer): void $renderer->setXy($cXT, $cYT); // This will be mostly used to trick the multiple images last height if ($this->reseth) { + $this->top = $first_Y; //- $cH; $cH = 0; } - // New line and some clean up + error_log("\nhtml end1: [top,top_position,cYT,getY(),first_Y,newline]=". + var_export([$this->top,$this->top_position,$cYT,$renderer->getY(),$first_Y,$this->newline],true)."\n",3,"html_tb.log"); + // New line and some clean up if (!$this->newline) { $renderer->setXy($cX + $this->width, $this->top); $renderer->lastCellHeight = $cH; } else { - $renderer->setXy(0, $this->top + $cH + $cP * 2); + //$renderer->setXy(0, $this->top + $cH + $cP * 2); + $renderer->setXy(0, $first_Y + $cH + $cP * 2); $renderer->lastCellHeight = 0; } + error_log("html end2: [top,reseth,cYT,cY,first_Y,newline]=". + var_export([$this->top,$this->reseth,$cYT,$renderer->getY(),$first_Y,$this->newline],true)."\n",3,"html_tb.log"); + // This will make images in textboxes to ignore images in previous textboxes + // Without this trick the $lastpicbottom in the previos textbox will be used in ReportHtmlImage + $renderer->pageN++; } } diff --git a/app/Report/ReportParserGenerate.php b/app/Report/ReportParserGenerate.php index e56e6810690..87a3763ee54 100644 --- a/app/Report/ReportParserGenerate.php +++ b/app/Report/ReportParserGenerate.php @@ -22,7 +22,7 @@ use DomainException; use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Date; -use Fisharebest\Webtrees\DB; +//use Fisharebest\Webtrees\DB; // webtrees 2.2.x use Fisharebest\Webtrees\Elements\UnknownElement; use Fisharebest\Webtrees\Factories\MarkdownFactory; use Fisharebest\Webtrees\Family; @@ -36,6 +36,7 @@ use Fisharebest\Webtrees\Place; use Fisharebest\Webtrees\Registry; use Fisharebest\Webtrees\Tree; +use Illuminate\Database\Capsule\Manager as DB; // webtrees 2.1.x use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Expression; use Illuminate\Database\Query\JoinClause; @@ -79,6 +80,7 @@ use function strpos; use function strtoupper; use function substr; +use function substr_replace; use function trim; use function uasort; use function xml_error_string; @@ -181,8 +183,20 @@ class ReportParserGenerate extends ReportParserBase /** @var array> Variables defined in the report at run-time */ private array $vars; + /** @var array Family relationship */ + private array $mfrelation = []; + private Tree $tree; + // position of the TextBox: true=absolute or false=relative: + private bool $abs_position = true; + + // Value of top positionof the TextBox + private float $top_position = 0.0; + + // Spec position for html textbox + private bool $html_only_position = true; + /** * Create a parser for a report * @@ -457,7 +471,7 @@ protected function docStartHandler(array $attrs): void } // Page Size if (!empty($attrs['pageSize'])) { - $this->wt_report->page_format = strtoupper($attrs['pageSize']); + $this->wt_report->page_format = $attrs['pageSize']; } // Show Generated By... @@ -632,6 +646,14 @@ protected function cellStartHandler(array $attrs): void $tcolor, $reseth ); + + // set string URL to be a link + if (isset($attrs['url'])) { + $url = $attrs['url']; + $this->current_element->setUrl($url); + } else { + $url = ""; + } } /** @@ -822,7 +844,29 @@ protected function textBoxStartHandler(array $attrs): void $top = 0; } } + // position of box absolute or relative, and possibly top and height + $abs_position = false; + $top_position = $top; + if (isset($attrs['pos'])) { + $pos = $attrs['pos']; + if (substr($pos, 0, 3) == 'abs') { + $abs_position = true; + } + if (substr($pos, 0, 3) == 'rel') { + $abs_position = false; + $top_position = 0; + } + } + + if (isset($attrs['top_adj'])) { + if (!empty($attrs['top_adj'])) { + $top_position = (int) $attrs['top_adj']; + } + } + + error_log("\nRPG:867 [abs_position,top_position,top]=".var_export([$abs_position,$top_position,$top],true)."\n",3,"rpg.log"); // boolean After this box is finished rendering, should the next section of text start immediately after the this box or should it start on a new line under this box. 0 = no new line, 1 = force new line. Default is 0 + $newline = false; if (isset($attrs['newline'])) { if ($attrs['newline'] === '1') { @@ -878,7 +922,9 @@ protected function textBoxStartHandler(array $attrs): void $style, $fill, $padding, - $reseth + $reseth, + $abs_position, + $top_position ); } @@ -914,13 +960,13 @@ protected function textStartHandler(array $attrs): void // string The name of the Style that should be used to render the text. $style = ''; - if (!empty($attrs['style'])) { + if (isset($attrs['style'])) { $style = $attrs['style']; } // string The color of the text - Keep the black color as default $color = ''; - if (!empty($attrs['color'])) { + if (isset($attrs['color'])) { $color = $attrs['color']; } @@ -972,6 +1018,18 @@ protected function getPersonNameStartHandler(array $attrs): void } } } + $nameselect = ""; + if (isset($attrs['select'])) { + $nameselect = $attrs['select']; + } + $namesep = ""; + if (isset($attrs['name_sep'])) { + $namesep = $attrs['name_sep']; + } + $famrel = false; + if (isset($attrs['fam_relation'])) { + $famrel = true; + } if (!empty($id)) { $record = Registry::gedcomRecordFactory()->make($id, $this->tree); if ($record === null) { @@ -979,11 +1037,33 @@ protected function getPersonNameStartHandler(array $attrs): void } if (!$record->canShowName()) { $this->current_element->addText(I18N::translate('Private')); + } elseif ($nameselect == 'latest') { + $tmp = $record->getAllNames(); + $name = strip_tags($tmp[count($tmp) - 1]['full']); + $this->current_element->addText(trim($name)); + } elseif ($nameselect == 'combined') { + $tmp = $record->getAllNames(); + $name = $tmp[count($tmp) - 1]['full']; + $ix1 = strpos($name, ''); + if ($ix1 !== false) { // '«' and '»' mark text for underlining + $name = substr_replace($name, '«', $ix1, 26); + $ix1 = strpos($name, '', $ix1); + if ($ix1 !== false) { // '«' and '»' mark text for underlining + $name = substr_replace($name, '»', $ix1, 7); + } + } + $addname = strip_tags((string) $tmp[0]['surn']); + if (!empty($addname) && !($addname === '@N.N.') && !str_contains($name, $addname)) { + $name .= " " . $namesep . " " . $addname; + } + $this->current_element->addText(trim($name)); } else { $name = $record->fullName(); $name = strip_tags($name); if (!empty($attrs['truncate'])) { - $name = Str::limit($name, (int) $attrs['truncate'], I18N::translate('…')); + if ((int) $attrs['truncate'] > 0) { + $name = Str::limit($name, (int) $attrs['truncate'], I18N::translate('…')); + } } else { $addname = (string) $record->alternateName(); $addname = strip_tags($addname); @@ -994,6 +1074,9 @@ protected function getPersonNameStartHandler(array $attrs): void $this->current_element->addText(trim($name)); } } + if (isset($record) && $famrel && ($this->mfrelation[$record->xref()] != "")) { + $this->current_element->addText(" (" . (string) $this->mfrelation[$record->xref()] . ")"); + } } /** @@ -1041,7 +1124,16 @@ protected function gedcomValueStartHandler(array $attrs): void switch (end($tags)) { case 'DATE': $tmp = new Date($value); - $value = strip_tags($tmp->display()); + $dfmt = "%j %F %Y"; + if (!empty($attrs['truncate'])) { + if ($attrs['truncate'] === "d") { + $dfmt = "%j %M %Y"; + } + if ($attrs['truncate'] === "Y") { + $dfmt = "%Y"; + } + } + $value = strip_tags($tmp->display(null, $dfmt)); break; case 'PLAC': $tmp = new Place($value, $this->tree); @@ -1060,16 +1152,25 @@ protected function gedcomValueStartHandler(array $attrs): void } $tmp = explode(':', $tag); if (in_array(end($tmp), ['NOTE', 'TEXT'], true)) { - if ($this->tree->getPreference('FORMAT_TEXT') === 'markdown') { + if ($this->tree->getPreference('FORMAT_TEXT') === 'xxmarkdown') { $value = strip_tags(Registry::markdownFactory()->markdown($value, $this->tree), ['br']); } else { - $value = strip_tags(Registry::markdownFactory()->autolink($value, $this->tree), ['br']); + $value = str_replace("\n", "
", $value); + //$value = strip_tags(Registry::markdownFactory()->autolink($value, $this->tree), ['br']); } $value = strtr($value, [MarkdownFactory::BREAK => ' ']); } + if (isset($attrs['lcfirst'])) { + $value = lcfirst($value); + $value = str_replace(["Å","Ä","Ö"], ["å","ä","ö"], $value); + } + if (!empty($attrs['truncate'])) { - $value = Str::limit($value, (int) $attrs['truncate'], I18N::translate('…')); + $value = strip_tags($value); + if ((int) $attrs['truncate'] > 0) { + $value = Str::limit($value, (int) $attrs['truncate'], I18N::translate('…')); + } } $this->current_element->addText($value); } @@ -1138,7 +1239,7 @@ protected function repeatTagStartHandler(array $attrs): void if (preg_match('/^\d ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@/', $subrecord, $xref_match)) { $linked_object = Registry::gedcomRecordFactory()->make($xref_match[1], $this->tree); if ($linked_object && !$linked_object->canShow()) { - continue; + //continue; } } $this->repeats[] = $subrecord; @@ -1159,22 +1260,33 @@ protected function repeatTagEndHandler(): void return; } + $nnnn = count($this->repeats); + $rpt1 = isset($this->repeats[0]) ? $this->repeats[0] : ""; // Check if there is anything to repeat if (count($this->repeats) > 0) { // No need to load them if not used... + //-- read the xml from the file + $lines = file($this->report); + if (empty($lines)) { + error_log(__FILE__ . ":" . __LINE__ . " impossible error!? \n"); + // this can not happen! phpstan forces me to add stupid code + // we come here because the xml file exists! Is it possible to tell phpstan the $lines *is* an array ?? + die("can not happen!!!"); + } $lineoffset = 0; foreach ($this->repeats_stack as $rep) { - $lineoffset += $rep[1]; + if (!empty($rep[1])) { + $lineoffset = $lineoffset + (int) ($rep[1]) - 1; + } } - //-- read the xml from the file - $lines = file($this->report); while (!str_contains($lines[$lineoffset + $this->repeat_bytes], 'repeat_bytes; + $lnnn = $line_nr; // RepeatTag Level counter $count = 1; while (0 < $count) { @@ -1248,7 +1360,7 @@ function ($parser, string $data): void { */ protected function varStartHandler(array $attrs): void { - if (empty($attrs['var'])) { + if (!isset($attrs['var'])) { throw new DomainException('REPORT ERROR var: The attribute "var=" is missing or not set in the XML file on line: ' . xml_get_current_line_number($this->parser)); } @@ -1279,6 +1391,8 @@ protected function varStartHandler(array $attrs): void $var = I18N::number((int) $match[1]); } elseif (preg_match('/^I18N::translate\(\'(.+)\'\)$/', $var, $match)) { $var = I18N::translate($match[1]); + } elseif (preg_match('/^I18N::translate\(\$(.+)\)$/', $var, $match)) { + $var = I18N::translate($this->vars[$match[1]]['id']); } elseif (preg_match('/^I18N::translateContext\(\'(.+)\', *\'(.+)\'\)$/', $var, $match)) { $var = I18N::translateContext($match[1], $match[2]); } @@ -1290,8 +1404,21 @@ protected function varStartHandler(array $attrs): void $var = $g->display(); } } + if (isset($attrs['amp'])) { + $var = str_replace("%26", '&', $var); + } + if (isset($attrs['cut'])) { + $cut = (int) $attrs['cut']; + $var = $cut > 0 ? substr($var, 0, $cut) : substr($var, $cut); + if ($cut == 0) { + $var = ""; + } + } + if (isset($attrs['lcfirst'])) { + $var = lcfirst($var); + } $this->current_element->addText($var); - $this->text = $var; // Used for title/descriptio + $this->text = $var; // Used for title/description } /** @@ -1344,6 +1471,105 @@ protected function factsStartHandler(array $attrs): void } } } + + $jdarr = []; + // Add fact/event for FAM:DIV and for death of spouse + foreach ($this->repeats as $key => $fact) { + $jdarr[$key] = 0; + if (preg_match('/1 FAMS @(.+)@/', $fact, $match)) { + $famid = $match[1]; + $fam = Registry::familyFactory()->make($match[1], $this->tree); + if ($fam === null) { + continue; + } + $dt = $this->getGedcomValue("MARR:DATE", 0, $fam->gedcom()); + if ($dt == "") { + $dt = $this->getGedcomValue("ENGA:DATE", 0, $fam->gedcom()); + } + if ($dt == "" && $this->getGedcomValue("EVEN:TYPE", 0, $fam->gedcom()) == "Sambo") { + $dt = $this->getGedcomValue("EVEN:DATE", 0, $fam->gedcom()); + } + $date = new Date($dt); + $jd = $date->julianDay(); + $jdarr[$key] = $jd; + // Divorce + $dt = $this->getGedcomValue("DIV:DATE", 0, $fam->gedcom()); + if ($dt != "") { + $this->repeats[] = "1 DIV\n2 DATE " . $dt . "\n"; + } + // Separation // Doesn't work!! getGedComValue only reports the first event!! I.e. no match here + if ($this->getGedcomValue("EVEN:TYPE", 0, $fam->gedcom()) == "Separation") { + $dt = $this->getGedcomValue("EVEN:DATE", 0, $fam->gedcom()); + if ($dt != "") { + $this->repeats[] = "1 EVEN\n2 TYPE Separation\n2 DATE " . $dt . "\n"; + } + } + // death of husband / wife + $husb = $fam->husband(); + $wife = $fam->wife(); + if ($this->getGedcomValue("SEX", 0, $this->gedrec) == "M") { + $spouse = $wife; + } else { + $spouse = $husb; + } + if ($spouse) { + $dt = $this->getGedcomValue("DEAT:DATE", 0, $spouse->gedcom()); + } else { + $dt = ""; + } + if ($dt != "") { + $this->repeats[] = "1 _SP_DEAT\n2 DATE " . $dt . "\n2 _O_FAM " . $famid . "\n"; + } + } + } + // Find the dates for the facts that are found + foreach ($this->repeats as $key => $fact) { + if (preg_match('/[234] DATE ([^\n]+)/', $fact, $match)) { + $date = new Date($match[1]); + $jd = $date->julianDay(); + $jdarr[$key] = $jd; + } + } + + // Sort facts in chronological order, if possible + $m = count($this->repeats) - 1; + $prevd = 0; + for ($i = 0; $i <= $m; $i++) { // keep undated events after previous dated event + if ($jdarr[$i] === 0) { + $jdarr[$i] = $prevd; + } else { + $prevd = $jdarr[$i]; + } + } + + while ($m > 1) { + $n = count($this->repeats); + while ($n > 1) { + if ($jdarr[$n - 2] > $jdarr[$n - 1] && $jdarr[$n - 1] !== 0) { + $s = $this->repeats[$n - 1]; + $this->repeats[$n - 1] = $this->repeats[$n - 2]; + $this->repeats[$n - 2] = $s; + $s = $jdarr[$n - 1]; + $jdarr[$n - 1] = $jdarr[$n - 2]; + $jdarr[$n - 2] = $s; + } + $n -= 1; + } + $m -= 1; + } + + // Remove spouse deaths that are too late: after new marriage or own death + $currfam = ""; + for ($i = 0; $i <= count($this->repeats) - 1; $i++) { + if (preg_match('/[1234] FAMS @(.+)@/', $this->repeats[$i], $match)) { + $currfam = $match[1]; + } + if (preg_match('/_SP_DEAT.*\n2 DATE (.*)\n.*_O_FAM (.+)\n/', $this->repeats[$i], $match)) { + if ($currfam != $match[2] || $i == count($this->repeats) - 1) { + $this->repeats[$i] = "1 _XXX\n"; + } // ignore fact + } + } } /** @@ -1363,11 +1589,17 @@ protected function factsEndHandler(): void $line = xml_get_current_line_number($this->parser) - 1; $lineoffset = 0; foreach ($this->repeats_stack as $rep) { - $lineoffset += $rep[1]; + $lineoffset = $lineoffset + (int) ($rep[1]) - 1; } //-- read the xml from the file $lines = file($this->report); + if (empty($lines)) { + error_log(__FILE__ . ":" . __LINE__ . " impossible error!? \n"); + // this can not happen! phpstan forces me to add stupid code + // we come here because the xml file exists! Is it possible to tell phpstan the $lines *is* an array ?? + die("can not happen!!!"); + } while ($lineoffset + $this->repeat_bytes > 0 && !str_contains($lines[$lineoffset + $this->repeat_bytes], 'parser_stack[] = $this->parser; - $oldgedrec = $this->gedrec; - $count = count($this->repeats); - $i = 0; + $oldgedrec = $this->gedrec; + $count = count($this->repeats); + $i = 0; while ($i < $count) { + if (!isset($this->repeats[$i])) { + $i++; + continue; // this fact has been removed above, occured too late + } $this->gedrec = $this->repeats[$i]; - $this->fact = ''; - $this->desc = ''; + $this->fact = ''; + $this->desc = ''; if (preg_match('/1 (\w+)(.*)/', $this->gedrec, $match)) { $this->fact = $match[1]; if ($this->fact === 'EVEN' || $this->fact === 'FACT') { @@ -1458,6 +1694,16 @@ protected function setVarStartHandler(array $attrs): void $name = $attrs['name']; $value = $attrs['value']; + if (isset($attrs['dumpvar'])) { + $dumpvar = $attrs['dumpvar']; + } else { + $dumpvar = ""; + } + $curr_id = ""; + $match = []; + if (preg_match('/0 @(.+)@/', $this->gedrec, $match)) { + $curr_id = $match[1]; + } $match = []; // Current GEDCOM record strings if ($value === '@ID') { @@ -1468,13 +1714,71 @@ protected function setVarStartHandler(array $attrs): void $value = $this->fact; } elseif ($value === '@desc') { $value = $this->desc; + } elseif ($value === '@format') { + if (isset($_GET["format"])) { + $value = $_GET["format"]; + } else { + $value = ""; + } } elseif ($value === '@generation') { $value = (string) $this->generation; + } elseif ($value === '@base_url') { + $value = ""; + if (array_key_exists("REQUEST_URI", $_SERVER)) { + $value = urldecode($_SERVER["REQUEST_URI"]); + } + $url1 = ""; + $i = strpos($value, "route="); + if ($i !== false) { + $url1 = substr($value, 0, $i + 6); + $value = substr($value, $i + 6); + } + $i = strpos($value, "/report"); + if ($i !== false) { + $value = substr($value, 0, $i); + } + $value = $url1 . $value; + } elseif ($value === '@relation') { + if (isset($this->mfrelation[$curr_id]) && $curr_id != "") { + $value = (string) $this->mfrelation[$curr_id]; + } else { + $value = ""; + } } elseif (preg_match("/@(\w+)/", $value, $match)) { $gmatch = []; if (preg_match("/\d $match[1] (.+)/", $this->gedrec, $gmatch)) { $value = str_replace('@', '', trim($gmatch[1])); } + } elseif (preg_match("/@\\$(\w+)/", $value, $match)) { + if ($match[1] == "dump" && $this->vars['dval']['id'] > 0) { + // if ($this->vars[ 'dval' ]['id'] == 1001) + if ($dumpvar == "gedrec") { + error_log("\n---- setvar start " . date("Y-m-d H:i:s") . " RPG " . __LINE__ . " " . $name . " gedcom=\n" . $this->gedrec . "\n", 3, "my-errors.log"); + } elseif ($dumpvar != "") { + error_log("var: " . $dumpvar . " = " . $this->vars[$dumpvar]['id'] . "\n", 3, "my-errors.log"); + } else { + if (array_key_exists('dval', $this->vars)) { + $nnn = $this->vars['dval']['id']; + } else { + $nnn = 0; + } + error_log("\n---- setvar start " . date("Y-m-d H:i:s") . " RPG " . __LINE__ . " " . $name . " -----\n", 3, "my-errors.log"); + foreach ($this->vars as $key => $val) { + if ($nnn-- < 0) { + error_log($key . "='" . $val['id'] . "'\n", 3, "my-errors.log"); + } + } + } + } + $value = $this->vars[$match[1]]['id']; + if (isset($this->vars[$value]['id'])) { + $value = '$' . $this->vars[$match[1]]['id']; + } else { + $value = "0"; + } + } + if (isset($attrs['trim'])) { + $value = str_replace($attrs['trim'], '', $value); } if (preg_match("/\\$(\w+)/", $name, $match)) { $name = $this->vars["'" . $match[1] . "'"]['id']; @@ -1493,6 +1797,9 @@ protected function setVarStartHandler(array $attrs): void } elseif (preg_match('/^I18N::translateContext\(\'(.+)\', *\'(.+)\'\)$/', $value, $match)) { $value = I18N::translateContext($match[1], $match[2]); } + if (isset($attrs['lcfirst'])) { // set 1st char to lower case + $value = lcfirst($value); + } // Arithmetic functions if (preg_match("/(\d+)\s*([-+*\/])\s*(\d+)/", $value, $match)) { @@ -1508,6 +1815,9 @@ protected function setVarStartHandler(array $attrs): void $value = ''; } $this->vars[$name]['id'] = $value; + if ($name == 'title') { + $this->wt_report->title = $value; + } } /** @@ -1899,7 +2209,7 @@ protected function listStartHandler(array $attrs): void ->groupBy(['xref']); }) ->get() - ->map(fn (object $row): GedcomRecord|null => Registry::gedcomRecordFactory()->make($row->xref, $this->tree, $row->new_gedcom ?: $row->old_gedcom)) + ->map(fn (object $row): ?GedcomRecord => Registry::gedcomRecordFactory()->make($row->xref, $this->tree, $row->new_gedcom ?: $row->old_gedcom)) ->filter() ->all(); break; @@ -2281,10 +2591,16 @@ protected function listEndHandler(): void if (count($this->list) > 0) { $lineoffset = 0; foreach ($this->repeats_stack as $rep) { - $lineoffset += $rep[1]; + $lineoffset = $lineoffset + (int) ($rep[1]) - 1; } //-- read the xml from the file $lines = file($this->report); + if (empty($lines)) { + error_log(__FILE__ . ":" . __LINE__ . " impossible error!? \n"); + // this can not happen! phpstan forces me to add stupid code + // we come here because the xml file exists! Is it possible to tell phpstan the $lines *is* an array ?? + die("can not happen!!!"); + } while ((!str_contains($lines[$lineoffset + $this->repeat_bytes], 'repeat_bytes) > 0)) { $lineoffset--; } @@ -2418,6 +2734,8 @@ protected function relativesStartHandler(array $attrs): void $person = Registry::individualFactory()->make($id, $this->tree); if ($person instanceof Individual) { $this->list[$id] = $person; + $this->mfrelation[$id] = ""; + $nam = $person->getAllNames()[0]['fullNN']; switch ($group) { case 'child-family': foreach ($person->childFamilies() as $family) { @@ -2474,7 +2792,12 @@ protected function relativesStartHandler(array $attrs): void $genCounter = 1; while (count($newarray) < count($this->list)) { foreach ($this->list as $key => $value) { - $this->generation = $value->generation; + if ($value->generation < 0) { + // indication of husband or wife + $this->generation = -$value->generation; + } else { + $this->generation = $value->generation; + } if ($this->generation == $genCounter) { $newarray[$key] = (object) ['generation' => $this->generation]; } @@ -2507,10 +2830,16 @@ protected function relativesEndHandler(): void if (count($this->list) > 0) { $lineoffset = 0; foreach ($this->repeats_stack as $rep) { - $lineoffset += $rep[1]; + $lineoffset = $lineoffset + (int) ($rep[1]) - 1; } //-- read the xml from the file $lines = file($this->report); + if (empty($lines)) { + error_log(__FILE__ . ":" . __LINE__ . " impossible error!? \n"); + // this can not happen! phpstan forces me to add stupid code + // we come here because the xml file exists! Is it possible to tell phpstan the $lines *is* an array ?? + die("can not happen!!!"); + } while (!str_contains($lines[$lineoffset + $this->repeat_bytes], 'repeat_bytes > 0) { $lineoffset--; } @@ -2539,10 +2868,16 @@ protected function relativesEndHandler(): void $this->list_total = count($this->list); $this->list_private = 0; - foreach ($this->list as $xref => $value) { + foreach ($this->list as $key => $value) { if (isset($value->generation)) { $this->generation = $value->generation; } + $xref = $key; + $this->vars["dupl"]["id"] = "no"; + if (substr($key, 0, 2) == "D_") { + $xref = substr($key, strrpos($key, "_") + 1); + $this->vars["dupl"]["id"] = "yes"; + } $tmp = Registry::gedcomRecordFactory()->make((string) $xref, $this->tree); $this->gedrec = $tmp->privatizeGedcom(Auth::accessLevel($this->tree)); @@ -2639,12 +2974,33 @@ private function addDescendancy(&$list, $pid, $parents = false, $generations = - if ($person === null) { return; } + + static $focusperson = true; + static $dupl = 1; + $sx = $person->sex(); + $rl = "x"; // unknown + if ($sx == "M") { + $rl = "s"; + } // son + if ($sx == "F") { + $rl = "d"; + } // daughter + if ($focusperson) { + $this->mfrelation[$pid] = ""; + } + $nam = $person->getAllNames()[0]['fullNN']; + + $newpid = $pid; if (!isset($list[$pid])) { $list[$pid] = $person; + } elseif (!$focusperson) { + $newpid = "D_" . $dupl . "_" . $pid; + $list[$newpid] = $person; } - if (!isset($list[$pid]->generation)) { - $list[$pid]->generation = 0; + if (!isset($list[$newpid]->generation)) { + $list[$newpid]->generation = 0; } + $focusperson = false; foreach ($person->spouseFamilies() as $family) { if ($parents) { $husband = $family->husband(); @@ -2666,26 +3022,60 @@ private function addDescendancy(&$list, $pid, $parents = false, $generations = - } } } + $husband = $family->husband(); + $wife = $family->wife(); - $children = $family->children(); + if ($husband && $wife) { + if ($husband->xref() == $person->xref()) { + $this->mfrelation[$wife->xref()] = $this->mfrelation[$person->xref()] . "x"; + if ($wife->canShow()) { + $list[$wife->xref()] = $wife; + } + if (!isset($wife->generation)) { + $wife->generation = $person->generation; + } + $nam = $wife->getAllNames()[0]['fullNN']; + } else { + $this->mfrelation[$husband->xref()] = $this->mfrelation[$person->xref()] . "x"; + if ($husband->canShow()) { + $list[$husband->xref()] = $husband; + } + if (!isset($husband->generation)) { + $husband->generation = $person->generation; + } + $nam = $husband->getAllNames()[0]['fullNN']; + } + } + $children = $family->children(); foreach ($children as $child) { if ($child) { - $list[$child->xref()] = $child; - + $sx = $child->sex(); + $rl = "x"; // unknown + if ($sx == "M") { + $rl = "s"; + } // son + if ($sx == "F") { + $rl = "d"; + } // daughter + $rl = $this->mfrelation[$person->xref()] . $rl; + $this->mfrelation[$child->xref()] = $rl; if (isset($list[$pid]->generation)) { - $list[$child->xref()]->generation = $list[$pid]->generation + 1; + $child->generation = $list[$pid]->generation + 1; } else { - $list[$child->xref()]->generation = 2; + $child->generation = 2; } } } - if ($generations == -1 || $list[$pid]->generation + 1 < $generations) { + if ($generations == -1 || $list[$pid]->generation < $generations) { foreach ($children as $child) { - $this->addDescendancy($list, $child->xref(), $parents, $generations); // recurse on the childs family + if ($child->canShow()) { + $this->addDescendancy($list, $child->xref(), $parents, $generations); + } // recurse on the childs family } } } + $focusperson = false; } /** @@ -2707,6 +3097,9 @@ private function addAncestors(array &$list, string $pid, bool $children = false, if (str_starts_with($id, 'empty')) { continue; // id can be something like “empty7” } + if (!isset($this->mfrelation[$id])) { + $this->mfrelation[$id] = ""; + } $person = Registry::individualFactory()->make($id, $this->tree); foreach ($person->childFamilies() as $family) { $husband = $family->husband(); @@ -2714,10 +3107,12 @@ private function addAncestors(array &$list, string $pid, bool $children = false, if ($husband) { $list[$husband->xref()] = $husband; $list[$husband->xref()]->generation = $list[$id]->generation + 1; + $this->mfrelation[$husband->xref()] = $this->mfrelation[$id] . "f"; } if ($wife) { $list[$wife->xref()] = $wife; $list[$wife->xref()]->generation = $list[$id]->generation + 1; + $this->mfrelation[$wife->xref()] = $this->mfrelation[$id] . "m"; } if ($generations == -1 || $list[$id]->generation + 1 < $generations) { if ($husband) { @@ -2727,10 +3122,14 @@ private function addAncestors(array &$list, string $pid, bool $children = false, $genlist[] = $wife->xref(); } } - if ($children) { + if ($children && isset($person)) { + // unnecessary test of $person to satisfy phpstan! foreach ($family->children() as $child) { $list[$child->xref()] = $child; - $list[$child->xref()]->generation = $list[$id]->generation ?? 1; + $child->generation = $list[$id]->generation ?? 1; + if ($child->xref() != $person->xref()) { + $this->mfrelation[$child->xref()] = $this->mfrelation[$id] . "x"; + } } } } @@ -2812,6 +3211,10 @@ private function getGedcomValue(string $tag, int $level, string $gedrec): string return strtr($value, ['/' => '']); } + if ($tag === 'NAME' || $tag === '_MARNM' || $tag === '_AKA') { + return strtr($value, ['/' => '']); + } + return $value; } diff --git a/app/Report/ReportPdfText.php b/app/Report/ReportPdfText.php index 1aa28c7c512..69a5df6d0a2 100644 --- a/app/Report/ReportPdfText.php +++ b/app/Report/ReportPdfText.php @@ -80,7 +80,7 @@ public function render($renderer): void ], $temptext ); - $renderer->tcpdf->writeHTML( + $renderer->tcpdf->writeHTML( // function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') $temptext, false, false, @@ -125,8 +125,33 @@ public function getWidth($renderer): array $renderer->largestFontHeight = $fsize; } + // Tcpdf does not support box-drawing chars, change them to simple chars + $this->text = str_replace([ + '├', + '╰' + ], [ + "|", + "\\" + ], $this->text); + // Get the line width for the text in points - $lw = $renderer->tcpdf->GetStringWidth($this->text); + // Count some html entities as one char + $txt2 = str_replace([ + ' ', + ' ', + '—', + '╰', + '', + '' + ], [ + "#", + "#", + "#", + "#", + "", + "" + ], $this->text); + $lw = $renderer->tcpdf->GetStringWidth($txt2); // Line Feed counter - Number of lines in the text $lfct = substr_count($this->text, "\n") + 1; // If there is still remaining wrap width... @@ -139,10 +164,17 @@ public function getWidth($renderer): array // Go through the text line by line foreach ($lines as $line) { // Line width in points + a little margin - $lw = $renderer->tcpdf->GetStringWidth($line); + $txt2 = str_replace([ + ' ', + ' ' + ], [ + "#", + "#" + ], $line); + $lw = $renderer->tcpdf->GetStringWidth($txt2); // If the line has to be wrapped if ($lw > $wrapWidthRemaining) { - $words = explode(' ', $line); + $words = explode(' ', $txt2); $addspace = count($words); $lw = 0; foreach ($words as $word) { diff --git a/app/Report/ReportPdfTextBox.php b/app/Report/ReportPdfTextBox.php index 48130940783..60eec60445a 100644 --- a/app/Report/ReportPdfTextBox.php +++ b/app/Report/ReportPdfTextBox.php @@ -129,13 +129,37 @@ public function render($renderer): void $cX = $renderer->addMarginX($this->left); } + $cE = count($this->elements); + if ($cE>0) + error_log("\npdfTB start: [cE,element[1]]=". + var_export([$cE,$this->elements[0]],true)."\n",3,"pdf_tb.log"); + // If current position (top) - if ($this->top === ReportBaseElement::CURRENT_POSITION) { + $align_Y = false; + $curr_P = $renderer->tcpdf->getPage(); + //$first_Y = $renderer->tcpdf->GetY(); + error_log("\npdfTB1: [top,curr_P,abs_position,top_position,GetY()]=". + var_export([$this->top,$curr_P,$this->abs_position,$this->top_position,$renderer->tcpdf->GetY()],true)."\n",3,"pdf_tb.log"); + if ($this->abs_position) { + $this->top_position = 0; + $align_Y = true; + if ($this->top != ReportBaseElement::CURRENT_POSITION) { + $renderer->tcpdf->setY($this->top); + //} else { + // $renderer->tcpdf->setY($first_Y); + } $cY = $renderer->tcpdf->GetY(); } else { - $cY = $this->top; - $renderer->tcpdf->setY($cY); + $align_Y = true; + $this->top = $renderer->tcpdf->GetY(); // ReportBaseElement::CURRENT_POSITION; + if ($this->top_position != 0) { + $this->top += $this->top_position; + } + $cY = $renderer->tcpdf->GetY(); } + $start_Y = $renderer->tcpdf->GetY(); + error_log("\npdfTB2: [top,curr_P,top_position,start_Y]=". + var_export([$this->top,$curr_P,$this->top_position,$start_Y],true)."\n",3,"pdf_tb.log"); // Check the width if set to page wide OR set by xml to larger then page width (margin) if ($this->width === 0.0 || $this->width > $renderer->getRemainingWidthPDF()) { @@ -222,7 +246,11 @@ public function render($renderer): void if ($cH < $renderer->lastCellHeight) { $cH = $renderer->lastCellHeight; } + if ($cH < $this->height) { + $cH = $this->height; + } // Add a new page if needed + $cPN = $renderer->tcpdf->getPage(); if ($this->pagecheck) { // Reset last cell height or Header/Footer will inherit it, in case of pagebreak $renderer->lastCellHeight = 0; @@ -230,6 +258,8 @@ public function render($renderer): void $cY = $renderer->tcpdf->GetY(); } } + $curr_P = $renderer->tcpdf->getPage(); + $min_Y = $cY + $cH; // Setup the border and background color $cS = ''; // Class Style @@ -293,11 +323,18 @@ public function render($renderer): void } // Save the current page number $cPN = $renderer->tcpdf->getPage(); + $start_P = $cPN; // Render the elements (write text, print picture...) foreach ($this->elements as $element) { if ($element instanceof ReportBaseElement) { + $cPT = $renderer->tcpdf->getPage(); + $cMT = $renderer->tcpdf->getMargins(); $element->render($renderer); + // If tcpdf has added a new page the left margin must be restored + if ($cPT != $renderer->tcpdf->getPage()) { + $renderer->tcpdf->setLeftMargin($cMT['left']); + } } elseif ($element === 'footnotetexts') { $renderer->footnotes(); } elseif ($element === 'addpage') { @@ -309,6 +346,7 @@ public function render($renderer): void $renderer->tcpdf->setRightMargin($cM['right']); // This will be mostly used to trick the multiple images last height + if ($this->reseth) { $cH = 0; // This can only happen with multiple images and with pagebreak @@ -316,14 +354,22 @@ public function render($renderer): void $renderer->tcpdf->setPage($cPN); } } + $curr_Y = $renderer->tcpdf->GetY(); + $curr_P = $renderer->tcpdf->getPage(); // New line and some clean up if (!$this->newline) { - $renderer->tcpdf->setXY($cX + $cW, $cY); + $renderer->tcpdf->setXY($cX + $cW, $cY); // $curr_Y); $renderer->lastCellHeight = $cH; } else { // addMarginX() also updates X $renderer->addMarginX(0); - $renderer->tcpdf->setY($cY + $cH); + //$renderer->tcpdf->setY($cY + $cH); + if ($align_Y && ($curr_Y < $min_Y) && ($curr_P == $start_P)) { + $renderer->tcpdf->setY($min_Y); + } else { + // 15 is good enough, should be a more general value ($cH is too large!) + $renderer->tcpdf->setY($curr_Y + ($cH > 15 ? 15 : $cH)); + } $renderer->lastCellHeight = 0; } } diff --git a/resources/xml/reports/bdm_report.xml b/resources/xml/reports/bdm_report.xml index 9e610d21f64..6cc2eb83ec7 100644 --- a/resources/xml/reports/bdm_report.xml +++ b/resources/xml/reports/bdm_report.xml @@ -45,7 +45,7 @@ - + @@ -53,7 +53,7 @@ - + @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + @@ -101,6 +101,8 @@ + + @@ -109,12 +111,15 @@ + - - -
-
+ + +
+ +

+
:
@@ -122,7 +127,7 @@
- + @@ -144,7 +149,7 @@ - + @@ -182,7 +187,7 @@ - + diff --git a/resources/xml/reports/birth_report.xml b/resources/xml/reports/birth_report.xml index b5a2d317127..32d8a94a9e4 100644 --- a/resources/xml/reports/birth_report.xml +++ b/resources/xml/reports/birth_report.xml @@ -12,6 +12,7 @@ --> +