jsPDF with Cordova - Adding images - android

I am trying to generate a PDF using the jsPDF library (https://github.com/MrRio/jsPDF) from within a mobile Cordova app. I am currently testing the app on an Android 4.0.4 device but it also needs to run on Windows mobile 8. The text in the PDF document is shown correctly however any images are scrambled. See image below
I did find this page (https://coderwall.com/p/nc8hia) that seemed to indicate there is a problem with jsPDF displaying images in Cordova (see comments) but the author never posted the follow-up. Has anyone been able to use jsPDF with Cordova and properly add images to the generated PDF? My code is below, any assistance or advice would be greatly appreciated.
function demoReceipt() {
var img = new Image();
img.onError = function() {
alert('Cannot load image: "' + url + '"');
};
img.onload = function() {
createPdf2(img);
};
img.src = 'img/testlogo.png';
}
function createPdf2(myLogo) {
// var doc = new jsPDF('p', 'pt', 'jontype');
var doc = new jsPDF('p', 'pt', 'letter');
doc.setProperties({
title : 'Fueling Receipt',
author : 'Jon Hoffman',
creater : 'Jon Hoffman'
});
doc.addImage(myLogo, 'PNG', 5, 5, 140, 30);
doc.setFontSize(12);
doc.text(10, 40, 'Sample PDF receipt');
doc.setFontSize(8);
doc.text(10, 45, 'Smaller text - new');
var pdfOutput = doc.output();
//NEXT SAVE IT TO THE DEVICE'S LOCAL FILE SYSTEM
//Requires cordova plugin add org.apache.cordova.file
console.log("file system...");
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSystem) {
console.log(fileSystem.name);
console.log(fileSystem.root.name);
console.log(fileSystem.root.fullPath);
fileSystem.root.getDirectory("myPDFs", {
create : true,
exclusive : false
}, function(dir) {
fileSystem.root.getFile("myPDFs/test.pdf", {
create : true
}, function(entry) {
var fileEntry = entry;
console.log(entry);
entry.createWriter(function(writer) {
writer.onwrite = function(evt) {
console.log("write success");
};
console.log("writing to file");
writer.write(pdfOutput);
}, function(error) {
console.log(error);
});
}, function(error) {
console.log(error);
});
}, function(error) {
});
}, function(event) {
console.log(evt.target.error.code);
});
}

I solved the issue with help from this blog post: https://coderwall.com/p/nc8hia. There does seems to be significant differences between the 0.90 version used in that post and the version that I am using from https://github.com/MrRio/jsPDF however the solution is pretty much the same.
First off, in the version from MyRio, you can get the PDF generation working without fixing the Blob issue noted in Igor’s post. All you need is to generate the PDF output by calling “doc.ouput()” and then save it using the Cordova filesystem plugin. So I thought I did not have to create the Blob (this is where I was wrong).
Igor (from the coderwall post) responded back to my question with some additional code but when I searched the jspdf.js file from MyRio version, I saw that the code (more compact version) was already in the code on lines 734 – 738:
var data = buildDocument(), len = data.length,
ab = new ArrayBuffer(len), u8 = new Uint8Array(ab);
while(len--) u8[len] = data.charCodeAt(len);
return new Blob([ab], { type : "application/pdf" });
But I also notice that the blob creation code that Igor fixed in his initial post was at the end of this block of code. So I commented out the “return new Blob([ab], { type : “application/pdf”});” line and put in the following code from Igor’s post with minor variable name changes:
try
{
var blob = new Blob([ab], {type: "application/pdf"});
console.debug("case 1");
return blob;
}
catch (e)
{
window.BlobBuilder = window.BlobBuilder ||
window.WebKitBlobBuilder ||
window.MozBlobBuilder ||
window.MSBlobBuilder;
if (e.name == 'TypeError' && window.BlobBuilder)
{
var bb = new BlobBuilder();
bb.append(ab);
console.debug("case 2");
return bb.getBlob("application/pdf");
}
else if (e.name == "InvalidStateError")
{
// InvalidStateError (tested on FF13 WinXP)
console.debug("case 3");
return new Blob([ab], {type: "application/pdf"});
}
else
{
// We're screwed, blob constructor unsupported entirely
console.debug("Errore");
}
}
Then when I generate that pdfOutput, in my code, I changed
var pdfOutput = doc.output();
to
var pdfOutput = doc.output(“blob”);
and it worked.
I hope this post is able to help out others experiencing the same issues.

