CaiWuXiTong/app/Support/CourierAgreementPdf.php

98 lines
3.9 KiB
PHP

<?php
namespace App\Support;
use App\Models\Business\CourierAgreementSignature;
use DOMDocument;
use DOMXPath;
use Dompdf\FrameReflower\Text;
use Illuminate\Support\Carbon;
use RuntimeException;
final class CourierAgreementPdf
{
public function render(CourierAgreementSignature $signature): string
{
$payload = $signature->payload ?? [];
$signatureDate = (string) ($payload['signature_date'] ?? $payload['entity_proof_date'] ?? $payload['shared_proof_date'] ?? '');
try {
$signatureDate = $signatureDate !== '' ? Carbon::parse($signatureDate)->format('Y年n月j日') : '待填写';
} catch (\Throwable) {
$signatureDate = '待填写';
}
$supplierContract = app(SupplierCooperationTemplates::class)->isSupplierContract($signature);
$html = view($supplierContract ? 'business.pdf.supplier-cooperation-contract' : 'business.pdf.courier-agreement', [
'signature' => $signature,
'type' => $signature->document_type,
'title' => $this->title($signature),
'payload' => $payload,
'signatureDate' => $signatureDate,
'fontPath' => str_replace('\\', '/', resource_path('fonts/DroidSansFallback.ttf')),
])->render();
$pdf = app('dompdf.wrapper');
$pdf->setPaper('a4', 'portrait');
$pdf->loadHTML($html, 'UTF-8');
$this->applyLatinFont($pdf->getDomPDF()->getDom());
// Dompdf otherwise treats uninterrupted Chinese text as one word,
// moving a whole clause to the next line after its Latin number.
$wordBreakPattern = Text::$_wordbreak_pattern;
Text::$_wordbreak_pattern = '/([^\S\xA0\x{202F}\x{2007}\n]+|\R|\-+|\xAD+|(?<=[\p{Han}\x{3000}-\x{303F}\x{FF00}-\x{FFEF}])(?![,。、;:?!)】》]))/u';
try {
$bytes = $pdf->output();
} finally {
Text::$_wordbreak_pattern = $wordBreakPattern;
}
if (! is_string($bytes) || ! str_starts_with($bytes, '%PDF-')) {
throw new RuntimeException('协议 PDF 生成失败。');
}
return $bytes;
}
private function applyLatinFont(DOMDocument $document): void
{
// DroidSansFallback has no ASCII or typographic quote glyphs. Static
// clauses need the Latin font too, or glyph widths and layout diverge.
$nodes = (new DOMXPath($document))->query(
'//body//text()[not(ancestor::style or ancestor::script or ancestor::*[contains(concat(" ", normalize-space(@class), " "), " latin ")])]'
);
foreach ($nodes as $node) {
if (preg_match('/[\x21-\x7E\x{2018}-\x{201F}]/u', $node->nodeValue) !== 1) {
continue;
}
$parts = preg_split('/([\x20-\x7E\x{2018}-\x{201F}]+)/u', $node->nodeValue, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
$fragment = $document->createDocumentFragment();
foreach ($parts as $part) {
$text = $document->createTextNode($part);
if (preg_match('/^[\x20-\x7E\x{2018}-\x{201F}]+$/u', $part) === 1) {
$span = $document->createElement('span');
$span->setAttribute('class', 'latin');
$span->appendChild($text);
$fragment->appendChild($span);
} else {
$fragment->appendChild($text);
}
}
$node->parentNode->replaceChild($fragment, $node);
}
}
private function title(CourierAgreementSignature $signature): string
{
return match ($signature->document_type) {
'account-proof' => '收款帐户资料证明',
'account-change' => '收款帐户资料变更证明',
'entity-change' => '公司主体变更证明',
'shared-contract' => '证明',
'cooperation-contract' => '服务协议',
default => '授权委托书',
};
}
}