Files
anxinyan/server-api/app/support/ReportPdfGenerator.php
2026-05-22 13:31:02 +08:00

235 lines
8.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\support;
class ReportPdfGenerator
{
public function generate(array $payload): string
{
$heroImage = $this->normalizeHeroImage($payload['hero_image'] ?? null);
$content = $this->buildContentStream($payload, $heroImage);
$resources = '<< /Font << /F1 5 0 R >>';
if ($heroImage) {
$resources .= ' /XObject << /Im1 7 0 R >>';
}
$resources .= ' >>';
$objects = [
1 => '<< /Type /Catalog /Pages 2 0 R >>',
2 => '<< /Type /Pages /Kids [3 0 R] /Count 1 >>',
3 => sprintf('<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources %s /Contents 4 0 R >>', $resources),
4 => sprintf("<< /Length %d >>\nstream\n%s\nendstream", strlen($content), $content),
5 => '<< /Type /Font /Subtype /Type0 /BaseFont /STSong-Light /Encoding /UniGB-UCS2-H /DescendantFonts [6 0 R] >>',
6 => '<< /Type /Font /Subtype /CIDFontType0 /BaseFont /STSong-Light /CIDSystemInfo << /Registry (Adobe) /Ordering (GB1) /Supplement 4 >> /DW 1000 >>',
];
if ($heroImage) {
$objects[7] = sprintf(
"<< /Type /XObject /Subtype /Image /Width %d /Height %d /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length %d >>\nstream\n%s\nendstream",
$heroImage['width'],
$heroImage['height'],
strlen($heroImage['data']),
$heroImage['data']
);
}
return $this->renderPdf($objects);
}
private function buildContentStream(array $payload, ?array $heroImage): string
{
$title = $this->normalizeText((string)($payload['report_title'] ?? '鉴定报告'));
$serviceProviderText = $this->normalizeText((string)($payload['service_provider_text'] ?? '-'));
$institutionName = $this->normalizeText((string)($payload['institution_name'] ?? '-'));
$reportNo = $this->normalizeText((string)($payload['report_no'] ?? '-'));
$publishTime = $this->normalizeText((string)($payload['publish_time'] ?? '-'));
$productName = $this->normalizeText((string)($payload['product_name'] ?? '-'));
$verifyInfo = $this->normalizeText((string)($payload['verify_info'] ?? '-'));
$riskNotice = $this->normalizeText((string)($payload['risk_notice_text'] ?? '-'));
$productItems = is_array($payload['product_items'] ?? null) ? $payload['product_items'] : [];
$heroImageLabels = is_array($payload['hero_image_labels'] ?? null) ? $payload['hero_image_labels'] : [];
$blocks = [];
$y = 790;
$blocks[] = $this->textBlock($title, 52, $y, 20);
$y -= 32;
$blocks[] = $this->textBlock('正式报告凭证,请以报告页防伪查询结果为准。', 52, $y, 10);
$y -= 30;
foreach ([
sprintf('报告编号:%s', $reportNo),
sprintf('检测机构:%s', $institutionName),
sprintf('出具时间:%s', $publishTime),
sprintf('服务类型:%s', $serviceProviderText),
] as $line) {
$blocks[] = $this->textBlock($line, 52, $y, 12);
$y -= 22;
}
if ($heroImage) {
$maxWidth = 230;
$maxHeight = 150;
$scale = min($maxWidth / $heroImage['width'], $maxHeight / $heroImage['height'], 1);
$drawWidth = (int)round($heroImage['width'] * $scale);
$drawHeight = (int)round($heroImage['height'] * $scale);
$y -= $drawHeight + 6;
$blocks[] = $this->imageBlock('Im1', 52, $y, $drawWidth, $drawHeight);
$y -= 24;
} elseif ($heroImageLabels) {
foreach ($this->wrapText('鉴定图片:' . implode('、', array_slice($heroImageLabels, 0, 3)), 34) as $line) {
$blocks[] = $this->textBlock($line, 52, $y, 11);
$y -= 18;
}
$y -= 8;
}
$y -= 8;
$blocks[] = $this->textBlock('产品信息', 52, $y, 15);
$y -= 24;
$blocks[] = $this->textBlock(sprintf('产品名称:%s', $productName), 52, $y, 12);
$y -= 22;
foreach ($productItems as $item) {
if (!is_array($item)) {
continue;
}
$label = $this->normalizeText((string)($item['label'] ?? ''));
if ($label === '') {
continue;
}
$value = $this->normalizeText((string)($item['value'] ?? '-'));
$remark = $this->normalizeText((string)($item['remark'] ?? ''));
foreach ($this->wrapText(sprintf('%s%s', $label, $value !== '' ? $value : '-'), 34) as $line) {
$blocks[] = $this->textBlock($line, 52, $y, 11);
$y -= 18;
}
if ($remark !== '') {
foreach ($this->wrapText('说明:' . $remark, 34) as $line) {
$blocks[] = $this->textBlock($line, 72, $y, 10);
$y -= 16;
}
}
$y -= 2;
}
$y -= 6;
$blocks[] = $this->textBlock(sprintf('验真信息:%s', $verifyInfo), 52, $y, 11);
$y -= 22;
$y -= 8;
foreach ($this->wrapText('风险说明:' . $riskNotice, 30) as $line) {
$blocks[] = $this->textBlock($line, 52, $y, 10);
$y -= 17;
}
if ($y > 48) {
$blocks[] = $this->textBlock('安心检验鉴定平台', 52, 42, 9);
}
return implode("\n", array_filter($blocks));
}
private function normalizeHeroImage(mixed $image): ?array
{
if (!is_array($image)) {
return null;
}
$data = (string)($image['data'] ?? '');
$width = (int)($image['width'] ?? 0);
$height = (int)($image['height'] ?? 0);
if ($data === '' || $width <= 0 || $height <= 0) {
return null;
}
return [
'data' => $data,
'width' => $width,
'height' => $height,
];
}
private function wrapText(string $text, int $maxUnits): array
{
$normalized = $this->normalizeText($text);
if ($normalized === '') {
return ['-'];
}
$chars = preg_split('//u', $normalized, -1, PREG_SPLIT_NO_EMPTY) ?: [];
$lines = [];
$current = '';
$width = 0.0;
foreach ($chars as $char) {
$charWidth = strlen($char) === 1 && ord($char) < 128 ? 0.5 : 1.0;
if ($current !== '' && $width + $charWidth > $maxUnits) {
$lines[] = $current;
$current = '';
$width = 0.0;
}
$current .= $char;
$width += $charWidth;
}
if ($current !== '') {
$lines[] = $current;
}
return $lines ?: ['-'];
}
private function textBlock(string $text, int $x, int $y, int $fontSize): string
{
if ($text === '') {
return '';
}
return sprintf(
"BT\n/F1 %d Tf\n1 0 0 1 %d %d Tm\n<%s> Tj\nET",
$fontSize,
$x,
$y,
strtoupper(bin2hex(mb_convert_encoding($text, 'UCS-2BE', 'UTF-8')))
);
}
private function imageBlock(string $name, int $x, int $y, int $width, int $height): string
{
return sprintf("q\n%d 0 0 %d %d %d cm\n/%s Do\nQ", $width, $height, $x, $y, $name);
}
private function normalizeText(string $text): string
{
$text = trim(str_replace(["\r\n", "\r", "\n", "\t"], [' ', ' ', ' ', ' '], $text));
return preg_replace('/\s+/u', ' ', $text) ?: '';
}
private function renderPdf(array $objects): string
{
$pdf = "%PDF-1.4\n%\xE2\xE3\xCF\xD3\n";
$offsets = [];
foreach ($objects as $id => $body) {
$offsets[$id] = strlen($pdf);
$pdf .= sprintf("%d 0 obj\n%s\nendobj\n", $id, $body);
}
$xrefPosition = strlen($pdf);
$pdf .= sprintf("xref\n0 %d\n", count($objects) + 1);
$pdf .= "0000000000 65535 f \n";
foreach ($objects as $id => $_body) {
$pdf .= sprintf("%010d 00000 n \n", $offsets[$id]);
}
$pdf .= sprintf(
"trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%d\n%%%%EOF",
count($objects) + 1,
$xrefPosition
);
return $pdf;
}
}