Harbor

branch main
showing the latest snapshot on main
os2_post.odin 6.9 KB · Plain text
os2_post.odin 0644 Raw
package font

// ============================================================================
// OS/2 TABLE — Font Metadata
// ============================================================================

// Font weight classification (from OS/2 usWeightClass)
Font_Weight :: enum u16 {
	Thin       = 100,
	Extra_Light = 200,
	Light      = 300,
	Normal     = 400,
	Medium     = 500,
	Semi_Bold  = 600,
	Bold       = 700,
	Extra_Bold = 800,
	Black      = 900,
}

// Font width classification (from OS/2 usWidthClass)
Font_Width :: enum u16 {
	Ultra_Condensed = 1,
	Extra_Condensed = 2,
	Condensed       = 3,
	Semi_Condensed  = 4,
	Normal          = 5,
	Semi_Expanded   = 6,
	Expanded        = 7,
	Extra_Expanded  = 8,
	Ultra_Expanded  = 9,
}

// OS/2 table data
OS2_Metrics :: struct {
	weight_class:     u16,
	width_class:      u16,
	fs_type:          u16,         // embedding permissions
	y_strikeout_size: i16,
	y_strikeout_pos:  i16,
	typo_ascender:    i16,
	typo_descender:   i16,
	typo_line_gap:    i16,
	win_ascent:       u16,
	win_descent:      u16,
	x_height:         i16,         // 0 if OS/2 version < 2
	cap_height:       i16,         // 0 if OS/2 version < 2
	unicode_range:    [4]u32,      // Unicode coverage bitfield
}

// Get OS/2 table metrics. Returns false if the table is not present.
get_os2_metrics :: proc(info: ^Font_Info, metrics: ^OS2_Metrics) -> bool {
	os2 := find_table(info.data, u32(info.fontstart), "OS/2")
	if os2 == 0 do return false

	data := info.data[os2:]
	version := ttUSHORT(data)

	metrics.weight_class     = ttUSHORT(data[4:])
	metrics.width_class      = ttUSHORT(data[6:])
	metrics.fs_type          = ttUSHORT(data[8:])
	metrics.y_strikeout_size = ttSHORT(data[26:])
	metrics.y_strikeout_pos  = ttSHORT(data[28:])
	metrics.unicode_range[0] = ttULONG(data[42:])
	metrics.unicode_range[1] = ttULONG(data[46:])
	metrics.unicode_range[2] = ttULONG(data[50:])
	metrics.unicode_range[3] = ttULONG(data[54:])
	metrics.typo_ascender    = ttSHORT(data[68:])
	metrics.typo_descender   = ttSHORT(data[70:])
	metrics.typo_line_gap    = ttSHORT(data[72:])
	metrics.win_ascent       = ttUSHORT(data[74:])
	metrics.win_descent      = ttUSHORT(data[76:])

	// x-height and cap-height only available in version >= 2
	if version >= 2 {
		metrics.x_height   = ttSHORT(data[86:])
		metrics.cap_height = ttSHORT(data[88:])
	}

	return true
}

// Convenience: get font weight as a classified enum
get_font_weight :: proc(info: ^Font_Info) -> Font_Weight {
	os2: OS2_Metrics
	if !get_os2_metrics(info, &os2) do return .Normal
	w := os2.weight_class
	switch {
	case w <= 150: return .Thin
	case w <= 250: return .Extra_Light
	case w <= 350: return .Light
	case w <= 450: return .Normal
	case w <= 550: return .Medium
	case w <= 650: return .Semi_Bold
	case w <= 750: return .Bold
	case w <= 850: return .Extra_Bold
	case:          return .Black
	}
}

// ============================================================================
// POST TABLE — Glyph Names
// ============================================================================

