Giving back (Printing)

Topics: SharpMap v0.9 / v1.x
Dec 3, 2013 at 4:57 PM
I have created a couple of classes for printing. One is a subclass of the PrintDocument for actual printing and another is a map decoration for a frame. I am adding them here for anyone interested. I have only tried this on a few 8.5 x 11 printers and acrobat but this should work on large format printers. I have only tried to print one other decoration, the ScaleBar and there is some problem where it prints at a very small size.

These classes could probably be refined but I thought I would post them since I use SharpMap extensively and want to give back.

Carlos

The Map Print Document
using System;
using System.Drawing.Printing;
using System.Drawing;
using System.Drawing.Drawing2D;
using GeoAPI.Geometries;
using SharpMap;
using SharpMap.Rendering.Decoration;

namespace SharpMap.Rendering.Printing
{
  class MapPrintDocument : PrintDocument
  {

    #region Class wide variables

    /// <summary>
    /// The map
    /// </summary>
    private Map map;

    /// <summary>
    /// The map decoration fram
    /// </summary>
    private Frame frame = null;

    /// <summary>
    /// The extent of the map at start up
    /// </summary>
    private Envelope startEnv;

    /// <summary>
    /// The size of the map at start up
    /// </summary>
    private Size startSize;

    /// <summary>
    /// The center of the map at start up
    /// </summary>
    private Coordinate startCenter;
    
    /// <summary>
    /// The print document units for margins etc.
    /// </summary>
    private float printDocUnits = 100f;

    /// <summary>
    /// The width of the area to print in print document units
    /// This generally is page width less right and left margins
    /// </summary>
    private float pageWidth;

    /// <summary>
    /// The height of the area to print in print document units
    /// This generally is page height less top and bottom margins
    /// </summary>
    private float pageHeight;

    #endregion

    /// <summary>
    /// Initializer for MapPrinterClass
    /// </summary>
    /// <param name="Map">The map</param>
    /// <param name="AreaOfInterest">The envelope of the area of interest</param>
    public MapPrintDocument(Map Map, Envelope AreaOfInterest)
    {
      var page = this.DefaultPageSettings;
      var margins = page.Margins;

      // Set map variables and events
      map = Map;
      map.MapRendered += map_MapRendered;
      startEnv = AreaOfInterest;
      startCenter = map.Center;
      startSize = map.Size;

      // Tell frame decoration if it exists that we are printing
      foreach (MapDecoration decoration in map.Decorations)
      {
        if (decoration is Frame)
        {
          frame = (Frame)decoration;
          frame.IsPrinting = true;
        }
      }

      // Set margins
      this.OriginAtMargins = true;
      // margins by default are set at 100


      // Add Margins to my Frame in inches
      if (frame != null)
      {
        // The Hard Margin X and Y are the actual 0 for margins
        // need to offset the left and top for them
        frame.LeftMargin = (margins.Left - page.HardMarginX) / printDocUnits;  
        frame.RightMargin = margins.Right / printDocUnits;
        frame.TopMargin = (margins.Top - page.HardMarginY) / printDocUnits; ;
        frame.BottomMargin = margins.Bottom / printDocUnits;
      }

      // convert to inches then convert to dpi
      var dpiX = page.PrinterResolution.X;
      var dpiY = page.PrinterResolution.Y;

      var left = ((float)margins.Left / printDocUnits) * dpiX;
      var top = ((float)margins.Top / printDocUnits) * dpiY;

      pageWidth = (float)(page.PaperSize.Width - margins.Left - margins.Right);
      var widthInches = pageWidth / printDocUnits;
      var width = widthInches * dpiX;

      pageHeight = (float)(page.PaperSize.Height - margins.Top - margins.Bottom);
      var heightInches = pageHeight / printDocUnits;
      var height = heightInches * dpiY;

      map.Size = new Size((int)width, (int)height);
      
      // Zoom to extent with 5% zoom out
      Envelope env;
      if (map.Envelope.Width < map.Envelope.Height)
        env = startEnv.Grow(startEnv.Width * .05);
      else
        env = startEnv.Grow(startEnv.Height * .05);
      map.ZoomToBox(env);

      // Recenter map
      PointF pt = map.WorldToImage(startCenter);
      pt.X = pt.X - left;
      pt.Y = pt.Y - top;
      Coordinate worldCoord = map.ImageToWorld(pt, true);
      map.Center = worldCoord;
      
    }
    
    #region PrintDocument overrides

    protected override void OnPrintPage(PrintPageEventArgs e)
    {

      base.OnPrintPage(e);

      Graphics g = e.Graphics;
      // this rectangle has to be in PrintDocument units
      RectangleF rect = new RectangleF(0, 0, pageWidth, pageHeight);
      Region region = new Region(rect);
      g.SetClip(region, CombineMode.Replace);

      map.RenderMap(g);

    }

