Skip to content
Open
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
11 changes: 5 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,7 @@ jobs:
- name: Select build target
id: build-target
run: |
# HtmlRenderer.WinUI/HtmlRenderer.Demo.WinUI (Windows App SDK/Win2D, MSIX PRI generation via
# native makepri.exe/makeappx.exe) can only build on Windows - unlike the WinForms/WPF adapters,
# which cross-compile cleanly everywhere via EnableWindowsTargeting's reference-assembly story,
# there is no non-Windows equivalent for those native tools at all. Non-Windows runners build a
# solution filter excluding just those two projects; everything else still builds and tests
# identically on every OS.
# WinUI needs the Windows-only XAML and MSIX tooling, the other platforms build everything else
$target = $IsWindows ? "Source/HtmlRenderer.sln" : "Source/HtmlRenderer.CrossPlatform.slnf"
echo "path=$target" >> $env:GITHUB_OUTPUT

Expand Down Expand Up @@ -107,6 +102,10 @@ jobs:
- name: Create HtmlRenderer.WPF NuGet package
run: dotnet pack Source/HtmlRenderer.WPF/HtmlRenderer.WPF.csproj --configuration Release --include-symbols -p:SymbolPackageFormat=snupkg --no-build --verbosity normal --output ${{ env.NuGetDirectory }}

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

- name: Upload NuGet package artifacts
uses: actions/upload-artifact@v7
with:
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# HTML Renderer

