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 => '授权委托书', }; } }