From 081a3cb02c29e5e7e24ff6770a94ad7095d07c4a Mon Sep 17 00:00:00 2001 From: Matthias Schilder Date: Mon, 25 Feb 2019 09:16:26 +0100 Subject: [PATCH 1/2] enable native-cairo capabilities to write SVG output to streams --- demos/Windows/WinFormDemo/Form1.cs | 85 +++++++++++++++++++++++++++--- source/CairoSharp/NativeMethods.cs | 37 ++++++++----- source/CairoSharp/SvgSurface.cs | 27 +++++++++- 3 files changed, 128 insertions(+), 21 deletions(-) diff --git a/demos/Windows/WinFormDemo/Form1.cs b/demos/Windows/WinFormDemo/Form1.cs index 75a6282..8f73f1a 100644 --- a/demos/Windows/WinFormDemo/Form1.cs +++ b/demos/Windows/WinFormDemo/Form1.cs @@ -2,6 +2,7 @@ using System; using System.ComponentModel; using System.Drawing; +using System.IO; using System.Windows.Forms; namespace WinFormDemo @@ -12,6 +13,7 @@ public partial class Form1 : Form private ToolStripMenuItem staticToolStripMenuItem; private Timer timer1; private IContainer components; + private ToolStripMenuItem tosvgstreamToolStripMenuItem; private ToolStripMenuItem animatedToolStripMenuItem; public Form1() @@ -19,7 +21,15 @@ 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) { @@ -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 = new SvgSurface(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); @@ -119,6 +176,7 @@ 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(); // @@ -126,7 +184,8 @@ private void InitializeComponent() // 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); @@ -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); @@ -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(); } @@ -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(); + } } } diff --git a/source/CairoSharp/NativeMethods.cs b/source/CairoSharp/NativeMethods.cs index 89301eb..d3afcb2 100644 --- a/source/CairoSharp/NativeMethods.cs +++ b/source/CairoSharp/NativeMethods.cs @@ -350,26 +350,40 @@ static NativeMethods() [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate Status cairo_read_func_t(IntPtr closure, IntPtr data, int length); - /// + + /// + /// cairo_write_func_t is the type of function which is called when a backend needs to write data to an output stream. + /// + /// the output closure + /// data to write to the stream + /// length of data, which should be written to stream + /// + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate Status cairo_write_func_t(IntPtr closure, IntPtr data, int length); + + /// /// Creates a new image surface from PNG data read incrementally via the read_func function. - /// + /// /// function called to read the data of the file /// data to pass to read_func /// new cairo_surface_t or nil /// /// - /// 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); /// - /// 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(). - /// - [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(). + /// + [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)] @@ -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); diff --git a/source/CairoSharp/SvgSurface.cs b/source/CairoSharp/SvgSurface.cs index bf052ac..ff9b6f7 100644 --- a/source/CairoSharp/SvgSurface.cs +++ b/source/CairoSharp/SvgSurface.cs @@ -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 { @@ -22,11 +24,32 @@ public SvgSurface (string filename, double width, double height) { } - public void RestrictToVersion (SvgVersion version) + public SvgSurface(Stream stream, double width, double height) : + base (NativeMethods.cairo_svg_surface_create_for_stream (CreateWriteFunc(stream), IntPtr.Zero, width, height), true) + { + } + + public void RestrictToVersion (SvgVersion version) { CheckDisposed (); NativeMethods.cairo_svg_surface_restrict_to_version (Handle, version); } - } + + private static NativeMethods.cairo_write_func_t CreateWriteFunc(Stream stream) + { + if(stream == null || !stream.CanWrite) + throw new ArgumentException(); + + return (closure, in_data, length) => + { + byte[] tempBuff = new byte[length]; + Marshal.Copy(in_data, tempBuff, 0, length); + + stream.Write(tempBuff, 0, tempBuff.Length); + + return Status.Success; + }; + } + } } From 384ad206be898d22406f595244e38e6687f49a89 Mon Sep 17 00:00:00 2001 From: Matthias Schilder Date: Wed, 6 Mar 2019 10:09:50 +0100 Subject: [PATCH 2/2] enable native-cairo capabilities to write SVG output to streams --- demos/Windows/WinFormDemo/Form1.cs | 2 +- source/CairoSharp/SvgSurface.cs | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/demos/Windows/WinFormDemo/Form1.cs b/demos/Windows/WinFormDemo/Form1.cs index 8f73f1a..e31c89c 100644 --- a/demos/Windows/WinFormDemo/Form1.cs +++ b/demos/Windows/WinFormDemo/Form1.cs @@ -57,7 +57,7 @@ private void DrawStaticAndToOutputStream() { MemoryStream outpStream = new MemoryStream(); // create a SvgSurface, which output is bound to a stream. - var svgStreamSurface = new SvgSurface(outpStream, 1024, 1024); + var svgStreamSurface = SvgSurface.CreateForStream(outpStream, 1024, 1024); using (Context context = new Context(svgStreamSurface)) { //clear the background to white diff --git a/source/CairoSharp/SvgSurface.cs b/source/CairoSharp/SvgSurface.cs index ff9b6f7..1b6c05b 100644 --- a/source/CairoSharp/SvgSurface.cs +++ b/source/CairoSharp/SvgSurface.cs @@ -24,24 +24,32 @@ public SvgSurface (string filename, double width, double height) { } - public SvgSurface(Stream stream, double width, double height) : - base (NativeMethods.cairo_svg_surface_create_for_stream (CreateWriteFunc(stream), IntPtr.Zero, width, height), true) - { - } - public void RestrictToVersion (SvgVersion version) { CheckDisposed (); NativeMethods.cairo_svg_surface_restrict_to_version (Handle, version); } - private static NativeMethods.cairo_write_func_t CreateWriteFunc(Stream stream) + #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); @@ -50,6 +58,7 @@ private static NativeMethods.cairo_write_func_t CreateWriteFunc(Stream stream) return Status.Success; }; } + #endregion } }