diff --git a/data/handouts/Images/_Export-Asy.ps1 b/data/handouts/Images/_Export-Asy.ps1
index 7233713..b8392df 100644
--- a/data/handouts/Images/_Export-Asy.ps1
+++ b/data/handouts/Images/_Export-Asy.ps1
@@ -5,7 +5,10 @@
# Per .asy file the pipeline is:
# 1. Run asy with -noView to render the .asy to a PDF next to the source. asy is run with
# -cd
so `import _common;` and other relative imports resolve next to the source file.
-# 2. Convert the PDF to SVG via Inkscape (matches the conversion path used by _Export-Ggb.ps1
+# 2. Patch the PDF's non-deterministic metadata (timestamps and IDs that the bundled Ghostscript
+# stamps with wall-clock values) so re-rendering an unchanged .asy produces a byte-identical
+# PDF and stops creating spurious git diffs.
+# 3. Convert the PDF to SVG via Inkscape (matches the conversion path used by _Export-Ggb.ps1
# so handout figures coming from either source render identically downstream).
param(
# One or more directories, .asy files, or PowerShell wildcards (e.g. 'angles-*.asy'). Default
@@ -62,7 +65,40 @@ function Convert-OneAsy {
throw "asy failed (exit $($proc.ExitCode))"
}
- # 2. Convert the PDF to plain SVG via Inkscape. The .com launcher on PATH is the console
+ # 2. Replace the non-deterministic metadata that asy's bundled Ghostscript stamps with wall-clock
+ # values (/CreationDate, /ModDate, /ID in the trailer plus a second copy of the dates and a
+ # DocumentID UUID inside the XMP stream). With those derived from the .asy source instead,
+ # re-rendering an unchanged .asy yields a byte-identical PDF and stops producing spurious git
+ # diffs. The Windows builds of GS we have access to (asy's bundled 10.x and MiKTeX's 9.25)
+ # ignore SOURCE_DATE_EPOCH and the -dOmit* flags, so we patch the bytes ourselves.
+ $mtimeUtc = (Get-Item -LiteralPath $AsyPath).LastWriteTimeUtc
+ # Date timestamps for the two formats Ghostscript emits: PDF date string in the trailer /Info,
+ # ISO 8601 in the XMP stream. UTC normalised so the field length doesn't depend on local TZ.
+ $pdfDate = "D:{0:yyyyMMddHHmmss}+00'00'" -f $mtimeUtc
+ $xmpDate = "{0:yyyy-MM-ddTHH:mm:ss}+00:00" -f $mtimeUtc
+ # Identifiers: SHA-256 of the .asy content gives 64 hex chars. First 32 supply the /ID halves
+ # (PDF spec calls for 16-byte values); next 32 fill the DocumentID UUID positions.
+ $hash = (Get-FileHash -LiteralPath $AsyPath -Algorithm SHA256).Hash
+ $idHex = $hash.Substring(0, 32)
+ $uuidHex = $hash.Substring(32, 32)
+ $uuid = '{0}-{1}-{2}-{3}-{4}' -f $uuidHex.Substring(0, 8), $uuidHex.Substring(8, 4), $uuidHex.Substring(12, 4), $uuidHex.Substring(16, 4), $uuidHex.Substring(20, 12)
+ # Read the PDF as Latin1 so each byte round-trips losslessly through a string (PDFs are mostly
+ # ASCII outside of stream contents, and our regexes never match inside binary streams).
+ $pdfBytes = [System.IO.File]::ReadAllBytes($finalPdf)
+ $pdfText = [System.Text.Encoding]::Latin1.GetString($pdfBytes)
+ # Trailer /Info dict.
+ $pdfText = [regex]::Replace($pdfText, "/CreationDate\(D:\d{14}[+\-]\d{2}'\d{2}'\)", "/CreationDate($pdfDate)")
+ $pdfText = [regex]::Replace($pdfText, "/ModDate\(D:\d{14}[+\-]\d{2}'\d{2}'\)", "/ModDate($pdfDate)")
+ $pdfText = [regex]::Replace($pdfText, "/ID \[<[0-9A-Fa-f]{32}><[0-9A-Fa-f]{32}>\]", "/ID [<$idHex><$idHex>]")
+ # XMP metadata stream (uncompressed in asy's output).
+ $pdfText = [regex]::Replace($pdfText, "[^<]+", "$xmpDate")
+ $pdfText = [regex]::Replace($pdfText, "[^<]+", "$xmpDate")
+ $pdfText = [regex]::Replace($pdfText, "xapMM:DocumentID='uuid:[0-9a-fA-F-]+'", "xapMM:DocumentID='uuid:$uuid'")
+ # Every replacement above is the same length as the value it replaced, so the PDF's xref byte
+ # offsets stay valid without rebuilding the table.
+ [System.IO.File]::WriteAllBytes($finalPdf, [System.Text.Encoding]::Latin1.GetBytes($pdfText))
+
+ # 3. Convert the PDF to plain SVG via Inkscape. The .com launcher on PATH is the console
# launcher that waits for inkscape.exe, so -Wait is enough.
if (Test-Path -LiteralPath $finalSvg) { Remove-Item -LiteralPath $finalSvg -Force }
$proc = Start-Process -FilePath $inkscape -ArgumentList @($finalPdf, '--pdf-poppler', '--export-type=svg', '--export-plain-svg', "--export-filename=$finalSvg") -Wait -PassThru
diff --git a/data/handouts/Images/ag-proof.pdf b/data/handouts/Images/ag-proof.pdf
index ee3ffd8..fb200fe 100644
Binary files a/data/handouts/Images/ag-proof.pdf and b/data/handouts/Images/ag-proof.pdf differ
diff --git a/data/handouts/Images/angles-90-60-30.pdf b/data/handouts/Images/angles-90-60-30.pdf
index 8112d7f..b343911 100644
Binary files a/data/handouts/Images/angles-90-60-30.pdf and b/data/handouts/Images/angles-90-60-30.pdf differ
diff --git a/data/handouts/Images/angles-Ssa-proof-isosceles.pdf b/data/handouts/Images/angles-Ssa-proof-isosceles.pdf
index 4a85fa4..de36d79 100644
Binary files a/data/handouts/Images/angles-Ssa-proof-isosceles.pdf and b/data/handouts/Images/angles-Ssa-proof-isosceles.pdf differ
diff --git a/data/handouts/Images/angles-Ssa-proof-one-solution.pdf b/data/handouts/Images/angles-Ssa-proof-one-solution.pdf
index 7fb3c88..9cc5912 100644
Binary files a/data/handouts/Images/angles-Ssa-proof-one-solution.pdf and b/data/handouts/Images/angles-Ssa-proof-one-solution.pdf differ
diff --git a/data/handouts/Images/angles-Ssa-proof-two-solutions.pdf b/data/handouts/Images/angles-Ssa-proof-two-solutions.pdf
index d98c078..1544b70 100644
Binary files a/data/handouts/Images/angles-Ssa-proof-two-solutions.pdf and b/data/handouts/Images/angles-Ssa-proof-two-solutions.pdf differ
diff --git a/data/handouts/Images/angles-alternate.pdf b/data/handouts/Images/angles-alternate.pdf
index 67b9886..d8f23d7 100644
Binary files a/data/handouts/Images/angles-alternate.pdf and b/data/handouts/Images/angles-alternate.pdf differ
diff --git a/data/handouts/Images/angles-asa-proof.pdf b/data/handouts/Images/angles-asa-proof.pdf
index 3d82106..f0dfa61 100644
Binary files a/data/handouts/Images/angles-asa-proof.pdf and b/data/handouts/Images/angles-asa-proof.pdf differ
diff --git a/data/handouts/Images/angles-complementary-in-triangle.pdf b/data/handouts/Images/angles-complementary-in-triangle.pdf
index 37380ab..fd2e210 100644
Binary files a/data/handouts/Images/angles-complementary-in-triangle.pdf and b/data/handouts/Images/angles-complementary-in-triangle.pdf differ
diff --git a/data/handouts/Images/angles-complementary-parallel-solution.pdf b/data/handouts/Images/angles-complementary-parallel-solution.pdf
index cdcfa05..8520117 100644
Binary files a/data/handouts/Images/angles-complementary-parallel-solution.pdf and b/data/handouts/Images/angles-complementary-parallel-solution.pdf differ
diff --git a/data/handouts/Images/angles-complementary-parallel-statement.pdf b/data/handouts/Images/angles-complementary-parallel-statement.pdf
index cf46a14..275b801 100644
Binary files a/data/handouts/Images/angles-complementary-parallel-statement.pdf and b/data/handouts/Images/angles-complementary-parallel-statement.pdf differ
diff --git a/data/handouts/Images/angles-corresponding-alternate-supplementary-connection.pdf b/data/handouts/Images/angles-corresponding-alternate-supplementary-connection.pdf
index 0ba660c..4cda796 100644
Binary files a/data/handouts/Images/angles-corresponding-alternate-supplementary-connection.pdf and b/data/handouts/Images/angles-corresponding-alternate-supplementary-connection.pdf differ
diff --git a/data/handouts/Images/angles-corresponding.pdf b/data/handouts/Images/angles-corresponding.pdf
index e32c8d2..7132d82 100644
Binary files a/data/handouts/Images/angles-corresponding.pdf and b/data/handouts/Images/angles-corresponding.pdf differ
diff --git a/data/handouts/Images/angles-equilateral.pdf b/data/handouts/Images/angles-equilateral.pdf
index c49b5aa..b8c41c3 100644
Binary files a/data/handouts/Images/angles-equilateral.pdf and b/data/handouts/Images/angles-equilateral.pdf differ
diff --git a/data/handouts/Images/angles-isosceles-right-triangle.pdf b/data/handouts/Images/angles-isosceles-right-triangle.pdf
index 18db953..d50cb66 100644
Binary files a/data/handouts/Images/angles-isosceles-right-triangle.pdf and b/data/handouts/Images/angles-isosceles-right-triangle.pdf differ
diff --git a/data/handouts/Images/angles-isosceles-triangle.pdf b/data/handouts/Images/angles-isosceles-triangle.pdf
index 67e9be2..8a3b542 100644
Binary files a/data/handouts/Images/angles-isosceles-triangle.pdf and b/data/handouts/Images/angles-isosceles-triangle.pdf differ
diff --git a/data/handouts/Images/angles-polygon-fan.pdf b/data/handouts/Images/angles-polygon-fan.pdf
index 564cd92..877c3f3 100644
Binary files a/data/handouts/Images/angles-polygon-fan.pdf and b/data/handouts/Images/angles-polygon-fan.pdf differ
diff --git a/data/handouts/Images/angles-polygon-interior.pdf b/data/handouts/Images/angles-polygon-interior.pdf
index b5580fa..efcc06a 100644
Binary files a/data/handouts/Images/angles-polygon-interior.pdf and b/data/handouts/Images/angles-polygon-interior.pdf differ
diff --git a/data/handouts/Images/angles-polygon.pdf b/data/handouts/Images/angles-polygon.pdf
index 7dc7ff9..2fac12f 100644
Binary files a/data/handouts/Images/angles-polygon.pdf and b/data/handouts/Images/angles-polygon.pdf differ
diff --git a/data/handouts/Images/angles-sas-proof.pdf b/data/handouts/Images/angles-sas-proof.pdf
index 6e7b64f..214ed20 100644
Binary files a/data/handouts/Images/angles-sas-proof.pdf and b/data/handouts/Images/angles-sas-proof.pdf differ
diff --git a/data/handouts/Images/angles-sas-warning.pdf b/data/handouts/Images/angles-sas-warning.pdf
index 74e0e29..867753b 100644
Binary files a/data/handouts/Images/angles-sas-warning.pdf and b/data/handouts/Images/angles-sas-warning.pdf differ
diff --git a/data/handouts/Images/angles-sss-proof.pdf b/data/handouts/Images/angles-sss-proof.pdf
index 5d7be23..c7d5a55 100644
Binary files a/data/handouts/Images/angles-sss-proof.pdf and b/data/handouts/Images/angles-sss-proof.pdf differ
diff --git a/data/handouts/Images/angles-supplementary-derive-vertical.pdf b/data/handouts/Images/angles-supplementary-derive-vertical.pdf
index 7cbbe02..89a006c 100644
Binary files a/data/handouts/Images/angles-supplementary-derive-vertical.pdf and b/data/handouts/Images/angles-supplementary-derive-vertical.pdf differ
diff --git a/data/handouts/Images/angles-supplementary.pdf b/data/handouts/Images/angles-supplementary.pdf
index a962026..64cebb3 100644
Binary files a/data/handouts/Images/angles-supplementary.pdf and b/data/handouts/Images/angles-supplementary.pdf differ
diff --git a/data/handouts/Images/angles-triangle.pdf b/data/handouts/Images/angles-triangle.pdf
index d15e708..eed60c5 100644
Binary files a/data/handouts/Images/angles-triangle.pdf and b/data/handouts/Images/angles-triangle.pdf differ
diff --git a/data/handouts/Images/angles-vertical-derive-supplementary.pdf b/data/handouts/Images/angles-vertical-derive-supplementary.pdf
index 6c25853..1511c41 100644
Binary files a/data/handouts/Images/angles-vertical-derive-supplementary.pdf and b/data/handouts/Images/angles-vertical-derive-supplementary.pdf differ
diff --git a/data/handouts/Images/angles-vertical.pdf b/data/handouts/Images/angles-vertical.pdf
index 56f468d..3b44013 100644
Binary files a/data/handouts/Images/angles-vertical.pdf and b/data/handouts/Images/angles-vertical.pdf differ
diff --git a/data/handouts/Images/box.pdf b/data/handouts/Images/box.pdf
index 5e386a6..05dd426 100644
Binary files a/data/handouts/Images/box.pdf and b/data/handouts/Images/box.pdf differ