feat: F4.8d the documentation mode's report as a PDF, as Java's

ReportRun reads Java's report back with iText: every piece of text with its
place, font, size and colour. The editor lays the report out by iText's
rules (A4, leading 1.5, loose chunks in the current line, tables 80 % wide
with padding 2, fixed traffic columns, page breaks) and draws it with
pdf-lib in the PDF's own Helvetica, widths without kerning as iText measures.
All device pages of Internet_Komplett, the first page and the data exchange
of einfaches_rechnernetz_komplett match Java piece by piece; Java's iText
reads our PDF with the same text in the same places. The sidebar gains
Java's "Report"; the frames come from the room by a short subscription.
Not adopted: true/false for DHCP (Java's own Ja/Nein), hash table orders,
an image running off the page, the report writing back into the model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 12:35:35 +00:00
co-authored by Claude Opus 5.5
parent 7e68414bb2
commit b8b999fd66
29 changed files with 7726 additions and 23 deletions
+24 -1
View File
@@ -7,7 +7,7 @@ import { describe, expect, it } from 'vitest';
import { captureRows, TCP_FLAG, type CapturedFrame, type EthernetFrame } from '@mdg-filius/core';
import type { TraceFrame } from '@mdg-filius/protocol';
import { translate } from '../src/i18n/i18n.js';
import { captureWords, exportText, frameCells, frameDetails } from '../src/simulation/captureCells.js';
import { captureWords, exportText, frameCells, frameDetails, reportRows, reportTraffic } from '../src/simulation/captureCells.js';
/** A TCP segment with data from 10.0.0.1:50000 to 10.0.0.2:80, as the room sends it. */
function frame(number: number, timeMilliseconds: number): TraceFrame {
@@ -76,3 +76,26 @@ describe("the data exchange window's cells", () => {
expect(text.match(/^\+-/gm)).toHaveLength(2);
});
});
describe("the report's data exchange", () => {
it('writes every layer of a frame as a row, with addresses, as Java\'s report does', () => {
expect(reportRows([frame(3, 1_234)], clock, words('de'))).toEqual([
['3', '08:30:01.234', '02:00:00:00:00:01', '02:00:00:00:00:02', '', 'Netzzugang', '0x800'],
['3', '08:30:01.234', '10.0.0.1', '10.0.0.2', 'IP', 'Vermittlung', 'Protokoll: 6, TTL: 64'],
['3', '08:30:01.234', '10.0.0.1:50000', '10.0.0.2:80', 'TCP', 'Transport', 'SEQ: 2.000.001, ACK: 1.000.001'],
['3', '08:30:01.234', '10.0.0.1:50000', '10.0.0.2:80', 'HTTP', 'Anwendung', 'GET / HTTP/1.1\nHost: x\n'],
]);
});
it("lists each card that saw frames, under its device's label and address; one without frames not", () => {
const cards = [
{ portId: 'a', label: 'Rechner 1', ipAddress: '10.0.0.1' },
{ portId: 'b', label: 'Rechner 2', ipAddress: '10.0.0.2' },
{ portId: 'c', label: 'Rechner 3', ipAddress: '10.0.0.3' },
];
const traffic = reportTraffic(cards, new Map([['a', [frame(1, 0)]], ['b', []]]), clock, words('de'));
expect(traffic.map((card) => card.title)).toEqual(['Rechner 1 - 10.0.0.1']);
expect(traffic[0]!.header).toEqual(words('de').header);
expect(traffic[0]!.rows.length).toBe(4);
});
});