mirror of
https://github.com/QL-Win/QuickLook.git
synced 2025-09-12 10:19:07 +00:00
1929 lines
89 KiB
C#
1929 lines
89 KiB
C#
//MIT, 2019-present, WinterDev
|
||
using System.IO;
|
||
using System.Collections.Generic;
|
||
|
||
using Typography.OpenFont.Tables;
|
||
using Typography.OpenFont.Trimmable;
|
||
|
||
//see https://www.w3.org/TR/WOFF2/
|
||
|
||
namespace Typography.OpenFont.WebFont
|
||
{
|
||
//NOTE: Web Font file structure is not part of 'Open Font Format'.
|
||
|
||
class Woff2Header
|
||
{
|
||
//WOFF2 Header
|
||
//UInt32 signature 0x774F4632 'wOF2'
|
||
//UInt32 flavor The "sfnt version" of the input font.
|
||
//UInt32 length Total size of the WOFF file.
|
||
//UInt16 numTables Number of entries in directory of font tables.
|
||
//UInt16 reserved Reserved; set to 0.
|
||
//UInt32 totalSfntSize Total size needed for the uncompressed font data, including the sfnt header,
|
||
// directory, and font tables(including padding).
|
||
//UInt32 totalCompressedSize Total length of the compressed data block.
|
||
//UInt16 majorVersion Major version of the WOFF file.
|
||
//UInt16 minorVersion Minor version of the WOFF file.
|
||
//UInt32 metaOffset Offset to metadata block, from beginning of WOFF file.
|
||
//UInt32 metaLength Length of compressed metadata block.
|
||
//UInt32 metaOrigLength Uncompressed size of metadata block.
|
||
//UInt32 privOffset Offset to private data block, from beginning of WOFF file.
|
||
//UInt32 privLength Length of private data block.
|
||
|
||
public uint flavor;
|
||
public uint length;
|
||
public uint numTables;
|
||
|
||
//public ushort reserved;
|
||
public uint totalSfntSize;
|
||
|
||
public uint totalCompressedSize; //***
|
||
public ushort majorVersion;
|
||
public ushort minorVersion;
|
||
public uint metaOffset;
|
||
public uint metaLength;
|
||
public uint metaOriginalLength;
|
||
public uint privOffset;
|
||
public uint privLength;
|
||
}
|
||
|
||
class Woff2TableDirectory
|
||
{
|
||
//TableDirectoryEntry
|
||
//UInt8 flags table type and flags
|
||
//UInt32 tag 4-byte tag(optional)
|
||
//UIntBase128 origLength length of original table
|
||
//UIntBase128 transformLength transformed length(if applicable)
|
||
|
||
public uint origLength;
|
||
public uint transformLength;
|
||
|
||
//translated values
|
||
public string Name { get; set; } //translate from tag
|
||
|
||
public byte PreprocessingTransformation { get; set; }
|
||
public long ExpectedStartAt { get; set; }
|
||
#if DEBUG
|
||
|
||
public override string ToString()
|
||
{
|
||
return Name + " " + PreprocessingTransformation;
|
||
}
|
||
|
||
#endif
|
||
}
|
||
|
||
public delegate bool BrotliDecompressStreamFunc(byte[] compressedInput, Stream decompressStream);
|
||
|
||
public static class Woff2DefaultBrotliDecompressFunc
|
||
{
|
||
public static BrotliDecompressStreamFunc DecompressHandler;
|
||
}
|
||
|
||
class TransformedGlyf : UnreadTableEntry
|
||
{
|
||
private static TripleEncodingTable s_encTable = TripleEncodingTable.GetEncTable();
|
||
|
||
public TransformedGlyf(TableHeader header, Woff2TableDirectory tableDir) : base(header)
|
||
{
|
||
HasCustomContentReader = true;
|
||
TableDir = tableDir;
|
||
}
|
||
|
||
public Woff2TableDirectory TableDir { get; }
|
||
|
||
public override T CreateTableEntry<T>(BinaryReader reader, T expectedResult)
|
||
{
|
||
if (!(expectedResult is Glyf glyfTable)) throw new System.NotSupportedException();
|
||
|
||
ReconstructGlyfTable(reader, TableDir, glyfTable);
|
||
|
||
return expectedResult;
|
||
}
|
||
|
||
struct TempGlyph
|
||
{
|
||
public readonly ushort glyphIndex;
|
||
public readonly short numContour;
|
||
|
||
public ushort instructionLen;
|
||
public bool compositeHasInstructions;
|
||
|
||
public TempGlyph(ushort glyphIndex, short contourCount)
|
||
{
|
||
this.glyphIndex = glyphIndex;
|
||
this.numContour = contourCount;
|
||
|
||
instructionLen = 0;
|
||
compositeHasInstructions = false;
|
||
}
|
||
|
||
#if DEBUG
|
||
|
||
public override string ToString()
|
||
{
|
||
return glyphIndex + " " + numContour;
|
||
}
|
||
|
||
#endif
|
||
}
|
||
|
||
static void ReconstructGlyfTable(BinaryReader reader, Woff2TableDirectory woff2TableDir, Glyf glyfTable)
|
||
{
|
||
//fill the information to glyfTable
|
||
//reader.BaseStream.Position += woff2TableDir.transformLength;
|
||
//For greater compression effectiveness,
|
||
//the glyf table is split into several substreams, to group like data together.
|
||
|
||
//The transformed table consists of a number of fields specifying the size of each of the substreams,
|
||
//followed by the substreams in sequence.
|
||
|
||
//During the decoding process the reverse transformation takes place,
|
||
//where data from various separate substreams are recombined to create a complete glyph record
|
||
//for each entry of the original glyf table.
|
||
|
||
//Transformed glyf Table
|
||
//Data-Type Semantic Description and value type(if applicable)
|
||
//Fixed version = 0x00000000
|
||
//UInt16 numGlyphs Number of glyphs
|
||
//UInt16 indexFormatOffset format for loca table,
|
||
// should be consistent with indexToLocFormat of
|
||
// the original head table(see[OFF] specification)
|
||
|
||
//UInt32 nContourStreamSize Size of nContour stream in bytes
|
||
//UInt32 nPointsStreamSize Size of nPoints stream in bytes
|
||
//UInt32 flagStreamSize Size of flag stream in bytes
|
||
//UInt32 glyphStreamSize Size of glyph stream in bytes(a stream of variable-length encoded values, see description below)
|
||
//UInt32 compositeStreamSize Size of composite stream in bytes(a stream of variable-length encoded values, see description below)
|
||
//UInt32 bboxStreamSize Size of bbox data in bytes representing combined length of bboxBitmap(a packed bit array) and bboxStream(a stream of Int16 values)
|
||
//UInt32 instructionStreamSize Size of instruction stream(a stream of UInt8 values)
|
||
|
||
//Int16 nContourStream[] Stream of Int16 values representing number of contours for each glyph record
|
||
//255UInt16 nPointsStream[] Stream of values representing number of outline points for each contour in glyph records
|
||
//UInt8 flagStream[] Stream of UInt8 values representing flag values for each outline point.
|
||
//Vary glyphStream[] Stream of bytes representing point coordinate values using variable length encoding format(defined in subclause 5.2)
|
||
//Vary compositeStream[] Stream of bytes representing component flag values and associated composite glyph data
|
||
//UInt8 bboxBitmap[] Bitmap(a numGlyphs-long bit array) indicating explicit bounding boxes
|
||
//Int16 bboxStream[] Stream of Int16 values representing glyph bounding box data
|
||
//UInt8 instructionStream[] Stream of UInt8 values representing a set of instructions for each corresponding glyph
|
||
|
||
reader.BaseStream.Position = woff2TableDir.ExpectedStartAt;
|
||
|
||
long start = reader.BaseStream.Position;
|
||
|
||
uint version = reader.ReadUInt32();
|
||
ushort numGlyphs = reader.ReadUInt16();
|
||
ushort indexFormatOffset = reader.ReadUInt16();
|
||
|
||
uint nContourStreamSize = reader.ReadUInt32(); //in bytes
|
||
uint nPointsStreamSize = reader.ReadUInt32(); //in bytes
|
||
uint flagStreamSize = reader.ReadUInt32(); //in bytes
|
||
uint glyphStreamSize = reader.ReadUInt32(); //in bytes
|
||
uint compositeStreamSize = reader.ReadUInt32(); //in bytes
|
||
uint bboxStreamSize = reader.ReadUInt32(); //in bytes
|
||
uint instructionStreamSize = reader.ReadUInt32(); //in bytes
|
||
|
||
long expected_nCountStartAt = reader.BaseStream.Position;
|
||
long expected_nPointStartAt = expected_nCountStartAt + nContourStreamSize;
|
||
long expected_FlagStreamStartAt = expected_nPointStartAt + nPointsStreamSize;
|
||
long expected_GlyphStreamStartAt = expected_FlagStreamStartAt + flagStreamSize;
|
||
long expected_CompositeStreamStartAt = expected_GlyphStreamStartAt + glyphStreamSize;
|
||
|
||
long expected_BboxStreamStartAt = expected_CompositeStreamStartAt + compositeStreamSize;
|
||
long expected_InstructionStreamStartAt = expected_BboxStreamStartAt + bboxStreamSize;
|
||
long expected_EndAt = expected_InstructionStreamStartAt + instructionStreamSize;
|
||
|
||
//---------------------------------------------
|
||
Glyph[] glyphs = new Glyph[numGlyphs];
|
||
TempGlyph[] allGlyphs = new TempGlyph[numGlyphs];
|
||
List<ushort> compositeGlyphs = new List<ushort>();
|
||
int contourCount = 0;
|
||
for (ushort i = 0; i < numGlyphs; ++i)
|
||
{
|
||
short numContour = reader.ReadInt16();
|
||
allGlyphs[i] = new TempGlyph(i, numContour);
|
||
if (numContour > 0)
|
||
{
|
||
contourCount += numContour;
|
||
//>0 => simple glyph
|
||
//-1 = compound
|
||
//0 = empty glyph
|
||
}
|
||
else if (numContour < 0)
|
||
{
|
||
//composite glyph, resolve later
|
||
compositeGlyphs.Add(i);
|
||
}
|
||
else
|
||
{
|
||
}
|
||
}
|
||
|
||
//--------------------------------------------------------------------------------------------
|
||
//glyphStream
|
||
//5.2.Decoding of variable-length X and Y coordinates
|
||
|
||
//Simple glyph data structure defines all contours that comprise a glyph outline,
|
||
//which are presented by a sequence of on- and off-curve coordinate points.
|
||
|
||
//These point coordinates are encoded as delta values representing the incremental values
|
||
//between the previous and current corresponding X and Y coordinates of a point,
|
||
//the first point of each outline is relative to (0, 0) point.
|
||
|
||
//To minimize the size of the dataset of point coordinate values,
|
||
//each point is presented as a (flag, xCoordinate, yCoordinate) triplet.
|
||
|
||
//The flag value is stored in a separate data stream
|
||
//and the coordinate values are stored as part of the glyph data stream using a variable-length encoding format
|
||
//consuming a total of 2 - 5 bytes per point.
|
||
|
||
//Decoding of Simple Glyphs:
|
||
|
||
//For a simple glyph(when nContour > 0), the process continues as follows:
|
||
// 1) Read numberOfContours 255UInt16 values from the nPoints stream.
|
||
// Each of these is the number of points of that contour.
|
||
// Convert this into the endPtsOfContours[] array by computing the cumulative sum, then subtracting one.
|
||
// For example, if the values in the stream are[2, 4], then the endPtsOfContours array is [1, 5].Also,
|
||
// the sum of all the values in the array is the total number of points in the glyph, nPoints.
|
||
// In the example given, the value of nPoints is 6.
|
||
|
||
// 2) Read nPoints UInt8 values from the flags stream.Each corresponds to one point in the reconstructed glyph outline.
|
||
// The interpretation of the flag byte is described in details in subclause 5.2.
|
||
|
||
// 3) For each point(i.e.nPoints times), read a number of point coordinate bytes from the glyph stream.
|
||
// The number of point coordinate bytes is a function of the flag byte read in the previous step:
|
||
// for (flag < 0x7f) in the range 0 to 83 inclusive, it is one byte.
|
||
// In the range 84 to 119 inclusive, it is two bytes.
|
||
// In the range 120 to 123 inclusive, it is three bytes,
|
||
// and in the range 124 to 127 inclusive, it is four bytes.
|
||
// Decode these bytes according to the procedure specified in the subclause 5.2 to reconstruct delta-x and delta-y values of the glyph point coordinates.
|
||
// Store these delta-x and delta-y values in the reconstructed glyph using the standard TrueType glyph encoding[OFF] subclause 5.3.3.
|
||
|
||
// 4) Read one 255UInt16 value from the glyph stream, which is instructionLength, the number of instruction bytes.
|
||
// 5) Read instructionLength bytes from instructionStream, and store these in the reconstituted glyph as instructions.
|
||
//--------
|
||
#if DEBUG
|
||
if (reader.BaseStream.Position != expected_nPointStartAt)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine("ERR!!");
|
||
}
|
||
#endif
|
||
//
|
||
//1) nPoints stream, npoint for each contour
|
||
|
||
ushort[] pntPerContours = new ushort[contourCount];
|
||
for (int i = 0; i < contourCount; ++i)
|
||
{
|
||
// Each of these is the number of points of that contour.
|
||
pntPerContours[i] = Woff2Utils.Read255UInt16(reader);
|
||
}
|
||
#if DEBUG
|
||
if (reader.BaseStream.Position != expected_FlagStreamStartAt)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine("ERR!!");
|
||
}
|
||
#endif
|
||
//2) flagStream, flags value for each point
|
||
//each byte in flags stream represents one point
|
||
byte[] flagStream = reader.ReadBytes((int)flagStreamSize);
|
||
|
||
#if DEBUG
|
||
if (reader.BaseStream.Position != expected_GlyphStreamStartAt)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine("ERR!!");
|
||
}
|
||
#endif
|
||
|
||
//***
|
||
//some composite glyphs have instructions=> so we must check all composite glyphs
|
||
//before read the glyph stream
|
||
//**
|
||
using (MemoryStream compositeMS = new MemoryStream())
|
||
{
|
||
reader.BaseStream.Position = expected_CompositeStreamStartAt;
|
||
compositeMS.Write(reader.ReadBytes((int)compositeStreamSize), 0, (int)compositeStreamSize);
|
||
compositeMS.Position = 0;
|
||
|
||
int j = compositeGlyphs.Count;
|
||
ByteOrderSwappingBinaryReader compositeReader = new ByteOrderSwappingBinaryReader(compositeMS);
|
||
for (ushort i = 0; i < j; ++i)
|
||
{
|
||
ushort compositeGlyphIndex = compositeGlyphs[i];
|
||
allGlyphs[compositeGlyphIndex].compositeHasInstructions = CompositeHasInstructions(compositeReader, compositeGlyphIndex);
|
||
}
|
||
reader.BaseStream.Position = expected_GlyphStreamStartAt;
|
||
}
|
||
//--------
|
||
int curFlagsIndex = 0;
|
||
int pntContourIndex = 0;
|
||
for (int i = 0; i < allGlyphs.Length; ++i)
|
||
{
|
||
glyphs[i] = BuildSimpleGlyphStructure(reader,
|
||
ref allGlyphs[i],
|
||
glyfTable._emptyGlyph,
|
||
pntPerContours, ref pntContourIndex,
|
||
flagStream, ref curFlagsIndex);
|
||
}
|
||
|
||
#if DEBUG
|
||
if (pntContourIndex != pntPerContours.Length)
|
||
{
|
||
}
|
||
if (curFlagsIndex != flagStream.Length)
|
||
{
|
||
}
|
||
#endif
|
||
//--------------------------------------------------------------------------------------------
|
||
//compositeStream
|
||
//--------------------------------------------------------------------------------------------
|
||
#if DEBUG
|
||
if (expected_CompositeStreamStartAt != reader.BaseStream.Position)
|
||
{
|
||
//***
|
||
|
||
reader.BaseStream.Position = expected_CompositeStreamStartAt;
|
||
}
|
||
#endif
|
||
{
|
||
//now we read the composite stream again
|
||
//and create composite glyphs
|
||
int j = compositeGlyphs.Count;
|
||
for (ushort i = 0; i < j; ++i)
|
||
{
|
||
int compositeGlyphIndex = compositeGlyphs[i];
|
||
glyphs[compositeGlyphIndex] = ReadCompositeGlyph(glyphs, reader, i, glyfTable._emptyGlyph);
|
||
}
|
||
}
|
||
|
||
//--------------------------------------------------------------------------------------------
|
||
//bbox stream
|
||
//--------------------------------------------------------------------------------------------
|
||
|
||
//Finally, for both simple and composite glyphs,
|
||
//if the corresponding bit in the bounding box bit vector is set,
|
||
//then additionally read 4 Int16 values from the bbox stream,
|
||
//representing xMin, yMin, xMax, and yMax, respectively,
|
||
//and record these into the corresponding fields of the reconstructed glyph.
|
||
//For simple glyphs, if the corresponding bit in the bounding box bit vector is not set,
|
||
//then derive the bounding box by computing the minimum and maximum x and y coordinates in the outline, and storing that.
|
||
|
||
//A composite glyph MUST have an explicitly supplied bounding box.
|
||
//The motivation is that computing bounding boxes is more complicated,
|
||
//and would require resolving references to component glyphs taking into account composite glyph instructions and
|
||
//the specified scales of individual components, which would conflict with a purely streaming implementation of font decoding.
|
||
|
||
//A decoder MUST check for presence of the bounding box info as part of the composite glyph record
|
||
//and MUST NOT load a font file with the composite bounding box data missing.
|
||
#if DEBUG
|
||
if (expected_BboxStreamStartAt != reader.BaseStream.Position)
|
||
{
|
||
}
|
||
#endif
|
||
int bitmapCount = (numGlyphs + 7) / 8;
|
||
byte[] bboxBitmap = ExpandBitmap(reader.ReadBytes(bitmapCount));
|
||
for (ushort i = 0; i < numGlyphs; ++i)
|
||
{
|
||
TempGlyph tempGlyph = allGlyphs[i];
|
||
Glyph glyph = glyphs[i];
|
||
|
||
byte hasBbox = bboxBitmap[i];
|
||
if (hasBbox == 1)
|
||
{
|
||
//read bbox from the bboxstream
|
||
glyph.Bounds = new Bounds(
|
||
reader.ReadInt16(),
|
||
reader.ReadInt16(),
|
||
reader.ReadInt16(),
|
||
reader.ReadInt16());
|
||
}
|
||
else
|
||
{
|
||
//no bbox
|
||
//
|
||
if (tempGlyph.numContour < 0)
|
||
{
|
||
//composite must have bbox
|
||
//if not=> err
|
||
throw new System.NotSupportedException();
|
||
}
|
||
else if (tempGlyph.numContour > 0)
|
||
{
|
||
//simple glyph
|
||
//use simple calculation
|
||
//...For simple glyphs, if the corresponding bit in the bounding box bit vector is not set,
|
||
//then derive the bounding box by computing the minimum and maximum x and y coordinates in the outline, and storing that.
|
||
glyph.Bounds = FindSimpleGlyphBounds(glyph);
|
||
}
|
||
}
|
||
}
|
||
//--------------------------------------------------------------------------------------------
|
||
//instruction stream
|
||
#if DEBUG
|
||
if (reader.BaseStream.Position < expected_InstructionStreamStartAt)
|
||
{
|
||
}
|
||
else if (expected_InstructionStreamStartAt == reader.BaseStream.Position)
|
||
{
|
||
}
|
||
else
|
||
{
|
||
}
|
||
#endif
|
||
|
||
reader.BaseStream.Position = expected_InstructionStreamStartAt;
|
||
//--------------------------------------------------------------------------------------------
|
||
|
||
for (ushort i = 0; i < numGlyphs; ++i)
|
||
{
|
||
TempGlyph tempGlyph = allGlyphs[i];
|
||
if (tempGlyph.instructionLen > 0)
|
||
{
|
||
glyphs[i].GlyphInstructions = reader.ReadBytes(tempGlyph.instructionLen);
|
||
}
|
||
}
|
||
|
||
#if DEBUG
|
||
if (reader.BaseStream.Position != expected_EndAt)
|
||
{
|
||
}
|
||
#endif
|
||
|
||
glyfTable.Glyphs = glyphs;
|
||
}
|
||
|
||
static Bounds FindSimpleGlyphBounds(Glyph glyph)
|
||
{
|
||
GlyphPointF[] glyphPoints = glyph.GlyphPoints;
|
||
|
||
int j = glyphPoints.Length;
|
||
float xmin = float.MaxValue;
|
||
float ymin = float.MaxValue;
|
||
float xmax = float.MinValue;
|
||
float ymax = float.MinValue;
|
||
|
||
for (int i = 0; i < j; ++i)
|
||
{
|
||
GlyphPointF p = glyphPoints[i];
|
||
if (p.X < xmin) xmin = p.X;
|
||
if (p.X > xmax) xmax = p.X;
|
||
if (p.Y < ymin) ymin = p.Y;
|
||
if (p.Y > ymax) ymax = p.Y;
|
||
}
|
||
|
||
return new Bounds(
|
||
(short)System.Math.Round(xmin),
|
||
(short)System.Math.Round(ymin),
|
||
(short)System.Math.Round(xmax),
|
||
(short)System.Math.Round(ymax));
|
||
}
|
||
|
||
static byte[] ExpandBitmap(byte[] orgBBoxBitmap)
|
||
{
|
||
byte[] expandArr = new byte[orgBBoxBitmap.Length * 8];
|
||
|
||
int index = 0;
|
||
for (int i = 0; i < orgBBoxBitmap.Length; ++i)
|
||
{
|
||
byte b = orgBBoxBitmap[i];
|
||
expandArr[index++] = (byte)((b >> 7) & 0x1);
|
||
expandArr[index++] = (byte)((b >> 6) & 0x1);
|
||
expandArr[index++] = (byte)((b >> 5) & 0x1);
|
||
expandArr[index++] = (byte)((b >> 4) & 0x1);
|
||
expandArr[index++] = (byte)((b >> 3) & 0x1);
|
||
expandArr[index++] = (byte)((b >> 2) & 0x1);
|
||
expandArr[index++] = (byte)((b >> 1) & 0x1);
|
||
expandArr[index++] = (byte)((b >> 0) & 0x1);
|
||
}
|
||
return expandArr;
|
||
}
|
||
|
||
static Glyph BuildSimpleGlyphStructure(BinaryReader glyphStreamReader,
|
||
ref TempGlyph tmpGlyph,
|
||
Glyph emptyGlyph,
|
||
ushort[] pntPerContours, ref int pntContourIndex,
|
||
byte[] flagStream, ref int flagStreamIndex)
|
||
{
|
||
//reading from glyphstream***
|
||
//Building a SimpleGlyph
|
||
// 1) Read numberOfContours 255UInt16 values from the nPoints stream.
|
||
// Each of these is the number of points of that contour.
|
||
// Convert this into the endPtsOfContours[] array by computing the cumulative sum, then subtracting one.
|
||
// For example, if the values in the stream are[2, 4], then the endPtsOfContours array is [1, 5].Also,
|
||
// the sum of all the values in the array is the total number of points in the glyph, nPoints.
|
||
// In the example given, the value of nPoints is 6.
|
||
|
||
// 2) Read nPoints UInt8 values from the flags stream.Each corresponds to one point in the reconstructed glyph outline.
|
||
// The interpretation of the flag byte is described in details in subclause 5.2.
|
||
|
||
// 3) For each point(i.e.nPoints times), read a number of point coordinate bytes from the glyph stream.
|
||
// The number of point coordinate bytes is a function of the flag byte read in the previous step:
|
||
// for (flag < 0x7f)
|
||
// in the range 0 to 83 inclusive, it is one byte.
|
||
// In the range 84 to 119 inclusive, it is two bytes.
|
||
// In the range 120 to 123 inclusive, it is three bytes,
|
||
// and in the range 124 to 127 inclusive, it is four bytes.
|
||
// Decode these bytes according to the procedure specified in the subclause 5.2 to reconstruct delta-x and delta-y values of the glyph point coordinates.
|
||
// Store these delta-x and delta-y values in the reconstructed glyph using the standard TrueType glyph encoding[OFF] subclause 5.3.3.
|
||
|
||
// 4) Read one 255UInt16 value from the glyph stream, which is instructionLength, the number of instruction bytes.
|
||
// 5) Read instructionLength bytes from instructionStream, and store these in the reconstituted glyph as instructions.
|
||
|
||
if (tmpGlyph.numContour == 0) return emptyGlyph;
|
||
if (tmpGlyph.numContour < 0)
|
||
{
|
||
//composite glyph,
|
||
//check if this has instruction or not
|
||
if (tmpGlyph.compositeHasInstructions)
|
||
{
|
||
tmpGlyph.instructionLen = Woff2Utils.Read255UInt16(glyphStreamReader);
|
||
}
|
||
return null;//skip composite glyph (resolve later)
|
||
}
|
||
|
||
//-----
|
||
int curX = 0;
|
||
int curY = 0;
|
||
|
||
int numContour = tmpGlyph.numContour;
|
||
|
||
var _endContours = new ushort[numContour];
|
||
ushort pointCount = 0;
|
||
|
||
//create contours
|
||
for (ushort i = 0; i < numContour; ++i)
|
||
{
|
||
ushort numPoint = pntPerContours[pntContourIndex++];//increament pntContourIndex AFTER
|
||
pointCount += numPoint;
|
||
_endContours[i] = (ushort)(pointCount - 1);
|
||
}
|
||
|
||
//collect point for our contours
|
||
var _glyphPoints = new GlyphPointF[pointCount];
|
||
int n = 0;
|
||
for (int i = 0; i < numContour; ++i)
|
||
{
|
||
//read point detail
|
||
//step 3)
|
||
|
||
//foreach contour
|
||
//read 1 byte flags for each contour
|
||
|
||
//1) The most significant bit of a flag indicates whether the point is on- or off-curve point,
|
||
//2) the remaining seven bits of the flag determine the format of X and Y coordinate values and
|
||
//specify 128 possible combinations of indices that have been assigned taking into consideration
|
||
//typical statistical distribution of data found in TrueType fonts.
|
||
|
||
//When X and Y coordinate values are recorded using nibbles(either 4 bits per coordinate or 12 bits per coordinate)
|
||
//the bits are packed in the byte stream with most significant bit of X coordinate first,
|
||
//followed by the value for Y coordinate (most significant bit first).
|
||
//As a result, the size of the glyph dataset is significantly reduced,
|
||
//and the grouping of the similar values(flags, coordinates) in separate and contiguous data streams allows
|
||
//more efficient application of the entropy coding applied as the second stage of encoding process.
|
||
|
||
int endContour = _endContours[i];
|
||
for (; n <= endContour; ++n)
|
||
{
|
||
byte f = flagStream[flagStreamIndex++]; //increment the flagStreamIndex AFTER read
|
||
|
||
//int f1 = (f >> 7); // most significant 1 bit -> on/off curve
|
||
|
||
int xyFormat = f & 0x7F; // remainging 7 bits x,y format
|
||
|
||
TripleEncodingRecord enc = s_encTable[xyFormat]; //0-128
|
||
|
||
byte[] packedXY = glyphStreamReader.ReadBytes(enc.ByteCount - 1); //byte count include 1 byte flags, so actual read=> byteCount-1
|
||
//read x and y
|
||
|
||
int x = 0;
|
||
int y = 0;
|
||
|
||
switch (enc.XBits)
|
||
{
|
||
default:
|
||
throw new System.NotSupportedException();//???
|
||
case 0: //0,8,
|
||
x = 0;
|
||
y = enc.Ty(packedXY[0]);
|
||
break;
|
||
|
||
case 4: //4,4
|
||
x = enc.Tx(packedXY[0] >> 4);
|
||
y = enc.Ty(packedXY[0] & 0xF);
|
||
break;
|
||
|
||
case 8: //8,0 or 8,8
|
||
x = enc.Tx(packedXY[0]);
|
||
y = (enc.YBits == 8) ?
|
||
enc.Ty(packedXY[1]) :
|
||
0;
|
||
break;
|
||
|
||
case 12: //12,12
|
||
//x = enc.Tx((packedXY[0] << 8) | (packedXY[1] >> 4));
|
||
//y = enc.Ty(((packedXY[1] & 0xF)) | (packedXY[2] >> 4));
|
||
x = enc.Tx((packedXY[0] << 4) | (packedXY[1] >> 4));
|
||
y = enc.Ty(((packedXY[1] & 0xF) << 8) | (packedXY[2]));
|
||
break;
|
||
|
||
case 16: //16,16
|
||
x = enc.Tx((packedXY[0] << 8) | packedXY[1]);
|
||
y = enc.Ty((packedXY[2] << 8) | packedXY[3]);
|
||
break;
|
||
}
|
||
|
||
//incremental point format***
|
||
_glyphPoints[n] = new GlyphPointF(curX += x, curY += y, (f >> 7) == 0); // most significant 1 bit -> on/off curve
|
||
}
|
||
}
|
||
|
||
//----
|
||
//step 4) Read one 255UInt16 value from the glyph stream, which is instructionLength, the number of instruction bytes.
|
||
tmpGlyph.instructionLen = Woff2Utils.Read255UInt16(glyphStreamReader);
|
||
//step 5) resolve it later
|
||
|
||
return new Glyph(_glyphPoints,
|
||
_endContours,
|
||
new Bounds(), //calculate later
|
||
null, //load instruction later
|
||
tmpGlyph.glyphIndex);
|
||
}
|
||
|
||
static bool CompositeHasInstructions(BinaryReader reader, ushort compositeGlyphIndex)
|
||
{
|
||
//To find if a composite has instruction or not.
|
||
|
||
//This method is similar to ReadCompositeGlyph() (below)
|
||
//but this dose not create actual composite glyph.
|
||
|
||
Glyf.CompositeGlyphFlags flags;
|
||
do
|
||
{
|
||
flags = (Glyf.CompositeGlyphFlags)reader.ReadUInt16();
|
||
ushort glyphIndex = reader.ReadUInt16();
|
||
short arg1 = 0;
|
||
short arg2 = 0;
|
||
ushort arg1and2 = 0;
|
||
|
||
if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.ARG_1_AND_2_ARE_WORDS))
|
||
{
|
||
arg1 = reader.ReadInt16();
|
||
arg2 = reader.ReadInt16();
|
||
}
|
||
else
|
||
{
|
||
arg1and2 = reader.ReadUInt16();
|
||
}
|
||
//-----------------------------------------
|
||
float xscale = 1;
|
||
float scale01 = 0;
|
||
float scale10 = 0;
|
||
float yscale = 1;
|
||
|
||
bool useMatrix = false;
|
||
//-----------------------------------------
|
||
bool hasScale = false;
|
||
if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.WE_HAVE_A_SCALE))
|
||
{
|
||
//If the bit WE_HAVE_A_SCALE is set,
|
||
//the scale value is read in 2.14 format-the value can be between -2 to almost +2.
|
||
//The glyph will be scaled by this value before grid-fitting.
|
||
xscale = yscale = reader.ReadF2Dot14(); /* Format 2.14 */
|
||
hasScale = true;
|
||
}
|
||
else if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.WE_HAVE_AN_X_AND_Y_SCALE))
|
||
{
|
||
xscale = reader.ReadF2Dot14(); /* Format 2.14 */
|
||
yscale = reader.ReadF2Dot14(); /* Format 2.14 */
|
||
hasScale = true;
|
||
}
|
||
else if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.WE_HAVE_A_TWO_BY_TWO))
|
||
{
|
||
//The bit WE_HAVE_A_TWO_BY_TWO allows for linear transformation of the X and Y coordinates by specifying a 2 × 2 matrix.
|
||
//This could be used for scaling and 90-degree*** rotations of the glyph components, for example.
|
||
|
||
//2x2 matrix
|
||
|
||
//The purpose of USE_MY_METRICS is to force the lsb and rsb to take on a desired value.
|
||
//For example, an i-circumflex (U+00EF) is often composed of the circumflex and a dotless-i.
|
||
//In order to force the composite to have the same metrics as the dotless-i,
|
||
//set USE_MY_METRICS for the dotless-i component of the composite.
|
||
//Without this bit, the rsb and lsb would be calculated from the hmtx entry for the composite
|
||
//(or would need to be explicitly set with TrueType instructions).
|
||
|
||
//Note that the behavior of the USE_MY_METRICS operation is undefined for rotated composite components.
|
||
useMatrix = true;
|
||
hasScale = true;
|
||
xscale = reader.ReadF2Dot14(); /* Format 2.14 */
|
||
scale01 = reader.ReadF2Dot14(); /* Format 2.14 */
|
||
scale10 = reader.ReadF2Dot14();/* Format 2.14 */
|
||
yscale = reader.ReadF2Dot14(); /* Format 2.14 */
|
||
}
|
||
} while (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.MORE_COMPONENTS));
|
||
|
||
//
|
||
return Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.WE_HAVE_INSTRUCTIONS);
|
||
}
|
||
|
||
static Glyph ReadCompositeGlyph(Glyph[] createdGlyphs, BinaryReader reader, ushort compositeGlyphIndex, Glyph emptyGlyph)
|
||
{
|
||
//Decoding of Composite Glyphs
|
||
//For a composite glyph(nContour == -1), the following steps take the place of (Building Simple Glyph, steps 1 - 5 above):
|
||
|
||
//1a.Read a UInt16 from compositeStream.
|
||
// This is interpreted as a component flag word as in the TrueType spec.
|
||
// Based on the flag values, there are between 4 and 14 additional argument bytes,
|
||
// interpreted as glyph index, arg1, arg2, and optional scale or affine matrix.
|
||
|
||
//2a.Read the number of argument bytes as determined in step 2a from the composite stream,
|
||
//and store these in the reconstructed glyph.
|
||
//If the flag word read in step 2a has the FLAG_MORE_COMPONENTS bit(bit 5) set, go back to step 2a.
|
||
|
||
//3a.If any of the flag words had the FLAG_WE_HAVE_INSTRUCTIONS bit(bit 8) set,
|
||
//then read the instructions from the glyph and store them in the reconstructed glyph,
|
||
//using the same process as described in steps 4 and 5 above (see Building Simple Glyph).
|
||
|
||
Glyph finalGlyph = null;
|
||
Glyf.CompositeGlyphFlags flags;
|
||
do
|
||
{
|
||
flags = (Glyf.CompositeGlyphFlags)reader.ReadUInt16();
|
||
ushort glyphIndex = reader.ReadUInt16();
|
||
if (createdGlyphs[glyphIndex] == null)
|
||
{
|
||
// This glyph is not read yet, resolve it first!
|
||
long storedOffset = reader.BaseStream.Position;
|
||
Glyph missingGlyph = ReadCompositeGlyph(createdGlyphs, reader, glyphIndex, emptyGlyph);
|
||
createdGlyphs[glyphIndex] = missingGlyph;
|
||
reader.BaseStream.Position = storedOffset;
|
||
}
|
||
|
||
Glyph newGlyph = Glyph.TtfOutlineGlyphClone(createdGlyphs[glyphIndex], compositeGlyphIndex);
|
||
|
||
short arg1 = 0;
|
||
short arg2 = 0;
|
||
ushort arg1and2 = 0;
|
||
|
||
if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.ARG_1_AND_2_ARE_WORDS))
|
||
{
|
||
arg1 = reader.ReadInt16();
|
||
arg2 = reader.ReadInt16();
|
||
}
|
||
else
|
||
{
|
||
arg1and2 = reader.ReadUInt16();
|
||
}
|
||
//-----------------------------------------
|
||
float xscale = 1;
|
||
float scale01 = 0;
|
||
float scale10 = 0;
|
||
float yscale = 1;
|
||
|
||
bool useMatrix = false;
|
||
//-----------------------------------------
|
||
bool hasScale = false;
|
||
if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.WE_HAVE_A_SCALE))
|
||
{
|
||
//If the bit WE_HAVE_A_SCALE is set,
|
||
//the scale value is read in 2.14 format-the value can be between -2 to almost +2.
|
||
//The glyph will be scaled by this value before grid-fitting.
|
||
xscale = yscale = reader.ReadF2Dot14(); /* Format 2.14 */
|
||
hasScale = true;
|
||
}
|
||
else if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.WE_HAVE_AN_X_AND_Y_SCALE))
|
||
{
|
||
xscale = reader.ReadF2Dot14(); /* Format 2.14 */
|
||
yscale = reader.ReadF2Dot14(); /* Format 2.14 */
|
||
hasScale = true;
|
||
}
|
||
else if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.WE_HAVE_A_TWO_BY_TWO))
|
||
{
|
||
//The bit WE_HAVE_A_TWO_BY_TWO allows for linear transformation of the X and Y coordinates by specifying a 2 × 2 matrix.
|
||
//This could be used for scaling and 90-degree*** rotations of the glyph components, for example.
|
||
|
||
//2x2 matrix
|
||
|
||
//The purpose of USE_MY_METRICS is to force the lsb and rsb to take on a desired value.
|
||
//For example, an i-circumflex (U+00EF) is often composed of the circumflex and a dotless-i.
|
||
//In order to force the composite to have the same metrics as the dotless-i,
|
||
//set USE_MY_METRICS for the dotless-i component of the composite.
|
||
//Without this bit, the rsb and lsb would be calculated from the hmtx entry for the composite
|
||
//(or would need to be explicitly set with TrueType instructions).
|
||
|
||
//Note that the behavior of the USE_MY_METRICS operation is undefined for rotated composite components.
|
||
useMatrix = true;
|
||
hasScale = true;
|
||
xscale = reader.ReadF2Dot14(); /* Format 2.14 */
|
||
scale01 = reader.ReadF2Dot14(); /* Format 2.14 */
|
||
scale10 = reader.ReadF2Dot14(); /* Format 2.14 */
|
||
yscale = reader.ReadF2Dot14(); /* Format 2.14 */
|
||
|
||
if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.UNSCALED_COMPONENT_OFFSET))
|
||
{
|
||
}
|
||
else
|
||
{
|
||
}
|
||
if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.USE_MY_METRICS))
|
||
{
|
||
}
|
||
}
|
||
|
||
//--------------------------------------------------------------------
|
||
if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.ARGS_ARE_XY_VALUES))
|
||
{
|
||
//Argument1 and argument2 can be either x and y offsets to be added to the glyph or two point numbers.
|
||
//x and y offsets to be added to the glyph
|
||
//When arguments 1 and 2 are an x and a y offset instead of points and the bit ROUND_XY_TO_GRID is set to 1,
|
||
//the values are rounded to those of the closest grid lines before they are added to the glyph.
|
||
//X and Y offsets are described in FUnits.
|
||
|
||
if (useMatrix)
|
||
{
|
||
//use this matrix
|
||
Glyph.TtfTransformWith2x2Matrix(newGlyph, xscale, scale01, scale10, yscale);
|
||
Glyph.TtfOffsetXY(newGlyph, arg1, arg2);
|
||
}
|
||
else
|
||
{
|
||
if (hasScale)
|
||
{
|
||
if (xscale == 1.0 && yscale == 1.0)
|
||
{
|
||
}
|
||
else
|
||
{
|
||
Glyph.TtfTransformWith2x2Matrix(newGlyph, xscale, 0, 0, yscale);
|
||
}
|
||
Glyph.TtfOffsetXY(newGlyph, arg1, arg2);
|
||
}
|
||
else
|
||
{
|
||
if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.ROUND_XY_TO_GRID))
|
||
{
|
||
//TODO: implement round xy to grid***
|
||
//----------------------------
|
||
}
|
||
//just offset***
|
||
Glyph.TtfOffsetXY(newGlyph, arg1, arg2);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
//two point numbers.
|
||
//the first point number indicates the point that is to be matched to the new glyph.
|
||
//The second number indicates the new glyph's “matched” point.
|
||
//Once a glyph is added,its point numbers begin directly after the last glyphs (endpoint of first glyph + 1)
|
||
}
|
||
|
||
//
|
||
if (finalGlyph == null)
|
||
{
|
||
finalGlyph = newGlyph;
|
||
}
|
||
else
|
||
{
|
||
//merge
|
||
Glyph.TtfAppendGlyph(finalGlyph, newGlyph);
|
||
}
|
||
} while (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.MORE_COMPONENTS));
|
||
|
||
//
|
||
if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.WE_HAVE_INSTRUCTIONS))
|
||
{
|
||
//read this later
|
||
//ushort numInstr = reader.ReadUInt16();
|
||
//byte[] insts = reader.ReadBytes(numInstr);
|
||
//finalGlyph.GlyphInstructions = insts;
|
||
}
|
||
|
||
return finalGlyph ?? emptyGlyph;
|
||
}
|
||
|
||
readonly struct TripleEncodingRecord
|
||
{
|
||
public readonly byte ByteCount;
|
||
public readonly byte XBits;
|
||
public readonly byte YBits;
|
||
public readonly ushort DeltaX;
|
||
public readonly ushort DeltaY;
|
||
public readonly sbyte Xsign;
|
||
public readonly sbyte Ysign;
|
||
|
||
public TripleEncodingRecord(
|
||
byte byteCount,
|
||
byte xbits, byte ybits,
|
||
ushort deltaX, ushort deltaY,
|
||
sbyte xsign, sbyte ysign)
|
||
{
|
||
ByteCount = byteCount;
|
||
XBits = xbits;
|
||
YBits = ybits;
|
||
DeltaX = deltaX;
|
||
DeltaY = deltaY;
|
||
Xsign = xsign;
|
||
Ysign = ysign;
|
||
//#if DEBUG
|
||
// debugIndex = -1;
|
||
//#endif
|
||
}
|
||
|
||
#if DEBUG
|
||
|
||
//public int debugIndex;
|
||
public override string ToString()
|
||
{
|
||
return ByteCount + " " + XBits + " " + YBits + " " + DeltaX + " " + DeltaY + " " + Xsign + " " + Ysign;
|
||
}
|
||
|
||
#endif
|
||
|
||
/// <summary>
|
||
/// translate X
|
||
/// </summary>
|
||
/// <param name="orgX"></param>
|
||
/// <returns></returns>
|
||
public int Tx(int orgX) => (orgX + DeltaX) * Xsign;
|
||
|
||
/// <summary>
|
||
/// translate Y
|
||
/// </summary>
|
||
/// <param name="orgY"></param>
|
||
/// <returns></returns>
|
||
public int Ty(int orgY) => (orgY + DeltaY) * Ysign;
|
||
}
|
||
|
||
class TripleEncodingTable
|
||
{
|
||
private static TripleEncodingTable s_encTable;
|
||
|
||
private List<TripleEncodingRecord> _records = new List<TripleEncodingRecord>();
|
||
|
||
public static TripleEncodingTable GetEncTable()
|
||
{
|
||
if (s_encTable == null)
|
||
{
|
||
s_encTable = new TripleEncodingTable();
|
||
}
|
||
return s_encTable;
|
||
}
|
||
|
||
private TripleEncodingTable()
|
||
{
|
||
BuildTable();
|
||
|
||
#if DEBUG
|
||
if (_records.Count != 128)
|
||
{
|
||
throw new System.Exception();
|
||
}
|
||
dbugValidateTable();
|
||
#endif
|
||
}
|
||
|
||
#if DEBUG
|
||
|
||
void dbugValidateTable()
|
||
{
|
||
#if DEBUG
|
||
for (int xyFormat = 0; xyFormat < 128; ++xyFormat)
|
||
{
|
||
TripleEncodingRecord tripleRec = _records[xyFormat];
|
||
if (xyFormat < 84)
|
||
{
|
||
//0-83 inclusive
|
||
if ((tripleRec.ByteCount - 1) != 1)
|
||
{
|
||
throw new System.NotSupportedException();
|
||
}
|
||
}
|
||
else if (xyFormat < 120)
|
||
{
|
||
//84-119 inclusive
|
||
if ((tripleRec.ByteCount - 1) != 2)
|
||
{
|
||
throw new System.NotSupportedException();
|
||
}
|
||
}
|
||
else if (xyFormat < 124)
|
||
{
|
||
//120-123 inclusive
|
||
if ((tripleRec.ByteCount - 1) != 3)
|
||
{
|
||
throw new System.NotSupportedException();
|
||
}
|
||
}
|
||
else if (xyFormat < 128)
|
||
{
|
||
//124-127 inclusive
|
||
if ((tripleRec.ByteCount - 1) != 4)
|
||
{
|
||
throw new System.NotSupportedException();
|
||
}
|
||
}
|
||
}
|
||
|
||
#endif
|
||
}
|
||
|
||
#endif
|
||
public TripleEncodingRecord this[int index] => _records[index];
|
||
|
||
void BuildTable()
|
||
{
|
||
// Each of the 128 index values define the following properties and specified in details in the table below:
|
||
|
||
// Byte count(total number of bytes used for this set of coordinate values including one byte for 'flag' value).
|
||
// Number of bits used to represent X coordinate value(X bits).
|
||
// Number of bits used to represent Y coordinate value(Y bits).
|
||
// An additional incremental amount to be added to X bits value(delta X).
|
||
// An additional incremental amount to be added to Y bits value(delta Y).
|
||
// The sign of X coordinate value(X sign).
|
||
// The sign of Y coordinate value(Y sign).
|
||
|
||
//Please note that “Byte Count” field reflects total size of the triplet(flag, xCoordinate, yCoordinate),
|
||
//including ‘flag’ value that is encoded in a separate stream.
|
||
|
||
//Triplet Encoding
|
||
//Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign
|
||
|
||
//(set 1.1)
|
||
//0 2 0 8 N/A 0 N/A -
|
||
//1 0 +
|
||
//2 256 -
|
||
//3 256 +
|
||
//4 512 -
|
||
//5 512 +
|
||
//6 768 -
|
||
//7 768 +
|
||
//8 1024 -
|
||
//9 1024 +
|
||
BuildRecords(2, 0, 8, null, new ushort[] { 0, 256, 512, 768, 1024 }); //2*5
|
||
|
||
//---------------------------------------------------------------------
|
||
//Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign
|
||
//(set 1.2)
|
||
//10 2 8 0 0 N/A - N/A
|
||
//11 0 +
|
||
//12 256 -
|
||
//13 256 +
|
||
//14 512 -
|
||
//15 512 +
|
||
//16 768 -
|
||
//17 768 +
|
||
//18 1024 -
|
||
//19 1024 +
|
||
BuildRecords(2, 8, 0, new ushort[] { 0, 256, 512, 768, 1024 }, null); //2*5
|
||
|
||
//---------------------------------------------------------------------
|
||
//Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign
|
||
//(set 2.1)
|
||
//20 2 4 4 1 1 - -
|
||
//21 1 + -
|
||
//22 1 - +
|
||
//23 1 + +
|
||
//24 17 - -
|
||
//25 17 + -
|
||
//26 17 - +
|
||
//27 17 + +
|
||
//28 33 - -
|
||
//29 33 + -
|
||
//30 33 - +
|
||
//31 33 + +
|
||
//32 49 - -
|
||
//33 49 + -
|
||
//34 49 - +
|
||
//35 49 + +
|
||
BuildRecords(2, 4, 4, new ushort[] { 1 }, new ushort[] { 1, 17, 33, 49 });// 4*4 => 16 records
|
||
|
||
//---------------------------------------------------------------------
|
||
//Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign
|
||
//(set 2.2)
|
||
//36 2 4 4 17 1 - -
|
||
//37 1 + -
|
||
//38 1 - +
|
||
//39 1 + +
|
||
//40 17 - -
|
||
//41 17 + -
|
||
//42 17 - +
|
||
//43 17 + +
|
||
//44 33 - -
|
||
//45 33 + -
|
||
//46 33 - +
|
||
//47 33 + +
|
||
//48 49 - -
|
||
//49 49 + -
|
||
//50 49 - +
|
||
//51 49 + +
|
||
BuildRecords(2, 4, 4, new ushort[] { 17 }, new ushort[] { 1, 17, 33, 49 });// 4*4 => 16 records
|
||
|
||
//---------------------------------------------------------------------
|
||
//Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign
|
||
//(set 2.3)
|
||
//52 2 4 4 33 1 - -
|
||
//53 1 + -
|
||
//54 1 - +
|
||
//55 1 + +
|
||
//56 17 - -
|
||
//57 17 + -
|
||
//58 17 - +
|
||
//59 17 + +
|
||
//60 33 - -
|
||
//61 33 + -
|
||
//62 33 - +
|
||
//63 33 + +
|
||
//64 49 - -
|
||
//65 49 + -
|
||
//66 49 - +
|
||
//67 49 + +
|
||
BuildRecords(2, 4, 4, new ushort[] { 33 }, new ushort[] { 1, 17, 33, 49 });// 4*4 => 16 records
|
||
|
||
//---------------------------------------------------------------------
|
||
//Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign
|
||
//(set 2.4)
|
||
//68 2 4 4 49 1 - -
|
||
//69 1 + -
|
||
//70 1 - +
|
||
//71 1 + +
|
||
//72 17 - -
|
||
//73 17 + -
|
||
//74 17 - +
|
||
//75 17 + +
|
||
//76 33 - -
|
||
//77 33 + -
|
||
//78 33 - +
|
||
//79 33 + +
|
||
//80 49 - -
|
||
//81 49 + -
|
||
//82 49 - +
|
||
//83 49 + +
|
||
BuildRecords(2, 4, 4, new ushort[] { 49 }, new ushort[] { 1, 17, 33, 49 });// 4*4 => 16 records
|
||
|
||
//---------------------------------------------------------------------
|
||
//Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign
|
||
//(set 3.1)
|
||
//84 3 8 8 1 1 - -
|
||
//85 1 + -
|
||
//86 1 - +
|
||
//87 1 + +
|
||
//88 257 - -
|
||
//89 257 + -
|
||
//90 257 - +
|
||
//91 257 + +
|
||
//92 513 - -
|
||
//93 513 + -
|
||
//94 513 - +
|
||
//95 513 + +
|
||
BuildRecords(3, 8, 8, new ushort[] { 1 }, new ushort[] { 1, 257, 513 });// 4*3 => 12 records
|
||
|
||
//---------------------------------------------------------------------
|
||
//Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign
|
||
//(set 3.2)
|
||
//96 3 8 8 257 1 - -
|
||
//97 1 + -
|
||
//98 1 - +
|
||
//99 1 + +
|
||
//100 257 - -
|
||
//101 257 + -
|
||
//102 257 - +
|
||
//103 257 + +
|
||
//104 513 - -
|
||
//105 513 + -
|
||
//106 513 - +
|
||
//107 513 + +
|
||
BuildRecords(3, 8, 8, new ushort[] { 257 }, new ushort[] { 1, 257, 513 });// 4*3 => 12 records
|
||
|
||
//---------------------------------------------------------------------
|
||
//Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign
|
||
//(set 3.3)
|
||
//108 3 8 8 513 1 - -
|
||
//109 1 + -
|
||
//110 1 - +
|
||
//111 1 + +
|
||
//112 257 - -
|
||
//113 257 + -
|
||
//114 257 - +
|
||
//115 257 + +
|
||
//116 513 - -
|
||
//117 513 + -
|
||
//118 513 - +
|
||
//119 513 + +
|
||
BuildRecords(3, 8, 8, new ushort[] { 513 }, new ushort[] { 1, 257, 513 });// 4*3 => 12 records
|
||
|
||
//---------------------------------------------------------------------
|
||
//Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign
|
||
//(set 4)
|
||
//120 4 12 12 0 0 - -
|
||
//121 + -
|
||
//122 - +
|
||
//123 + +
|
||
BuildRecords(4, 12, 12, new ushort[] { 0 }, new ushort[] { 0 }); // 4*1 => 4 records
|
||
|
||
//---------------------------------------------------------------------
|
||
//Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign
|
||
//(set 5)
|
||
//124 5 16 16 0 0 - -
|
||
//125 + -
|
||
//126 - +
|
||
//127 + +
|
||
BuildRecords(5, 16, 16, new ushort[] { 0 }, new ushort[] { 0 });// 4*1 => 4 records
|
||
}
|
||
|
||
void AddRecord(byte byteCount, byte xbits, byte ybits, ushort deltaX, ushort deltaY, sbyte xsign, sbyte ysign)
|
||
{
|
||
var rec = new TripleEncodingRecord(byteCount, xbits, ybits, deltaX, deltaY, xsign, ysign);
|
||
#if DEBUG
|
||
//rec.debugIndex = _records.Count;
|
||
#endif
|
||
_records.Add(rec);
|
||
}
|
||
|
||
void BuildRecords(byte byteCount, byte xbits, byte ybits, ushort[] deltaXs, ushort[] deltaYs)
|
||
{
|
||
if (deltaXs == null)
|
||
{
|
||
//(set 1.1)
|
||
for (int y = 0; y < deltaYs.Length; ++y)
|
||
{
|
||
AddRecord(byteCount, xbits, ybits, 0, deltaYs[y], 0, -1);
|
||
AddRecord(byteCount, xbits, ybits, 0, deltaYs[y], 0, 1);
|
||
}
|
||
}
|
||
else if (deltaYs == null)
|
||
{
|
||
//(set 1.2)
|
||
for (int x = 0; x < deltaXs.Length; ++x)
|
||
{
|
||
AddRecord(byteCount, xbits, ybits, deltaXs[x], 0, -1, 0);
|
||
AddRecord(byteCount, xbits, ybits, deltaXs[x], 0, 1, 0);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
//set 2.1, - set5
|
||
for (int x = 0; x < deltaXs.Length; ++x)
|
||
{
|
||
ushort deltaX = deltaXs[x];
|
||
|
||
for (int y = 0; y < deltaYs.Length; ++y)
|
||
{
|
||
ushort deltaY = deltaYs[y];
|
||
|
||
AddRecord(byteCount, xbits, ybits, deltaX, deltaY, -1, -1);
|
||
AddRecord(byteCount, xbits, ybits, deltaX, deltaY, 1, -1);
|
||
AddRecord(byteCount, xbits, ybits, deltaX, deltaY, -1, 1);
|
||
AddRecord(byteCount, xbits, ybits, deltaX, deltaY, 1, 1);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
class TransformedLoca : UnreadTableEntry
|
||
{
|
||
public TransformedLoca(TableHeader header, Woff2TableDirectory tableDir) : base(header)
|
||
{
|
||
HasCustomContentReader = true;
|
||
TableDir = tableDir;
|
||
}
|
||
|
||
public Woff2TableDirectory TableDir { get; }
|
||
|
||
public override T CreateTableEntry<T>(BinaryReader reader, T expectedResult)
|
||
{
|
||
GlyphLocations loca = expectedResult as GlyphLocations;
|
||
if (loca == null) throw new System.NotSupportedException();
|
||
|
||
//nothing todo here :)
|
||
return expectedResult;
|
||
}
|
||
}
|
||
|
||
class Woff2Reader
|
||
{
|
||
private Woff2Header _header;
|
||
|
||
public BrotliDecompressStreamFunc DecompressHandler;
|
||
|
||
public Woff2Reader()
|
||
{
|
||
#if DEBUG
|
||
dbugVerifyKnownTables();
|
||
#endif
|
||
}
|
||
|
||
#if DEBUG
|
||
|
||
private static bool s_dbugPassVeriKnownTables;
|
||
|
||
static void dbugVerifyKnownTables()
|
||
{
|
||
if (s_dbugPassVeriKnownTables)
|
||
{
|
||
return;
|
||
}
|
||
//--------------
|
||
Dictionary<string, bool> uniqueNames = new Dictionary<string, bool>();
|
||
foreach (string name in s_knownTableTags)
|
||
{
|
||
if (!uniqueNames.ContainsKey(name))
|
||
{
|
||
uniqueNames.Add(name, true);
|
||
}
|
||
else
|
||
{
|
||
throw new System.Exception();
|
||
}
|
||
}
|
||
}
|
||
|
||
#endif
|
||
|
||
public PreviewFontInfo ReadPreview(BinaryReader reader)
|
||
{
|
||
_header = ReadHeader(reader);
|
||
if (_header == null) return null; //=> return here and notify user too.
|
||
Woff2TableDirectory[] woff2TablDirs = ReadTableDirectories(reader);
|
||
if (DecompressHandler == null)
|
||
{
|
||
//if no Brotli decoder=> return here and notify user too.
|
||
if (Woff2DefaultBrotliDecompressFunc.DecompressHandler != null)
|
||
{
|
||
DecompressHandler = Woff2DefaultBrotliDecompressFunc.DecompressHandler;
|
||
}
|
||
else
|
||
{
|
||
//return here and notify user too.
|
||
return null;
|
||
}
|
||
}
|
||
|
||
//try read each compressed tables
|
||
byte[] compressedBuffer = reader.ReadBytes((int)_header.totalCompressedSize);
|
||
if (compressedBuffer.Length != _header.totalCompressedSize)
|
||
{
|
||
//error!
|
||
return null; //can't read this, notify user too.
|
||
}
|
||
using (MemoryStream decompressedStream = new MemoryStream())
|
||
{
|
||
if (!DecompressHandler(compressedBuffer, decompressedStream))
|
||
{
|
||
//...Most notably,
|
||
//the data for the font tables is compressed in a SINGLE data stream comprising all the font tables.
|
||
|
||
//if not pass set to null
|
||
//decompressedBuffer = null;
|
||
return null;
|
||
}
|
||
//from decoded stream we read each table
|
||
decompressedStream.Position = 0;//reset pos
|
||
|
||
using (ByteOrderSwappingBinaryReader reader2 = new ByteOrderSwappingBinaryReader(decompressedStream))
|
||
{
|
||
TableEntryCollection tableEntryCollection = CreateTableEntryCollection(woff2TablDirs);
|
||
OpenFontReader openFontReader = new OpenFontReader();
|
||
return openFontReader.ReadPreviewFontInfo(tableEntryCollection, reader2);
|
||
}
|
||
}
|
||
}
|
||
|
||
internal bool Read(Typeface typeface, BinaryReader reader, RestoreTicket ticket)
|
||
{
|
||
_header = ReadHeader(reader);
|
||
if (_header == null) { return false; } //=> return here and notify user too.
|
||
|
||
Woff2TableDirectory[] woff2TablDirs = ReadTableDirectories(reader);
|
||
if (DecompressHandler == null)
|
||
{
|
||
//if no Brotli decoder=> return here and notify user too.
|
||
if (Woff2DefaultBrotliDecompressFunc.DecompressHandler != null)
|
||
{
|
||
DecompressHandler = Woff2DefaultBrotliDecompressFunc.DecompressHandler;
|
||
}
|
||
else
|
||
{
|
||
//return here and notify user too.
|
||
return false;
|
||
}
|
||
}
|
||
|
||
byte[] compressedBuffer = reader.ReadBytes((int)_header.totalCompressedSize);
|
||
if (compressedBuffer.Length != _header.totalCompressedSize)
|
||
{
|
||
//error!
|
||
return false; //can't read this, notify user too.
|
||
}
|
||
|
||
using (MemoryStream decompressedStream = new MemoryStream())
|
||
{
|
||
if (!DecompressHandler(compressedBuffer, decompressedStream))
|
||
{
|
||
//...Most notably,
|
||
//the data for the font tables is compressed in a SINGLE data stream comprising all the font tables.
|
||
|
||
//if not pass set to null
|
||
//decompressedBuffer = null;
|
||
return false;
|
||
}
|
||
//from decoded stream we read each table
|
||
decompressedStream.Position = 0;//reset pos
|
||
|
||
using (ByteOrderSwappingBinaryReader reader2 = new ByteOrderSwappingBinaryReader(decompressedStream))
|
||
{
|
||
TableEntryCollection tableEntryCollection = CreateTableEntryCollection(woff2TablDirs);
|
||
OpenFontReader openFontReader = new OpenFontReader();
|
||
return openFontReader.ReadTableEntryCollection(typeface, ticket, tableEntryCollection, reader2);
|
||
}
|
||
}
|
||
}
|
||
|
||
Woff2Header ReadHeader(BinaryReader reader)
|
||
{
|
||
//WOFF2 Header
|
||
//UInt32 signature 0x774F4632 'wOF2'
|
||
//UInt32 flavor The "sfnt version" of the input font.
|
||
//UInt32 length Total size of the WOFF file.
|
||
//UInt16 numTables Number of entries in directory of font tables.
|
||
//UInt16 reserved Reserved; set to 0.
|
||
//UInt32 totalSfntSize Total size needed for the uncompressed font data, including the sfnt header,
|
||
// directory, and font tables(including padding).
|
||
//UInt32 totalCompressedSize Total length of the compressed data block.
|
||
//UInt16 majorVersion Major version of the WOFF file.
|
||
//UInt16 minorVersion Minor version of the WOFF file.
|
||
//UInt32 metaOffset Offset to metadata block, from beginning of WOFF file.
|
||
//UInt32 metaLength Length of compressed metadata block.
|
||
//UInt32 metaOrigLength Uncompressed size of metadata block.
|
||
//UInt32 privOffset Offset to private data block, from beginning of WOFF file.
|
||
//UInt32 privLength Length of private data block.
|
||
|
||
Woff2Header header = new Woff2Header();
|
||
byte b0 = reader.ReadByte();
|
||
byte b1 = reader.ReadByte();
|
||
byte b2 = reader.ReadByte();
|
||
byte b3 = reader.ReadByte();
|
||
if (!(b0 == 0x77 && b1 == 0x4f && b2 == 0x46 && b3 == 0x32))
|
||
{
|
||
return null;
|
||
}
|
||
|
||
header.flavor = reader.ReadUInt32BE(); // sfnt version
|
||
string flavorName = Utils.TagToString(header.flavor);
|
||
|
||
header.length = reader.ReadUInt32BE();
|
||
header.numTables = reader.ReadUInt16BE();
|
||
ushort reserved = reader.ReadUInt16BE();
|
||
header.totalSfntSize = reader.ReadUInt32BE();
|
||
header.totalCompressedSize = reader.ReadUInt32BE();
|
||
|
||
header.majorVersion = reader.ReadUInt16BE();
|
||
header.minorVersion = reader.ReadUInt16BE();
|
||
|
||
header.metaOffset = reader.ReadUInt32BE();
|
||
header.metaLength = reader.ReadUInt32BE();
|
||
header.metaOriginalLength = reader.ReadUInt32BE();
|
||
|
||
header.privOffset = reader.ReadUInt32BE();
|
||
header.privLength = reader.ReadUInt32BE();
|
||
|
||
return header;
|
||
}
|
||
|
||
Woff2TableDirectory[] ReadTableDirectories(BinaryReader reader)
|
||
{
|
||
uint tableCount = (uint)_header.numTables; //?
|
||
var tableDirs = new Woff2TableDirectory[tableCount];
|
||
|
||
long expectedTableStartAt = 0;
|
||
|
||
for (int i = 0; i < tableCount; ++i)
|
||
{
|
||
//TableDirectoryEntry
|
||
//UInt8 flags table type and flags
|
||
//UInt32 tag 4-byte tag(optional)
|
||
//UIntBase128 origLength length of original table
|
||
//UIntBase128 transformLength transformed length(if applicable)
|
||
|
||
Woff2TableDirectory table = new Woff2TableDirectory();
|
||
byte flags = reader.ReadByte();
|
||
//The interpretation of the flags field is as follows.
|
||
|
||
//Bits[0..5] contain an index to the "known tag" table,
|
||
//which represents tags likely to appear in fonts.If the tag is not present in this table,
|
||
//then the value of this bit field is 63.
|
||
|
||
//interprete flags
|
||
int knowTable = flags & 0x1F; //5 bits => known table or not
|
||
|
||
table.Name = (knowTable < 63) ? s_knownTableTags[knowTable] : Utils.TagToString(reader.ReadUInt32()); //other tag
|
||
|
||
//Bits 6 and 7 indicate the preprocessing transformation version number(0 - 3) that was applied to each table.
|
||
|
||
//For all tables in a font, except for 'glyf' and 'loca' tables,
|
||
//transformation version 0 indicates the null transform where the original table data is passed directly
|
||
//to the Brotli compressor for inclusion in the compressed data stream.
|
||
|
||
//For 'glyf' and 'loca' tables,
|
||
//transformation version 3 indicates the null transform where the original table data was passed directly
|
||
//to the Brotli compressor without applying any pre - processing defined in subclause 5.1 and subclause 5.3.
|
||
|
||
//The transformed table formats and their associated transformation version numbers are
|
||
//described in details in clause 5 of this specification.
|
||
|
||
table.PreprocessingTransformation = (byte)((flags >> 5) & 0x3); //2 bits, preprocessing transformation
|
||
|
||
table.ExpectedStartAt = expectedTableStartAt;
|
||
//
|
||
if (!ReadUIntBase128(reader, out table.origLength))
|
||
{
|
||
//can't read 128=> error
|
||
}
|
||
|
||
switch (table.PreprocessingTransformation)
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case 0:
|
||
{
|
||
if (table.Name == Glyf._N)
|
||
{
|
||
if (!ReadUIntBase128(reader, out table.transformLength))
|
||
{
|
||
//can't read 128=> error
|
||
}
|
||
expectedTableStartAt += table.transformLength;//***
|
||
}
|
||
else if (table.Name == GlyphLocations._N)
|
||
{
|
||
//BUT by spec, transform 'loca' MUST has transformLength=0
|
||
if (!ReadUIntBase128(reader, out table.transformLength))
|
||
{
|
||
//can't read 128=> error
|
||
}
|
||
expectedTableStartAt += table.transformLength;//***
|
||
}
|
||
else
|
||
{
|
||
expectedTableStartAt += table.origLength;
|
||
}
|
||
}
|
||
break;
|
||
|
||
case 1:
|
||
{
|
||
expectedTableStartAt += table.origLength;
|
||
}
|
||
break;
|
||
|
||
case 2:
|
||
{
|
||
expectedTableStartAt += table.origLength;
|
||
}
|
||
break;
|
||
|
||
case 3:
|
||
{
|
||
expectedTableStartAt += table.origLength;
|
||
}
|
||
break;
|
||
}
|
||
tableDirs[i] = table;
|
||
}
|
||
|
||
return tableDirs;
|
||
}
|
||
|
||
static TableEntryCollection CreateTableEntryCollection(Woff2TableDirectory[] woffTableDirs)
|
||
{
|
||
TableEntryCollection tableEntryCollection = new TableEntryCollection();
|
||
for (int i = 0; i < woffTableDirs.Length; ++i)
|
||
{
|
||
Woff2TableDirectory woffTableDir = woffTableDirs[i];
|
||
UnreadTableEntry unreadTableEntry = null;
|
||
|
||
if (woffTableDir.Name == Glyf._N && woffTableDir.PreprocessingTransformation == 0)
|
||
{
|
||
//this is transformed glyf table,
|
||
//we need another techqniue
|
||
TableHeader tableHeader = new TableHeader(woffTableDir.Name, 0,
|
||
(uint)woffTableDir.ExpectedStartAt,
|
||
woffTableDir.transformLength);
|
||
unreadTableEntry = new TransformedGlyf(tableHeader, woffTableDir);
|
||
}
|
||
else if (woffTableDir.Name == GlyphLocations._N && woffTableDir.PreprocessingTransformation == 0)
|
||
{
|
||
//this is transformed glyf table,
|
||
//we need another techqniue
|
||
TableHeader tableHeader = new TableHeader(woffTableDir.Name, 0,
|
||
(uint)woffTableDir.ExpectedStartAt,
|
||
woffTableDir.transformLength);
|
||
unreadTableEntry = new TransformedLoca(tableHeader, woffTableDir);
|
||
}
|
||
else
|
||
{
|
||
TableHeader tableHeader = new TableHeader(woffTableDir.Name, 0,
|
||
(uint)woffTableDir.ExpectedStartAt,
|
||
woffTableDir.origLength);
|
||
unreadTableEntry = new UnreadTableEntry(tableHeader);
|
||
}
|
||
tableEntryCollection.AddEntry(unreadTableEntry);
|
||
}
|
||
|
||
return tableEntryCollection;
|
||
}
|
||
|
||
private static readonly string[] s_knownTableTags = new string[]
|
||
{
|
||
//Known Table Tags
|
||
//Flag Tag Flag Tag Flag Tag Flag Tag
|
||
//0 => cmap, 16 =>EBLC, 32 =>CBDT, 48 =>gvar,
|
||
//1 => head, 17 =>gasp, 33 =>CBLC, 49 =>hsty,
|
||
//2 => hhea, 18 =>hdmx, 34 =>COLR, 50 =>just,
|
||
//3 => hmtx, 19 =>kern, 35 =>CPAL, 51 =>lcar,
|
||
//4 => maxp, 20 =>LTSH, 36 =>SVG , 52 =>mort,
|
||
//5 => name, 21 =>PCLT, 37 =>sbix, 53 =>morx,
|
||
//6 => OS/2, 22 =>VDMX, 38 =>acnt, 54 =>opbd,
|
||
//7 => post, 23 =>vhea, 39 =>avar, 55 =>prop,
|
||
//8 => cvt , 24 =>vmtx, 40 =>bdat, 56 =>trak,
|
||
//9 => fpgm, 25 =>BASE, 41 =>bloc, 57 =>Zapf,
|
||
//10 => glyf, 26 =>GDEF, 42 =>bsln, 58 =>Silf,
|
||
//11 => loca, 27 =>GPOS, 43 =>cvar, 59 =>Glat,
|
||
//12 => prep, 28 =>GSUB, 44 =>fdsc, 60 =>Gloc,
|
||
//13 => CFF , 29 =>EBSC, 45 =>feat, 61 =>Feat,
|
||
//14 => VORG, 30 =>JSTF, 46 =>fmtx, 62 =>Sill,
|
||
//15 => EBDT, 31 =>MATH, 47 =>fvar, 63 =>arbitrary tag follows,...
|
||
//-------------------------------------------------------------------
|
||
|
||
//-- TODO:implement missing table too!
|
||
Cmap._N, //0
|
||
Head._N, //1
|
||
HorizontalHeader._N,//2
|
||
HorizontalMetrics._N,//3
|
||
MaxProfile._N,//4
|
||
NameEntry._N,//5
|
||
OS2Table._N, //6
|
||
PostTable._N,//7
|
||
CvtTable._N,//8
|
||
FpgmTable._N,//9
|
||
Glyf._N,//10
|
||
GlyphLocations._N,//11
|
||
PrepTable._N,//12
|
||
CFFTable._N,//13
|
||
"VORG",//14
|
||
EBDT._N,//15,
|
||
|
||
//---------------
|
||
EBLC._N,//16
|
||
Gasp._N,//17
|
||
HorizontalDeviceMetrics._N,//18
|
||
Kern._N,//19
|
||
"LTSH",//20
|
||
"PCLT",//21
|
||
VerticalDeviceMetrics._N,//22
|
||
VerticalHeader._N,//23
|
||
VerticalMetrics._N,//24
|
||
BASE._N,//25
|
||
GDEF._N,//26
|
||
GPOS._N,//27
|
||
GSUB._N,//28
|
||
EBSC._N, //29
|
||
"JSTF", //30
|
||
MathTable._N,//31
|
||
//---------------
|
||
|
||
//Known Table Tags (copy,same as above)
|
||
//Flag Tag Flag Tag Flag Tag Flag Tag
|
||
//0 => cmap, 16 =>EBLC, 32 =>CBDT, 48 =>gvar,
|
||
//1 => head, 17 =>gasp, 33 =>CBLC, 49 =>hsty,
|
||
//2 => hhea, 18 =>hdmx, 34 =>COLR, 50 =>just,
|
||
//3 => hmtx, 19 =>kern, 35 =>CPAL, 51 =>lcar,
|
||
//4 => maxp, 20 =>LTSH, 36 =>SVG , 52 =>mort,
|
||
//5 => name, 21 =>PCLT, 37 =>sbix, 53 =>morx,
|
||
//6 => OS/2, 22 =>VDMX, 38 =>acnt, 54 =>opbd,
|
||
//7 => post, 23 =>vhea, 39 =>avar, 55 =>prop,
|
||
//8 => cvt , 24 =>vmtx, 40 =>bdat, 56 =>trak,
|
||
//9 => fpgm, 25 =>BASE, 41 =>bloc, 57 =>Zapf,
|
||
//10 => glyf, 26 =>GDEF, 42 =>bsln, 58 =>Silf,
|
||
//11 => loca, 27 =>GPOS, 43 =>cvar, 59 =>Glat,
|
||
//12 => prep, 28 =>GSUB, 44 =>fdsc, 60 =>Gloc,
|
||
//13 => CFF , 29 =>EBSC, 45 =>feat, 61 =>Feat,
|
||
//14 => VORG, 30 =>JSTF, 46 =>fmtx, 62 =>Sill,
|
||
//15 => EBDT, 31 =>MATH, 47 =>fvar, 63 =>arbitrary tag follows,...
|
||
//-------------------------------------------------------------------
|
||
|
||
CBDT._N, //32
|
||
CBLC._N,//33
|
||
COLR._N,//34
|
||
CPAL._N,//35,
|
||
SvgTable._N,//36
|
||
"sbix",//37
|
||
"acnt",//38
|
||
"avar",//39
|
||
"bdat",//40
|
||
"bloc",//41
|
||
"bsln",//42
|
||
"cvar",//43
|
||
"fdsc",//44
|
||
"feat",//45
|
||
"fmtx",//46
|
||
"fvar",//47
|
||
//---------------
|
||
|
||
"gvar",//48
|
||
"hsty",//49
|
||
"just",//50
|
||
"lcar",//51
|
||
"mort",//52
|
||
"morx",//53
|
||
"opbd",//54
|
||
"prop",//55
|
||
"trak",//56
|
||
"Zapf",//57
|
||
"Silf",//58
|
||
"Glat",//59
|
||
"Gloc",//60
|
||
"Feat",//61
|
||
"Sill",//62
|
||
"...." //63 arbitrary tag follows
|
||
};
|
||
|
||
static bool ReadUIntBase128(BinaryReader reader, out uint result)
|
||
{
|
||
//UIntBase128 Data Type
|
||
|
||
//UIntBase128 is a different variable length encoding of unsigned integers,
|
||
//suitable for values up to 2^(32) - 1.
|
||
|
||
//A UIntBase128 encoded number is a sequence of bytes for which the most significant bit
|
||
//is set for all but the last byte,
|
||
//and clear for the last byte.
|
||
|
||
//The number itself is base 128 encoded in the lower 7 bits of each byte.
|
||
//Thus, a decoding procedure for a UIntBase128 is:
|
||
//start with value = 0.
|
||
//Consume a byte, setting value = old value times 128 + (byte bitwise - and 127).
|
||
//Repeat last step until the most significant bit of byte is false.
|
||
|
||
//UIntBase128 encoding format allows a possibility of sub-optimal encoding,
|
||
//where e.g.the same numerical value can be represented with variable number of bytes(utilizing leading 'zeros').
|
||
//For example, the value 63 could be encoded as either one byte 0x3F or two(or more) bytes: [0x80, 0x3f].
|
||
//An encoder must not allow this to happen and must produce shortest possible encoding.
|
||
//A decoder MUST reject the font file if it encounters a UintBase128 - encoded value with leading zeros(a value that starts with the byte 0x80),
|
||
//if UintBase128 - encoded sequence is longer than 5 bytes,
|
||
//or if a UintBase128 - encoded value exceeds 232 - 1.
|
||
|
||
//The "C-like" pseudo - code describing how to read the UIntBase128 format is presented below:
|
||
//bool ReadUIntBase128(data, * result)
|
||
// {
|
||
// UInt32 accum = 0;
|
||
|
||
// for (i = 0; i < 5; i++)
|
||
// {
|
||
// UInt8 data_byte = data.getNextUInt8();
|
||
|
||
// // No leading 0's
|
||
// if (i == 0 && data_byte = 0x80) return false;
|
||
|
||
// // If any of top 7 bits are set then << 7 would overflow
|
||
// if (accum & 0xFE000000) return false;
|
||
|
||
// *accum = (accum << 7) | (data_byte & 0x7F);
|
||
|
||
// // Spin until most significant bit of data byte is false
|
||
// if ((data_byte & 0x80) == 0)
|
||
// {
|
||
// *result = accum;
|
||
// return true;
|
||
// }
|
||
// }
|
||
// // UIntBase128 sequence exceeds 5 bytes
|
||
// return false;
|
||
// }
|
||
|
||
uint accum = 0;
|
||
result = 0;
|
||
for (int i = 0; i < 5; ++i)
|
||
{
|
||
byte data_byte = reader.ReadByte();
|
||
// No leading 0's
|
||
if (i == 0 && data_byte == 0x80) return false;
|
||
|
||
// If any of top 7 bits are set then << 7 would overflow
|
||
if ((accum & 0xFE000000) != 0) return false;
|
||
//
|
||
accum = (uint)(accum << 7) | (uint)(data_byte & 0x7F);
|
||
// Spin until most significant bit of data byte is false
|
||
if ((data_byte & 0x80) == 0)
|
||
{
|
||
result = accum;
|
||
return true;
|
||
}
|
||
//
|
||
}
|
||
// UIntBase128 sequence exceeds 5 bytes
|
||
return false;
|
||
}
|
||
}
|
||
|
||
class Woff2Utils
|
||
{
|
||
private const byte ONE_MORE_BYTE_CODE1 = 255;
|
||
private const byte ONE_MORE_BYTE_CODE2 = 254;
|
||
private const byte WORD_CODE = 253;
|
||
private const byte LOWEST_UCODE = 253;
|
||
|
||
public static short[] ReadInt16Array(BinaryReader reader, int count)
|
||
{
|
||
short[] arr = new short[count];
|
||
for (int i = 0; i < count; ++i)
|
||
{
|
||
arr[i] = reader.ReadInt16();
|
||
}
|
||
return arr;
|
||
}
|
||
|
||
public static ushort Read255UInt16(BinaryReader reader)
|
||
{
|
||
//255UInt16 Variable-length encoding of a 16-bit unsigned integer for optimized intermediate font data storage.
|
||
//255UInt16 Data Type
|
||
//255UInt16 is a variable-length encoding of an unsigned integer
|
||
//in the range 0 to 65535 inclusive.
|
||
//This data type is intended to be used as intermediate representation of various font values,
|
||
//which are typically expressed as UInt16 but represent relatively small values.
|
||
//Depending on the encoded value, the length of the data field may be one to three bytes,
|
||
//where the value of the first byte either represents the small value itself or is treated as a code that defines the format of the additional byte(s).
|
||
//The "C-like" pseudo-code describing how to read the 255UInt16 format is presented below:
|
||
// Read255UShort(data )
|
||
// {
|
||
// UInt8 code;
|
||
// UInt16 value, value2;
|
||
|
||
// const oneMoreByteCode1 = 255;
|
||
// const oneMoreByteCode2 = 254;
|
||
// const wordCode = 253;
|
||
// const lowestUCode = 253;
|
||
|
||
// code = data.getNextUInt8();
|
||
// if (code == wordCode)
|
||
// {
|
||
// /* Read two more bytes and concatenate them to form UInt16 value*/
|
||
// value = data.getNextUInt8();
|
||
// value <<= 8;
|
||
// value &= 0xff00;
|
||
// value2 = data.getNextUInt8();
|
||
// value |= value2 & 0x00ff;
|
||
// }
|
||
// else if (code == oneMoreByteCode1)
|
||
// {
|
||
// value = data.getNextUInt8();
|
||
// value = (value + lowestUCode);
|
||
// }
|
||
// else if (code == oneMoreByteCode2)
|
||
// {
|
||
// value = data.getNextUInt8();
|
||
// value = (value + lowestUCode * 2);
|
||
// }
|
||
// else
|
||
// {
|
||
// value = code;
|
||
// }
|
||
// return value;
|
||
// }
|
||
//Note that the encoding is not unique.For example,
|
||
//the value 506 can be encoded as [255, 253], [254, 0], and[253, 1, 250].
|
||
//An encoder may produce any of these, and a decoder MUST accept them all.An encoder should choose shorter encodings,
|
||
//and must be consistent in choice of encoding for the same value, as this will tend to compress better.
|
||
|
||
byte code = reader.ReadByte();
|
||
if (code == WORD_CODE)
|
||
{
|
||
/* Read two more bytes and concatenate them to form UInt16 value*/
|
||
//int value = (reader.ReadByte() << 8) & 0xff00;
|
||
//int value2 = reader.ReadByte();
|
||
//return (ushort)(value | (value2 & 0xff));
|
||
int value = reader.ReadByte();
|
||
value <<= 8;
|
||
value &= 0xff00;
|
||
int value2 = reader.ReadByte();
|
||
value |= value2 & 0x00ff;
|
||
|
||
return (ushort)value;
|
||
}
|
||
else if (code == ONE_MORE_BYTE_CODE1)
|
||
{
|
||
return (ushort)(reader.ReadByte() + LOWEST_UCODE);
|
||
}
|
||
else if (code == ONE_MORE_BYTE_CODE2)
|
||
{
|
||
return (ushort)(reader.ReadByte() + (LOWEST_UCODE * 2));
|
||
}
|
||
else
|
||
{
|
||
return code;
|
||
}
|
||
}
|
||
}
|
||
}
|