diff --git a/packaging/windows/pdf/.gitignore b/packaging/windows/pdf/.gitignore new file mode 100644 index 0000000..1b13aa1 --- /dev/null +++ b/packaging/windows/pdf/.gitignore @@ -0,0 +1,3 @@ +# Le DLL scaricate da prendi-gtk.ps1 non si committano: solo lo script (TieMeasureFlow#78). +gtk/ +cache/ diff --git a/packaging/windows/pdf/prendi-gtk.ps1 b/packaging/windows/pdf/prendi-gtk.ps1 new file mode 100644 index 0000000..c75c87f --- /dev/null +++ b/packaging/windows/pdf/prendi-gtk.ps1 @@ -0,0 +1,143 @@ +<# + prendi-gtk.ps1 - prepara le DLL di Pango/GTK che WeasyPrint 68.1 vuole su Windows. + + COSA FA: scarica da repo.msys2.org (ucrt64) un elenco FISSO di pacchetti, ne verifica lo sha256, + estrae solo le DLL che servono a WeasyPrint (+ etc\fonts di fontconfig e le licenze) in -Destinazione. + COME SI USA: powershell -NoProfile -ExecutionPolicy Bypass -File prendi-gtk.ps1 -Destinazione + Opzionali: -Cache (pacchetti scaricati, default %LOCALAPPDATA%\Tielogic\tmf-gtk-cache), + -Forza (rifa' anche se la cartella e' gia' pronta). + DA COSA DIPENDE: Windows 10/11 con tar.exe (bsdtar con zstd) oppure uv + Python (modulo zstandard); + Internet solo al momento della build, mai a runtime. PowerShell 5.1 va bene, non serve admin. + IDEMPOTENTE: se la cartella ha gia' le DLL e il file .gtk-pin coincide con questi pin, esce senza scaricare. + FALLISCE se lo sha256 di un pacchetto non coincide (nessun file parziale resta in -Destinazione). + TRAPPOLA: repo.msys2.org tiene solo le versioni recenti. Se un file pinnato sparisce (404) serve aggiornare + i pin qui sotto (nome file + sha256 dal db https://repo.msys2.org/mingw/ucrt64/ucrt64.db) e riprovare i test. + La -Cache conserva i pacchetti gia' scaricati: copiarla su Wasabi/NAS evita il problema. +#> +param( + [Parameter(Mandatory = $true)][string]$Destinazione, + [string]$Cache = (Join-Path $env:LOCALAPPDATA 'Tielogic\tmf-gtk-cache'), + [switch]$Forza +) +$ErrorActionPreference = 'Stop' +# PS 5.1 usa TLS 1.0 di default: repo.msys2.org vuole 1.2 (cosa/perche') +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$Base = 'https://repo.msys2.org/mingw/ucrt64/' + +# Pin: pacchetto, file, sha256 (dal db ucrt64 di MSYS2 del 2026-10-17), licenza dichiarata. +$Pacchetti = @( + @{ N='brotli'; F='mingw-w64-ucrt-x86_64-brotli-1.2.0-1-any.pkg.tar.zst'; H='9cc89665496ea504751476eafba15c71c554aaf3004babc2004f7c07ffd60514'; L='MIT' } + @{ N='bzip2'; F='mingw-w64-ucrt-x86_64-bzip2-1.0.8-4-any.pkg.tar.zst'; H='f03a2174034ddd2d96cecd34f617c5f8e2ef86c812b8d2bb3b8875257f2c8bfa'; L='bzip2 (BSD)' } + @{ N='expat'; F='mingw-w64-ucrt-x86_64-expat-2.9.0-1-any.pkg.tar.zst'; H='f52364dc874009316e1ee1fe46ee89784594b80d10cbcc1cc856945d575308e0'; L='MIT' } + @{ N='fontconfig'; F='mingw-w64-ucrt-x86_64-fontconfig-2.18.3-1-any.pkg.tar.zst'; H='d0b364f3ee37e3b6f6c136688ce8fa909bb6d8a69df7e08de1aa40870d9e1d60'; L='fontconfig (MIT-like)' } + @{ N='freetype'; F='mingw-w64-ucrt-x86_64-freetype-2.15.0-1-any.pkg.tar.zst'; H='a9a691f71d1a553099c54ca666e611abfb2b4ff08dae24f2a5af72e22a7e5feb'; L='GPL-2.0+ OR FTL' } + @{ N='fribidi'; F='mingw-w64-ucrt-x86_64-fribidi-1.0.17-1-any.pkg.tar.zst'; H='3581ee03cf83a9bb0e98bf688c4f532a29553500e317532d90746de295b0f8a8'; L='LGPL-2.1+' } + @{ N='gettext-runtime'; F='mingw-w64-ucrt-x86_64-gettext-runtime-1.0-1-any.pkg.tar.zst'; H='ba693dda4ac375af76ce481ff3a6e7481286546cc7dc6d56c7021dae34084157'; L='LGPL-2.1+ (libintl)' } + @{ N='glib2'; F='mingw-w64-ucrt-x86_64-glib2-2.90.1-1-any.pkg.tar.zst'; H='9a7826349279bcca0c935b01880d812be9de9609aef2205be68fd7a6128c1695'; L='LGPL-2.1+' } + @{ N='graphite2'; F='mingw-w64-ucrt-x86_64-graphite2-1.3.15-1-any.pkg.tar.zst'; H='34ca1f88bff10ef182a6b1c8e5622d5029f13b05748e361af13b552b422ea849'; L='LGPL-2.1+' } + @{ N='harfbuzz'; F='mingw-w64-ucrt-x86_64-harfbuzz-14.6.0-1-any.pkg.tar.zst'; H='8781d486c864bf91f482768cc5bd7267e49331636583d9f855af3974e6378e69'; L='MIT' } + @{ N='libdatrie'; F='mingw-w64-ucrt-x86_64-libdatrie-0.2.14-1-any.pkg.tar.zst'; H='ea92777801ad325278193a362967540752ae37035016df2f6bcee2d89928a195'; L='LGPL' } + @{ N='libffi'; F='mingw-w64-ucrt-x86_64-libffi-3.8.0-1-any.pkg.tar.zst'; H='99ad12f4ecfa00a889ef9c5c0188592368a5bac109d0c508ce4e04f29ea95a76'; L='MIT' } + @{ N='libgcc'; F='mingw-w64-ucrt-x86_64-libgcc-16.2.0-6-any.pkg.tar.zst'; H='80b2497f019977dd9528b1abc8621b1328a0115ab1f724047ccbb56bf323f11d'; L='GPL-3.0+ con GCC runtime exception' } + @{ N='libiconv'; F='mingw-w64-ucrt-x86_64-libiconv-1.19-1-any.pkg.tar.zst'; H='9a500f38c2b91808741c62fae746b3e9110b33a1ecf5c30fa0c66dbedddf7e16'; L='LGPL-2.1+' } + @{ N='libpng'; F='mingw-w64-ucrt-x86_64-libpng-1.6.59-1-any.pkg.tar.zst'; H='7f8448e06eceba8f8ce130390addea49f767bdf8f6b7e7eede8ca6d595e3d3a8'; L='libpng (permissiva)' } + @{ N='libstdc++'; F='mingw-w64-ucrt-x86_64-libstdc++-16.2.0-6-any.pkg.tar.zst'; H='33c5e58a85a538cf843a8672991cce591f0231d66da3b66a57629712d64be17f'; L='GPL-3.0+ con GCC runtime exception' } + @{ N='libthai'; F='mingw-w64-ucrt-x86_64-libthai-0.1.30-1-any.pkg.tar.zst'; H='c90ebbca3e7b4c34f4b3ffa501ba8d00cdaf6340cff7db3c46d79d5e01ef207c'; L='LGPL-2.1+' } + @{ N='libwinpthread'; F='mingw-w64-ucrt-x86_64-libwinpthread-14.0.0.r426.g4564ee4b5-2-any.pkg.tar.zst'; H='a02f5e09d666d3be657c78c86fe67c975d04fe33b5211cd5ff1b23a91c118524'; L='MIT AND BSD-3-Clause' } + @{ N='pcre2'; F='mingw-w64-ucrt-x86_64-pcre2-10.49-1-any.pkg.tar.zst'; H='63172179db724ac71e2439a7981134c1bcab8049bdddfd349b32b8f76ed52d1e'; L='BSD-3-Clause' } + @{ N='pango'; F='mingw-w64-ucrt-x86_64-pango-1.58.2-1-any.pkg.tar.zst'; H='8d86e4a018ff139b010bf357fa1c9f9030fa32ee195c54ed7e89d8ea701851c8'; L='LGPL-2.1' } + @{ N='zlib-ng-compat'; F='mingw-w64-ucrt-x86_64-zlib-ng-compat-2.3.3-2-any.pkg.tar.zst'; H='b3ca6b437fb154a0fa26652002cdda591498ee4a0e08d91cc653c3702d090f25'; L='Zlib' } +) + +# DLL che WeasyPrint carica (6 radici) piu' la loro chiusura di dipendenze (calcolata con pefile il 2026-10-17). +$Dll = @( + 'libbrotlicommon.dll','libbrotlidec.dll','libbz2-1.dll','libdatrie-1.dll','libexpat-1.dll','libffi-8.dll', + 'libfontconfig-1.dll','libfreetype-6.dll','libfribidi-0.dll','libgcc_s_seh-1.dll','libgio-2.0-0.dll', + 'libglib-2.0-0.dll','libgmodule-2.0-0.dll','libgobject-2.0-0.dll','libgraphite2.dll','libharfbuzz-0.dll', + 'libharfbuzz-subset-0.dll','libiconv-2.dll','libintl-8.dll','libpango-1.0-0.dll','libpangoft2-1.0-0.dll', + 'libpcre2-8-0.dll','libpng16-16.dll','libstdc++-6.dll','libthai-0.dll','libwinpthread-1.dll','zlib1.dll' +) + +function Scrivi([string]$m) { Write-Host ("[prendi-gtk] " + $m) } + +# Impronta dei pin: se cambia, la cartella gia' pronta non e' piu' valida. +$pinTesto = (($Pacchetti | ForEach-Object { $_.F + ':' + $_.H }) -join "`n") + "`n" + ($Dll -join ',') +$sha = [Security.Cryptography.SHA256]::Create() +$impronta = -join ($sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($pinTesto)) | ForEach-Object { $_.ToString('x2') }) + +$marker = Join-Path $Destinazione '.gtk-pin' +$dllMancanti = @($Dll | Where-Object { -not (Test-Path (Join-Path $Destinazione $_)) }) +if (-not $Forza -and (Test-Path $marker) -and ((Get-Content $marker -Raw).Trim() -eq $impronta) -and $dllMancanti.Count -eq 0) { + Scrivi "gia' pronto in $Destinazione (pin $($impronta.Substring(0,12)))" + exit 0 +} + +# --- estrazione .zst: tar.exe di Windows se sa di zstd, altrimenti Python zstandard via uv --- +$tar = Join-Path $env:SystemRoot 'System32\tar.exe' +$tarZstd = $false +if (Test-Path $tar) { $tarZstd = ((& $tar --version) -join ' ') -match 'zstd' } +function Estrai([string]$pkg, [string]$dest) { + if ($tarZstd) { + & $tar -xf $pkg -C $dest + if ($LASTEXITCODE -ne 0) { throw "tar.exe ha fallito su $pkg" } + } else { + $py = "import sys,tarfile,zstandard`nd=zstandard.ZstdDecompressor()`nwith open(sys.argv[1],'rb') as f, d.stream_reader(f) as r:`n tarfile.open(fileobj=r,mode='r|').extractall(sys.argv[2])" + $tmpPy = Join-Path ([IO.Path]::GetTempPath()) ('estrai-zst-' + [guid]::NewGuid().ToString('N') + '.py') + Set-Content -Path $tmpPy -Value $py -Encoding ASCII + try { + & uv run --quiet --with zstandard python -I $tmpPy $pkg $dest + if ($LASTEXITCODE -ne 0) { throw "estrazione zstd via uv fallita su $pkg" } + } + finally { Remove-Item $tmpPy -ErrorAction SilentlyContinue } + } +} + +New-Item -ItemType Directory -Force -Path $Cache | Out-Null +# Lavoro in una cartella temporanea, poi sposto: un errore a meta' non lascia mezza cartella in -Destinazione. +$tmp = Join-Path ([IO.Path]::GetTempPath()) ('tmf-gtk-' + [guid]::NewGuid().ToString('N')) +$stage = Join-Path $tmp 'stage' +New-Item -ItemType Directory -Force -Path $stage | Out-Null +try { + foreach ($p in $Pacchetti) { + $file = Join-Path $Cache $p.F + $ok = $false + if (Test-Path $file) { + $ok = ((Get-FileHash $file -Algorithm SHA256).Hash.ToLower() -eq $p.H) + if (-not $ok) { Scrivi "cache corrotta, riscarico $($p.F)"; Remove-Item $file -Force } + } + if (-not $ok) { + Scrivi "scarico $($p.F)" + $parz = $file + '.parz' + # il '+' di libstdc++ va codificato nell'URL + Invoke-WebRequest -UseBasicParsing -Uri ($Base + [Uri]::EscapeDataString($p.F)) -OutFile $parz + $h = (Get-FileHash $parz -Algorithm SHA256).Hash.ToLower() + if ($h -ne $p.H) { Remove-Item $parz -Force; throw "sha256 diverso per $($p.F): atteso $($p.H), trovato $h" } + Move-Item $parz $file -Force + } + $ex = Join-Path $tmp $p.N + New-Item -ItemType Directory -Force -Path $ex | Out-Null + Estrai $file $ex + $bin = Join-Path $ex 'ucrt64\bin' + if (Test-Path $bin) { Get-ChildItem $bin -Filter *.dll | Copy-Item -Destination $stage -Force } + # fontconfig: senza etc\fonts WeasyPrint non trova i font (errore "Cannot load default config file") + if ($p.N -eq 'fontconfig') { Copy-Item (Join-Path $ex 'ucrt64\etc\fonts') (Join-Path $stage 'etc\fonts') -Recurse -Force } + # licenze: le DLL LGPL si ridistribuiscono con il testo della licenza + New-Item -ItemType Directory -Force -Path (Join-Path $stage 'licenze') | Out-Null + $lic = Join-Path $ex 'ucrt64\share\licenses' + if (Test-Path $lic) { Copy-Item $lic (Join-Path $stage ('licenze\' + $p.N)) -Recurse -Force } + Set-Content -Path (Join-Path $stage ('licenze\' + $p.N + '.txt')) -Value ($p.F + "`nlicenza: " + $p.L + "`nsorgente: https://packages.msys2.org/") -Encoding UTF8 + } + # Tengo solo le DLL in whitelist: le altre DLL dei pacchetti non servono. + Get-ChildItem $stage -Filter *.dll | Where-Object { $Dll -notcontains $_.Name } | Remove-Item -Force + $mancanti = @($Dll | Where-Object { -not (Test-Path (Join-Path $stage $_)) }) + if ($mancanti.Count -gt 0) { throw "DLL attese non trovate: $($mancanti -join ', ')" } + Set-Content -Path (Join-Path $stage '.gtk-pin') -Value $impronta -Encoding ASCII + + if (Test-Path $Destinazione) { Get-ChildItem $Destinazione -Force | Remove-Item -Recurse -Force } + New-Item -ItemType Directory -Force -Path $Destinazione | Out-Null + Get-ChildItem $stage -Force | Move-Item -Destination $Destinazione -Force + Scrivi "pronto: $($Dll.Count) DLL in $Destinazione" +} +finally { + Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue +}