Xamarin Android print PDF file - c#

I am trying to print a PDF file I created using PrintManager. I have already written my code to generate PDF File using ITextSharp in my activity. Below is my generate PDF method:
private byte[] generatePdf()
{
using (var baos = new MemoryStream())
{
using (var ps = new StreamWriter(baos))
{
Document doc = new Document(PageSize.A4, 72, 72, 72, 72);
PdfWriter writer = PdfWriter.GetInstance(doc, ps.BaseStream);
if (!doc.IsOpen())
doc.Open();
doc.AddTitle("Sales Order " + GlobalVars.selectedSales.DocumentNo);
var TitleFont = new Font(Font.FontFamily.TIMES_ROMAN, 24, Font.BOLD);
var SubTitleFont = new Font(Font.FontFamily.TIMES_ROMAN, 20);
var HeadingFont = new Font(Font.FontFamily.TIMES_ROMAN, 18, Font.BOLD);
var BoldNormalFont = new Font(Font.FontFamily.TIMES_ROMAN, 14, Font.BOLD);
var NormalFont = new Font(Font.FontFamily.TIMES_ROMAN, 14);
doc.Add(new Paragraph("SALES ORDER", TitleFont));
... //other data
doc.Close();
writer.Close();
}
var bytes = baos.ToArray();
return bytes;
}
}
In short, the code above will return byte array containing the PDF file generated because I do not want to save the PDF anywhere in the Android device.
After generating this PDF, I am lost to use PrintManager, which takes only PrintDocumentAdapter argument to generate the file and not using my generated PDF. I am trying to generate CustomPrintAdapter which inherits from PrintDocumentAdapter, but have no idea how could I reuse the PDF that I created above from my activity because it uses PrintedPdfDocument data type.
Could you guys point me into the right direction here? Or is there any other easier approach to achieve printing custom PDF file?
UPDATE 1
Turns out Xamarin Android does not support System.Drawing.dll based on this article. I think ITextSharp library uses System.Drawing library because when I tried to build it, it raise an exception:
Exception while loading assemblies: System.IO.FileNotFoundException: Could not load assembly 'System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
Perhaps it doesn't exist in the Mono for Android profile?
And when I removed it, the build is success and no exception raised. Note that I have tried closing solution, delete all contents from Packages folder, reopen solution, and build; it did not work in this case.
Therefore, I will try generate the PDF using Android.Graphics.Canvas as seen on the Android Developer site.

UPDATE 2
I got no success generating PDF with Android.Graphics.Canvas like the way Android developer site does. The reason is I have no idea how to calculate the page and change the page as the content is dynamic. There was also a method like getPrintItemCount() in the site, which I have no idea what is that as the site does not include the explanation there.
Therefore, the workaround that I could think of now is printing WebView which is handled by my web service endpoint. I am creating a HTML layout for the page that I would like to print, then the WebView will open my web service endpoint. Luckily, printing with this approach can be done well. And of course, this approach has limitation which is the web service should be reachable to do printing.
To do this, first, I need to create a custom class that extends WebViewClient to get the event when page is finished loading using OnPageFinished method. I, then use the print method from my activity, which I pass it using the constructor. To make it clearer, I will attach my codes.
public class CustomWebViewClient : WebViewClient
{
private SalesDetailView myActivity;
public bool shouldOverrideUrlLoading(WebView view, string url)
{
return false;
}
public override void OnPageFinished(WebView view, string url)
{
Log.Debug("PRINT", "page finished loading " + url);
myActivity.doWebViewPrint(url);
base.OnPageFinished(view, url);
}
public CustomWebViewClient(SalesDetailView activity)
{
myActivity = activity;
}
}
Here is the doWebPrint method from my activity
public async void doWebViewPrint(string URL)
{
if (await vm.printCheck())
{
PrintDocumentAdapter adapter;
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.Lollipop)
adapter = webView.CreatePrintDocumentAdapter("test");
else
adapter = webView.CreatePrintDocumentAdapter();
var printMgr = (PrintManager)GetSystemService(PrintService);
printMgr.Print("printTest", adapter, null);
}
}
I then create the WebView, load the website whenever the user clicks print button.
Button btnPrintSO = FindViewById<Button>(Resource.Id.btnPrintSO);
btnPrintSO.Click += delegate
{
string URL = ServerDatabaseApi.printSOEndpoint + GlobalVars.selectedSales.DocumentNo;
webView = new WebView(this);
CustomWebViewClient client = new CustomWebViewClient(this);
webView.SetWebViewClient(client);
webView.Settings.JavaScriptEnabled = true;
webView.LoadUrl(URL);
webView.Settings.UseWideViewPort = true;
webView.Settings.LoadWithOverviewMode = true;
};
Hope these updates could help anyone in the future.

