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
13 changes: 13 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ jobs:
- name: Build
run: dotnet build Source/HtmlRenderer.sln --configuration Release --no-restore

- name: Run Tests
run: dotnet test Source/HtmlRenderer.sln --configuration Release --no-build --logger trx --results-directory ${{ github.workspace }}/TestResults
env:
PDF_OUTPUT_DIRECTORY: ${{ github.workspace }}/pdf-artifacts/${{ matrix.platform.name }}-${{ matrix.dotnet.name }}

- name: Create HtmlRenderer.Core NuGet package
run: dotnet pack Source/HtmlRenderer/HtmlRenderer.csproj --configuration Release --include-symbols -p:SymbolPackageFormat=snupkg --no-build --verbosity normal --output ${{ env.NuGetDirectory }}

Expand All @@ -69,6 +74,14 @@ jobs:
name: "HTML Renderer (${{ matrix.platform.name }} ${{ matrix.dotnet.name }})"
path: ${{ env.NuGetDirectory }}/*.*nupkg

- name: Upload PDF artifacts
if: always()
uses: actions/upload-artifact@v7
with:
name: "PDF artifacts (${{ matrix.platform.name }} ${{ matrix.dotnet.name }})"
path: ${{ github.workspace }}/pdf-artifacts/**/*.pdf
if-no-files-found: ignore

- name: NuGet Login
if: startsWith(github.ref, 'refs/tags/') && matrix.dotnet.name == '.NET 8' && runner.os == 'Windows'
uses: NuGet/login@v1
Expand Down
4 changes: 2 additions & 2 deletions Source/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<Authors>Arthur Teplitzki</Authors>
<Owners>Arthur Teplitzki</Owners>
<Company>Open source hosted on GitHub</Company>
<Copyright>Copyright © 2008-2025</Copyright>
<Copyright>Copyright © 2008-2026</Copyright>
<Version>1.6.0-dev</Version>
<AssemblyVersion>1.6.0.0</AssemblyVersion>
<FileVersion>1.6.0.0</FileVersion>
Expand All @@ -16,7 +16,7 @@
<PackageReleaseNotes>See https://github.com/ArthurHub/HTML-Renderer/releases.</PackageReleaseNotes>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<ItemGroup Condition="'$(IsTestProject)' != 'true'">
<None Include="../../html.png" Pack="true" PackagePath="\"/>
</ItemGroup>
</Project>
37 changes: 27 additions & 10 deletions Source/HtmlRenderer.PdfSharp/Adapters/PdfSharpAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@

using PdfSharp.Drawing;
using PdfSharp.Pdf;
using System.Drawing;
using System.Drawing.Text;
using System;
using System.IO;
using TheArtOfDev.HtmlRenderer.Adapters;
using TheArtOfDev.HtmlRenderer.Adapters.Entities;
using TheArtOfDev.HtmlRenderer.PdfSharp.FontResolution;
using TheArtOfDev.HtmlRenderer.PdfSharp.Utilities;

namespace TheArtOfDev.HtmlRenderer.PdfSharp.Adapters
Expand All @@ -41,14 +41,16 @@ internal sealed class PdfSharpAdapter : RAdapter
/// </summary>
private PdfSharpAdapter()
{
var fontResolver = FontResolver.Register();

AddFontFamilyMapping("monospace", "Courier New");
AddFontFamilyMapping("Helvetica", "Arial");

var families = new InstalledFontCollection();

foreach (var family in families.Families)
var fontFamilies = fontResolver.DiscoverFontFamilies();
foreach (var fontFamily in fontFamilies)
{
AddFontFamily(new FontFamilyAdapter(new XFontFamily(family.Name)));
AddFontFamily(new FontFamilyAdapter(new XFontFamily(fontFamily)));
}
}

