From f94de2eef850086bba72667e23890675fb7cca75 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Thu, 19 Oct 2017 19:15:46 -0400 Subject: [PATCH] And finally finished the core Core Text weight determination functionality pseudo-Go-code. God damn. And I have a funny feeling stretches are going to be a tad bit more inconsistent too... :| Not quite complete, though; need to fill in the name table parsing rules first. --- doc/export/ctweights | 263 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 261 insertions(+), 2 deletions(-) diff --git a/doc/export/ctweights b/doc/export/ctweights index 4cf2839d..4d25cc5f 100644 --- a/doc/export/ctweights +++ b/doc/export/ctweights @@ -13,8 +13,125 @@ func (f *CTFont) IsRegistered() bool { // also note that in reality these keys are strings but the implementation of the function turns them into ints and only uses them as such const ( kCGFontNameKeyPostScriptName = xxx + kCGFontNameKeyPreferredSubfamily = xxx + kCGFontNameKeyFontSubfamily = xxx + kCGFontNameKeyFullName = xxx + kCGFontNameKeyPreferredFamily = xxx + kCGFontNameKeyFontFamily = xxx ) func (f *CGFont) CopyName(key int) (string, bool) { + // TODO +} + +// based on libFontRegistry.dylib's __ZNK8OS2Table15DetermineWeightERf — OS2Table::DetermineWeight(float&) const +func RegistryDetermineOS2Weight(table *CFData) (float32, bool) { + if table == nil { + return 0, false + } + if table.Len() < 78 { + return 0, false + } + + b := table.Bytes() + usWeightClass := uint16be(b[4:6]) + if usWeightClass >= 10 { + // do nothing; we are preserving the original asm comparisons + } else { + usWeightClass *= 100 + } + /* TODO: +000000000000b37e mov dx, word [rax+4] +000000000000b382 mov cx, dx +000000000000b385 rol cx, 0x8 +000000000000b389 movzx esi, cx +000000000000b38c imul ecx, ecx, 100 +000000000000b38f cmp esi, 10 +000000000000b392 cmovae cx, si +000000000000b396 test dx, dx +000000000000b399 cmove cx, si + what's the function of the last two instructions? */ + + // note that this is an unsigned comparison, so underflow will result in a number > 998 + // the effect is the same as (usWeightClass == 0) || (usWeightClass >= 1000) + if (usWeightClass - 1) > 998 { + // note the - 2 here; the switch cases below reflect that! + // also note that b[0x22] and panose will be unsigned, so underflow will result in a number > 9 + panose := b[0x22] - 2 + if panose > 9 { + return 0, false + } + switch panose { + case 0: + return float32as(-0.500000, 0xbf000000), true + case 1: + return float32as(-0.400000, 0xbecccccd), true + case 2: + // yes, this returns false; I don't know why + return float32as(-0.300000, 0xbe99999a), false + case 3: + return float32as(-0.230000, 0xbe6b851f), true + case 4: + return float32as(0.230000, 0x3e6b851f), true + case 5: + return float32as(0.250000, 0x3e800000), true + case 6: + return float32as(0.400000, 0x3ecccccd), true + case 7: + return float32as(0.560000, 0x3f0f5c29), true + case 8: + return float32as(0.620000, 0x3f1eb852), true + case 9: + return float32as(0.800000, 0x3f4ccccd), true + } + // should not reach here + } + + // let's mimic the assembly here too + // the gotos avoid the massive if nesting + // also note I'm using Go idioms and not saying "else return", imagine those if you must + if usWeightClass > 100 { + if usWeightClass > 200 { + goto do201AndUp + } + return float32as(-0.500000, 0xbf000000), true + } + return float32as(-0.800000, 0xbf4ccccd), true + +do201AndUp: + if usWeightClass > 300 { + if usWeightClass > 400 { + goto do401AndUp + } + return float32as(0.000000, 0x0), true + } + return float32as(-0.400000, 0xbecccccd), true + +do401AndUp: + if usWeightClass > 500 { + if usWeightClass > 600 { + goto do601AndUp + } + return float32as(0.250000, 0x3e800000), true + } + return float32as(0.230000, 0x3e6b851), true + +do601AndUp: + if usWeightClass > 700 { + if usWeightClass > 800 { + goto do801AndUp + } + return float32as(0.500000, 0x3f000000), true + } + return float32as(0.400000, 0x3ecccccd), true + +do801AndUp: + if usWeightClass > 900 { + if usWeightClass > 950 { + return float32(0.800000, 0x3f4ccccd), true + } + return float32(0.750000, 0x3f400000), true + } + return float32as(0.620000, 0x3f1eb852), true } // based on libFontRegistry.dylib's __ZN11TFontTraitsC2EP6CGFontRK13TFontMetadata — TFontTraits::TFontTraits(CGFont*, TFontMetadata const&) @@ -81,6 +198,148 @@ func (f *Font) WeightFromFontRegistry32() float32 { hasWeight = true } } + + styleGlossaryStrings := []int{ + kCGFontNameKeyPreferredSubfamily, + kCGFontNameKeyFontSubfamily, + kCGFontNameKeyFullName, + kCGFontNameKeyPreferredFamily, + kCGFontNameKeyFontFamily, + } + weightNameMap := []struct { + key string + val float32 + }{ + { "Ultra Light", float32as(-0.800000f, 0xbf4ccccd) }, + { "Ultra Black", float32as(0.750000f, 0x3f400000) }, + { "Extra Light", float32as(-0.500000f, 0xbf000000) }, + { "UltraBlack", float32as(0.750000f, 0x3f400000) }, + { "ExtraBlack", float32as(0.800000f, 0x3f4ccccd) }, + { "UltraLight", float32as(-0.800000f, 0xbf4ccccd) }, + { "ExtraLight", float32as(-0.500000f, 0xbf000000) }, + { "Ultra Thin", float32as(-0.800000f, 0xbf4ccccd) }, + { "Extra Thin", float32as(-0.800000f, 0xbf4ccccd) }, + { "Heavy Face", float32as(0.560000f, 0x3f0f5c29) }, + { "Semi Light", float32as(-0.200000f, 0xbe4ccccd) }, + { "Extra Bold", float32as(0.500000f, 0x3f000000) }, + { "Ultra Bold", float32as(0.700000f, 0x3f333333) }, + { "HeavyFace", float32as(0.560000f, 0x3f0f5c29) }, + { "ExtraBold", float32as(0.500000f, 0x3f000000) }, + { "UltraBold", float32as(0.700000f, 0x3f333333) }, + { "Ext Black", float32as(0.800000f, 0x3f4ccccd) }, + { "SemiLight", float32as(-0.200000f, 0xbe4ccccd) }, + { "Demi Bold", float32as(0.250000f, 0x3e800000) }, + { "Semi Bold", float32as(0.300000f, 0x3e99999a) }, + { "Ext Light", float32as(-0.500000f, 0xbf000000) }, + { "Ext Bold", float32as(0.500000f, 0x3f000000) }, + { "DemiBold", float32as(0.250000f, 0x3e800000) }, + { "SemiBold", float32as(0.300000f, 0x3e99999a) }, + { "HairLine", float32as(-0.800000f, 0xbf4ccccd) }, + { "Ext Thin", float32as(-0.800000f, 0xbf4ccccd) }, + { "Medium", float32as(0.230000f, 0x3e6b851f) }, + { "Poster", float32as(0.800000f, 0x3f4ccccd) }, + { "Light", float32as(-0.400000f, 0xbecccccd) }, + { "Ultra", float32as(0.500000f, 0x3f000000) }, + { "Heavy", float32as(0.560000f, 0x3f0f5c29) }, + { "Extra", float32as(0.500000f, 0x3f000000) }, + { "Black", float32as(0.620000f, 0x3f1eb852) }, + { "Super", float32as(0.620000f, 0x3f1eb852) }, + { "Obese", float32as(0.850000f, 0x3f59999a) }, + { "Lite", float32as(-0.400000f, 0xbecccccd) }, + { "Book", float32as(-0.230000f, 0xbe6b851f) }, + { "Demi", float32as(0.250000f, 0x3e800000) }, + { "Semi", float32as(0.300000f, 0x3e99999a) }, + { "Thin", float32as(-0.500000f, 0xbf000000) }, + { "Bold", float32as(0.400000f, 0x3ecccccd) }, + { "Nord", float32as(0.800000f, 0x3f4ccccd) }, + { "Fat", float32as(0.750000f, 0x3f400000) }, + { "W1", float32as(-0.230000f, 0xbe6b851f) }, + { "W2", float32as(-0.500000f, 0xbf000000) }, + { "W3", float32as(-0.230000f, 0xbe6b851f) }, + { "W4", float32as(0.000000f, 0x0) }, + { "W5", float32as(0.230000f, 0x3e6b851f) }, + { "W6", float32as(0.300000f, 0x3e99999a) }, + { "W7", float32as(0.440000f, 0x3ee147ae) }, + { "W8", float32as(0.540000f, 0x3f0a3d71) }, + { "W9", float32as(0.620000f, 0x3f1eb852) }, + } + for _, key := range styleGlossaryStrings { + if hasWeight { + break + } + str, ok := cgfont.CopyName(key) + if !ok { + continue + } + for _, m := range weightNameMap { + if str.FindWithOptions(m.key, CFRangeMake(0, str.CFStringLength()), kCFCompareCaseInsensitive | kCFCompareBackwards | kCFCompareNonliteral, nil) { + weight = m.val + hasWeight = true + break + } + } + } + + if !hasWeight { + os2table := cgfont.TableForTag('OS/2') + weight, hasWeight = RegistryDetermineOS2Weight(os2table) + } + + if !hasWeight { + headtable := cgfont.TableForTag('head') + if headtable != nil { + if headtable.Len() >= 54 { + b := headtable.Bytes() + if (b[0x2d] & 1) != 0 { + weight = float32as(0.400000, 0x3ecccccd) + hasWeight = true + } + } + } + } + + styleGlossaryAbbreviationKeys := []int{ + kCGFontNameKeyPreferredSubfamily, + kCGFontNameKeyFontSubfamily, + } + abbreviatedWeightNameMap := []struct { + key string + val float32 + }{ + { "EL", float32as(-0.200000, 0xbe4ccccd) }, + { "EB", float32as(0.500000, 0x3f000000) }, + { "SB", float32as(0.300000, 0x3e99999a) }, + { "UH", float32as(0.800000, 0x3f4ccccd) }, + { "U", float32as(0.700000, 0x3f333333) }, + { "L", float32as(-0.400000, 0xbecccccd) }, + { "H", float32as(0.560000, 0x3f0f5c29) }, + { "B", float32as(0.400000, 0x3ecccccd) }, + { "M", float32as(0.230000, 0x3e6b851f) }, + { "R", float32as(0.000000, 0x0) }, + } + if !hasWeight { + for _, key := range styleGlossaryAbbreviationStrings { + str, ok := cgfont.CopyName(key) + if !ok { + continue + } + for _, m := range abbreviatedWeightNameMap { + if str.Compare(m.key, kCFCompareCaseInsensitive) == kCFCompareEqualTo { + weight = m.val + hasWeight = true + break + } + } + if hasWeight { + break + } + } + } + + if !hasWeight { + return float32as(0.000000, 0x0) + } + return weight } // because Core Text gets registry traits as a CFDictionary, convert the float to a double with CFNumber as that is what actually would be done @@ -92,6 +351,7 @@ func (f *Font) WeightFromFontRegistry() float64 { func CoreText_WeightOfClass(usWeightClass uint16) float64 { if usWeightClass >= 11 { // do nothing; we are preserving the original asm comparisons + // and yes, this one is 11, but the one above is 10 } else { usWeightClass *= 100 } @@ -133,8 +393,7 @@ func CoreText_WeightOfClass(usWeightClass uint16) float64 { // based on CoreText dylib's __ZL33CreateTraitsByStyleGlossaryStringPK10__CFString — CreateTraitsByStyleGlossaryString(__CFString const*) func CoreText_WeightByStyleGlossaryString(str string) (weight float64, ok bool) { str.Fold(kCFCompareCaseInsensitive, nil) - - var weightNameMap = []struct { + weightNameMap := []struct { key string val float32 }{