// Standard Mac glyph names (post format 1.0 uses these 258 names)
@(private)
MACINTOSH_GLYPH_NAMES := [258]string{
	".notdef", ".null", "nonmarkingreturn", "space", "exclam", "quotedbl",
	"numbersign", "dollar", "percent", "ampersand", "quotesingle", "parenleft",
	"parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash",
	"zero", "one", "two", "three", "four", "five", "six", "seven", "eight",
	"nine", "colon", "semicolon", "less", "equal", "greater", "question", "at",
	"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
	"O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
	"bracketleft", "backslash", "bracketright", "asciicircum", "underscore",
	"grave", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l",
	"m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
	"braceleft", "bar", "braceright", "asciitilde", "Adieresis", "Aring",
	"Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis", "aacute",
	"agrave", "acircumflex", "adieresis", "atilde", "aring", "ccedilla",
	"eacute", "egrave", "ecircumflex", "edieresis", "iacute", "igrave",
	"icircumflex", "idieresis", "ntilde", "oacute", "ograve", "ocircumflex",
	"odieresis", "otilde", "uacute", "ugrave", "ucircumflex", "udieresis",
	"dagger", "degree", "cent", "sterling", "section", "bullet", "paragraph",
	"germandbls", "registered", "copyright", "trademark", "acute", "dieresis",
	"notequal", "AE", "Oslash", "infinity", "plusminus", "lessequal",
	"greaterequal", "yen", "mu", "partialdiff", "summation", "product", "pi",
	"integral", "ordfeminine", "ordmasculine", "Omega", "ae", "oslash",
	"questiondown", "exclamdown", "logicalnot", "radical", "florin",
	"approxequal", "Delta", "guillemotleft", "guillemotright", "ellipsis",
	"nonbreakingspace", "Agrave", "Atilde", "Otilde", "OE", "oe", "endash",
	"emdash", "quotedblleft", "quotedblright", "quoteleft", "quoteright",
	"divide", "lozenge", "ydieresis", "Ydieresis", "fraction", "currency",
	"guilsinglleft", "guilsinglright", "fi", "fl", "daggerdbl", "periodcentered",
	"quotesinglbase", "quotedblbase", "perthousand", "Acircumflex",
	"Ecircumflex", "Aacute", "Edieresis", "Egrave", "Iacute", "Icircumflex",
	"Idieresis", "Igrave", "Oacute", "Ocircumflex", "apple", "Ograve",
	"Uacute", "Ucircumflex", "Ugrave", "dotlessi", "circumflex", "tilde",
	"macron", "breve", "dotaccent", "ring", "cedilla", "hungarumlaut",
	"ogonek", "caron", "Lslash", "lslash", "Scaron", "scaron", "Zcaron",
	"zcaron", "brokenbar", "Eth", "eth", "Yacute", "yacute", "Thorn",
	"thorn", "minus", "multiply", "onesuperior", "twosuperior", "threesuperior",
	"onehalf", "onequarter", "threequarters", "franc", "Gbreve", "gbreve",
	"Idotaccent", "Scedilla", "scedilla", "Cacute", "cacute", "Ccaron",
	"ccaron", "dcroat",
}

// Get the PostScript name of a glyph. Returns empty string if not available.
get_glyph_name :: proc(info: ^Font_Info, glyph_index: i32) -> string {
	post := find_table(info.data, u32(info.fontstart), "post")
	if post == 0 do return ""

	data := info.data[post:]
	version := ttULONG(data)

	switch version {
	case 0x00010000: // Format 1.0 — standard Mac names
		if glyph_index >= 0 && glyph_index < 258 {
			return MACINTOSH_GLYPH_NAMES[glyph_index]
		}
	case 0x00020000: // Format 2.0 — custom names
		num_glyphs := i32(ttUSHORT(data[32:]))
		if glyph_index < 0 || glyph_index >= num_glyphs do return ""

		name_index := i32(ttUSHORT(data[34 + u32(glyph_index) * 2:]))
		if name_index < 258 {
			return MACINTOSH_GLYPH_NAMES[name_index]
		}

		// Custom name: skip past name index array, then read Pascal strings
		name_index -= 258
		string_data := data[34 + u32(num_glyphs) * 2:]
		for i in 0..<name_index {
			str_len := i32(string_data[0])
			string_data = string_data[1 + str_len:]
		}
		str_len := int(string_data[0])
		if str_len == 0 do return ""
		return string(string_data[1:][:str_len])

	case 0x00030000: // Format 3.0 — no glyph names
		return ""
	}

	return ""
}