Skip to content

Commit f107d80

Browse files
authored
Merge pull request #9 from albyoo/features/net60
Add image generator
2 parents c5b00f4 + 3346803 commit f107d80

5 files changed

Lines changed: 205 additions & 7 deletions

File tree

Source/Demos/HtmlRenderer.Demo.Common/SampleConverterBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ public SampleConverterFileBase(string sampleRunIdentifier, string basePath) : ba
5555
_thisTypeName = this.GetType().Name;
5656
}
5757

58-
protected string GetSamplePath(HtmlSample sample)
58+
protected string GetSamplePath(HtmlSample sample, string extension = ".pdf")
5959
{
6060
var path = Path.Combine(_basePath, _sampleRunIdentifier);
6161
Directory.CreateDirectory(path);
62-
return Path.Combine(path, sample.FullName + _thisTypeName + "_" + ".pdf");
62+
return Path.Combine(path, sample.FullName + _thisTypeName + "_" + extension);
6363
}
6464
}
6565
}

Source/Demos/HtmlRenderer.Demo.Console/Program.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
//Probably won't be running a suite of tests more than once a second, so this will do.
1414
var runIdentifier = DateTime.Now.ToString("yyyyMMdd-hhmmss");
1515

16-
var skia = new SkiaConverter(runIdentifier, basePath);
16+
var skia = new SkiaPdfConverter(runIdentifier, basePath);
17+
var svgSkia = new SkiaSvgConverter(runIdentifier, basePath);
1718
var pdfSharp = new PdfSharpCoreConverter(runIdentifier, basePath);
1819

1920
SamplesLoader.Init("Console", typeof(Program).Assembly.GetName().Version.ToString());
@@ -23,10 +24,11 @@
2324
foreach (var htmlSample in samples)
2425
{
2526
////Just doing one test here. Comment this for all of them.
26-
//if (!htmlSample.FullName.Contains("16", StringComparison.OrdinalIgnoreCase)) continue;
27+
if (!htmlSample.FullName.Contains("16", StringComparison.OrdinalIgnoreCase)) continue;
2728

2829
await skia.GenerateSampleAsync(htmlSample);
29-
await pdfSharp.GenerateSampleAsync(htmlSample);
30+
await svgSkia.GenerateSampleAsync(htmlSample);
31+
//await pdfSharp.GenerateSampleAsync(htmlSample);
3032
}
3133

3234

Source/Demos/HtmlRenderer.Demo.Console/SkiaConverter.cs renamed to Source/Demos/HtmlRenderer.Demo.Console/SkiaPdfConverter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99

