A lot of things has changed in the past year and it seems more is going to change in the next, unfortunately those changes are not aligned with the work required for HTML Renderer. That's why it took so long for v1.5 release to finally be ready and the reason not all features I originally planned made it.
With a heavy heart I'm going to hold all significant work on HTML Renderer until either my career path brings me back to .NET, I have more free time to work on it or the project will get more attentions. In the meanwhile I will continue to support the library with small fixes and answering question on the discussions/issues pages.
The goal of this version is to break the confinement of WinForms UI framework and allow the HTML Renderer to be used across all .NET supported platforms/frameworks1:
- WinForms – done.
- WPF – done.
- Mono – done.
- PdfSharp – done.
- MigraDoc, iTextSharp or others – couple days work.
- Silverlight – couple of days work.
- Metro – probably no more than a week work.
- Xamarin – hard to say but I think it is possible in 2-3 weeks.
- Unity, XNA – no idea, is it interesting?
The important part is not the frameworks already supported in v1.5 but the extensibility model baked into the core of HTML Renderer that allows reusing 90% of the code when adding support to more frameworks without effecting the already supported frameworks.
This update has a few breaking changes that were required to effectively move forward with the library.
The required adjustments are not significant but it is important to note that existing projects will not compile after upgrading to v1.5.
- Two assembly's instead of one.
- Changing all values to doubles.
- Object model changes to expose universal and not framework specific entities.
- Object model namespaces changes as part of assemblies separation.
Separating Core library
There are a few options available to separate core HTML Renderer code from framework specific code
- Conditional compilation
- Linked reference files
- Separated assemblies
The main difference between going with the first two and the separated assembly approach is simplicity to HTML Renderer developer vs. simplicity to the client developer. I decided that the price to the HTML Renderer developer was much higher than the price for the client developer and didn't want to see HTML Renderer development stall because of it. Separating the library into core assembly and framework specific assemblies allows clear separation between generic and framework specific code, preventing accidental usage of framework specific classes.
The core assembly has minimal dependencies, currently it's only dependency is "System" and the goal is to make it universal portable class library allowing it to run on frameworks like Windows Phone, Xamarin, Unity, etc. This will restrict the core even more than the current implementation and will require extending the adapters implementation to cover more parts of the library like resources (stylesheets/images) downloading and caching.
Adapters extensibility model
The create the cross-framework core library I had to remove all framework specific references and to replace them with custom classes. Those can be separated into two categories: basic entities and functional objects.
Basic entities, like Point, Size, Color, Mouse event, etc. do not have any functionality or complex state, therefore can be replaced by matching internal entities used in the core library, appropriately named RPoint, Rsize, Rcolor, RMouseEvent, etc. it is left to the specific framework libraries to convert between the external and internal entities so library client will not be aware of the internal, framework agnostic, entities.
Function objects, like Font, Image, Brush, Control, Graphics, etc. expose functionality themselves or concrete, framework specific, instance are required for other functional object to do its work. Therefore, framework specific functional objects are wrapped in framework agnostic adapters that the core library can use to execute functionality (measuring string width, font size, showing context menu, etc.) and the adapter class delegates it to the proper framework specific execution code.
Currently there are 10 adapters:
The main adapter, provide the global access point to framework specific functionality and caching. It doesn't wrap any framework specific functional object, passed in the constructor of the HtmlContainer it is available during all stages of html parsing, layout and rendering. Provides access to colors, brushes, fonts, images, clipboard and files functionality.
The rendering adapter, wraps framework specific graphics object (Graphics in WinForms, DrawingContext in WPF). Provides draw functionality to draw shapes and text.
UI control element adapter, wraps framework specific control object. Provide active UI elemetn functionality for the ability to handle keyboard, mouse, refresh, re-layout, set cursor, etc.
The rest of the adapters: RBrush, RPen, RImage, RFont, RFontFamily, RGraphicsPath and RContextMenu are simpler and less interesting, mostly wrap framework specific object to either delegate action to it or provide it when required in one of the core adapters above.
Unbelievably heavy, complex and unfriendly UI framework! I had numerous issues with clipping, blurry text rendering, fonts (typefaces), blurry line rendering and pixel snapping in general. I'm not sure that I handled those issues correctly or even if proper solution even exists, extremely frustrating experience all-in-all.
With the rant out of the way, I did managed to work out most of the issues to provide fully supported native WPF rendering library.
The obvious way to render text programmatically in WPF is to use FormattedText class that provides rich API for layout, alignment, fonts, etc. unfortunately it is not designed for rendering large amount of small strings, the performance is just unacceptable.
I have used a more low-level approach using GlyphRun class, that is used under the hood by the FormattedText class without many of the nice features it provides. But low-level has its price, I had to use the lower level GlyphTypeface class and handle scenarios where it is not available, manually map characters to glyph indexes and, again, handle scenario where simple mapping is not available or invalid, manually calculate each glyph width for text rectangle measurement, and I'm pretty sure this approach losses support for text kerning, fortunately it doesn't seems to be very significant visually.
The end result though is decent in my opinion, the time performance is roughly the same as native GDI text rendering and almost as sharp as it.
From all the annoyances of WPF this is by far the most frustrating. The core issue is straightforward, rendering 2.5 pixel width line or text at location (4.75, 2.2) won't work well on monitors as there is no way to light half pixel. The obvious solution is to round to the nearest pixel, unfortunately WPF, as GDI+, really wants to be device independent, which means that rendered object size and location won't change between devices with different resolution and pixel densities like monitor and printer. The result: unbelievably fuzzy/blurry rendering!
WPF tried to solve this issue by providing flags to favor sharp rendering over device independent rendering using SnapsToDevicePixels, UseLayoutRounding and TextFormattingMode unfortunately they either to high-level, not compatible to 3.0, 3.5 frameworks or just don't work as expected.
In the end I did multiple layers of hacks and pixel rounding to provide as sharp rendering as possible.
Execution time performance is in the same area of WinForms: average less than 10 millisecond for sample HTML rendering2, though you can see difference on specific hardware.
Memory, on the other hand, is an issue. Because everything in WPF requires multiple object allocation without any (as far as I know) way to reuse or recycle, the average sample HTML rendering memory allocation is x4 times that of WinForms. The higher memory usage may effect performance via higher GC pressure though I didn't notice any in my tests.
The nice thing about WPF is its ability to compose UI elements, so WPF Tooltip can embed HtmlLabel in it creating the same tooltip control that required special work in WinForms. For that reason I didn't see significant benefits in creating dedicated HtmlTooltip control, the usage example of HtmlLabel as content of WPF tooltip can be seen in WPF demo project.
Basically, Mono was always supported by HTML Renderer as it uses standard .NET code. It changed when I provided the ability to use GDI text rendering via P/Invoke and made it the default setting, HTML Renderer would not work out-of-the-box as the developer needed to manually to change the rendering method to GDI+.
To make things simpler I added conditional compilation that omits GDI text rendering when building with MONO flag, this way referencing mono targeted library will work out-of-the-box and there is no way to use unsupported code.
I have used PdfSharp library that is based on WinForms framework to simplify the work and provide the currently best rendering framework. As PdfSharp graphics closely match that of WinForms it was relatively straightforward to create the library, fortunately PDF document generation doesn't require any controls.
Major missing feature, one of the features I wish I had more time to work on, is paging support. Without it, it is possible for a line of text to be split between two pages, half at the end of the page and the other half at the begging of the next page.
I have decided to leave it as is for now, possibly for the community to fix or if the library will have usages I will invest the time in the future.
There are 4 new NuGet packages added to the family:
All packages, including the previously existing HtmlRenderer.WinForms, depends on platform independent HtmlRenderer.Core package. That, also, can be referenced independently to add custom platform support.
For 1.4 I used MSBuild XML script that proved to be hard to write and maintain, moving to 1.5 it was even more complex as I needed to handle multiple frameworks, demo applications and NuGet packages.
So for 1.5 I decided to go with batch file that executes MSBuild exe to build each framework release for each supported framework, that's more than 20 builds in the matrix!
After the builds the batch scripts continues to create NuGet packages, download the source code from GitHub and then ZIPs everything for CodePlex download page.
The batch file approach proved, so far, to be simpler and more straightforward than MSBuild script though it sure feels like a hack. I'm not a fan of playing with build stuff so if anyone knows of a better solution please come forward.
As with 1.4 I want to provide assembly for each supported framework and each version of .NET, including support for client profile for .NET 3.5 and 4.0. And as with 1.4 I don't want to support multiple project files as it adds overhead to handling the source files for each project separately.
My solution to this issue was to use single project file and specify the targeted framework in MSBuild command, this approach works perfectly for all projects except WPF as .NET 4.0 added "System.Xaml" reference, I solved it by making the reference conditional in the ".csproj" file:
<Reference Include="System.Xaml" Condition=" '$(TargetFrameworkVersion)' == 'v4.0' Or '$(TargetFrameworkVersion)' == 'v4.5'" />
Thought it created a problem when changing the targeted framework to 4.0 and then back to 3.0, so if this happens the conditional reference needs to be added back manually.
With Mono I didn't want to duplicate the code between WinForms and Mono frameworks, so I used conditional compilations removing the features not supported by Mono framework. The demo application, on the other hand, checks at runtime if it runs on Mono to disable unsupported features.
I wanted the demo applications, WinForms and WPF, to remain a single executable despite the fact I created a common assembly for demo and the 3 assemblies required for HTML Renderer (Core, WinForms, WPF).
For 1.4 I have used ILMerge to combine referenced assembly into the demo executable, unfortunately it doesn't work well for WPF and has other issues.
Therefore I wanted to use the technique described here but have it done automatically so I won't have to add the assemblies manually each time. Fortunately I found this absolutely awesome solution to embed the reference assemblies automatically via the ".csproj" file build script.
All in all I have a relatively simple build script that work most of the time creating everything I need to release a new version on CodePlex and NuGet.
For the long version see my blog post.
- Multi-framework support
- Native – no WinForms interop or hacks
- Native WPF controls (HtmlPanel, HtmlLabel)
- Feature parity with WinForms
- Dependency property and routed events
- Excellent performance
- Subset to WinForms HtmlRenderer
- GdiPlus text rendering only
- Text selection
- Links and anchors
- Extensibility support for other frameworks using Core library
- Metro (Win8/Windows RT/Windows Phone)
- Xamarin (iOS/Android)
- Split library into core assembly and additional assembly for each framework
- Core – dependency only on System
- Object model minor changes and namespace changes
- Improve WinForms demo to show image and PDF generation
- WinForms demo build that work on Mono
- Full WPF demo application
- Change build script to batch file
- Added clear current selection method
- Added LoadComplete event on when set html if fully parsed and layouted
- Fix table center/right alignment
- Fix sub, sup style text handling.
- Fix possible infinite loop in CorrectRelativeUrls
- Improve image downloading handling
- Fix collision of downloading the same image
- Don't cache failed or partial image downloads responses
- Synchronization issues
- Padding support on HtmlLabel and HtmlPanel controls
- URL attributes with double quotes are not processed correctly (thx Richard)
- Fix html clearing not resetting ActualSize
- Height calculation trimming last pixel
- Fix actual width calculation when location.X is set
- Fix WinForms horizonal scroll content clipping
- Fix high CPU usage while holding mouse down (thx Ryan)
- Handle possibility of no requested style exists for the font, use regular then
- Fix table cell width calculation too small when using multiple blocks with whitespaces
- Fix styles parsing for html and * wildcard
- Removed default body style (10pt Tahoma)
- I'm not sure the proper terminology to use to distinguish WinForms, WPF, Mono, PdfSharp, Metro and Xamarin as not all can be described as different platforms, I decided to use the more neutral term framework. ↩
- Machine dependent of course, but the results matched on multiple modern machines running different windows versions. ↩
First, thanks for this awesome project.
I have used your “HtmlRenderer.WinForms” in my web application (using AJAX to render the HTML at the server and return the URL to the client).
However, the number of users who simultaneously utilise the service still between 3 and 7 users.
My question: do you believe that the performance of your tool will NOT be affected if more than 100 users simultaneously utilise it?
Many thanks again.
Depends on the complexity of the HTML, I believe 100 concurrent users is a small number that HTML Renderer will easily handle.
I would do some basic stress testing though.
I use a very simple HTML. I will try to update this comment right after my web site reach a 500 concurrent user.
All the best.
You may want to see this discussion, he ran 10,000 user test: https://htmlrenderer.codeplex.com/discussions/569613
Thanks again for all your effort with this project. I use HTMLRenderer for the Edit View (not Explore View) in FormulaDesk ( http://www.formuladesk.com ). The Edit View is a fully interactive and dynamic formula editor, and only HTMLRenderer enables this approach, due to its very high performance. Thanks!
Just low volume user (one page, display only, soon to be two pages, display only) but this component has made my dev life easier! I’m working on a tool for my daughter’s fencing club, using the HTML Renderer to show a remote display of tournament results. The kids love it. I know HTML and haven’t taken the WPF or Windows Graphics plunge, so this has been a big help – showing tabular data in an HTML table, with CSS styling, excellent! Thanks for your hard work with this, much appreciated.
fantastic project actually. .Net should adopt it as the standard webbrowser component eventually!
I stumbled upon your HTML renderer searching for a native WPF control to display HTML formatted emails.
I am part of a group that is developing a unique email application with integrated peer to peer encryption based on several papers published by different universities. Due to its security critical nature we want to embed as little closed source code as possible. The WebBrowser/Awesomium renderer we are currently using to display HTML mails is the last component which is not open sourced.
We have a test account containing 26000 random emails/newsletters and tried to view several of those using the HTML-Renderer 188.8.131.52. It should come to no surprise that most newsletters are horribly malformed and thus wouldn’t render properly or render at all, some even crashed the application.
Since it requires a huge amount of work to exchange the current html stack from our application with a new one, I would like to ask for your assistance. We have a couple of very special newsletters, they are considered to be the worst case scenario. If a component can somehow render these more or less correctly, it can render everything.
You would do us a great favor if it were possible for you to simply evaluate how much work it would take to make them fly.
Should you agree, I’d be happy to send you a couple of those newsletters.
Sorry for the late reply,
Unfortunately I’m unable to commit resources for this project at this time or the near future so I won’t be able to help.
But it is my hope to return to it one day, it would be great to have the HTML samples to for future work.
Can you provide them?
Sorry to flatter you but must say, awesome project! Putting the control in Syncfusion grid cells dynamically was quite easy, you did all the grunt work. Had the Getmetrics/unicode problem when went from 32 to 64 bit but all had to do was get latest source and compile, all good from there. Keep up the good work!
I apologize for my terrible English. I am Brazilian and not write English very well.
Thanks for all the effort put in the development of this fantastic tool! This tool meets all my needs.
Could I contribute with maintenance and fixing bugs?
I found a bug in the construction of the layout of the tables by using colspan in the first row of the table. I made a correction in “GetColumnsMinMaxWidthByContent” method that allows colspan function normally.
Rafael Camillis tairum
Glad you found the project useful,
You can contribute to the project on GitHub: https://github.com/ArthurHub/HTML-Renderer
I try to merge all pull requests I receive.