public static LayoutElement NewRow(IEnumerable<LayoutElement> nodes, 
            LayoutElementType type = LayoutElementType.Row)
        {
            RectangleF dummyBounds = new RectangleF(0,0,1,1);
            var e = new LayoutElement(type, dummyBounds, null, nodes);

            // set bounds
            if (!nodes.IsEmpty())
            {
                e.Text = nodes.Select(x => x.Text).Where(x => !String.IsNullOrEmpty(x)).ElementsToStringS(delim: " ");
                e.SetBoundsFromNodes(false);
            }

            return e;
        }
        void DetectRowBounds(PageLayout cbi, Blob[] blobs)
        {
            if (blobs.Length == 0) { return; }
            if (cbi.Bounds == Rectangle.Empty) { return; }

            List<LayoutElement> rows = new List<LayoutElement>();

            LayoutElement currentRow = null;
            // Attempt drawing lines between the rows.
            for (int y = cbi.Bounds.Top; y < cbi.Bounds.Bottom; y++)
            {
                Rectangle rowRect = new Rectangle(cbi.Bounds.Left, y, cbi.Bounds.Width, 1);

                var blobsInRow = blobs.Where(b => b.Rectangle.IntersectsWith(rowRect));

                if (blobsInRow.FirstOrDefault() == null)
                {
                    // Empty row detected. Commit current row (if any)
                    TryAddRow(rows, currentRow);
                    currentRow = null;
                }
                else
                {
                    // Start new row if needed
                    if (currentRow == null)
                    {
                        currentRow = new LayoutElement();
                        currentRow.Type = LayoutElementType.Row;
                    }
                    currentRow.Children.AddRange(blobsInRow.Select(x => LayoutElement.NewWord(cbi.PageSize, x.Rectangle)));

                    // Advance to test the next empty space
                    // TODO: beware of off-by-1
                    //y = currentRow.Bounds.Bottom - 1;
                }
            }

            // Add row at the end
            TryAddRow(rows, currentRow);

            FindAndRemoveHeaderAndFooter(cbi, rows);

            cbi.Children = rows;
            cbi.SetBoundsFromNodes(true);
        }
        void TryAddRow(List<LayoutElement> rows, LayoutElement currentRow)
        {
            if (currentRow == null) { return; }

            currentRow.Children = currentRow.Children.Distinct().ToList();
            currentRow.SetBoundsFromNodes(false);
            rows.Add(currentRow);
        }
 public static LayoutElement NewWord(Size pageSize, Rectangle bounds, String text = null)
 {
     RectangleF unitBounds = GetUnitBounds(pageSize, bounds);
     var e = new LayoutElement(LayoutElementType.Word, unitBounds, text);
     return e;
 }
 bool StartsNewColumn(LayoutElement a, LayoutElement b)
 {
     return b.UnitBounds.Bottom <= a.UnitBounds.Top &&
         b.UnitBounds.Left > a.UnitBounds.Right;
 }
        bool StartsNewRow(LayoutElement prev, LayoutElement cur)
        {
            // Normal case: next row
            // QQ: do rows ever overlap?
            if (prev.UnitBounds.Bottom <= cur.UnitBounds.Top) { return true; }
            
            // Special case: next column
            if (prev.UnitBounds.Top >= cur.UnitBounds.Bottom) { return true; }

            return false;
        }