/// <summary> /// Creates a <see cref="TrippyFontFile"/> by reading it's data from a stream. /// </summary> public static TrippyFontFile FromStream(BinaryReader streamReader) { if (streamReader == null) { throw new ArgumentNullException(nameof(streamReader)); } if (!ReadPreamble(streamReader.BaseStream)) { throw new FontLoadingException("Wrong preamble. Ensure you're loading the correct data."); } streamReader.ReadInt32(); streamReader.ReadInt32(); streamReader.ReadInt32(); streamReader.ReadInt32(); ushort fontCount = streamReader.ReadUInt16(); if (fontCount == 0) { throw new FontLoadingException("Font count can't be 0."); } TextureFontData[] fontDatas = new TextureFontData[fontCount]; for (int i = 0; i < fontDatas.Length; i++) { fontDatas[i] = TextureFontData.FromStream(streamReader); } streamReader.ReadChar(); // 'p' streamReader.ReadChar(); // 'n' streamReader.ReadChar(); // 'g' Image <Rgba32> image = SixLabors.ImageSharp.Image.Load <Rgba32>(streamReader.BaseStream); return(new TrippyFontFile(fontDatas, image)); }
/// <summary> /// Creates a <see cref="TrippyFontFile"/> holding information for multiple fonts. /// </summary> /// <param name="glyphSources">The <see cref="IGlyphSource"/>-s for getting the information of each font.</param> /// <param name="backgroundColor">The background color of the generated image. Null for transparent.</param> public static TrippyFontFile CreateFontFile(ReadOnlySpan <IGlyphSource> glyphSources, Color?backgroundColor = null) { // We create all the TextureFontData-s and query their basic information from the glyph sources. TextureFontData[] fontDatas = new TextureFontData[glyphSources.Length]; for (int i = 0; i < fontDatas.Length; i++) { fontDatas[i] = new TextureFontData() { Size = glyphSources[i].Size, FirstChar = glyphSources[i].FirstChar, LastChar = glyphSources[i].LastChar, Ascender = glyphSources[i].Ascender, Descender = glyphSources[i].Descender, LineGap = glyphSources[i].LineGap, Name = glyphSources[i].Name }; } // We count the total amount of characters in all glyph sources combined. int charCount = 0; for (int i = 0; i < glyphSources.Length; i++) { charCount += fontDatas[i].CharCount; } // We need to find a way to pack all the characters into a single texture. // For this we use the included Rectpack library in TrippyGL.Fonts.Rectpack. PackingRectangle[] packingRects = new PackingRectangle[charCount]; // The way we identify the PackingRectangles is with their rect.Id property. // Since we have multiple fonts, we can't set these to the characters they represent // because there can be collisions. So the way we assign IDs will be basically like this: // The IDs for the characters of the first font start at 0 and go up to font.CharCount // inclusive. These are, of course, in order of character. // The second font then gets the range that starts right after where the first font's // range ends and gets enough range for all it's characters, etc etc. // So we need to know where the range of IDs for each font starts and end. We'll store it here: Span <int> idsStart = glyphSources.Length <= 96 ? stackalloc int[glyphSources.Length] : new int[glyphSources.Length]; int packingRectCount = 0; for (int i = 0; i < fontDatas.Length; i++) { // We calculate the starting ID for the current glyph source. int idStart = i == 0 ? 0 : idsStart[i - 1] + fontDatas[i - 1].CharCount; idsStart[i] = idStart; // We go through all the characters in the current glyph source. for (int c = fontDatas[i].FirstChar; c <= fontDatas[i].LastChar; c++) { // We get the size. If it is positive, we add a PackingRectangle to represent it. System.Drawing.Point size = glyphSources[i].GetGlyphSize(c); if (size.X > 0 && size.Y > 0) { // We add 2 to the width and height of the rectangle so chars have an empty border. int id = idStart + c - fontDatas[i].FirstChar; packingRects[packingRectCount++] = new PackingRectangle(0, 0, (uint)size.X + 2, (uint)size.Y + 2, id); } } } // We trim extra elements off the packingRects array. if (packingRects.Length != packingRectCount) { Array.Resize(ref packingRects, packingRectCount); } // We use RectanglePacker to find a bin for all the rectangles. RectanglePacker.Pack(packingRects, out PackingRectangle bounds); Image <Rgba32> image = new Image <Rgba32>((int)bounds.Width, (int)bounds.Height); try { // First we clear the image to the specified background color, or transparent. Color bgColor = backgroundColor ?? Color.Transparent; image.Mutate(x => x.BackgroundColor(bgColor)); // We create the source rectangles arrays for all the fontDatas. for (int i = 0; i < fontDatas.Length; i++) { fontDatas[i].SourceRectangles = new System.Drawing.Rectangle[fontDatas[i].CharCount]; } // We go through all the packing rectangles. for (int i = 0; i < packingRects.Length; i++) { PackingRectangle rect = packingRects[i]; // We find which glyph source this rectangle belongs to. int glyphSourceIndex = 0; while (glyphSourceIndex + 1 < idsStart.Length && idsStart[glyphSourceIndex + 1] < rect.Id) { glyphSourceIndex++; } // We find which character this rectangle represents. int charIndex = rect.Id - idsStart[glyphSourceIndex]; int charCode = charIndex + fontDatas[glyphSourceIndex].FirstChar; // We draw the glyph onto the image at this rectangle's location. glyphSources[glyphSourceIndex].DrawGlyphToImage(charCode, new System.Drawing.Point((int)rect.X + 1, (int)rect.Y + 1), image); // We set the glyph's source to match this rectangle. fontDatas[glyphSourceIndex].SourceRectangles[charIndex] = new System.Drawing.Rectangle((int)rect.X + 1, (int)rect.Y + 1, (int)rect.Width - 2, (int)rect.Height - 2); } // We go through all the fontDatas and set the remaining information. for (int i = 0; i < fontDatas.Length; i++) { fontDatas[i].RenderOffsets = glyphSources[i].GetRenderOffsets(); glyphSources[i].GetAdvances(out fontDatas[i].Advances); if (!glyphSources[i].TryGetKerning(out fontDatas[i].KerningOffsets)) { fontDatas[i].KerningOffsets = null; } } // Done! return(new TrippyFontFile(fontDatas, image)); } catch { // If anything failed, we dispose the image and re-throw the exception. image.Dispose(); throw; } }