Expand All @@ -64,8 +66,23 @@ protected override RColor GetColorInt(string colorName)
{
try
{
var color = Color.FromKnownColor((KnownColor)System.Enum.Parse(typeof(KnownColor), colorName, true));
return Utils.Convert(color);
var colorResourceManager = new XColorResourceManager();

var knownColors = XColorResourceManager.GetKnownColors(true);

foreach (var knownColor in knownColors)
{
var name = colorResourceManager.ToColorName(knownColor);
if (!string.Equals(name, colorName, StringComparison.OrdinalIgnoreCase))
{
continue;
}

var xColor = XColor.FromKnownColor(knownColor);
return xColor.IsEmpty ? RColor.Empty : Utils.Convert(xColor);
}

return RColor.Empty;
}
catch
{
Expand Down Expand Up @@ -119,14 +136,14 @@ protected override RImage ImageFromStreamInt(Stream memoryStream)

protected override RFont CreateFontInt(string family, double size, RFontStyle style)
{
var fontStyle = (XFontStyle)((int)style);
var fontStyle = Utils.Convert(style);
var xFont = new XFont(family, size, fontStyle, new XPdfFontOptions(PdfFontEncoding.Unicode));
return new FontAdapter(xFont);
}

protected override RFont CreateFontInt(RFontFamily family, double size, RFontStyle style)
{
var fontStyle = (XFontStyle)((int)style);
var fontStyle = Utils.Convert(style);
var xFont = new XFont(((FontFamilyAdapter)family).FontFamily.Name, size, fontStyle, new XPdfFontOptions(PdfFontEncoding.Unicode));
return new FontAdapter(xFont);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using TheArtOfDev.HtmlRenderer.PdfSharp.FontResolution.Parsing;

namespace TheArtOfDev.HtmlRenderer.PdfSharp.FontResolution.Discovery
{
internal static class DirectoryFontDiscovery
{
public static List<FontInfo> DiscoverFontFilesFromDirectories(List<string> customFontDirectories)
{
var fontDirectories = new List<string>();

// Add platform-specific directories
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
fontDirectories.Add(Environment.GetFolderPath(Environment.SpecialFolder.Fonts));
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
fontDirectories.AddRange(new[]
{
"/usr/share/fonts",
"/usr/local/share/fonts",
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".fonts"),
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local/share/fonts")
});
}

// Add custom directories
fontDirectories.AddRange(customFontDirectories);

return DiscoverFontFilesInDirectories(fontDirectories);
}

public static List<FontInfo> DiscoverFontFilesInDirectories(List<string> directories)
{
var fontInfos = new List<FontInfo>();

foreach (var directory in directories)
{
if (!Directory.Exists(directory))
{
continue;
}

try
{
fontInfos.AddRange(FontDiscoveryService.SupportedFontExtensions.SelectMany(e => Directory.GetFiles(directory, $"*{e}", SearchOption.AllDirectories))
.Select(f => new FontInfo
{
Name = Path.GetFileNameWithoutExtension(f),
FilePath = f
}));
}
catch
{
// Silently continue on directory access errors
}
}

return fontInfos;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace TheArtOfDev.HtmlRenderer.PdfSharp.FontResolution.Discovery
{
internal class FontDiscoveryService
{
private readonly List<string> _customFontDirectories = new List<string>();

public static string[] SupportedFontExtensions { get; } = new[] { ".ttf", ".otf" };

public List<FontInfo> DiscoverFontInfos()
{
var fontInfos = new List<FontInfo>();

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
fontInfos = WindowsFontDiscovery.DiscoverFontInfos();
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
fontInfos = LinuxFontDiscovery.DiscoverFontFiles();
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
fontInfos = MacOsFontDiscovery.DiscoverFontFiles();
}

// Always use directory scan as fallback
fontInfos.AddRange(DirectoryFontDiscovery.DiscoverFontFilesFromDirectories(_customFontDirectories));

return fontInfos;
}

public void RegisterCustomFontDirectory(string fontDirectory)
{
if (_customFontDirectories.Contains(fontDirectory))
{
return;
}

_customFontDirectories.Add(fontDirectory);
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;

namespace TheArtOfDev.HtmlRenderer.PdfSharp.FontResolution.Discovery
{
internal static class LinuxFontDiscovery
{
public static List<FontInfo> DiscoverFontFiles()
{
var fontInfos = new List<FontInfo>();

if (!IsFontConfigAvailable())
{
return fontInfos;
}

try
{
var startInfo = new ProcessStartInfo
{
FileName = "fc-list",
Arguments = ":",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};

using (var process = Process.Start(startInfo))
{
if (process == null)
{
return fontInfos;
}

var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();

if (process.ExitCode != 0 || string.IsNullOrWhiteSpace(output))
{
return fontInfos;
}

var lines = output.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
var fontFamilies = DiscoverFontFile(line);

if (fontFamilies.Count > 0)
{
fontInfos.AddRange(fontFamilies);
}
}
}
}
catch
{
// Silently continue on errors
}

return fontInfos;
}

private static bool IsFontConfigAvailable()
{
try
{
var startInfo = new ProcessStartInfo
{
FileName = "which",
Arguments = "fc-list",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};

using (var process = Process.Start(startInfo))
{
if (process == null)
{
return false;
}

process.WaitForExit();
return process.ExitCode == 0;
}
}
catch
{
return false;
}
}

private static List<FontInfo> DiscoverFontFile(string line)
{
var fonts = new List<FontInfo>();

// Parse fc-list output: "path: family1,family2:style=style"
var parts = line.Split(new[] { ':' }, 2, StringSplitOptions.None);
if (parts.Length < 2)
{
return fonts;
}

var fontFilePath = parts[0].Trim();
var families = parts[1].Trim().Split(',');

if (!File.Exists(fontFilePath) ||
!FontDiscoveryService.SupportedFontExtensions.Any(e => fontFilePath.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)))
{
return fonts;
}

foreach (var family in families)
{
var familyName = family.Trim();
if (!string.IsNullOrEmpty(familyName) && File.Exists(fontFilePath))
{
fonts.Add(new FontInfo { Name = familyName, FilePath = fontFilePath });
}
}
return fonts;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.IO;

namespace TheArtOfDev.HtmlRenderer.PdfSharp.FontResolution.Discovery
{
internal static class MacOsFontDiscovery
{
public static List<FontInfo> DiscoverFontFiles()
{
var fontDirectories = new List<string>
{
"/System/Library/Fonts",
"/Library/Fonts",
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library/Fonts")
};

return DirectoryFontDiscovery.DiscoverFontFilesInDirectories(fontDirectories);
}
}
}
Loading
Loading