Skip to content

Commit

Permalink
Improve OxyPlot.SvgRenderContext text rendering (oxyplot#1531)
Browse files Browse the repository at this point in the history
  • Loading branch information
VisualMelon committed May 11, 2020
1 parent b2ff4f6 commit cbc086d
Show file tree
Hide file tree
Showing 5 changed files with 297 additions and 42 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ All notable changes to this project will be documented in this file.
- Support for unix line endings in OxyPlot.ImageSharp, OxyPlot.Svg, and OxyPlot.Pdf (#1545)
- Multi-Line Text support to SkiaRenderContext (#1538)
- Added title clipping to PlotModel (#1510)
- SvgExporter for OxyPlot.ImageSharp

### Changed
- Legends model (#644)
Expand Down Expand Up @@ -66,6 +67,7 @@ All notable changes to this project will be documented in this file.
- WPF CanvasRenderContext draws ellipses too small by half stroke thickness (#1537)
- Text measurement and rendering in OxyPlot.ImageSharp
- ExampleLibrary reporting annotation-only PlotModels as transposable (#1544)
- Vertical text alignment in OxyPlot.SvgRenderContext (#1531)

## [2.0.0] - 2019-10-19
### Added
Expand Down
189 changes: 189 additions & 0 deletions Source/OxyPlot.ImageSharp.Tests/SvgExporterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SvgExporterTests.cs" company="OxyPlot">
// Copyright (c) 2020 OxyPlot contributors
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace OxyPlot.ImageSharp.Tests
{
using System;
using System.IO;
using NUnit.Framework;

using OxyPlot.Series;
using OxyPlot.ImageSharp;
using OxyPlot.Annotations;

[TestFixture]
public class SvgExporterTests
{
private const string SVG_FOLDER = "SVG";
private string outputDirectory;

[OneTimeSetUp]
public void Setup()
{
this.outputDirectory = Path.Combine(TestContext.CurrentContext.WorkDirectory, SVG_FOLDER);
Directory.CreateDirectory(this.outputDirectory);
}

[Test]
public void Export_SomeExamplesInExampleLibrary_CheckThatAllFilesExist()
{
var exporter = new SvgExporter(1000, 750);
var directory = Path.Combine(this.outputDirectory, "ExampleLibrary");
ExportTest.Export_FirstExampleOfEachExampleGroup_CheckThatAllFilesExist(exporter, directory, ".svg");
}

[Test]
public void ExportToStream()
{
var plotModel = CreateTestModel1();
var exporter = new SvgExporter(1000, 750);
var stream = new MemoryStream();
exporter.Export(plotModel, stream);

Assert.IsTrue(stream.Length > 0);
}

[Test]
public void ExportToFile()
{
var plotModel = CreateTestModel1();
var fileName = Path.Combine(this.outputDirectory, "Plot1.svg");
SvgExporter.Export(plotModel, fileName, 1000, 750);

Assert.IsTrue(File.Exists(fileName));
}

[Test]
public void ExportWithDifferentBackground()
{
var plotModel = CreateTestModel1();
plotModel.Background = OxyColors.Yellow;
var fileName = Path.Combine(this.outputDirectory, "Background_Yellow.svg");
var exporter = new SvgExporter(1000, 750);
using (var stream = File.OpenWrite(fileName))
{
exporter.Export(plotModel, stream);
}

Assert.IsTrue(File.Exists(fileName));
}

[Test]
[TestCase(0.75)]
[TestCase(1)]
[TestCase(1.2)]
[TestCase(2)]
[TestCase(3.1415)]
public void ExportWithResolution(double factor)
{
var resolution = (int)(96 * factor);
var plotModel = CreateTestModel1();
var directory = Path.Combine(this.outputDirectory, "Resolution");
Directory.CreateDirectory(directory);

var fileName = Path.Combine(directory, $"Resolution{resolution}.svg");
var exporter = new SvgExporter((int)(400 * factor), (int)(300 * factor), resolution);

using (var stream = File.OpenWrite(fileName))
{
exporter.Export(plotModel, stream);
}

Assert.IsTrue(File.Exists(fileName));
}

[Test]
[TestCase(true)]
[TestCase(false)]
public void PlotBackgroundImageTest(bool interpolate)
{
// this is a test of the DrawImage function; don't add pointless backgrounds to your plots

var plotModel = CreateTestModel1();

var pixelData = new OxyColor[5, 5];
for (int i = 0; i < pixelData.GetLength(0); i++)
{
for (int j = 0; j < pixelData.GetLength(1); j++)
{
pixelData[i, j] = OxyColor.FromArgb(255, 128, (byte)((i * 255) / pixelData.GetLength(0)), (byte)((j * 255) / pixelData.GetLength(1)));
}
}

var oxyImage = OxyImage.Create(pixelData, ImageFormat.Png);
var imageAnnotation = new ImageAnnotation()
{
ImageSource = oxyImage,
X = new PlotLength(-0.0, PlotLengthUnit.RelativeToPlotArea),
Y = new PlotLength(-0.0, PlotLengthUnit.RelativeToPlotArea),
Width = new PlotLength(1.0, PlotLengthUnit.RelativeToPlotArea),
Height = new PlotLength(1.0, PlotLengthUnit.RelativeToPlotArea),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Interpolate = interpolate
};
plotModel.Annotations.Add(imageAnnotation);

var fileName = Path.Combine(this.outputDirectory, $"PlotBackground{(interpolate ? "Interpolated" : "Pixelated")}.svg");
var exporter = new SvgExporter(1000, 750);
using (var stream = File.OpenWrite(fileName))
{
exporter.Export(plotModel, stream);
}

Assert.IsTrue(File.Exists(fileName));
}

[Test]
[TestCase(true)]
[TestCase(false)]
public void LargeImageTest(bool interpolate)
{
// this is a test of the DrawImage function; don't add pointless backgrounds to your plots

var plotModel = CreateTestModel1();

var pixelData = new OxyColor[5, 5];
for (int i = 0; i < pixelData.GetLength(0); i++)
{
for (int j = 0; j < pixelData.GetLength(1); j++)
{
pixelData[i, j] = OxyColor.FromArgb(255, 128, (byte)((i * 255) / pixelData.GetLength(0)), (byte)((j * 255) / pixelData.GetLength(1)));
}
}

var oxyImage = OxyImage.Create(pixelData, ImageFormat.Png);
var imageAnnotation = new ImageAnnotation()
{
ImageSource = oxyImage,
X = new PlotLength(-1, PlotLengthUnit.RelativeToViewport),
Y = new PlotLength(-1, PlotLengthUnit.RelativeToViewport),
Width = new PlotLength(3, PlotLengthUnit.RelativeToViewport),
Height = new PlotLength(3, PlotLengthUnit.RelativeToViewport),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Interpolate = interpolate
};
plotModel.Annotations.Add(imageAnnotation);

var fileName = Path.Combine(this.outputDirectory, $"LargeImage{(interpolate ? "Interpolated" : "Pixelated")}.svg");
var exporter = new SvgExporter(1000, 750);
using (var stream = File.OpenWrite(fileName))
{
exporter.Export(plotModel, stream);
}

Assert.IsTrue(File.Exists(fileName));
}

private static PlotModel CreateTestModel1()
{
var model = new PlotModel { Title = "Test 1" };
model.Series.Add(new FunctionSeries(Math.Sin, 0, Math.PI * 8, 200, "sin(x)"));
return model;
}
}
}
61 changes: 61 additions & 0 deletions Source/OxyPlot.ImageSharp/SvgExporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SvgExporter.cs" company="OxyPlot">
// Copyright (c) 2014 OxyPlot contributors
// </copyright>
// <summary>
// Provides functionality to export plots to scalable vector graphics using <see cref="Graphics" /> for text measuring.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace OxyPlot.ImageSharp
{
using System;
using System.IO;

/// <summary>
/// Provides functionality to export plots to scalable vector graphics using ImageSharp for text measuring.
/// </summary>
public class SvgExporter : OxyPlot.SvgExporter, IDisposable
{
/// <summary>
/// The render context.
/// </summary>
private ImageRenderContext irc;

/// <summary>
/// Initializes a new instance of the <see cref="SvgExporter" /> class.
/// </summary>
public SvgExporter(double width, double height, double resolution = 96)
{
this.Width = width;
this.Height = height;
this.irc = new ImageRenderContext(1, 1, OxyColors.Undefined, resolution);
this.TextMeasurer = irc;
}

/// <summary>
/// Exports the specified <see cref="PlotModel" /> to the specified file.
/// </summary>
/// <param name="model">The model.</param>
/// <param name="fileName">The file name.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="resolution">The resolution in dpi (defaults to 96dpi).</param>
public static void Export(IPlotModel model, string fileName, int width, int height, double resolution = 96)
{
var exporter = new SvgExporter(width, height, resolution);
using (var stream = File.Create(fileName))
{
exporter.Export(model, stream);
}
}

/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
this.irc.Dispose();
}
}
}
9 changes: 5 additions & 4 deletions Source/OxyPlot/Svg/SvgExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public SvgExporter()
this.Width = 600;
this.Height = 400;
this.IsDocument = true;
this.UseVerticalTextAlignmentWorkaround = true;
}

/// <summary>
Expand Down Expand Up @@ -60,8 +61,8 @@ public SvgExporter()
/// <param name="height">The height (points).</param>
/// <param name="isDocument">if set to <c>true</c>, the xml headers will be included (?xml and !DOCTYPE).</param>
/// <param name="textMeasurer">The text measurer.</param>
/// <param name="useVerticalTextAlignmentWorkaround">Whether to use the workaround for vertical text alignment</param>
public static void Export(IPlotModel model, Stream stream, double width, double height, bool isDocument, IRenderContext textMeasurer = null, bool useVerticalTextAlignmentWorkaround = false)
/// <param name="useVerticalTextAlignmentWorkaround">Whether to use the workaround for vertical text alignment.</param>
public static void Export(IPlotModel model, Stream stream, double width, double height, bool isDocument, IRenderContext textMeasurer = null, bool useVerticalTextAlignmentWorkaround = true)
{
if (textMeasurer == null)
{
Expand All @@ -86,8 +87,8 @@ public static void Export(IPlotModel model, Stream stream, double width, double
/// <param name="isDocument">if set to <c>true</c>, the xml headers will be included (?xml and !DOCTYPE).</param>
/// <param name="textMeasurer">The text measurer.</param>
/// <returns>The plot as an <c>SVG</c> string.</returns>
/// <param name="useVerticalTextAlignmentWorkaround">Whether to use the workaround for vertical text alignment</param>
public static string ExportToString(IPlotModel model, double width, double height, bool isDocument, IRenderContext textMeasurer = null, bool useVerticalTextAlignmentWorkaround = false)
/// <param name="useVerticalTextAlignmentWorkaround">Whether to use the workaround for vertical text alignment.</param>
public static string ExportToString(IPlotModel model, double width, double height, bool isDocument, IRenderContext textMeasurer = null, bool useVerticalTextAlignmentWorkaround = true)
{
string svg;
using (var ms = new MemoryStream())
Expand Down

0 comments on commit cbc086d

Please sign in to comment.