Related

How to download base-64 source images in mobile device(Android,iPhone,Windows) and save into device phone memory

I have created one sample app using telerik app builder. I have created one simple view and one js file, render one image in view and convert it into base-64. I have requirement to download this image and save it to device internal storage using javascript
I have used download.js plugin of javascript but download functionality is not working.Please give suggestion.
My Code is below:
function (image)
{
var imageData = image.src;
imageData = imageData.replace('data:image/png;base64,', '');
download(imageData, "image11111", "image/png");
}
I found my own question's answer.
You can download image in mobile device using cordova filetransfer.
function download()
{
var filepath = encodeURI("http://www.telerik.com/sfimages/default-source/logos/app_builder.png"),
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fileSystem) {
fileSystem.root.getFile("sample.jpg", { create: true, exclusive: false }, function (fileEntry) {
// get the full path to the newly created file on the device
var localPath = fileEntry.fullPath;
// massage the path for android devices (not tested)
if (device.platform === "Android" && localPath.indexOf("file://") === 0) {
localPath = localPath.substring(7);
}
// download the remote file and save it
var remoteFile = filepath;
//loadingOverlay.displayLoading("Image will be save on your device.");
var fileTransfer = new FileTransfer();
fileTransfer.download(remoteFile, localPath, function (newFileEntry) {
// successful download, continue to the next image
var dwnldImagePath = newFileEntry.fullPath;
console.log('successful download');
},
function (error) { // error callback for #download
console.log('Error with #download method.', error);
});
});
function(error) { // error callback for #getFile
console.log('Error with #getFile method.', error);
});
})
}

Loading binary image data to an image fails on Android 4.1 in Cordova Project

