#region MigraDoc - Creating Documents on the Fly // // Authors: // Stefan Lange // Klaus Potzesny // David Stephensen // // Copyright (c) 2001-2017 empira Software GmbH, Cologne Area (Germany) // // http://www.pdfsharp.com // http://www.migradoc.com // http://sourceforge.net/projects/pdfsharp // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. #endregion using System; using System.Collections.Generic; using MigraDoc.DocumentObjectModel.Tables; using MigraDoc.DocumentObjectModel.publics; namespace MigraDoc.DocumentObjectModel.Visitors { /// /// Represents a merged list of cells of a table. /// public class MergedCellList : List { /// /// Enumeration of neighbor positions of cells in a table. /// enum NeighborPosition { Top, Left, Right, Bottom } /// /// Initializes a new instance of the MergedCellList class. /// public MergedCellList(Table table) { Init(table); } /// /// Initializes this instance from a table. /// private void Init(Table table) { for (int rwIdx = 0; rwIdx < table.Rows.Count; ++rwIdx) { for (int clmIdx = 0; clmIdx < table.Columns.Count; ++clmIdx) { Cell cell = table[rwIdx, clmIdx]; if (!IsAlreadyCovered(cell)) Add(cell); } } } /// /// Returns whether the given cell is already covered by a preceding cell in this instance. /// /// /// Help function for Init(). /// private bool IsAlreadyCovered(Cell cell) { for (int index = Count - 1; index >= 0; --index) { Cell currentCell = this[index]; if (currentCell.Column.Index <= cell.Column.Index && currentCell.Column.Index + currentCell.MergeRight >= cell.Column.Index) { if (currentCell.Row.Index <= cell.Row.Index && currentCell.Row.Index + currentCell.MergeDown >= cell.Row.Index) return true; if (currentCell.Row.Index + currentCell.MergeDown == cell.Row.Index - 1) return false; } } return false; } /// /// Gets the cell at the specified position. /// public new Cell this[int index] { get { return base[index]; } } /// /// Gets a borders object that should be used for rendering. /// /// /// Thrown when the cell is not in this list. /// This situation occurs if the given cell is merged "away" by a previous one. /// public Borders GetEffectiveBorders(Cell cell) { //Borders borders = cell.GetValue("Borders", GV.ReadOnly) as Borders; Borders borders = cell._borders; if (borders != null) { //Document doc = borders.Document; borders = borders.Clone(); borders._parent = cell; //doc = borders.Document; } else borders = new Borders(cell._parent); int cellIdx = BinarySearch(cell, new CellComparer()); if (!(cellIdx >= 0 && cellIdx < Count)) throw new ArgumentException("cell is not a relevant cell", "cell"); if (cell._mergeRight > 0) { Cell rightBorderCell = cell.Table[cell.Row.Index, cell.Column.Index + cell._mergeRight]; if (rightBorderCell._borders != null && rightBorderCell._borders._right != null) borders.Right = rightBorderCell._borders._right.Clone(); else borders._right = null; } if (cell._mergeDown > 0) { Cell bottomBorderCell = cell.Table[cell.Row.Index + cell._mergeDown, cell.Column.Index]; if (bottomBorderCell._borders != null && bottomBorderCell._borders._bottom != null) borders.Bottom = bottomBorderCell._borders._bottom.Clone(); else borders._bottom = null; } // For BorderTypes Top, Right, Bottom and Left update the width with the neighbours touching border where required. // In case of rounded corners this should not be done. Cell leftNeighbor = GetNeighbor(cellIdx, NeighborPosition.Left); if (leftNeighbor != null && leftNeighbor.RoundedCorner != RoundedCorner.TopRight && leftNeighbor.RoundedCorner != RoundedCorner.BottomRight) { Borders nbrBrdrs = leftNeighbor.GetValue("Borders", GV.ReadWrite) as Borders; if (nbrBrdrs != null && GetEffectiveBorderWidth(nbrBrdrs, BorderType.Right) >= GetEffectiveBorderWidth(borders, BorderType.Left)) borders.SetValue("Left", GetBorderFromBorders(nbrBrdrs, BorderType.Right)); } Cell rightNeighbor = GetNeighbor(cellIdx, NeighborPosition.Right); if (rightNeighbor != null && rightNeighbor.RoundedCorner != RoundedCorner.TopLeft && rightNeighbor.RoundedCorner != RoundedCorner.BottomLeft) { Borders nbrBrdrs = rightNeighbor.GetValue("Borders", GV.ReadWrite) as Borders; if (nbrBrdrs != null && GetEffectiveBorderWidth(nbrBrdrs, BorderType.Left) > GetEffectiveBorderWidth(borders, BorderType.Right)) borders.SetValue("Right", GetBorderFromBorders(nbrBrdrs, BorderType.Left)); } Cell topNeighbor = GetNeighbor(cellIdx, NeighborPosition.Top); if (topNeighbor != null && topNeighbor.RoundedCorner != RoundedCorner.BottomLeft && topNeighbor.RoundedCorner != RoundedCorner.BottomRight) { Borders nbrBrdrs = topNeighbor.GetValue("Borders", GV.ReadWrite) as Borders; if (nbrBrdrs != null && GetEffectiveBorderWidth(nbrBrdrs, BorderType.Bottom) >= GetEffectiveBorderWidth(borders, BorderType.Top)) borders.SetValue("Top", GetBorderFromBorders(nbrBrdrs, BorderType.Bottom)); } Cell bottomNeighbor = GetNeighbor(cellIdx, NeighborPosition.Bottom); if (bottomNeighbor != null && bottomNeighbor.RoundedCorner != RoundedCorner.TopLeft && bottomNeighbor.RoundedCorner != RoundedCorner.TopRight) { Borders nbrBrdrs = bottomNeighbor.GetValue("Borders", GV.ReadWrite) as Borders; if (nbrBrdrs != null && GetEffectiveBorderWidth(nbrBrdrs, BorderType.Top) > GetEffectiveBorderWidth(borders, BorderType.Bottom)) borders.SetValue("Bottom", GetBorderFromBorders(nbrBrdrs, BorderType.Top)); } return borders; } /// /// Gets the cell that covers the given cell by merging. Usually the cell itself if not merged. /// public Cell GetCoveringCell(Cell cell) { int cellIdx = BinarySearch(cell, new CellComparer()); if (cellIdx >= 0 && cellIdx < Count) return this[cellIdx]; // Binary Search returns the complement of the next value, therefore, "~cellIdx - 1" is the previous cell. cellIdx = ~cellIdx - 1; for (int index = cellIdx; index >= 0; --index) { Cell currCell = this[index]; if (currCell.Column.Index <= cell.Column.Index && currCell.Column.Index + currCell.MergeRight >= cell.Column.Index && currCell.Row.Index <= cell.Row.Index && currCell.Row.Index + currCell.MergeDown >= cell.Row.Index) return currCell; } return null; } /// /// Returns the border of the given borders-object of the specified type (top, bottom, ...). /// If that border doesn't exist, it returns a new border object that inherits all properties from the given borders object /// private static Border GetBorderFromBorders(Borders borders, BorderType type) { Border returnBorder = borders.GetBorderReadOnly(type); if (returnBorder == null) { returnBorder = new Border(); returnBorder._style = borders._style; returnBorder._width = borders._width; returnBorder._color = borders._color; returnBorder._visible = borders._visible; } return returnBorder; } /// /// Returns the width of the border at the specified position. /// private static Unit GetEffectiveBorderWidth(Borders borders, BorderType type) { if (borders == null) return 0; Border border = borders.GetBorderReadOnly(type); DocumentObject relevantDocObj = border; if (relevantDocObj == null || relevantDocObj.IsNull("Width")) relevantDocObj = borders; // Avoid unnecessary GetValue calls. object visible = relevantDocObj.GetValue("visible", GV.GetNull); if (visible != null && !(bool)visible) return 0; object width = relevantDocObj.GetValue("width", GV.GetNull); if (width != null) return (Unit)width; object color = relevantDocObj.GetValue("color", GV.GetNull); if (color != null) return 0.5; object style = relevantDocObj.GetValue("style", GV.GetNull); if (style != null) return 0.5; return 0; } /// /// Gets the specified cell's uppermost neighbor at the specified position. /// private Cell GetNeighbor(int cellIdx, NeighborPosition position) { Cell cell = this[cellIdx]; if (cell.Column.Index == 0 && position == NeighborPosition.Left || cell.Row.Index == 0 && position == NeighborPosition.Top || cell.Row.Index + cell.MergeDown == cell.Table.Rows.Count - 1 && position == NeighborPosition.Bottom || cell.Column.Index + cell.MergeRight == cell.Table.Columns.Count - 1 && position == NeighborPosition.Right) return null; switch (position) { case NeighborPosition.Top: case NeighborPosition.Left: for (int index = cellIdx - 1; index >= 0; --index) { Cell currCell = this[index]; if (IsNeighbor(cell, currCell, position)) return currCell; } break; case NeighborPosition.Right: if (cellIdx + 1 < Count) { Cell cell2 = this[cellIdx + 1]; if (cell2.Row.Index == cell.Row.Index) return cell2; } for (int index = cellIdx - 1; index >= 0; --index) { Cell currCell = this[index]; if (IsNeighbor(cell, currCell, position)) return currCell; } break; case NeighborPosition.Bottom: for (int index = cellIdx + 1; index < Count; ++index) { Cell currCell = this[index]; if (IsNeighbor(cell, currCell, position)) return currCell; } break; } return null; } /// /// Returns whether cell2 is a neighbor of cell1 at the specified position. /// private bool IsNeighbor(Cell cell1, Cell cell2, NeighborPosition position) { bool isNeighbor = false; switch (position) { case NeighborPosition.Bottom: int bottomRowIdx = cell1.Row.Index + cell1.MergeDown + 1; isNeighbor = cell2.Row.Index == bottomRowIdx && cell2.Column.Index <= cell1.Column.Index && cell2.Column.Index + cell2.MergeRight >= cell1.Column.Index; break; case NeighborPosition.Left: int leftClmIdx = cell1.Column.Index - 1; isNeighbor = cell2.Row.Index <= cell1.Row.Index && cell2.Row.Index + cell2.MergeDown >= cell1.Row.Index && cell2.Column.Index + cell2.MergeRight == leftClmIdx; break; case NeighborPosition.Right: int rightClmIdx = cell1.Column.Index + cell1.MergeRight + 1; isNeighbor = cell2.Row.Index <= cell1.Row.Index && cell2.Row.Index + cell2.MergeDown >= cell1.Row.Index && cell2.Column.Index == rightClmIdx; break; case NeighborPosition.Top: int topRowIdx = cell1.Row.Index - 1; isNeighbor = cell2.Row.Index + cell2.MergeDown == topRowIdx && cell2.Column.Index + cell2.MergeRight >= cell1.Column.Index && cell2.Column.Index <= cell1.Column.Index; break; } return isNeighbor; } } }