report-print-pdf/SKILL.md
Guidance for building report templates that serve both mPDF exports and the browser-based print workflow, including the auto-print standard introduced in the report-printing-style guide.
npx skillsauth add peterbamuhigire/skills-web-dev report-print-pdfInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
report-print-pdf or would be better handled by a more specific companion skill.SKILL.md first, then load only the referenced deep-dive files that are necessary for the task.Build a single, minimal HTML report that renders consistently in mPDF and browser print (HTML → print dialog). Keep typography compact, tables pagination-friendly, and metadata consistent across outputs.
Always load and apply the Vibe Security Skill when reports touch user input, filters, file paths, or authentication. Treat output encoding and access control as mandatory.
Cross-Platform: Reports deploy to Windows dev, Ubuntu staging, and Debian production. Use forward-slash paths (/) for file references. Use sys_get_temp_dir() for temporary PDF files. Ensure font paths work on both OS (use relative paths from project root).
Prepare data and metadata
23 Aug, 2025)d F Y, h:i A (e.g., 23 September 2025, 05:36 PM)2026-01-25 00:00:00). Always format dates as d M Y or d F Y, and include time only when explicitly required.use DateTime (it has no effect) or other global symbol use statements; just instantiate the built-in class via new \DateTime() so your files stay warning-free.Build minimal HTML (shared by PDF + Print)
thead/tbody and subtle bordersRender with mPDF
Render with browser print
text/html)window.print() when readyfinal class ReportFormatting {
public static function formatDateShort(?string $value): string {
if (empty($value)) {
return '-';
}
return (new DateTime(substr($value, 0, 10)))->format('d M Y');
}
public static function formatDateLong(?string $value): string {
if (empty($value)) {
return '-';
}
return (new DateTime(substr($value, 0, 10)))->format('d F Y');
}
public static function formatPeriod(?string $startDate, ?string $endDate): string {
if (empty($startDate) && empty($endDate)) {
return '';
}
$start = $startDate ? (new DateTime($startDate))->format('d M, Y') : '';
$end = $endDate ? (new DateTime($endDate))->format('d M, Y') : '';
if ($start && $end) {
return $start === $end ? $start : "$start - $end";
}
return $start ?: $end;
}
public static function formatPrintedOn(DateTime $date): string {
return $date->format('d F Y, h:i A');
}
public static function formatNumber($value): string {
if ($value === null || $value === '') {
return '0';
}
$num = (float)$value;
$isWhole = abs($num - round($num)) < 0.00001;
return $isWhole
? number_format($num, 0, '.', ',')
: number_format($num, 2, '.', ',');
}
}
final class ReportHtmlTemplate {
public static function build(
array $meta,
string $tableHtml,
string $summaryHtml = '',
bool $includeFooter = false,
string $printedBy = '',
string $printedOn = ''
): string {
$orgName = htmlspecialchars($meta['org_name'] ?? '');
$orgAddress = htmlspecialchars($meta['org_address'] ?? '');
$reportTitle = htmlspecialchars($meta['title'] ?? '');
$periodLabel = htmlspecialchars($meta['period'] ?? '');
$logoPath = htmlspecialchars($meta['logo_path'] ?? '');
$periodHtml = $periodLabel !== ''
? '<div class="period">Period: ' . $periodLabel . '</div>'
: '';
$footerHtml = $includeFooter
? '<div class="print-footer">'
. '<div class="print-left">Printed By: ' . htmlspecialchars($printedBy) . '</div>'
. '<div class="print-right">Printed On ' . htmlspecialchars($printedOn) . '</div>'
. '</div>'
: '';
return <<<HTML
<style>
body { font-family: dejavu sans; font-size: 10px; color: #111; }
.report-header { width: 100%; border-bottom: 1px solid #e6e6e6; padding-bottom: 6px; }
.header-row { display: table; width: 100%; }
.header-cell { display: table-cell; vertical-align: middle; }
.logo { width: 54px; height: 54px; }
.org-name { font-size: 13px; font-weight: bold; margin-bottom: 2px; }
.org-address { font-size: 9px; color: #555; }
.report-title { font-size: 12px; font-weight: 600; text-align: right; }
.period { font-size: 9px; color: #555; text-align: right; margin-top: 2px; }
.section-gap { margin-top: 8px; }
table { width: 100%; border-collapse: collapse; margin-top: 6px; }
thead { display: table-header-group; }
th { background: #f7f7f7; font-weight: 600; border: 1px solid #e5e5e5; padding: 5px; }
td { border: 1px solid #e5e5e5; padding: 5px; }
.text-right { text-align: right; }
.text-center { text-align: center; }
.print-footer { width: 100%; font-size: 7px; color: #555; margin-top: 8px; }
.print-left { float: left; }
.print-right { float: right; }
@media print {
body { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.print-footer { position: fixed; bottom: 8px; left: 0; right: 0; }
.section-gap { margin-top: 6px; }
tr, td, th { page-break-inside: avoid; }
}
</style>
<div class="report-header">
<div class="header-row">
<div class="header-cell" style="width: 60px;">
<img class="logo" src="{$logoPath}" alt="Logo" />
</div>
<div class="header-cell">
<div class="org-name">{$orgName}</div>
<div class="org-address">{$orgAddress}</div>
</div>
<div class="header-cell" style="text-align: right;">
<div class="report-title">{$reportTitle}</div>
{$periodHtml}
</div>
</div>
</div>
<div class="section-gap">{$summaryHtml}</div>
{$tableHtml}
{$footerHtml}
HTML;
}
}
thead + tbodythead to display: table-header-group for repeating headerspage-break-inside: avoid)$mpdf = new \Mpdf\Mpdf([
'format' => 'A4',
'margin_left' => 10,
'margin_right' => 10,
'margin_top' => 38,
'margin_bottom' => 18,
'default_font' => 'dejavusans'
]);
$footerHtml = '<div style="width:100%; font-size:7px; color:#555;">'
. '<div style="float:left;">Printed By: ' . htmlspecialchars($printedBy) . '</div>'
. '<div style="float:right;">Printed On ' . ReportFormatting::formatPrintedOn(new DateTime()) . '</div>'
. '</div>';
$mpdf->SetHTMLHeader('');
$mpdf->SetHTMLFooter($footerHtml);
$mpdf->WriteHTML(ReportHtmlTemplate::build($meta, $tableHtml, $summaryHtml));
$mpdf->Output($fileName, 'I');
text/htmlwindow.print()thead, small footer)*-print.php), append this snippet before </body> and keep docs/report-printing-standard.md in sync:<script>
window.addEventListener("DOMContentLoaded", function () {
setTimeout(function () {
window.print();
}, 400);
});
</script>
no-print helpers for reprint actions so the dialog can be reopened without a full refresh.async function openReportPrint(url, params) {
const query = new URLSearchParams(params).toString();
const html = await fetch(`${url}?${query}`, { credentials: "include" }).then(
(r) => r.text(),
);
const printWindow = window.open("", "_blank");
printWindow.document.open();
printWindow.document.write(html);
printWindow.document.close();
printWindow.onload = () => {
printWindow.focus();
printWindow.print();
};
}
header('Content-Type: text/html; charset=utf-8');
$meta = [
'org_name' => $org->name,
'org_address' => $org->address,
'logo_path' => $org->logo_path,
'title' => $reportTitle,
'period' => ReportFormatting::formatPeriod($startDate, $endDate)
];
$printedOn = ReportFormatting::formatPrintedOn(new DateTime());
$printedBy = $currentUser->name;
echo ReportHtmlTemplate::build(
$meta,
$tableHtml,
$summaryHtml,
true,
$printedBy,
$printedOn
);
Use this skill to produce consistent, clean, minimal PDF exports and browser-printed reports with the same HTML: compact header/footer, repeatable table headers, strict number/date formatting, and tight spacing for professional multi-page output.
data-ai
Use when adding AI-powered analytics to a SaaS platform — semantic search over business data, natural language queries, trend detection, anomaly alerts, and AI-generated insights for dashboards. Covers embeddings, NL2SQL, and per-tenant analytics...
data-ai
Design AI-powered analytics dashboards — what metrics to show, how to display AI predictions and confidence, drill-down patterns, KPI cards, trend visualisation, AI Insights panels, export design, and role-based dashboard variants. Invoke when...
development
Use when designing, building, reviewing, or upgrading production software systems that must be secure, performant, maintainable, scalable, and user-centered. Apply before writing specs, code, architecture, APIs, databases, mobile apps, SaaS platforms, or ERP systems.
development
Professional web app UI using commercial templates (Tabler/Bootstrap 5) with strong frontend design direction when needed. Use for CRUD interfaces, dashboards, admin panels with SweetAlert2, DataTables, Flatpickr. Clone seeder-page.php, use...