821 lines
33 KiB
C#
821 lines
33 KiB
C#
|
#region MigraDoc - Creating Documents on the Fly
|
||
|
|
||
|
//
|
||
|
// Authors:
|
||
|
// Klaus Potzesny
|
||
|
//
|
||
|
// 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.publics;
|
||
|
using PdfSharp.Drawing;
|
||
|
using MigraDoc.DocumentObjectModel;
|
||
|
using MigraDoc.DocumentObjectModel.Visitors;
|
||
|
using MigraDoc.DocumentObjectModel.Tables;
|
||
|
|
||
|
namespace MigraDoc.Rendering
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Renders a table to an XGraphics object.
|
||
|
/// </summary>
|
||
|
public class TableRenderer : Renderer
|
||
|
{
|
||
|
public TableRenderer(XGraphics gfx, Table documentObject, FieldInfos fieldInfos)
|
||
|
: base(gfx, documentObject, fieldInfos)
|
||
|
{
|
||
|
_table = documentObject;
|
||
|
}
|
||
|
|
||
|
public TableRenderer(XGraphics gfx, RenderInfo renderInfo, FieldInfos fieldInfos)
|
||
|
: base(gfx, renderInfo, fieldInfos)
|
||
|
{
|
||
|
_table = (Table)_renderInfo.DocumentObject;
|
||
|
}
|
||
|
|
||
|
public override LayoutInfo InitialLayoutInfo
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
LayoutInfo layoutInfo = new LayoutInfo();
|
||
|
layoutInfo.KeepTogether = _table.KeepTogether;
|
||
|
layoutInfo.KeepWithNext = false;
|
||
|
layoutInfo.MarginBottom = 0;
|
||
|
layoutInfo.MarginLeft = 0;
|
||
|
layoutInfo.MarginTop = 0;
|
||
|
layoutInfo.MarginRight = 0;
|
||
|
return layoutInfo;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void InitRendering()
|
||
|
{
|
||
|
TableFormatInfo formatInfo = (TableFormatInfo)_renderInfo.FormatInfo;
|
||
|
_bottomBorderMap = formatInfo.BottomBorderMap;
|
||
|
_connectedRowsMap = formatInfo.ConnectedRowsMap;
|
||
|
_formattedCells = formatInfo.FormattedCells;
|
||
|
|
||
|
_currRow = formatInfo.StartRow;
|
||
|
_startRow = formatInfo.StartRow;
|
||
|
_endRow = formatInfo.EndRow;
|
||
|
|
||
|
_mergedCells = formatInfo.MergedCells;
|
||
|
_lastHeaderRow = formatInfo.LastHeaderRow;
|
||
|
_startX = _renderInfo.LayoutInfo.ContentArea.X;
|
||
|
_startY = _renderInfo.LayoutInfo.ContentArea.Y;
|
||
|
}
|
||
|
|
||
|
private void RenderHeaderRows()
|
||
|
{
|
||
|
if (_lastHeaderRow < 0)
|
||
|
return;
|
||
|
|
||
|
foreach (Cell cell in _mergedCells)
|
||
|
{
|
||
|
if (cell.Row.Index <= _lastHeaderRow)
|
||
|
RenderCell(cell);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void RenderCell(Cell cell)
|
||
|
{
|
||
|
Rectangle innerRect = GetInnerRect(CalcStartingHeight(), cell);
|
||
|
RenderShading(cell, innerRect);
|
||
|
RenderContent(cell, innerRect);
|
||
|
RenderBorders(cell, innerRect);
|
||
|
}
|
||
|
|
||
|
private void EqualizeRoundedCornerBorders(Cell cell)
|
||
|
{
|
||
|
// If any of a corner relevant border is set, we want to copy its values to the second corner relevant border,
|
||
|
// to ensure the innerWidth of the cell is the same, regardless of which border is used.
|
||
|
// If set, we use the vertical borders as source for the values, otherwise we use the horizontal borders.
|
||
|
RoundedCorner roundedCorner = cell.RoundedCorner;
|
||
|
|
||
|
if (roundedCorner == RoundedCorner.None)
|
||
|
return;
|
||
|
|
||
|
BorderType primaryBorderType = BorderType.Top, secondaryBorderType = BorderType.Top;
|
||
|
|
||
|
if (roundedCorner == RoundedCorner.TopLeft || roundedCorner == RoundedCorner.BottomLeft)
|
||
|
primaryBorderType = BorderType.Left;
|
||
|
if (roundedCorner == RoundedCorner.TopRight || roundedCorner == RoundedCorner.BottomRight)
|
||
|
primaryBorderType = BorderType.Right;
|
||
|
|
||
|
if (roundedCorner == RoundedCorner.TopLeft || roundedCorner == RoundedCorner.TopRight)
|
||
|
secondaryBorderType = BorderType.Top;
|
||
|
if (roundedCorner == RoundedCorner.BottomLeft || roundedCorner == RoundedCorner.BottomRight)
|
||
|
secondaryBorderType = BorderType.Bottom;
|
||
|
|
||
|
// If both borders don't exist, there's nothing to do and we should not create one by accessing it.
|
||
|
if (!cell.Borders.HasBorder(primaryBorderType) && !cell.Borders.HasBorder(secondaryBorderType))
|
||
|
return;
|
||
|
|
||
|
// Get the borders. By using GV.ReadWrite we create the border, if not existing.
|
||
|
Border primaryBorder = (Border)cell.Borders.GetValue(primaryBorderType.ToString(), GV.ReadWrite);
|
||
|
Border secondaryBorder = (Border)cell.Borders.GetValue(secondaryBorderType.ToString(), GV.ReadWrite);
|
||
|
|
||
|
Border source = primaryBorder.Visible ? primaryBorder
|
||
|
: secondaryBorder.Visible ? secondaryBorder : null;
|
||
|
Border target = primaryBorder.Visible ? secondaryBorder
|
||
|
: secondaryBorder.Visible ? primaryBorder : null;
|
||
|
|
||
|
if (source == null || target == null)
|
||
|
return;
|
||
|
|
||
|
target.Visible = source.Visible;
|
||
|
target.Width = source.Width;
|
||
|
target.Style = source.Style;
|
||
|
target.Color = source.Color;
|
||
|
}
|
||
|
|
||
|
private void RenderShading(Cell cell, Rectangle innerRect)
|
||
|
{
|
||
|
ShadingRenderer shadeRenderer = new ShadingRenderer(_gfx, cell.Shading);
|
||
|
shadeRenderer.Render(innerRect.X, innerRect.Y, innerRect.Width, innerRect.Height, cell.RoundedCorner);
|
||
|
}
|
||
|
|
||
|
private void RenderBorders(Cell cell, Rectangle innerRect)
|
||
|
{
|
||
|
XUnit leftPos = innerRect.X;
|
||
|
XUnit rightPos = leftPos + innerRect.Width;
|
||
|
XUnit topPos = innerRect.Y;
|
||
|
XUnit bottomPos = innerRect.Y + innerRect.Height;
|
||
|
Borders mergedBorders = _mergedCells.GetEffectiveBorders(cell);
|
||
|
|
||
|
BordersRenderer bordersRenderer = new BordersRenderer(mergedBorders, _gfx);
|
||
|
XUnit bottomWidth = bordersRenderer.GetWidth(BorderType.Bottom);
|
||
|
XUnit leftWidth = bordersRenderer.GetWidth(BorderType.Left);
|
||
|
XUnit topWidth = bordersRenderer.GetWidth(BorderType.Top);
|
||
|
XUnit rightWidth = bordersRenderer.GetWidth(BorderType.Right);
|
||
|
|
||
|
if (cell.RoundedCorner == RoundedCorner.TopLeft)
|
||
|
bordersRenderer.RenderRounded(cell.RoundedCorner, innerRect.X, innerRect.Y, innerRect.Width + rightWidth, innerRect.Height + bottomWidth);
|
||
|
else if (cell.RoundedCorner == RoundedCorner.TopRight)
|
||
|
bordersRenderer.RenderRounded(cell.RoundedCorner, innerRect.X - leftWidth, innerRect.Y, innerRect.Width + leftWidth, innerRect.Height + bottomWidth);
|
||
|
else if (cell.RoundedCorner == RoundedCorner.BottomLeft)
|
||
|
bordersRenderer.RenderRounded(cell.RoundedCorner, innerRect.X, innerRect.Y - topWidth, innerRect.Width + rightWidth, innerRect.Height + topWidth);
|
||
|
else if (cell.RoundedCorner == RoundedCorner.BottomRight)
|
||
|
bordersRenderer.RenderRounded(cell.RoundedCorner, innerRect.X - leftWidth, innerRect.Y - topWidth, innerRect.Width + leftWidth, innerRect.Height + topWidth);
|
||
|
|
||
|
// Render horizontal and vertical borders only if touching no rounded corner.
|
||
|
if (cell.RoundedCorner != RoundedCorner.TopRight && cell.RoundedCorner != RoundedCorner.BottomRight)
|
||
|
bordersRenderer.RenderVertically(BorderType.Right, rightPos, topPos, bottomPos + bottomWidth - topPos);
|
||
|
|
||
|
if (cell.RoundedCorner != RoundedCorner.TopLeft && cell.RoundedCorner != RoundedCorner.BottomLeft)
|
||
|
bordersRenderer.RenderVertically(BorderType.Left, leftPos - leftWidth, topPos, bottomPos + bottomWidth - topPos);
|
||
|
|
||
|
if (cell.RoundedCorner != RoundedCorner.BottomLeft && cell.RoundedCorner != RoundedCorner.BottomRight)
|
||
|
bordersRenderer.RenderHorizontally(BorderType.Bottom, leftPos - leftWidth, bottomPos, rightPos + rightWidth + leftWidth - leftPos);
|
||
|
|
||
|
if (cell.RoundedCorner != RoundedCorner.TopLeft && cell.RoundedCorner != RoundedCorner.TopRight)
|
||
|
bordersRenderer.RenderHorizontally(BorderType.Top, leftPos - leftWidth, topPos - topWidth, rightPos + rightWidth + leftWidth - leftPos);
|
||
|
|
||
|
RenderDiagonalBorders(mergedBorders, innerRect);
|
||
|
}
|
||
|
|
||
|
private void RenderDiagonalBorders(Borders mergedBorders, Rectangle innerRect)
|
||
|
{
|
||
|
BordersRenderer bordersRenderer = new BordersRenderer(mergedBorders, _gfx);
|
||
|
bordersRenderer.RenderDiagonally(BorderType.DiagonalDown, innerRect.X, innerRect.Y, innerRect.Width, innerRect.Height);
|
||
|
bordersRenderer.RenderDiagonally(BorderType.DiagonalUp, innerRect.X, innerRect.Y, innerRect.Width, innerRect.Height);
|
||
|
}
|
||
|
|
||
|
private void RenderContent(Cell cell, Rectangle innerRect)
|
||
|
{
|
||
|
FormattedCell formattedCell = _formattedCells[cell];
|
||
|
RenderInfo[] renderInfos = formattedCell.GetRenderInfos();
|
||
|
|
||
|
if (renderInfos == null)
|
||
|
return;
|
||
|
|
||
|
VerticalAlignment verticalAlignment = cell.VerticalAlignment;
|
||
|
XUnit contentHeight = formattedCell.ContentHeight;
|
||
|
XUnit innerHeight = innerRect.Height;
|
||
|
XUnit targetX = innerRect.X + cell.Column.LeftPadding;
|
||
|
|
||
|
XUnit targetY;
|
||
|
if (verticalAlignment == VerticalAlignment.Bottom)
|
||
|
{
|
||
|
targetY = innerRect.Y + innerRect.Height;
|
||
|
targetY -= cell.Row.BottomPadding;
|
||
|
targetY -= contentHeight;
|
||
|
}
|
||
|
else if (verticalAlignment == VerticalAlignment.Center)
|
||
|
{
|
||
|
targetY = innerRect.Y + cell.Row.TopPadding;
|
||
|
targetY += innerRect.Y + innerRect.Height - cell.Row.BottomPadding;
|
||
|
targetY -= contentHeight;
|
||
|
targetY /= 2;
|
||
|
}
|
||
|
else
|
||
|
targetY = innerRect.Y + cell.Row.TopPadding;
|
||
|
|
||
|
RenderByInfos(targetX, targetY, renderInfos);
|
||
|
}
|
||
|
|
||
|
private Rectangle GetInnerRect(XUnit startingHeight, Cell cell)
|
||
|
{
|
||
|
BordersRenderer bordersRenderer = new BordersRenderer(_mergedCells.GetEffectiveBorders(cell), _gfx);
|
||
|
FormattedCell formattedCell = _formattedCells[cell];
|
||
|
XUnit width = formattedCell.InnerWidth;
|
||
|
|
||
|
XUnit y = _startY;
|
||
|
if (cell.Row.Index > _lastHeaderRow)
|
||
|
y += startingHeight;
|
||
|
else
|
||
|
y += CalcMaxTopBorderWidth(0);
|
||
|
|
||
|
#if true
|
||
|
// !!!new 18-03-09 Attempt to fix an exception. begin
|
||
|
XUnit upperBorderPos;
|
||
|
if (!_bottomBorderMap.TryGetValue(cell.Row.Index, out upperBorderPos))
|
||
|
{
|
||
|
//GetType();
|
||
|
}
|
||
|
// !!!new 18-03-09 Attempt to fix an exception. end
|
||
|
#else
|
||
|
XUnit upperBorderPos = _bottomBorderMap[cell.Row.Index];
|
||
|
#endif
|
||
|
|
||
|
y += upperBorderPos;
|
||
|
if (cell.Row.Index > _lastHeaderRow)
|
||
|
y -= _bottomBorderMap[_startRow];
|
||
|
|
||
|
#if true
|
||
|
// !!!new 18-03-09 Attempt to fix an exception. begin
|
||
|
XUnit lowerBorderPos;
|
||
|
if (!_bottomBorderMap.TryGetValue(cell.Row.Index + cell.MergeDown + 1, out lowerBorderPos))
|
||
|
{
|
||
|
//GetType();
|
||
|
}
|
||
|
// !!!new 18-03-09 Attempt to fix an exception. end
|
||
|
#else
|
||
|
XUnit lowerBorderPos = _bottomBorderMap[cell.Row.Index + cell.MergeDown + 1];
|
||
|
#endif
|
||
|
|
||
|
XUnit height = lowerBorderPos - upperBorderPos;
|
||
|
height -= bordersRenderer.GetWidth(BorderType.Bottom);
|
||
|
|
||
|
XUnit x = _startX;
|
||
|
for (int clmIdx = 0; clmIdx < cell.Column.Index; ++clmIdx)
|
||
|
{
|
||
|
x += _table.Columns[clmIdx].Width;
|
||
|
}
|
||
|
x += LeftBorderOffset;
|
||
|
|
||
|
return new Rectangle(x, y, width, height);
|
||
|
}
|
||
|
|
||
|
public override void Render()
|
||
|
{
|
||
|
InitRendering();
|
||
|
RenderHeaderRows();
|
||
|
if (_startRow < _table.Rows.Count)
|
||
|
{
|
||
|
Cell cell = _table[_startRow, 0];
|
||
|
|
||
|
int cellIdx = _mergedCells.BinarySearch(_table[_startRow, 0], new CellComparer());
|
||
|
while (cellIdx < _mergedCells.Count)
|
||
|
{
|
||
|
cell = _mergedCells[cellIdx];
|
||
|
if (cell.Row.Index > _endRow)
|
||
|
break;
|
||
|
|
||
|
RenderCell(cell);
|
||
|
++cellIdx;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void InitFormat(Area area, FormatInfo previousFormatInfo)
|
||
|
{
|
||
|
TableFormatInfo prevTableFormatInfo = (TableFormatInfo)previousFormatInfo;
|
||
|
TableRenderInfo tblRenderInfo = new TableRenderInfo();
|
||
|
tblRenderInfo.DocumentObject = _table;
|
||
|
|
||
|
// Equalize the two borders, that are used to determine a rounded corner's border.
|
||
|
// This way the innerWidth of the cell, which is got by the saved _formattedCells, is the same regardless of which corner relevant border is set.
|
||
|
foreach (Row row in _table.Rows)
|
||
|
foreach (Cell cell in row.Cells)
|
||
|
EqualizeRoundedCornerBorders(cell);
|
||
|
|
||
|
_renderInfo = tblRenderInfo;
|
||
|
|
||
|
if (prevTableFormatInfo != null)
|
||
|
{
|
||
|
_mergedCells = prevTableFormatInfo.MergedCells;
|
||
|
_formattedCells = prevTableFormatInfo.FormattedCells;
|
||
|
_bottomBorderMap = prevTableFormatInfo.BottomBorderMap;
|
||
|
_lastHeaderRow = prevTableFormatInfo.LastHeaderRow;
|
||
|
_connectedRowsMap = prevTableFormatInfo.ConnectedRowsMap;
|
||
|
_startRow = prevTableFormatInfo.EndRow + 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_mergedCells = new MergedCellList(_table);
|
||
|
FormatCells();
|
||
|
CalcLastHeaderRow();
|
||
|
CreateConnectedRows();
|
||
|
CreateBottomBorderMap();
|
||
|
if (_doHorizontalBreak)
|
||
|
{
|
||
|
CalcLastHeaderColumn();
|
||
|
CreateConnectedColumns();
|
||
|
}
|
||
|
_startRow = _lastHeaderRow + 1;
|
||
|
}
|
||
|
((TableFormatInfo)tblRenderInfo.FormatInfo).MergedCells = _mergedCells;
|
||
|
((TableFormatInfo)tblRenderInfo.FormatInfo).FormattedCells = _formattedCells;
|
||
|
((TableFormatInfo)tblRenderInfo.FormatInfo).BottomBorderMap = _bottomBorderMap;
|
||
|
((TableFormatInfo)tblRenderInfo.FormatInfo).ConnectedRowsMap = _connectedRowsMap;
|
||
|
((TableFormatInfo)tblRenderInfo.FormatInfo).LastHeaderRow = _lastHeaderRow;
|
||
|
}
|
||
|
|
||
|
private void FormatCells()
|
||
|
{
|
||
|
_formattedCells = new Dictionary<Cell, FormattedCell>(); //new Sorted_List(new CellComparer());
|
||
|
foreach (Cell cell in _mergedCells)
|
||
|
{
|
||
|
FormattedCell formattedCell = new FormattedCell(cell, _documentRenderer, _mergedCells.GetEffectiveBorders(cell),
|
||
|
_fieldInfos, 0, 0);
|
||
|
formattedCell.Format(_gfx);
|
||
|
_formattedCells.Add(cell, formattedCell);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Formats (measures) the table.
|
||
|
/// </summary>
|
||
|
/// <param name="area"> The area on which to fit the table. </param>
|
||
|
/// <param name="previousFormatInfo"> </param>
|
||
|
public override void Format(Area area, FormatInfo previousFormatInfo)
|
||
|
{
|
||
|
DocumentElements elements = DocumentRelations.GetParent(_table) as DocumentElements;
|
||
|
if (elements != null)
|
||
|
{
|
||
|
Section section = DocumentRelations.GetParent(elements) as Section;
|
||
|
if (section != null)
|
||
|
_doHorizontalBreak = section.PageSetup.HorizontalPageBreak;
|
||
|
}
|
||
|
|
||
|
_renderInfo = new TableRenderInfo();
|
||
|
InitFormat(area, previousFormatInfo);
|
||
|
|
||
|
// Don't take any Rows higher then MaxElementHeight
|
||
|
XUnit topHeight = CalcStartingHeight();
|
||
|
XUnit probeHeight = topHeight;
|
||
|
XUnit offset;
|
||
|
if (_startRow > _lastHeaderRow + 1 &&
|
||
|
_startRow < _table.Rows.Count)
|
||
|
offset = _bottomBorderMap[_startRow] - topHeight;
|
||
|
else
|
||
|
offset = -CalcMaxTopBorderWidth(0);
|
||
|
|
||
|
int probeRow = _startRow;
|
||
|
XUnit currentHeight = 0;
|
||
|
XUnit startingHeight = 0;
|
||
|
bool isEmpty = false;
|
||
|
|
||
|
while (probeRow < _table.Rows.Count)
|
||
|
{
|
||
|
bool firstProbe = probeRow == _startRow;
|
||
|
probeRow = _connectedRowsMap[probeRow];
|
||
|
// Don't take any Rows higher then MaxElementHeight
|
||
|
probeHeight = _bottomBorderMap[probeRow + 1] - offset;
|
||
|
// First test whether MaxElementHeight has been set.
|
||
|
if (MaxElementHeight > 0 && firstProbe && probeHeight > MaxElementHeight - Tolerance)
|
||
|
probeHeight = MaxElementHeight - Tolerance;
|
||
|
//if (firstProbe && probeHeight > MaxElementHeight - Tolerance)
|
||
|
// probeHeight = MaxElementHeight - Tolerance;
|
||
|
|
||
|
//The height for the first new row(s) + headerrows:
|
||
|
if (startingHeight == 0)
|
||
|
{
|
||
|
if (probeHeight > area.Height)
|
||
|
{
|
||
|
isEmpty = true;
|
||
|
break;
|
||
|
}
|
||
|
startingHeight = probeHeight;
|
||
|
}
|
||
|
|
||
|
if (probeHeight > area.Height)
|
||
|
break;
|
||
|
|
||
|
else
|
||
|
{
|
||
|
_currRow = probeRow;
|
||
|
currentHeight = probeHeight;
|
||
|
++probeRow;
|
||
|
}
|
||
|
}
|
||
|
if (!isEmpty)
|
||
|
{
|
||
|
TableFormatInfo formatInfo = (TableFormatInfo)_renderInfo.FormatInfo;
|
||
|
formatInfo.StartRow = _startRow;
|
||
|
formatInfo._isEnding = _currRow >= _table.Rows.Count - 1;
|
||
|
formatInfo.EndRow = _currRow;
|
||
|
}
|
||
|
FinishLayoutInfo(area, currentHeight, startingHeight);
|
||
|
}
|
||
|
|
||
|
private void FinishLayoutInfo(Area area, XUnit currentHeight, XUnit startingHeight)
|
||
|
{
|
||
|
LayoutInfo layoutInfo = _renderInfo.LayoutInfo;
|
||
|
layoutInfo.StartingHeight = startingHeight;
|
||
|
//REM: Trailing height would have to be calculated in case tables had a keep with next property.
|
||
|
layoutInfo.TrailingHeight = 0;
|
||
|
if (_currRow >= 0)
|
||
|
{
|
||
|
layoutInfo.ContentArea = new Rectangle(area.X, area.Y, 0, currentHeight);
|
||
|
XUnit width = LeftBorderOffset;
|
||
|
foreach (Column clm in _table.Columns)
|
||
|
{
|
||
|
width += clm.Width;
|
||
|
}
|
||
|
layoutInfo.ContentArea.Width = width;
|
||
|
}
|
||
|
layoutInfo.MinWidth = layoutInfo.ContentArea.Width;
|
||
|
|
||
|
if (!_table.Rows._leftIndent.IsNull)
|
||
|
layoutInfo.Left = _table.Rows.LeftIndent.Point;
|
||
|
|
||
|
else if (_table.Rows.Alignment == RowAlignment.Left)
|
||
|
{
|
||
|
XUnit leftOffset = LeftBorderOffset;
|
||
|
leftOffset += _table.Columns[0].LeftPadding;
|
||
|
layoutInfo.Left = -leftOffset;
|
||
|
}
|
||
|
|
||
|
switch (_table.Rows.Alignment)
|
||
|
{
|
||
|
case RowAlignment.Left:
|
||
|
layoutInfo.HorizontalAlignment = ElementAlignment.Near;
|
||
|
break;
|
||
|
|
||
|
case RowAlignment.Right:
|
||
|
layoutInfo.HorizontalAlignment = ElementAlignment.Far;
|
||
|
break;
|
||
|
|
||
|
case RowAlignment.Center:
|
||
|
layoutInfo.HorizontalAlignment = ElementAlignment.Center;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private XUnit LeftBorderOffset
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (_leftBorderOffset < 0)
|
||
|
{
|
||
|
if (_table.Rows.Count > 0 && _table.Columns.Count > 0)
|
||
|
{
|
||
|
Borders borders = _mergedCells.GetEffectiveBorders(_table[0, 0]);
|
||
|
BordersRenderer bordersRenderer = new BordersRenderer(borders, _gfx);
|
||
|
_leftBorderOffset = bordersRenderer.GetWidth(BorderType.Left);
|
||
|
}
|
||
|
else
|
||
|
_leftBorderOffset = 0;
|
||
|
}
|
||
|
return _leftBorderOffset;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private XUnit _leftBorderOffset = -1;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Calcs either the height of the header rows or the height of the uppermost top border.
|
||
|
/// </summary>
|
||
|
/// <returns> </returns>
|
||
|
private XUnit CalcStartingHeight()
|
||
|
{
|
||
|
XUnit height = 0;
|
||
|
if (_lastHeaderRow >= 0)
|
||
|
{
|
||
|
height = _bottomBorderMap[_lastHeaderRow + 1];
|
||
|
height += CalcMaxTopBorderWidth(0);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (_table.Rows.Count > _startRow)
|
||
|
height = CalcMaxTopBorderWidth(_startRow);
|
||
|
}
|
||
|
|
||
|
return height;
|
||
|
}
|
||
|
|
||
|
|
||
|
private void CalcLastHeaderColumn()
|
||
|
{
|
||
|
_lastHeaderColumn = -1;
|
||
|
foreach (Column clm in _table.Columns)
|
||
|
{
|
||
|
if (clm.HeadingFormat)
|
||
|
_lastHeaderColumn = clm.Index;
|
||
|
else break;
|
||
|
}
|
||
|
if (_lastHeaderColumn >= 0)
|
||
|
_lastHeaderRow = CalcLastConnectedColumn(_lastHeaderColumn);
|
||
|
|
||
|
// Ignore heading format if all the table is heading:
|
||
|
if (_lastHeaderRow == _table.Rows.Count - 1)
|
||
|
_lastHeaderRow = -1;
|
||
|
}
|
||
|
|
||
|
private void CalcLastHeaderRow()
|
||
|
{
|
||
|
_lastHeaderRow = -1;
|
||
|
foreach (Row row in _table.Rows)
|
||
|
{
|
||
|
if (row.HeadingFormat)
|
||
|
_lastHeaderRow = row.Index;
|
||
|
else break;
|
||
|
}
|
||
|
if (_lastHeaderRow >= 0)
|
||
|
_lastHeaderRow = CalcLastConnectedRow(_lastHeaderRow);
|
||
|
|
||
|
// Ignore heading format if all the table is heading:
|
||
|
if (_lastHeaderRow == _table.Rows.Count - 1)
|
||
|
_lastHeaderRow = -1;
|
||
|
}
|
||
|
|
||
|
private void CreateConnectedRows()
|
||
|
{
|
||
|
_connectedRowsMap = new Dictionary<int, int>(); //new Sorted_List();
|
||
|
foreach (Cell cell in _mergedCells)
|
||
|
{
|
||
|
if (!_connectedRowsMap.ContainsKey(cell.Row.Index))
|
||
|
{
|
||
|
int lastConnectedRow = CalcLastConnectedRow(cell.Row.Index);
|
||
|
_connectedRowsMap[cell.Row.Index] = lastConnectedRow;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void CreateConnectedColumns()
|
||
|
{
|
||
|
_connectedColumnsMap = new Dictionary<int, int>(); //new SortedList();
|
||
|
foreach (Cell cell in _mergedCells)
|
||
|
{
|
||
|
if (!_connectedColumnsMap.ContainsKey(cell.Column.Index))
|
||
|
{
|
||
|
int lastConnectedColumn = CalcLastConnectedColumn(cell.Column.Index);
|
||
|
_connectedColumnsMap[cell.Column.Index] = lastConnectedColumn;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void CreateBottomBorderMap()
|
||
|
{
|
||
|
_bottomBorderMap = new Dictionary<int, XUnit>(); //new SortedList();
|
||
|
_bottomBorderMap.Add(0, XUnit.FromPoint(0));
|
||
|
while (!_bottomBorderMap.ContainsKey(_table.Rows.Count))
|
||
|
{
|
||
|
CreateNextBottomBorderPosition();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Calculates the top border width for the first row that is rendered or formatted.
|
||
|
/// </summary>
|
||
|
/// <param name="row"> The row index. </param>
|
||
|
private XUnit CalcMaxTopBorderWidth(int row)
|
||
|
{
|
||
|
XUnit maxWidth = 0;
|
||
|
if (_table.Rows.Count > row)
|
||
|
{
|
||
|
int cellIdx = _mergedCells.BinarySearch(_table[row, 0], new CellComparer());
|
||
|
Cell rowCell = _mergedCells[cellIdx];
|
||
|
while (cellIdx < _mergedCells.Count)
|
||
|
{
|
||
|
rowCell = _mergedCells[cellIdx];
|
||
|
if (rowCell.Row.Index > row)
|
||
|
break;
|
||
|
|
||
|
if (rowCell._borders != null && !rowCell._borders.IsNull())
|
||
|
{
|
||
|
BordersRenderer bordersRenderer = new BordersRenderer(rowCell.Borders, _gfx);
|
||
|
XUnit width = bordersRenderer.GetWidth(BorderType.Top);
|
||
|
if (width > maxWidth)
|
||
|
maxWidth = width;
|
||
|
}
|
||
|
++cellIdx;
|
||
|
}
|
||
|
}
|
||
|
return maxWidth;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates the next bottom border position.
|
||
|
/// </summary>
|
||
|
private void CreateNextBottomBorderPosition()
|
||
|
{
|
||
|
//int lastIdx = _bottomBorderMap.Count - 1;
|
||
|
// SortedList version:
|
||
|
//int lastBorderRow = (int)bottomBorderMap.GetKey(lastIdx);
|
||
|
//XUnit lastPos = (XUnit)bottomBorderMap.GetByIndex(lastIdx);
|
||
|
int lastBorderRow = 0;
|
||
|
foreach (int key in _bottomBorderMap.Keys)
|
||
|
{
|
||
|
if (key > lastBorderRow)
|
||
|
lastBorderRow = key;
|
||
|
}
|
||
|
XUnit lastPos = _bottomBorderMap[lastBorderRow];
|
||
|
|
||
|
Cell minMergedCell = GetMinMergedCell(lastBorderRow);
|
||
|
FormattedCell minMergedFormattedCell = _formattedCells[minMergedCell];
|
||
|
XUnit maxBottomBorderPosition = lastPos + minMergedFormattedCell.InnerHeight;
|
||
|
maxBottomBorderPosition += CalcBottomBorderWidth(minMergedCell);
|
||
|
|
||
|
foreach (Cell cell in _mergedCells)
|
||
|
{
|
||
|
if (cell.Row.Index > minMergedCell.Row.Index + minMergedCell.MergeDown)
|
||
|
break;
|
||
|
|
||
|
if (cell.Row.Index + cell.MergeDown == minMergedCell.Row.Index + minMergedCell.MergeDown)
|
||
|
{
|
||
|
FormattedCell formattedCell = _formattedCells[cell];
|
||
|
// !!!new 18-03-09 Attempt to fix an exception. begin
|
||
|
// if (cell.Row.Index < _bottomBorderMap.Count)
|
||
|
{
|
||
|
// !!!new 18-03-09 Attempt to fix an exception. end
|
||
|
#if true
|
||
|
// !!!new 18-03-09 Attempt to fix an exception. begin
|
||
|
XUnit topBorderPos = maxBottomBorderPosition;
|
||
|
if (!_bottomBorderMap.TryGetValue(cell.Row.Index, out topBorderPos))
|
||
|
{
|
||
|
//GetType();
|
||
|
}
|
||
|
// !!!new 18-03-09 Attempt to fix an exception. end
|
||
|
#else
|
||
|
XUnit topBorderPos = _bottomBorderMap[cell.Row.Index];
|
||
|
#endif
|
||
|
XUnit bottomBorderPos = topBorderPos + formattedCell.InnerHeight;
|
||
|
bottomBorderPos += CalcBottomBorderWidth(cell);
|
||
|
if (bottomBorderPos > maxBottomBorderPosition)
|
||
|
maxBottomBorderPosition = bottomBorderPos;
|
||
|
// !!!new 18-03-09 Attempt to fix an exception. begin
|
||
|
}
|
||
|
// !!!new 18-03-09 Attempt to fix an exception. end
|
||
|
}
|
||
|
}
|
||
|
_bottomBorderMap.Add(minMergedCell.Row.Index + minMergedCell.MergeDown + 1, maxBottomBorderPosition);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Calculates bottom border width of a cell.
|
||
|
/// </summary>
|
||
|
/// <param name="cell"> The cell the bottom border of the row that is probed. </param>
|
||
|
/// <returns> The calculated border width. </returns>
|
||
|
private XUnit CalcBottomBorderWidth(Cell cell)
|
||
|
{
|
||
|
Borders borders = _mergedCells.GetEffectiveBorders(cell);
|
||
|
if (borders != null)
|
||
|
{
|
||
|
BordersRenderer bordersRenderer = new BordersRenderer(borders, _gfx);
|
||
|
return bordersRenderer.GetWidth(BorderType.Bottom);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the first cell that ends in the given row or as close as possible.
|
||
|
/// </summary>
|
||
|
/// <param name="row">The row to probe.</param>
|
||
|
/// <returns>The first cell with minimal vertical merge.</returns>
|
||
|
private Cell GetMinMergedCell(int row)
|
||
|
{
|
||
|
#if true
|
||
|
//!!!new 18-03-10 begin
|
||
|
// Also look at rows above "row", but only consider cells that end at "row" or as close as possible.
|
||
|
int minMerge = _table.Rows.Count;
|
||
|
Cell minCell = null;
|
||
|
foreach (Cell cell in _mergedCells)
|
||
|
{
|
||
|
if (cell.Row.Index <= row && cell.Row.Index + cell.MergeDown >= row)
|
||
|
{
|
||
|
if (cell.Row.Index == row && cell.MergeDown == 0)
|
||
|
{
|
||
|
// Perfect match: non-merged cell in the desired row.
|
||
|
minCell = cell;
|
||
|
break;
|
||
|
}
|
||
|
else if (cell.Row.Index + cell.MergeDown - row < minMerge)
|
||
|
{
|
||
|
minMerge = cell.Row.Index + cell.MergeDown - row;
|
||
|
minCell = cell;
|
||
|
}
|
||
|
}
|
||
|
else if (cell.Row.Index > row)
|
||
|
break;
|
||
|
}
|
||
|
//!!!new 18-03-10 end
|
||
|
#else
|
||
|
int minMerge = _table.Rows.Count;
|
||
|
Cell minCell = null;
|
||
|
foreach (Cell cell in _mergedCells)
|
||
|
{
|
||
|
if (cell.Row.Index == row)
|
||
|
{
|
||
|
if (cell.MergeDown == 0)
|
||
|
{
|
||
|
minCell = cell;
|
||
|
break;
|
||
|
}
|
||
|
else if (cell.MergeDown < minMerge)
|
||
|
{
|
||
|
minMerge = cell.MergeDown;
|
||
|
minCell = cell;
|
||
|
}
|
||
|
}
|
||
|
else if (cell.Row.Index > row)
|
||
|
break;
|
||
|
}
|
||
|
#endif
|
||
|
return minCell;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Calculates the last row that is connected with the given row.
|
||
|
/// </summary>
|
||
|
/// <param name="row"> The row that is probed for downward connection. </param>
|
||
|
/// <returns> The last row that is connected with the given row. </returns>
|
||
|
private int CalcLastConnectedRow(int row)
|
||
|
{
|
||
|
int lastConnectedRow = row;
|
||
|
foreach (Cell cell in _mergedCells)
|
||
|
{
|
||
|
if (cell.Row.Index <= lastConnectedRow)
|
||
|
{
|
||
|
int downConnection = Math.Max(cell.Row.KeepWith, cell.MergeDown);
|
||
|
if (lastConnectedRow < cell.Row.Index + downConnection)
|
||
|
lastConnectedRow = cell.Row.Index + downConnection;
|
||
|
}
|
||
|
}
|
||
|
return lastConnectedRow;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Calculates the last column that is connected with the specified column.
|
||
|
/// </summary>
|
||
|
/// <param name="column"> The column that is probed for downward connection. </param>
|
||
|
/// <returns> The last column that is connected with the given column. </returns>
|
||
|
private int CalcLastConnectedColumn(int column)
|
||
|
{
|
||
|
int lastConnectedColumn = column;
|
||
|
foreach (Cell cell in _mergedCells)
|
||
|
{
|
||
|
if (cell.Column.Index <= lastConnectedColumn)
|
||
|
{
|
||
|
int rightConnection = Math.Max(cell.Column.KeepWith, cell.MergeRight);
|
||
|
if (lastConnectedColumn < cell.Column.Index + rightConnection)
|
||
|
lastConnectedColumn = cell.Column.Index + rightConnection;
|
||
|
}
|
||
|
}
|
||
|
return lastConnectedColumn;
|
||
|
}
|
||
|
|
||
|
private readonly Table _table;
|
||
|
private MergedCellList _mergedCells;
|
||
|
private Dictionary<Cell, FormattedCell> _formattedCells; //SortedList formattedCells;
|
||
|
private Dictionary<int, XUnit> _bottomBorderMap; //SortedList bottomBorderMap;
|
||
|
private Dictionary<int, int> _connectedRowsMap; //SortedList connectedRowsMap;
|
||
|
private Dictionary<int, int> _connectedColumnsMap; //SortedList connectedColumnsMap;
|
||
|
|
||
|
private int _lastHeaderRow;
|
||
|
private int _lastHeaderColumn;
|
||
|
private int _startRow;
|
||
|
private int _currRow;
|
||
|
private int _endRow = -1;
|
||
|
|
||
|
private bool _doHorizontalBreak;
|
||
|
private XUnit _startX;
|
||
|
private XUnit _startY;
|
||
|
}
|
||
|
}
|