Exemplo n.º 1
0
        /// <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);
        }
Exemplo n.º 2
0
        /// <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);
        }
Exemplo n.º 3
0
        /// <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;
            }
        }
Exemplo n.º 4
0
        /// <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;
            }
        }
Exemplo n.º 5
0
 public bool Intersects(PackingRectangle value)
 {
     return((((value.x < (x + Width)) && (x < (value.x + value.Width))) && (value.y < (y + Height))) && (y < (value.y + value.Height)));
 }
Exemplo n.º 6
0
 public bool Intersects(PackingRectangle value)
 {
     return((((value.X < (X + Width)) && (X < (value.X + value.Width))) && (value.Y < (Y + Height))) && (Y < (value.Y + value.Height)));
 }
Exemplo n.º 7
0
        /// <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;
            }
        }