Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions OfficeIMO.Drawing/OfficeChartDrawingRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,18 @@ public static OfficeDrawing Render(OfficeChartSnapshot snapshot) {
double contentTop = 0D;
if (!string.IsNullOrWhiteSpace(snapshot.Title)) {
double titleHeight = Math.Min(22D, Math.Max(16D, height * 0.12D));
double titleTop = Math.Min(layout.TitleTopPadding, Math.Max(0D, height - titleHeight));
drawing.AddText(
snapshot.Title!,
8D,
5D,
titleTop,
Math.Max(1D, width - 16D),
Math.Max(1D, titleHeight - 4D),
new OfficeFontInfo(style.FontFamily, Math.Min(12D, Math.Max(8D, titleHeight - 7D)), OfficeFontStyle.Bold),
style.TitleColor,
OfficeTextAlignment.Center);
if (!layout.OverlayTitle) {
contentTop = titleHeight;
contentTop = titleHeight + Math.Max(0D, titleTop - 5D);
}
}

Expand Down
23 changes: 20 additions & 3 deletions OfficeIMO.Drawing/OfficeChartLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public sealed class OfficeChartLayout {
/// <param name="showCategoryAxisLabels">Whether category or horizontal tick labels should be rendered.</param>
/// <param name="showValueAxisLabels">Whether value or vertical tick labels should be rendered.</param>
/// <param name="overlayTitle">Whether the title should overlay the plot instead of reserving layout space.</param>
/// <param name="titleTopPadding">Top padding before the chart title inside the chart canvas.</param>
public OfficeChartLayout(
double? seriesLegendWidthRatio = null,
double? categoryLegendWidthRatio = null,
Expand Down Expand Up @@ -91,7 +92,8 @@ public OfficeChartLayout(
bool showValueAxisLine = true,
bool showCategoryAxisLabels = true,
bool showValueAxisLabels = true,
bool overlayTitle = false)
bool overlayTitle = false,
double? titleTopPadding = null)
: this(
overlayLegend: false,
seriesLegendWidthRatio: seriesLegendWidthRatio,
Expand Down Expand Up @@ -132,7 +134,8 @@ public OfficeChartLayout(
showValueAxisLine: showValueAxisLine,
showCategoryAxisLabels: showCategoryAxisLabels,
showValueAxisLabels: showValueAxisLabels,
overlayTitle: overlayTitle) {
overlayTitle: overlayTitle,
titleTopPadding: titleTopPadding) {
}

/// <summary>
Expand Down Expand Up @@ -178,6 +181,7 @@ public OfficeChartLayout(
/// <param name="showCategoryAxisLabels">Whether category or horizontal tick labels should be rendered.</param>
/// <param name="showValueAxisLabels">Whether value or vertical tick labels should be rendered.</param>
/// <param name="overlayTitle">Whether the title should overlay the plot instead of reserving layout space.</param>
/// <param name="titleTopPadding">Top padding before the chart title inside the chart canvas.</param>
public OfficeChartLayout(
bool overlayLegend,
double? seriesLegendWidthRatio = null,
Expand Down Expand Up @@ -218,7 +222,8 @@ public OfficeChartLayout(
bool showValueAxisLine = true,
bool showCategoryAxisLabels = true,
bool showValueAxisLabels = true,
bool overlayTitle = false) {
bool overlayTitle = false,
double? titleTopPadding = null) {
SeriesLegendWidthRatio = ValidateRatio(seriesLegendWidthRatio ?? 0.34D, nameof(seriesLegendWidthRatio));
CategoryLegendWidthRatio = ValidateRatio(categoryLegendWidthRatio ?? 0.38D, nameof(categoryLegendWidthRatio));
LegendRowHeight = ValidatePositiveFinite(legendRowHeight ?? 12D, nameof(legendRowHeight));
Expand Down Expand Up @@ -259,6 +264,7 @@ public OfficeChartLayout(
ShowCategoryAxisLabels = showCategoryAxis && showCategoryAxisLabels;
ShowValueAxisLabels = showValueAxis && showValueAxisLabels;
OverlayTitle = overlayTitle;
TitleTopPadding = ValidateNonNegativeFinite(titleTopPadding ?? 5D, nameof(titleTopPadding));
}

/// <summary>Default premium OfficeIMO chart layout.</summary>
Expand Down Expand Up @@ -396,6 +402,9 @@ public OfficeChartLayout(
/// <summary>Whether the chart title should overlay the plot area instead of reserving a title band.</summary>
public bool OverlayTitle { get; }

/// <summary>Top padding before the chart title inside the chart canvas.</summary>
public double TitleTopPadding { get; }

private static double ValidateRatio(double value, string paramName) {
ValidatePositiveFinite(value, paramName);
if (value > 0.75D) {
Expand All @@ -413,6 +422,14 @@ private static double ValidatePositiveFinite(double value, string paramName) {
return value;
}

private static double ValidateNonNegativeFinite(double value, string paramName) {
if (double.IsNaN(value) || double.IsInfinity(value) || value < 0D) {
throw new ArgumentOutOfRangeException(paramName, "Chart layout values must be finite non-negative numbers.");
}

return value;
}

private static int ValidatePositive(int value, string paramName) {
if (value <= 0) {
throw new ArgumentOutOfRangeException(paramName, "Chart layout counts must be positive.");
Expand Down
20 changes: 11 additions & 9 deletions OfficeIMO.Markup.VSCode/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions OfficeIMO.Pdf/Model/PdfEmbeddedFontFamily.System.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,16 @@ private static bool IsMetadataFamilyMatch(TrueTypeNameMetadata metadata, string
}
}

foreach (string? faceName in metadata.GetFaceNames()) {
if (string.IsNullOrWhiteSpace(faceName)) {
continue;
}

if (IsMetadataFamilyNameMatch(faceName!, normalizedMetadataFamily)) {
return true;
}
}

return false;
}

Expand Down Expand Up @@ -580,6 +590,26 @@ public TrueTypeNameMetadata(
yield return TypographicFamilyName;
yield return FamilyName;
}

public System.Collections.Generic.IEnumerable<string?> GetFaceNames() {
yield return FullName;
yield return PostScriptName;
yield return CombineFamilyAndSubfamily(TypographicFamilyName, TypographicSubfamilyName);
yield return CombineFamilyAndSubfamily(FamilyName, SubfamilyName);
}

private static string? CombineFamilyAndSubfamily(string? familyName, string? subfamilyName) {
if (string.IsNullOrWhiteSpace(familyName)) {
return null;
}

if (string.IsNullOrWhiteSpace(subfamilyName) ||
string.Equals(subfamilyName, "Regular", System.StringComparison.OrdinalIgnoreCase)) {
return familyName;
}

return familyName + " " + subfamilyName;
}
}

private sealed class TrueTypeNameValue {
Expand Down
4 changes: 4 additions & 0 deletions OfficeIMO.Pdf/Model/PdfHeadingStyle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ public double? SpacingAfter {
/// <summary>Heading text color. A heading block color overrides this value.</summary>
public PdfColor? Color { get; set; }

/// <summary>Heading font slot. When null the writer uses the document default font family.</summary>
public PdfStandardFont? Font { get; set; }

/// <summary>When true, headings use the bold variant of the document font.</summary>
public bool Bold { get; set; } = true;

Expand All @@ -65,6 +68,7 @@ public PdfHeadingStyle Clone() {
SpacingBefore = SpacingBefore,
SpacingAfter = SpacingAfter,
Color = Color,
Font = Font,
Bold = Bold,
ApplySpacingBeforeAtTop = ApplySpacingBeforeAtTop,
KeepWithNext = KeepWithNext
Expand Down
80 changes: 78 additions & 2 deletions OfficeIMO.Pdf/Model/PdfTableCell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public PdfTableCell(string? text, int columnSpan = 1, string? linkUri = null, st
CheckBoxes = SnapshotCheckBoxes(checkBoxes, nameof(checkBoxes));
FormFields = SnapshotFormFields(formFields, nameof(formFields));
Images = SnapshotImages(images, nameof(images));
Paragraphs = System.Array.AsReadOnly(System.Array.Empty<PdfTableCellParagraph>());
}

/// <summary>Creates a table cell with rich text runs, optional column/row spans, optional link metadata, images, and form fields.</summary>
Expand Down Expand Up @@ -46,6 +47,35 @@ public PdfTableCell(System.Collections.Generic.IEnumerable<TextRun> runs, int co
CheckBoxes = SnapshotCheckBoxes(checkBoxes, nameof(checkBoxes));
FormFields = SnapshotFormFields(formFields, nameof(formFields));
Images = SnapshotImages(images, nameof(images));
Paragraphs = System.Array.AsReadOnly(System.Array.Empty<PdfTableCellParagraph>());
}

internal PdfTableCell(System.Collections.Generic.IEnumerable<TextRun> runs, System.Collections.Generic.IEnumerable<PdfTableCellParagraph>? paragraphs, int columnSpan = 1, string? linkUri = null, string? linkContents = null, int rowSpan = 1, System.Collections.Generic.IEnumerable<PdfTableCellCheckBox>? checkBoxes = null, System.Collections.Generic.IEnumerable<PdfTableCellFormField>? formFields = null, System.Collections.Generic.IEnumerable<PdfTableCellImage>? images = null, string? linkDestinationName = null, string? namedDestinationName = null) {
Guard.NotNull(runs, nameof(runs));
Validate(columnSpan, rowSpan, linkUri, linkDestinationName, linkContents, namedDestinationName);
var snapshot = new System.Collections.Generic.List<TextRun>();
var text = new System.Text.StringBuilder();
foreach (TextRun run in runs) {
if (run is null) {
throw new System.ArgumentException("Table cell text runs cannot contain null entries.", nameof(runs));
}

snapshot.Add(run);
text.Append(run.Text);
}

Text = text.ToString();
Runs = snapshot.AsReadOnly();
ColumnSpan = columnSpan;
RowSpan = rowSpan;
LinkUri = linkUri;
LinkDestinationName = linkDestinationName;
NamedDestinationName = namedDestinationName;
LinkContents = HasLinkTarget(linkUri, linkDestinationName) ? linkContents ?? Text : null;
CheckBoxes = SnapshotCheckBoxes(checkBoxes, nameof(checkBoxes));
FormFields = SnapshotFormFields(formFields, nameof(formFields));
Images = SnapshotImages(images, nameof(images));
Paragraphs = SnapshotParagraphs(paragraphs, nameof(paragraphs));
}

/// <summary>Cell text content.</summary>
Expand Down Expand Up @@ -81,6 +111,8 @@ public PdfTableCell(System.Collections.Generic.IEnumerable<TextRun> runs, int co
/// <summary>Images rendered inside this cell.</summary>
public System.Collections.Generic.IReadOnlyList<PdfTableCellImage> Images { get; }

internal System.Collections.Generic.IReadOnlyList<PdfTableCellParagraph> Paragraphs { get; }

/// <summary>Creates a single-column text cell.</summary>
public static PdfTableCell TextCell(string? text, string? linkUri = null, string? linkContents = null, string? linkDestinationName = null, string? namedDestinationName = null) => new PdfTableCell(text, linkUri: linkUri, linkContents: linkContents, linkDestinationName: linkDestinationName, namedDestinationName: namedDestinationName);

Expand Down Expand Up @@ -118,9 +150,9 @@ public PdfTableCell(System.Collections.Generic.IEnumerable<TextRun> runs, int co
public static PdfTableCell WithImages(string? text, System.Collections.Generic.IEnumerable<PdfTableCellImage> images, int columnSpan = 1, string? linkUri = null, string? linkContents = null, int rowSpan = 1, System.Collections.Generic.IEnumerable<PdfTableCellCheckBox>? checkBoxes = null, System.Collections.Generic.IEnumerable<PdfTableCellFormField>? formFields = null, string? linkDestinationName = null) => new PdfTableCell(text, columnSpan, linkUri, linkContents, rowSpan, checkBoxes, formFields, images, linkDestinationName);

/// <summary>Returns a copy of this cell with a PDF named destination defined at the cell.</summary>
public PdfTableCell WithNamedDestination(string? namedDestinationName) => new PdfTableCell(Runs, ColumnSpan, LinkUri, LinkContents, RowSpan, CheckBoxes, FormFields, Images, LinkDestinationName, namedDestinationName);
public PdfTableCell WithNamedDestination(string? namedDestinationName) => new PdfTableCell(Runs, Paragraphs, ColumnSpan, LinkUri, LinkContents, RowSpan, CheckBoxes, FormFields, Images, LinkDestinationName, namedDestinationName);

internal PdfTableCell Clone() => new PdfTableCell(Runs, ColumnSpan, LinkUri, LinkContents, RowSpan, CheckBoxes, FormFields, Images, LinkDestinationName, NamedDestinationName);
internal PdfTableCell Clone() => new PdfTableCell(Runs, Paragraphs, ColumnSpan, LinkUri, LinkContents, RowSpan, CheckBoxes, FormFields, Images, LinkDestinationName, NamedDestinationName);

private static void Validate(int columnSpan, int rowSpan, string? linkUri, string? linkDestinationName, string? linkContents, string? namedDestinationName) {
if (columnSpan < 1) {
Expand Down Expand Up @@ -206,4 +238,48 @@ private static System.Collections.ObjectModel.ReadOnlyCollection<PdfTableCellIma

return snapshot.AsReadOnly();
}

private static System.Collections.ObjectModel.ReadOnlyCollection<PdfTableCellParagraph> SnapshotParagraphs(System.Collections.Generic.IEnumerable<PdfTableCellParagraph>? paragraphs, string paramName) {
if (paragraphs == null) {
return System.Array.AsReadOnly(System.Array.Empty<PdfTableCellParagraph>());
}

var snapshot = new System.Collections.Generic.List<PdfTableCellParagraph>();
foreach (PdfTableCellParagraph paragraph in paragraphs) {
if (paragraph == null) {
throw new System.ArgumentException("Table cell paragraphs cannot contain null entries.", paramName);
}

snapshot.Add(paragraph.Clone());
}

return snapshot.AsReadOnly();
}
}

internal sealed class PdfTableCellParagraph {
public PdfTableCellParagraph(System.Collections.Generic.IEnumerable<TextRun> runs, double spacingAfter = 0D) {
Guard.NotNull(runs, nameof(runs));
if (spacingAfter < 0 || double.IsNaN(spacingAfter) || double.IsInfinity(spacingAfter)) {
throw new System.ArgumentOutOfRangeException(nameof(spacingAfter), "Table cell paragraph spacing must be a non-negative finite value.");
}

var snapshot = new System.Collections.Generic.List<TextRun>();
foreach (TextRun run in runs) {
if (run is null) {
throw new System.ArgumentException("Table cell paragraph runs cannot contain null entries.", nameof(runs));
}

snapshot.Add(run);
}

Runs = snapshot.AsReadOnly();
SpacingAfter = spacingAfter;
}

public System.Collections.Generic.IReadOnlyList<TextRun> Runs { get; }

public double SpacingAfter { get; }

internal PdfTableCellParagraph Clone() => new PdfTableCellParagraph(Runs, SpacingAfter);
}
13 changes: 13 additions & 0 deletions OfficeIMO.Pdf/Model/PdfTableStyle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class PdfTableStyle {
private double _cellSpacing;
private double _minRowHeight;
private double _spacingBefore;
private double _pageContinuationSpacingBefore;
private double? _captionFontSize;
private double _captionSpacingAfter = 4;
private double _spacingAfter;
Expand Down Expand Up @@ -411,6 +412,14 @@ public double SpacingBefore {
_spacingBefore = value;
}
}
/// <summary>Vertical space to reserve before table content when the same table continues on a new page.</summary>
public double PageContinuationSpacingBefore {
get => _pageContinuationSpacingBefore;
set {
ValidateNonNegativeFiniteValue(value, nameof(PageContinuationSpacingBefore), "Table page continuation spacing before must be a non-negative finite value.");
_pageContinuationSpacingBefore = value;
}
}
/// <summary>Optional text rendered above the table grid as part of the table flow.</summary>
public string? Caption { get; set; }
/// <summary>Caption alignment inside the rendered table width.</summary>
Expand Down Expand Up @@ -463,6 +472,8 @@ public double? MaxWidth {
_maxWidth = value;
}
}
/// <summary>When true, the resolved table frame width is preserved even if measured columns would otherwise shrink to their content.</summary>
public bool PreserveWidth { get; set; }
/// <summary>Optional left indentation before table placement, in points.</summary>
public double LeftIndent {
get => _leftIndent;
Expand Down Expand Up @@ -581,6 +592,7 @@ public PdfTableStyle Clone() {
MinRowHeight = MinRowHeight,
RowMinHeights = RowMinHeights,
SpacingBefore = SpacingBefore,
PageContinuationSpacingBefore = PageContinuationSpacingBefore,
Caption = Caption,
CaptionAlign = CaptionAlign,
CaptionColor = CaptionColor,
Expand All @@ -589,6 +601,7 @@ public PdfTableStyle Clone() {
SpacingAfter = SpacingAfter,
RowBaselineOffset = RowBaselineOffset,
MaxWidth = MaxWidth,
PreserveWidth = PreserveWidth,
LeftIndent = LeftIndent,
AutoFitColumns = AutoFitColumns,
RightAlignNumeric = RightAlignNumeric,
Expand Down
Loading
Loading