1010
namespace HtmlRenderer.Demo.Console
1111
{
12-
public class SkiaConverter : SampleConverterFileBase
12+
public class SkiaPdfConverter : SampleConverterFileBase
1313
{
14-
public SkiaConverter(string sampleRunIdentifier, string basePath) : base(sampleRunIdentifier, basePath)
14+
public SkiaPdfConverter(string sampleRunIdentifier, string basePath) : base(sampleRunIdentifier, basePath)
1515
{
1616
}
1717

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using HtmlRenderer.Demo.Common;
2+
using SkiaSharp;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
using TheArtOfDev.HtmlRenderer.Demo.Common;
9+
using TheArtOfDev.HtmlRenderer.SkiaSharp;
10+
11+
namespace HtmlRenderer.Demo.Console
12+
{
13+
public class SkiaSvgConverter : SampleConverterFileBase
14+
{
15+
public SkiaSvgConverter(string sampleRunIdentifier, string basePath) : base(sampleRunIdentifier, basePath)
16+
{
17+
}
18+
19+
public async Task GenerateSampleAsync(HtmlSample sample)
20+
{
21+
var size = new SKSize(500, 1000);
22+
23+
using (var fileStream = File.Open(GetSamplePath(sample, ".svg"), FileMode.CreateNew))
24+
{
25+
await ImageGenerator.GenerateSvgAsync(sample.Html, fileStream, size, imageLoad: OnImageLoaded);
26+
fileStream.Flush();
27+
}
28+
29+
using (var fileStream = File.Open(GetSamplePath(sample, ".png"), FileMode.CreateNew))
30+
{
31+
await ImageGenerator.GenerateBitmapAsync(sample.Html, fileStream, size, SKEncodedImageFormat.Png, 100, imageLoad: OnImageLoaded);
32+
fileStream.Flush();
33+
}
34+
}
35+
}
36+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// "Therefore those skilled at the unorthodox
2+
// are infinite as heaven and earth,
3+
// inexhaustible as the great rivers.
4+
// When they come to an end,
5+
// they begin again,
6+
// like the days and months;
7+
// they die and are reborn,
8+
// like the four seasons."
9+
//
10+
// - Sun Tsu,
11+
// "The Art of War"
12+
13+
using HtmlRenderer.Core.Dom;
14+
using SkiaSharp;
15+
using Svg.Skia;
16+
using System;
17+
using TheArtOfDev.HtmlRenderer.Core;
18+
using TheArtOfDev.HtmlRenderer.Core.Entities;
19+
using TheArtOfDev.HtmlRenderer.Core.Utils;
20+
using TheArtOfDev.HtmlRenderer.SkiaSharp.Adapters;
21+
using TheArtOfDev.HtmlRenderer.SkiaSharp.Utilities;
22+
23+
namespace TheArtOfDev.HtmlRenderer.SkiaSharp
24+
{
25+
/// <summary>
26+
/// TODO:a add doc
27+
/// </summary>
28+
public static class ImageGenerator
29+
{
30+
/// <summary>
31+
/// Adds a font mapping from <paramref name="fromFamily"/> to <paramref name="toFamily"/> iff the <paramref name="fromFamily"/> is not found.<br/>
32+
/// When the <paramref name="fromFamily"/> font is used in rendered html and is not found in existing
33+
/// fonts (installed or added) it will be replaced by <paramref name="toFamily"/>.<br/>
34+
/// </summary>
35+
/// <remarks>
36+
/// This fonts mapping can be used as a fallback in case the requested font is not installed in the client system.
37+
/// </remarks>
38+
/// <param name="fromFamily">the font family to replace</param>
39+
/// <param name="toFamily">the font family to replace with</param>
40+
public static void AddFontFamilyMapping(string fromFamily, string toFamily)
41+
{
42+
ArgChecker.AssertArgNotNullOrEmpty(fromFamily, "fromFamily");
43+
ArgChecker.AssertArgNotNullOrEmpty(toFamily, "toFamily");
44+
45+
SkiaSharpAdapter.Instance.AddFontFamilyMapping(fromFamily, toFamily);
46+
}
47+
48+
/// <summary>
49+
/// Parse the given stylesheet to <see cref="CssData"/> object.<br/>
50+
/// If <paramref name="combineWithDefault"/> is true the parsed css blocks are added to the
51+
/// default css data (as defined by W3), merged if class name already exists. If false only the data in the given stylesheet is returned.
52+
/// </summary>
53+
/// <seealso cref="http://www.w3.org/TR/CSS21/sample.html"/>
54+
/// <param name="stylesheet">the stylesheet source to parse</param>
55+
/// <param name="combineWithDefault">true - combine the parsed css data with default css data, false - return only the parsed css data</param>
56+
/// <returns>the parsed css data</returns>
57+
public static CssData ParseStyleSheet(string stylesheet, bool combineWithDefault = true)
58+
{
59+
return CssData.Parse(SkiaSharpAdapter.Instance, stylesheet, combineWithDefault);
60+
}
61+
62+
/// <summary>
63+
/// Create Svg document from given HTML.<br/>
64+
/// </summary>
65+
/// <param name="html">HTML source to create image from</param>
66+
/// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param>
67+
/// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param>
68+
/// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param>
69+
/// <returns>the generated image of the html</returns>
70+
public static async Task<SKCanvas> GenerateSvgAsync(
71+
string html,
72+
Stream outputStream,
73+
SKSize size,
74+
CssData cssData = null,
75+
EventHandler<HtmlStylesheetLoadEventArgs> stylesheetLoad = null,
76+
EventHandler<HtmlImageLoadEventArgs> imageLoad = null)
77+
{
78+
// create svg document to render the HTML into
79+
var canvas = SKSvgCanvas.Create(new SKRect(0, 0, size.Width, size.Height), outputStream);
80+
81+
// add rendered image
82+
await DrawSvgAsync(canvas, html, size, cssData, stylesheetLoad, imageLoad);
83+
canvas.Dispose();
84+
85+
return canvas;
86+
}
87+
88+
/// <summary>
89+
/// Writes html to a bitmap image
90+
/// </summary>
91+
/// <param name="html">HTML source to create image from</param>
92+
/// <param name="size">The size of the image</param>
93+
/// <param name="imageFormat">The file format used to encode the image.</param>
94+
/// <param name="quality">The quality level to use for the image. Quality range from 0-100. Higher values correspond to improved visual quality, but less compression.</param>
95+
/// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param>
96+
/// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param>
97+
/// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param>
98+
/// <returns></returns>
99+
public static async Task<SKCanvas> GenerateBitmapAsync(
100+
string html,
101+
Stream outputStream,
102+
SKSize size,
103+
SKEncodedImageFormat imageFormat,
104+
int quality,
105+
CssData cssData = null,
106+
EventHandler<HtmlStylesheetLoadEventArgs> stylesheetLoad = null,
107+
EventHandler<HtmlImageLoadEventArgs> imageLoad = null)
108+
{
109+
110+
var bitmap = new SKBitmap((int)size.Width, (int)size.Height);
111+
var canvas = new SKCanvas(bitmap);
112+
113+
// add rendered image
114+
await DrawSvgAsync(canvas, html, size, cssData, stylesheetLoad, imageLoad);
115+
bitmap.Encode(outputStream, imageFormat, quality);
116+
117+
return canvas;
118+
}
119+
120+
/// <summary>
121+
/// Create image pages from given HTML and appends them to the provided image document.<br/>
122+
/// </summary>
123+
/// <param name="canvas">canvas to draw to</param>
124+
/// <param name="html">HTML source to create image from</param>
125+
/// <param name="cssData">optional: the style to use for html rendering (default - use W3 default style)</param>
126+
/// <param name="stylesheetLoad">optional: can be used to overwrite stylesheet resolution logic</param>
127+
/// <param name="imageLoad">optional: can be used to overwrite image resolution logic</param>
128+
/// <returns>the generated image of the html</returns>
129+
public static async Task DrawSvgAsync(
130+
SKCanvas canvas,
131+
string html,
132+
SKSize size,
133+
CssData cssData = null,
134+
EventHandler<HtmlStylesheetLoadEventArgs> stylesheetLoad = null,
135+
EventHandler<HtmlImageLoadEventArgs> imageLoad = null)
136+
{
137+
using var container = new HtmlContainer();
138+
if (stylesheetLoad != null)
139+
container.StylesheetLoad += stylesheetLoad;
140+
if (imageLoad != null)
141+
container.ImageLoad += imageLoad;
142+
143+
container.Location = new SKPoint(0, 0);
144+
//container.MaxSize = size;
145+
container.MaxSize = new SKSize(size.Width, 0);
146+
container.PageSize = size;
147+
container.MarginBottom = 0;
148+
container.MarginLeft = 0;
149+
container.MarginRight = 0;
150+
container.MarginTop = 0;
151+
container.ScrollOffset = new SKPoint(0, 0);
152+
153+
await container.SetHtml(html, cssData);
154+
155+
// layout the HTML with the page width restriction to know how many pages are required
156+
await container.PerformLayout(canvas);
157+
await container.PerformPaint(canvas);
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)