I have serious issues loading binary image data into a simple image-element. I coded a cordova app (using Sencha touch) which loads images the following way:
xhr.open('GET', imageUrl, true);
xhr.responseType = 'blob';
xhr.addEventListener('load', function () {
if (xhr.status === 200) {
// onload needed since Google Chrome doesn't support addEventListener for FileReader
fileReader.onload = function (evt) {
image.setSrc(evt.target.result);
};
// Load blob as Data URL
fileReader.readAsDataURL(xhr.response);
}
}, false);
On Android 5 and 4.4 (these are the ones I tested) it works like a charm. Now I ran it Android 4.1 on an ASUS Tablet and the onload callback doesn't get fired. When I throw a blob in the readAsDataURL-function, at least the onload callback is fired, but the image doesn't show any image as well :(
Has anyone a suggestion, what the failure could be or what I'm doing wrong?
Ah, finally I got it to work on Android 4.1.1 (ASUS Tablet) with the following code. Another issue was, that saving an arraybuffer response from the xhr could not simply serialized, so I converted the stuff to string. Also I receive the blob taking into account, that on some systems the Blob object simply isn't there:
function arrayBufferToString(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
};
function stringToArrayBuffer(str) {
var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
var bufView = new Uint8Array(buf);
for (var i=0, strLen=str.length; i<strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
};
function getBlob(content, type) {
var blob = null;
// Android 4 only has the deprecated BlobBuilder :-(
try {
blob = new Blob([content], {type: type});
} catch(e) {
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
window.MozBlobBuilder || window.MSBlobBuilder;
if (window.BlobBuilder)
{
var bb = new BlobBuilder();
bb.append(content);
blob = bb.getBlob(type);
}
}
return blob;
};
cachedFile = getCachedFile(...); // not of interest here, I think :-)
if (cachedFile)
{
callback.apply(this, [window.webkitURL.createObjectURL(getBlob(stringToArrayBuffer(cachedFile.data), 'image/jpeg'))]);
}
else {
xhr.open('GET', imageUrl, true);
xhr.responseType = 'arraybuffer';
xhr.addEventListener('load', function () {
if (xhr.status === 200) {
cachedFile = {
url: imageUrl,
data: arrayBufferToString(xhr.response)
};
addCachedFile(cachedFile); // not of interest here, I think :-)
callback.apply(this, [window.webkitURL.createObjectURL(getBlob(xhr.response, 'image/jpeg'))]);
}
}, false);
// Send XHR
xhr.send();
}
Edit: Just did a little change and now used the Uint8Array Class instead of the Uint16Array Class because I got errors: "RangeError: byte length of Uint16Array should be a multiple of 2". Now it works well.
Edit2: Just saw, that the above code doesn't work out in all situations because of the usage of Uint8Array resp. Uint16Array.
Now I think I have a solid solution: I convert the binary responded by the image url into a base64 using canvas with the function from here http://appcropolis.com/blog/web-technology/javascript-encode-images-dataurl/ . Takes a little time, but still a working solution :)

Downloading Transparent PNG's on Android using Appcelerator turns the background black

Can anyone shed any light on why, using the Image Factory module to download and store images on Android, does it ignore the transparency on PNG graphics and give them a black background?
It works fine on iOS and everything is "as is".
Do I need to add anything to the download script to retain the transparency?
Help!
Here is my download script, I'm building using Titanium 3.5.1 GA:
function getMarker(url, filename) {
// this will enable us to have multiple file sizes per device
var filename2 = filename.replace(".png", "#2x.png");
var filename3 = filename.replace(".png", "#3x.png");
var mapMarker = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, 'map_marker_icons', filename);
var mapMarker2 = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, 'map_marker_icons', filename2);
var mapMarker3 = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, 'map_marker_icons', filename3);
// now we need to download the map marker and save it into our device
var getMarker = Titanium.Network.createHTTPClient({
timeout: 30000
});
getMarker.onload = function() {
// if the file loads, then write to the filesystem
if (getMarker.status == 200) {
// resize the images into non-retina, retina and retina HD and only download and resize what is actyally required
var getOriginal = ImageFactory.imageWithAlpha(this.responseData, {});
var resized2 = ImageFactory.imageAsResized(getOriginal, {
width: 50,
height: 50
});
mapMarker.write(resized2);
Ti.API.info(filename + " Image resized");
//I ALWAYS NULL ANY PROXIES CREATED SO THAT IT CAN BE RELEASED
mapMarker = null;
} else {
Ti.API.info("Image not loaded");
}
// load the tours in next
loadNav();
};
getMarker.onerror = function(e) {
Ti.API.info('XHR Error ' + e.error);
//alert('markers data error');
};
getMarker.ondatastream = function(e) {
//Ti.API.info('Download progress: ' + e.progress);
};
// open the client
getMarker.open('GET', url);
// change the loading message
MainActInd.message = 'Downloading Markers';
// show the indicator
MainActInd.show();
// send the data
getMarker.send(); }
Any help would be much appreciated!
Simon
Please try the following code, I tried it on Android and iOS with a png Url, first I GET the photo with HTTP client request, then I save it as a file with extension png and then I read it with an ImageView.
index.js:
$.win.open();
savePng("https://cdn1.iconfinder.com/data/icons/social-media-set/29/Soundcloud-128.png");
function savePng(pngUrl) {
var client = Titanium.Network.createHTTPClient({
onload : function(e) {
var image_file = Ti.Filesystem.getFile(Titanium.Filesystem.applicationDataDirectory, "test.png");
image_file.write(this.responseData);
$.img.image = image_file.read();
},
onerror : function(e) {
alert(e.error);
},
timeout : 10000
});
client.open("GET", pngUrl);
client.send();
}
index.xml:
<Alloy>
<Window id="win" backgroundColor="gray">
<ImageView id="img" />
</Window>
</Alloy>

phonegap iOS file.path

