void ArrangeLines(MathNode node) { //Get spacings and line styles; expand to cover the table fully List<double> spacings = node.GetListProperty("rowspacing").Select(x=> node.ParseLength(x)).ToList(); List<string> lines = node.GetListProperty("rowlines"); for (int i=0; i < node.Rows.Count -1; i++) //Todo: Verify { node.Rows[i].SpaceAfter = (double)Measurer.GetByIndexOrLast(spacings, i); string line = GetByIndexOrLast(lines, i); if (line != "none") { node.Rows[i].LineAfter = line; node.Rows[i].SpaceAfter += node.LineWidth; } } spacings = node.GetListProperty("columnspacing").Select(x => node.ParseLength(x)).ToList(); lines = node.GetListProperty("columnlines"); for (int i=0; i < node.Columns.Count -1; i++) //Todo: Verify { node.Columns[i].SpaceAfter = GetByIndexOrLast(spacings, i); string line = GetByIndexOrLast(lines, i); if (line != "none") { node.Columns[i].LineAfter = line; node.Columns[i].SpaceAfter += node.LineWidth; } } node.FrameSpacings = new List<double>(){0, 0}; node.FrameLines = new List<string>(){null, null}; spacings = node.GetListProperty("framespacing").Select(x => node.ParseSpace(x)).ToList(); lines = node.GetListProperty("frame"); for (int i = 0; i < 2; i++) //Todo: Verify { string line = GetByIndexOrLast(lines, i); if (line != "none") { node.FrameSpacings[i] = GetByIndexOrLast(spacings, i); node.FrameLines[i] = line; } } }
void CalculateColumnWidths(MathNode node) { // Get total width string fullwidthattr = "auto"; if (node.Attributes.ContainsKey("width")) fullwidthattr = node.Attributes["width"]; double? fullwidth; if (fullwidthattr == "auto") { fullwidth = null; } else { fullwidth = node.ParseLength(fullwidthattr); if (fullwidth <= 0) fullwidth = null; } // Fill fixed column widths List<string> columnwidths = node.GetListProperty("columnwidth"); foreach (var item in node.Columns.Select((value,i) => new {i, value})) { ColumnDescriptor column = item.value; string attr = GetByIndexOrLast(columnwidths, item.i); if (attr == "auto" || attr == "fit") { column.Fit = (attr == "fit"); } else if (attr.EndsWith("%")) { if (fullwidth == null) { //ToDo: node.error("Percents in column widths supported only in tables with explicit width; width of column %d treated as 'auto'" % (i+1)) } else { double value = 0.0; bool result = double.TryParse(attr.Substring(0, attr.Length - 1), out value); if (result && value > 0) { column.Width = (double)fullwidth * value / 100; column.Auto = false; } } } else { column.Width = node.ParseSpace(attr); column.Auto = true; } } // Set initial auto widths for cells with colspan == 1 foreach (RowDescriptor r in node.Rows) { foreach (var item in r.Cells.Select((value, i)=> new {i, value})) { CellDescriptor c = item.value; if (c == null || c.Content == null || c.ColSpan > 1) continue; ColumnDescriptor column = node.Columns[item.i]; if (column.Auto) column.Width = Math.Max(column.Width, c.Content.Width); } } // Calculate auto widths for cells with colspan > 1 while (true) { List<ColumnDescriptor> adjustedColumns = new List<ColumnDescriptor>(); double adjustedWidth = 0; foreach (RowDescriptor r in node.Rows) { foreach (var item in r.Cells.Select((value,i) => new {i, value})) { CellDescriptor c = item.value; if (c == null || c.Content == null || c.ColSpan == 1) continue; List<ColumnDescriptor> columns = node.Columns.Skip(item.i).Take(item.i + c.ColSpan).ToList(); //ToDo: Verify List<ColumnDescriptor> autoColumns = columns.Where(x => x.Auto == true).ToList(); if (autoColumns.Count == 0) continue; List<ColumnDescriptor> fixedColumns = columns.Where(x=> x.Auto == false).ToList(); double fixedWidth = columns.TakeWhile((value, i) => i != columns.Count-1).Select(x=> x.SpaceAfter).Sum(); if (fixedColumns.Count > 0) fixedWidth += fixedColumns.Select(x => x.Width).Sum(); //ToDo: Verify double autoWidth = autoColumns.Select(x => x.Width).Sum(); //ToDo: Verify if (c.Content.Width <= fixedWidth + autoWidth) continue; // already fits double requiredWidth = c.Content.Width - fixedWidth; double unitWidth = requiredWidth / autoColumns.Count; while (true) { List<ColumnDescriptor> oversizedColumns = autoColumns.Where(x=> x.Width >= unitWidth).ToList(); if (oversizedColumns.Count == 0) break; autoColumns = autoColumns.Where(x => x.Width < unitWidth).ToList(); if (autoColumns.Count == 0) break; //weird rounding effects requiredWidth -= oversizedColumns.Select(x => x.Width).Sum(); unitWidth = requiredWidth / autoColumns.Count; if (autoColumns.Count == 0) continue; //protection against arithmetic overflow //Store the maximum unit width if (unitWidth > adjustedWidth) { adjustedWidth = unitWidth; adjustedColumns = autoColumns; } } } } if (adjustedColumns.Count == 0) break; foreach (ColumnDescriptor col in adjustedColumns) col.Width = adjustedWidth; } if (node.GetProperty("equalcolumns") == "true") { double globalWidth = node.Columns.Where(x => x.Auto = true).Select(x => x.Width).Max(); foreach (ColumnDescriptor col in node.Columns) { if (col.Auto == true) col.Width = globalWidth; } } if (fullwidth != null) { double delta = (double)fullwidth; delta -= node.Columns.Select(x => x.Width).Sum(); delta -= node.Columns.TakeWhile((x, i) => i < node.Columns.Count - 1).Select(x => x.SpaceAfter).Sum(); //ToDo: verify delta -= 2 * node.FrameSpacings[0]; if (delta != 0) { List<ColumnDescriptor> sizableColumns = node.Columns.Where(x => x.Fit == true).ToList(); if (sizableColumns.Count == 0) sizableColumns = node.Columns.Where(x => x.Auto == true).ToList(); if (sizableColumns.Count == 0) { //ToDo: Implement //node.error("Overconstrained table layout: explicit table width specified, but no column has automatic width; table width attribute ignored") } else { delta /= sizableColumns.Count; foreach (ColumnDescriptor col in sizableColumns) col.Width += delta; } } } }
void ArrangeCells(MathNode node) { node.Rows = new List<RowDescriptor>(); node.Columns = new List<ColumnDescriptor>(); List<int> busycells = new List<int>(); // Read table-level alignment properties List<string> table_rowaligns = node.GetListProperty("rowalign"); List<string> table_columnaligns = node.GetListProperty("columnalign"); foreach (MathNode ch in node.Children) { string rowalign = GetByIndexOrLast(table_rowaligns, node.Rows.Count); List<string> row_columnaligns = table_columnaligns; List<MathNode> cells = new List<MathNode>(); if (ch.ElementName == "mtr" || ch.ElementName == "mlabeledtr") { cells = ch.Children; if (ch.Attributes.ContainsKey("rowalign")) rowalign = ch.Attributes["rowalign"]; if (ch.Attributes.ContainsKey("columnalign")) { List<string> columnaligns = node.GetListProperty("columnalign", ch.Attributes["columnalign"]); } } else { cells.Add(ch); } RowDescriptor row = new RowDescriptor(node, cells, rowalign, row_columnaligns, busycells); node.Rows.Add(row); // busycells passes information about cells spanning multiple rows busycells = busycells.Select(n => Math.Max(0, n - 1)).ToList(); while (busycells.Count < row.Cells.Count) busycells.Add(0); for(int i =0; i < row.Cells.Count; i++) { CellDescriptor cell = row.Cells[i]; if (cell == null) continue; if (cell.RowSpan > 1) { for (int j = i; j <= i + cell.ColSpan; i++) { busycells[j] = cell.RowSpan - 1; } } } } //Pad the table with empty rows until no spanning cell protrudes while (busycells.Max() > 0) { string rowalign = Measurer.GetByIndexOrLast(table_rowaligns, node.Rows.Count); node.Rows.Add(new RowDescriptor(node, new List<MathNode>(), rowalign, table_columnaligns, busycells)); busycells = busycells.Select(x => Math.Max(0, x - 1)).ToList(); } }