Skip to content

Enable CairoSharp to use native-cairo capabilities to write SVG output to streams #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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
85 changes: 79 additions & 6 deletions demos/Windows/WinFormDemo/Form1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Windows.Forms;

namespace WinFormDemo
Expand All @@ -12,14 +13,23 @@ public partial class Form1 : Form
private ToolStripMenuItem staticToolStripMenuItem;
private Timer timer1;
private IContainer components;
private ToolStripMenuItem tosvgstreamToolStripMenuItem;
private ToolStripMenuItem animatedToolStripMenuItem;

public Form1()
{
InitializeComponent();
}

bool displayStaticExample = true;
private enum example_t
{
static_example = 0,
animated_example = 1,
to_svg_stream_example = 2
}

private example_t example_type = example_t.static_example;


protected override void OnPaintBackground(PaintEventArgs e)
{
Expand All @@ -29,14 +39,61 @@ protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);

if (displayStaticExample)
if (example_type == example_t.static_example)
{
DrawStatic(e);
}
else
else if (example_type == example_t.animated_example)
{
DrawAnimated(e);
}
else if (example_type == example_t.to_svg_stream_example)
{
DrawStaticAndToOutputStream();
}
}

private void DrawStaticAndToOutputStream()
{
MemoryStream outpStream = new MemoryStream();
// create a SvgSurface, which output is bound to a stream.
var svgStreamSurface = SvgSurface.CreateForStream(outpStream, 1024, 1024);
using (Context context = new Context(svgStreamSurface))
{
//clear the background to white
context.SetSourceRGB(1, 1, 1);
context.Paint();

//stroke the bug
context.LineWidth = 2.0;
context.SetSourceColor(this.bugColor);
context.MoveTo(7.0, 64.0);
context.CurveTo(1.0, 47.0, 2.0, 46.0, 9.0, 51.0);
context.MoveTo(25.0, 80.0);
context.CurveTo(10.0, 73.0, 11.0, 70.0, 14.0, 63.0);
context.MoveTo(10.0, 41.0);
context.CurveTo(2.0, 36.0, 1.0, 33.0, 1.0, 26.0);
context.LineWidth = 1.0;
context.MoveTo(1.0, 26.0);
context.CurveTo(5.0, 23.0, 7.0, 18.0, 12.0, 17.0);
context.LineTo(12.0, 14.0);
context.Stroke();
context.MoveTo(30.0, 74.0);
context.CurveTo(14.0, 64.0, 10.0, 48.0, 11.0, 46.0);
context.LineTo(10.0, 45.0);
context.LineTo(10.0, 40.0);
context.CurveTo(13.0, 37.0, 15.0, 35.0, 19.0, 34.0);
context.Stroke();
}
// .Finish() is mandatory. Without this, cairo does not write anything to the stream.
svgStreamSurface.Finish();

outpStream.Seek(0, SeekOrigin.Begin);
StreamReader reader = new StreamReader(outpStream);

Console.WriteLine(reader.ReadToEnd());

timer1.Enabled = false;
}

private Cairo.Color bugColor = new Cairo.Color(0.95294117647058818, 0.6, 0.0784313725490196, 1.0);
Expand Down Expand Up @@ -119,14 +176,16 @@ private void InitializeComponent()
this.staticToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.animatedToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.timer1 = new System.Windows.Forms.Timer(this.components);
this.tosvgstreamToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.menuStrip1.SuspendLayout();
this.SuspendLayout();
//
// menuStrip1
//
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.staticToolStripMenuItem,
this.animatedToolStripMenuItem});
this.animatedToolStripMenuItem,
this.tosvgstreamToolStripMenuItem});
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
this.menuStrip1.Name = "menuStrip1";
this.menuStrip1.Size = new System.Drawing.Size(284, 24);
Expand All @@ -152,6 +211,13 @@ private void InitializeComponent()
this.timer1.Interval = 30;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
//
// tosvgstreamToolStripMenuItem
//
this.tosvgstreamToolStripMenuItem.Name = "tosvgstreamToolStripMenuItem";
this.tosvgstreamToolStripMenuItem.Size = new System.Drawing.Size(84, 20);
this.tosvgstreamToolStripMenuItem.Text = "tosvgstream";
this.tosvgstreamToolStripMenuItem.Click += new System.EventHandler(this.tosvgstreamToolStripMenuItem_Click_1);
//
// Form1
//
this.ClientSize = new System.Drawing.Size(284, 261);
Expand All @@ -167,14 +233,14 @@ private void InitializeComponent()

private void staticToolStripMenuItem_Click(object sender, EventArgs e)
{
displayStaticExample = true;
example_type = example_t.static_example;
timer1.Enabled = false;
this.Invalidate();
}

private void animatedToolStripMenuItem_Click(object sender, EventArgs e)
{
displayStaticExample = false;
example_type = example_t.animated_example;
timer1.Enabled = true;
this.Invalidate();
}
Expand All @@ -183,5 +249,12 @@ private void timer1_Tick(object sender, EventArgs e)
{
this.Invalidate();
}