Related

JsReport save action result in PDF

I would like to generate the PDF based on the View, but I don't want to display it after generating it. Just save to disk.
As I am in Azure, I had to use the version with Docker, but it did not print the footer (page count)
With that I will use iText7 to add the footer (page count) to delete the original PDF and display the new output.
Huge job, but the only way I found it, since Rotativa and other components that work with wkhtmltopdf.org did not print the CSS correctly.
So my problem is:
How to save the PDF without displaying it?
With the example of the site:
https://jsreport.net/learn/dotnet-aspnetcore#save-to-file
Itneeds the return View() which makes me unable to display the modified
PDF and not the original
.
[MiddlewareFilter(typeof(JsReportPipeline))]
public async Task<IActionResult> InvoiceWithHeader()
{
HttpContext.JsReportFeature().Recipe(Recipe.ChromePdf);
HttpContext.JsReportFeature().OnAfterRender((r) => {
using (var file = System.IO.File.Open("report.pdf", FileMode.Create))
{
r.Content.CopyTo(file);
}
r.Content.Seek(0, SeekOrigin.Begin);
});
return View(InvoiceModel.Example());
}
OnAfterRender does not answer my problem, is there how to do the step as I said? or is there another better solution?
Generate PDF of Action by JsReport
Save PDF from JsReport
Add page count by iText7
Delete original PDF from JsReport
View the pdf modified by iText7 in the view
NOTE: using new jsreport.Local.LocalReporting() works perfectly the problem was when going up to Azure.
Update:
I'm tried, but it didn't work
var htmlContent = await JsReportMVCService.RenderViewToStringAsync(HttpContext, RouteData, "/Views/OcorrenciaTalaos/GerarPdf.cshtml", retorno);
(var contentType, var generatedFile) = await GeneratePDFAsync(htmlContent);
using (var fileStream = new FileStream("tempJsReport.pdf", FileMode.Create))
{
await generatedFile.CopyToAsync(fileStream);
}
public async Task<(string ContentType, MemoryStream GeneratedFileStream)> GeneratePDFAsync(string htmlContent)
{
IJsReportFeature feature = new JsReportFeature(HttpContext);
feature.Recipe(Recipe.ChromePdf);
if (!feature.Enabled) return (null, null);
feature.RenderRequest.Template.Content = htmlContent;
// var htmlContent = await JsReportMVCService.RenderViewToStringAsync(HttpCSexontext, RouteData, "GerarPdf", retorno);
var report = await JsReportMVCService.RenderAsync(feature.RenderRequest);
var contentType = report.Meta.ContentType;
MemoryStream ms = new MemoryStream();
report.Content.CopyTo(ms);
return (contentType, ms);
}
You can overwrite the final response inside the OnAfterRender this way:
[MiddlewareFilter(typeof(JsReportPipeline))]
public IActionResult InvoiceDownload()
{
HttpContext.JsReportFeature().Recipe(Recipe.ChromePdf)
.OnAfterRender((r) =>
{
// write current report to file
using(var fileStream = System.IO.File.Create("c://temp/out.pdf"))
{
r.Content.CopyTo(fileStream);
}
// do modifications
// ...
// overwrite response with a new pdf
r.Content = System.IO.File.OpenRead("c://temp/final.pdf");
});
return View("Invoice", InvoiceModel.Example());
}
However, the page numbers should work and you shouldn't need to do it this complicated way. No matter you are in docker or not. Here is the answer in another question

