// 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);
}
}
}