    protected override void OnEndPrint(PrintEventArgs e)
    {
      base.OnEndPrint(e);

      // When done with all drawing to page 
      // reset map to original state and set
      // frame to not printing

      // Reset the frame to not printing
      if (frame != null)
        frame.IsPrinting = false;

      // Return map to orgianal size and positon
      map.Size = startSize;
      map.ZoomToBox(startEnv);
      map.Center = startCenter;
    }

    #endregion

    #region Map events

    void map_MapRendered(Graphics g)
    {

      // this area is where post map drawing
      // on the page is done.

    }

    #endregion

  }
}
The map decoration frame
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using SharpMap.Utilities;

namespace SharpMap.Rendering.Decoration
{
  class Frame : MapDecoration
  {

    public Frame()
    {
      Size = new Size(300, 300);
      ForeColor = Color.Black;
      OutlineWidth = 3f;
      LeftMargin = 1;
      RightMargin = 1;
      TopMargin = 1;
      BottomMargin = 1;
    }

    #region Properties

    /// <summary>
    /// Gets or sets size of the Border
    /// </summary>
    public Size Size { get; set; }

    /// <summary>
    /// Get or sets the width of the Border line
    /// </summary>
    public float OutlineWidth { get; set; }

    /// <summary>
    /// Gets or sets the fore color
    /// </summary>
    public Color ForeColor { get; set; }

    /// <summary>
    /// Gets or sets the left margin
    /// </summary>
    public float LeftMargin { get; set; }

    /// <summary>
    /// Gets or sets the right margin
    /// </summary>
    public float RightMargin { get; set; }

    /// <summary>
    /// Gets or sets the top margin
    /// </summary>
    public float TopMargin { get; set; }

    /// <summary>
    /// Gets or sets the bottom margin
    /// </summary>
    public float BottomMargin { get; set; }

    /// <summary>
    /// Get or sets if the border is to print
    /// used to determine when to use margins
    /// </summary>
    public bool IsPrinting { get; set; }

    #endregion

    #region MapDecoration overrides

    /// <summary>
    /// Fuction to compute the required size for rendering the map decoration object
    /// <para>This is just the size of the decoration object, border settings are excluded</para>
    /// </summary>
    /// <param name="g">The graphics object</param>
    /// <param name="map">The map</param>
    /// <returns>The size of the map decoration</returns>
    protected override Size InternalSize(Graphics g, Map map)
    {
      return Size;
    }

    /// <summary>
    /// Function to render the actual map decoration
    /// </summary>
    /// <param name="g">The graphic object</param>
    /// <param name="map">The Map</param>
    protected override void OnRender(Graphics g, Map map)
    {

      var oldClip = g.Clip;
      
      var oWidth = (OutlineWidth * (g.DpiX / 100));
      var halfOutlineWidth = oWidth / 2;
      var left = LeftMargin * g.DpiX;
      var right = RightMargin * g.DpiX;
      var top = TopMargin * g.DpiY;
      var bottom = BottomMargin * g.DpiY;
      var printWidth = map.Size.Width;
      var printHeight = map.Size.Height;

      g.ResetClip();
      
      using (Pen p = new Pen(new SolidBrush(ForeColor), oWidth))
      {
        if (IsPrinting)
          g.DrawRectangle(p, left + halfOutlineWidth, top + halfOutlineWidth, printWidth, printHeight);
        else
          g.DrawRectangle(p, 0, 0, map.Size.Width, map.Size.Height);
      }

      g.SetClip(oldClip, CombineMode.Replace);
       
    }

    #endregion
  }
}
Coordinator
Dec 3, 2013 at 10:16 PM
Thanks a lot for sharing this. I think there is some issue that can be closed once we applied your code.
I will look into the scaling issue you had with the scale bar decoration.

Again, thanks a lot.

P.S. You can also post patches. This makes it a bit easier for us.
Coordinator
Dec 5, 2013 at 10:39 AM
Is there a reason you do all the page setup work in the constructor, relying on DefaultPageSettings instead of performing that step in the OnPrintPage overload where you have access to the actually selected page settings?
Dec 5, 2013 at 12:58 PM
No not really. Coding from a stream of consciousness. I should have put it into a method and called it from on page print.
Coordinator
Dec 5, 2013 at 2:05 PM
Thanks for the clarification. I'll modify the code like you said.
Dec 9, 2013 at 10:02 PM
FObermaier,

After using my MapPrintDocument I have noticed some sizing issues. I also did not account for Landscape. I am making modifications to the code and will post or create a patch in the next few days.

Carlos Ortiz
Coordinator
Dec 10, 2013 at 2:11 PM
I noticed that, too. That is why I have not committed anything yet.
Dec 26, 2013 at 7:50 PM
Just a heads up. I have been pulled to work on a project at work and haven't had time to completely fix this class. I should have the time shortly after the new year.

Carlos