From 524a991098822c8ec088109e8acf6daa91cad594 Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Sun, 27 Sep 2026 13:40:35 +0530 Subject: [PATCH] fix(opd): preserve template settings and flow while compacting font sizes for large prescriptions --- app/Controllers/Opd.php | 182 ++++------------------ app/Views/billing/opd_letter_head_pdf.php | 25 +-- 2 files changed, 44 insertions(+), 163 deletions(-) diff --git a/app/Controllers/Opd.php b/app/Controllers/Opd.php index 118af28..a50caeb 100644 --- a/app/Controllers/Opd.php +++ b/app/Controllers/Opd.php @@ -2199,7 +2199,7 @@ class Opd extends BaseController $tokenVars = $this->buildOpdPdfTokenVars($data); $html = $this->applyOpdPdfTemplateTokens($customHtml, $tokenVars); $html = $this->sanitizeOpdNamedHeaderFooterReferences($html); - $html = $this->applyOpdLongContentPaginationGuard($html, 8.0, 210.0); + $html = $this->applyOpdLongContentPaginationGuard($html); $html = mpdf_normalize_font_weight_css($html); if ($debugHtml) { @@ -2407,8 +2407,7 @@ class Opd extends BaseController $rawFooterHtml = ''; } - $resolvedPaperWidthMm = $format === 'LETTER' ? 215.9 : ($format === 'A5' ? 148.0 : ($format === 'CUSTOM' ? (float) $customWidthMm : 210.0)); - $html = $this->applyOpdLongContentPaginationGuard($html, (float) $marginTop, $resolvedPaperWidthMm); + $html = $this->applyOpdLongContentPaginationGuard($html); $html = mpdf_normalize_font_weight_css($html); if ($rawHeaderHtml !== '') { @@ -2760,23 +2759,19 @@ class Opd extends BaseController $medicalHtml = ''; if (!empty($rxMeds)) { - $medCount = count($rxMeds); - // Idea 1: Dynamic compact styling for prescriptions with >= 6 medicines - // Strictly enforce readability floor: font-size must never drop below 9.5px (or 8.5px for Hindi subtext). - $isCompactMeds = ($medCount >= 6); - $tblFontSize = $isCompactMeds ? '10px' : '12px'; - $tblCellPadding = $isCompactMeds ? '2.5px 5px' : '6px 8px'; - $tblHeaderPadding = $isCompactMeds ? '3px 5px' : '6px 8px'; - $tblMarginY = $isCompactMeds ? '2px' : '5px'; + $isCompactMeds = count($rxMeds) >= 8; + $tableFontSize = $isCompactMeds ? '10px' : '12px'; + $cellPadding = $isCompactMeds ? '2.5px 5px' : '6px 8px'; + $tableMargin = $isCompactMeds ? '2px' : '5px'; $genericFontSize = $isCompactMeds ? '8.5px' : '10px'; - $hindiFontSize = $isCompactMeds ? '8.5px' : '11px'; - $hindiLineHeight = $isCompactMeds ? '1.22' : '1.4'; + $localFontSize = $isCompactMeds ? '8.5px' : '11px'; + $localLineHeight = $isCompactMeds ? '1.2' : '1.4'; - $medicalHtml .= ''; + $medicalHtml .= '
'; $medicalHtml .= '' - . '' - . '' - . '' + . '' + . '' + . '' . ''; $whenCodeDescMap = [ @@ -3041,11 +3036,11 @@ class Opd extends BaseController $localDirectionsText = !empty($dirLocalParts) ? implode(' | ', array_values(array_unique($dirLocalParts))) : ''; $medicalHtml .= '' - . '' - . '' - . '' + . '' + . '' . ''; } @@ -3952,15 +3947,11 @@ class Opd extends BaseController } $rxMeds = is_array($data['rx_medicines'] ?? null) ? $data['rx_medicines'] : []; - $isCompactMeds = count($rxMeds) >= 6; + $isCompactMeds = count($rxMeds) >= 8; $headingStyleMode = (string) ($this->opdPlaceholderRenderOptions['heading_style'] ?? 'bold'); - $lineGap = (float) ($this->opdPlaceholderRenderOptions['line_gap'] ?? 1.6); - $blockGapMm = (float) ($this->opdPlaceholderRenderOptions['block_gap_mm'] ?? 2.8); - if ($isCompactMeds) { - $lineGap = min($lineGap, 1.35); - $blockGapMm = min($blockGapMm, 1.5); - } + $lineGap = (float) ($this->opdPlaceholderRenderOptions['line_gap'] ?? ($isCompactMeds ? 1.35 : 1.6)); + $blockGapMm = (float) ($this->opdPlaceholderRenderOptions['block_gap_mm'] ?? ($isCompactMeds ? 1.5 : 2.8)); $lineGapCss = number_format($lineGap, 2, '.', ''); $blockGapCss = number_format($blockGapMm, 2, '.', ''); @@ -4671,7 +4662,7 @@ class Opd extends BaseController return $html; } - private function applyOpdLongContentPaginationGuard(string $html, float $pageMarginTopMm = 10.0, float $paperWidthMm = 210.0): string + private function applyOpdLongContentPaginationGuard(string $html): string { if ($html === '') { return ''; @@ -4687,138 +4678,21 @@ class Opd extends BaseController $lineBreakCount = substr_count(strtolower($html), '= 8 rows, >= 18 line breaks, or text length > 900 + // When content has 8 or more rows, or substantial length, apply compact font size CSS only. + // Flow the setting naturally: never alter .RxPlace position, top, left, width, or page margins. $looksLong = $rowCount >= 8 || $lineBreakCount >= 18 || $textLength > 900; if (! $looksLong) { return $html; } - // Parse print area dimensions (top, left, width) from .RxPlace CSS - $dim = [ - 'top' => 90.0, - 'left' => 10.0, - 'width' => 195.0, - ]; - - $convertMm = static function (float $val, string $unit, float $baseMm = 210.0): float { - $unit = strtolower(trim($unit)); - if ($unit === 'cm') { - return $val * 10.0; - } - if ($unit === 'in') { - return $val * 25.4; - } - if ($unit === 'px') { - return $val * 0.264583; - } - if ($unit === 'pt') { - return $val * 0.352778; - } - if ($unit === '%') { - return ($val / 100.0) * $baseMm; - } - return $val; // default mm - }; - - if (preg_match('/(\.(?:RxPlace|rxplace)\s*\{[^}]+\})/is', $html, $blockMatch)) { - $body = $blockMatch[1]; - if (preg_match('/(?:^|[;\{\s])top\s*:\s*([0-9\.]+)\s*(mm|cm|px|in|pt|%)?/i', $body, $m)) { - $dim['top'] = $convertMm((float) $m[1], $m[2] ?? 'mm', 297.0); - } - if (preg_match('/(?:^|[;\{\s])left\s*:\s*([0-9\.]+)\s*(mm|cm|px|in|pt|%)?/i', $body, $m)) { - $dim['left'] = $convertMm((float) $m[1], $m[2] ?? 'mm', $paperWidthMm); - } - if (preg_match('/(?:^|[;\{\s])width\s*:\s*([0-9\.]+)\s*(mm|cm|px|in|pt|%)?/i', $body, $m)) { - $unit = $m[2] ?? 'mm'; - if ($unit !== '%' || (float) $m[1] < 99.0) { - $dim['width'] = $convertMm((float) $m[1], $unit, $paperWidthMm); - } - } - } - - // Sanitize print area and calculate right margin to preserve width ratio - $leftMm = max(0.0, min($paperWidthMm - 30.0, $dim['left'])); - $widthMm = max(30.0, min($paperWidthMm - $leftMm, $dim['width'])); - $rightMarginMm = max(0.0, $paperWidthMm - $leftMm - $widthMm); - - // Check existing @page margin-top in document - $existingPageTopMm = 0.0; - if (preg_match_all('/@page\s*\{[^}]*?margin-top\s*:\s*([0-9\.]+)\s*(mm|cm|px|in|pt)?/i', $html, $allTm, PREG_SET_ORDER)) { - $lastTm = end($allTm); - $existingPageTopMm = $convertMm((float) $lastTm[1], $lastTm[2] ?? 'mm', 297.0); - } - if ($existingPageTopMm <= 0.0 && $pageMarginTopMm > 0.0) { - $existingPageTopMm = $pageMarginTopMm; - } - - // Top clearance spacer height on Page 1 (only the delta needed to reach the template's target top) - $spacerHeightMm = max(0.0, $dim['top'] - $existingPageTopMm); - - // In-place update margin-left and margin-right in any existing @page blocks - if (preg_match('/@page\s*\{/i', $html)) { - if (preg_match('/(@page\s*\{[^}]*?margin-left\s*:\s*)([^;\}]+)/is', $html)) { - $html = preg_replace('/(@page\s*\{[^}]*?margin-left\s*:\s*)([^;\}]+)/is', '${1}' . round($leftMm, 1) . 'mm', $html); - } - if (preg_match('/(@page\s*\{[^}]*?margin-right\s*:\s*)([^;\}]+)/is', $html)) { - $html = preg_replace('/(@page\s*\{[^}]*?margin-right\s*:\s*)([^;\}]+)/is', '${1}' . round($rightMarginMm, 1) . 'mm', $html); - } - } - - // Neutralize position: absolute inside .RxPlace declaration directly in existing style blocks - // (mPDF strips !important, so replacing the rule in-place ensures it does not override) - $html = preg_replace( - '/(\.(?:RxPlace|rxplace)[^{]*\{)([^}]+)(\})/is', - '$1 position: static; width: 100%; max-width: 100%; margin: 0; padding: 0; $3', - $html - ); - - // Insert top clearance spacer right before .RxPlace so Page 1 reserves space for letterhead, - // but subsequent pages start cleanly at the top of the continuation sheet - if ($spacerHeightMm > 0.5) { - $topSpacer = '
 
'; - $html = preg_replace('/()/i', $topSpacer . "\n$1", $html, 1); - } - - // Preserve existing named header/footer references if present in the document - $extraPageRules = ''; - if (preg_match('/(?' - . '@page {' - . 'margin-left: ' . round($leftMm, 1) . 'mm !important;' - . 'margin-right: ' . round($rightMarginMm, 1) . 'mm !important;' - . $extraPageRules - . '}' - . '.RxPlace, .rxplace {' - . 'position: static !important;' - . 'top: auto !important;' - . 'left: auto !important;' - . 'right: auto !important;' - . 'bottom: auto !important;' - . 'width: 100% !important;' - . 'max-width: 100% !important;' - . 'height: auto !important;' - . 'max-height: none !important;' - . 'overflow: visible !important;' - . 'margin: 0 !important;' - . 'padding: 0 !important;' - . '}' - . 'table { page-break-inside: auto !important; width: 100% !important; }' - . 'tr, td, th { page-break-inside: avoid !important; }' - . 'thead { display: table-header-group !important; }' - . 'br { margin-bottom: 2px !important; }' - . 'table p { margin-bottom: 3px !important; }' + $guardCss = ''; - return $html . $guardCss; + return $html . "\n" . $guardCss; } /** diff --git a/app/Views/billing/opd_letter_head_pdf.php b/app/Views/billing/opd_letter_head_pdf.php index 2d567f0..20940d6 100644 --- a/app/Views/billing/opd_letter_head_pdf.php +++ b/app/Views/billing/opd_letter_head_pdf.php @@ -345,12 +345,19 @@ $toLocalText = static function (string $input): string {
Rx :
-
#MedicineDirections#MedicineDirections
' . $i . '' . $nameHtml . '' + . '' . $i . '' . $nameHtml . '' . '
' . esc($directionsText) . '
' - . ($localDirectionsText !== '' ? ('
' . esc($localDirectionsText) . '
') : '') + . ($localDirectionsText !== '' ? ('
' . esc($localDirectionsText) . '
') : '') . '
+ = 8; + $cellPadding = $isCompactMeds ? '2.5px 5px' : '6px 8px'; + $genericFontSize = $isCompactMeds ? '8.5px' : '9.5px'; + $localFontSize = $isCompactMeds ? '8.5px' : '9.5px'; + $localLineHeight = $isCompactMeds ? '1.2' : '1.4'; + ?> +
- - - + + + @@ -459,17 +466,17 @@ $toLocalText = static function (string $input): string { $localDirectionsText = !empty($dirLocalParts) ? implode(' | ', array_values(array_unique($dirLocalParts))) : ''; ?> - - + -
#MedicineDirections#MedicineDirections
+ -
+
+
-
+