**Cross framework** (WinForms/WPF/PDF/Metro/Mono/etc.), **Multipurpose** (UI Controls / Image generation / PDF generation / etc.), **100% managed** (C#), High performance HTML Rendering library.
**Cross framework** (WinForms/WPF/WinUI/PDF/Mono/etc.), **Multipurpose** (UI Controls / Image generation / PDF generation / etc.), **100% managed** (C#), High performance HTML Rendering library.

The library is 100% managed **C#** code without any external dependencies (no WebBrowser control, ActiveX / COM or MSHTML dll), the only requirement is **.NET Standard 2.0 / .NET 8.0 or higher**.

Expand All @@ -22,6 +22,7 @@ The latest NuGet packages can be found on NuGet.org:

* [HtmlRenderer.WinForms](https://www.nuget.org/packages/HtmlRenderer.WinForms)
* [HtmlRenderer.WPF](https://www.nuget.org/packages/HtmlRenderer.WPF)
* [HtmlRenderer.WinUI](https://www.nuget.org/packages/HtmlRenderer.WinUI)
* [HtmlRenderer.PdfSharp](https://www.nuget.org/packages/HtmlRenderer.PdfSharp)
* [HtmlRenderer.Core](https://www.nuget.org/packages/HtmlRenderer.Core)

Expand All @@ -33,6 +34,7 @@ The latest NuGet packages can be found on NuGet.org:
* Support text selection, copy-paste and context menu.
* WinForms controls: HtmlPanel, HtmlLabel and HtmlToolTip.
* WPF controls: HtmlPanel and HtmlLabel.
* WinUI 3 controls: HtmlPanel and HtmlLabel.
* Works on Mono.
* Create images/PDFs from HTML snippets.
* Handles "real world" malformed HTML, it doesn't have to be XHTML.
Expand All @@ -43,7 +45,7 @@ The latest NuGet packages can be found on NuGet.org:
* Powerful [Demo application](https://codeplexarchive.org/ProjectTab/Wiki/HtmlRenderer/Documentation/Demo%20application) to explore and learn the library.


## WinForms/WPF controls
## WinForms/WPF/WinUI controls

* *HtmlPanel* - The full power of HTML control build to replace WebBrowser control, accepts HTML, text selection, scrollbars, link click intercept, image load intercept and much more.
* *HtmlLabel* - As WinForms label but accepts HTML, text selection, auto-size capabilities, transparent background and more.
Expand Down
8 changes: 3 additions & 5 deletions Source/Demo/WinUI/App.xaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
<Application
x:Class="TheArtOfDev.HtmlRenderer.Demo.WinUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
RequestedTheme="Light">
<!-- Light, as the HTML panels and the RTF editors are white with dark text -->
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Supplies the shared theme resources (e.g. TabViewButtonBackground) that TreeView's
own default style depends on - without this, any non-trivial control throws a
XamlParseException at InitializeComponent time because those keys don't exist. -->
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
Expand Down
24 changes: 19 additions & 5 deletions Source/Demo/WinUI/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@
// - Sun Tsu,
// "The Art of War"

using System;
using System.IO;
using Microsoft.UI.Xaml;
using TheArtOfDev.HtmlRenderer.Demo.Common;
using TheArtOfDev.HtmlRenderer.WinUI;

namespace TheArtOfDev.HtmlRenderer.Demo.WinUI
{
/// <summary>
/// Minimal application entry point - hosts one <see cref="MainWindow"/> with a single
/// <see cref="TheArtOfDev.HtmlRenderer.WinUI.HtmlPanel"/> rendering a hardcoded sample document, just
/// enough to prove the WinUI 3 control renders real HTML end to end.
/// </summary>
public partial class App : Application
{
private Window _window;
Expand All @@ -30,8 +29,23 @@ public App()

protected override void OnLaunched(LaunchActivatedEventArgs args)
{
SamplesLoader.Init("WinUI", typeof(HtmlRender).Assembly.GetName().Version.ToString());

LoadCustomFonts();

_window = new MainWindow();
_window.Activate();
}

/// <summary>
/// Load custom fonts to be used by renderer HTMLs
/// </summary>
private static void LoadCustomFonts()
{
// Win2D loads fonts only from files
var file = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N") + ".ttf");
File.WriteAllBytes(file, Common.Resources.CustomFont);
HtmlRender.AddFontFamily(new Uri(file));
}
}
}
19 changes: 19 additions & 0 deletions Source/Demo/WinUI/GenerateImageWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Window
x:Class="TheArtOfDev.HtmlRenderer.Demo.WinUI.GenerateImageWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<CommandBar Grid.Row="0" HorizontalAlignment="Left" DefaultLabelPosition="Right">
<AppBarButton Icon="Refresh" Label="Re-Generate" Click="OnGenerateImage_Click"/>
<AppBarSeparator/>
<AppBarButton Icon="Save" Label="Save to File" Click="OnSaveToFile_Click"/>
</CommandBar>
<Border x:Name="_imageBoxBorder" Grid.Row="1">
<Image x:Name="_imageBox" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</Border>
</Grid>
</Window>
130 changes: 130 additions & 0 deletions Source/Demo/WinUI/GenerateImageWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they begin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
//
// - Sun Tsu,
// "The Art of War"

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Graphics.Canvas;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media.Imaging;
using TheArtOfDev.HtmlRenderer.Demo.Common;
using TheArtOfDev.HtmlRenderer.WinUI;
using Windows.Foundation;
using Windows.Storage;
using Windows.Storage.Streams;

namespace TheArtOfDev.HtmlRenderer.Demo.WinUI
{
public sealed partial class GenerateImageWindow : Window
{
private readonly string _html;
private CanvasRenderTarget _generatedImage;

public GenerateImageWindow(string html)
{
_html = html;

InitializeComponent();

WindowHelper.Setup(this, "Generate Image", 0.56, 0.64);

_imageBoxBorder.SizeChanged += OnFirstSizeChanged;
Closed += (sender, args) => _generatedImage?.Dispose();
}

private async void OnFirstSizeChanged(object sender, SizeChangedEventArgs e)
{
_imageBoxBorder.SizeChanged -= OnFirstSizeChanged;
await GenerateImage();
}

private async void OnGenerateImage_Click(object sender, RoutedEventArgs e)
{
await GenerateImage();
}

private async void OnSaveToFile_Click(object sender, RoutedEventArgs e)
{
if (_generatedImage == null)
return;

try
{
var picker = WindowHelper.CreateSavePicker(this);
picker.SuggestedFileName = "image";
picker.DefaultFileExtension = ".png";
picker.FileTypeChoices.Add("Images", new List<string> { ".png", ".bmp", ".jpg", ".tif", ".gif" });

var file = await picker.PickSaveFileAsync();
if (file == null)
return;

using var stream = await file.OpenAsync(FileAccessMode.ReadWrite);
stream.Size = 0;
await _generatedImage.SaveAsync(stream, GetBitmapFileFormat(Path.GetExtension(file.Path)));
}
catch (Exception ex)
{
await WindowHelper.ShowMessageAsync(Content.XamlRoot, ex.ToString(), "Failed to save the image");
}
}

private async Task GenerateImage()
{
var size = new Size(_imageBoxBorder.ActualWidth, _imageBoxBorder.ActualHeight);
if (size.Width > 0 && size.Height > 0)
{
try
{
_generatedImage?.Dispose();
_generatedImage = await HtmlRender.RenderToImageAsync(_html, size, null, DemoUtils.OnStylesheetLoad, HtmlRenderingHelper.OnImageLoad);

using var stream = new InMemoryRandomAccessStream();
await _generatedImage.SaveAsync(stream, CanvasBitmapFileFormat.Png);
stream.Seek(0);

var bitmap = new BitmapImage();
await bitmap.SetSourceAsync(stream);
_imageBox.Source = bitmap;
}
catch (Exception ex)
{
_generatedImage = null;
await WindowHelper.ShowMessageAsync(Content.XamlRoot, ex.ToString(), "Failed to generate the image");
}
}
}

/// <summary>
/// Get the Win2D format to encode the image with for the given file extension, default is PNG.
/// </summary>
private static CanvasBitmapFileFormat GetBitmapFileFormat(string ext)
{
switch (ext.ToLower())
{
case ".jpg":
case ".jpeg":
return CanvasBitmapFileFormat.Jpeg;
case ".bmp":
return CanvasBitmapFileFormat.Bmp;
case ".tif":
case ".tiff":
return CanvasBitmapFileFormat.Tiff;
case ".gif":
return CanvasBitmapFileFormat.Gif;
default:
return CanvasBitmapFileFormat.Png;
}
}
}
}
12 changes: 4 additions & 8 deletions Source/Demo/WinUI/HtmlRenderer.Demo.WinUI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,8 @@
<RootNamespace>TheArtOfDev.HtmlRenderer.Demo.WinUI</RootNamespace>
<AssemblyName>HtmlRendererWinUIDemo</AssemblyName>
<UseWinUI>true</UseWinUI>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<GenerateResourceUsePreserializedResources>true</GenerateResourceUsePreserializedResources>
<!-- Unpackaged WinUI 3 app: WindowsPackageType=None + a self-contained deploy (both the .NET runtime
and the Windows App SDK runtime), since an unpackaged app has no MSIX identity to resolve a
framework-package dependency through - a genuinely new deployment concern the WPF/WinForms demos
don't have (see the plan's Decisions section). -->
<!-- Unpackaged and self-contained, so the demo runs without installing the .NET and Windows App SDK runtimes -->
<WindowsPackageType>None</WindowsPackageType>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<SelfContained>true</SelfContained>
Expand All @@ -23,8 +19,7 @@
<ApplicationIcon>html.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<!-- ApplicationIcon alone only stamps the .exe resource; MainWindow also loads this file at runtime
to set the window/taskbar icon, which a WinUI 3 window does not take from the exe on its own. -->
<!-- Loaded by the windows, the icon of the executable isn't used for them -->
<Content Include="html.ico" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<Target Name="HideWindowsAppSDKPayloadFiles" AfterTargets="AddMicrosoftWindowsAppSDKPayloadFilesFromComponents">
Expand All @@ -34,12 +29,13 @@
</ItemGroup>
</Target>
<ItemGroup>
<ProjectReference Include="..\..\HtmlRenderer.PdfSharp\HtmlRenderer.PdfSharp.csproj" />
<ProjectReference Include="..\..\HtmlRenderer.WinUI\HtmlRenderer.WinUI.csproj" />
<ProjectReference Include="..\..\HtmlRenderer\HtmlRenderer.csproj" />
<ProjectReference Include="..\Common\HtmlRenderer.Demo.Common.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="2.6.5" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.4.*" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.4.0" />
</ItemGroup>
</Project>
Loading
Loading