Partial solution for anyone out there out finds this thread:
Because I could not force ligatures down Unity's wretched throat any other way, I made a last minute processing system to bypass the problem. I took all the glyphs for correctly rendered ligatures and pasted them over top of unicode characters that I am not using. After this, I made a text file of this format:
AA=¢
Aa=¢
aa=£
CH=¤
Ch=¥
ch=¦
EE=§
Ee=§
ee=¨
GH=©
etc...
This solution covers only two character glyphs. Sorry to not have something better, but I already wasted way too much time on this. With some work, the system could be adapted for arbitrarily lengthed ligatures. The order in this list also represents the PRIORITY of the ligatures. Earlier ones are checked first. They are applied in a left to right fashion. Tags are ignored and unmodified, so you don't need to worry about those. The code to apply this is below. I realize that it is not optimized, but again. Already over-engineered for the a feature as tiny as this one in my game. Some of the code in here is specific to my game, but I feel like anyone looking for this kind of solution will have no trouble with that.
private static string ApplyLigatures(string target, string ligatureFile)
{
var ligatureAsset = Resources.Load<UnityEngine.TextAsset>($"STMFonts/{ligatureFile}_lig");
if (ligatureAsset == null)
return target;
// capture ligatures (dummied to unused unicode positions)
var ligatureMap = new Dictionary<string, string>();
foreach (var line in ligatureAsset.text.Split('\n'))
{
var values = line.Split('=');
// currently each ligature must be exactly 2 characters long (may upgrade later) - dummy character MUST be single character
if (values.Count() != 2 || values[0].Length != 2 || values[1].Length != 1)
throw new Exception($"Improperly formatted ligature file: {ligatureFile}: {line}");
ligatureMap[values[0]] = values[1];
}
// iterate over length of string...
var result = "";
for (int i = 0; i < target.Length; i++)
{
// capture tags
if (target[i] == '<')
{
var tag = target.Substring(i, target.IndexOf('>', i) - i + 1);
result += tag;
i += (tag.Length - 1); // account for the +1 on loop
continue;
}
// test if the character should be replaced with a ligature
if (i < target.Length - 1)
{
var testPair = target.Substring(i, 2);
if (ligatureMap.ContainsKey(testPair))
{
result += ligatureMap[testPair];
i++; // only add 1 (account for the +1 on loop)
continue;
}
}
// if nothing else fired off, just add the character
if (i < target.Length)
result += target[i];
}
return result;
}