예제 #1
0
        private HashSet <LineShape> FilterHorizontalLines(Image hlines, BoundedObjectGrid <CheckboxShape> checkboxGrid)
        {
            HashSet <LineShape> lines = new HashSet <LineShape>();

            int minThickLineWidth  = LineDetector.MinThickLineWidth.MulDiv(hlines.VerticalResolution, 200);
            int minThickLineLength = LineDetector.MinThickLineLength.MulDiv(hlines.HorizontalResolution, 200);
            int maxBoxSize         = this.MaxBoxSize.MulDiv(hlines.HorizontalResolution, 200);

            // find connected components and do preliminary filtering
            foreach (ConnectedComponent component in hlines.FindConnectedComponents(8))
            {
                Rectangle bounds   = component.Bounds;
                int       maxWidth = component.MaxHeight();

                bool isBad = false;

                if (bounds.Width.Between(minThickLineWidth, minThickLineLength - 1) &&
                    bounds.Height.Between(minThickLineWidth, minThickLineLength - 1) &&
                    maxWidth > minThickLineLength)
                {
                    // too thick for the length
                    isBad = true;
                }

                if (!isBad && bounds.Width <= maxBoxSize && checkboxGrid != null && checkboxGrid.EnumObjects(bounds).Any())
                {
                    // is part of the check box
                    isBad = true;
                }

                if (isBad)
                {
                    hlines.SetWhite(bounds);
                }
                else
                {
                    LineShape newline = new LineShape(bounds, maxWidth, LineTypes.Horizontal);
                    lines.Add(newline);
                }
            }

            // find line vectors
            AlignedObjectGrid <LineShape> grid = new AlignedObjectGrid <LineShape>(hlines.Bounds, 10, 20, RectangleLTRBComparer.Default);

            grid.AddRange(lines, true, true);

            int minLineLength = (this.MinHorizontalLineLength * hlines.HorizontalResolution).Round();
            int maxGap        = LineDetector.MaxLineGap.MulDiv(hlines.HorizontalResolution, 200);
            int tolerance     = 3.MulDiv(hlines.VerticalResolution, 200);

            HashSet <LineShape> newlines = new HashSet <LineShape>();

            foreach (LineShape line in lines)
            {
                if (line.VerticalAlignment == VerticalAlignment.None)
                {
                    IList <LineShape> alignedLines = grid.FindVerticalAlignment(
                        line,
                        VerticalAlignment.Center,
                        maxGap,
                        tolerance,
                        (result, bounds) => bounds.Width >= minLineLength && (result.Count >= 2 || result.Any(o => o.Bounds.Width >= minLineLength)));

                    if (alignedLines.Count > 0)
                    {
                        LineShape newline = new LineShape(
                            Rectangle.Union(alignedLines.Select(x => x.Bounds)),
                            alignedLines[0].Begin,
                            alignedLines[alignedLines.Count - 1].End,
                            alignedLines.Max(x => x.Width),
                            LineTypes.Horizontal);

                        newlines.Add(newline);
                    }
                }
            }

            // remove merged and short lines
            lines.RemoveWhere(x =>
            {
                // remove merged lines
                bool remove = x.VerticalAlignment != VerticalAlignment.None;

                // remove small not merged lines
                // also delete them from the image
                if (x.VerticalAlignment == VerticalAlignment.None && x.Bounds.Width < minLineLength)
                {
                    hlines.SetWhite(x.Bounds);
                    remove = true;
                }

                return(remove);
            });

            lines.UnionWith(newlines);

            return(lines);
        }