I have a app using cordova 3.4 and file 1.1.0.
If I copy a image using the camera-modul, I use
myFileObj.path = file.toNativeURL()
to get the file-path. If I put this path into a img-tag I get shown the picture on Android.
On iOS it doesn't work. The result of file.toNativeURL():
myFileObj.path -> file:///Users/.../Library/Application%20Support/..../myFolder/myImage.JPG
Using file 1.0 I had to build the url and it looked like this:
myFileObj.path = dirTarget.toURL() + '/' + targetFileName
myFileObj.path -> cdvfile://localhost/persisten/myFolder/myImage.JPG
Where videos and audios didn't work, but at least pictures.
Using file 1.1.0/1.1.1 the result of this method is different too:
myFileObj.path -> file:///Users/mak/Library/.../myFolder/myImage.JPG?id=.....&ext=JPG
This doesn't work on iOS either.
How can I get a working file-path by using cordova file-module version 1.1.0 and 1.1.1?
EDIT: What am I doing and what doesn't work:
I copy images from the media-library and put it into a folder I create myself.
What works in Android and doesn't work in iOS:
In Android the media-tags src attribute is able to display the resource, iOS can't find a resource at the src-path.
catching a file from the media-library:
navigator.camera.getPicture(onSuccess, onFail, {
destinationType: Camera.DestinationType.NATIVE_URI,
sourceType : Camera.PictureSourceType.PHOTOLIBRARY,
mediaType: Camera.MediaType.ALLMEDIA
});
success-callback:
function onSuccess(imageData) {
A.StoreFile(imageData, id);
}
create a folder and store file:
A.StoreFile = function(file, idBox) {
var targetDirectory = Config.getRootPath();
window.resolveLocalFileSystemURL(file, resolveFileSystemSuccess, resolveFileSystemError);
function resolveFileSystemSuccess(fileEntry) {
fileEntry.file(function(filee) {
mimeType = filee.type;
getFileSuccess(fileEntry, mimeType);
}, function() {
});
}
function getFileSuccess(fileEntry, mimeType) {
var targetFileName = name + '.' + fileNativeType;
var parentName = targetDirectory.substring(targetDirectory.lastIndexOf('/')+1),
parentEntry = new DirectoryEntry(parentName, targetDirectory);
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSystem) {
fileSystem.root.getDirectory(targetDirectory, {create: true, exclusive: false}, function(dirTarget) {
fileEntry.copyTo(dirTarget, targetFileName, function(entry) {
addFileToLocalStorage(entry);
}, function() {
})
})
}, resolveFileSystemError);
}
store file-information to a localStorageObject
function addFileToLocalStorage(file) {
fileList.addFile(
{
name:file.name,
internalURL: file.toNativeURL()
});
}
adding files dynamically to the dom:
myElement.find('.myMimeTypeTag').attr('src', fileList[f].internalURL);
This works with android, and not with iOS.
iOS-result of img-container:
Error-message:
DEPRECATED: Update your code to use 'toURL'
toURL doesn't work either
id="org.apache.cordova.file"
version="1.1.1-dev"
I've just tested this out, with a slightly simplified version of your code (you seem to have a lot of extra structure to your code that isn't shown, but if there's a significant difference between what I've done here and what your app does, then let me know. The problem will be in the difference.)
I've run this with Cordova 3.5.0, just released, using File 1.1.0 and Camera 0.2.9.
To create the app, I used the cordova command line tool, and just ran
cordova create so23801369 com.example.so23801369 so23801369
cd so23801369
cordova platform add ios
cordova plugin add org.apache.cordova.file
cordova plugin add org.apache.cordova.camera
This creates a default "Hello, Cordova" app, to which I've added some code that (I believe) replicates what your code does.
I added two lines to index.html:
<button id="doit">Do it</button>
<img class="myMimeTypeTag" src="file:///nothing" />
And I edited www/js/index.js to look like this:
var app = {
initialize: function() {
// Don't activate the button until Cordova is initialized
document.addEventListener('deviceready', this.onDeviceReady, false);
},
onDeviceReady: function() {
document.getElementById('doit').addEventListener('click', app.runTest, false);
},
runTest: function(ev) {
var StoreFile = function(file) {
var targetDirectory = "myFolder";
window.resolveLocalFileSystemURL(file, resolveFileSystemSuccess, resolveFileSystemError);
function resolveFileSystemSuccess(fileEntry) {
console.log("resolveLocalFileSystemURL returned: ", fileEntry.toURL());
fileEntry.file(function(filee) {
mimeType = filee.type;
getFileSuccess(fileEntry, mimeType);
}, function() {
});
}
function resolveFileSystemError() {
console.log("resolveFileSystemError: FAIL");
console.log(arguments);
alert("FAIL");
}
function getFileSuccess(fileEntry, mimeType) {
var targetFileName = "myImage.JPG";
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSystem) {
fileSystem.root.getDirectory(targetDirectory, {create: true, exclusive: false}, function(dirTarget) {
fileEntry.copyTo(dirTarget, targetFileName, function(entry) {
console.log("copyTo returned: ", entry.toURL());
// I have replaced the localstorage handling with this code
// addFileToLocalStorage(entry);
var img = document.querySelector('.myMimeTypeTag');
img.setAttribute('src', entry.toNativeURL());
}, function() {
});
});
}, resolveFileSystemError);
}
};
var onSuccess = function(imageData) {
console.log("getPicture returned: ", imageData);
StoreFile(imageData);
};
var onFail = function() {
console.log("getPicture FAIL");
console.log(arguments);
alert("FAIL");
};
ev.preventDefault();
ev.stopPropagation();
navigator.camera.getPicture(onSuccess, onFail, {
destinationType: Camera.DestinationType.NATIVE_URI,
sourceType : Camera.PictureSourceType.PHOTOLIBRARY,
mediaType: Camera.MediaType.ALLMEDIA
});
}
};
When I run this, I can pick an image from the media library, and it successfully displays it in the page, with the image src set to the URL of the copied image file. If I connect Safari dev tools to the iPad, I see this console output:
[Log] getPicture returned: assets-library://asset/asset.JPG?id=F9B8C942-367E-433A-9A71-40C5F2806A74&ext=JPG (index.js, line 49)
[Log] resolveLocalFileSystemURL returned: cdvfile://localhost/assets-library/asset/asset.JPG?id=F9B8C942-367E-433A-9A71-40C5F2806A74&ext=JPG (index.js, line 18)
[Log] copyTo returned: file:///var/mobile/Applications/9C838867-30BE-4703-945F-C9DD48AB4D64/Documents/myFolder/myImage.JPG (index.js, line 36)
[Log] DEPRECATED: Update your code to use 'toURL' (Entry.js, line 202)
This shows the camera and file plugins going through three different types of URL:
Camera returns an assets-library:// URL, with query parameters to identify the asset
calling resolveLocalFileSystemURL on that turns it into a cdvfile:// URL, also with query paramaters, as an internal Cordova representation.
After being copied, File returns a new file:/// URL showing the image's new place on the filesystem. This URL has no query parameters. (Calling toNativeURL() on this entry returns the same URL now, as of File 1.1.0)
This final URL is usable by iOS as an image source, and so that's what gets assigned to the <img> element.
The problem was, that the new file-plugin has to be case-sensitve.
I created a folder with capital letters and refered the copy-instruction to a folder with lowercase letters. The point is that android is case insensitive and ios is not.
This came out with the new file-plugin where the output was case sensitive.

