Files
2026-02-17 09:29:34 -06:00

1896 lines
77 KiB
VB.net

'*
' *
' * 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
' *
'
Imports System.IO
Imports System.Web
Imports System.Drawing
Imports System.Collections
Imports System.Collections.Generic
Imports System.Drawing.Imaging
Imports System.Text.RegularExpressions
Imports SharpVectors.Converters
Imports System.Web.Script.Serialization
''' <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>
'''
Partial Public Class FCExporter
Inherits 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 SAVE_PATH As String = "./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 HTTP_URI As String = "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 OVERWRITEFILE As Boolean = False
Private INTELLIGENTFILENAMING As Boolean = True
Private FILESUFFIXFORMAT As String = "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 MIMETYPES As String = "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 EXTENSIONS As String = "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 DEFAULTPARAMS As String = "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 notices As String = ""
''' <summary>
''' Whether the export action is download. Default value. Would change as per setting retrieved from chart.
''' </summary>
Private isDownload As Boolean = True
''' <summary>
''' DOMId of the chart
''' </summary>
Private DOMId As String
Public Property IsSVGData() As Boolean
Get
Return m_IsSVGData
End Get
Set(value As Boolean)
m_IsSVGData = Value
End Set
End Property
Private m_IsSVGData As Boolean
Public Property isImgData() As Boolean
Get
Return m_isImgData
End Get
Set(value As Boolean)
m_isImgData = Value
End Set
End Property
Private m_isImgData As Boolean
''' <summary>
''' Stores SVG information.
''' </summary>
Private svgData As TextReader
''' <summary>
''' Stores SVG in-memory file.
''' </summary>
Private svgStream As MemoryStream
Private exportData As Hashtable
''' <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 Sub Page_Load(sender As Object, e As EventArgs)
'*
' * Retrieve export data from POST Request sent by chart
' * Parse the Request stream into export data readable by this script
'
exportData = parseExportRequestStream()
' process export data and get the processed data (image/PDF) to be exported
Dim exportObject As MemoryStream = Nothing
If IsSVGData Then
If exportData("encodedImgData") IsNot Nothing AndAlso exportData("encodedImgData").ToString() <> "" AndAlso DirectCast(exportData("parameters"), Hashtable)("exportformat").ToString() = "svg" Then
exportObject = exportProcessor(DirectCast(exportData("parameters"), Hashtable)("exportformat").ToString(), exportData("svg").ToString(), DirectCast(exportData("parameters"), Hashtable), exportData("encodedImgData").ToString())
Else
exportObject = exportProcessor(DirectCast(exportData("parameters"), Hashtable)("exportformat").ToString(), "svg", DirectCast(exportData("parameters"), Hashtable))
End If
Else
If isImgData Then
convertRawImageDataToFile(exportData)
Else
If exportData("encodedImgData") IsNot Nothing AndAlso exportData("encodedImgData").ToString() <> "" AndAlso DirectCast(exportData("parameters"), Hashtable)("exportformat").ToString() = "svg" Then
exportObject = exportProcessor(DirectCast(exportData("parameters"), Hashtable)("exportformat").ToString(), "svg", DirectCast(exportData("parameters"), Hashtable), exportData("encodedImgData").ToString())
Else
exportObject = exportProcessor(DirectCast(exportData("parameters"), Hashtable)("exportformat").ToString(), "svg", DirectCast(exportData("parameters"), Hashtable))
End If
End If
End If
'
' * 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"]);
Dim exportedStatus As Object = outputExportObject(exportObject, DirectCast(exportData("parameters"), Hashtable))
' 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, DirectCast(exportData("meta"), Hashtable))
End Sub
''' <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 Function parseExportRequestStream() As Hashtable
' store all export data
Dim exportData As New Hashtable()
Dim svgStr As String = ""
IsSVGData = False
Dim test As String = Request("stream_type")
If Request("stream_type") = "svg" Then
IsSVGData = True
exportData("svg") = Request("stream")
' Added custom parameter
exportData("exporttargetwindow") = "_self"
'Get all export parameters into a Hastable
Dim parameters As Hashtable = 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 &nbsp; string occurred in some data source specifically excel
' need to look for a more proper method which covers all such situations
svgStr = svgStr.Replace("&nbsp;", " ")
exportData("svg") = svgStr
If Request("encodedImgData") IsNot Nothing Then
exportData("encodedImgData") = Request("encodedImgData")
End If
Dim svg As Byte() = System.Text.Encoding.UTF8.GetBytes(exportData("svg").ToString())
svgStream = New MemoryStream(svg)
svgData = New StreamReader(svgStream)
ElseIf Request("stream_type") = "IMAGE-DATA" Then
'for modern browser exporting
convertRAWImageDataToFile(Request("stream"), Request("parameters"))
ElseIf Request("stream_type") = "image-data" Then
' If Flash Charts
If Request("stream_type") = "image-data" Then
isImgData = True
If Request("stream") Is Nothing OrElse Request("stream").Trim() = "" Then
raise_error("100", True)
End If
exportData("stream") = Request("stream").Trim().Replace(" ", "+")
exportData("stream") = exportData("stream").ToString().Substring(exportData("stream").ToString().IndexOf(",") + 1)
Dim parametersArray As [String]() = Request("parameters").ToString().Split("|"c)
exportData("exportfilename") = parametersArray(0).Split("="c)(1)
exportData("exportformat") = parametersArray(1).Split("="c)(1).ToLower()
Else
isImgData = False
'String of compressed image data
exportData("stream") = Request("stream")
'Halt execution if image stream is not provided.
If Request("stream") Is Nothing OrElse Request("stream").Trim() = "" Then
raise_error("100", True)
End If
'Get all export parameters into a Hastable
Dim parameters As Hashtable = parseParams(Request("parameters"))
exportData("parameters") = parameters
End If
End If
'get width and height of the chart
Dim meta As New Hashtable()
meta("width") = Request("meta_width")
'Halt execution on error
If Request("meta_width") Is Nothing OrElse Request("meta_width").Trim() = "" Then
raise_error("101", True)
End If
meta("height") = Request("meta_height")
'Halt execution on error
If Request("meta_height") Is Nothing OrElse Request("meta_height").Trim() = "" Then
raise_error("101", True)
End If
'Background color of chart
meta("bgcolor") = Request("meta_bgColor")
If meta("bgcolor") Is Nothing OrElse meta("bgcolor").ToString().Trim() = "" Then
' 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"
End If
' DOMId of the chart
meta("DOMId") = If(Request("meta_DOMId") Is Nothing, "", Request("meta_DOMId"))
DOMId = meta("DOMId").ToString()
exportData("meta") = meta
Return exportData
End Function
''' <summary>
''' Decode a base64 encoded string
''' </summary>
''' <param name="data">A base64 encoded string </param>
''' <returns>String decoded from input </returns>
Private Function base64Decode(data As String) As Byte()
Return Convert.FromBase64String(data)
End Function
Private Sub convertRAWImageDataToFile(imageData As String, parameters As String)
Dim fileName As String = parameters.Split("|"c)(0).Split("="c)(1), extention As String = parameters.Split("|"c)(1).Split("="c)(1), exportAction As String = parameters.Split("|"c)(2).Split("="c)(1), fullFileName As String = fileName & "." & extention, filLocation As String = HttpContext.Current.Server.MapPath("~/Exported_Images/" & fullFileName)
Dim contentType As String = getMime(extention)
Dim bytes As Byte() = base64Decode(imageData.Split(","c)(1))
File.WriteAllBytes(filLocation, bytes)
If exportAction = "download" Then
Response.ClearContent()
Response.AddHeader("Content-Disposition", "attachment; filename=" & fullFileName)
Response.ContentType = contentType
Response.TransmitFile(filLocation)
Response.[End]()
End If
End Sub
Private Sub convertRawImageDataToFile(exportData As Hashtable)
Dim fileName As [String] = ""
fileName = Server.MapPath(".") & SAVE_PATH & exportData("exportfilename").ToString() & "." & exportData("exportformat").ToString().ToLower()
System.IO.File.WriteAllBytes(fileName, base64Decode(exportData("stream").ToString()))
Dim data As Byte() = System.IO.File.ReadAllBytes(fileName)
Dim mime As String = getMime(exportData("exportformat").ToString())
Response.ContentType = mime
Response.AddHeader("Content-Disposition", "attachment" & "; filename=""" & fileName & "." & exportData("exportformat").ToString().ToLower() & """")
Dim retStatus As New Hashtable()
retStatus("filepath") = ""
' set the output strem to Response stream as the file is going to be downloaded
retStatus("outStream") = Response.OutputStream
Dim outStream As Stream = DirectCast(retStatus("outStream"), Stream)
outStream.Flush()
outStream.Close()
Response.[End]()
End Sub
''' <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 Function parseParams(strParams As String) As Hashtable
'default parameter values
Dim defaultParameterValues As Hashtable = bang(DEFAULTPARAMS)
' get parameters
Dim parameters As Hashtable = bang(strParams, New Char() {"|"c, "="c})
' sync with default values
' iterate through each default parameter value
For Each param As DictionaryEntry 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) Is Nothing Then
parameters(param.Key) = param.Value.ToString()
End If
Next
' 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
End Function
''' <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 Function getImageData(imageData As Dictionary(Of String, Dictionary(Of String, String)), imageName As String) As String
Dim data As String = ""
For Each key As String In imageData.Keys
If (imageData(key)("name") & "." & imageData(key)("type")) = imageName Then
data = imageData(key)("encodedData")
Exit For
End If
Next
Return data
End Function
Private Function stichImageToSVG(svgData As String, imageData As String) As String
Dim ser As New JavaScriptSerializer()
Dim data = ser.Deserialize(Of Dictionary(Of String, Dictionary(Of String, String)))(imageData)
Dim rawImageDataArray As New List(Of String)()
Dim hrefArray As New List(Of String)()
' /(<image[^>]*xlink:href *= *[\"']?)([^\"']*)/i
Dim regex As New Regex("<image.+?xlink:href=""(.+?)"".+?/?>")
Dim counter As Integer = 0
For Each match As Match In regex.Matches(svgData)
Dim temp1 As String() = match.Value.Split(New String() {"xlink:href="}, StringSplitOptions.None)
hrefArray.Add(temp1(1).Split(""""c)(1))
Dim imageNameArray As String() = hrefArray(counter).Split("/"c)
rawImageDataArray.Add(getImageData(data, imageNameArray(imageNameArray.Length - 1)))
counter += 1
Next
For index As Integer = 0 To rawImageDataArray.Count - 1
svgData = svgData.Replace(hrefArray(index), rawImageDataArray(index))
Next
Return svgData
End Function
Private Function stichImageToSVGAndGetString(svgData As String, imageData As String) As String
Return stichImageToSVG(svgData, imageData)
End Function
Private Function stichImageToSVGAndGetStream(svgData As String, imageData As String) As MemoryStream
svgData = stichImageToSVG(svgData, imageData)
Dim svg As Byte() = System.Text.Encoding.UTF8.GetBytes(svgData.ToString())
Return New MemoryStream(svg)
End Function
''' <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>
''' <param name="meta">{string)Image data</param>
''' <returns></returns>
Private Function exportProcessor(strFormat As String, stream As String, meta As Hashtable, imageData As String) As MemoryStream
Return stichImageToSVGAndGetStream(stream, imageData)
End Function
Private Function exportProcessor(strFormat As String, stream As String, meta As Hashtable) As MemoryStream
If exportData("encodedImgData") IsNot Nothing AndAlso exportData("encodedImgData").ToString() <> "" Then
svgStream = stichImageToSVGAndGetStream(exportData("svg").ToString(), exportData("encodedImgData").ToString())
svgData = New StringReader(stichImageToSVGAndGetString(exportData("svg").ToString(), exportData("encodedImgData").ToString()))
End If
strFormat = strFormat.ToLower()
' initilize memeory stream object to store output bytes
Dim exportObjectStream As New MemoryStream()
' Handle Export class as per export format
Select Case strFormat
Case "pdf"
If Not IsSVGData Then
' Instantiate Export class for PDF, build Binary stream and store in stream object
Dim PDFGEN As New FCPDFGenerator(stream, meta("width").ToString(), meta("height").ToString(), meta("bgcolor").ToString())
exportObjectStream = PDFGEN.getBinaryStream(strFormat)
Else
exportObjectStream = GetJSImage(meta, True)
End If
Exit Select
Case "jpg", "jpeg", "png", "gif"
If Not IsSVGData Then
' Instantiate Export class for Images, build Binary stream and store in stream object
Dim IMGGEN As New FCIMGGenerator(stream, meta("width").ToString(), meta("height").ToString(), meta("bgcolor").ToString())
exportObjectStream = IMGGEN.getBinaryStream(strFormat)
Else
exportObjectStream = GetJSImage(meta, False)
End If
Exit Select
Case "svg"
exportObjectStream = svgStream
Exit Select
Case Else
' In case the format is not recognized
raise_error(" Invalid Export Format.", True)
Exit Select
End Select
Return exportObjectStream
End Function
Private Function GetJSImage(exportData As Hashtable, processPdf As Boolean) As MemoryStream
Dim exportObjectStream As New MemoryStream()
'string filename = exportData["filename"].ToString();
Dim type As String = exportData("exportformat").ToString().ToLower()
If processPdf Then
type = "jpg"
End If
Dim ds As New SharpVectors.Renderers.Wpf.WpfDrawingSettings()
Dim ssc As New StreamSvgConverter(ds)
ssc.SaveXaml = False
ssc.SaveZaml = False
Dim encoder As ImageEncoderType = ImageEncoderType.JpegBitmap
Select Case type
Case "png"
encoder = ImageEncoderType.PngBitmap
Exit Select
Case "jpeg"
encoder = ImageEncoderType.JpegBitmap
Exit Select
End Select
ssc.EncoderType = encoder
ssc.SaveXaml = False
If ssc.Convert(svgData, exportObjectStream) Then
If processPdf Then
Dim PDFGEN As New FCJSPDFGenerator(True, exportObjectStream, ssc.Drawing.Bounds.Width.ToString(), ssc.Drawing.Bounds.Height.ToString())
exportObjectStream = PDFGEN.getBinaryStream(type)
End If
End If
svgData.Close()
svgData.Dispose()
svgStream.Close()
svgStream.Dispose()
Return exportObjectStream
End Function
''' <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 Function outputExportObject(exportObj As MemoryStream, exportParams As Hashtable) As Object
'pass export paramters and get back export settings as per export action
Dim exportActionSettings As Hashtable = (If(isDownload, setupDownload(exportParams), setupServer(exportParams)))
' set default export status to true
Dim status As Boolean = 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 TypeOf exportActionSettings("filepath") Is Boolean Then
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
Dim outStream As Stream = DirectCast(exportActionSettings("outStream"), Stream)
exportObj.WriteTo(outStream)
outStream.Flush()
outStream.Close()
exportObj.Close()
Catch e As ArgumentNullException
raise_error(" Failed to export. Error:" & e.Message)
status = False
Catch e As ObjectDisposedException
raise_error(" Failed to export. Error:" & e.Message)
status = False
End Try
If isDownload Then
' If 'download'- terminate imediately
' As nothing is to be written to response now.
Response.[End]()
End If
End If
' This is the response after save action
' If status remains true return the 'filepath'. Otherwise return false to denote failure.
Return (If(status, exportActionSettings("filepath"), False))
End Function
''' <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 Sub flushStatus(filename As Object, meta As Hashtable, msg As String)
' Process and flush message to response stream and terminate
Response.Output.Write(buildResponse(parseExportedStatus(filename, meta, msg)))
Response.Flush()
Response.[End]()
End Sub
''' <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 Sub flushStatus(filename As Object, meta As Hashtable)
flushStatus(filename, meta, "")
End Sub
''' <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 Function parseExportedStatus(filename As Object, meta As Hashtable, msg As String) As ArrayList
Dim arrStatus As New ArrayList()
' get status
Dim status As Boolean = (If(TypeOf filename Is String, True, False))
' add notices
If notices.Trim() <> "" Then
arrStatus.Add("notice=" & notices.Trim())
End If
' DOMId of the chart
arrStatus.Add("DOMId=" & (If(meta("DOMId") Is Nothing, DOMId, meta("DOMId").ToString())))
' add width and height
' provide 0 as width and height on failure
If meta("width") Is Nothing Then
meta("width") = "0"
End If
If meta("height") Is Nothing Then
meta("height") = "0"
End If
arrStatus.Add("height=" & (If(status, meta("height").ToString(), "0")))
arrStatus.Add("width=" & (If(status, meta("width").ToString(), "0")))
' add file URI
arrStatus.Add("fileName=" & (If(status, (Regex.Replace(HTTP_URI, "([^\/]$)", "${1}/") & Convert.ToString(filename)), "")))
arrStatus.Add("statusMessage=" & (If(msg.Trim() <> "", msg.Trim(), (If(status, "Success", "Failure")))))
arrStatus.Add("statusCode=" & (If(status, "1", "0")))
Return arrStatus
End Function
''' <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 Function buildResponse(arrMsg As ArrayList) As String
' 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.
Dim msg As String = If(isDownload, "", "&")
msg += String.Join((If(isDownload, "<br>", "&")), DirectCast(arrMsg.ToArray(GetType(String)), String()))
Return msg
End Function
''' <summary>
''' Finds if a directory is writable
''' </summary>
''' <param name="path">String Path</param>
''' <returns></returns>
Private Function isDirectoryWritable(path As String) As Boolean
Dim info As New DirectoryInfo(path)
Return (info.Attributes And FileAttributes.[ReadOnly]) <> FileAttributes.[ReadOnly]
End Function
''' <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 Function setupServer(exportParams As Hashtable) As Hashtable
'get export file name
Dim exportFile As String = exportParams("exportfilename").ToString()
' get extension related to specified type
Dim ext As String = getExtension(exportParams("exportformat").ToString())
Dim retServerStatus As 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
Dim fos As FileStream
' process SAVE_PATH : the path where export file would be saved
' add a / at the end of path if / is absent at the end
Dim path As String = SAVE_PATH
' if path is null set it to folder where FCExporter.aspx is present
If path.Trim() = "" Then
path = "./"
End If
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 e As HttpException
raise_error(e.Message)
End Try
' check whether directory exists
' raise error and halt execution if directory does not exists
If Not Directory.Exists(path) Then
raise_error(" Server Directory does not exist.", True)
End If
' check if directory is writable or not
Dim dirWritable As Boolean = isDirectoryWritable(path)
' build filepath
retServerStatus("filepath") = exportFile & "." & ext
' check whether file exists
If Not File.Exists(path & retServerStatus("filepath").ToString()) Then
' 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 Then
' 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)
End If
End If
' add notice that file exists
raise_error(" File already exists.")
'if overwrite is on return with ready flag
If OVERWRITEFILE Then
' 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 Then
raise_error(" Overwrite forbidden. File cannot be overwritten.", True)
End If
' 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
End If
' raise error and halt execution when overwrite is off and intelligent naming is off
If Not INTELLIGENTFILENAMING Then
raise_error(" Export handler's Overwrite setting is off. Cannot overwrite.", True)
End If
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 Then
' 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)
End If
' in any unknown case the export should not execute
retServerStatus("ready") = False
raise_error(" Not exported due to unknown reasons.")
Return retServerStatus
End Function
''' <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 Function setupDownload(exportParams As Hashtable) As Hashtable
'get export filename
Dim exportFile As String = exportParams("exportfilename").ToString()
'get extension
Dim ext As String = getExtension(exportParams("exportformat").ToString())
'get mime type
Dim mime As String = getMime(exportParams("exportformat").ToString())
' get target window
Dim target As String = 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", (If(target = "_self", "attachment", "inline")) & "; filename=""" & exportFile & "." & ext & """")
' return exportSetting array. 'Ready' key should be set to 'download'
Dim retStatus As 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
End Function
''' <summary>
''' gets file extension checking the export type.
''' </summary>
''' <param name="exportType">(string) export format</param>
''' <returns>string extension name</returns>
Private Function getExtension(exportType As String) As String
' get a Hashtable array of [type=> extension]
' from EXTENSIONS constant
Dim extensionList As Hashtable = bang(EXTENSIONS)
exportType = exportType.ToLower()
' if extension type is present in $extensionList return it, otherwise return the type
Return (If(extensionList(exportType).ToString() IsNot Nothing, extensionList(exportType).ToString(), exportType))
End Function
''' <summary>
''' gets mime type for an export type
''' </summary>
''' <param name="exportType">Export format</param>
''' <returns>Mime type as stirng</returns>
Private Function getMime(exportType As String) As String
' get a Hashtable array of [type=> extension]
' from MIMETYPES constant
Dim mimelist As Hashtable = bang(MIMETYPES)
Dim ext As String = getExtension(exportType)
' get mime type asociated to extension
Dim mime As String = If(mimelist(ext).ToString() IsNot Nothing, mimelist(ext).ToString(), "")
Return mime
End Function
''' <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 Function generateIntelligentFileId() As String
' Generate Guid
Dim guid As String = System.Guid.NewGuid().ToString("D")
' check FILESUFFIXFORMAT type
If FILESUFFIXFORMAT.ToLower() = "timestamp" Then
' Add time stamp with file name
guid += "_" & DateTime.Now.ToString("ddMMyyyyHHmmssff")
Else
' Add Random Number with fileName
guid += "_" & (New Random()).[Next]().ToString()
End If
Return guid
End Function
''' <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 Function bang(str As String, delimiterList As Char()) As Hashtable
Dim retArray As New Hashtable()
' split string as per first delimiter
If str Is Nothing OrElse str.Trim() = "" Then
Return retArray
End If
Dim tmpArray As String() = str.Split(delimiterList(0))
' iterate through each element of split string
For i As Integer = 0 To tmpArray.Length - 1
' split each element as per second delimiter
Dim tmp2Array As String() = tmpArray(i).Split(delimiterList(1))
If tmp2Array.Length >= 2 Then
' 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)
End If
Next
Return retArray
End Function
Private Function bang(str As String) As Hashtable
Return bang(str, New Char(1) {";"c, "="c})
End Function
Private Sub raise_error(msg As String)
raise_error(msg, False)
End Sub
''' <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 Sub raise_error(msg As String, halt As Boolean)
Dim errMessages As 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.
Dim err_message As String = (If((msg Is Nothing OrElse msg.Trim() = ""), "ERROR!", (If(errMessages(msg) Is Nothing, msg, errMessages(msg).ToString()))))
' Halt executon after flushing the error message to response (if halt is true)
If halt Then
flushStatus(False, New Hashtable(), err_message)
Else
' add error to notices global variable
notices += err_message
End If
End Sub
End Class
''' <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 arrExportData As New ArrayList()
'stores number of pages = length of $arrExportData array
Private numPages As Integer = 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 Sub New(imageData_FCFormat As String, width As String, height As String, bgcolor As String)
setBitmapData(imageData_FCFormat, width, height, bgcolor)
End Sub
''' <summary>
''' Gets the binary data stream of the image
''' The passed parameter determines the file format of the image
''' to be exported
''' </summary>
Public Function getBinaryStream(strFormat As String) As MemoryStream
' the image object
Dim exportObj As Bitmap = getImageObject()
' initiates a new binary data sream
Dim outStream As New MemoryStream()
' determines the image format
Select Case strFormat
Case "jpg", "jpeg"
exportObj.Save(outStream, ImageFormat.Jpeg)
Exit Select
Case "png"
exportObj.Save(outStream, ImageFormat.Png)
Exit Select
Case "gif"
exportObj.Save(outStream, ImageFormat.Gif)
Exit Select
Case "tiff"
exportObj.Save(outStream, ImageFormat.Tiff)
Exit Select
Case Else
exportObj.Save(outStream, ImageFormat.Bmp)
Exit Select
End Select
exportObj.Dispose()
Return outStream
End Function
''' <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 Sub setBitmapData(imageData_FCFormat As String, width As String, height As String, bgcolor As String)
Dim chartExportData As New Hashtable()
chartExportData("width") = width
chartExportData("height") = height
chartExportData("bgcolor") = bgcolor
chartExportData("imagedata") = imageData_FCFormat
arrExportData.Add(chartExportData)
numPages += 1
End Sub
''' <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 Function getImageObject(id As Integer) As Bitmap
Dim rawImageData As Hashtable = DirectCast(arrExportData(id), Hashtable)
' create blank bitmap object which would store image pixel data
Dim image As New Bitmap(Convert.ToInt16(rawImageData("width")), Convert.ToInt16(rawImageData("height")), System.Drawing.Imaging.PixelFormat.Format24bppRgb)
' drwaing surface
Dim gr As Graphics = Graphics.FromImage(image)
' set background color
gr.Clear(ColorTranslator.FromHtml("#" & rawImageData("bgcolor").ToString()))
Dim rows As String() = rawImageData("imagedata").ToString().Split(";"c)
For yPixel As Integer = 0 To rows.Length - 1
'Split each row into 'color_count' columns.
Dim color_count As [String]() = rows(yPixel).Split(","c)
'Set horizontal row index to 0
Dim xPixel As Integer = 0
For col As Integer = 0 To color_count.Length - 1
'Now, if it's not empty, we process it
'Split the 'color_count' into color and repeat factor
Dim split_data As [String]() = color_count(col).Split("_"c)
'Reference to color
Dim hexColor As String = split_data(0)
'refer to repeat factor
Dim fRepeat As Integer = Integer.Parse(split_data(1))
'If color is not empty (i.e. not background pixel)
If hexColor <> "" Then
'If the hexadecimal code is less than 6 characters, pad with 0
hexColor = If(hexColor.Length < 6, hexColor.PadLeft(6, "0"c), hexColor)
For k As Integer = 1 To fRepeat
'draw pixel with specified color
image.SetPixel(xPixel, yPixel, ColorTranslator.FromHtml("#" & hexColor))
'Increment horizontal row count
xPixel += 1
Next
Else
'Just increment horizontal index
xPixel += fRepeat
End If
Next
Next
gr.Dispose()
Return image
End Function
''' <summary>
''' Retreives the bitmap image object
''' </summary>
Private Function getImageObject() As Bitmap
Return getImageObject(0)
End Function
End Class
''' <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 arrExportData As New ArrayList()
'stores number of pages = length of $arrExportData array
Private numPages As Integer = 1
Private _IsJsChart As Boolean = False
Private _ImagePath As String = ""
Private _ImageStream As MemoryStream
Private _width As String = "", _height As String = ""
Public Sub New(IsJsChart As Boolean, fileName As String, width As String, height As String)
Me._IsJsChart = IsJsChart
Me._ImagePath = fileName
Me._width = width
Me._height = height
End Sub
Public Sub New(IsJsChart As Boolean, ImageStream As MemoryStream, width As String, height As String)
Me._IsJsChart = IsJsChart
Me._ImageStream = ImageStream
Me._width = width
Me._height = height
End Sub
''' <summary>
''' Gets the binary data stream of the image
''' The passed parameter determines the file format of the image
''' to be exported
''' </summary>
Public Function getBinaryStream(strFormat As String) As MemoryStream
Dim exportObj As Byte() = getPDFObjects(False)
Dim outStream As New MemoryStream()
outStream.Write(exportObj, 0, exportObj.Length)
Return outStream
End Function
'create image PDF object containing the chart image
Private Function addImageToPDF(id As Integer, isCompressed As Boolean) As Byte()
Dim imgObj As New MemoryStream()
'PDF Object number
Dim imgObjNo As Integer = 6 + id * 3
'Get chart Image binary
Dim imgBinary As Byte() = getBitmapData24(Me._ImageStream)
'get the length of the image binary
Dim len As Integer = imgBinary.Length
Dim width As String = Me._width
Dim height As String = Me._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.
Dim strImgObjHead As String = imgObjNo.ToString() & " 0 obj" & vbLf & "<<" & vbLf & "/Subtype /Image /Filter /DCTDecode /ColorSpace /DeviceRGB /BitsPerComponent 8 /Width " & width & " /Height " & height & " /Length " & len.ToString() & " >>" & vbLf & "stream" & vbLf
imgObj.Write(stringToBytes(strImgObjHead), 0, strImgObjHead.Length)
imgObj.Write(imgBinary, 0, CInt(imgBinary.Length))
Dim strImgObjEnd As String = "endstream" & vbLf & "endobj" & vbLf
imgObj.Write(stringToBytes(strImgObjEnd), 0, strImgObjEnd.Length)
imgObj.Close()
Return imgObj.ToArray()
End Function
Private Function getPDFObjects(isCompressed As Boolean) As Byte()
Dim PDFBytes As New MemoryStream()
'Store all PDF objects in this temporary string to be written to ByteArray
Dim strTmpObj As String = ""
'start xref array
Dim xRefList As New ArrayList()
xRefList.Add("xref" & vbLf & "0 ")
xRefList.Add("0000000000 65535 f " & vbLf)
'Address Refenrece to obj 0
'Build PDF objects s equentially
'version and header
strTmpObj = "%PDF-1.3" & vbLf & "%{FC}" & vbLf
PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length)
'OBJECT 1 : info (optional)
strTmpObj = "1 0 obj<<" & vbLf & "/Author (FusionCharts)" & vbLf & "/Title (FusionCharts)" & vbLf & "/Creator (FusionCharts)" & vbLf & ">>" & vbLf & "endobj" & vbLf
xRefList.Add(calculateXPos(CInt(PDFBytes.Length)))
'refenrece to obj 1
PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length)
'OBJECT 2 : Starts with Pages Catalogue
strTmpObj = "2 0 obj" & vbLf & "<< /Type /Catalog /Pages 3 0 R >>" & vbLf & "endobj" & vbLf
xRefList.Add(calculateXPos(CInt(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" & vbLf & "<< /Type /Pages /Kids ["
For i As Integer = 0 To numPages - 1
strTmpObj += (((i + 1) * 3) + 1) & " 0 R" & vbLf
Next
strTmpObj += "] /Count " & numPages & " >>" & vbLf & "endobj" & vbLf
xRefList.Add(calculateXPos(CInt(PDFBytes.Length)))
'refenrece to obj 3
PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length)
Dim itr As Integer = 0
Dim iWidth As String = Me._width
Dim iHeight As String = Me._height
'OBJECT 4..7..10..n : Page config
strTmpObj = (((itr + 2) * 3) - 2) & " 0 obj" & vbLf & "<<" & vbLf & "/Type /Page /Parent 3 0 R " & vbLf & "/MediaBox [ 0 0 " & iWidth & " " & iHeight & " ]" & vbLf & "/Resources <<" & vbLf & "/ProcSet [ /PDF ]" & vbLf & "/XObject <</R" & (itr + 1) & " " & ((itr + 2) * 3) & " 0 R>>" & vbLf & ">>" & vbLf & "/Contents [ " & (((itr + 2) * 3) - 1) & " 0 R ]" & vbLf & ">>" & vbLf & "endobj" & vbLf
xRefList.Add(calculateXPos(CInt(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(CInt(PDFBytes.Length)))
'refenrece to obj 5,8,11,14,17...
Dim xObjR As String = getXObjResource(itr)
PDFBytes.Write(stringToBytes(xObjR), 0, xObjR.Length)
'OBJECT 6...9...12...n : Binary xobject of the page (image)
Dim imgBA As Byte() = addImageToPDF(itr, isCompressed)
xRefList.Add(calculateXPos(CInt(PDFBytes.Length)))
'refenrece to obj 6,9,12,15,18...
PDFBytes.Write(imgBA, 0, imgBA.Length)
'xrefs compilation
xRefList(0) += ((xRefList.Count - 1) & vbLf)
'get trailer
Dim trailer As String = getTrailer(CInt(PDFBytes.Length), xRefList.Count - 1)
'write xref and trailer to PDF
Dim strXRefs As String = String.Join("", DirectCast(xRefList.ToArray(GetType(String)), String()))
PDFBytes.Write(stringToBytes(strXRefs), 0, strXRefs.Length)
'
PDFBytes.Write(stringToBytes(trailer), 0, trailer.Length)
'write EOF
Dim strEOF As String = "%%EOF" & vbLf
PDFBytes.Write(stringToBytes(strEOF), 0, strEOF.Length)
PDFBytes.Close()
Return PDFBytes.ToArray()
End Function
'Build Image resource object that transforms the image from First Quadrant system to Second Quadrant system
Private Function getXObjResource() As String
Return getXObjResource(0)
End Function
Private Function getXObjResource(itr As Integer) As String
Dim width As String = Me._width
Dim height As String = Me._height
Return (((itr + 2) * 3) - 1) & " 0 obj" & vbLf & "<< /Length " & (24 + (width & height).Length) & " >>" & vbLf & "stream" & vbLf & "q" & vbLf & width & " 0 0 " & height & " 0 0 cm" & vbLf & "/R" & (itr + 1) & " Do" & vbLf & "Q" & vbLf & "endstream" & vbLf & "endobj" & vbLf
End Function
Private Function calculateXPos(posn As Integer) As String
Return posn.ToString().PadLeft(10, "0"c) & " 00000 n " & vbLf
End Function
Private Function getTrailer(xrefpos As Integer) As String
Return getTrailer(xrefpos, 7)
End Function
Private Function getTrailer(xrefpos As Integer, numxref As Integer) As String
Return "trailer" & vbLf & "<<" & vbLf & "/Size " & numxref.ToString() & vbLf & "/Root 2 0 R" & vbLf & "/Info 1 0 R" & vbLf & ">>" & vbLf & "startxref" & vbLf & xrefpos.ToString() & vbLf
End Function
Private Function getBitmapData24(fileName As String) As Byte()
Return File.ReadAllBytes(fileName)
End Function
Private Function getBitmapData24(ImageStream As MemoryStream) As Byte()
Return ImageStream.ToArray()
End Function
' converts a hexadecimal colour string to it's respective byte value
Private Function hexToBytes(strHex As String) As Byte()
If strHex Is Nothing OrElse strHex.Trim().Length = 0 Then
strHex = "00"
End If
strHex = Regex.Replace(strHex, "[^0-9a-fA-f]", "")
If strHex.Length Mod 2 <> 0 Then
strHex = "0" & strHex
End If
Dim len As Integer = strHex.Length \ 2
Dim bytes As Byte() = New Byte(len - 1) {}
For i As Integer = 0 To len - 1
Dim hex As String = strHex.Substring(i * 2, 2)
bytes(i) = Byte.Parse(hex, System.Globalization.NumberStyles.HexNumber)
Next
Return bytes
End Function
Private Function stringToBytes(str As String) As Byte()
If str Is Nothing Then
str = ""
End If
Return System.Text.Encoding.ASCII.GetBytes(str)
End Function
End Class
''' <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 arrExportData As New ArrayList()
'stores number of pages = length of $arrExportData array
Private numPages As Integer = 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 Sub New(imageData_FCFormat As String, width As String, height As String, bgcolor As String)
setBitmapData(imageData_FCFormat, width, height, bgcolor)
End Sub
''' <summary>
''' Gets the binary data stream of the image
''' The passed parameter determines the file format of the image
''' to be exported
''' </summary>
Public Function getBinaryStream(strFormat As String) As MemoryStream
Dim exportObj As Byte() = getPDFObjects(True)
Dim outStream As New MemoryStream()
outStream.Write(exportObj, 0, exportObj.Length)
Return outStream
End Function
''' <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 Sub setBitmapData(imageData_FCFormat As String, width As String, height As String, bgcolor As String)
Dim chartExportData As New Hashtable()
chartExportData("width") = width
chartExportData("height") = height
chartExportData("bgcolor") = bgcolor
chartExportData("imagedata") = imageData_FCFormat
arrExportData.Add(chartExportData)
numPages += 1
End Sub
'create image PDF object containing the chart image
Private Function addImageToPDF(id As Integer, isCompressed As Boolean) As Byte()
Dim imgObj As New MemoryStream()
'PDF Object number
Dim imgObjNo As Integer = 6 + id * 3
'Get chart Image binary
Dim imgBinary As Byte() = getBitmapData24(id, isCompressed)
'get the length of the image binary
Dim len As Integer = imgBinary.Length
Dim width As String = getMeta("width", id)
Dim height As String = 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";
Dim strImgObjHead As String = imgObjNo.ToString() & " 0 obj" & vbLf & "<<" & vbLf & "/Subtype /Image /ColorSpace /DeviceRGB /BitsPerComponent 8 /HDPI 72 /VDPI 72 " & (If(isCompressed, "/Filter /RunLengthDecode ", "")) & "/Width " & width & " /Height " & height & " /Length " & len.ToString() & " >>" & vbLf & "stream" & vbLf
imgObj.Write(stringToBytes(strImgObjHead), 0, strImgObjHead.Length)
imgObj.Write(imgBinary, 0, CInt(imgBinary.Length))
Dim strImgObjEnd As String = "endstream" & vbLf & "endobj" & vbLf
imgObj.Write(stringToBytes(strImgObjEnd), 0, strImgObjEnd.Length)
imgObj.Close()
Return imgObj.ToArray()
End Function
Private Function addImageToPDF(id As Integer) As Byte()
Return addImageToPDF(id, True)
End Function
Private Function addImageToPDF() As Byte()
Return addImageToPDF(0, True)
End Function
'Main PDF builder function
Private Function getPDFObjects() As Byte()
Return getPDFObjects(True)
End Function
Private Function getPDFObjects(isCompressed As Boolean) As Byte()
Dim PDFBytes As New MemoryStream()
'Store all PDF objects in this temporary string to be written to ByteArray
Dim strTmpObj As String = ""
'start xref array
Dim xRefList As New ArrayList()
xRefList.Add("xref" & vbLf & "0 ")
xRefList.Add("0000000000 65535 f " & vbLf)
'Address Refenrece to obj 0
'Build PDF objects sequentially
'version and header
strTmpObj = "%PDF-1.3" & vbLf & "%{FC}" & vbLf
PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length)
'OBJECT 1 : info (optional)
strTmpObj = "1 0 obj<<" & vbLf & "/Author (FusionCharts)" & vbLf & "/Title (FusionCharts)" & vbLf & "/Creator (FusionCharts)" & vbLf & ">>" & vbLf & "endobj" & vbLf
xRefList.Add(calculateXPos(CInt(PDFBytes.Length)))
'refenrece to obj 1
PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length)
'OBJECT 2 : Starts with Pages Catalogue
strTmpObj = "2 0 obj" & vbLf & "<< /Type /Catalog /Pages 3 0 R >>" & vbLf & "endobj" & vbLf
xRefList.Add(calculateXPos(CInt(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" & vbLf & "<< /Type /Pages /Kids ["
For i As Integer = 0 To numPages - 1
strTmpObj += (((i + 1) * 3) + 1) & " 0 R" & vbLf
Next
strTmpObj += "] /Count " & numPages & " >>" & vbLf & "endobj" & vbLf
xRefList.Add(calculateXPos(CInt(PDFBytes.Length)))
'refenrece to obj 3
PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length)
'Each image page
For itr As Integer = 0 To numPages - 1
Dim iWidth As String = getMeta("width", itr)
Dim iHeight As String = getMeta("height", itr)
'OBJECT 4..7..10..n : Page config
strTmpObj = (((itr + 2) * 3) - 2) & " 0 obj" & vbLf & "<<" & vbLf & "/Type /Page /Parent 3 0 R " & vbLf & "/MediaBox [ 0 0 " & iWidth & " " & iHeight & " ]" & vbLf & "/Resources <<" & vbLf & "/ProcSet [ /PDF ]" & vbLf & "/XObject <</R" & (itr + 1) & " " & ((itr + 2) * 3) & " 0 R>>" & vbLf & ">>" & vbLf & "/Contents [ " & (((itr + 2) * 3) - 1) & " 0 R ]" & vbLf & ">>" & vbLf & "endobj" & vbLf
xRefList.Add(calculateXPos(CInt(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(CInt(PDFBytes.Length)))
'refenrece to obj 5,8,11,14,17...
Dim xObjR As String = getXObjResource(itr)
PDFBytes.Write(stringToBytes(xObjR), 0, xObjR.Length)
'OBJECT 6...9...12...n : Binary xobject of the page (image)
Dim imgBA As Byte() = addImageToPDF(itr, isCompressed)
xRefList.Add(calculateXPos(CInt(PDFBytes.Length)))
'refenrece to obj 6,9,12,15,18...
PDFBytes.Write(imgBA, 0, imgBA.Length)
Next
'xrefs compilation
xRefList(0) += ((xRefList.Count - 1) & vbLf)
'get trailer
Dim trailer As String = getTrailer(CInt(PDFBytes.Length), xRefList.Count - 1)
'write xref and trailer to PDF
Dim strXRefs As String = String.Join("", DirectCast(xRefList.ToArray(GetType(String)), String()))
PDFBytes.Write(stringToBytes(strXRefs), 0, strXRefs.Length)
'
PDFBytes.Write(stringToBytes(trailer), 0, trailer.Length)
'write EOF
Dim strEOF As String = "%%EOF" & vbLf
PDFBytes.Write(stringToBytes(strEOF), 0, strEOF.Length)
PDFBytes.Close()
Return PDFBytes.ToArray()
End Function
'Build Image resource object that transforms the image from First Quadrant system to Second Quadrant system
Private Function getXObjResource() As String
Return getXObjResource(0)
End Function
Private Function getXObjResource(itr As Integer) As String
Dim width As String = getMeta("width", itr)
Dim height As String = getMeta("height", itr)
Return (((itr + 2) * 3) - 1) & " 0 obj" & vbLf & "<< /Length " & (24 + (width & height).Length) & " >>" & vbLf & "stream" & vbLf & "q" & vbLf & width & " 0 0 " & height & " 0 0 cm" & vbLf & "/R" & (itr + 1) & " Do" & vbLf & "Q" & vbLf & "endstream" & vbLf & "endobj" & vbLf
End Function
Private Function calculateXPos(posn As Integer) As String
Return posn.ToString().PadLeft(10, "0"c) & " 00000 n " & vbLf
End Function
Private Function getTrailer(xrefpos As Integer) As String
Return getTrailer(xrefpos, 7)
End Function
Private Function getTrailer(xrefpos As Integer, numxref As Integer) As String
Return "trailer" & vbLf & "<<" & vbLf & "/Size " & numxref.ToString() & vbLf & "/Root 2 0 R" & vbLf & "/Info 1 0 R" & vbLf & ">>" & vbLf & "startxref" & vbLf & xrefpos.ToString() & vbLf
End Function
Private Function getBitmapData24() As Byte()
Return getBitmapData24(0, True)
End Function
Private Function getBitmapData24(id As Integer, isCompressed As Boolean) As Byte()
Dim rawImageData As String = getMeta("imagedata", id)
Dim bgColor As String = getMeta("bgcolor", id)
Dim imageData24 As New MemoryStream()
' Split the data into rows using ; as separator
Dim rows As String() = rawImageData.Split(";"c)
For yPixel As Integer = 0 To rows.Length - 1
'Split each row into 'color_count' columns.
Dim color_count As String() = rows(yPixel).Split(","c)
For col As Integer = 0 To color_count.Length - 1
'Now, if it's not empty, we process it
'Split the 'color_count' into color and repeat factor
Dim split_data As String() = color_count(col).Split("_"c)
'Reference to color
Dim hexColor As String = If(split_data(0) <> "", split_data(0), bgColor)
'If the hexadecimal code is less than 6 characters, pad with 0
hexColor = If(hexColor.Length < 6, hexColor.PadLeft(6, "0"c), hexColor)
'refer to repeat factor
Dim fRepeat As Integer = Integer.Parse(split_data(1))
' convert color string to byte[] array
Dim rgb As Byte() = hexToBytes(hexColor)
' Set the repeated pixel in MemoryStream
For cRepeat As Integer = 0 To fRepeat - 1
imageData24.Write(rgb, 0, 3)
Next
Next
Next
Dim len As Integer = CInt(imageData24.Length)
imageData24.Close()
'Compress image binary
If isCompressed Then
Return New PDFCompress(imageData24.ToArray()).RLECompress()
Else
Return imageData24.ToArray()
End If
End Function
' converts a hexadecimal colour string to it's respective byte value
Private Function hexToBytes(strHex As String) As Byte()
If strHex Is Nothing OrElse strHex.Trim().Length = 0 Then
strHex = "00"
End If
strHex = Regex.Replace(strHex, "[^0-9a-fA-f]", "")
If strHex.Length Mod 2 <> 0 Then
strHex = "0" & strHex
End If
Dim len As Integer = strHex.Length \ 2
Dim bytes As Byte() = New Byte(len - 1) {}
For i As Integer = 0 To len - 1
Dim hex As String = strHex.Substring(i * 2, 2)
bytes(i) = Byte.Parse(hex, System.Globalization.NumberStyles.HexNumber)
Next
Return bytes
End Function
Private Function getMeta(metaName As String) As String
Return getMeta(metaName, 0)
End Function
Private Function getMeta(metaName As String, id As Integer) As String
If metaName Is Nothing Then
metaName = ""
End If
Dim chartData As Hashtable = DirectCast(arrExportData(id), Hashtable)
Return (If(chartData(metaName) Is Nothing, "", chartData(metaName).ToString()))
End Function
Private Function stringToBytes(str As String) As Byte()
If str Is Nothing Then
str = ""
End If
Return System.Text.Encoding.ASCII.GetBytes(str)
End Function
End Class
''' <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 _Compressed As New MemoryStream()
''' <summary>
''' Uncompresses data as byte[] array
''' </summary>
Private _UnCompressed As Byte()
''' <summary>
''' Takes the uncompressed byte array
''' </summary>
''' <param name="UnCompressed">uncompressed data</param>
Public Sub New(UnCompressed As Byte())
_UnCompressed = UnCompressed
End Sub
''' <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 Function WriteRunLength(length As Integer, encodee As Byte) As Integer
' write the repeat length
_Compressed.WriteByte(CByte(257 - length))
' write the byte to be repeated
_Compressed.WriteByte(encodee)
're-set repeat length
length = 1
Return length
End Function
Private Sub WriteNoRepeater(NoRepeatBytes As MemoryStream)
' write the length of non repeted data
_Compressed.WriteByte(CByte(CInt(NoRepeatBytes.Length) - 1))
' write the non repeated data put literally
_Compressed.Write(NoRepeatBytes.ToArray(), 0, CInt(NoRepeatBytes.Length))
' re-set non repeat byte storage stream
NoRepeatBytes.SetLength(0)
End Sub
''' <summary>
''' compresses uncompressed data to compressed data in byte array
''' </summary>
''' <returns></returns>
Public Function RLECompress() As Byte()
' stores non repeatable data
Dim NoRepeat As New MemoryStream()
' repeat counter
Dim _RL As Integer = 1
' 2 consecutive bytes to compare
Dim preByte As Byte = 0, postByte As Byte = 0
' iterate through the uncompressed bytes
For i As Integer = 0 To _UnCompressed.Length - 2
' get 2 consecutive bytes
preByte = _UnCompressed(i)
postByte = _UnCompressed(i + 1)
' if both are same there is scope for repitition
If preByte = postByte Then
' but flush the non repeatable data (if present) to compressed stream
If NoRepeat.Length > 0 Then
WriteNoRepeater(NoRepeat)
End If
' increase repeat count
_RL += 1
' if repeat count reaches limit of repeat i.e. 128
' write the repeat data and reset the repeat counter
If _RL > 128 Then
_RL = WriteRunLength(_RL - 1, preByte)
End If
Else
' when consecutive bytes do not match
' store non-repeatable data
If _RL = 1 Then
NoRepeat.WriteByte(preByte)
End If
' write repeated length and byte (if present ) to output stream
If _RL > 1 Then
_RL = WriteRunLength(_RL, preByte)
End If
' write non repeatable data to out put stream if the length reaches limit
If NoRepeat.Length = 128 Then
WriteNoRepeater(NoRepeat)
End If
End If
Next
' at the end of iteration
' take care of the last byte
' if repeated
If _RL > 1 Then
' 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)
End If
' wrote EOD
_Compressed.WriteByte(CByte(128))
'close streams
NoRepeat.Close()
_Compressed.Close()
' return compressed data in byte array
Return _Compressed.ToArray()
End Function
End Class