/// <summary> /// Scale the widths given in the column specification to split up the specified full width as evenly as possible /// </summary> /// <exception cref="System.ArgumentOutOfRangeException">Thrown if the width is smaller than the number of columns</exception> public static int[] ScaleColumnWidths(int fullWidth, ColumnFormat[] columnFormats) { fullWidth = Math.Abs(fullWidth); // Two small to fit anything in if (fullWidth < columnFormats.Length) { throw new ArgumentOutOfRangeException("fullWidth"); } int[] specifiedWidths = columnFormats.Select(c => c.Width).ToArray(); return ColumnFormat.ScaleWidths(fullWidth, specifiedWidths); }
private static ColumnFormat[] Helper(bool firstColumnMustBeVisible, params int[] widths) { ColumnFormat[] formats = new ColumnFormat[widths.Length]; for (int i = 0; i < widths.Length; ++i) { formats[i] = new ColumnFormat(widths[i]); } if (firstColumnMustBeVisible) formats[0].Visibility = ContentVisibility.ShowAll; return formats; }
/// <summary> /// Scale the widths given in the column specification to split up the specified full width as evenly as possible /// </summary> /// <exception cref="System.ArgumentOutOfRangeException">Thrown if the width is smaller than the number of columns</exception> public static int[] ScaleColumnWidths(int fullWidth, ColumnFormat[] columnFormats) { fullWidth = Math.Abs(fullWidth); // Two small to fit anything in if (fullWidth < columnFormats.Length) { throw new ArgumentOutOfRangeException("fullWidth"); } int[] specifiedWidths = columnFormats.Select(c => c.Width).ToArray(); return(ColumnFormat.ScaleWidths(fullWidth, specifiedWidths)); }
private static ColumnFormat[] Helper(bool firstColumnMustBeVisible, params int[] widths) { ColumnFormat[] formats = new ColumnFormat[widths.Length]; for (int i = 0; i < widths.Length; ++i) { formats[i] = new ColumnFormat(widths[i]); } if (firstColumnMustBeVisible) { formats[0].Visibility = ContentVisibility.ShowAll; } return(formats); }
private static int[] ScaleWidths(int fullWidth, int[] widths) { int[] columnWidths = new int[widths.Length]; int remainingWidth = fullWidth; int fractionalWidths = 0; // Add up the specified widths for (int i = 0; i < columnWidths.Length; i++) { fractionalWidths += (columnWidths[i] = widths[i]); } // Scale up to whole numbers, adding any remainder bool zeroSpace = false; double widthMultiplier = (double)fullWidth / fractionalWidths; for (int i = 0; i < columnWidths.Length; i++) { int scaledWidth = (int)(columnWidths[i] * widthMultiplier); if (scaledWidth == 0) { zeroSpace = true; columnWidths[i] = 1; } else { columnWidths[i] = scaledWidth; } remainingWidth -= columnWidths[i]; } if (remainingWidth > 0) { columnWidths[columnWidths.Length - 1] += remainingWidth; } if (zeroSpace) { // Recursing should continue to drop us down to a truer fit return(ColumnFormat.ScaleWidths(fullWidth, columnWidths)); } else { return(columnWidths); } }
/// <summary> /// Creates a standard table with a header row with the specifed widths (forcing first column visibility) /// </summary> public static Table Create(params int[] widths) { return(new Table(ColumnFormat.FromWidths(widths))); }
// Font Colors: // ============ // Red- \red255\green0\blue0; // Orange- \red255\green192\blue0; // Yellow- \red255\green255\blue0; // Green- \red0\green176\blue80; // Blue- \red0\green77\blue187; // Purple- \red155\green0\blue211; // Highlight Colors: // ================= // Yellow- \red255\green255\blue0; // Green- \red0\green255\blue0; // LightBlue- \red0\green255\blue255; // Pink- \red255\green0\blue255; // {\colortbl ;} // \highlight1/0 // \cf1/0 public override void Write(ITable table) { // <row> (<tbldef> <cell>+ <tbldef> \row) | (<tbldef> <cell>+ \row) | (<cell>+ <tbldef> \row) // <cell> (<nestrow>? <tbldef>?) & <textpar>+ \cell // 12 point font // 6 point padding (120 twips) // 14 point height (280 twips) // 6.5" total width (9360 twips) // Calculate widths (default 1n), converting to fractions of 6.5" in twips (9360) int[] columnWidths = ColumnFormat.ScaleColumnWidths(9360, table.ColumnFormats); // Create the row header // \trowd // Start row // \trgaph120 // Half space between cells in twips (6pt) // \trrh280 // Row height in twips (0 auto, + at least, - exact) (14pt) // \trpaddl120 // Cell left padding (6pt) // \trpaddr120 // Cell right padding // \trpaddfl3 // Left padding unit is twips // \trpaddfr3 // Right padding unit is twips StringBuilder rowHeader = new StringBuilder(128); rowHeader.AppendFormat(@"{{\trowd\trgaph{0}\trrh-{1}\trpaddl{0}\trpaddr{0}\trpaddfl3\trpaddfr3", RichTextLogger.FontSize / 2 * RichTextLogger.TwipsPerPoint, // Padding of half the font size (RichTextLogger.FontSize + 2) * RichTextLogger.TwipsPerPoint); // Row height, negative to make "exactly" int totalWidth = 0; for (int i = 0; i < columnWidths.Length; i++) { totalWidth += columnWidths[i]; rowHeader.Append(@"\cellx"); rowHeader.Append(totalWidth); } bool headerRow = table.HasHeader; this.priorControlCharacter = false; foreach (var row in table.Rows) { this.richText.Append(rowHeader); if (headerRow) { this.richText.Append(@"\b"); } for (int i = 0; i < row.Length; i++) { string alignment; switch (table.ColumnFormats[i].Justification) { case Justification.Centered: alignment = @"\qc"; break; case Justification.Right: alignment = @"\qr"; break; case Justification.Left: default: alignment = @"\ql"; break; } this.richText.AppendFormat(@"\pard\intbl\widctlpar{0} {1}\cell", alignment, this.Escape(row[i])); } if (headerRow) { this.richText.Append(@"\b0"); headerRow = false; } this.richText.Append(@"\row}"); } this.priorControlCharacter = true; }
/// <summary> /// Construct a simple format array of x columns of equivalent width, forcing the first to be visible if possible /// </summary> public static ColumnFormat[] FromCount(int columns) { return(ColumnFormat.Helper(firstColumnMustBeVisible: true, widths: new int[columns])); }
/// <summary> /// Construct a simple format array of relative column widths, forcing the first to be visible if possible /// </summary> public static ColumnFormat[] FromWidths(params int[] widths) { return(ColumnFormat.Helper(firstColumnMustBeVisible: true, widths: widths)); }
public override void Write(ITable table) { // Get the desired column widths in characters int columnCount = table.ColumnFormats.Length; int[] columnWidths = ColumnFormat.ScaleColumnWidths(this.TableWidth, table.ColumnFormats); // Convert tabs to spaces so we can layout properly and // get our max widths to see if we fall under the TableWidth List <string[]> rowsCopy = new List <string[]>(); int[] maxColumnWidth = new int[columnCount]; foreach (var row in table.Rows) { string[] rowCopy = new string[row.Length]; for (int i = 0; i < row.Length; i++) { rowCopy[i] = Strings.TabsToSpaces(row[i]); if (table.ColumnFormats[i].Visibility.HasFlag(ContentVisibility.CompressWhitespace)) { rowCopy[i] = Strings.CompressWhiteSpace(rowCopy[i]); } maxColumnWidth[i] = Math.Max(maxColumnWidth[i], rowCopy[i].Length + 1); // We add a space between rows } rowsCopy.Add(rowCopy); } // Shrink the columns to fit, expanding the last column if needed with what we trim int availableSpace = 0; int neededSpace = 0; for (int i = 0; i < columnCount; ++i) { int overage = columnWidths[i] - maxColumnWidth[i]; if (overage > 0) { availableSpace += overage; columnWidths[i] -= overage; } else { neededSpace -= overage; } } // Try to fit columns that have required visibility (greedy) for (int i = 0; i < columnCount; ++i) { if (table.ColumnFormats[i].Visibility.HasFlag(ContentVisibility.ShowAll) && maxColumnWidth[i] > columnWidths[i]) { int columnNeededSpace = maxColumnWidth[i] - columnWidths[i]; if (availableSpace >= columnNeededSpace) { // Plenty available columnWidths[i] += columnNeededSpace; availableSpace -= columnNeededSpace; neededSpace -= columnNeededSpace; } else { // Take whatever is available columnWidths[i] += availableSpace; columnNeededSpace -= availableSpace; neededSpace -= availableSpace; availableSpace = 0; // Keep culling a space from available columns while we can while (columnNeededSpace > 0) { for (int j = 0; j < columnCount && columnNeededSpace > 0; j++) { if (!table.ColumnFormats[j].Visibility.HasFlag(ContentVisibility.ShowAll) && columnWidths[j] > 4) // Don't ever go below 4 (3 characters with a buffer space) { columnWidths[j]--; columnWidths[i]++; availableSpace++; columnNeededSpace--; neededSpace--; } } // If we were unable to find any more space, bail if (availableSpace == 0) { break; } else { availableSpace = 0; } } } } } // If we still have space and need it, try and fit any "normal" columns by evenly distributing space while (neededSpace > 0 && availableSpace > 0) { for (int i = 0; i < columnCount; ++i) { if (maxColumnWidth[i] - columnWidths[i] > 0) { columnWidths[i]++; neededSpace--; if (--availableSpace == 0) { break; } } } } // To make things look nicer, if we have enough space to add a single space to each column, do it if (availableSpace >= columnCount) { for (int i = 0; i < columnCount; ++i) { columnWidths[i]++; } } bool headerRow = table.HasHeader; StringBuilder rowBuilder = new StringBuilder(TableWidth + 1); foreach (var row in rowsCopy) { rowBuilder.Clear(); for (int i = 0; i < row.Length; i++) { bool lastColumn = (i == row.Length - 1); rowBuilder.WriteColumn( row[i], table.ColumnFormats[i].Justification, columnWidths[i] - 1, lastColumn && !headerRow); if (!lastColumn) { if (headerRow) { rowBuilder.Append('_'); } else { rowBuilder.Append(' '); } } } if (headerRow) { WriteLine(WriteStyle.Underline, rowBuilder.ToString()); headerRow = false; } else { WriteLine(rowBuilder.ToString()); } } }