Playing sound from object URL

I use XMLHttpRequest to preload files for a game. If WebAudio isnt supported I fall back to using audio elements.
On Android it works fine in Firefox but sound does not play in chrome. Here's some testing code:
document.addEventListener('touchstart', function(event) {
event.preventDefault();
if( window.audio ) window.audio.play();
console.log( window.audio );
});
window.audio = null;
var xhr = new XMLHttpRequest();
xhr.open('GET', '/sounds/shotgun.webm', true);
xhr.responseType = 'arraybuffer';
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
var blob = new Blob( new Array( new Int8Array( xhr.response ) ), { type: 'audio/webm' } );
var url = window.URL.createObjectURL( blob )
window.audio = new Audio();
window.audio.src = url;
console.log( 'sound loaded' );
}
};
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send();
I suspect this is a Chrome for android issue with the object url...
Anyway I'm looking for a way to play sounds on Chrome for Android using cached data from an XMLHttpRequest.
This does in fact appear to be a bug. I have raised it here https://code.google.com/p/chromium/issues/detail?id=227476 and created a test case for it. When I get back from vacation I will assign it to the correct team if it has not already been triaged. I will also update this answer if I find any other work around.
I don't have an Android phone handy, but I know that in the past you needed to use window.webkitURL instead of window.URL. Maybe try:
( window.URL || window.webkitURL ).createObjectURL( blob )

Categories

Resources