Skip to content

Instantly share code, notes, and snippets.

@jamikado
Last active July 7, 2020 04:52
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jamikado/dc9fdaba9e1b34567b9d to your computer and use it in GitHub Desktop.
Save jamikado/dc9fdaba9e1b34567b9d to your computer and use it in GitHub Desktop.
FontUtils contains FontUtils.allFontNames():Vector.<String> which will return a complete sorted list of font names sorted alphabetically by base font name but then additionally secondary sorting based on font weight names. Additionally, on iOS discovery of more working weighted and fixed names are added. NOTE: this uses FTE text engine and so th…
package
{
import flash.system.Capabilities;
import flash.text.Font;
import flash.text.engine.FontDescription;
import flash.text.engine.FontPosture;
import flash.text.engine.FontWeight;
import flash.ui.Mouse;
import flash.utils.Dictionary;
public class FontUtils
{
public static const SKIPCHECKS:Vector.<String> = Vector.<String>([
"Bold", "Italic", "Oblique", "Light", "Regular",
"Medium", "Roman", "Condensed", "Black", "Compressed", "Semibold", "SemiBold"]);
public static const CHECKS:Vector.<String> = Vector.<String>([
"Bold", "Italic", "Bold Italic", "Bold Oblique",
"Light", "Light Italic", "Light Oblique", "Oblique",
"Black", "Black Italic", "Thin", "Wide", "Plain",
"Condensed", "Condensed Light", "Condensed Bold",
"Condensed Black", "Condensed Medium", "Condensed ExtraBold",
"Medium", "Medium Italic", "ExtraBold", "ExtraBlack", "Regular",
// for now only check for SemiBold not Semibold knowing only case is on iOS if we ever needed to avoid
// duplication on Semibold would need to add case insensitive checking on a lowercase dictionary copy...
"SemiBold", "SemiBold Italic",
"Book", "Book Italic", "UltraLight", "UltraLight Italic"]);
// These must be in the order from longest to shortest so
// a match always occurs on the longest possible match
// Note they all must begin with a space as these are also used to strip from the end of the font name
// to create the base names
public static const ORDERED_ENDINGS_15:Vector.<String> = Vector.<String>([
// len >= 15
" Ultra Light Italic",
" UltraLight Italic",
" Ultra Compressed",
" Extra Compressed",
" Condensed ExtraBold",
" Condensed Medium",
" Condensed Black",
" Condensed Bold Italic",
" Condensed Bold Oblique",
" Light Condensed Italic",
" Bold Condensed Italic",
" Black Condensed",
" Condensed Bold",
" Condensed Italic",
" Condensed Oblique",
" Bold Condensed",
" Condensed Light",
" Light Condensed",
" SemiBold Italic",
" Semibold Italic"
]);
public static const ORDERED_ENDINGS_12:Vector.<String> = Vector.<String>([
// len >= 12
" Bold Oblique",
" BoldOblique",
" Bold Italic",
" Bold Slanted",
" Bold Inclined",
" Black Italic",
" Black Oblique",
" Book Italic",
" Book Oblique",
" Medium Italic",
" Medium Oblique",
" Light Italic",
" LightItalic",
" Light Oblique"
]);
public static const ORDERED_ENDINGS_9:Vector.<String> = Vector.<String>([
// len >= 9
" BoldItalic",
" Semibold",
" SemiBold",
" ExtraBlack",
" ExtraBold",
" Condensed",
" Compressed",
" Ultra Light",
" UltraLight"
]);
public static const ORDERED_ENDINGS_5:Vector.<String> = Vector.<String>([
// len >= 5
" Bold",
" Italic",
" Slanted",
" Oblique",
" Regular",
" Roman",
" Medium",
" Heavy",
" Black",
" Light",
" Plain",
" Wide",
" Thin",
" Book",
" Narrow"
]);
public static const ENDING_WEIGHTS:Object = {
// lower than Regular:
" Light Condensed Italic":-32,
" Condensed Light":-30,
" Light Condensed":-30,
" Ultra Compressed":-28,
" Extra Compressed":-26,
" Narrow":-24,
" Condensed":-22,
" Compressed":-22,
" Condensed Medium":-20,
" Condensed Italic":-18,
" Condensed Oblique":-18,
" Condensed Bold":-16,
" Bold Condensed":-16,
" Condensed Black":-14,
" Black Condensed":-14,
" Condensed ExtraBold":-12,
" Condensed Bold Italic":-10,
" Condensed Bold Oblique":-10,
" Bold Condensed Italic":-10,
" UltraLight":-8,
" Ultra Light":-8,
" UltraLight Italic":-6,
" Ultra Light Italic":-6,
" Light":-4,
" Light Italic":-2,
" LightItalic":-2,
" Light Oblique":-2,
// Regular and none is zero
" Regular":0,
// higher than Regular
" Plain":2,
" Thin":4,
" Book":6,
" Book Italic":8,
" Book Oblique":8,
" Italic":10,
" Oblique":10,
" Slanted":10,
" Inclined":10,
" Roman":11,
" Medium":12,
" Medium Italic":14,
" Medium Oblique":14,
" Semibold":16,
" SemiBold":16,
" SemiBold Italic":18,
" Semibold Italic":18,
" Heavy":19,
" Wide":20,
" Bold":22,
" ExtraBold":24,
" Bold Oblique":26,
" BoldOblique":26,
" Bold Italic":26,
" BoldItalic":26,
" Bold Slanted":26,
" Bold Inclined":26,
" Black":28,
" ExtraBlack":30,
" Black Italic":32,
" Black Oblique":32
};
public static function allFontNames():Vector.<String>
{
var allFonts:Array = new Array();
try {
allFonts = Font.enumerateFonts(true);
}
catch (e:Error) {
allFonts = new Array();
}
// building a local dictionary so we have unique items
var fontNameDict:Dictionary = new Dictionary(false);
// first place all base font names in dictionary
var fcount:int = allFonts.length;
var fontName:String;
for (var i:int = 0; i < fcount; ++i) {
fontName = allFonts[i].fontName;
fontNameDict[fontName] = fontName;
}
// As it turns out from extensive tests, desktops will report
// 99% unique proper font names and so doing the extra work to modify
// or check for hidden forms not only causes extensive startup slowdowns
// but also doesn't really lead to many missing cases
// For now only do checks for other weighted names on mobile only
if (Mouse.supportsCursor == false) {
// do case insensitive contains as skips
var skipCount:int = SKIPCHECKS.length;
var checkCount:int = CHECKS.length;
var check:String;
var skip:Boolean;
for (i = 0; i < fcount; ++i) {
fontName = allFonts[i].fontName;
skip = false;
// try to skip looping over font names that already have the common styles in name
// since we know they won't be base forms
for (var k:int = 0; k < skipCount; ++k) {
if (fontName.indexOf(SKIPCHECKS[k]) != -1) {
skip = true;
break;
}
}
if (skip) continue;
for (var j:int = 0; j < checkCount; ++j) {
check = fontName + " " + CHECKS[j];
if (fontNameDict[check] == undefined) {
// expensive check so try and avoid full cycles
if (FontDescription.isDeviceFontCompatible(check, FontWeight.NORMAL, FontPosture.NORMAL)) {
fontNameDict[check] = check;
}
}
}
}
}
allFonts.length = 0;
// For given platforms we might first want to force remove names that we know
// are duplicated/ambiguous or not functional
// Special iOS mangling from known issues
if (Capabilities.version.toLowerCase().indexOf("ios") > -1) {
// test this special case added if not found, from certain versions
if (fontNameDict["AppleGothic Regular"] == undefined) {
fontNameDict["AppleGothic Regular"] = "AppleGothic Regular";
}
if (fontNameDict["Avenir Light"] != undefined) {
fontNameDict["Avenir Roman"] = "Avenir Roman";
fontNameDict["Avenir Heavy"] = "Avenir Heavy";
}
// special case not dynamically found with above checks
if (fontNameDict["Academy Engraved LET"] != undefined) {
delete fontNameDict["Academy Engraved LET"];
fontNameDict["Academy Engraved LET Plain:1.0"] = "Academy Engraved LET Plain:1.0";
}
// special cases of DIN non-Bolds showing up
if (fontNameDict["DIN Alternate"] != undefined) {
delete fontNameDict["DIN Alternate"];
}
if (fontNameDict["DIN Condensed"] != undefined) {
delete fontNameDict["DIN Condensed"];
fontNameDict["DIN Condensed Bold"] = "DIN Condensed Bold";
}
if (fontNameDict["Hiragino Kaku Gothic ProN"] != undefined) {
delete fontNameDict["Hiragino Kaku Gothic ProN"];
fontNameDict["Hiragino Kaku Gothic ProN W3"] = "Hiragino Kaku Gothic ProN W3";
fontNameDict["Hiragino Kaku Gothic ProN W6"] = "Hiragino Kaku Gothic ProN W6";
}
if (fontNameDict["Hiragino Mincho ProN"] != undefined) {
delete fontNameDict["Hiragino Mincho ProN"];
fontNameDict["Hiragino Mincho ProN W3"] = "Hiragino Mincho ProN W3";
fontNameDict["Hiragino Mincho ProN W6"] = "Hiragino Mincho ProN W6";
}
// cases that dont need to exist in their base form since the styled forms uniquely determine all active cases
delete fontNameDict["Apple SD Gothic Neo"];
delete fontNameDict["Bodoni 72"];
delete fontNameDict["Bodoni 72 Oldstyle"];
delete fontNameDict["Bodoni 72 Smallcaps"];
delete fontNameDict["Bradley Hand"];
delete fontNameDict["Chalkboard SE"];
delete fontNameDict["DB LCD Temp"];
delete fontNameDict["Futura"];
delete fontNameDict["Heiti SC"];
delete fontNameDict["Heiti TC"];
delete fontNameDict["Kailasa"];
delete fontNameDict["Marion"];
delete fontNameDict["Marker Felt"];
delete fontNameDict["Noteworthy"];
delete fontNameDict["Optima"];
delete fontNameDict["Party LET"];
// added in iOS 6/ iPhone 5
delete fontNameDict["Avenir"];
delete fontNameDict["Avenir Next"];
delete fontNameDict["Avenir Next Condensed"];
}
// removed because not really supported on any platform with FTE rendering
delete fontNameDict["Apple Color Emoji"];
return sortedFontNames(fontNameDict);
}
private static function sortFontObjects(a:Object, b:Object):int
{
var aName:String = (a.baseName as String).toLowerCase();
var bName:String = (b.baseName as String).toLowerCase();
var comp:int = (aName == bName ? 0 : (aName < bName ? -1 : 1));
if (comp == 0) {
var aWeight:Number = a.weight as Number;
var bWeight:Number = b.weight as Number;
return ( aWeight < bWeight ? -1 : (aWeight > bWeight ? 1 : 0));
}
return comp;
}
// Will return null if a suffix name is not part of the full name
private static function getSuffixName(fullName:String):String {
var fullLen:int = fullName.length;
var suffix:String;
var suffixLen:int;
var i:int;
var count:int;
if (fullLen >= 15) {
count = ORDERED_ENDINGS_15.length;
for(i=0; i<count; ++i) {
suffix = ORDERED_ENDINGS_15[i];
suffixLen = suffix.length;
if (fullLen > suffixLen && endsWith(fullName, fullLen, suffix, suffixLen))
return suffix;
}
}
if (fullLen >= 12) {
count = ORDERED_ENDINGS_12.length;
for(i=0; i<count; ++i) {
suffix = ORDERED_ENDINGS_12[i];
suffixLen = suffix.length;
if (fullLen > suffixLen && endsWith(fullName, fullLen, suffix, suffixLen))
return suffix;
}
}
if (fullLen >= 9) {
count = ORDERED_ENDINGS_9.length;
for(i=0; i<count; ++i) {
suffix = ORDERED_ENDINGS_9[i];
suffixLen = suffix.length;
if (fullLen > suffixLen && endsWith(fullName, fullLen, suffix, suffixLen))
return suffix;
}
}
if (fullLen >= 5) {
count = ORDERED_ENDINGS_5.length;
for(i=0; i<count; ++i) {
suffix = ORDERED_ENDINGS_5[i];
suffixLen = suffix.length;
if (fullLen > suffixLen && endsWith(fullName, fullLen, suffix, suffixLen))
return suffix;
}
}
return null;
}
private static function sortedFontNames(fontNameDict:Dictionary):Vector.<String> {
var fontObjects:Vector.<Object> = new Vector.<Object>();
for (var key:Object in fontNameDict) {
try {
var fontName:String = key as String;
if (fontName == null || fontName.length == 0) continue;
var oneObj:Object = new Object();
oneObj.name = fontName;
var suffixName:String = getSuffixName(fontName);
if (suffixName != null && suffixName.length > 0 && suffixName.length < fontName.length) {
oneObj.baseName = fontName.substring(0, fontName.length - suffixName.length);
oneObj.weight = ENDING_WEIGHTS[suffixName];
if (oneObj.weight == null || oneObj.weight == undefined) oneObj.weight = 0;
}
else {
oneObj.baseName = fontName;
oneObj.weight = 0;
}
fontObjects.push(oneObj);
}
catch (e:Error) {
// trace("Fonts-UnexpectedException BuildingWeights: " + key as String);
}
}
fontObjects.sort(sortFontObjects);
var count:int = fontObjects.length;
var allFontNames:Vector.<String> = new Vector.<String>(count);
for (var i:int = 0; i < count; ++i) {
allFontNames[i] = fontObjects[i].name;
}
fontObjects.length = 0;
return allFontNames;
}
private static function endsWith(input:String, inputLen:int, suffix:String, suffixLen:int):Boolean
{
return suffix == input.substring(inputLen - suffixLen);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment