/// <summary> /// Determines whether the rectangle can be placed in the packing area /// at its current location. /// </summary> /// <param name="rectangle">Rectangle whose position to check</param> /// <param name="testedPackingAreaWidth">Total width of the packing area</param> /// <param name="testedPackingAreaHeight">Total height of the packing area</param> /// <returns>True if the rectangle can be placed at its current position</returns> private bool isFree( ref PackingRectangle rectangle, int testedPackingAreaWidth, int testedPackingAreaHeight ) { // If the rectangle is partially or completely outside of the packing // area, it can't be placed at its current location bool leavesPackingArea = (rectangle.x < 0) || (rectangle.y < 0) || (rectangle.Right > testedPackingAreaWidth) || (rectangle.Bottom > testedPackingAreaHeight); if (leavesPackingArea) { return(false); } // Brute-force search whether the rectangle touches any of the other // rectangles already in the packing area for (int index = 0; index < this.packedRectangles.Count; ++index) { if (this.packedRectangles[index].Intersects(rectangle)) { return(false); } } // Success! The rectangle is inside the packing area and doesn't overlap // with any other rectangles that have already been packed. return(true); }
/// <summary>Locates the first free anchor at which the rectangle fits</summary> /// <param name="rectangleWidth">Width of the rectangle to be placed</param> /// <param name="rectangleHeight">Height of the rectangle to be placed</param> /// <param name="testedPackingAreaWidth">Total width of the packing area</param> /// <param name="testedPackingAreaHeight">Total height of the packing area</param> /// <returns>The index of the first free anchor or -1 if none is found</returns> private int findFirstFreeAnchor( int rectangleWidth, int rectangleHeight, int testedPackingAreaWidth, int testedPackingAreaHeight ) { PackingRectangle potentialLocation = new PackingRectangle( 0, 0, rectangleWidth, rectangleHeight ); // Walk over all anchors (which are ordered by their distance to the // upper left corner of the packing area) until one is discovered that // can house the new rectangle. for (int index = 0; index < this.anchors.Count; ++index) { potentialLocation.x = this.anchors[index].x; potentialLocation.y = this.anchors[index].y; // See if the rectangle would fit in at this anchor point if (isFree(ref potentialLocation, testedPackingAreaWidth, testedPackingAreaHeight)) { return(index); } } // No anchor points were found where the rectangle would fit in return(-1); }
/// <summary> /// Optimizes the rectangle's placement by moving it either left or up to fill /// any gaps resulting from rectangles blocking the anchors of the most optimal /// placements. /// </summary> /// <param name="placement">Placement to be optimized</param> /// <param name="rectangleWidth">Width of the rectangle to be optimized</param> /// <param name="rectangleHeight">Height of the rectangle to be optimized</param> private void optimizePlacement( ref Point placement, int rectangleWidth, int rectangleHeight ) { PackingRectangle rectangle = new PackingRectangle( placement.x, placement.y, rectangleWidth, rectangleHeight ); // Try to move the rectangle to the left as far as possible int leftMost = placement.x; while (isFree(ref rectangle, packingAreaWidth, packingAreaHeight)) { leftMost = rectangle.x; --rectangle.x; } // Reset rectangle to original position rectangle.x = placement.x; // Try to move the rectangle upwards as far as possible int topMost = placement.y; while (isFree(ref rectangle, packingAreaWidth, packingAreaHeight)) { topMost = rectangle.y; --rectangle.y; } // Use the dimension in which the rectangle could be moved farther if ((placement.x - leftMost) > (placement.y - topMost)) { placement.x = leftMost; } else { placement.y = topMost; } }
/// <summary> /// Optimizes the rectangle's placement by moving it either left or up to fill /// any gaps resulting from rectangles blocking the anchors of the most optimal /// placements. /// </summary> /// <param name="placement">Placement to be optimized</param> /// <param name="rectangleWidth">Width of the rectangle to be optimized</param> /// <param name="rectangleHeight">Height of the rectangle to be optimized</param> private void OptimizePlacement( ref Point placement, int rectangleWidth, int rectangleHeight ) { var rectangle = new PackingRectangle( placement.X, placement.Y, rectangleWidth, rectangleHeight ); // Try to move the rectangle to the left as far as possible var leftMost = placement.X; while (IsFree(ref rectangle, _packingAreaWidth, _packingAreaHeight)) { leftMost = rectangle.X; --rectangle.X; } // Reset rectangle to original position rectangle.X = placement.X; // Try to move the rectangle upwards as far as possible var topMost = placement.Y; while (IsFree(ref rectangle, _packingAreaWidth, _packingAreaHeight)) { topMost = rectangle.Y; --rectangle.Y; } // Use the dimension in which the rectangle could be moved farther if ((placement.X - leftMost) > (placement.Y - topMost)) { placement.X = leftMost; } else { placement.Y = topMost; } }
public bool Intersects(PackingRectangle value) { return((((value.x < (x + Width)) && (x < (value.x + value.Width))) && (value.y < (y + Height))) && (y < (value.y + value.Height))); }
public bool Intersects(PackingRectangle value) { return((((value.X < (X + Width)) && (X < (value.X + value.Width))) && (value.Y < (Y + Height))) && (Y < (value.Y + value.Height))); }
/// <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; } }