added packaging
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Draws the application icons: the logos for MSIX and the .ico for the exe.
|
||||
|
||||
.DESCRIPTION
|
||||
The icons are drawn by code rather than kept as pictures so that the whole
|
||||
set of sizes is rebuilt by a single command whenever another drawing is
|
||||
wanted.
|
||||
|
||||
The current drawing is a placeholder: a dark rounded square with a white
|
||||
"Aя". Before publishing to the Store it is worth replacing with a real one:
|
||||
rewriting New-Logo is enough, the rest of the script does not depend on the
|
||||
drawing.
|
||||
|
||||
.EXAMPLE
|
||||
pwsh -File Packaging\New-Assets.ps1
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
# Where to put the MSIX logos
|
||||
[string] $AssetsPath,
|
||||
|
||||
# Where to put the .ico for the exe
|
||||
[string] $IconPath
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# The defaults are worked out here rather than in the parameter declarations:
|
||||
# in Windows PowerShell $PSScriptRoot is still empty inside param()
|
||||
$root = $PSScriptRoot
|
||||
if (-not $AssetsPath) { $AssetsPath = Join-Path $root 'Assets' }
|
||||
if (-not $IconPath) { $IconPath = Join-Path $root '..\CursorLang\Resources\CursorLang.ico' }
|
||||
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
|
||||
$Background = [System.Drawing.Color]::FromArgb(255, 0x20, 0x20, 0x20)
|
||||
$Foreground = [System.Drawing.Color]::FromArgb(255, 0xFF, 0xFF, 0xFF)
|
||||
$Glyph = 'Aя'
|
||||
|
||||
function New-Logo {
|
||||
param(
|
||||
[int] $Width,
|
||||
[int] $Height,
|
||||
|
||||
# The plate under the glyph. The Windows taskbar draws its shortcut
|
||||
# without one, so for those sizes only the white glyph remains
|
||||
[bool] $WithPlate = $true
|
||||
)
|
||||
|
||||
$bitmap = New-Object System.Drawing.Bitmap($Width, $Height, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
||||
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
||||
|
||||
try {
|
||||
$graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
|
||||
$graphics.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::AntiAliasGridFit
|
||||
$graphics.Clear([System.Drawing.Color]::Transparent)
|
||||
|
||||
$side = [Math]::Min($Width, $Height)
|
||||
|
||||
if ($WithPlate) {
|
||||
# The corner radius is taken as a share of the side so that small
|
||||
# sizes do not turn into a circle and large ones do not look like a
|
||||
# plain square
|
||||
$radius = [Math]::Max(2, [int]($side * 0.18))
|
||||
$plate = New-Object System.Drawing.Drawing2D.GraphicsPath
|
||||
|
||||
$plate.AddArc(0, 0, $radius * 2, $radius * 2, 180, 90)
|
||||
$plate.AddArc($Width - $radius * 2 - 1, 0, $radius * 2, $radius * 2, 270, 90)
|
||||
$plate.AddArc($Width - $radius * 2 - 1, $Height - $radius * 2 - 1, $radius * 2, $radius * 2, 0, 90)
|
||||
$plate.AddArc(0, $Height - $radius * 2 - 1, $radius * 2, $radius * 2, 90, 90)
|
||||
$plate.CloseFigure()
|
||||
|
||||
$brush = New-Object System.Drawing.SolidBrush($Background)
|
||||
try { $graphics.FillPath($brush, $plate) } finally { $brush.Dispose(); $plate.Dispose() }
|
||||
}
|
||||
|
||||
# Typographic metrics leave out the side bearings MeasureString reports by
|
||||
# default. Measured with those bearings the glyph looks wider than it is,
|
||||
# the fitted type comes out too small, and at 16 px the strokes wash out
|
||||
[System.Drawing.StringFormat] $format = [System.Drawing.StringFormat]::GenericTypographic.Clone()
|
||||
|
||||
try {
|
||||
# The type size is found by trying: with different tile sizes the same
|
||||
# share of the side gives text that is either cramped or too small
|
||||
$target = $side * 0.80
|
||||
$ceiling = $side * 0.86
|
||||
$fontSize = $side * 0.9
|
||||
$font = $null
|
||||
$measured = $null
|
||||
|
||||
for ($attempt = 0; $attempt -lt 20; $attempt++) {
|
||||
if ($font) { $font.Dispose() }
|
||||
$font = New-Object System.Drawing.Font('Segoe UI', $fontSize, [System.Drawing.FontStyle]::Bold, [System.Drawing.GraphicsUnit]::Pixel)
|
||||
$measured = $graphics.MeasureString($Glyph, $font, ([System.Drawing.PointF]::new(0, 0)), $format)
|
||||
|
||||
if ($measured.Width -le $target -and $measured.Height -le $ceiling) { break }
|
||||
|
||||
# Both dimensions are pulled in at once, otherwise a tall glyph
|
||||
# keeps overflowing while the width already fits
|
||||
$scale = [Math]::Min($target / $measured.Width, $ceiling / $measured.Height)
|
||||
$fontSize = $fontSize * $scale * 0.98
|
||||
}
|
||||
|
||||
try {
|
||||
# AntiAliasGridFit snaps the stems onto the pixel grid, so the
|
||||
# glyph is placed at a whole pixel: a fractional origin spreads
|
||||
# every stem across two pixels, which is what blurred the small
|
||||
# sizes when the text was centred inside a RectangleF
|
||||
$x = [Math]::Round(($Width - $measured.Width) / 2.0)
|
||||
$y = [Math]::Round(($Height - $measured.Height) / 2.0)
|
||||
|
||||
$brush = New-Object System.Drawing.SolidBrush($Foreground)
|
||||
$origin = New-Object System.Drawing.PointF($x, $y)
|
||||
|
||||
try { $graphics.DrawString($Glyph, $font, $brush, $origin, $format) }
|
||||
finally { $brush.Dispose() }
|
||||
}
|
||||
finally {
|
||||
$font.Dispose()
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$format.Dispose()
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$graphics.Dispose()
|
||||
}
|
||||
|
||||
return $bitmap
|
||||
}
|
||||
|
||||
function Save-Logo {
|
||||
param(
|
||||
[string] $Path,
|
||||
[int] $Width,
|
||||
[int] $Height,
|
||||
[bool] $WithPlate = $true
|
||||
)
|
||||
|
||||
$bitmap = New-Logo -Width $Width -Height $Height -WithPlate $WithPlate
|
||||
try { $bitmap.Save($Path, [System.Drawing.Imaging.ImageFormat]::Png) } finally { $bitmap.Dispose() }
|
||||
Write-Host " $([System.IO.Path]::GetFileName($Path))"
|
||||
}
|
||||
|
||||
function Save-Icon {
|
||||
param(
|
||||
[string] $Path,
|
||||
[int[]] $Sizes
|
||||
)
|
||||
|
||||
# The .ico is put together by hand: System.Drawing can only save a single
|
||||
# size to that format, and the icon of the exe is needed in several at once
|
||||
$images = @()
|
||||
|
||||
foreach ($size in $Sizes) {
|
||||
$bitmap = New-Logo -Width $size -Height $size
|
||||
try {
|
||||
$stream = New-Object System.IO.MemoryStream
|
||||
$bitmap.Save($stream, [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
$images += , @{ Size = $size; Bytes = $stream.ToArray() }
|
||||
$stream.Dispose()
|
||||
}
|
||||
finally {
|
||||
$bitmap.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
$output = New-Object System.IO.MemoryStream
|
||||
$writer = New-Object System.IO.BinaryWriter($output)
|
||||
|
||||
try {
|
||||
$writer.Write([uint16]0) # reserved
|
||||
$writer.Write([uint16]1) # type: icon
|
||||
$writer.Write([uint16]$images.Count)
|
||||
|
||||
# The directory of entries comes before the pictures themselves, so the
|
||||
# offsets are worked out beforehand: the header plus 16 bytes per entry
|
||||
$offset = 6 + 16 * $images.Count
|
||||
|
||||
foreach ($image in $images) {
|
||||
# 256 does not fit in a byte and is written as zero — the format
|
||||
# means it that way
|
||||
$dimension = if ($image.Size -ge 256) { 0 } else { $image.Size }
|
||||
|
||||
$writer.Write([byte]$dimension) # width
|
||||
$writer.Write([byte]$dimension) # height
|
||||
$writer.Write([byte]0) # palette colours: no palette
|
||||
$writer.Write([byte]0) # reserved
|
||||
$writer.Write([uint16]1) # planes
|
||||
$writer.Write([uint16]32) # bits per pixel
|
||||
$writer.Write([uint32]$image.Bytes.Length)
|
||||
$writer.Write([uint32]$offset)
|
||||
|
||||
$offset += $image.Bytes.Length
|
||||
}
|
||||
|
||||
foreach ($image in $images) {
|
||||
$writer.Write($image.Bytes)
|
||||
}
|
||||
|
||||
$writer.Flush()
|
||||
[System.IO.File]::WriteAllBytes($Path, $output.ToArray())
|
||||
}
|
||||
finally {
|
||||
$writer.Dispose()
|
||||
$output.Dispose()
|
||||
}
|
||||
|
||||
Write-Host " $([System.IO.Path]::GetFileName($Path))"
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Path $AssetsPath -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path (Split-Path -Parent $IconPath) -Force | Out-Null
|
||||
|
||||
Write-Host 'MSIX logos:'
|
||||
Save-Logo -Path (Join-Path $AssetsPath 'StoreLogo.png') -Width 50 -Height 50
|
||||
Save-Logo -Path (Join-Path $AssetsPath 'Square44x44Logo.png') -Width 44 -Height 44
|
||||
Save-Logo -Path (Join-Path $AssetsPath 'Square71x71Logo.png') -Width 71 -Height 71
|
||||
Save-Logo -Path (Join-Path $AssetsPath 'Square150x150Logo.png') -Width 150 -Height 150
|
||||
Save-Logo -Path (Join-Path $AssetsPath 'Square310x310Logo.png') -Width 310 -Height 310
|
||||
Save-Logo -Path (Join-Path $AssetsPath 'Wide310x150Logo.png') -Width 310 -Height 150
|
||||
|
||||
# The taskbar and the application list take the shortcut without a plate
|
||||
Save-Logo -Path (Join-Path $AssetsPath 'Square44x44Logo.targetsize-24_altform-unplated.png') -Width 24 -Height 24 -WithPlate $false
|
||||
Save-Logo -Path (Join-Path $AssetsPath 'Square44x44Logo.targetsize-32_altform-unplated.png') -Width 32 -Height 32 -WithPlate $false
|
||||
Save-Logo -Path (Join-Path $AssetsPath 'Square44x44Logo.targetsize-48_altform-unplated.png') -Width 48 -Height 48 -WithPlate $false
|
||||
|
||||
Write-Host 'The exe icon:'
|
||||
Save-Icon -Path ([System.IO.Path]::GetFullPath($IconPath)) -Sizes @(16, 24, 32, 48, 64, 128, 256)
|
||||
Reference in New Issue
Block a user