fix: 修复报价确认单 PDF 方框乱码 (#993)
This commit is contained in:
parent
d98faf851a
commit
dffc01dc51
@ -43,7 +43,9 @@
|
||||
<body>
|
||||
@php
|
||||
$mixedText = static function ($value): string {
|
||||
$parts = preg_split('/([\x20-\x7E]+)/', (string) $value, -1, PREG_SPLIT_DELIM_CAPTURE) ?: [];
|
||||
// Spreadsheet-pasted whitespace has no glyph in the CJK font.
|
||||
$text = str_replace(["\r\n", "\r", "\t", "\u{00A0}"], ["\n", "\n", ' ', ' '], (string) $value);
|
||||
$parts = preg_split('/([\x20-\x7E]+)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE) ?: [];
|
||||
|
||||
return implode('', array_map(static function (string $part): string {
|
||||
$escaped = e($part);
|
||||
@ -57,7 +59,7 @@
|
||||
<header class="heading">
|
||||
<div class="heading__title">
|
||||
<h1>报价确认单</h1>
|
||||
<p>{!! $mixedText($version->carrier_name) !!} · {!! $mixedText($version->quote_type) !!}</p>
|
||||
<p>{!! $mixedText($version->carrier_name) !!} <span class="latin">·</span> {!! $mixedText($version->quote_type) !!}</p>
|
||||
</div>
|
||||
<div class="heading__mark">不可覆盖版本:{!! $mixedText($version->version_code) !!}</div>
|
||||
</header>
|
||||
@ -140,7 +142,7 @@
|
||||
<h2>签署确认</h2>
|
||||
<div class="signature__meta">报价版本:{!! $mixedText($version->version_code) !!} 快递公司:{!! $mixedText($version->carrier_name) !!} 执行日期:{!! $mixedText($version->effective_date?->toDateString()) !!}</div>
|
||||
<div class="signature__box"><div>乙方(快递方)签章:{!! $mixedText($version->carrier_name) !!}</div><div class="signature__blank"></div><div>签署日期:{!! $mixedText($signatureDate) !!}</div></div>
|
||||
<div class="document-control">生成时间:{!! $mixedText($generatedAt->format('Y-m-d H:i:s')) !!} · 版本 {!! $mixedText($version->version_code) !!}</div>
|
||||
<div class="document-control">生成时间:{!! $mixedText($generatedAt->format('Y-m-d H:i:s')) !!} <span class="latin">·</span> 版本 {!! $mixedText($version->version_code) !!}</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
99
tests/Unit/CourierQuotePdfWhitespaceTest.php
Normal file
99
tests/Unit/CourierQuotePdfWhitespaceTest.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Models\Business\CourierQuoteVersion;
|
||||
use DOMElement;
|
||||
use Dompdf\FrameDecorator\Text;
|
||||
use FontLib\Font;
|
||||
use Illuminate\Foundation\Testing\TestCase;
|
||||
use Tests\CreatesApplication;
|
||||
|
||||
class CourierQuotePdfWhitespaceTest extends TestCase
|
||||
{
|
||||
use CreatesApplication;
|
||||
|
||||
public function test_pasted_quote_rules_render_without_missing_glyphs_and_keep_separate_lines(): void
|
||||
{
|
||||
$html = view('business.pdf.courier-quote', [
|
||||
'version' => new CourierQuoteVersion([
|
||||
'version_code' => 'QB-TEST', 'carrier_name' => '示例快递',
|
||||
'quote_type' => '普通报价', 'effective_date' => '2026-11-01',
|
||||
]),
|
||||
'change' => null,
|
||||
'values' => [['地区', '费用'], ['江苏', '6.50']],
|
||||
'formulas' => [],
|
||||
'partyA' => '示例甲方', 'expiryDate' => '长期',
|
||||
'faceFee' => '0.50', 'bubbleCoefficient' => '1.2',
|
||||
'billingRules' => "1、\u{00A0}计费重 > 0.5kg,费用6.50元。\t\t\t\r\n2、\u{00A0}轻抛比1:10000。\t\t\t\r3、保留末行。",
|
||||
'signatureDate' => '2026年11月7日', 'generatedAt' => now(),
|
||||
'fontPath' => str_replace('\\', '/', resource_path('fonts/DroidSansFallback.ttf')),
|
||||
])->render();
|
||||
|
||||
$pdf = app('dompdf.wrapper');
|
||||
$frames = [];
|
||||
$pdf->getDomPDF()->setCallbacks([[
|
||||
'event' => 'end_frame',
|
||||
'f' => static function ($frame) use (&$frames): void {
|
||||
if (! $frame instanceof Text) {
|
||||
return;
|
||||
}
|
||||
|
||||
$isSummary = false;
|
||||
for ($node = $frame->get_node()->parentNode; $node; $node = $node->parentNode) {
|
||||
if ($node instanceof DOMElement && $node->getAttribute('class') === 'summary') {
|
||||
$isSummary = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$frames[] = [
|
||||
'text' => $frame->get_text(),
|
||||
'font' => $frame->get_style()->font_family,
|
||||
'y' => $frame->get_position()['y'],
|
||||
'summary' => $isSummary,
|
||||
];
|
||||
},
|
||||
]]);
|
||||
$pdf->setPaper('a4', 'landscape');
|
||||
$pdf->loadHTML($html, 'UTF-8');
|
||||
$this->assertStringStartsWith('%PDF-', $pdf->output());
|
||||
$this->assertNotEmpty($frames);
|
||||
|
||||
$characterMaps = [];
|
||||
foreach ($frames as $frame) {
|
||||
if (! isset($characterMaps[$frame['font']])) {
|
||||
$font = Font::load($frame['font'] . '.ttf');
|
||||
$font->parse();
|
||||
$characterMaps[$frame['font']] = $font->getUnicodeCharMap();
|
||||
$font->close();
|
||||
}
|
||||
foreach (mb_str_split($frame['text']) as $character) {
|
||||
if ($character === "\n" || $character === "\r") {
|
||||
continue;
|
||||
}
|
||||
$codepoint = mb_ord($character);
|
||||
$this->assertNotEmpty(
|
||||
$characterMaps[$frame['font']][$codepoint] ?? null,
|
||||
sprintf('Quote PDF would display a missing glyph for U+%04X.', $codepoint)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$ruleFrames = array_values(array_filter($frames, fn ($frame) => $frame['summary']));
|
||||
$renderedText = implode('', array_column($ruleFrames, 'text'));
|
||||
$this->assertStringContainsString('计费重 > 0.5kg,费用6.50元。', $renderedText);
|
||||
$this->assertStringContainsString('轻抛比1:10000。', $renderedText);
|
||||
$this->assertStringContainsString('保留末行。', $renderedText);
|
||||
$lineY = [];
|
||||
foreach ($ruleFrames as $frame) {
|
||||
foreach (['1', '2', '3'] as $number) {
|
||||
if (str_starts_with($frame['text'], $number)) {
|
||||
$lineY[$number] ??= $frame['y'];
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->assertCount(3, $lineY);
|
||||
$this->assertGreaterThan($lineY['1'], $lineY['2']);
|
||||
$this->assertGreaterThan($lineY['2'], $lineY['3']);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user