ASCU_ALL/PrintPDF/MigraDoc.DocumentObjectModel/DocumentObjectModel.Visitors/MergedCellList.cs

358 lines
15 KiB
C#
Raw Normal View History

2020-09-04 12:49:15 +05:00
#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
{
/// <summary>
/// Represents a merged list of cells of a table.
/// </summary>
public class MergedCellList : List<Cell>
{
/// <summary>
/// Enumeration of neighbor positions of cells in a table.
/// </summary>
enum NeighborPosition
{
Top,
Left,
Right,
Bottom
}
/// <summary>
/// Initializes a new instance of the MergedCellList class.
/// </summary>
public MergedCellList(Table table)
{
Init(table);
}
/// <summary>
/// Initializes this instance from a table.
/// </summary>
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);
}
}
}
/// <summary>
/// Returns whether the given cell is already covered by a preceding cell in this instance.
/// </summary>
/// <remarks>
/// Help function for Init().
/// </remarks>
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;
}
/// <summary>
/// Gets the cell at the specified position.
/// </summary>
public new Cell this[int index]
{
get { return base[index]; }
}
/// <summary>
/// Gets a borders object that should be used for rendering.
/// </summary>
/// <exception cref="System.ArgumentException">
/// Thrown when the cell is not in this list.
/// This situation occurs if the given cell is merged "away" by a previous one.
/// </exception>
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;
}
/// <summary>
/// Gets the cell that covers the given cell by merging. Usually the cell itself if not merged.
/// </summary>
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;
}
/// <summary>
/// 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
/// </summary>
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;
}
/// <summary>
/// Returns the width of the border at the specified position.
/// </summary>
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;
}
/// <summary>
/// Gets the specified cell's uppermost neighbor at the specified position.
/// </summary>
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;
}
/// <summary>
/// Returns whether cell2 is a neighbor of cell1 at the specified position.
/// </summary>
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;
}
}
}