<# 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 }