Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions src/Danfse/HTMLTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,24 @@
<style>
<?= $fontFaces ?>
/* NT008 2.2: 0.15–0.20 cm margins, 1pt perimeter, 0.5pt sections. */
@page { margin: 5pt; }
@page { margin: 0; }
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: Arial, "Liberation Sans", Helvetica, sans-serif;
font-size: 7pt;
line-height: 1.14;
color: #000;
margin: 0;
margin: 5pt;
padding: 1pt 1pt 65pt;
}
/* Fixed page border includes footer and uses the full A4 frame.
This is not a text container border that ends after its content. */
/* Dompdf fixes positioned elements to the physical page. Inset both
the perimeter and content to keep the required printable margins. */
.page-border {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
top: 5pt;
left: 5pt;
right: 5pt;
bottom: 5pt;
border: 1pt solid #000;
z-index: -2;
}
Expand Down Expand Up @@ -66,16 +66,16 @@
.municipality-cell { width: 26%; font-family: "Microsoft Sans Serif", Arial, sans-serif; text-align: left; }
.municipality-name { font-size: 8pt; font-weight: normal; }
.municipality-environment { font-size: 6pt; font-weight: normal; }
.qr-container { text-align: center; padding-top: 8.7pt; }
.qr-container img { position: relative; left: -1.4pt; }
.qr-container { text-align: center; position: relative; top: 3.7pt; }
.qr-container img { position: relative; left: 2.4pt; }
.qr-notice { font-size: 6pt; line-height: 1.15; margin-top: 2pt; text-align: left; }
.access-key { white-space: nowrap; font-size: 7pt; }
.no-party { text-align: center; font-size: 6pt; padding: 2pt; border-bottom: 0.5pt solid #000; }
.no-party div { border-top: 0.5pt solid #222; margin-top: 2pt; padding-top: 2pt; }
.value-highlight { font-weight: bold; }
.total-emphasis { background: #f2f2f2; }
.emitter-cell { background: #f2f2f2; }
.footer-container { position: fixed; bottom: 2pt; left: 1pt; right: 1pt; }
.footer-container { position: fixed; bottom: 7pt; left: 6pt; right: 6pt; }
.footer-table { width: 100%; background: #fff; }
.footer-table td { border: 0.5pt solid #000; padding: 2pt 3pt; }
.footer-table .label { font-size: 6pt; }
Expand Down
9 changes: 7 additions & 2 deletions tests/Unit/Danfse/DanfseGoldenReferenceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,16 @@ public function testNationalPortalStyleUsesCompactGridsAndFooter(): void
self::assertStringContainsString('background: #f2f2f2', $html);
self::assertStringContainsString('border: 1pt solid #000', $html);
self::assertStringContainsString('border-bottom: 0.5pt solid #000', $html);
self::assertStringContainsString('@page { margin: 5pt; }', $html);
self::assertStringContainsString('@page { margin: 0; }', $html);
self::assertStringContainsString('margin: 5pt;', $html);
self::assertStringContainsString('class="page-border"', $html);
self::assertStringContainsString('left: 5pt;', $html);
self::assertStringContainsString('right: 5pt;', $html);
self::assertStringContainsString('top: 5pt;', $html);
self::assertStringContainsString('.first-section .label { font-size: 7pt; text-transform: uppercase; }', $html);
self::assertStringContainsString('position: fixed;', $html);
self::assertStringContainsString('bottom: 0;', $html);
self::assertStringContainsString('bottom: 5pt;', $html);
self::assertStringContainsString('bottom: 7pt;', $html);
self::assertStringContainsString('PRESTADOR / FORNECEDOR', $html);
self::assertStringContainsString('TOMADOR / ADQUIRENTE', $html);
self::assertStringContainsString('TRIBUTAÇÃO MUNICIPAL (ISSQN)', $html);
Expand Down
32 changes: 32 additions & 0 deletions tests/Unit/Danfse/DanfsePdfEvidenceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,36 @@ public function testMalformedFontMetadataCannotProduceFalseSuccess(): void
self::assertContains('Unrecognized pdffonts row', $errors);
self::assertContains('No fonts reported by pdffonts', $errors);
}

public function testPhysicalMarginRasterRejectsFullBleedAndAcceptsFivePointInset(): void
{
self::assertSame([], DanfsePdfEvidence::checkPageMargins(self::pageRaster(10)));
$errors = DanfsePdfEvidence::checkPageMargins(self::pageRaster(0));
self::assertCount(4, $errors);
self::assertStringContainsString('Physical left margin', $errors[0]);
}

public function testPhysicalMarginRasterRejectsInvalidData(): void
{
self::assertSame(['Invalid grayscale PGM evidence'], DanfsePdfEvidence::checkPageMargins('invalid'));
}

private static function pageRaster(int $inset): string
{
$width = 1191;
$height = 1684;
$pgm = "P5\n{$width} {$height}\n255\n";
$pixels = str_repeat("\xFF", $width * $height);
foreach ([
[$inset, intdiv($height, 2)],
[$width - 1 - $inset, intdiv($height, 2)],
[intdiv($width, 2), $inset],
[intdiv($width, 2), $height - 1 - $inset],
] as [$x, $y]) {
$pixels[$y * $width + $x] = "\x00";
}

return $pgm . $pixels;
}

}
12 changes: 10 additions & 2 deletions tools/bin/verify-danfse-visual-fixtures.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@
}

$outputs = [];
foreach ([['pdfinfo', $path], ['pdffonts', $path], ['pdftotext', '-layout', $path, '-']] as $command) {
foreach ([
['pdfinfo', $path],
['pdffonts', $path],
['pdftotext', '-layout', $path, '-'],
['pdftoppm', '-gray', '-r', '144', '-f', '1', '-l', '1', '-singlefile', $path],
] as $command) {
// argv-array form prevents shell interpretation of filenames.
$pipes = [];
$process = proc_open($command, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
Expand All @@ -49,7 +54,10 @@
$outputs[] = $stdout;
}

$errors = DanfsePdfEvidence::check($filename, $outputs[0], $outputs[1], $outputs[2]);
$errors = array_merge(
DanfsePdfEvidence::check($filename, $outputs[0], $outputs[1], $outputs[2]),
DanfsePdfEvidence::checkPageMargins($outputs[3]),
);
if ($errors !== []) {
$failed = true;
foreach ($errors as $error) {
Expand Down
53 changes: 53 additions & 0 deletions tools/src/DanfsePdfEvidence.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,57 @@ public static function check(string $filename, string $pdfInfo, string $pdfFonts
}
return $errors;
}

/**
* Check physical sheet margins in the 144-dpi grayscale PGM generated
* by pdftoppm, rather than trusting a CSS page-margin declaration.
*
* @return list<string>
*/
public static function checkPageMargins(string $pgm): array
{
if (preg_match('/\AP5\r?\n(\d+) (\d+)\r?\n255\r?\n/', $pgm, $match) !== 1) {
return ['Invalid grayscale PGM evidence'];
}

$width = (int) $match[1];
$height = (int) $match[2];
$dataOffset = strlen($match[0]);
if ($width < 500 || $height < 700 || strlen($pgm) - $dataOffset !== $width * $height) {
return ['Invalid PGM dimensions or pixel data'];
}

$errors = [];
foreach (['left', 'right', 'top', 'bottom'] as $side) {
$horizontal = $side === 'left' || $side === 'right';
$pixelsPerPoint = $horizontal ? $width / 595.28 : $height / 841.89;
$minimum = (int) floor(72 * 0.15 / 2.54 * $pixelsPerPoint) - 1;
$maximum = (int) ceil(72 * 0.20 / 2.54 * $pixelsPerPoint) + 1;
$inset = null;
for ($distance = 0; $distance <= $maximum + 4; ++$distance) {
[$x, $y] = match ($side) {
'left' => [$distance, intdiv($height, 2)],
'right' => [$width - 1 - $distance, intdiv($height, 2)],
'top' => [intdiv($width, 2), $distance],
'bottom' => [intdiv($width, 2), $height - 1 - $distance],
};

if (ord($pgm[$dataOffset + $y * $width + $x]) < 128) {
$inset = $distance;
break;
}
}

if ($inset === null || $inset < $minimum || $inset > $maximum) {
$errors[] = sprintf(
'Physical %s margin must be 0.15-0.20 cm (found %s px at 144 dpi)',
$side,
$inset === null ? 'none' : (string) $inset,
);
}
}

return $errors;
}

}
Loading