"""Compare forensic snapshots and print diff tables.""" import json import sys from pathlib import Path def load(p): return json.loads(Path(p).read_text(encoding="utf-8-sig")) def diff_table(a, b, keys): rows = [] for k in keys: va, vb = a.get(k), b.get(k) changed = va != vb rows.append((k, va, vb, "CHANGED" if changed else "same")) return rows def print_table(title, rows): print(f"\n=== {title} ===") print(f"{'Property':<22} {'Before':<40} {'After':<40} {'Status'}") for k, va, vb, st in rows: sa = json.dumps(va) if not isinstance(va, str) else va sb = json.dumps(vb) if not isinstance(vb, str) else vb if len(sa) > 38: sa = sa[:35] + "..." if len(sb) > 38: sb = sb[:35] + "..." print(f"{k:<22} {sa:<40} {sb:<40} {st}") def compare_advances(orig, new): oa, na = orig or [], new or [] diffs = [] for i in range(max(len(oa), len(na))): ov = oa[i] if i < len(oa) else None nv = na[i] if i < len(na) else None if ov != nv: diffs.append((i, ov, nv)) return diffs def main(): extract_path = Path(sys.argv[1] if len(sys.argv) > 1 else "forensic_extract_out.json") wasm_path = Path(sys.argv[2] if len(sys.argv) > 2 else "forensic_wasm_out.json") ex = load(extract_path) wasm = load(wasm_path) keys = [ "text", "fontId", "fontName", "fontSize", "color", "glyphCount", "advanceCount", "paragraphWidth", "baseline", "lineHeight", "origLinesProvided", ] orig = ex["original"] entry_payload = ex["editEntry"] one_payload = ex["editOne"] # WASM layout snapshots from forensic_extract import snapshot_from_wasm_layout, para_signature # type: ignore seed_fn = orig.get("fontName", "") entry_wasm = snapshot_from_wasm_layout("ENTRY_WASM", wasm["editEntryWasm"]["layout"], seed_fn) one_wasm = snapshot_from_wasm_layout("ONE_WASM", wasm["editOneWasm"]["layout"], seed_fn) print_table("ORIGINAL vs EDIT_MODE_ENTRY (payload)", diff_table(orig, entry_payload, keys)) print_table("ORIGINAL vs EDIT_MODE_ENTRY (WASM layout)", diff_table(orig, entry_wasm, keys)) adv_diff_entry = compare_advances(orig.get("advances"), entry_wasm.get("advances")) if adv_diff_entry: print(f"\nAdvance diffs ORIGINAL vs ENTRY_WASM: {len(adv_diff_entry)} positions") for i, ov, nv in adv_diff_entry[:20]: print(f" [{i}] {ov} -> {nv}") else: print("\nAdvance arrays IDENTICAL: ORIGINAL vs ENTRY_WASM") print_table("EDIT_ENTRY vs AFTER_ONE_CHAR (payload)", diff_table(entry_payload, one_payload, keys)) print_table("ENTRY_WASM vs ONE_WASM", diff_table(entry_wasm, one_wasm, keys)) adv_diff_one = compare_advances(entry_wasm.get("advances"), one_wasm.get("advances")) if adv_diff_one: print(f"\nAdvance diffs ENTRY_WASM vs ONE_WASM: {len(adv_diff_one)} positions") for i, ov, nv in adv_diff_one[:20]: print(f" [{i}] {ov} -> {nv}") # Line widths print("\n=== LINE WIDTHS ===") print(f"Original lineWidths: {orig.get('lineWidths')}") print(f"Entry WASM lineWidths: {entry_wasm.get('lineWidths')}") print(f"One WASM lineWidths: {one_wasm.get('lineWidths')}") # Signatures chain sigs = { "original": para_signature(orig), "entry_payload": para_signature(entry_payload), "entry_wasm": para_signature(entry_wasm), "one_payload": para_signature(one_payload), "one_wasm": para_signature(one_wasm), } print("\n=== SIGNATURE CHAIN ===") prev = None for name, sig in sigs.items(): changed = prev and sig != prev print(f" {name}: {sig}" + (" <-- FIRST CHANGE from " + prev_name if changed else "")) if changed and prev: pass prev_name = name prev = sig # First mutation chain = list(sigs.items()) base = chain[0][1] for i in range(1, len(chain)): if chain[i][1] != base: print(f"\nFIRST SIGNATURE CHANGE: {chain[i][0]} (vs original)") break if __name__ == "__main__": main()