$ErrorActionPreference = 'Stop' # Big-endian readers. NOTE: must cast to [int] first - [byte] -shl wraps in PS 5.1. function R16([byte[]]$b, [int]$p) { return [int]($b[$p]) -shl 8 -bor [int]$b[$p+1] } function R32([byte[]]$b, [int]$p) { return ([int]$b[$p] -shl 24) -bor ([int]$b[$p+1] -shl 16) -bor ([int]$b[$p+2] -shl 8) -bor [int]$b[$p+3] } # Correct OTF/TTF name-table parser (table-record offset field is at +8) function Get-FontNames([string]$path) { $bytes = [IO.File]::ReadAllBytes($path) # header: major(2) minor(2) numTables(2) searchedNum(2) rangeShift(2) = 12 bytes $numTables = R16 $bytes 4 $pos = 12 $nameOff = -1 for ($i = 0; $i -lt $numTables; $i++) { $rec = $pos + $i * 16 $tag = [Text.Encoding]::ASCII.GetString($bytes, $rec, 4) if ($tag -eq 'name') { $nameOff = R32 $bytes ($rec + 8) } } if ($nameOff -lt 0) { Write-Output ((Split-Path $path -Leaf) + " : NO name TABLE"); return } $base = $nameOff $count = R16 $bytes ($base + 2) # stringOffset: spec says 32-bit, but some builds (incl. some LM releases) store 16-bit. # A valid value must point at/after the end of the record block (base + 6 + count*12). $recordsEnd = 6 + $count * 12 $strOff32 = R32 $bytes ($base + 4) if ($strOff32 -ge $recordsEnd -and ($base + $strOff32) -le $bytes.Length) { $strOff = $strOff32 } else { $strOff = R16 $bytes ($base + 4) } $strBase = $base + $strOff $res = @{} $macRes = @{} for ($i = 0; $i -lt $count; $i++) { $r = $base + 6 + $i * 12 $plat = R16 $bytes $r $lang = R16 $bytes ($r + 4) $id = R16 $bytes ($r + 6) $len = R16 $bytes ($r + 8) $off = R16 $bytes ($r + 10) if ($len -eq 0 -or ($strBase + $off + $len) -gt $bytes.Length) { continue } $bytes2 = New-Object byte[] $len [Array]::Copy($bytes, $strBase + $off, $bytes2, 0, $len) if ($plat -eq 3 -and $lang -eq 0x0409) { if (-not $res.ContainsKey("n$id")) { $res["n$id"] = [Text.Encoding]::Unicode.GetString($bytes2) } } elseif ($plat -eq 1) { if (-not $macRes.ContainsKey("n$id")) { $macRes["n$id"] = [Text.Encoding]::ASCII.GetString($bytes2) } } } $pick = { param($id) if ($res.ContainsKey("n$id")) { $res["n$id"] } elseif ($macRes.ContainsKey("n$id")) { $macRes["n$id"] } else { '' } } $fam = & $pick 16; if (-not $fam) { $fam = & $pick 1 } $sub = & $pick 17; if (-not $sub) { $sub = & $pick 2 } Write-Output ("{0,-28} family = {1} | subfamily = {2}" -f (Split-Path $path -Leaf), $fam, $sub) } $lmDir = "c:\Users\fdavalos\OneDrive - up.edu.mx\microtracking\MicrotrackingFer\tools\fonts\lm\fonts\opentype\public\lm" Write-Output '=== Local LM samples (family used by fontconfig) ===' foreach ($f in @('lmroman10-regular.otf','lmsans10-regular.otf','lmmono10-regular.otf','lmmonolt10-regular.otf')) { $p = Join-Path $lmDir $f if (Test-Path $p) { Get-FontNames $p } else { Write-Output "$f : MISSING" } } Write-Output '' Write-Output '=== Windows fonts (ptm / phv candidates) ===' foreach ($f in @('times.ttf','timesbd.ttf','timesi.ttf','timesbi.ttf','arial.ttf','arialbd.ttf','ariali.ttf','arialbi.ttf')) { $p = Join-Path $env:windir "Fonts\$f" if (Test-Path $p) { Get-FontNames $p } else { Write-Output "$f : MISSING" } }