// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using SixLabors.Fonts.Utilities; using SixLabors.Fonts.WellKnownIds; namespace SixLabors.Fonts.Tables.General.Name { /// /// Represents the OpenType 'name' table, which contains human-readable string names /// for features, font metadata, and other descriptive information. /// /// internal class NameTable : Table { /// /// The table tag name identifying the 'name' table. /// internal const string TableName = "name"; /// /// The array of name records contained in this table. /// private readonly NameRecord[] names; /// /// Initializes a new instance of the class. /// /// The name records. /// The language tag strings (format 1 only). internal NameTable(NameRecord[] names, string[] languages) => this.names = names; /// /// Gets the unique font identifier for the specified culture. /// /// The culture used to select the appropriate language-specific name. /// The unique font identifier string. public string Id(CultureInfo culture) => this.GetNameById(culture, KnownNameIds.UniqueFontID); /// /// Gets the full font name for the specified culture. /// /// The culture used to select the appropriate language-specific name. /// The full font name string. public string FontName(CultureInfo culture) => this.GetNameById(culture, KnownNameIds.FullFontName); /// /// Gets the font family name for the specified culture. /// /// The culture used to select the appropriate language-specific name. /// The font family name string. public string FontFamilyName(CultureInfo culture) => this.GetNameById(culture, KnownNameIds.FontFamilyName); /// /// Gets the font subfamily name (e.g., "Bold", "Italic") for the specified culture. /// /// The culture used to select the appropriate language-specific name. /// The font subfamily name string. public string FontSubFamilyName(CultureInfo culture) => this.GetNameById(culture, KnownNameIds.FontSubfamilyName); /// /// Gets the name string for the specified culture and name identifier. /// Falls back to US English (0x0409), then the first Windows platform record, then any record. /// /// The culture used to select the appropriate language-specific name. /// The name identifier to look up. /// The name string, or if not found. public string GetNameById(CultureInfo culture, KnownNameIds nameId) { int languageId = culture.LCID; NameRecord? usaVersion = null; NameRecord? firstWindows = null; NameRecord? first = null; foreach (NameRecord name in this.names) { if (name.NameID == nameId) { // Get just the first one, just in case. first ??= name; if (name.Platform == PlatformIDs.Windows) { // If us not found return the first windows one. firstWindows ??= name; if (name.LanguageID == 0x0409) { // Grab the us version as its on next best match. usaVersion ??= name; } if (name.LanguageID == languageId) { // Return the most exact first. return name.Value; } } } } return usaVersion?.Value ?? firstWindows?.Value ?? first?.Value ?? string.Empty; } /// /// Gets the name string for the specified culture and raw name identifier. /// /// The culture used to select the appropriate language-specific name. /// The raw name identifier value to look up. /// The name string, or if not found. public string GetNameById(CultureInfo culture, ushort nameId) => this.GetNameById(culture, (KnownNameIds)nameId); /// /// Loads the from the specified font reader. /// /// The font reader to read the table from. /// The loaded . /// Thrown when the table is missing from the font. public static NameTable? Load(FontReader fontReader) { if (!fontReader.TryGetReaderAtTablePosition(TableName, out BigEndianBinaryReader? binaryReader)) { throw new InvalidFontTableException($"Table '{TableName}' is missing", TableName); } using (binaryReader) { // Move to start of table. return Load(binaryReader); } } /// /// Loads the from the specified binary reader. /// /// The binary reader positioned at the start of the name table data. /// The loaded . public static NameTable Load(BigEndianBinaryReader reader) { var strings = new List(); ushort format = reader.ReadUInt16(); ushort nameCount = reader.ReadUInt16(); ushort stringOffset = reader.ReadUInt16(); var names = new NameRecord[nameCount]; for (int i = 0; i < nameCount; i++) { names[i] = NameRecord.Read(reader); StringLoader? sr = names[i].StringReader; if (sr is not null) { strings.Add(sr); } } StringLoader[]? langs = Array.Empty(); if (format == 1) { // Format 1 adds language data. ushort langCount = reader.ReadUInt16(); langs = new StringLoader[langCount]; for (int i = 0; i < langCount; i++) { langs[i] = StringLoader.Create(reader); strings.Add(langs[i]); } } foreach (StringLoader readable in strings) { int readableStartOffset = stringOffset + readable.Offset; reader.Seek(readableStartOffset, SeekOrigin.Begin); readable.LoadValue(reader); } string[] langNames = langs?.Select(x => x.Value).ToArray() ?? Array.Empty(); return new NameTable(names, langNames); } } }