1929 lines
71 KiB
C#
1929 lines
71 KiB
C#
/**
|
|
*
|
|
* FusionCharts Exporter is an ASP.NET C# script that handles
|
|
* FusionCharts (since v3.1) Server Side Export feature.
|
|
* This in conjuncture with various export classes would
|
|
* process FusionCharts Export Data POSTED to it from FusionCharts
|
|
* and convert the data to image or PDF and subsequently save to the
|
|
* server or response back as http response to client side as download.
|
|
*
|
|
* This script might be called as the FusionCharts Exporter - main module
|
|
*
|
|
* @author FusionCharts
|
|
* @description FusionCharts Exporter (Server-Side - ASP.NET C#)
|
|
* @version 4.0 [ 21 June 2016 ]
|
|
*
|
|
*/
|
|
/**
|
|
* ChangeLog / Version History:
|
|
* ----------------------------
|
|
*
|
|
* 4.0 [ 21 June 2016 ]
|
|
* - Support export if direct image base64 encoded data is provided (for FusionCharts v 3.11.0 or more).
|
|
* - Support for download of xls format
|
|
* - Export with images suppported for every format including svg if browser is capable of sending the image data
|
|
* as base64 data.
|
|
*
|
|
* 3.0 [ 18 July 2014 ]
|
|
* - Support for JavaScript Chart (SVG)
|
|
*
|
|
* 2.0 [ 12 February 2009 ]
|
|
* - Integrated with new Export feature of FusionCharts 3.1
|
|
* - can save to server side directory
|
|
* - can provide download or open in popup window.
|
|
* - can report back to chart
|
|
* - can save as PDF/JPG/PNG/GIF
|
|
*
|
|
* 1.0 [ 16 August 2007 ]
|
|
* - can process chart data to jpg image and response back to client side as download.
|
|
*
|
|
*/
|
|
/**
|
|
* Copyright (c) 2016 InfoSoft Global Private Limited. All Rights Reserved
|
|
*
|
|
*/
|
|
/**
|
|
* GENERAL NOTES
|
|
* -------------
|
|
*
|
|
* Chart would POST export data (which consists of encoded image data stream,
|
|
* width, height, background color and various other export parameters like
|
|
* exportFormat, exportFileName, exportAction, exportTargetWindow) to this script.
|
|
*
|
|
* The script would process this data using appropriate resource classes & build
|
|
* export binary (PDF/image)
|
|
*
|
|
* It either saves the binary as file to a server side directory or push it as
|
|
* Download to client side.
|
|
*
|
|
*
|
|
* ISSUES
|
|
* ------
|
|
* Q. What if someone wishes to open in the same page where the chart existed as postback
|
|
* replacing the old page?
|
|
*
|
|
* A. Not directly supported using any chart attribute or parameter but can do by
|
|
* removing/commenting the line containing 'header( content-disposition ...'
|
|
*
|
|
*/
|
|
/**
|
|
*
|
|
* @requires FCIMGGenerator Class to export FusionCharts image data to JPG, PNG, GIF binary
|
|
* @requires FCPDFGenerator Class to export FusionCharts image data to PDF binary
|
|
*
|
|
*/
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Web;
|
|
using System.Drawing;
|
|
using System.Collections;
|
|
using System.Drawing.Imaging;
|
|
using System.Text.RegularExpressions;
|
|
using SharpVectors.Converters;
|
|
using System.Web.Script.Serialization;
|
|
using System.Collections.Generic;
|
|
|
|
/// <summary>
|
|
/// FusionCharts Exporter is an ASP.NET C# script that handles
|
|
/// FusionCharts (since v3.1) Server Side Export feature.
|
|
/// This in conjuncture with other resource classses would
|
|
/// process FusionCharts Export Data POSTED to it from FusionCharts
|
|
/// and convert the data to an image or a PDF. Subsequently, it would save
|
|
/// to the server or respond back as an HTTP response to client side as download.
|
|
///
|
|
/// This script might be called as the FusionCharts Exporter - main module
|
|
/// </summary>
|
|
///
|
|
public partial class FCExporter : System.Web.UI.Page
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
/// IMPORTANT: You need to change the location of folder where
|
|
/// the exported chart images/PDFs will be saved on your
|
|
/// server. Please specify the path to a folder with
|
|
/// write permissions in the constant SAVE_PATH below.
|
|
///
|
|
/// Please provide the path as per ASP.NET path conventions.
|
|
/// You can use relative or absolute path.
|
|
///
|
|
/// Special Cases:
|
|
/// '/' means 'wwwroot' directory.
|
|
/// '. /' ( without the space after .) is the directory where the FCExporter.aspx file recides.
|
|
///
|
|
/// Absolute Path :
|
|
///
|
|
/// can be like this : "C:\\myFolders\\myImages"
|
|
/// ( Please never use single backslash as that would stop execution of the code instantly)
|
|
/// or "C:/myFolders/myImages"
|
|
///
|
|
/// You may have a // or \ at end : "C:\\myFolders\\myImages\\" or "C:/myFolders/myImages/"
|
|
///
|
|
/// You can also have mixed slashes : "C:\\myFolders/myImages"
|
|
///
|
|
///
|
|
/// </summary>
|
|
/// directory where the FCExporter.aspx file recides
|
|
private const string SAVE_PATH = "./Exported_Images/";
|
|
|
|
/// <summary>
|
|
/// IMPORTANT: This constant HTTP_URI stores the HTTP reference to
|
|
/// the folder where exported charts will be saved.
|
|
/// Please enter the HTTP representation of that folder
|
|
/// in this constant e.g., http://www.yourdomain.com/images/
|
|
/// </summary>
|
|
private const string HTTP_URI = "Exported_Images/";
|
|
|
|
/// <summary>
|
|
/// OVERWRITEFILE sets whether the export handler would overwrite an existing file
|
|
/// the newly created exported file. If it is set to false the export handler would
|
|
/// not overwrite. In this case, if INTELLIGENTFILENAMING is set to true the handler
|
|
/// would add a suffix to the new file name. The suffix is a randomly generated GUID.
|
|
/// Additionally, you add a timestamp or a random number as additional suffix.
|
|
/// </summary>
|
|
private bool OVERWRITEFILE = false;
|
|
private bool INTELLIGENTFILENAMING = true;
|
|
private string FILESUFFIXFORMAT = "TIMESTAMP";// // value can be either 'TIMESTAMP' or 'RANDOM'
|
|
|
|
|
|
/// <summary>
|
|
/// This is a constant list of the MIME types related to each export format this resource handles
|
|
/// The value is semicolon separated key value pair for each format
|
|
/// Each key is the format and value is the MIME type
|
|
/// </summary>
|
|
private const string MIMETYPES = "pdf=application/pdf;jpg=image/jpeg;jpeg=image/jpeg;gif=image/gif;png=image/png;svg=image/svg+xml;xls=application/vnd.ms-excel";
|
|
|
|
/// <summary>
|
|
/// This is a constant list of all the file extensions for the export formats
|
|
/// The value is semicolon separated key value pair for each format
|
|
/// Each key is the format and value is the file extension
|
|
/// </summary>
|
|
private const string EXTENSIONS = "pdf=pdf;jpg=jpg;jpeg=jpg;gif=gif;png=png;svg=svg;xls=xls";
|
|
|
|
/// <summary>
|
|
/// Lists the default exportParameter values taken, if not provided by chart
|
|
/// </summary>
|
|
private const string DEFAULTPARAMS = "exportfilename=FusionCharts;exportformat=PDF;exportaction=download;exporttargetwindow=_self";
|
|
|
|
/// <summary>
|
|
/// Stores server notices, if any as string [ to be sent back to chart after save ]
|
|
/// </summary>
|
|
private string notices = "";
|
|
/// <summary>
|
|
/// Whether the export action is download. Default value. Would change as per setting retrieved from chart.
|
|
/// </summary>
|
|
private bool isDownload = true;
|
|
|
|
/// <summary>
|
|
/// DOMId of the chart
|
|
/// </summary>
|
|
private string DOMId;
|
|
|
|
public bool IsSVGData { get; set; }
|
|
|
|
/// <summary>
|
|
/// Stores SVG information.
|
|
/// </summary>
|
|
private TextReader svgData;
|
|
|
|
/// <summary>
|
|
/// Stores SVG in-memory file.
|
|
/// </summary>
|
|
private MemoryStream svgStream;
|
|
|
|
/// <summary>
|
|
/// The main function that handles all Input - Process - Output of this Export Architecture
|
|
/// </summary>
|
|
/// <param name="sender">FusionCharts chart SWF</param>
|
|
/// <param name="e"></param>
|
|
protected void Page_Load(object sender, EventArgs e)
|
|
{
|
|
|
|
/**
|
|
* Retrieve export data from POST Request sent by chart
|
|
* Parse the Request stream into export data readable by this script
|
|
*/
|
|
Hashtable exportData = parseExportRequestStream();
|
|
|
|
// process export data and get the processed data (image/PDF) to be exported
|
|
MemoryStream exportObject = null;
|
|
|
|
if (IsSVGData)
|
|
{
|
|
if (exportData["encodedImgData"] != null && !string.IsNullOrEmpty(exportData["encodedImgData"].ToString()) && ((Hashtable)exportData["parameters"])["exportformat"].ToString() == "svg")
|
|
{
|
|
exportObject = exportProcessor(((Hashtable)exportData["parameters"])["exportformat"].ToString(), exportData["svg"].ToString(), (Hashtable)exportData["parameters"], exportData["encodedImgData"].ToString());
|
|
}
|
|
else
|
|
{
|
|
exportObject = exportProcessor(((Hashtable)exportData["parameters"])["exportformat"].ToString(), "svg", (Hashtable)exportData["parameters"]);
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
exportObject = exportProcessor(((Hashtable)exportData["parameters"])["exportformat"].ToString(), exportData["stream"].ToString(), (Hashtable)exportData["meta"]);
|
|
}
|
|
|
|
|
|
/*
|
|
* Send the export binary to output module which would either save to a server directory
|
|
* or send the export file to download. Download terminates the process while
|
|
* after save the output module sends back export status
|
|
*/
|
|
//object exportedStatus = IsSVGData ? outputExportObject(exportObject, exportData) : outputExportObject(exportObject, (Hashtable)exportData["parameters"]);
|
|
object exportedStatus = outputExportObject(exportObject, (Hashtable)exportData["parameters"]);
|
|
|
|
// Dispose export object
|
|
exportObject.Close();
|
|
exportObject.Dispose();
|
|
|
|
/*
|
|
* Build Appropriate Export Status and send back to chart by flushing the
|
|
* procesed status to http response. This returns status back to chart.
|
|
* [ This is not applicable when Download action took place ]
|
|
*/
|
|
flushStatus(exportedStatus, (Hashtable)exportData["meta"]);
|
|
|
|
}
|
|
private void convertRAWImageDataToFile(string imageData, string parameters)
|
|
{
|
|
string fileName = parameters.Split('|')[0].Split('=')[1],
|
|
extention = parameters.Split('|')[1].Split('=')[1],
|
|
exportAction = parameters.Split('|')[2].Split('=')[1],
|
|
fullFileName = fileName + "." + extention,
|
|
filLocation = HttpContext.Current.Server.MapPath("~/Exported_Images/" + fullFileName),
|
|
contentType = getMime(extention);
|
|
|
|
byte[] bytes = System.Convert.FromBase64String(imageData.Split(',')[1]);
|
|
File.WriteAllBytes(filLocation, bytes);
|
|
if (exportAction == "download") {
|
|
Response.ClearContent();
|
|
Response.AddHeader("Content-Disposition", "attachment; filename=" + fullFileName);
|
|
Response.ContentType = contentType;
|
|
Response.TransmitFile(filLocation);
|
|
Response.End();
|
|
}
|
|
}
|
|
|
|
private string stichImageToSVGAndGetString(string svgData, string imageData)
|
|
{
|
|
return stichImageToSVG(svgData, imageData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses POST stream from chart and builds a Hashtable containing
|
|
/// export data and parameters in a format readable by other functions.
|
|
/// The Hashtable contains keys 'stream' (contains encoded
|
|
/// image data) ; 'meta' ( Hashtable with 'width', 'height' and 'bgColor' keys) ;
|
|
/// and 'parameters' ( Hashtable of all export parameters from chart as keys, like - exportFormat,
|
|
/// exportFileName, exportAction etc.)
|
|
/// </summary>
|
|
/// <returns>Hashtable of processed export data and parameters.</returns>
|
|
private Hashtable parseExportRequestStream()
|
|
{
|
|
// store all export data
|
|
Hashtable exportData = new Hashtable();
|
|
string svgStr = "";
|
|
|
|
IsSVGData = false;
|
|
if (Request["stream_type"] == "IMAGE-DATA")
|
|
{
|
|
this.convertRAWImageDataToFile(Request["stream"], Request["parameters"]);
|
|
}
|
|
else if (Request["stream_type"] == "svg")
|
|
{
|
|
IsSVGData = true;
|
|
exportData["svg"] = Request["stream"];
|
|
|
|
// Added custom parameter
|
|
exportData["exporttargetwindow"] = "_self";
|
|
|
|
//Get all export parameters into a Hastable
|
|
Hashtable parameters = parseParams(Request["parameters"]); //parseParams("exportaction=" + exportData["exportaction"].ToString());
|
|
exportData["parameters"] = parameters;
|
|
|
|
svgStr = exportData["svg"].ToString();
|
|
svgStr = svgStr.Substring(0, svgStr.IndexOf("</svg>") + 6);
|
|
// fix to replace string occurred in some data source specifically excel
|
|
// need to look for a more proper method which covers all such situations
|
|
svgStr = svgStr.Replace(" ", " ");
|
|
exportData["svg"] = svgStr;
|
|
|
|
if (Request["encodedImgData"] != null)
|
|
{
|
|
exportData["encodedImgData"] = Request["encodedImgData"];
|
|
}
|
|
|
|
byte[] svg = System.Text.Encoding.UTF8.GetBytes(exportData["svg"].ToString());
|
|
|
|
if (exportData["encodedImgData"] != null && !string.IsNullOrEmpty(exportData["encodedImgData"].ToString()))
|
|
{
|
|
svgStream = stichImageToSVGAndGetStream(exportData["svg"].ToString(), exportData["encodedImgData"].ToString());
|
|
svgData = new StringReader(stichImageToSVGAndGetString(exportData["svg"].ToString(), exportData["encodedImgData"].ToString()));
|
|
}
|
|
else
|
|
{
|
|
svgStream = new MemoryStream(svg);
|
|
svgData = new StreamReader(svgStream);
|
|
}
|
|
}
|
|
// If Flash Charts
|
|
else
|
|
{
|
|
//String of compressed image data
|
|
exportData["stream"] = Request["stream"];
|
|
|
|
//Halt execution if image stream is not provided.
|
|
if (Request["stream"] == null || Request["stream"].Trim() == "") raise_error("100", true);
|
|
|
|
//Get all export parameters into a Hastable
|
|
Hashtable parameters = parseParams(Request["parameters"]);
|
|
exportData["parameters"] = parameters;
|
|
|
|
}
|
|
|
|
//get width and height of the chart
|
|
Hashtable meta = new Hashtable();
|
|
|
|
meta["width"] = Request["meta_width"];
|
|
//Halt execution on error
|
|
if (Request["meta_width"] == null || Request["meta_width"].Trim() == "") raise_error("101", true);
|
|
|
|
meta["height"] = Request["meta_height"];
|
|
//Halt execution on error
|
|
if (Request["meta_height"] == null || Request["meta_height"].Trim() == "") raise_error("101", true);
|
|
|
|
|
|
//Background color of chart
|
|
meta["bgcolor"] = Request["meta_bgColor"];
|
|
if (meta["bgcolor"] == null || meta["bgcolor"].ToString().Trim() == "")
|
|
{
|
|
// Send notice if BgColor is not provided
|
|
raise_error(" Background color not specified. Taking White (FFFFFF) as default background color.");
|
|
// Set White as Default Background color
|
|
meta["bgcolor"] = "FFFFFF";
|
|
}
|
|
|
|
// DOMId of the chart
|
|
meta["DOMId"] = Request["meta_DOMId"] == null ? "" : Request["meta_DOMId"];
|
|
DOMId = meta["DOMId"].ToString();
|
|
|
|
exportData["meta"] = meta;
|
|
|
|
return exportData;
|
|
}
|
|
|
|
private string stichImageToSVG(string svgData, string imageData)
|
|
{
|
|
JavaScriptSerializer ser = new JavaScriptSerializer();
|
|
var data = ser.Deserialize<Dictionary<string, Dictionary<string, string>>>(imageData);
|
|
|
|
List<string> rawImageDataArray = new List<string>();
|
|
List<string> hrefArray = new List<string>();
|
|
|
|
// /(<image[^>]*xlink:href *= *[\"']?)([^\"']*)/i
|
|
Regex regex = new Regex("<image.+?xlink:href=\"(.+?)\".+?/?>");
|
|
int counter = 0;
|
|
foreach (Match match in regex.Matches(svgData))
|
|
{
|
|
string[] temp1 = match.Value.Split(new string[] { "xlink:href=" }, StringSplitOptions.None);
|
|
hrefArray.Add(temp1[1].Split('"')[1]);
|
|
string[] imageNameArray = hrefArray[counter].Split('/');
|
|
rawImageDataArray.Add(getImageData(data, imageNameArray[imageNameArray.Length - 1]));
|
|
counter += 1;
|
|
}
|
|
for (int index = 0; index <= rawImageDataArray.Count - 1; index++)
|
|
{
|
|
svgData = svgData.Replace(hrefArray[index], rawImageDataArray[index]);
|
|
}
|
|
|
|
return svgData;
|
|
}
|
|
|
|
// <summary>
|
|
// Get image data from the json object Request["encodedImgData"].
|
|
// </summary>
|
|
// <param name="imageData">(Dictionary<string, Dictionary<string, string>>) all image Image data as a combined object</param>
|
|
// <param name="imageName">(string) Image Name</param>
|
|
// <returns></returns>
|
|
private string getImageData(Dictionary<string, Dictionary<string, string>> imageData, string imageName)
|
|
{
|
|
string data = "";
|
|
foreach (string key in imageData.Keys)
|
|
{
|
|
if ((imageData[key]["name"] + "." + imageData[key]["type"]) == imageName)
|
|
{
|
|
data = imageData[key]["encodedData"];
|
|
break; // TODO: might not be correct. Was : Exit For
|
|
}
|
|
}
|
|
|
|
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse export 'parameters' string into a Hashtable
|
|
/// Also synchronise default values from defaultparameterValues Hashtable
|
|
/// </summary>
|
|
/// <param name="strParams">A string with parameters (key=value pairs) separated by | (pipe)</param>
|
|
/// <returns>Hashtable containing parsed key = value pairs.</returns>
|
|
private Hashtable parseParams(string strParams)
|
|
{
|
|
|
|
//default parameter values
|
|
Hashtable defaultParameterValues = bang(DEFAULTPARAMS);
|
|
|
|
// get parameters
|
|
Hashtable parameters = bang(strParams, new char[] { '|', '=' });
|
|
|
|
// sync with default values
|
|
// iterate through each default parameter value
|
|
foreach (DictionaryEntry param in defaultParameterValues)
|
|
{
|
|
// if a parameter from the defaultParameterValues Hashtable is not present
|
|
// in the parameters hashtable take the parameter and value from default
|
|
// parameter hashtable and add it to params hashtable
|
|
// This is needed to ensure proper export
|
|
if (parameters[param.Key] == null) parameters[param.Key] = param.Value.ToString();
|
|
}
|
|
|
|
// set a global flag which denotes whether the export is download or not
|
|
// this is needed in many a functions
|
|
isDownload = parameters["exportaction"].ToString().ToLower() == "download";
|
|
|
|
|
|
// return parameters
|
|
return parameters;
|
|
|
|
|
|
}
|
|
|
|
private MemoryStream stichImageToSVGAndGetStream(string svgData, string imageData)
|
|
{
|
|
|
|
svgData = stichImageToSVG(svgData, imageData);
|
|
byte[] svg = System.Text.Encoding.UTF8.GetBytes(svgData.ToString());
|
|
return new MemoryStream(svg);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Get Export data from and build the export binary/objct.
|
|
/// </summary>
|
|
/// <param name="strFormat">(string) Export format</param>
|
|
/// <param name="stream">(string) Export image data in FusionCharts compressed format</param>
|
|
/// <param name="meta">{Hastable)Image meta data in keys "width", "heigth" and "bgColor"</param>
|
|
/// <returns></returns>
|
|
|
|
private MemoryStream exportProcessor(string strFormat, string stream, Hashtable meta, string imageData)
|
|
{
|
|
return stichImageToSVGAndGetStream(stream, imageData);
|
|
}
|
|
|
|
private MemoryStream exportProcessor(string strFormat, string stream, Hashtable meta)
|
|
{
|
|
|
|
strFormat = strFormat.ToLower();
|
|
// initilize memeory stream object to store output bytes
|
|
MemoryStream exportObjectStream = new MemoryStream();
|
|
|
|
// Handle Export class as per export format
|
|
switch (strFormat)
|
|
{
|
|
case "pdf":
|
|
if (!IsSVGData)
|
|
{
|
|
// Instantiate Export class for PDF, build Binary stream and store in stream object
|
|
FCPDFGenerator PDFGEN = new FCPDFGenerator(stream, meta["width"].ToString(), meta["height"].ToString(), meta["bgcolor"].ToString());
|
|
exportObjectStream = PDFGEN.getBinaryStream(strFormat);
|
|
}
|
|
else
|
|
{
|
|
exportObjectStream = GetJSImage(meta, true);
|
|
}
|
|
|
|
break;
|
|
case "jpg":
|
|
case "jpeg":
|
|
case "png":
|
|
case "gif":
|
|
if (!IsSVGData)
|
|
{
|
|
// Instantiate Export class for Images, build Binary stream and store in stream object
|
|
FCIMGGenerator IMGGEN = new FCIMGGenerator(stream, meta["width"].ToString(), meta["height"].ToString(), meta["bgcolor"].ToString());
|
|
exportObjectStream = IMGGEN.getBinaryStream(strFormat);
|
|
}
|
|
else
|
|
{
|
|
exportObjectStream = GetJSImage(meta, false);
|
|
}
|
|
break;
|
|
case "svg":
|
|
exportObjectStream = svgStream;
|
|
break;
|
|
default:
|
|
// In case the format is not recognized
|
|
raise_error(" Invalid Export Format.", true);
|
|
break;
|
|
}
|
|
|
|
return exportObjectStream;
|
|
}
|
|
|
|
private MemoryStream GetJSImage(Hashtable exportData, bool processPdf)
|
|
{
|
|
MemoryStream exportObjectStream = new MemoryStream();
|
|
|
|
//string filename = exportData["filename"].ToString();
|
|
string type = exportData["exportformat"].ToString().ToLower();
|
|
|
|
if (processPdf)
|
|
{
|
|
type = "jpg";
|
|
}
|
|
|
|
SharpVectors.Renderers.Wpf.WpfDrawingSettings ds = new SharpVectors.Renderers.Wpf.WpfDrawingSettings();
|
|
|
|
StreamSvgConverter ssc = new StreamSvgConverter(ds);
|
|
ssc.SaveXaml = false;
|
|
ssc.SaveZaml = false;
|
|
|
|
ImageEncoderType encoder = ImageEncoderType.JpegBitmap;
|
|
|
|
switch (type)
|
|
{
|
|
case "png":
|
|
encoder = ImageEncoderType.PngBitmap;
|
|
break;
|
|
case "jpeg":
|
|
encoder = ImageEncoderType.JpegBitmap;
|
|
break;
|
|
}
|
|
|
|
ssc.EncoderType = encoder;
|
|
ssc.SaveXaml = false;
|
|
|
|
if (ssc.Convert(svgData, exportObjectStream))
|
|
{
|
|
|
|
if (processPdf)
|
|
{
|
|
FCJSPDFGenerator PDFGEN = new FCJSPDFGenerator(true, exportObjectStream, ssc.Drawing.Bounds.Width.ToString(), ssc.Drawing.Bounds.Height.ToString());
|
|
exportObjectStream = PDFGEN.getBinaryStream(type);
|
|
}
|
|
}
|
|
|
|
svgData.Close();
|
|
svgData.Dispose();
|
|
svgStream.Close();
|
|
svgStream.Dispose();
|
|
|
|
return exportObjectStream;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks whether the export action is download or save.
|
|
/// If action is 'download', send export parameters to 'setupDownload' function.
|
|
/// If action is not-'download', send export parameters to 'setupServer' function.
|
|
/// In either case it gets exportSettings and passes the settings along with
|
|
/// processed export binary (image/PDF) to the output handler function if the
|
|
/// export settings return a 'ready' flag set to 'true' or 'download'. The export
|
|
/// process would stop here if the action is 'download'. In the other case,
|
|
/// it gets back success status from output handler function and returns it.
|
|
/// </summary>
|
|
/// <param name="exportObj">Export binary/object in memery stream</param>
|
|
/// <param name="exportParams">Hashtable of export parameters</param>
|
|
/// <returns>Export success status ( filename if success, false if not)</returns>
|
|
private object outputExportObject(MemoryStream exportObj, Hashtable exportParams)
|
|
{
|
|
//pass export paramters and get back export settings as per export action
|
|
Hashtable exportActionSettings = (isDownload ? setupDownload(exportParams) : setupServer(exportParams));
|
|
|
|
// set default export status to true
|
|
bool status = true;
|
|
|
|
// filepath returned by server setup would be a string containing the file path
|
|
// where the export file is to be saved.
|
|
// If filepath is a boolean (i.e. false) the server setup must have failed. Hence, terminate process.
|
|
if (exportActionSettings["filepath"] is bool)
|
|
{
|
|
status = false;
|
|
raise_error(" Failed to export.", true);
|
|
}
|
|
else
|
|
{
|
|
// When 'filepath' is a sting write the binary to output stream
|
|
try
|
|
{
|
|
// Write export binary stream to output stream
|
|
Stream outStream = (Stream)exportActionSettings["outStream"];
|
|
exportObj.WriteTo(outStream);
|
|
outStream.Flush();
|
|
outStream.Close();
|
|
exportObj.Close();
|
|
}
|
|
catch (ArgumentNullException e)
|
|
{
|
|
raise_error(" Failed to export. Error:" + e.Message);
|
|
status = false;
|
|
}
|
|
catch (ObjectDisposedException e)
|
|
{
|
|
raise_error(" Failed to export. Error:" + e.Message);
|
|
status = false;
|
|
}
|
|
|
|
|
|
if (isDownload)
|
|
{
|
|
// If 'download'- terminate imediately
|
|
// As nothing is to be written to response now.
|
|
Response.End();
|
|
}
|
|
|
|
}
|
|
|
|
// This is the response after save action
|
|
// If status remains true return the 'filepath'. Otherwise return false to denote failure.
|
|
return (status ? exportActionSettings["filepath"] : false);
|
|
|
|
|
|
}
|
|
/// <summary>
|
|
/// Flushes exported status message/or any status message to the chart or the output stream.
|
|
/// It parses the exported status through parser function parseExportedStatus,
|
|
/// builds proper response string using buildResponse function and flushes the response
|
|
/// string to the output stream and terminates the program.
|
|
/// </summary>
|
|
/// <param name="filename">Name of the exported file or false on failure</param>
|
|
/// <param name="meta">Image's meta data</param>
|
|
/// <param name="msg">Additional messages</param>
|
|
private void flushStatus(object filename, Hashtable meta, string msg)
|
|
{
|
|
// Process and flush message to response stream and terminate
|
|
Response.Output.Write(buildResponse(parseExportedStatus(filename, meta, msg)));
|
|
Response.Flush();
|
|
Response.End();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flushes exported status message/or any status message to the chart or the output stream.
|
|
/// It parses the exported status through parser function parseExportedStatus,
|
|
/// builds proper response string using buildResponse function and flushes the response
|
|
/// string to the output stream and terminates the program.
|
|
/// </summary>
|
|
/// <param name="filename">Name of the exported file or false on failure</param>
|
|
/// <param name="meta">Image's meta data</param>
|
|
/// <param name="meta"></param>
|
|
private void flushStatus(object filename, Hashtable meta)
|
|
{
|
|
flushStatus(filename, meta, "");
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Parses the exported status and builds an array of export status information. As per
|
|
/// status it builds a status array which contains statusCode (0/1), statusMesage, fileName,
|
|
/// width, height and notice in some cases.
|
|
/// </summary>
|
|
/// <param name="filename">exported status ( false if failed/error, filename as stirng if success)</param>
|
|
/// <param name="meta">Hastable containing meta descriptions of the chart like width, height</param>
|
|
/// <param name="msg">custom message to be added as statusMessage.</param>
|
|
/// <returns></returns>
|
|
private ArrayList parseExportedStatus(object filename, Hashtable meta, string msg)
|
|
{
|
|
|
|
ArrayList arrStatus = new ArrayList();
|
|
// get status
|
|
bool status = (filename is string ? true : false);
|
|
|
|
// add notices
|
|
if (notices.Trim() != "") arrStatus.Add("notice=" + notices.Trim());
|
|
|
|
// DOMId of the chart
|
|
arrStatus.Add("DOMId=" + (meta["DOMId"]==null? DOMId : meta["DOMId"].ToString()));
|
|
|
|
// add width and height
|
|
// provide 0 as width and height on failure
|
|
if (meta["width"] == null) meta["width"] = "0";
|
|
if (meta["height"] == null) meta["height"] = "0";
|
|
arrStatus.Add("height=" + (status ? meta["height"].ToString() : "0"));
|
|
arrStatus.Add("width=" + (status ? meta["width"].ToString() : "0"));
|
|
|
|
// add file URI
|
|
arrStatus.Add("fileName=" + (status ? (Regex.Replace(HTTP_URI, @"([^\/]$)", "${1}/") + filename) : ""));
|
|
arrStatus.Add("statusMessage=" + (msg.Trim() != "" ? msg.Trim() : (status ? "Success" : "Failure")));
|
|
arrStatus.Add("statusCode=" + (status ? "1" : "0"));
|
|
|
|
return arrStatus;
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Builds response from an array of status information. Joins the array to a string.
|
|
/// Each array element should be a string which is a key=value pair. This array are either joined by
|
|
/// a & to build a querystring (to pass to chart) or joined by a HTML <BR> to show neat
|
|
/// and clean status informaton in Browser window if download fails at the processing stage.
|
|
/// </summary>
|
|
/// <param name="arrMsg">Array of string containing status data as [key=value ]</param>
|
|
/// <returns>A string to be written to output stream</returns>
|
|
private string buildResponse(ArrayList arrMsg)
|
|
{
|
|
// Join export status array elements into querystring key-value pairs in case of 'save' action
|
|
// or separate with <BR> in case of 'download' action. This would make the imformation readable in browser window.
|
|
string msg = isDownload ? "" : "&";
|
|
msg += string.Join((isDownload ? "<br>" : "&"), (string[])arrMsg.ToArray(typeof(string)));
|
|
return msg;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds if a directory is writable
|
|
/// </summary>
|
|
/// <param name="path">String Path</param>
|
|
/// <returns></returns>
|
|
private bool isDirectoryWritable(string path)
|
|
{
|
|
DirectoryInfo info = new DirectoryInfo(path);
|
|
return (info.Attributes & FileAttributes.ReadOnly) != FileAttributes.ReadOnly;
|
|
|
|
}
|
|
/// <summary>
|
|
/// check server permissions and settings and return ready flag to exportSettings
|
|
/// </summary>
|
|
/// <param name="exportParams">Various export parameters</param>
|
|
/// <returns>Hashtable containing various export settings</returns>
|
|
private Hashtable setupServer(Hashtable exportParams)
|
|
{
|
|
|
|
//get export file name
|
|
string exportFile = exportParams["exportfilename"].ToString();
|
|
// get extension related to specified type
|
|
string ext = getExtension(exportParams["exportformat"].ToString());
|
|
|
|
Hashtable retServerStatus = new Hashtable();
|
|
|
|
//set server status to true by default
|
|
retServerStatus["ready"] = true;
|
|
|
|
// Open a FileStream to be used as outpur stream when the file would be saved
|
|
FileStream fos;
|
|
|
|
// process SAVE_PATH : the path where export file would be saved
|
|
// add a / at the end of path if / is absent at the end
|
|
|
|
string path = SAVE_PATH;
|
|
// if path is null set it to folder where FCExporter.aspx is present
|
|
if (path.Trim() == "") path = "./";
|
|
path = Regex.Replace(path, @"([^\/]$)", "${1}/");
|
|
|
|
try
|
|
{
|
|
// check if the path is relative if so assign the actual path to path
|
|
path = HttpContext.Current.Server.MapPath(path);
|
|
}
|
|
catch (HttpException e)
|
|
{
|
|
raise_error(e.Message);
|
|
}
|
|
|
|
|
|
// check whether directory exists
|
|
// raise error and halt execution if directory does not exists
|
|
if (!Directory.Exists(path)) raise_error(" Server Directory does not exist.", true);
|
|
|
|
// check if directory is writable or not
|
|
bool dirWritable = isDirectoryWritable(path);
|
|
|
|
// build filepath
|
|
retServerStatus["filepath"] = exportFile + "." + ext;
|
|
|
|
// check whether file exists
|
|
if (!File.Exists(path + retServerStatus["filepath"].ToString()))
|
|
{
|
|
// need to create a new file if does not exists
|
|
// need to check whether the directory is writable to create a new file
|
|
if (dirWritable)
|
|
{
|
|
// if directory is writable return with ready flag
|
|
|
|
// open the output file in FileStream
|
|
fos = File.Open(path + retServerStatus["filepath"].ToString(), FileMode.Create, FileAccess.Write);
|
|
|
|
// set the output stream to the FileStream object
|
|
retServerStatus["outStream"] = fos;
|
|
return retServerStatus;
|
|
}
|
|
else
|
|
{
|
|
// if not writable halt and raise error
|
|
raise_error("403", true);
|
|
}
|
|
}
|
|
|
|
// add notice that file exists
|
|
raise_error(" File already exists.");
|
|
|
|
//if overwrite is on return with ready flag
|
|
if (OVERWRITEFILE)
|
|
{
|
|
// add notice while trying to overwrite
|
|
raise_error(" Export handler's Overwrite setting is on. Trying to overwrite.");
|
|
|
|
// see whether the existing file is writable
|
|
// if not halt raising error message
|
|
if ((new FileInfo(path + retServerStatus["filepath"].ToString())).IsReadOnly)
|
|
raise_error(" Overwrite forbidden. File cannot be overwritten.", true);
|
|
|
|
// if writable return with ready flag
|
|
// open the output file in FileStream
|
|
// set the output stream to the FileStream object
|
|
fos = File.Open(path + retServerStatus["filepath"].ToString(), FileMode.Create, FileAccess.Write);
|
|
retServerStatus["outStream"] = fos;
|
|
return retServerStatus;
|
|
}
|
|
|
|
// raise error and halt execution when overwrite is off and intelligent naming is off
|
|
if (!INTELLIGENTFILENAMING)
|
|
{
|
|
raise_error(" Export handler's Overwrite setting is off. Cannot overwrite.", true);
|
|
}
|
|
|
|
raise_error(" Using intelligent naming of file by adding an unique suffix to the exising name.");
|
|
// Intelligent naming
|
|
// generate new filename with additional suffix
|
|
exportFile = exportFile + "_" + generateIntelligentFileId();
|
|
retServerStatus["filepath"] = exportFile + "." + ext;
|
|
|
|
// return intelligent file name with ready flag
|
|
// need to check whether the directory is writable to create a new file
|
|
if (dirWritable)
|
|
{
|
|
// if directory is writable return with ready flag
|
|
// add new filename notice
|
|
// open the output file in FileStream
|
|
// set the output stream to the FileStream object
|
|
raise_error(" The filename has changed to " + retServerStatus["filepath"].ToString() + ".");
|
|
fos = File.Open(path + retServerStatus["filepath"].ToString(), FileMode.Create, FileAccess.Write);
|
|
|
|
// set the output stream to the FileStream object
|
|
retServerStatus["outStream"] = fos;
|
|
return retServerStatus;
|
|
}
|
|
else
|
|
{
|
|
// if not writable halt and raise error
|
|
raise_error("403", true);
|
|
}
|
|
|
|
// in any unknown case the export should not execute
|
|
retServerStatus["ready"] = false;
|
|
raise_error(" Not exported due to unknown reasons.");
|
|
return retServerStatus;
|
|
|
|
}
|
|
/// <summary>
|
|
/// setup download headers and return ready flag in exportSettings
|
|
/// </summary>
|
|
/// <param name="exportParams">Various export parameters</param>
|
|
/// <returns>Hashtable containing various export settings</returns>
|
|
private Hashtable setupDownload(Hashtable exportParams)
|
|
{
|
|
|
|
//get export filename
|
|
string exportFile = exportParams["exportfilename"].ToString();
|
|
//get extension
|
|
string ext = getExtension(exportParams["exportformat"].ToString());
|
|
//get mime type
|
|
string mime = getMime(exportParams["exportformat"].ToString());
|
|
// get target window
|
|
string target = exportParams["exporttargetwindow"].ToString().ToLower();
|
|
|
|
// set content-type header
|
|
Response.ContentType = mime;
|
|
|
|
// set content-disposition header
|
|
// when target is _self the type is 'attachment'
|
|
// when target is other than self type is 'inline'
|
|
// NOTE : you can comment this line in order to replace present window (_self) content with the image/PDF
|
|
Response.AddHeader("Content-Disposition", (target == "_self" ? "attachment" : "inline") + "; filename=\"" + exportFile + "." + ext + "\"");
|
|
|
|
// return exportSetting array. 'Ready' key should be set to 'download'
|
|
Hashtable retStatus = new Hashtable();
|
|
retStatus["filepath"] = "";
|
|
|
|
// set the output strem to Response stream as the file is going to be downloaded
|
|
retStatus["outStream"] = Response.OutputStream;
|
|
return retStatus;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// gets file extension checking the export type.
|
|
/// </summary>
|
|
/// <param name="exportType">(string) export format</param>
|
|
/// <returns>string extension name</returns>
|
|
private string getExtension(string exportType)
|
|
{
|
|
// get a Hashtable array of [type=> extension]
|
|
// from EXTENSIONS constant
|
|
Hashtable extensionList = bang(EXTENSIONS);
|
|
exportType = exportType.ToLower();
|
|
|
|
// if extension type is present in $extensionList return it, otherwise return the type
|
|
return (extensionList[exportType].ToString() != null ? extensionList[exportType].ToString() : exportType);
|
|
}
|
|
/// <summary>
|
|
/// gets mime type for an export type
|
|
/// </summary>
|
|
/// <param name="exportType">Export format</param>
|
|
/// <returns>Mime type as stirng</returns>
|
|
private string getMime(string exportType)
|
|
{
|
|
// get a Hashtable array of [type=> extension]
|
|
// from MIMETYPES constant
|
|
Hashtable mimelist = bang(MIMETYPES);
|
|
string ext = getExtension(exportType);
|
|
|
|
// get mime type asociated to extension
|
|
string mime = mimelist[ext].ToString() != null ? mimelist[ext].ToString() : "";
|
|
return mime;
|
|
}
|
|
|
|
/// <summary>
|
|
/// generates a file suffix for a existing file name to apply smart file naming
|
|
/// </summary>
|
|
/// <returns>a string containing GUID and random number /timestamp</returns>
|
|
private string generateIntelligentFileId()
|
|
{
|
|
// Generate Guid
|
|
string guid = System.Guid.NewGuid().ToString("D");
|
|
|
|
// check FILESUFFIXFORMAT type
|
|
if (FILESUFFIXFORMAT.ToLower() == "timestamp")
|
|
{
|
|
// Add time stamp with file name
|
|
guid += "_" + DateTime.Now.ToString("ddMMyyyyHHmmssff");
|
|
}
|
|
else
|
|
{
|
|
// Add Random Number with fileName
|
|
guid += "_" + (new Random()).Next().ToString();
|
|
}
|
|
|
|
return guid;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Helper function that splits a string containing delimiter separated key value pairs
|
|
/// into hashtable
|
|
/// </summary>
|
|
/// <param name="str">delimiter separated key value pairs</param>
|
|
/// <param name="delimiterList">List of delimiters</param>
|
|
/// <returns></returns>
|
|
private Hashtable bang(string str, char[] delimiterList)
|
|
{
|
|
Hashtable retArray = new Hashtable();
|
|
// split string as per first delimiter
|
|
if (str == null || str.Trim() == "") return retArray;
|
|
string[] tmpArray = str.Split(delimiterList[0]);
|
|
|
|
|
|
// iterate through each element of split string
|
|
for (int i = 0; i < tmpArray.Length; i++)
|
|
{
|
|
// split each element as per second delimiter
|
|
string[] tmp2Array = tmpArray[i].Split(delimiterList[1]);
|
|
|
|
if (tmp2Array.Length >= 2)
|
|
{
|
|
// if the secondary split creats at-least 2 array elements
|
|
// make the fisrt element as the key and the second as the value
|
|
// of the resulting array
|
|
retArray[tmp2Array[0].ToLower()] = tmp2Array[1];
|
|
}
|
|
}
|
|
return retArray;
|
|
|
|
}
|
|
private Hashtable bang(string str)
|
|
{
|
|
return bang(str, new char[2] { ';', '=' });
|
|
}
|
|
private void raise_error(string msg)
|
|
{
|
|
raise_error(msg, false);
|
|
}
|
|
/// <summary>
|
|
/// Error reporter function that has a list of error messages. It can terminate the execution
|
|
/// and send successStatus=0 along with a error message. It can also append notice to a global variable
|
|
/// and continue execution of the program.
|
|
/// </summary>
|
|
/// <param name="msg">error code as Integer (referring to the index of the errMessages
|
|
/// array containing list of error messages) OR, it can be a string containing the error message/notice</param>
|
|
/// <param name="halt">Whether to halt execution</param>
|
|
private void raise_error(string msg, bool halt)
|
|
{
|
|
Hashtable errMessages = new Hashtable();
|
|
|
|
//list of defined error messages
|
|
errMessages["100"] = " Insufficient data.";
|
|
errMessages["101"] = " Width/height not provided.";
|
|
errMessages["102"] = " Insufficient export parameters.";
|
|
errMessages["400"] = " Bad request.";
|
|
errMessages["401"] = " Unauthorized access.";
|
|
errMessages["403"] = " Directory write access forbidden.";
|
|
errMessages["404"] = " Export Resource class not found.";
|
|
|
|
// Find whether error message is passed in msg or it is a custom error string.
|
|
string err_message = ((msg == null || msg.Trim() == "") ? "ERROR!" :
|
|
(errMessages[msg] == null ? msg : errMessages[msg].ToString())
|
|
);
|
|
|
|
// Halt executon after flushing the error message to response (if halt is true)
|
|
if (halt)
|
|
{
|
|
flushStatus(false, new Hashtable(), err_message);
|
|
|
|
}
|
|
// add error to notices global variable
|
|
else
|
|
{
|
|
notices += err_message;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// FusionCharts Image Generator Class
|
|
/// FusionCharts Exporter - 'Image Resource' handles
|
|
/// FusionCharts (since v3.1) Server Side Export feature that
|
|
/// helps FusionCharts exported as Image files in various formats.
|
|
/// </summary>
|
|
public class FCIMGGenerator
|
|
{
|
|
//Array - Stores multiple chart export data
|
|
private ArrayList arrExportData = new ArrayList();
|
|
//stores number of pages = length of $arrExportData array
|
|
private int numPages = 0;
|
|
|
|
|
|
/// <summary>
|
|
/// Generates bitmap data for the image from a FusionCharts export format
|
|
/// the height and width of the original export needs to be specified
|
|
/// the default background color can also be specified
|
|
/// </summary>
|
|
public FCIMGGenerator(string imageData_FCFormat, string width, string height, string bgcolor)
|
|
{
|
|
setBitmapData(imageData_FCFormat, width, height, bgcolor);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the binary data stream of the image
|
|
/// The passed parameter determines the file format of the image
|
|
/// to be exported
|
|
/// </summary>
|
|
public MemoryStream getBinaryStream(string strFormat)
|
|
{
|
|
|
|
// the image object
|
|
Bitmap exportObj = getImageObject();
|
|
|
|
// initiates a new binary data sream
|
|
MemoryStream outStream = new MemoryStream();
|
|
|
|
// determines the image format
|
|
switch (strFormat)
|
|
{
|
|
case "jpg":
|
|
case "jpeg":
|
|
exportObj.Save(outStream, ImageFormat.Jpeg);
|
|
break;
|
|
case "png":
|
|
exportObj.Save(outStream, ImageFormat.Png);
|
|
break;
|
|
case "gif":
|
|
exportObj.Save(outStream,ImageFormat.Gif);
|
|
break;
|
|
case "tiff":
|
|
exportObj.Save(outStream, ImageFormat.Tiff);
|
|
break;
|
|
default:
|
|
exportObj.Save(outStream, ImageFormat.Bmp);
|
|
break;
|
|
}
|
|
exportObj.Dispose();
|
|
|
|
return outStream;
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Generates bitmap data for the image from a FusionCharts export format
|
|
/// the height and width of the original export needs to be specified
|
|
/// the default background color can also be specified
|
|
/// </summary>
|
|
private void setBitmapData(string imageData_FCFormat, string width, string height, string bgcolor)
|
|
{
|
|
Hashtable chartExportData = new Hashtable();
|
|
chartExportData["width"] = width;
|
|
chartExportData["height"] = height;
|
|
chartExportData["bgcolor"] = bgcolor;
|
|
chartExportData["imagedata"] = imageData_FCFormat;
|
|
arrExportData.Add(chartExportData);
|
|
numPages++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates bitmap data for the image from a FusionCharts export format
|
|
/// the height and width of the original export needs to be specified
|
|
/// the default background color should also be specified
|
|
/// </summary>
|
|
private Bitmap getImageObject(int id)
|
|
{
|
|
Hashtable rawImageData = (Hashtable)arrExportData[id];
|
|
|
|
// create blank bitmap object which would store image pixel data
|
|
Bitmap image = new Bitmap(Convert.ToInt16(rawImageData["width"]), Convert.ToInt16(rawImageData["height"]), System.Drawing.Imaging.PixelFormat.Format24bppRgb);
|
|
|
|
// drwaing surface
|
|
Graphics gr = Graphics.FromImage(image);
|
|
|
|
// set background color
|
|
gr.Clear(ColorTranslator.FromHtml("#" + rawImageData["bgcolor"].ToString()));
|
|
|
|
string[] rows = rawImageData["imagedata"].ToString().Split(';');
|
|
|
|
for (int yPixel = 0; yPixel < rows.Length; yPixel++)
|
|
{
|
|
//Split each row into 'color_count' columns.
|
|
String[] color_count = rows[yPixel].Split(',');
|
|
//Set horizontal row index to 0
|
|
int xPixel = 0;
|
|
|
|
for (int col = 0; col < color_count.Length; col++)
|
|
{
|
|
//Now, if it's not empty, we process it
|
|
//Split the 'color_count' into color and repeat factor
|
|
String[] split_data = color_count[col].Split('_');
|
|
|
|
//Reference to color
|
|
string hexColor = split_data[0];
|
|
//refer to repeat factor
|
|
int fRepeat = int.Parse(split_data[1]);
|
|
|
|
//If color is not empty (i.e. not background pixel)
|
|
if (hexColor != "")
|
|
{
|
|
//If the hexadecimal code is less than 6 characters, pad with 0
|
|
hexColor = hexColor.Length < 6 ? hexColor.PadLeft(6, '0') : hexColor;
|
|
for (int k = 1; k <= fRepeat; k++)
|
|
{
|
|
|
|
//draw pixel with specified color
|
|
image.SetPixel(xPixel, yPixel, ColorTranslator.FromHtml("#" + hexColor));
|
|
//Increment horizontal row count
|
|
xPixel++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Just increment horizontal index
|
|
xPixel += fRepeat;
|
|
}
|
|
}
|
|
}
|
|
gr.Dispose();
|
|
return image;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retreives the bitmap image object
|
|
/// </summary>
|
|
private Bitmap getImageObject()
|
|
{
|
|
return getImageObject(0);
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// FusionCharts Exporter - 'PDF Resource' handles
|
|
/// FusionCharts (since v3.1) Server Side Export feature that
|
|
/// helps FusionCharts exported as PDF file.
|
|
/// </summary>
|
|
public class FCJSPDFGenerator
|
|
{
|
|
|
|
//Array - Stores multiple chart export data
|
|
private ArrayList arrExportData = new ArrayList();
|
|
//stores number of pages = length of $arrExportData array
|
|
private int numPages = 1;
|
|
|
|
private bool _IsJsChart = false;
|
|
private string _ImagePath = "";
|
|
private MemoryStream _ImageStream;
|
|
private string _width = "", _height = "";
|
|
|
|
public FCJSPDFGenerator(bool IsJsChart, string fileName, string width, string height)
|
|
{
|
|
this._IsJsChart = IsJsChart;
|
|
this._ImagePath = fileName;
|
|
this._width = width;
|
|
this._height = height;
|
|
|
|
}
|
|
|
|
public FCJSPDFGenerator(bool IsJsChart, MemoryStream ImageStream, string width, string height)
|
|
{
|
|
this._IsJsChart = IsJsChart;
|
|
this._ImageStream = ImageStream;
|
|
this._width = width;
|
|
this._height = height;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the binary data stream of the image
|
|
/// The passed parameter determines the file format of the image
|
|
/// to be exported
|
|
/// </summary>
|
|
public MemoryStream getBinaryStream(string strFormat)
|
|
{
|
|
byte[] exportObj = getPDFObjects(false);
|
|
|
|
MemoryStream outStream = new MemoryStream();
|
|
|
|
outStream.Write(exportObj, 0, exportObj.Length);
|
|
|
|
return outStream;
|
|
|
|
}
|
|
|
|
//create image PDF object containing the chart image
|
|
private byte[] addImageToPDF(int id, bool isCompressed)
|
|
{
|
|
|
|
MemoryStream imgObj = new MemoryStream();
|
|
|
|
//PDF Object number
|
|
int imgObjNo = 6 + id * 3;
|
|
|
|
//Get chart Image binary
|
|
byte[] imgBinary = getBitmapData24(this._ImageStream);
|
|
|
|
//get the length of the image binary
|
|
int len = imgBinary.Length;
|
|
|
|
string width = this._width;
|
|
string height = this._height;
|
|
|
|
//Build PDF object containing the image binary and other formats required
|
|
//string strImgObjHead = imgObjNo.ToString() + " 0 obj\n<<\n/Subtype /Image /ColorSpace /DeviceRGB /BitsPerComponent 8 /HDPI 72 /VDPI 72 " + (isCompressed ? "" : "") + "/Width " + width + " /Height " + height + " /Length " + len.ToString() + " >>\nstream\n";
|
|
// Use it for JPG.
|
|
string strImgObjHead = imgObjNo.ToString() + " 0 obj\n<<\n/Subtype /Image /Filter /DCTDecode /ColorSpace /DeviceRGB /BitsPerComponent 8 /Width " + width + " /Height " + height + " /Length " + len.ToString() + " >>\nstream\n";
|
|
|
|
imgObj.Write(stringToBytes(strImgObjHead), 0, strImgObjHead.Length);
|
|
imgObj.Write(imgBinary, 0, (int)imgBinary.Length);
|
|
|
|
string strImgObjEnd = "endstream\nendobj\n";
|
|
imgObj.Write(stringToBytes(strImgObjEnd), 0, strImgObjEnd.Length);
|
|
|
|
imgObj.Close();
|
|
return imgObj.ToArray();
|
|
}
|
|
|
|
private byte[] getPDFObjects(bool isCompressed)
|
|
{
|
|
MemoryStream PDFBytes = new MemoryStream();
|
|
|
|
//Store all PDF objects in this temporary string to be written to ByteArray
|
|
string strTmpObj = "";
|
|
|
|
|
|
//start xref array
|
|
ArrayList xRefList = new ArrayList();
|
|
xRefList.Add("xref\n0 ");
|
|
xRefList.Add("0000000000 65535 f \n"); //Address Refenrece to obj 0
|
|
|
|
//Build PDF objects sequentially
|
|
//version and header
|
|
strTmpObj = "%PDF-1.3\n%{FC}\n";
|
|
PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);
|
|
|
|
//OBJECT 1 : info (optional)
|
|
strTmpObj = "1 0 obj<<\n/Author (FusionCharts)\n/Title (FusionCharts)\n/Creator (FusionCharts)\n>>\nendobj\n";
|
|
xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 1
|
|
PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);
|
|
|
|
//OBJECT 2 : Starts with Pages Catalogue
|
|
strTmpObj = "2 0 obj\n<< /Type /Catalog /Pages 3 0 R >>\nendobj\n";
|
|
xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 2
|
|
PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);
|
|
|
|
//OBJECT 3 : Page Tree (reference to pages of the catalogue)
|
|
strTmpObj = "3 0 obj\n<< /Type /Pages /Kids [";
|
|
for (int i = 0; i < numPages; i++)
|
|
{
|
|
strTmpObj += (((i + 1) * 3) + 1) + " 0 R\n";
|
|
}
|
|
strTmpObj += "] /Count " + numPages + " >>\nendobj\n";
|
|
|
|
xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 3
|
|
PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);
|
|
|
|
int itr = 0;
|
|
string iWidth = this._width;
|
|
string iHeight = this._height;
|
|
|
|
//OBJECT 4..7..10..n : Page config
|
|
strTmpObj = (((itr + 2) * 3) - 2) + " 0 obj\n<<\n/Type /Page /Parent 3 0 R \n/MediaBox [ 0 0 " + iWidth + " " + iHeight + " ]\n/Resources <<\n/ProcSet [ /PDF ]\n/XObject <</R" + (itr + 1) + " " + ((itr + 2) * 3) + " 0 R>>\n>>\n/Contents [ " + (((itr + 2) * 3) - 1) + " 0 R ]\n>>\nendobj\n";
|
|
xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 4,7,10,13,16...
|
|
PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);
|
|
|
|
//OBJECT 5...8...11...n : Page resource object (xobject resource that transforms the image)
|
|
xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 5,8,11,14,17...
|
|
string xObjR = getXObjResource(itr);
|
|
PDFBytes.Write(stringToBytes(xObjR), 0, xObjR.Length);
|
|
|
|
//OBJECT 6...9...12...n : Binary xobject of the page (image)
|
|
byte[] imgBA = addImageToPDF(itr, isCompressed);
|
|
xRefList.Add(calculateXPos((int)PDFBytes.Length));//refenrece to obj 6,9,12,15,18...
|
|
PDFBytes.Write(imgBA, 0, imgBA.Length);
|
|
|
|
//xrefs compilation
|
|
xRefList[0] += ((xRefList.Count - 1) + "\n");
|
|
|
|
//get trailer
|
|
string trailer = getTrailer((int)PDFBytes.Length, xRefList.Count - 1);
|
|
|
|
//write xref and trailer to PDF
|
|
string strXRefs = string.Join("", (string[])xRefList.ToArray(typeof(string)));
|
|
PDFBytes.Write(stringToBytes(strXRefs), 0, strXRefs.Length);
|
|
//
|
|
PDFBytes.Write(stringToBytes(trailer), 0, trailer.Length);
|
|
|
|
//write EOF
|
|
string strEOF = "%%EOF\n";
|
|
PDFBytes.Write(stringToBytes(strEOF), 0, strEOF.Length);
|
|
|
|
PDFBytes.Close();
|
|
return PDFBytes.ToArray();
|
|
|
|
}
|
|
|
|
|
|
//Build Image resource object that transforms the image from First Quadrant system to Second Quadrant system
|
|
private string getXObjResource()
|
|
{
|
|
return getXObjResource(0);
|
|
}
|
|
private string getXObjResource(int itr)
|
|
{
|
|
|
|
string width = this._width;
|
|
string height = this._height;
|
|
return (((itr + 2) * 3) - 1) + " 0 obj\n<< /Length " + (24 + (width + height).Length) + " >>\nstream\nq\n" + width + " 0 0 " + height + " 0 0 cm\n/R" + (itr + 1) + " Do\nQ\nendstream\nendobj\n";
|
|
}
|
|
|
|
private string calculateXPos(int posn)
|
|
{
|
|
return posn.ToString().PadLeft(10, '0') + " 00000 n \n";
|
|
}
|
|
|
|
|
|
private string getTrailer(int xrefpos)
|
|
{
|
|
return getTrailer(xrefpos, 7);
|
|
}
|
|
|
|
private string getTrailer(int xrefpos, int numxref)
|
|
{
|
|
return "trailer\n<<\n/Size " + numxref.ToString() + "\n/Root 2 0 R\n/Info 1 0 R\n>>\nstartxref\n" + xrefpos.ToString() + "\n";
|
|
}
|
|
|
|
|
|
private byte[] getBitmapData24(string fileName)
|
|
{
|
|
return File.ReadAllBytes(fileName);
|
|
}
|
|
|
|
private byte[] getBitmapData24(MemoryStream ImageStream)
|
|
{
|
|
return ImageStream.ToArray();
|
|
}
|
|
|
|
// converts a hexadecimal colour string to it's respective byte value
|
|
private byte[] hexToBytes(string strHex)
|
|
{
|
|
if (strHex == null || strHex.Trim().Length == 0) strHex = "00";
|
|
strHex = Regex.Replace(strHex, @"[^0-9a-fA-f]", "");
|
|
if (strHex.Length % 2 != 0) strHex = "0" + strHex;
|
|
|
|
int len = strHex.Length / 2;
|
|
byte[] bytes = new byte[len];
|
|
|
|
for (int i = 0; i < len; i++)
|
|
{
|
|
string hex = strHex.Substring(i * 2, 2);
|
|
bytes[i] = byte.Parse(hex, System.Globalization.NumberStyles.HexNumber);
|
|
}
|
|
return bytes;
|
|
|
|
}
|
|
|
|
private byte[] stringToBytes(string str)
|
|
{
|
|
if (str == null) str = "";
|
|
return System.Text.Encoding.ASCII.GetBytes(str);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// FusionCharts Exporter - 'PDF Resource' handles
|
|
/// FusionCharts (since v3.1) Server Side Export feature that
|
|
/// helps FusionCharts exported as PDF file.
|
|
/// </summary>
|
|
public class FCPDFGenerator
|
|
{
|
|
|
|
//Array - Stores multiple chart export data
|
|
private ArrayList arrExportData = new ArrayList();
|
|
//stores number of pages = length of $arrExportData array
|
|
private int numPages = 0;
|
|
|
|
/// <summary>
|
|
/// Generates a PDF file with the given parameters
|
|
/// The imageData_FCFormat parameter is the FusionCharts export format data
|
|
/// width, height are the respective width and height of the original image
|
|
/// bgcolor determines the default background colour
|
|
/// </summary>
|
|
public FCPDFGenerator(string imageData_FCFormat, string width, string height, string bgcolor)
|
|
{
|
|
setBitmapData(imageData_FCFormat, width, height, bgcolor);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the binary data stream of the image
|
|
/// The passed parameter determines the file format of the image
|
|
/// to be exported
|
|
/// </summary>
|
|
public MemoryStream getBinaryStream(string strFormat)
|
|
{
|
|
byte[] exportObj = getPDFObjects(true);
|
|
|
|
MemoryStream outStream = new MemoryStream();
|
|
|
|
outStream.Write(exportObj, 0, exportObj.Length);
|
|
|
|
return outStream;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates bitmap data for the image from a FusionCharts export format
|
|
/// the height and width of the original export needs to be specified
|
|
/// the default background color should also be specified
|
|
/// </summary>
|
|
private void setBitmapData(string imageData_FCFormat, string width, string height, string bgcolor)
|
|
{
|
|
Hashtable chartExportData = new Hashtable();
|
|
chartExportData["width"] = width;
|
|
chartExportData["height"] = height;
|
|
chartExportData["bgcolor"] = bgcolor;
|
|
chartExportData["imagedata"] = imageData_FCFormat;
|
|
arrExportData.Add(chartExportData);
|
|
numPages++;
|
|
}
|
|
|
|
|
|
|
|
//create image PDF object containing the chart image
|
|
private byte[] addImageToPDF(int id, bool isCompressed)
|
|
{
|
|
|
|
MemoryStream imgObj = new MemoryStream();
|
|
|
|
//PDF Object number
|
|
int imgObjNo = 6 + id * 3;
|
|
|
|
//Get chart Image binary
|
|
byte[] imgBinary = getBitmapData24(id, isCompressed);
|
|
|
|
//get the length of the image binary
|
|
int len = imgBinary.Length;
|
|
|
|
string width = getMeta("width", id);
|
|
string height = getMeta("height", id);
|
|
|
|
//Build PDF object containing the image binary and other formats required
|
|
//string strImgObjHead = imgObjNo.ToString() + " 0 obj\n<<\n/Subtype /Image /ColorSpace /DeviceRGB /BitsPerComponent 8 /HDPI 72 /VDPI 72 " + (isCompressed ? "" : "") + "/Width " + width + " /Height " + height + " /Length " + len.ToString() + " >>\nstream\n";
|
|
string strImgObjHead = imgObjNo.ToString() + " 0 obj\n<<\n/Subtype /Image /ColorSpace /DeviceRGB /BitsPerComponent 8 /HDPI 72 /VDPI 72 " + (isCompressed ? "/Filter /RunLengthDecode " : "") + "/Width " + width + " /Height " + height + " /Length " + len.ToString() + " >>\nstream\n";
|
|
|
|
|
|
|
|
imgObj.Write(stringToBytes(strImgObjHead), 0, strImgObjHead.Length);
|
|
imgObj.Write(imgBinary, 0, (int)imgBinary.Length);
|
|
|
|
string strImgObjEnd = "endstream\nendobj\n";
|
|
imgObj.Write(stringToBytes(strImgObjEnd), 0, strImgObjEnd.Length);
|
|
|
|
imgObj.Close();
|
|
return imgObj.ToArray();
|
|
|
|
}
|
|
private byte[] addImageToPDF(int id)
|
|
{
|
|
return addImageToPDF(id, true);
|
|
}
|
|
private byte[] addImageToPDF()
|
|
{
|
|
return addImageToPDF(0, true);
|
|
}
|
|
|
|
|
|
|
|
//Main PDF builder function
|
|
private byte[] getPDFObjects()
|
|
{
|
|
return getPDFObjects(true);
|
|
}
|
|
|
|
private byte[] getPDFObjects(bool isCompressed)
|
|
{
|
|
MemoryStream PDFBytes = new MemoryStream();
|
|
|
|
//Store all PDF objects in this temporary string to be written to ByteArray
|
|
string strTmpObj = "";
|
|
|
|
|
|
//start xref array
|
|
ArrayList xRefList = new ArrayList();
|
|
xRefList.Add("xref\n0 ");
|
|
xRefList.Add("0000000000 65535 f \n"); //Address Refenrece to obj 0
|
|
|
|
//Build PDF objects sequentially
|
|
//version and header
|
|
strTmpObj = "%PDF-1.3\n%{FC}\n";
|
|
PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);
|
|
|
|
//OBJECT 1 : info (optional)
|
|
strTmpObj = "1 0 obj<<\n/Author (FusionCharts)\n/Title (FusionCharts)\n/Creator (FusionCharts)\n>>\nendobj\n";
|
|
xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 1
|
|
PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);
|
|
|
|
//OBJECT 2 : Starts with Pages Catalogue
|
|
strTmpObj = "2 0 obj\n<< /Type /Catalog /Pages 3 0 R >>\nendobj\n";
|
|
xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 2
|
|
PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);
|
|
|
|
//OBJECT 3 : Page Tree (reference to pages of the catalogue)
|
|
strTmpObj = "3 0 obj\n<< /Type /Pages /Kids [";
|
|
for (int i = 0; i < numPages; i++)
|
|
{
|
|
strTmpObj += (((i + 1) * 3) + 1) + " 0 R\n";
|
|
}
|
|
strTmpObj += "] /Count " + numPages + " >>\nendobj\n";
|
|
|
|
xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 3
|
|
PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);
|
|
|
|
|
|
//Each image page
|
|
for (int itr = 0; itr < numPages; itr++)
|
|
{
|
|
string iWidth = getMeta("width", itr);
|
|
string iHeight = getMeta("height", itr);
|
|
//OBJECT 4..7..10..n : Page config
|
|
strTmpObj = (((itr + 2) * 3) - 2) + " 0 obj\n<<\n/Type /Page /Parent 3 0 R \n/MediaBox [ 0 0 " + iWidth + " " + iHeight + " ]\n/Resources <<\n/ProcSet [ /PDF ]\n/XObject <</R" + (itr + 1) + " " + ((itr + 2) * 3) + " 0 R>>\n>>\n/Contents [ " + (((itr + 2) * 3) - 1) + " 0 R ]\n>>\nendobj\n";
|
|
xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 4,7,10,13,16...
|
|
PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);
|
|
|
|
|
|
//OBJECT 5...8...11...n : Page resource object (xobject resource that transforms the image)
|
|
xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 5,8,11,14,17...
|
|
string xObjR = getXObjResource(itr);
|
|
PDFBytes.Write(stringToBytes(xObjR), 0, xObjR.Length);
|
|
|
|
//OBJECT 6...9...12...n : Binary xobject of the page (image)
|
|
byte[] imgBA = addImageToPDF(itr, isCompressed);
|
|
xRefList.Add(calculateXPos((int)PDFBytes.Length));//refenrece to obj 6,9,12,15,18...
|
|
PDFBytes.Write(imgBA, 0, imgBA.Length);
|
|
}
|
|
|
|
//xrefs compilation
|
|
xRefList[0] += ((xRefList.Count - 1) + "\n");
|
|
|
|
//get trailer
|
|
string trailer = getTrailer((int)PDFBytes.Length, xRefList.Count - 1);
|
|
|
|
//write xref and trailer to PDF
|
|
string strXRefs = string.Join("", (string[])xRefList.ToArray(typeof(string)));
|
|
PDFBytes.Write(stringToBytes(strXRefs), 0, strXRefs.Length);
|
|
//
|
|
PDFBytes.Write(stringToBytes(trailer), 0, trailer.Length);
|
|
|
|
//write EOF
|
|
string strEOF = "%%EOF\n";
|
|
PDFBytes.Write(stringToBytes(strEOF), 0, strEOF.Length);
|
|
|
|
PDFBytes.Close();
|
|
return PDFBytes.ToArray();
|
|
|
|
}
|
|
|
|
|
|
//Build Image resource object that transforms the image from First Quadrant system to Second Quadrant system
|
|
private string getXObjResource()
|
|
{
|
|
return getXObjResource(0);
|
|
}
|
|
private string getXObjResource(int itr)
|
|
{
|
|
|
|
string width = getMeta("width", itr);
|
|
string height = getMeta("height", itr);
|
|
return (((itr + 2) * 3) - 1) + " 0 obj\n<< /Length " + (24 + (width + height).Length) + " >>\nstream\nq\n" + width + " 0 0 " + height + " 0 0 cm\n/R" + (itr + 1) + " Do\nQ\nendstream\nendobj\n";
|
|
}
|
|
|
|
private string calculateXPos(int posn)
|
|
{
|
|
return posn.ToString().PadLeft(10, '0') + " 00000 n \n";
|
|
}
|
|
|
|
|
|
private string getTrailer(int xrefpos)
|
|
{
|
|
return getTrailer(xrefpos, 7);
|
|
}
|
|
|
|
private string getTrailer(int xrefpos, int numxref)
|
|
{
|
|
return "trailer\n<<\n/Size " + numxref.ToString() + "\n/Root 2 0 R\n/Info 1 0 R\n>>\nstartxref\n" + xrefpos.ToString() + "\n";
|
|
}
|
|
|
|
|
|
private byte[] getBitmapData24()
|
|
{
|
|
return getBitmapData24(0, true);
|
|
}
|
|
private byte[] getBitmapData24(int id, bool isCompressed)
|
|
{
|
|
|
|
string rawImageData = getMeta("imagedata", id);
|
|
string bgColor = getMeta("bgcolor", id);
|
|
|
|
MemoryStream imageData24 = new MemoryStream();
|
|
|
|
// Split the data into rows using ; as separator
|
|
string[] rows = rawImageData.Split(';');
|
|
|
|
for (int yPixel = 0; yPixel < rows.Length; yPixel++)
|
|
{
|
|
//Split each row into 'color_count' columns.
|
|
string[] color_count = rows[yPixel].Split(',');
|
|
|
|
for (int col = 0; col < color_count.Length; col++)
|
|
{
|
|
//Now, if it's not empty, we process it
|
|
//Split the 'color_count' into color and repeat factor
|
|
string[] split_data = color_count[col].Split('_');
|
|
|
|
//Reference to color
|
|
string hexColor = split_data[0] != "" ? split_data[0] : bgColor;
|
|
//If the hexadecimal code is less than 6 characters, pad with 0
|
|
hexColor = hexColor.Length < 6 ? hexColor.PadLeft(6, '0') : hexColor;
|
|
|
|
//refer to repeat factor
|
|
int fRepeat = int.Parse(split_data[1]);
|
|
|
|
// convert color string to byte[] array
|
|
byte[] rgb = hexToBytes(hexColor);
|
|
|
|
// Set the repeated pixel in MemoryStream
|
|
for (int cRepeat = 0; cRepeat < fRepeat; cRepeat++)
|
|
{
|
|
imageData24.Write(rgb, 0, 3);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
int len = (int)imageData24.Length;
|
|
imageData24.Close();
|
|
|
|
//Compress image binary
|
|
if (isCompressed)
|
|
{
|
|
return new PDFCompress(imageData24.ToArray()).RLECompress();
|
|
}
|
|
else
|
|
{
|
|
return imageData24.ToArray();
|
|
}
|
|
}
|
|
|
|
// converts a hexadecimal colour string to it's respective byte value
|
|
private byte[] hexToBytes(string strHex)
|
|
{
|
|
if (strHex == null || strHex.Trim().Length == 0) strHex = "00";
|
|
strHex = Regex.Replace(strHex, @"[^0-9a-fA-f]", "");
|
|
if (strHex.Length % 2 != 0) strHex = "0" + strHex;
|
|
|
|
int len = strHex.Length / 2;
|
|
byte[] bytes = new byte[len];
|
|
|
|
for (int i = 0; i < len; i++)
|
|
{
|
|
string hex = strHex.Substring(i * 2, 2);
|
|
bytes[i] = byte.Parse(hex, System.Globalization.NumberStyles.HexNumber);
|
|
}
|
|
return bytes;
|
|
|
|
}
|
|
|
|
private string getMeta(string metaName)
|
|
{
|
|
return getMeta(metaName, 0);
|
|
}
|
|
|
|
private string getMeta(string metaName, int id)
|
|
{
|
|
if (metaName == null) metaName = "";
|
|
Hashtable chartData = (Hashtable)arrExportData[id];
|
|
return (chartData[metaName] == null ? "" : chartData[metaName].ToString());
|
|
}
|
|
|
|
private byte[] stringToBytes(string str)
|
|
{
|
|
if (str == null) str = "";
|
|
return System.Text.Encoding.ASCII.GetBytes(str);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// This is an ad-hoc class to compress PDF stream.
|
|
/// Currently this class compresses binary (byte) stream using RLE which
|
|
/// PDF 1.3 specification has thus formulated:
|
|
///
|
|
/// The RunLengthDecode filter decodes data that has been encoded in a simple
|
|
/// byte-oriented format based on run length. The encoded data is a sequence of
|
|
/// runs, where each run consists of a length byte followed by 1 to 128 bytes of data. If
|
|
/// the length byte is in the range 0 to 127, the following length + 1 (1 to 128) bytes
|
|
/// are copied literally during decompression. If length is in the range 129 to 255, the
|
|
/// following single byte is to be copied 257 - length (2 to 128) times during decompression.
|
|
/// A length value of 128 denotes EOD.
|
|
///
|
|
/// The chart image compression ratio comes to around 10:3
|
|
///
|
|
/// </summary>
|
|
public class PDFCompress
|
|
{
|
|
|
|
/// <summary>
|
|
/// stores the output compressed data in MemoryStream object later to be converted to byte[] array
|
|
/// </summary>
|
|
private MemoryStream _Compressed = new MemoryStream();
|
|
|
|
/// <summary>
|
|
/// Uncompresses data as byte[] array
|
|
/// </summary>
|
|
private byte[] _UnCompressed;
|
|
|
|
|
|
/// <summary>
|
|
/// Takes the uncompressed byte array
|
|
/// </summary>
|
|
/// <param name="UnCompressed">uncompressed data</param>
|
|
public PDFCompress(byte[] UnCompressed)
|
|
{
|
|
_UnCompressed = UnCompressed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write compressed data as RunLength
|
|
/// </summary>
|
|
/// <param name="length">The length of repeated data</param>
|
|
/// <param name="encodee">The byte to be repeated</param>
|
|
/// <returns></returns>
|
|
private int WriteRunLength(int length, byte encodee)
|
|
{
|
|
// write the repeat length
|
|
_Compressed.WriteByte((byte)(257 - length));
|
|
// write the byte to be repeated
|
|
_Compressed.WriteByte(encodee);
|
|
|
|
//re-set repeat length
|
|
length = 1;
|
|
return length;
|
|
}
|
|
|
|
private void WriteNoRepeater(MemoryStream NoRepeatBytes)
|
|
{
|
|
// write the length of non repeted data
|
|
_Compressed.WriteByte((byte)((int)NoRepeatBytes.Length - 1));
|
|
// write the non repeated data put literally
|
|
_Compressed.Write(NoRepeatBytes.ToArray(), 0, (int)NoRepeatBytes.Length);
|
|
|
|
// re-set non repeat byte storage stream
|
|
NoRepeatBytes.SetLength(0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// compresses uncompressed data to compressed data in byte array
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public byte[] RLECompress()
|
|
{
|
|
// stores non repeatable data
|
|
MemoryStream NoRepeat = new MemoryStream();
|
|
|
|
// repeat counter
|
|
int _RL = 1;
|
|
|
|
// 2 consecutive bytes to compare
|
|
byte preByte = 0, postByte = 0;
|
|
|
|
// iterate through the uncompressed bytes
|
|
for (int i = 0; i < _UnCompressed.Length - 1; i++)
|
|
{
|
|
// get 2 consecutive bytes
|
|
preByte = _UnCompressed[i];
|
|
postByte = _UnCompressed[i + 1];
|
|
|
|
// if both are same there is scope for repitition
|
|
if (preByte == postByte)
|
|
{
|
|
// but flush the non repeatable data (if present) to compressed stream
|
|
if (NoRepeat.Length > 0) WriteNoRepeater(NoRepeat);
|
|
|
|
// increase repeat count
|
|
_RL++;
|
|
|
|
// if repeat count reaches limit of repeat i.e. 128
|
|
// write the repeat data and reset the repeat counter
|
|
if (_RL > 128) _RL = WriteRunLength(_RL-1,preByte);
|
|
|
|
}
|
|
else
|
|
{
|
|
// when consecutive bytes do not match
|
|
|
|
// store non-repeatable data
|
|
if (_RL == 1) NoRepeat.WriteByte(preByte);
|
|
|
|
// write repeated length and byte (if present ) to output stream
|
|
if (_RL > 1) _RL = WriteRunLength(_RL, preByte);
|
|
|
|
// write non repeatable data to out put stream if the length reaches limit
|
|
if (NoRepeat.Length == 128) WriteNoRepeater(NoRepeat);
|
|
}
|
|
}
|
|
|
|
// at the end of iteration
|
|
// take care of the last byte
|
|
|
|
// if repeated
|
|
if (_RL > 1)
|
|
{
|
|
// write run length and byte (if present ) to output stream
|
|
_RL = WriteRunLength(_RL, preByte);
|
|
}
|
|
else
|
|
{
|
|
// if non repeated byte is left behind
|
|
// write non repeatable data to output stream
|
|
NoRepeat.WriteByte(postByte);
|
|
WriteNoRepeater(NoRepeat);
|
|
}
|
|
|
|
|
|
// wrote EOD
|
|
_Compressed.WriteByte((byte)128);
|
|
|
|
//close streams
|
|
NoRepeat.Close();
|
|
_Compressed.Close();
|
|
|
|
// return compressed data in byte array
|
|
return _Compressed.ToArray();
|
|
}
|
|
|
|
}
|
|
|