private void tosvgstreamToolStripMenuItem_Click_1(object sender, EventArgs e)
{
example_type = example_t.to_svg_stream_example;
timer1.Enabled = true;
this.Invalidate();
}
}
}
37 changes: 24 additions & 13 deletions source/CairoSharp/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -350,26 +350,40 @@ static NativeMethods()
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate Status cairo_read_func_t(IntPtr closure, IntPtr data, int length);

/// <summary>

/// <summary>
/// cairo_write_func_t is the type of function which is called when a backend needs to write data to an output stream.
/// </summary>
/// <param name="closure">the output closure</param>
/// <param name="data">data to write to the stream</param>
/// <param name="length">length of data, which should be written to stream</param>
/// <returns></returns>
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate Status cairo_write_func_t(IntPtr closure, IntPtr data, int length);

/// <summary>
/// Creates a new image surface from PNG data read incrementally via the read_func function.
/// </summary>
/// </summary>
/// <param name="read_func">function called to read the data of the file</param>
/// <param name="closure">data to pass to read_func</param>
/// <returns>new cairo_surface_t or nil</returns>
/// <remarks>
/// <code>
/// cairo_surface_t * cairo_image_surface_create_from_png_stream(
/// cairo_read_func_t read_func,
/// cairo_surface_t * cairo_image_surface_create_from_png_stream(
/// cairo_read_func_t read_func,
/// void *closure);
/// </code>
/// a new cairo_surface_t initialized with the contents of the PNG file or a "nil" surface if the data read is not a valid PNG image or memory could not be allocated for the operation. A nil surface can be checked for with cairo_surface_status(surface) which may return one of the following values:
/// CAIRO_STATUS_NO_MEMORY CAIRO_STATUS_READ_ERROR
/// Alternatively, you can allow errors to propagate through the drawing operations and check the status on the context upon completion using cairo_status().
/// </remarks>
[DllImport (cairo, CallingConvention=CallingConvention.Cdecl)]
/// a new cairo_surface_t initialized with the contents of the PNG file or a "nil" surface if the data read is not a valid PNG image or memory could not be allocated for the operation. A nil surface can be checked for with cairo_surface_status(surface) which may return one of the following values:
/// CAIRO_STATUS_NO_MEMORY CAIRO_STATUS_READ_ERROR
/// Alternatively, you can allow errors to propagate through the drawing operations and check the status on the context upon completion using cairo_status().
/// </remarks>
[DllImport (cairo, CallingConvention=CallingConvention.Cdecl)]
internal static extern IntPtr cairo_image_surface_create_from_png_stream ([MarshalAs(UnmanagedType.FunctionPtr)]cairo_read_func_t read_func, IntPtr closure);

[DllImport (cairo, CallingConvention=CallingConvention.Cdecl)]
[DllImport(cairo, CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr cairo_svg_surface_create_for_stream([MarshalAs(UnmanagedType.FunctionPtr)]cairo_write_func_t write_func, IntPtr closure, double width_in_points, double height_in_points);

[DllImport (cairo, CallingConvention=CallingConvention.Cdecl)]
internal static extern IntPtr cairo_image_surface_get_data (IntPtr surface);

[DllImport (cairo, CallingConvention=CallingConvention.Cdecl)]
Expand Down Expand Up @@ -962,9 +976,6 @@ static NativeMethods()
[DllImport (cairo, CallingConvention=CallingConvention.Cdecl)]
internal static extern IntPtr cairo_svg_surface_create (string fileName, double width, double height);

//[DllImport (cairo, CallingConvention=CallingConvention.Cdecl)]
//internal static extern IntPtr cairo_svg_surface_create_for_stream (double width, double height);

[DllImport (cairo, CallingConvention=CallingConvention.Cdecl)]
internal static extern IntPtr cairo_svg_surface_restrict_to_version (IntPtr surface, SvgVersion version);

Expand Down
36 changes: 34 additions & 2 deletions source/CairoSharp/SvgSurface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
// Licensed under the GNU LGPL v3, see LICENSE for more infomation.
#endregion
using System;
using System.IO;
using System.Runtime.InteropServices;

namespace Cairo {

Expand All @@ -22,11 +24,41 @@ public SvgSurface (string filename, double width, double height)
{
}

public void RestrictToVersion (SvgVersion version)
public void RestrictToVersion (SvgVersion version)
{
CheckDisposed ();
NativeMethods.cairo_svg_surface_restrict_to_version (Handle, version);
}
}

#region Streaming surface
private NativeMethods.cairo_write_func_t write_func { get; set; }
public static SvgSurface CreateForStream(Stream stream, double width, double height)
{
var write_func = CreateWriteFunc(stream);
// keep a reference to write_func in a property, otherwise it may be garbage-collected
// before it is called from native side.
return new SvgSurface(NativeMethods.cairo_svg_surface_create_for_stream(
write_func, IntPtr.Zero, width, height), true) {write_func = write_func};
}
private static NativeMethods.cairo_write_func_t CreateWriteFunc(Stream stream)
{
if(stream == null || !stream.CanWrite)
throw new ArgumentException();

return (closure, in_data, length) =>
{
if (length == 0)
return Status.Success;

byte[] tempBuff = new byte[length];
Marshal.Copy(in_data, tempBuff, 0, length);

stream.Write(tempBuff, 0, tempBuff.Length);

return Status.Success;
};
}
#endregion
}
}