PDFJS.getDocument not working and not throwing an error - pdf.js

It's not going into the .then afterwards, and it's not throwing any error.
Here's my calling code:
function loadPage(base64Data, pageIndex) {
var pdfData = base64ToUint8Array(base64Data);
// this gets hit
PDFJS.getDocument(pdfData).then(function (pdf) {
// never gets here
pdf.getPage(pageIndex).then(function (page) {
var scale = 1;
var viewport = page.getViewport(scale);
var canvas = document.getElementById('pdfPage');
var context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
page.render({ canvasContext: context, viewport: viewport });
});
});
}
function base64ToUint8Array(base64) {
var raw = atob(base64); // convert base 64 string to raw string
var uint8Array = new Uint8Array(raw.length);
for (var i = 0; i < raw.length; i++) {
uint8Array[i] = raw.charCodeAt(i);
}
return uint8Array;
}
At one point it worked. When I step through it in the debugger, I can step into PDFJS.getDocument but that's way over my head.
My base64Data looks like JVBERi0x...g==. It's a base64 encoded pdf document.

To solve this, I had to add
PDFJS.disableWorker = true;
to the beginning of my loadPage function.
From View PDF files directly within browser using PDF.js,
PDF.js uses Web Workers concept of HTML5 internally to process the
request. If this statement is set to false, it creates an instance of
Web workers. Web Workers run in an isolated thread. For more
information on web workers; please refer
http://www.html5rocks.com/en/tutorials/workers/basics/

Promise is missing in your code. Here how i fixed this probelm:
PDFJS.getDocument(pdfData).promise.then(function (pdf) {
// do your stuff
});

Related

Render PDF inline mobile and desktop

Hi
i'm struggling browsing a pdf inline well on mobile and Desktop
env is vite 3.1.3, rails 7.0.4
Desktop i brought to run on all Browsers by mozilla/pdfjs but on mobile it doesnt show anything.
My Code is (on Rails)
view
<canvas id="the-canvas"></canvas>
javascript
// NPM
import * as pdfjsLib from 'pdfjs-dist/build/pdf'
window.showMozillaPdf = function () {
var pdfData = atob(gon.doc_b64);
console.log(pdfjsLib)
pdfjsLib.GlobalWorkerOptions.workerSrc = '/documents/pdfjs_worker';
console.log('pdf-part-2!')
var loadingTask = pdfjsLib.getDocument({data: pdfData});
loadingTask.promise.then(function (pdf) {
console.log('PDF loaded');
// Fetch the first page
var pageNumber = 1;
pdf.getPage(pageNumber).then(function (page) {
console.log('Page loaded');
var scale = 1.5;
var viewport = page.getViewport({scale: scale});
// Prepare canvas using PDF page dimensions
var canvas = document.getElementById('the-canvas');
var context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
// Render PDF page into canvas context
var renderContext = {
canvasContext: context,
viewport: viewport
};
var renderTask = page.render(renderContext);
renderTask.promise.then(function () {
console.log('Page rendered');
});
});
}, function (reason) {
// PDF loading error
console.error(reason);
});
}
The workers js (/documents/pdfjs_worker) i inserted from a controller because of mime type conflicts. This can be made nicer but it runs now.
This runs well on desktop but doesnt show anything on Mobile (iPhone 11 / Safari)
Has anyone experience with mozilla/pdfjs on Mobile?
thanks,
Chris
found it
i had to change the way for getting the url to the worker asset:
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf'
import workerUrl from 'pdfjs-dist/legacy/build/pdf.worker.min?url'
...
pdfjsLib.GlobalWorkerOptions.workerSrc = workerUrl;
this replaces the '/documents/pdfjs_worker'
and i had to use the /legacy/ for getting it to run on mobile.
See vite-docs

Firefox addon - monitoring network

