From 4119824ffcc6863cf8f52f587d35692a797585d9 Mon Sep 17 00:00:00 2001 From: GoldenCrystal Date: Fri, 21 Nov 2014 15:41:27 +0100 Subject: [PATCH] First tentative of name alias encoding. --- .../BinaryWriterExtensions.cs | 36 ++++++++++ .../UnicodeCharacterDataBuilder.cs | 70 ++++++++++++++----- UnicodeInformation/UcdFields.cs | 4 +- UnicodeInformation/UnicodeCharacterData.cs | 3 + UnicodeInformation/UnicodeInfo.cs | 47 +++++++++++-- UnicodeInformation/UnicodeInformation.csproj | 2 + UnicodeInformation/UnicodeNameAlias.cs | 16 +++++ UnicodeInformation/UnicodeNameAliasKind.cs | 11 +++ 8 files changed, 165 insertions(+), 24 deletions(-) create mode 100644 UnicodeInformation/UnicodeNameAlias.cs create mode 100644 UnicodeInformation/UnicodeNameAliasKind.cs diff --git a/UnicodeInformation.Builder/BinaryWriterExtensions.cs b/UnicodeInformation.Builder/BinaryWriterExtensions.cs index 2e4bdf1..3f97206 100644 --- a/UnicodeInformation.Builder/BinaryWriterExtensions.cs +++ b/UnicodeInformation.Builder/BinaryWriterExtensions.cs @@ -50,5 +50,41 @@ namespace System.Unicode.Builder writer.Write((byte)value); } } + + /// Writes a character name alias. + /// We assume that character names will not exceed 64 bytes in length. + /// The writer to use. + /// The name alias value to write. + public static void WriteNameAliasToFile(this BinaryWriter writer, UnicodeNameAlias nameAlias) + { + WriteNameToFile(writer, (byte)(nameAlias.Kind - 1), nameAlias.Name); + } + + /// Writes a character name, packing two information bits along with the length. + /// We assume that character names will not exceed 64 bytes in length. + /// The writer to use. + /// Extra bits to pack with the 6 bit length. + /// The name to write. + public static void WriteNameToFile(this BinaryWriter writer, byte extraBits, string name) + { + // This method will stuff two extra bits together with the byte count, provided that this one doesn't exceed 64. + var bytes = Encoding.UTF8.GetBytes(name); + if (bytes.Length > 64) throw new InvalidOperationException("Did not expect UTF-8 encoded names to be longer than 64 bytes."); + writer.WritePackedLength(extraBits, name.Length); + writer.Write(bytes); + } + + /// Writes a 6 bits length packed with two extra bits. + /// The parameters have a restricted range, which must be respected. + /// The writer used to perform the operation. + /// The value of the two extra bits. + /// The length to write. + public static void WritePackedLength(this BinaryWriter writer, byte extraBits, int length) + { + if (extraBits > 3) throw new ArgumentOutOfRangeException("extraBits"); + if (length < 1 || length > 64) throw new ArgumentOutOfRangeException("length"); + + writer.Write((byte)((extraBits << 6) | (length - 1))); + } } } diff --git a/UnicodeInformation.Builder/UnicodeCharacterDataBuilder.cs b/UnicodeInformation.Builder/UnicodeCharacterDataBuilder.cs index 5f5b58a..e0c17de 100644 --- a/UnicodeInformation.Builder/UnicodeCharacterDataBuilder.cs +++ b/UnicodeInformation.Builder/UnicodeCharacterDataBuilder.cs @@ -26,7 +26,8 @@ namespace System.Unicode.Builder private ContributoryProperties contributoryProperties; private CoreProperties coreProperties; - private List relatedCodePoints = new List(); + private readonly List nameAliases = new List(); + private readonly List relatedCodePoints = new List(); public UnicodeCharacterRange CodePointRange { get { return codePointRange; } } @@ -36,6 +37,8 @@ namespace System.Unicode.Builder set { name = value; } } + public ICollection NameAliases { get { return nameAliases; } } + public UnicodeCategory Category { get { return category; } @@ -138,33 +141,36 @@ namespace System.Unicode.Builder { return new UnicodeCharacterData ( - codePointRange, + CodePointRange, Name, - category, - canonicalCombiningClass, - bidirectionalClass, - characterDecompositionMapping.DecompositionType, - characterDecompositionMapping.DecompositionMapping, - numericType, - numericValue, - bidirectionalMirrored, - oldName, - simpleUpperCaseMapping, - simpleLowerCaseMapping, - simpleTitleCaseMapping, - contributoryProperties, - coreProperties, - relatedCodePoints.Count > 0 ? relatedCodePoints.ToArray() : null + NameAliases.Count > 0 ? NameAliases.ToArray() : UnicodeNameAlias.EmptyArray, + Category, + CanonicalCombiningClass, + BidirectionalClass, + CharacterDecompositionMapping.DecompositionType, + CharacterDecompositionMapping.DecompositionMapping, + NumericType, + NumericValue, + BidirectionalMirrored, + OldName, + SimpleUpperCaseMapping, + SimpleLowerCaseMapping, + SimpleTitleCaseMapping, + ContributoryProperties, + CoreProperties, + RelatedCodePoints.Count > 0 ? RelatedCodePoints.ToArray() : null ); } internal void WriteToFile(BinaryWriter writer) { + if (nameAliases.Count > 64) throw new InvalidDataException("Cannot handle more than 64 name aliases."); + UcdFields fields = default(UcdFields); if (!codePointRange.IsSingleCodePoint) fields = UcdFields.CodePointRange; - if (name != null) fields |= UcdFields.Name; + if (name != null || nameAliases.Count > 0) fields |= UcdFields.Name; // This field combines name and alias. if (category != UnicodeCategory.OtherNotAssigned) fields |= UcdFields.Category; if (canonicalCombiningClass != CanonicalCombiningClass.NotReordered) fields |= UcdFields.CanonicalCombiningClass; /*if (bidirectionalClass != 0)*/ @@ -184,7 +190,33 @@ namespace System.Unicode.Builder writer.WriteCodePoint(codePointRange.FirstCodePoint); if ((fields & UcdFields.CodePointRange) != 0) writer.WriteCodePoint(CodePointRange.LastCodePoint); - if ((fields & UcdFields.Name) != 0) writer.Write(name); + if ((fields & UcdFields.Name) != 0) + { + // We write the names by optimizing for the common case. + // i.e. Most characters have one name, and most of those characters have only one name. + // In the first byte, we will encode a 6bit length, along with two extra bits describing the structure: + // 00: The common case, this byte is the length of the name, and no other name follows. + // 01: This byte is the length of the name, and one alias follows. + // 10: This byte is the length of the name, and the number of aliases follows. + // 11: This byte is the number of aliases. + + if (name == null) + { + writer.WritePackedLength(3, nameAliases.Count); + } + else + { + writer.WriteNameToFile((byte)(nameAliases.Count > 0 ? nameAliases.Count > 1 ? 2 : 1 : 0), name); + } + + if (nameAliases.Count > 0) + { + if (name != null) writer.Write((byte)nameAliases.Count); + + foreach (var nameAlias in nameAliases) + writer.WriteNameAliasToFile(nameAlias); + } + } if ((fields & UcdFields.Category) != 0) writer.Write((byte)category); if ((fields & UcdFields.CanonicalCombiningClass) != 0) writer.Write((byte)canonicalCombiningClass); if ((fields & UcdFields.BidirectionalClass) != 0) writer.Write((byte)bidirectionalClass); diff --git a/UnicodeInformation/UcdFields.cs b/UnicodeInformation/UcdFields.cs index 4f6916e..0590f3e 100644 --- a/UnicodeInformation/UcdFields.cs +++ b/UnicodeInformation/UcdFields.cs @@ -14,7 +14,7 @@ namespace System.Unicode // Not really a field, just here to indicate that the entry is a range CodePointRange = 1, - Name = 2, + Name = 2, // Will stand in for official name as well as related names. Category = 4, CanonicalCombiningClass = 8, BidirectionalClass = 16, @@ -35,5 +35,7 @@ namespace System.Unicode ContributoryProperties = 8192, CoreProperties = 16384, + + RelatedCodePoints = 32768, } } diff --git a/UnicodeInformation/UnicodeCharacterData.cs b/UnicodeInformation/UnicodeCharacterData.cs index de7619b..73c66a2 100644 --- a/UnicodeInformation/UnicodeCharacterData.cs +++ b/UnicodeInformation/UnicodeCharacterData.cs @@ -11,6 +11,7 @@ namespace System.Unicode { public readonly UnicodeCharacterRange CodePointRange; public readonly string Name; + public readonly UnicodeNameAlias[] NameAliases; public readonly UnicodeCategory Category; public readonly CanonicalCombiningClass CanonicalCombiningClass; public readonly BidirectionalClass BidirectionalClass; @@ -32,6 +33,7 @@ namespace System.Unicode ( UnicodeCharacterRange codePointRange, string name, + UnicodeNameAlias[] nameAliases, UnicodeCategory category, CanonicalCombiningClass canonicalCombiningClass, BidirectionalClass bidirectionalClass, @@ -51,6 +53,7 @@ namespace System.Unicode { this.CodePointRange = codePointRange; this.Name = name; + this.NameAliases = nameAliases; this.Category = category; this.CanonicalCombiningClass = canonicalCombiningClass; this.BidirectionalClass = bidirectionalClass; diff --git a/UnicodeInformation/UnicodeInfo.cs b/UnicodeInformation/UnicodeInfo.cs index 00850b1..e409f4a 100644 --- a/UnicodeInformation/UnicodeInfo.cs +++ b/UnicodeInformation/UnicodeInfo.cs @@ -31,7 +31,7 @@ namespace System.Unicode { using (var reader = new BinaryReader(stream, Encoding.UTF8)) { - if (reader.ReadByte() != 'U' + if (reader.ReadByte() != 'U' | reader.ReadByte() != 'C' | reader.ReadByte() != 'D') throw new InvalidDataException(); @@ -43,10 +43,11 @@ namespace System.Unicode var fileUnicodeVersion = new Version(reader.ReadUInt16(), reader.ReadByte()); var unicodeCharacterDataEntries = new UnicodeCharacterData[ReadCodePoint(reader)]; + byte[] nameBuffer = new byte[64]; for (int i = 0; i < unicodeCharacterDataEntries.Length; ++i) { - unicodeCharacterDataEntries[i] = ReadUnicodeCharacterDataEntry(reader); + unicodeCharacterDataEntries[i] = ReadUnicodeCharacterDataEntry(reader, nameBuffer); } var blockEntries = new UnicodeBlock[reader.ReadByte()]; @@ -70,13 +71,50 @@ namespace System.Unicode } } - private static UnicodeCharacterData ReadUnicodeCharacterDataEntry(BinaryReader reader) + private static UnicodeCharacterData ReadUnicodeCharacterDataEntry(BinaryReader reader, byte[] nameBuffer) { var fields = (UcdFields)reader.ReadUInt16(); var codePointRange = (fields & UcdFields.CodePointRange) != 0 ? new UnicodeCharacterRange(ReadCodePoint(reader), ReadCodePoint(reader)) : new UnicodeCharacterRange(ReadCodePoint(reader)); - string name = (fields & UcdFields.Name) != 0 ? reader.ReadString() : null; + string name = null; + UnicodeNameAlias[] nameAliases = UnicodeNameAlias.EmptyArray; + + // Read all the official names of the character. + if ((fields & UcdFields.Name) != 0) + { + int length = reader.ReadByte(); + byte @case = (byte)(length & 0xC0); + length = (length & 0x3F) + 1; + + if (@case < 0xC0) // These cases have an official name. + { + if (@case != 0) + { + } + if (reader.Read(nameBuffer, 0, length) != length) throw new EndOfStreamException(); + + name = Encoding.UTF8.GetString(nameBuffer, 0, length); + if (@case == 2) length = (reader.ReadByte() & 0x3F) + 1; + else length = @case; + } + + if (length > 0) // These cases have official name aliases. + { + nameAliases = new UnicodeNameAlias[length]; + + for (int i = 0; i < nameAliases.Length; ++i) + { + length = reader.ReadByte(); + UnicodeNameAliasKind aliasKind = (UnicodeNameAliasKind)((length >> 6) + 1); + length = (length & 0x3F) + 1; + + if (reader.Read(nameBuffer, 0, length) != length) throw new EndOfStreamException(); + nameAliases[i] = new UnicodeNameAlias(Encoding.UTF8.GetString(nameBuffer, 0, length), aliasKind); + } + } + } + var category = (fields & UcdFields.Category) != 0 ? (UnicodeCategory)reader.ReadByte() : UnicodeCategory.OtherNotAssigned; var canonicalCombiningClass = (fields & UcdFields.CanonicalCombiningClass) != 0 ? (CanonicalCombiningClass)reader.ReadByte() : CanonicalCombiningClass.NotReordered; var bidirectionalClass = (fields & UcdFields.BidirectionalClass) != 0 ? (BidirectionalClass)reader.ReadByte() : 0; @@ -97,6 +135,7 @@ namespace System.Unicode ( codePointRange, name, + nameAliases, category, canonicalCombiningClass, bidirectionalClass, diff --git a/UnicodeInformation/UnicodeInformation.csproj b/UnicodeInformation/UnicodeInformation.csproj index 344722d..7b422b8 100644 --- a/UnicodeInformation/UnicodeInformation.csproj +++ b/UnicodeInformation/UnicodeInformation.csproj @@ -77,6 +77,8 @@ + + diff --git a/UnicodeInformation/UnicodeNameAlias.cs b/UnicodeInformation/UnicodeNameAlias.cs new file mode 100644 index 0000000..7b3d3a3 --- /dev/null +++ b/UnicodeInformation/UnicodeNameAlias.cs @@ -0,0 +1,16 @@ +namespace System.Unicode +{ + public struct UnicodeNameAlias + { + internal static readonly UnicodeNameAlias[] EmptyArray = new UnicodeNameAlias[0]; + + public string Name { get; } + public UnicodeNameAliasKind Kind { get; } + + internal UnicodeNameAlias(string name, UnicodeNameAliasKind kind) + { + Name = name; + Kind = kind; + } + } +} diff --git a/UnicodeInformation/UnicodeNameAliasKind.cs b/UnicodeInformation/UnicodeNameAliasKind.cs new file mode 100644 index 0000000..6a0c9e9 --- /dev/null +++ b/UnicodeInformation/UnicodeNameAliasKind.cs @@ -0,0 +1,11 @@ +namespace System.Unicode +{ + public enum UnicodeNameAliasKind : byte + { + Correction = 1, + Control = 2, + Alternate = 3, + Figment = 4, + Abbreviation = 5 + } +}