vendor: import KillerPDF 1.7.5 source (GPL-3.0) as the base for MMD PDF
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
#Requires -Version 5.1
|
||||
param(
|
||||
[string]$Configuration = "Release",
|
||||
[switch]$KeepSymbols,
|
||||
[switch]$RepackOnly,
|
||||
[switch]$RequireSignature
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
$projectDir = Split-Path -Parent $PSScriptRoot
|
||||
$appProject = Join-Path $projectDir 'KillerPDF.csproj'
|
||||
$launcherProject = Join-Path $projectDir 'Packaging\KillerLauncher\KillerLauncher.csproj'
|
||||
$artifactRoot = Join-Path $projectDir "bin\$Configuration\net48\portable-package"
|
||||
$payloadDir = Join-Path $artifactRoot 'payload'
|
||||
$payloadZip = Join-Path $artifactRoot 'payload.zip'
|
||||
$launcherOutput = Join-Path $artifactRoot 'launcher'
|
||||
$publicDir = Join-Path $projectDir "bin\$Configuration\net48\publish"
|
||||
$publicExe = Join-Path $publicDir 'KillerPDF.exe'
|
||||
|
||||
if (-not $RepackOnly) {
|
||||
if ([IO.Directory]::Exists($artifactRoot)) { [IO.Directory]::Delete($artifactRoot, $true) }
|
||||
}
|
||||
if ([IO.Directory]::Exists($launcherOutput)) { [IO.Directory]::Delete($launcherOutput, $true) }
|
||||
[IO.Directory]::CreateDirectory($artifactRoot) | Out-Null
|
||||
[IO.Directory]::CreateDirectory($payloadDir) | Out-Null
|
||||
[IO.Directory]::CreateDirectory($launcherOutput) | Out-Null
|
||||
[IO.Directory]::CreateDirectory($publicDir) | Out-Null
|
||||
|
||||
$versionXml = [xml](Get-Content -Raw -LiteralPath $appProject)
|
||||
$versionNode = $versionXml.SelectSingleNode('/Project/PropertyGroup/Version')
|
||||
$version = if ($versionNode) { [string]$versionNode.InnerText } else { '' }
|
||||
if (-not $version) { throw 'KillerPDF.csproj has no Version.' }
|
||||
|
||||
if (-not $RepackOnly) {
|
||||
Write-Host "==> Building loose KillerPDF payload $version..." -ForegroundColor Cyan
|
||||
& dotnet publish $appProject -c $Configuration `
|
||||
-p:KillerPayloadBuild=true `
|
||||
-p:PublishDir="$payloadDir\"
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Payload build failed.' }
|
||||
|
||||
if (-not $KeepSymbols) {
|
||||
foreach ($symbol in [IO.Directory]::GetFiles($payloadDir, '*.pdb', [IO.SearchOption]::AllDirectories)) {
|
||||
[IO.File]::Delete($symbol)
|
||||
}
|
||||
}
|
||||
|
||||
# The PDF file-type icon is a loose installed asset even though the application also embeds it.
|
||||
[IO.File]::Copy((Join-Path $projectDir 'Resources\pdf-file.ico'),
|
||||
(Join-Path $payloadDir 'pdf-file.ico'), $true)
|
||||
} elseif (-not [IO.File]::Exists((Join-Path $payloadDir 'KillerPDF.App.exe'))) {
|
||||
throw 'RepackOnly requested but the prepared payload is missing.'
|
||||
}
|
||||
|
||||
$manifestPath = Join-Path $payloadDir 'payload.manifest'
|
||||
$payloadFiles = @([IO.Directory]::GetFiles($payloadDir, '*', [IO.SearchOption]::AllDirectories) |
|
||||
Where-Object { -not [string]::Equals($_, $manifestPath, [StringComparison]::OrdinalIgnoreCase) } |
|
||||
Sort-Object { $_.Substring($payloadDir.Length + 1) })
|
||||
$actualPayloadNames = @($payloadFiles | ForEach-Object {
|
||||
$_.Substring($payloadDir.Length + 1).Replace('\', '/')
|
||||
})
|
||||
$expectedPayloadNames = @(Get-Content -LiteralPath (Join-Path $PSScriptRoot 'payload-files.txt') |
|
||||
Where-Object { $_ -and -not $_.StartsWith('#') } | Sort-Object)
|
||||
$payloadDifference = @(Compare-Object $expectedPayloadNames $actualPayloadNames)
|
||||
if ($payloadDifference.Count -gt 0) {
|
||||
$details = ($payloadDifference | ForEach-Object { "$($_.SideIndicator) $($_.InputObject)" }) -join [Environment]::NewLine
|
||||
throw "Payload file set changed. Review dependencies and update build\payload-files.txt deliberately:`n$details"
|
||||
}
|
||||
foreach ($required in 'KillerPDF.App.exe', 'pdfium.dll', 'PdfSharpCore.dll', 'System.Text.Json.dll') {
|
||||
if ($actualPayloadNames -notcontains $required) {
|
||||
throw "Required loose payload file is missing ($required). Costura/Fody may have run accidentally."
|
||||
}
|
||||
}
|
||||
$manifestLines = foreach ($file in $payloadFiles) {
|
||||
$relative = $file.Substring($payloadDir.Length + 1).Replace('\', '/')
|
||||
$hash = (Get-FileHash -LiteralPath $file -Algorithm SHA256).Hash
|
||||
$size = ([IO.FileInfo]$file).Length
|
||||
"$hash`t$size`t$relative"
|
||||
}
|
||||
[IO.File]::WriteAllLines($manifestPath, $manifestLines, [Text.UTF8Encoding]::new($false))
|
||||
|
||||
Write-Host "==> Compressing one verified payload..." -ForegroundColor Cyan
|
||||
Add-Type -AssemblyName System.IO.Compression
|
||||
if ([IO.File]::Exists($payloadZip)) { [IO.File]::Delete($payloadZip) }
|
||||
$zipStream = [IO.File]::Open($payloadZip, [IO.FileMode]::CreateNew)
|
||||
try {
|
||||
$archive = [IO.Compression.ZipArchive]::new($zipStream, [IO.Compression.ZipArchiveMode]::Create, $false)
|
||||
try {
|
||||
$allFiles = @([IO.Directory]::GetFiles($payloadDir, '*', [IO.SearchOption]::AllDirectories) |
|
||||
Sort-Object { $_.Substring($payloadDir.Length + 1) })
|
||||
foreach ($file in $allFiles) {
|
||||
$relative = $file.Substring($payloadDir.Length + 1).Replace('\', '/')
|
||||
$entry = $archive.CreateEntry($relative, [IO.Compression.CompressionLevel]::Optimal)
|
||||
$entry.LastWriteTime = [DateTimeOffset]::new(2020, 1, 1, 0, 0, 0, [TimeSpan]::Zero)
|
||||
$input = [IO.File]::OpenRead($file)
|
||||
$output = $entry.Open()
|
||||
try { $input.CopyTo($output) }
|
||||
finally { $output.Dispose(); $input.Dispose() }
|
||||
}
|
||||
}
|
||||
finally { $archive.Dispose() }
|
||||
}
|
||||
finally { $zipStream.Dispose() }
|
||||
|
||||
Write-Host "==> Building the public portable launcher..." -ForegroundColor Cyan
|
||||
& dotnet publish $launcherProject -c $Configuration `
|
||||
-p:LauncherAssemblyName=KillerPDF `
|
||||
-p:LauncherVersion=$version `
|
||||
-p:LauncherIcon="$(Join-Path $projectDir 'Resources\kp-icon.ico')" `
|
||||
-p:PayloadZip="$payloadZip" `
|
||||
-p:AllowUnsignedInstall="$(!$RequireSignature)" `
|
||||
-p:PublishDir="$launcherOutput\"
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Launcher build failed.' }
|
||||
|
||||
$builtLauncher = Join-Path $launcherOutput 'KillerPDF.exe'
|
||||
if (-not [IO.File]::Exists($builtLauncher)) { throw "Launcher output is missing: $builtLauncher" }
|
||||
[IO.File]::Copy($builtLauncher, $publicExe, $true)
|
||||
|
||||
$payloadBytes = ([IO.FileInfo]$payloadZip).Length
|
||||
$publicBytes = ([IO.FileInfo]$publicExe).Length
|
||||
Write-Host " Payload files : $($payloadFiles.Count)" -ForegroundColor Green
|
||||
Write-Host " Payload zip : $payloadBytes bytes" -ForegroundColor Green
|
||||
Write-Host " Public EXE : $publicBytes bytes" -ForegroundColor Green
|
||||
Write-Host " Output : $publicExe" -ForegroundColor Green
|
||||
|
||||
[pscustomobject]@{
|
||||
Version = $version
|
||||
PayloadFiles = $payloadFiles.Count
|
||||
PayloadZipBytes = $payloadBytes
|
||||
PublicExeBytes = $publicBytes
|
||||
PublicExe = $publicExe
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
# Called automatically by the csproj after Publish.
|
||||
# Produces <AppName>-<Version>-src.zip inside the publish folder.
|
||||
# PS 5.1 / PS 7 compatible. Uses git to list tracked files so bin/obj/.vs never ship.
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$ProjectDir,
|
||||
[Parameter(Mandatory)][string]$Version,
|
||||
[Parameter(Mandatory)][string]$AppName,
|
||||
[Parameter(Mandatory)][string]$PublishDir
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$projectDirFull = (Resolve-Path $ProjectDir).Path
|
||||
$publishDirFull = if ([System.IO.Path]::IsPathRooted($PublishDir)) {
|
||||
$PublishDir
|
||||
} else {
|
||||
Join-Path $projectDirFull $PublishDir
|
||||
}
|
||||
|
||||
if (-not (Test-Path $publishDirFull)) {
|
||||
New-Item -ItemType Directory -Force -Path $publishDirFull | Out-Null
|
||||
}
|
||||
|
||||
$zip = Join-Path $publishDirFull "$AppName-$Version-src.zip"
|
||||
if (Test-Path $zip) { Remove-Item $zip -Force }
|
||||
|
||||
$staging = Join-Path $env:TEMP "$AppName-src-$([guid]::NewGuid())"
|
||||
try {
|
||||
New-Item -ItemType Directory -Force -Path $staging | Out-Null
|
||||
Push-Location $projectDirFull
|
||||
try {
|
||||
# Exclude the landing site: it is a separate deployable, not app source, and a
|
||||
# release's own exe hash can never live correctly inside the source it is built
|
||||
# from (circular). Keeps the bundle buildable-app-only and free of stale site info.
|
||||
$files = @(& git ls-files 2>$null | Where-Object { $_ -notlike 'pdf-landing/*' })
|
||||
if ($LASTEXITCODE -ne 0 -or $files.Count -eq 0) {
|
||||
Write-Warning "Source bundle skipped: git ls-files returned no tracked files (is git installed and is this a repo?)."
|
||||
return
|
||||
}
|
||||
foreach ($f in $files) {
|
||||
# A file can be tracked in git but deleted on disk (removed without `git rm`).
|
||||
# Skip it instead of aborting the whole bundle.
|
||||
if (-not (Test-Path $f)) { Write-Warning "Skipping tracked file missing on disk: $f"; continue }
|
||||
$dst = Join-Path $staging $f
|
||||
$parent = Split-Path $dst -Parent
|
||||
if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Force -Path $parent | Out-Null }
|
||||
Copy-Item $f $dst -Force
|
||||
}
|
||||
$rootLicense = Join-Path $projectDirFull 'LICENSE'
|
||||
if (Test-Path $rootLicense) { Copy-Item $rootLicense (Join-Path $staging 'LICENSE') -Force }
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
Compress-Archive -Path (Join-Path $staging '*') -DestinationPath $zip -Force
|
||||
Write-Host "Source bundle: $zip" -ForegroundColor Green
|
||||
} finally {
|
||||
Remove-Item $staging -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
#Requires -Version 5.1
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ExePath,
|
||||
[int]$Iterations = 5,
|
||||
[string]$OutputCsv = "",
|
||||
[int]$TimeoutSeconds = 45,
|
||||
[switch]$Launcher
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
$ExePath = (Resolve-Path -LiteralPath $ExePath).Path
|
||||
if (-not $OutputCsv) {
|
||||
$OutputCsv = Join-Path $PSScriptRoot "startup-results.csv"
|
||||
}
|
||||
$outputParent = Split-Path -Parent $OutputCsv
|
||||
if ($outputParent) { [System.IO.Directory]::CreateDirectory($outputParent) | Out-Null }
|
||||
|
||||
function Read-Trace([string]$Path) {
|
||||
$lines = @(Get-Content -LiteralPath $Path)
|
||||
if ($lines.Count -lt 2) { throw "Startup trace is incomplete: $Path" }
|
||||
|
||||
$header = $lines[0]
|
||||
$processStartText = [regex]::Match($header, 'processStartUtc=([^|]+)').Groups[1].Value.Trim()
|
||||
$traceStartText = [regex]::Match($header, 'traceStartUtc=([^|]+)').Groups[1].Value.Trim()
|
||||
$processStart = [datetimeoffset]::Parse($processStartText, [Globalization.CultureInfo]::InvariantCulture)
|
||||
$traceStart = [datetimeoffset]::Parse($traceStartText, [Globalization.CultureInfo]::InvariantCulture)
|
||||
$loaderMs = ($traceStart - $processStart).TotalMilliseconds
|
||||
|
||||
$marks = @{}
|
||||
foreach ($line in $lines | Select-Object -Skip 1) {
|
||||
$parts = $line -split "`t", 2
|
||||
if ($parts.Count -eq 2) {
|
||||
$marks[$parts[1]] = [double]::Parse($parts[0], [Globalization.CultureInfo]::InvariantCulture)
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $marks.ContainsKey('MainWindow ready')) { throw "Trace has no ready marker: $Path" }
|
||||
$pdfiumMs = if ($marks.ContainsKey('pdfium integrity check starting') -and
|
||||
$marks.ContainsKey('pdfium integrity check complete')) {
|
||||
$marks['pdfium integrity check complete'] - $marks['pdfium integrity check starting']
|
||||
} else { 0 }
|
||||
|
||||
[pscustomobject]@{
|
||||
LoaderToOnStartupMs = [math]::Round($loaderMs, 1)
|
||||
OnStartupToReadyMs = [math]::Round($marks['MainWindow ready'], 1)
|
||||
ProcessToReadyMs = [math]::Round($loaderMs + $marks['MainWindow ready'], 1)
|
||||
PdfiumIntegrityMs = [math]::Round($pdfiumMs, 1)
|
||||
MainWindowConstructMs = if ($marks.ContainsKey('Locale initialized') -and
|
||||
$marks.ContainsKey('MainWindow constructed')) {
|
||||
[math]::Round($marks['MainWindow constructed'] - $marks['Locale initialized'], 1)
|
||||
} else { 0 }
|
||||
ReadyUtc = $traceStart.AddMilliseconds($marks['MainWindow ready'])
|
||||
}
|
||||
}
|
||||
|
||||
$results = @()
|
||||
$oldTrace = [Environment]::GetEnvironmentVariable('KILLERPDF_STARTUP_TRACE', 'Process')
|
||||
try {
|
||||
for ($i = 1; $i -le $Iterations; $i++) {
|
||||
$trace = Join-Path ([IO.Path]::GetTempPath()) ("killerpdf-startup-{0}.trace" -f [guid]::NewGuid().ToString('N'))
|
||||
[Environment]::SetEnvironmentVariable('KILLERPDF_STARTUP_TRACE', $trace, 'Process')
|
||||
$process = Start-Process -FilePath $ExePath -PassThru
|
||||
$launchedUtc = $process.StartTime.ToUniversalTime()
|
||||
$deadline = [datetime]::UtcNow.AddSeconds($TimeoutSeconds)
|
||||
|
||||
try {
|
||||
while ([datetime]::UtcNow -lt $deadline) {
|
||||
if ($process.HasExited) { throw "KillerPDF exited before reaching the ready marker (exit $($process.ExitCode))." }
|
||||
if ((Test-Path -LiteralPath $trace) -and
|
||||
(Select-String -LiteralPath $trace -SimpleMatch 'MainWindow ready' -Quiet)) { break }
|
||||
Start-Sleep -Milliseconds 25
|
||||
$process.Refresh()
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $trace) -or
|
||||
-not (Select-String -LiteralPath $trace -SimpleMatch 'MainWindow ready' -Quiet)) {
|
||||
throw "Timed out after $TimeoutSeconds seconds waiting for KillerPDF startup."
|
||||
}
|
||||
|
||||
$timing = Read-Trace $trace
|
||||
$processToReady = if ($Launcher) {
|
||||
($timing.ReadyUtc.UtcDateTime - $launchedUtc).TotalMilliseconds
|
||||
} else { $timing.ProcessToReadyMs }
|
||||
$results += [pscustomobject]@{
|
||||
Iteration = $i
|
||||
CacheState = if ($i -eq 1) { 'first' } else { 'warm' }
|
||||
Exe = $ExePath
|
||||
ExeBytes = (Get-Item -LiteralPath $ExePath).Length
|
||||
LoaderToOnStartupMs = $timing.LoaderToOnStartupMs
|
||||
OnStartupToReadyMs = $timing.OnStartupToReadyMs
|
||||
ProcessToReadyMs = [math]::Round($processToReady, 1)
|
||||
PdfiumIntegrityMs = $timing.PdfiumIntegrityMs
|
||||
MainWindowConstructMs = $timing.MainWindowConstructMs
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if ($Launcher -and -not $process.HasExited) {
|
||||
$children = @(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.ParentProcessId -eq $process.Id -and $_.Name -eq 'KillerPDF.App.exe' })
|
||||
foreach ($child in $children) {
|
||||
Stop-Process -Id $child.ProcessId -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
$null = $process.WaitForExit(5000)
|
||||
}
|
||||
if (-not $process.HasExited) {
|
||||
$null = $process.CloseMainWindow()
|
||||
if (-not $process.WaitForExit(3000)) { Stop-Process -Id $process.Id -Force }
|
||||
}
|
||||
Remove-Item -LiteralPath $trace -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
[Environment]::SetEnvironmentVariable('KILLERPDF_STARTUP_TRACE', $oldTrace, 'Process')
|
||||
}
|
||||
|
||||
$results | Export-Csv -LiteralPath $OutputCsv -NoTypeInformation -Encoding UTF8
|
||||
$results | Format-Table -AutoSize
|
||||
|
||||
$warm = @($results | Where-Object CacheState -eq 'warm')
|
||||
if ($warm.Count -gt 0) {
|
||||
Write-Host ("Warm mean process-to-ready: {0:N1} ms" -f (($warm | Measure-Object ProcessToReadyMs -Average).Average))
|
||||
Write-Host ("Warm mean pdfium integrity: {0:N1} ms" -f (($warm | Measure-Object PdfiumIntegrityMs -Average).Average))
|
||||
}
|
||||
Write-Host "Results: $OutputCsv"
|
||||
@@ -0,0 +1,44 @@
|
||||
CommunityToolkit.Mvvm.dll
|
||||
de/PdfSharpCore.resources.dll
|
||||
Docnet.Core.dll
|
||||
ICSharpCode.SharpZipLib.dll
|
||||
KillerPDF.App.exe
|
||||
KillerPDF.App.exe.config
|
||||
LICENSE
|
||||
Microsoft.Bcl.AsyncInterfaces.dll
|
||||
Microsoft.Bcl.HashCode.dll
|
||||
Microsoft.Extensions.DependencyInjection.Abstractions.dll
|
||||
Microsoft.Extensions.Logging.Abstractions.dll
|
||||
pdf-file.ico
|
||||
pdfium.dll
|
||||
PdfSharp.BarCodes.dll
|
||||
PdfSharp.Charting.dll
|
||||
PdfSharp.Cryptography.dll
|
||||
PdfSharp.dll
|
||||
PdfSharp.Quality.dll
|
||||
PdfSharp.Shared.dll
|
||||
PdfSharp.Snippets.dll
|
||||
PdfSharp.System.dll
|
||||
PdfSharp.WPFonts.dll
|
||||
PdfSharpCore.dll
|
||||
SixLabors.Fonts.dll
|
||||
SixLabors.ImageSharp.dll
|
||||
System.Buffers.dll
|
||||
System.ComponentModel.Annotations.dll
|
||||
System.IO.Pipelines.dll
|
||||
System.Memory.dll
|
||||
System.Numerics.Vectors.dll
|
||||
System.Runtime.CompilerServices.Unsafe.dll
|
||||
System.Security.Cryptography.Pkcs.dll
|
||||
System.Text.Encoding.CodePages.dll
|
||||
System.Text.Encodings.Web.dll
|
||||
System.Text.Json.dll
|
||||
System.Threading.Tasks.Extensions.dll
|
||||
Tesseract.dll
|
||||
UglyToad.PdfPig.Core.dll
|
||||
UglyToad.PdfPig.dll
|
||||
UglyToad.PdfPig.DocumentLayoutAnalysis.dll
|
||||
UglyToad.PdfPig.Fonts.dll
|
||||
UglyToad.PdfPig.Package.dll
|
||||
UglyToad.PdfPig.Tokenization.dll
|
||||
UglyToad.PdfPig.Tokens.dll
|
||||
Reference in New Issue
Block a user