If I add an nsIHttpChannel observer, is there any way I can know what initiated the HTTP request(script, iframe, image etc..)
In chrome when monitoring the network from the background page you have the request type telling you if it came from an iframe, script etc...
Don't accept this as solution yet. I hope some other people can come and help build this solution.
I know this is for sure correct:
TEST FOR: XHR - identify XHR (ajax) response while listening to http response in firefox addon
Get load context of request (like which tab, which html window, which xul window) - Firefox add-on pageMod, catch ajax done in contentScriptFile (this is marked as optional though in the code below it requires a helper function: https://gist.github.com/Noitidart/644494bdc26f996739ef )
This I think is correct, by this i mean it works in my test cases but I'm not sure if its the recommended way:
TEST FOR: Frame or full page load - Can we differentiate between frame and non-frame loads with Ci in firefox addon
This I don't know how to do so I need help from community on this:
TEST FOR image - Comment on "identify XHR (ajax) response while listening to http response in firefox addon"
Image detection can be done through the MIME type. Check channel.contentType
Solution in progress:
var myobserve = function(aSubject, aTopic, aData) {
var httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
//start - test if xhr
var isXHR;
try {
var callbacks = httpChannel.notificationCallbacks;
var xhr = callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null;
isXHR = !!xhr;
} catch (e) {
isXHR = false;
}
//end - test if xhr
//start - test if frame OR full page load
var isFrameLoad;
var isFullPageLoad;
if (httpChannel.loadFlags & Ci.nsIHttpChannel.LOAD_INITIAL_DOCUMENT_URI) {
isFullPageLoad = true;
isFrameLoad = false;
} else if (httpChannel.loadFlags & Ci.nsIHttpChannel.LOAD_DOCUMENT_URI) {
isFrameLoad = true;
isFullPageLoad = false;
}
//end - test if frame OR full page load
//start - test if image
var isImg;
var isCss;
var isJs;
var isAudio;
//can keep going here
var mimeType = httpChannel.contentType;
if (/^image/i.test(mimeType)) {
isImg = true;
}
if (/^audio/i.test(mimeType)) {
isAudio = true;
}
if (/\/css$/i.test(mimeType)) {
isCss = true;
}
if (/\/js$/i.test(mimeType)) {
isJs = true;
}
//end - test if image
//start - OPTIONAL use loadContext to get a bunch of good stuff
//must paste the function from here: https://gist.github.com/Noitidart/644494bdc26f996739ef somewhere in your code
var goodies = loadContextAndGoodies(aSubject, true);
/*
//goodies is an object that holds the following information:
var goodies = {
loadContext: loadContext,
DOMWindow: DOMWindow,
gBrowser: gBrowser,
contentWindow: contentWindow,
browser: browser,
tab: tab
};
*/
// test if resource (such as image, or whatever) is being loaded is going into a frame [can also be used as altnerative way to test if frame load or full page]
var itemDestinationIsFrame;
var itemDestinationIsTopWin;
if (goodies.contentWindow) {
if (goodies.contentWindow.frameElement) {
itemDestinationIsFrame = true;
itemDestinationIsTopWin = false;
} else {
itemDestinationIsFrame = false;
itemDestinationIsTopWin = true;
}
}
//end - OPTIONAL use loadContext to get a bunch of good stuff
}
Of course to start observing:
Services.obs.addObserver(myobserve, 'http-on-modify-request', false);
and to stop:
Services.obs.removeObserver(myobserve, 'http-on-modify-request', false);
To start observing
To start start obseving all requests do this (for example on startup of your addon)
for (var o in observers) {
observers[o].reg();
}
To stop observing
Its important to stop observring (make sure to run this at least on shutdown of addon, you dont want to leave the observer registered for memory reasons)
for (var o in observers) {
observers[o].unreg();
}

Cordova Phonegap inappbrowser losing File Api functionality

I am developing an app using Web Audio Api. I have discovered that there is a memory leak in the way that Safari handles audio and doesn't garbage college the Audio Context correctly. For this reason I wish to load a new page. Have that page create the Audio Context, complete the operation and then close the window, so that the memory is released.
I have done the following to achieve this.
ref = window.open('record.html', '_self'); This will open the record.html page in the Cordova WebView according to https://wiki.apache.org/cordova/InAppBrowser
1 window.open('local-url.html');// loads in the
Cordova WebView
2 window.open('local-url.html', '_self');
// loads in the Cordova WebView
The record.html page loads a javascript file, that runs the operations that I wish to run. Here is the recordLoad.js file that makes some calls to native operations ( The native API is only available if loaded in the Cordova Webview and as you can see I need to access the file system, so this is the only way I can see to do it.
window.onload = createAudioContext;
ref = null;
function createAudioContext(){
console.log('createAudioContext');
window.AudioContext = window.AudioContext || window.webkitAudioContext;
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia;
window.URL = window.URL || window.webkitURL;
audioContext = new AudioContext;
getDirectory();
}
function getDirectory(){
console.log('getDirectory');
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, getFileSystem, fail);
}
function getFileSystem(directory){
console.log('getFileSystem');
var audioPath = localStorage.getItem('audioPath');
directory.root.getFile(audioPath, null, getVocalFile, fail);
}
function getVocalFile(fileEntry){
console.log('getVocalFile');
fileEntry.file(readVocalsToBuffer, fail);
}
function readVocalsToBuffer(file){
console.log('readVocalsToBuffer');
var reader = new FileReader();
reader.onloadend = function(evt){
var x = audioContext.decodeAudioData(evt.target._result, function(buffer){
if(!buffer){
console.log('error decoding file to Audio Buffer');
return;
}
window.voiceBuffer = buffer;
buffer = null;
loadBuffers();
});
}
reader.readAsArrayBuffer(file);
}
//web
function loadBuffers(){
console.log('loadBuffers');
var srcSong = localStorage.getItem('srcSong');
try{
var bufferLoader = new BufferLoader(
audioContext,
[
"."+srcSong
],
createOffLineContext
);
bufferLoader.load()
}
catch(e){
console.log(e.message);
}
}
//
function createOffLineContext(bufferList){
console.log('createOfflineContext');
offline = new webkitOfflineAudioContext(2, window.voiceBuffer.length, 44100);
var vocalSource = offline.createBufferSource();
vocalSource.buffer = window.voiceBuffer;
vocalSource.connect(offline.destination);
var backing = offline.createBufferSource();
backing.buffer = bufferList[0];
backing.connect(offline.destination);
vocalSource.start(0);
backing.start(0);
offline.oncomplete = function(ev){
bufferList = null;
console.log('audioContext');
console.log(audioContext);
audioContext = null;
console.log(audioContext);
vocalSource.stop(0);
backing.stop(0);
vocalSource.disconnect(0);
backing.disconnect(0);
vocalSource = null;
backing = null;
window.voiceBuffer = null;
window.renderedFile = ev.renderedBuffer;
var bufferR = ev.renderedBuffer.getChannelData(0);
var bufferL = ev.renderedBuffer.getChannelData(1);
var interleaved = interleave(bufferL, bufferR);
var dataview = encodeWAV(interleaved);
window.audioBlob = new Blob([dataview], {type: 'Wav'});
saveFile();
}
offline.startRendering();
}
// This file is very long, but once it is finished mixing the two audio buffers it writes a new file to the file system. And when that operation is complete I use
function gotFileWriter(writer){
console.log('gotFileWriter');
writer.onwriteend = function(evt){
console.log('onwriteEnd');
console.log(window.audioBlob);
delete window.audioBlob;
console.log(window.audioBlob);
// checkDirectory();
var ref = window.open('index.html', '_self');
// ref.addEventListener('exit', windowClose);
}
writer.write(audioBlob);
}
I return back to the original index.html file. This solves the memory issues. However, once I try to run the same operation a second time. ie load in the record.html file, and run the recordLoad.js file I receive an error ReferenceError: Can't find variable: LocalFileSystem
It would appear that in reload index.html some, but not all of the links to the Cordova API have been lost. I can still for example use the Media Api but not the File Api. I understand that this is a bit of a hacky way, (opening and closing windows) to solve the memory leak, but I cannot find any other way of doing it. I really need some help with this. So any pointers very very welcome.

Receive, send file over Web Api

I'm trying to write a WebApi service that receives a file, does a trivial manipulation, and sends the file back. I'm having issues on sending and/or receiving the file from the service.
The issue I'm having is that the file returned from the service is ~1.5x larger than the manipulated file, e.g. when the file is returned it's like 300kb instead of the 200kb it should be.
I assume its being wrapped and or manipulated somehow, and I'm unsure of how to receive it properly. The code for the WebAPI service and the method that calls the web service are included below
In, the WebApi service, when I hit the line return Ok(bufferResult), the file is a byte[253312]
In the method that calls the web service, after the file is manipulated and returned, following the line var content = stream.Result;, the stream has a length of 337754 bytes.
Web API service code
public ConversionController: APIController{
public async Task<IHttpActionResult> TransformImage()
{
if (!Request.Content.IsMimeMultipartContent())
throw new Exception();
var provider = new MultipartMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(provider);
var file = provider.Contents.First();
var filename = file.Headers.ContentDisposition.FileName.Trim('\"');
var buffer = await file.ReadAsByteArrayAsync();
var stream = new MemoryStream(buffer);
// [file manipulations omitted;]
// [the result is populated into a MemoryStream named response ]
//debug : save memory stream to disk to make sure tranformation is successfull
/*response.Position = 0;
path = #"C:\temp\file.ext";
using (var fileStream = System.IO.File.Create(path))
{
saveStream.CopyTo(fileStream);
}*/
var bufferResult = response.GetBuffer();
return Ok(bufferResult);
}
}
Method Calling the Service
public async Task<ActionResult> AsyncConvert()
{
var url = "http://localhost:49246/api/conversion/transformImage";
var filepath = "drive/file/path.ext";
HttpContent fileContent = new ByteArrayContent(System.IO.File.ReadAllBytes(filepath));
using (var client = new HttpClient())
{
using (var formData = new MultipartFormDataContent())
{
formData.Add(fileContent, "file", "fileName");
//call service
var response = client.PostAsync(url, formData).Result;
if (!response.IsSuccessStatusCode)
{
throw new Exception();
}
else
{
if (response.Content.GetType() != typeof(System.Net.Http.StreamContent))
throw new Exception();
var stream = response.Content.ReadAsStreamAsync();
var content = stream.Result;
var path = #"drive\completed\name.ext";
using (var fileStream = System.IO.File.Create(path))
{
content.CopyTo(fileStream);
}
}
}
}
return null;
}
I'm still new to streams and WebApi, so I may be missing something quite obvious. Why are the file streams different sizes? (eg. is it wrapped and how do I unwrap and/or receive the stream)
okay, to receive the file correctly, I needed to replace the line
var stream = response.Content.ReadAsStreamAsync();
with
var contents = await response.Content.ReadAsAsync<Byte[]>();
to provide the correct type for the binding
so, the later part of the methods that calls the service looks something like
var content = await response.Content.ReadAsAsync<Byte[]>();
var saveStream = new MemoryStream(content);
saveStream.Position = 0;
//Debug: save converted file to disk
/*
var path = #"drive\completed\name.ext";
using (var fileStream = System.IO.File.Create(path))
{
saveStream.CopyTo(fileStream);
}*/

In Actionscript, is there a way to create a copy or cached version of a Loader?

I'm loading an SWF animation and want to display it in multiple places concurrently, but the only way I could figure out how to do that is to load it every time I display it, as seen here:
private function playSounds():void {
for (var i:Number = 0; i < 14; i++)
{
for (var a:Number = 0; a < 16; a++)
{
if (boxes[i][a].x == linePos && boxes[i][a].selected == true && played[i][a] == false)
{
played[i][a] = true;
MovieClip();
var swf:URLRequest = new URLRequest("../assets/glow2.swf")
var glow:Loader = new Loader()
glow.load(swf)
glow.x = boxes[i][a].x - 25*0.7;
glow.y = boxes[i][a].y - 27*0.7;
glow.scaleX = 0.7;
glow.scaleY = 0.7;
this.addChild(glow);
glows.push(glow)
glowTime.push(0)
var sc:SoundChannel = new SoundChannel();
sc = (sounds[i] as Sound).play();
}
}
}
}
This is very very slow when it's being displayed more than, say, 5 times at once so I'm wondering if there's a way to only have to load it once and use it in multiple places.
You have the content property of the Loader.
Thus, load once, use the content many times.
edit: you may want to add a listener to know when the loading completes, before you use the loaded content:
addEventListener(Event.COMPLETE, completeHandler);
edit2:
var mc1:MovieClip = new MovieClip();
var mc2:MovieClip = new MovieClip();
var my_Loader:Loader = new Loader();
mc1.addChild(my_Loader);
mc2.addChild(my_Loader);
(haven't tried though).
One quick workaround is to create a second loader and pass the loaded bytes to that via the loadBytes() method:
var l:Loader = new Loader();
l.contentLoaderInfo.addEventListener(Event.COMPLETE,ready);
l.load(new URLRequest("../assets/glow2.swf"));
function ready(event:Event):void{
var mc:MovieClip = event.currentTarget.content as MovieClip;
var clone:Loader = new Loader();
clone.loadBytes(event.currentTarget.bytes);
addChild(mc);
addChild(clone);
clone.x = 100;
}
This will work in most cases. In some cases you should be able to get away with something as simple as:
var clone:MovieClip = MovieClip(new mc.constructor());
And there is also a 3rd option: 'blitting' your moviclip, which means you'll store one or more (depending how many MoveClip frames you need to cache) of BitmapData objects in memory which will draw() from the source MovieClip you want to draw (in multiple locations at the same time).
The loadBytes approach achieves what the question suggests: creates a copy of the Loader, so it re-uses the bytes loaded, but initializes new content, so uses memory for that.
If this is not what you need, caching the MovieClip using BitmapData is your best bet.

Resources