Copy a visio page to a new document

What I want to accomplish:
I want to copy the active page in my Visio application to a new document and save it (and make it a byte[] for the db), I am already doing this but in a slightly "wrong" way as there is too much interaction with the Visio application.
Method to copy page to byte array:
private static byte[] VisioPageToBytes()
{
//Make a new invisible app to dump the shapes in
var app = new InvisibleApp();
Page page = MainForm.IVisioApplication.ActivePage;
app.AlertResponse = 2;
//Selact all shapes and copy, then deselect
MainForm.IVisioApplication.ActiveWindow.SelectAll();
MainForm.IVisioApplication.ActiveWindow.Selection.Copy();
MainForm.IVisioApplication.ActiveWindow.DeselectAll();
//Add empty document to invisible app and dump shapes
app.Documents.Add( string.Empty );
app.ActivePage.Paste();
//Save document and convert to byte[]
app.ActiveDocument.SaveAs( Application.UserAppDataPath + #"/LastStored.vsd" );
app.ActiveDocument.Close();
app.Quit();
app.AlertResponse = 0;
var bytes = File.ReadAllBytes( Application.UserAppDataPath + #"/LastStored.vsd" );
Clipboard.Clear();
return bytes;
}
Why it's wrong:
This code makes selections in the visio page and has to open an invisible window to store the page. I'm looking for a way with less interaction with the Visio application (as its unstable). The opening of the 2nd (invisible) Visio application occasionally makes my main Visio application crash.
I would like to do something like:
Page page = MainForm.IVisioApplication.ActivePage;
Document doc;
doc.Pages.Add( page ); //Pages.Add has no parameters so this doesn't work
doc.SaveAs(Application.UserAppDataPath + #"/LastStored.vsd");
If this is not possible in a way with less interaction (by "building" the document), please comment to let me know.
TL;DR;
I wan't to make a new Visio document without opening Visio and copy (the content of) 1 page to it.
If you want to create a copy page then you might find the Duplicate method on Page handy, but by the sounds of it just save the existing doc should work:
void Main()
{
var vApp = MyExtensions.GetRunningVisio();
var sourcePage = vApp.ActivePage;
var sourcePageNameU = sourcePage.NameU;
var vDoc = sourcePage.Document;
vDoc.Save(); //to retain original
var origFileName = vDoc.FullName;
var newFileName = Path.Combine(vDoc.Path, $"LastStored{Path.GetExtension(origFileName)}");
vDoc.SaveAs(newFileName);
//Remove all other pages
for (short i = vDoc.Pages.Count; i > 0; i--)
{
if (vDoc.Pages[i].NameU != sourcePageNameU)
{
vDoc.Pages[i].Delete(0);
}
}
//Save single page state
vDoc.Save();
//Close copy and reopen original
vDoc.Close();
vDoc = vApp.Documents.Open(origFileName);
}
GetRunningVisio is my extension method for using with LinqPad:
http://visualsignals.typepad.co.uk/vislog/2015/12/getting-started-with-c-in-linqpad-with-visio.html
...but you've already got a reference to your app so you can use that instead.
Update based on comments:
Ok, so how about this modification of your original code? Note that I'm creating a new Selection object from the page but not changing the Window one, so this shouldn't interfere with what the user sees or change the source doc at all.
void Main()
{
var vApp = MyExtensions.GetRunningVisio();
var sourcePage = vApp.ActivePage;
var sourceDoc = sourcePage.Document;
var vSel = sourcePage.CreateSelection(Visio.VisSelectionTypes.visSelTypeAll);
vSel.Copy(Visio.VisCutCopyPasteCodes.visCopyPasteNoTranslate);
var copyDoc = vApp.Documents.AddEx(string.Empty,
Visio.VisMeasurementSystem.visMSDefault,
(int)Visio.VisOpenSaveArgs.visAddHidden);
copyDoc.Pages[1].Paste(Visio.VisCutCopyPasteCodes.visCopyPasteNoTranslate);
var origFileName = sourceDoc.FullName;
var newFileName = Path.Combine(sourceDoc.Path, $"LastStored{Path.GetExtension(origFileName)}");
copyDoc.SaveAs(newFileName);
copyDoc.Close();
}
Note that this will only create a default page so you might want to include copying over page cells such as PageWidth, PageHeight, PageScale and DrawingScale etc. prior to pasting.

PDF to bmp Images (12 pages = 12 images)

I have to deconstruct/extract a pdf page by page into bitmap images. This will be done on a server via a web service which I've setup. How do I get this right? It has to be page by page (1 page per image).
I am really stuck and I know one of you geniuses have the answer that I've been looking for.
I have tried: http://www.pdfsharp.net/wiki/ExportImages-sample.ashx Which didn't work correctly.
I am using C#;
The PDF is not password protected;
If this solution could take a Uri as a parameter for the location of the PDF it would be excellent!
The solution should not be reliant on Acrobat PDF Reader at all
I have been struggling for a very long time trying to use MigraDoc and PDFSharp and their alternatives to achieve the aforementioned problem.
ANY help/advice/code would be greatly appreciated!!
Thanks in advance!
LibPdf
This library converts converts PDF file to an image. Supported image formats are PNG and BMP, but you can easily add more.
Usage example:
using (FileStream file = File.OpenRead(#"..\path\to\pdf\file.pdf")) // in file
{
var bytes = new byte[file.Length];
file.Read(bytes, 0, bytes.Length);
using (var pdf = new LibPdf(bytes))
{
byte[] pngBytes = pdf.GetImage(0,ImageType.BMP); // image type
using (var outFile = File.Create(#"..\path\to\pdf\file.bmp")) // out file
{
outFile.Write(pngBytes, 0, pngBytes.Length);
}
}
}
Or Bytescout PDF Renderer SDK
using System;
using Bytescout.PDFRenderer;
namespace PDF2BMP
{
class Program
{
static void Main(string[] args)
{
// Create an instance of Bytescout.PDFRenderer.RasterRenderer object and register it.
RasterRenderer renderer = new RasterRenderer();
renderer.RegistrationName = "demo";
renderer.RegistrationKey = "demo";
// Load PDF document.
renderer.LoadDocumentFromFile("multipage.pdf");
for (int i = 0; i < renderer.GetPageCount(); i++)
{
// Render first page of the document to BMP image file.
renderer.RenderPageToFile(i, RasterOutputFormat.BMP, "image" + i + ".bmp");
}
// Open the first output file in default image viewer.
System.Diagnostics.Process.Start("image0.bmp");
}
}
}

Add PDF file as attachment to TestTrack using SOAP

I have recently started using C# over the past year so I'm somewhat new to this, but can usually hack through things with some effort, but this one is eluding me. We use TestTrack for development bug/issue tracking at our company. I've created a custom windows forms app to be the front-end to TestTrack for one of our departments. It connects using SOAP. I'm not using WPF/WCF and don't want to go that route. I'm having difficulty finding any examples of how to correctly encode a file for attachment that is a PDF. The code below does actually create an attachment in TestTrack to an already-existing issue, but when you try to open it in TestTrack, it pops up an error message that says "Insufficient Data For An Image". The example below does work if you're wanting to add a text file to TestTrack using SOAP. I'm wanting to know what I need to change below so that I can get a PDF file into TestTrack and then be able to open it in the TestTrack application without the error mentioned above. Thanks in advance for any input/help.
public void getAttachments(long lSession, CDefect def)
{
ttsoapcgi cgiengine = new ttsoapcgi();
// Lock the defect for edit.
CDefect lockedDefect = cgiengine.editDefect(lSession, def.recordid, "", false);
string attachment = "c:\\TEST\\TEST_PDF.PDF";
CFileAttachment file = new CFileAttachment();
file.mstrFileName = Path.GetFileName(attachment);
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
StreamReader reader = new StreamReader(attachment);
file.mstrFileName = Path.GetFileName(attachment);
file.mpFileData = enc.GetBytes(reader.ReadToEnd());
reader.Close();
CReportedByRecord reprec = lockedDefect.reportedbylist[0];
CFileAttachment[] afile = reprec.attachmentlist;
if (afile == null)
{
lockedDefect.reportedbylist[0].attachmentlist = new CFileAttachment[1];
lockedDefect.reportedbylist[0].attachmentlist[0] = file;
}
// Save our changes.
cgiengine.saveDefect(lSession, lockedDefect);
}
}
Here is the modified method that allowed me to attach a PDF to SOAP and get it into TestTrack as an attachment to an issue:
public void getAttachments(long lSession, CDefect def)
{
ttsoapcgi cgiengine = new ttsoapcgi();
// Lock the defect for edit.
CDefect lockedDefect = cgiengine.editDefect(lSession, def.recordid, "", false);
string attachment = "c:\\TEST\\TEST_PDF.PDF";
CFileAttachment file = new CFileAttachment();
file.mpFileData = File.ReadAllBytes(attachment);
file.mstrFileName = Path.GetFileName(attachment);
CReportedByRecord reprec = lockedDefect.reportedbylist[0];
CFileAttachment[] afile = reprec.attachmentlist;
if (afile == null)
{
lockedDefect.reportedbylist[0].attachmentlist = new CFileAttachment[1];
lockedDefect.reportedbylist[0].attachmentlist[0] = file;
}
// Save our changes.
cgiengine.saveDefect(lSession, lockedDefect);
}

Reloading and refreshing flash files inside a windows form

Using DOTNET 3.5 . I have a app which shows load a flash movie in form, using this code
axShockwaveFlash1 = new AxShockwaveFlashObjects.AxShockwaveFlash()
axShockwaveFlash1.LoadMovie(0, Form1.currentGame);
The problem is that whenever I make a changes in the flash hosted in our application and try to refresh the to see the changes, the new changes is 'messed' up. to be more specific , it seems that the background and some controls of the previous flash still remain, 'spoiling' the new flash that is loaded. why?
Using the following methods before loading the second flash video makes no difference
axShockwaveFlash1.Refresh();
axShockwaveFlash1.Stop();
I tried other methods, it doesn't work for me. Here is what I did to achieve the desired result.
private void btnReload_Click(object sender, EventArgs e)
{
byte[] fileContent = File.ReadAllBytes(Application.StartupPath + #"\yourflashfile.swf");
if (fileContent != null && fileContent.Length > 0)
{
using (MemoryStream stm = new MemoryStream())
{
using (BinaryWriter writer = new BinaryWriter(stm))
{
/* Write length of stream for AxHost.State */
writer.Write(8 + fileContent.Length);
/* Write Flash magic 'fUfU' */
writer.Write(0x55665566);
/* Length of swf file */
writer.Write(fileContent.Length);
writer.Write(fileContent);
stm.Seek(0, SeekOrigin.Begin);
/* 1 == IPeristStreamInit */
//Same as LoadMovie()
this.axShockwaveFlash1.OcxState = new AxHost.State(stm, 1, false, null);
}
}
fileContent = null;
GC.Collect();
}
}
I copied the core code somewhere in SO but I don't remember the source.
Have you tried loading an "empty" flash video before loading your new video?
e.g.
axShockwaveFlash1.LoadMovie(0,"");
I'm sure that I encountered a similar problem and resolved it this way.
Try this.
Make a blank swf. "Blank.swf" load it first and then re-load your game.
axShockwaveFlash1.LoadMovie(0,"Blank.swf");
axShockwaveFlash1.Play();
axShockwaveFlash1.LoadMovie(0, Form1.currentGame);
axShockwaveFlash1.Play();
Ensure you give the correct path for the Blank.swf.

Categories