2017-10-17 22:40:07 -05:00
// pseudo-go
func (f *CTFont) IsRegistered() bool {
n := f.Attribute(kCTFontRegistrationScopeAttribute)
if n == nil {
return false
}
return n.(*CFNumber).Uint32Value() == kCTFontManagerScopeNone
}
2017-10-18 22:59:54 -05:00
// based on CoreGraphics dylib's _CGFontCopyName
// note that this is different from the public API function CGFontCopyPostScriptName() (which is font type-independent)
// 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
2017-10-19 18:15:46 -05:00
kCGFontNameKeyPreferredSubfamily = xxx
kCGFontNameKeyFontSubfamily = xxx
kCGFontNameKeyFullName = xxx
kCGFontNameKeyPreferredFamily = xxx
kCGFontNameKeyFontFamily = xxx
2017-10-18 22:59:54 -05:00
)
func (f *CGFont) CopyName(key int) (string, bool) {
2017-10-19 18:15:46 -05:00
// 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
2017-10-18 22:59:54 -05:00
}
2017-10-17 22:40:07 -05:00
// based on libFontRegistry.dylib's __ZN11TFontTraitsC2EP6CGFontRK13TFontMetadata — TFontTraits::TFontTraits(CGFont*, TFontMetadata const&)
func (f *Font) WeightFromFontRegistry32() float32 {
var weight float32
2017-10-18 22:59:54 -05:00
var hasWeight bool = false
2017-10-17 22:40:07 -05:00
cgfont := f.CGFont()
2017-10-18 22:59:54 -05:00
if f.RegistryHasMetadata() {
wv := f.RegistryMetadataValueForKey("MTD_Typeface_Weight_VisualDescriptor")
if wv != nil {
if wn, ok := wv.(string); ok {
// note: uses CFStringCompare(0)
switch wn {
case "reg":
weight = float32as(0.000000, 0x0)
hasWeight = true
case "semi":
weight = float32as(0.300000, 0x3e99999a)
hasWeight = true
case "bold":
weight = float32as(0.400000, 0x3ecccccd)
hasWeight = true
case "light":
weight = float32as(-0.400000, 0xbecccccd)
hasWeight = true
case "med":
weight = float32as(0.230000, 0x3e6b851f)
hasWeight = true
case "heavy":
weight = float32as(0.560000, 0x3f0f5c29)
hasWeight = true
case "black":
weight = float32as(0.620000, 0x3f1eb852)
hasWeight = true
case "thin":
weight = float32as(-0.600000, 0xbf19999a)
hasWeight = true
case "ulight":
weight = float32as(-0.800000, 0xbf4ccccd)
hasWeight = true
}
}
}
}
cgpsname, ok := cgfont.CopyName(kCGFontNameKeyPostScriptName)
if ok {
// note: uses CFStringCompare(0)
switch cgpsname {
case "LucidaGrande",
".LucidaGrandeUI",
".Keyboard":
weight = float32as(0.000000, 0x0)
hasWeight = true
case "STHeiti":
weight = float32as(0.240000, 0x3e75c28f)
hasWeight = true
case "STXihei":
weight = float32as(-0.100000, 0xbdcccccd)
hasWeight = true
case "TimesNewRomanPSMT":
weight = float32as(0.000000, 0x0)
hasWeight = true
}
}
2017-10-19 18:15:46 -05:00
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
2017-10-17 22:40:07 -05:00
}
// 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
func (f *Font) WeightFromFontRegistry() float64 {
return CFNumberWithFloat32(f.WeightFromFontRegistry32()).Float64Value()
}
// based on CoreText dylib's __Z13WeightOfClasst — WeightOfClass(unsigned short)
func CoreText_WeightOfClass(usWeightClass uint16) float64 {
if usWeightClass >= 11 {
// do nothing; we are preserving the original asm comparisons
2017-10-19 18:15:46 -05:00
// and yes, this one is 11, but the one above is 10
2017-10-17 22:40:07 -05:00
} else {
usWeightClass *= 100
}
// figure out what two floats our weight will be between
i := usWeightClass / 100
j := i + 1
if j > 10 {
j = 10
}
b := float64(i * 100)
c := float64(j * 100)
a := float64(0)
if b != c {
a = float64(usWeightClass)
a -= b
c -= b
a /= c
}
scales := []float32{
float32as(-1.000000, 0xbf800000),
float32as(-0.700000, 0xbf333333),
float32as(-0.500000, 0xbf000000),
float32as(-0.230000, 0xbe6b851f),
float32as(0.000000, 0x0),
float32as(0.200000, 0x3e4ccccd),
float32as(0.300000, 0x3e99999a),
float32as(0.400000, 0x3ecccccd),
float32as(0.600000, 0x3f19999a),
float32as(0.800000, 0x3f4ccccd),
float32as(1.000000, 0x3f800000),
}
c = float64(scale[i])
b = float64[scale[j])
return fma(a, b, c)
}
// based on CoreText dylib's __ZL33CreateTraitsByStyleGlossaryStringPK10__CFString — CreateTraitsByStyleGlossaryString(__CFString const*)
func CoreText_WeightByStyleGlossaryString(str string) (weight float64, ok bool) {
str.Fold(kCFCompareCaseInsensitive, nil)
2017-10-19 18:15:46 -05:00
weightNameMap := []struct {
2017-10-17 22:40:07 -05:00
key string
val float32
}{
{ "ultra light", float32as(-0.800000, 0xbf4ccccd) },
{ "ultra black", float32as(0.750000, 0x3f400000) },
{ "extra light", float32as(-0.500000, 0xbf000000) },
{ "ultralight", float32as(-0.800000, 0xbf4ccccd) },
{ "ultrablack", float32as(0.750000, 0x3f400000) },
{ "extrablack", float32as(0.800000, 0x3f4ccccd) },
{ "extralight", float32as(-0.500000, 0xbf000000) }
{ "heavy face", float32as(0.560000, 0x3f0f5c29) },
{ "semi light", float32as(-0.200000, 0xbe4ccccd) },
{ "extra bold", float32as(0.500000, 0x3f000000) },
{ "ultra bold", float32as(0.700000, 0x3f333333) },
{ "heavyface", float32as(0.560000, 0x3f0f5c29) },
{ "extrabold", float32as(0.500000, 0x3f000000) },
{ "ultrabold", float32as(0.700000, 0x3f333333) },
{ "semilight", float32as(-0.200000, 0xbe4ccccd) },
{ "demi bold", float32as(0.250000, 0x3e800000) },
{ "semi bold", float32as(0.300000, 0x3e99999a) },
{ "demibold", float32as(0.250000, 0x3e800000) },
{ "semibold", float32as(0.300000, 0x3e99999a) },
{ "hairline", float32as(-0.700000, 0xbf333333) },
{ "medium", float32as(0.230000, 0x3e6b851f) },
{ "poster", float32as(0.800000, 0x3f4ccccd) },
{ "light", float32as(-0.400000, 0xbecccccd) },
{ "heavy", float32as(0.560000, 0x3f0f5c29) },
{ "extra", float32as(0.500000, 0x3f000000) },
{ "black", float32as(0.620000, 0x3f1eb852) },
{ "super", float32as(0.620000, 0x3f1eb852) },
{ "obese", float32as(0.850000, 0x3f59999a) },
{ "lite", float32as(-0.400000, 0xbecccccd) },
{ "book", float32as(-0.230000, 0xbe6b851f) },
{ "demi", float32as(0.250000, 0x3e800000) },
{ "semi", float32as(0.300000, 0x3e99999a) },
{ "thin", float32as(-0.500000, 0xbf000000) },
{ "bold", float32as(0.400000, 0x3ecccccd) },
{ "nord", float32as(0.800000, 0x3f4ccccd) },
{ "fat", float32as(0.750000, 0x3f400000) },
{ "w1", float32as(-0.700000, 0xbf333333) },
{ "w2", float32as(-0.500000, 0xbf000000) },
{ "w3", float32as(-0.230000, 0xbe6b851f) },
{ "w4", float32as(0.000000, 0x0) },
{ "w5", float32as(0.230000, 0x3e6b851f) },
{ "w6", float32as(0.300000, 0x3e99999a) },
{ "w7", float32as(0.440000, 0x3ee147ae) },
{ "w8", float32as(0.540000, 0x3f0a3d71) },
{ "w9", float32as(0.620000, 0x3f1eb852) },
}
for _, m := range weightNameMap {
if strstr(str, m.key) != nil {
val := CFNumberWithFloat32(m.val)
return val.Float64Value(), true
}
}
return 0, false
}
// based on CoreText dylib's __ZNK9TBaseFont29CreateTraitsValuesPerFontInfoEP12MetadataFlag — TBaseFont::CreateTraitsValuesPerFontInfo(MetadataFlag*) const
func (f *CTFont) Weight() float64 {
if f.IsRegistered() {
return f.WeightFromFontRegistry()
}
weight := float64as(2.0, 0x4000000000000000)
ebx := -1
hasWeight := false
name := f.Name(kCTFontPostScriptNameKey)
if name != nil {
switch *name {
case "LucidaGrande":
weight = float64as(0.000000, 0x0)
hasWeight = true
case ".LucidaGrandeUI":
weight = float64as(0.000000, 0x0)
hasWeight = true
case "STHeiti":
weight = float64as(0.240000, 0x3fceb851eb851eb8)
hasWeight = true
case "STXihei":
weight = float64as(-0.100000, 0xbfb999999999999a)
hasWeight = true
case "TimesNewRomanPSMT":
weight = float64as(0.000000, 0x0)
hasWeight = true
// there is one more hardcoded case, for "Times-Roman", but that will only set the class style, not the weight
}
}
os2table := f.Table('OS/2')
if os2table != nil {
if !hasWeight {
var usWeightClass uint16
valid := false
if os2table.Len() > 77 {
b := os2table.Bytes()
usWeightClass = uint16be(b[4:6])
if usWeightClass > 1000 {
weight = 0
hasWeight = false
} else {
valid = true
}
} else {
usWeightClass = 0
valid = true
}
if valid {
weight = CoreText_WeightOfClass(usWeightClass)
hasWeight = true
}
}
}
styleGlossaryNames := []string{
kCTFontSubFamilyNameKey,
kCTFontFullNameKey,
kCTFontFamilyNameKey,
}
for _, key := range styleGlossaryNames {
name := f.Name(key)
if name == nil {
continue
}
candidate, ok := CoreText_WeightByStyleGlossaryString(*name)
if !ok {
continue
}
if !hasWeight {
weight = candidate
hasWeight = true
}
}
if hasWeight {
return weight
}
return 0
}
func (f *Font) ShouldEnableBoldSymbolicTrait() bool {
if f.IsRegistered() {
return f.ShouldEnableBoldSymbolicTraitFromRegistry()
}
no := f.Weight() <= float64as(0.239000, 0x3fce978d4fdf3b64)
return !no
}