예제 #2
0
        private ISet <CheckboxShape> FindBoxes(Image image, CancellationToken cancellationToken)
        {
            HashSet <CheckboxShape> result = new HashSet <CheckboxShape>(32);

            // compute image-dependent parameters
            int minBoxSizeH = this.MinBoxSize.MulDiv(image.HorizontalResolution, 200);
            int maxBoxSizeH = this.MaxBoxSize.MulDiv(image.HorizontalResolution, 200);
            int minBoxSizeV = this.MinBoxSize.MulDiv(image.VerticalResolution, 200);
            int maxBoxSizeV = this.MaxBoxSize.MulDiv(image.VerticalResolution, 200);

            // keep track of tested horizontal components that did not yield results
            ////HashSet<Rectangle> testedHBounds = new HashSet<Rectangle>();
            HashSet <Rectangle> testedVBounds = new HashSet <Rectangle>();

            // the algorithm proceeds in two steps
            // first, we find a pair of parallel horizontal lines that have similar length and horizontal position
            // second, we find a pair of parallel vertical lines that would connect horizontal lines on both sides to form a box
            IList <ConnectedComponent> hlines = FindHorizontalLines();

            if (hlines != null && hlines.Count > 0)
            {
                IList <ConnectedComponent> vlines = FindVerticalLines();
                if (vlines != null && vlines.Count > 0)
                {
                    BoundedObjectGrid <ConnectedComponent> hgrid = CreateGrid(hlines);
                    BoundedObjectGrid <ConnectedComponent> vgrid = CreateGrid(vlines);
                    foreach (ConnectedComponent vcomp1 in vlines.Where(x => x.Bounds.Height <= maxBoxSizeV))
                    {
                        if (vcomp1.HorizontalAlignment == HorizontalAlignment.None)
                        {
                            Rectangle vbounds1 = vcomp1.Bounds;
                            int       vdelta   = vbounds1.Height / 5;

                            foreach (ConnectedComponent vcomp2 in vgrid.EnumObjects(Rectangle.Inflate(vbounds1, vbounds1.Height.MulDiv(2, 1), 0)))
                            {
                                if (vcomp2 != vcomp1 && vcomp2.HorizontalAlignment == HorizontalAlignment.None)
                                {
                                    // test second vertical component
                                    if (!TestVerticalComponents(vbounds1, vcomp2, out Rectangle vbounds2, vdelta, out bool longVLine))
                                    {
                                        continue;
                                    }

                                    Rectangle vbounds = Rectangle.Union(vbounds1, vbounds2);
                                    vdelta = MinMax.Max(vbounds.Width, vbounds.Height) / 5;

                                    if (testedVBounds.Contains(vbounds))
                                    {
                                        continue;
                                    }

                                    testedVBounds.Add(vbounds);

                                    // after we found a pair of matching horizontal lines
                                    // start looking for a pair of vertical lines that connect them
                                    ConnectedComponent hcompTop = null;
                                    int topEst = 0;
                                    ConnectedComponent hcompBottom = null;
                                    int       bottomEst            = 0;
                                    Rectangle hboundsBottom        = Rectangle.Empty;
                                    bool      longHLine            = false;
                                    foreach (ConnectedComponent hcomp in hgrid.EnumObjects(Rectangle.Inflate(vbounds, 0, vdelta)))
                                    {
                                        if (hcomp.VerticalAlignment == VerticalAlignment.None)
                                        {
                                            if (TestTopComponent(vbounds, hcomp.Bounds, vdelta, out int est))
                                            {
                                                if (hcompTop == null || est < topEst)
                                                {
                                                    hcompTop = hcomp;
                                                    topEst   = est;
                                                }
                                            }

                                            if (TestBottomComponent(vbounds, hcomp, out Rectangle hbounds, vdelta, out bool longLine, out est))
                                            {
                                                if (hcompBottom == null || est < bottomEst || (longHLine && !longLine))
                                                {
                                                    hcompBottom   = hcomp;
                                                    bottomEst     = est;
                                                    hboundsBottom = hbounds;
                                                    longHLine     = longLine;
                                                }
                                            }
                                        }
                                    }

                                    if (hcompTop != null && hcompBottom != null)
                                    {
                                        Rectangle bounds = Rectangle.FromLTRB(vbounds.X, hcompTop.Bounds.Y, vbounds.Right, hboundsBottom.Bottom);
                                        result.Add(new CheckboxShape(bounds));

                                        // mark used components, so we do not test them twice
                                        hcompTop.VerticalAlignment = VerticalAlignment.Top;
                                        if (!longHLine)
                                        {
                                            hcompBottom.VerticalAlignment = VerticalAlignment.Bottom;
                                        }

                                        vcomp1.HorizontalAlignment = vbounds1.X < vbounds2.X ? HorizontalAlignment.Left : HorizontalAlignment.Right;
                                        if (!longVLine)
                                        {
                                            vcomp2.HorizontalAlignment = vbounds2.X < vbounds1.X ? HorizontalAlignment.Left : HorizontalAlignment.Right;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return(result);

            IList <ConnectedComponent> FindVerticalLines()
            {
                int maxLineWidth = LineDetector.MaxLineWidth.MulDiv(image.HorizontalResolution, 200);

                Image lines = image.MorphOpen(null, StructuringElement.Brick(1, minBoxSizeV), 1, BorderType.BorderConst, image.WhiteColor);

                if (lines.IsAllWhite())
                {
                    return(null);
                }

                ISet <ConnectedComponent> comps = lines.FindConnectedComponents(8);

                cancellationToken.ThrowIfCancellationRequested();

                return(comps.Where(x => x.MaxWidth() <= maxLineWidth).ToArray());
            }

            IList <ConnectedComponent> FindHorizontalLines()
            {
                int maxLineWidth = LineDetector.MaxLineWidth.MulDiv(image.VerticalResolution, 200);

                Image lines = image.MorphOpen(null, StructuringElement.Brick(minBoxSizeH, 1), 1, BorderType.BorderConst, image.WhiteColor);

                if (lines.IsAllWhite())
                {
                    return(null);
                }

                cancellationToken.ThrowIfCancellationRequested();

                ISet <ConnectedComponent> comps = lines.FindConnectedComponents(8);

                cancellationToken.ThrowIfCancellationRequested();

                return(comps.Where(x => x.MaxHeight() <= maxLineWidth).ToArray());
            }

            BoundedObjectGrid <ConnectedComponent> CreateGrid(IEnumerable <ConnectedComponent> comps)
            {
                BoundedObjectGrid <ConnectedComponent> grid = new BoundedObjectGrid <ConnectedComponent>(image.Bounds, 10, 20);

                grid.AddRange(comps, true, true);
                return(grid);
            }

            bool TestVerticalComponents(Rectangle bounds1, ConnectedComponent comp, out Rectangle bounds2, int delta, out bool longLine)
            {
                longLine = false;
                bounds2  = comp.Bounds;

                if (bounds2.Height > maxBoxSizeV)
                {
                    // if touching long line, get the part of it that intersects with the box
                    bounds2  = comp.Intersect(Rectangle.FromLTRB(bounds2.X, bounds1.Y, bounds2.Right, bounds1.Bottom));
                    longLine = true;
                }

                int dist = Math.Abs(bounds1.CenterX - bounds2.CenterX);

                if (!dist.Between(minBoxSizeH, maxBoxSizeH))
                {
                    // the distance between lines is invalid
                    return(false);
                }

                // both lines are isolated - check for squareness of the box
                return(dist.Between(MinMax.Min(bounds1.Height, bounds2.Height) - delta, (2 * MinMax.Max(bounds1.Height, bounds2.Height)) + delta) &&
                       bounds2.Y.AreEqual(bounds1.Y, delta) &&
                       bounds2.Bottom.AreEqual(bounds1.Bottom, delta));
            }

            bool TestTopComponent(Rectangle vbounds, Rectangle hbounds, int delta, out int est)
            {
                int est1 = Math.Abs(hbounds.Top - vbounds.Top);
                int est2 = Math.Abs(hbounds.X - vbounds.X);
                int est3 = Math.Abs(hbounds.Right - vbounds.Right);

                est = est1 + est2 + est3;

                return(est1 <= delta && est2 <= delta && est3 <= delta);
            }

            bool TestBottomComponent(Rectangle vbounds, ConnectedComponent hcomp, out Rectangle hbounds, int delta, out bool longLine, out int est)
            {
                longLine = false;
                hbounds  = hcomp.Bounds;

                if (hbounds.Width > maxBoxSizeH)
                {
                    // if touching long line, get the part of it that intersects with the box
                    hbounds  = hcomp.Intersect(Rectangle.FromLTRB(vbounds.X, hbounds.Y, vbounds.Right, hbounds.Bottom));
                    longLine = true;
                }

                int est1 = Math.Abs(hbounds.Bottom - vbounds.Bottom);
                int est2 = Math.Abs(hbounds.X - vbounds.X);
                int est3 = Math.Abs(hbounds.Right - vbounds.Right);

                est = est1 + est2 + est3;

                return(est1 <= delta && est2 <= delta && est3 <= delta);
            }
        }
예제 #3
0
        private HashSet <LineShape> FilterVerticalLines(Image vlines, Image hlines, BoundedObjectGrid <CheckboxShape> checkboxGrid)
        {
            HashSet <LineShape> lines = new HashSet <LineShape>();

            int minThickLineWidth  = LineDetector.MinThickLineWidth.MulDiv(vlines.HorizontalResolution, 200);
            int minThickLineLength = LineDetector.MinThickLineLength.MulDiv(vlines.VerticalResolution, 200);
            int maxBoxSize         = this.MaxBoxSize.MulDiv(vlines.VerticalResolution, 200);

            // find connected components and do preliminary filtering
            foreach (ConnectedComponent component in vlines.FindConnectedComponents(8))
            {
                Rectangle bounds   = component.Bounds;
                int       maxWidth = component.MaxWidth();

                bool isBad = false;

                if (bounds.Width.Between(minThickLineWidth, minThickLineLength - 1) &&
                    bounds.Height.Between(minThickLineWidth, minThickLineLength - 1) &&
                    maxWidth > minThickLineLength)
                {
                    // too thick for the length
                    isBad = true;
                }

                if (!isBad && bounds.Height <= maxBoxSize && checkboxGrid != null && checkboxGrid.EnumObjects(bounds).Any())
                {
                    // is part of the check box
                    isBad = true;
                }

                if (isBad)
                {
                    vlines.SetWhite(bounds);
                }
                else
                {
                    Point     begin = new Point(component.GetLine(bounds.Y).Center, bounds.Y);
                    Point     end   = new Point(component.GetLine(bounds.Bottom - 1).Center, bounds.Bottom - 1);
                    LineShape line  = new LineShape(bounds, begin, end, maxWidth, LineTypes.Vertical);
                    lines.Add(line);
                }
            }

            // find line vectors
            AlignedObjectGrid <LineShape> grid = new AlignedObjectGrid <LineShape>(vlines.Bounds, 10, 20, RectangleTBLRComparer.Default);

            grid.AddRange(lines, true, true);

            int minLineLength = (this.MinVerticalLineLength * vlines.VerticalResolution).Round();
            int maxGap        = LineDetector.MaxLineGap.MulDiv(vlines.VerticalResolution, 200);
            int tolerance     = 6.MulDiv(vlines.HorizontalResolution, 200);

            HashSet <LineShape> newlines = new HashSet <LineShape>();

            foreach (LineShape line in lines)
            {
                if (line.HorizontalAlignment == HorizontalAlignment.None)
                {
                    IList <LineShape> alignedLines = grid.FindHorizontalAlignment(
                        line,
                        HorizontalAlignment.Center,
                        Math.Min(maxGap, line.Bounds.Height),
                        tolerance,
                        (result, bounds) => (bounds.Height >= minLineLength && (result.Count >= 3 || result.Any(o => o.Bounds.Height >= minLineLength))) || LineHasIntersectionsOnBothEnds(bounds));

                    if (alignedLines.Count > 0)
                    {
                        LineShape newline = new LineShape(
                            Rectangle.Union(alignedLines.Select(x => x.Bounds)),
                            alignedLines[0].Begin,
                            alignedLines[alignedLines.Count - 1].End,
                            alignedLines.Max(x => x.Width),
                            LineTypes.Vertical);

                        newlines.Add(newline);
                    }
                }
            }

            // remove merged and short lines
            lines.RemoveWhere(x =>
            {
                // remove merged lines
                bool remove = x.HorizontalAlignment != HorizontalAlignment.None;

                // remove small not merged lines
                // also delete them from the image
                if (x.HorizontalAlignment == HorizontalAlignment.None && x.Bounds.Height < minLineLength && !LineHasIntersectionsOnBothEnds(x.Bounds))
                {
                    vlines.SetWhite(x.Bounds);
                    remove = true;
                }

                return(remove);
            });

            lines.UnionWith(newlines);

            bool LineHasIntersectionsOnBothEnds(Rectangle bounds)
            {
                if (hlines != null)
                {
                    int boxheight = Math.Min(bounds.Width, bounds.Height);

                    Rectangle top = new Rectangle(bounds.X, bounds.Y, bounds.Width, boxheight);
                    top.Inflate(0, 2 * boxheight);
                    top.Intersect(hlines.Bounds);

                    Rectangle bottom = new Rectangle(bounds.X, bounds.Bottom - boxheight, bounds.Width, boxheight);
                    bottom.Inflate(0, 2 * boxheight);
                    bottom.Intersect(hlines.Bounds);

                    return(!hlines.IsAllWhite(top) && !hlines.IsAllWhite(bottom));
                }
                else
                {
                    return(false);
                }
            }

            return(lines);
        }
예제 #4
0
        /// <summary>
        /// Finds machine-printed text on the <see cref="Image"/>.
        /// The type of text to find is determined by the class parameters.
        /// </summary>
        /// <param name="image">The source <see cref="Image"/>.</param>
        /// <param name="lines">The lines located on the <paramref name="image"/>.</param>
        /// <param name="cancellationToken">The cancellationToken token used to notify the <see cref="TextDetector"/> that operation should be canceled.</param>
        /// <returns>
        /// The detected text.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="image"/> is <b>null</b>.
        /// </exception>
        /// <exception cref="NotImplementedException">
        /// <see cref="Image{T}.BitsPerPixel"/> is not one.
        /// </exception>
        /// <remarks>
        /// <para>This method works with binary (1bpp) images only.</para>
        /// </remarks>
        public ISet <TextShape> FindText(Image image, IEnumerable <LineShape> lines, CancellationToken cancellationToken)
        {
            if (image == null)
            {
                throw new ArgumentNullException(nameof(image));
            }

            if (image.BitsPerPixel != 1)
            {
                throw new NotImplementedException(Properties.Resources.E_UnsupportedDepth_1bpp);
            }

            Image closing = image.MorphClose(null, StructuringElement.Brick(9, 2), 1, BorderType.BorderConst, 0);

            // find components
            HashSet <ConnectedComponent> components = closing.FindConnectedComponents(4);

            // filter components
            int maxTextHeight = (TextDetector.MaxTextHeight * image.VerticalResolution).Round();

            components.RemoveWhere(x => /*x.Power > 10 &&*/ x.Bounds.Height > maxTextHeight);

            // index components
            BoundedObjectGrid <ConnectedComponent> componentgrid = new BoundedObjectGrid <ConnectedComponent>(image.Bounds, 10, 20);

            componentgrid.AddRange(components, true, true);
            componentgrid.Compact();

            // index vertical lines
            BoundedObjectGrid <LineShape> vlines = new BoundedObjectGrid <LineShape>(image.Bounds, 10, 1);

            vlines.AddRange(lines.Where(x => x.Types.HasFlag(LineTypes.Vertical)), true, true);

            BoundedObjectGrid <TextShape> shapegrid = new BoundedObjectGrid <TextShape>(image.Bounds, 10, 20);

            foreach (ConnectedComponent component in componentgrid.EnumObjects())
            {
                if (component.VerticalAlignment == VerticalAlignment.None)
                {
                    IList <ConnectedComponent> alignedComponents = FindTextShapes(componentgrid, component);
                    if (alignedComponents.Count > 0)
                    {
                        shapegrid.Add(new TextShape(Rectangle.Union(alignedComponents.Select(x => x.Bounds))), true, true);
                    }
                }
            }

            // assign unassigned components
            foreach (ConnectedComponent component in componentgrid.EnumObjects())
            {
                if (component.VerticalAlignment == VerticalAlignment.None)
                {
                    if (shapegrid.FindContainer(component.Bounds) == null)
                    {
                        shapegrid.Add(new TextShape(component.Bounds), true, true);
                    }
                }
            }

            shapegrid.Compact();

            // create result
            HashSet <TextShape> shapes = new HashSet <TextShape>();

            shapes.UnionWith(shapegrid.EnumObjects().Where(x => x.Bounds.Width > 4 && x.Bounds.Height > 10));

#if DEBUG
            Image draft = image.ConvertTo(null, 32);
            foreach (TextShape shape in shapes)
            {
                draft.DrawRectangle(shape.Bounds, Color.Red);
            }
#endif

            return(shapes);

            IList <ConnectedComponent> FindTextShapes(BoundedObjectGrid <ConnectedComponent> grid, ConnectedComponent obj)
            {
                ////Rectangle bounds = obj.Bounds;
                SortedList <Rectangle, ConnectedComponent> result = new SortedList <Rectangle, ConnectedComponent>(RectangleLTRBComparer.Default);

                Rectangle obounds = obj.Bounds;

                // calculate initial pivot points
                Line topline    = new Line(obounds.Left, obounds.Top, obounds.Right, obounds.Top);
                Line bottomline = new Line(obounds.Left, obounds.Bottom, obounds.Right, obounds.Bottom);

                // find objects to the left
                ConnectedComponent next;

                while ((next = FindNext(obounds, false)) != null)
                {
                    result.Add(next.Bounds, next);

                    next.VerticalAlignment = VerticalAlignment.Bottom;
                    obounds.Union(next.Bounds);
                }

                // find objects to the right
                while ((next = FindNext(obounds, true)) != null)
                {
                    result.Add(next.Bounds, next);

                    next.VerticalAlignment = VerticalAlignment.Bottom;
                    obounds.Union(next.Bounds);
                }

                if (result.Count > 0)
                {
                    obj.VerticalAlignment = VerticalAlignment.Bottom;
                    result.Add(obj.Bounds, obj);

                    // mark contained elements
                    foreach (ConnectedComponent o in grid.EnumObjects(obounds))
                    {
                        if (o.VerticalAlignment == VerticalAlignment.None && obounds.Contains(o.Bounds))
                        {
                            o.VerticalAlignment = VerticalAlignment.Bottom;
                            result.Add(o.Bounds, o);
                        }
                    }
                }

                return(result.Values);

                ConnectedComponent FindNext(Rectangle box, bool searchForward)
                {
                    // Tolerance to skew on top of current estimate of skew. Divide x or y length
                    // by kMaxSkewFactor to get the y or x skew distance.
                    // If the angle is small, the angle in degrees is roughly 60/kMaxSkewFactor.
                    const int MaxSkewFactor = 15;

                    // compute search area
                    int xstart = searchForward ? box.Right : box.Left;
                    int xend   = searchForward ? box.Right + (box.Height * 2) : box.Left - (box.Height * 2);

                    Rectangle searchArea = searchForward ?
                                           Rectangle.FromLTRB(xstart, box.Top, xend, box.Bottom) :
                                           Rectangle.FromLTRB(xend, box.Top, xstart, box.Bottom);

                    // look for lines crossing the search area
                    foreach (LineShape line in vlines.EnumObjects(searchArea))
                    {
                        int x = new Line(line.Begin, line.End).X(box.Bottom);

                        if (searchForward)
                        {
                            int right = MinMax.Min(searchArea.Right, x - (line.Width / 2));
                            searchArea.InflateX(0, right - searchArea.Right);
                        }
                        else
                        {
                            int left = MinMax.Max(searchArea.Left, x + (line.Width / 2));
                            searchArea.InflateX(searchArea.Left - left, 0);
                        }
                    }

                    if (searchArea.Width == 0 || searchArea.Height == 0)
                    {
                        return(null);
                    }

                    // Compute skew tolerance and expand the search box
                    int skewTolerance = searchArea.Width / MaxSkewFactor;

                    searchArea.Inflate(0, skewTolerance);

                    ConnectedComponent bestCandidate = null;
                    int bestDistance = int.MaxValue;

                    foreach (ConnectedComponent candidate in grid.EnumObjects(searchArea))
                    {
                        if (candidate.VerticalAlignment == VerticalAlignment.None)
                        {
                            Rectangle cbounds  = candidate.Bounds;
                            int       nearestx = searchForward ? cbounds.Left : cbounds.Right;
                            int       distx    = box.DistanceToX(nearestx);
                            bool      skip     = false;

                            // verify candidate position against baseline
                            if (!skip && bottomline.IsBelow(nearestx, cbounds.Top))
                            {
                                // candidate is below baseline
                                skip = true;
                            }

                            if (!skip && Math.Abs(bottomline.Y(nearestx) - cbounds.Bottom) > 10)
                            {
                                // candidate's bottom is far from baseline
                                skip = true;
                            }

                            if (!skip &&
                                ((cbounds.Height > box.Height / 2 && cbounds.Height > 2 * box.Height) ||
                                 (box.Height > cbounds.Height / 2 && box.Height > 2 * cbounds.Height)))
                            {
                                // box sizes too different
                                skip = true;

                                if (box.ContainsY(cbounds) && distx < box.Height)
                                {
                                    // accept small elements locates inside the box that close to it
                                    // could be some detached c.c. or punctuation
                                    skip = false;
                                }
                            }

                            // find nearest element based on Eucledian distance
                            if (!skip)
                            {
                                int distance = box.DistanceToSquared(cbounds);
                                if (distance < bestDistance)
                                {
                                    bestCandidate = candidate;
                                    bestDistance  = distance;
                                }
                            }
                        }
                    }

                    // update baseline and box
                    if (bestCandidate != null)
                    {
                        Rectangle cbounds = bestCandidate.Bounds;

                        if (cbounds.Height > box.Height / 2)
                        {
                            if (searchForward)
                            {
                                bottomline.X2 = cbounds.Right;
                                bottomline.Y2 = cbounds.Bottom;
                            }
                            else
                            {
                                bottomline.X1 = cbounds.Left;
                                bottomline.Y1 = cbounds.Bottom;
                            }
                        }
                    }

                    return(bestCandidate);
                }
            }
        }