2013-10-22 14 views
8

Sto disperatamente cercando di produrre un PDF generato da phantomJS stdout come herephantomjs pdf a stdout

Quello che sto ottenendo è un file PDF vuoto, anche se non 0 è in termini di dimensioni, viene visualizzata una pagina vuota.

var page = require('webpage').create(), 
system = require('system'), 
address; 

address = system.args[1]; 
page.paperSize = {format: 'A4'}; 

page.open(address, function (status) { 
    if (status !== 'success') { 
     console.log('Unable to load the address!'); 
     phantom.exit(); 
    } else { 
     window.setTimeout(function() { 
      page.render('/dev/stdout', { format: 'pdf' }); 
      phantom.exit(); 
     }, 1000); 
    } 
}); 

E mi chiamano in questo modo: phantomjs rasterize.js http://google.com>test.pdf

Ho provato a cambiare /dev/stdout-system.stdout ma non fortuna. Scrivere file PDF direttamente su file funziona senza problemi.

Sto cercando un'implementazione multipiattaforma, quindi spero che questo sia possibile sui sistemi non Linux.

+0

quale versione di phantomjs? prova ad aggiornare alla versione più recente. – philfreo

+1

Sto vedendo questo stesso problema su 1.9.2 Win8x64. Non piping l'output sembra avere qualche contenuto pdf nella console, ma piping l'output direttamente sul file tramite phantomjs rasterize.js> test.pdf non ha nulla da fare. –

+0

@philfreo Ho usato 1.9.2 su Win7 – michaeltintiuc

risposta

15

Quando si scrive di uscita per /dev/stdout/ o /dev/stderr/ su Windows, PhantomJS passa attraverso le seguenti fasi (come si vede nel render metodo in \phantomjs\src\webpage.cpp):

  1. in assenza di /dev/stdout/ e /dev/stderr/ una tem il percorso del file porary è allocato.
  2. Chiama il renderPdf con il percorso del file temporaneo.
  3. Renderizza la pagina Web su questo percorso file.
  4. Leggere il contenuto di questo file in un QByteArray.
  5. Chiamare QString::fromAscii sull'array di byte e scrivere su stdout o stderr.
  6. Elimina il file temporaneo.

Per cominciare, ho creato la sorgente per PhantomJS, ma ho commentato la cancellazione del file. Alla prossima esecuzione, sono stato in grado di esaminare il file temporaneo che aveva reso, che si è rivelato essere completamente soddisfacente. Ho anche provato a eseguire phantomjs.exe rasterize.js http://google.com > test.png con gli stessi risultati. Questo ha immediatamente escluso un problema di rendering, o qualcosa di specifico da fare con i PDF, il che significa che il problema doveva essere correlato al modo in cui i dati vengono scritti su stdout.

A questo punto avevo dei sospetti sull'esistenza di alcuni imbrogli di codifica del testo. Dalle esecuzioni precedenti, avevo sia una versione valida che non valida dello stesso file (un PNG in questo caso).

Utilizzando alcuni codice C#, ho eseguito il seguente esperimento:

//Read the contents of the known good file. 
byte[] bytesFromGoodFile = File.ReadAllBytes("valid_file.png"); 
//Read the contents of the known bad file. 
byte[] bytesFromBadFile = File.ReadAllBytes("invalid_file.png"); 

//Take the bytes from the valid file and convert to a string 
//using the Latin-1 encoding. 
string iso88591String = Encoding.GetEncoding("iso-8859-1").GetString(bytesFromGoodFile); 
//Take the Latin-1 encoded string and retrieve its bytes using the UTF-8 encoding. 
byte[] bytesFromIso88591String = Encoding.UTF8.GetBytes(iso88591String); 

//If the bytes from the Latin-1 string are all the same as the ones from the 
//known bad file, we have an encoding problem. 
Debug.Assert(bytesFromBadFile 
    .Select((b, i) => b == bytesFromIso88591String[i]) 
    .All(c => c)); 

Nota che ho usato la codifica ISO-8859-1 come QT utilizza questo come il default encoding for c-strings. Come si è scoperto, tutti quei byte erano uguali. Il punto di quell'esercizio era vedere se potevo imitare le fasi di codifica che hanno reso invalidi dati validi.

Per ulteriori prove, ho studiato \phantomjs\src\system.cpp e \phantomjs\src\filesystem.cpp.

  • Nel system.cpp, la classe System stive riferimenti, tra le altre cose, File oggetti per stdout, stdin e stderr, che sono impostati per utilizzare la codifica UTF-8.
  • Durante la scrittura su stdout, viene richiamata la funzione write dell'oggetto File. Questa funzione supporta la scrittura su file di testo e binari, ma a causa del modo in cui la classe System li inizializza, tutta la scrittura verrà trattata come se si trattasse di un file di testo.

Così il problema si riduce a questo: abbiamo bisogno di essere l'esecuzione di una scrittura binaria a stdout, eppure le nostre scritture finiscono per essere trattati come testo e avere una codifica applicato a loro che fa sì che il file risultante non valida.


Dato il problema descritto in precedenza, non riesco a vedere alcun modo per ottenere questo lavoro nel modo desiderato su Windows senza apportare modifiche al codice PhantomJS. Quindi sono qui:

Questo primo cambiamento fornirà una funzione che possiamo chiamare il File oggetti per eseguire in modo esplicito una scrittura binaria.

Aggiungere funzione il seguente prototipo in \phantomjs\src\filesystem.h:

bool binaryWrite(const QString &data); 

e luogo la sua definizione nel \phantomjs\src\filesystem.cpp (il codice per questo metodo deriva dal metodo write in questo file):

bool File::binaryWrite(const QString &data) 
{ 
    if (!m_file->isWritable()) { 
     qDebug() << "File::write - " << "Couldn't write:" << m_file->fileName(); 
     return true; 
    } 

    QByteArray bytes(data.size(), Qt::Uninitialized); 
    for(int i = 0; i < data.size(); ++i) { 
     bytes[i] = data.at(i).toAscii(); 
    } 
    return m_file->write(bytes); 
} 

A circa la linea 920 di \phantomjs\src\webpage.cpp vedrete un blocco di codice che assomiglia a questo:

if(fileName == STDOUT_FILENAME){ 
#ifdef Q_OS_WIN32 
     _setmode(_fileno(stdout), O_BINARY);    
#endif  

     ((File *)system->_stderr())->write(QString::fromAscii(name.constData(), name.size())); 

#ifdef Q_OS_WIN32 
     _setmode(_fileno(stdout), O_TEXT); 
#endif   
    } 

Change a questo:

if(fileName == STDOUT_FILENAME){ 
#ifdef Q_OS_WIN32 
     _setmode(_fileno(stdout), O_BINARY); 
     ((File *)system->_stdout())->binaryWrite(QString::fromAscii(ba.constData(), ba.size())); 
#elif    
     ((File *)system->_stderr())->write(QString::fromAscii(name.constData(), name.size())); 
#endif  

#ifdef Q_OS_WIN32 
     _setmode(_fileno(stdout), O_TEXT); 
#endif   
    } 

Allora, cosa che la sostituzione del codice non fa altro che richiama la nostra nuova funzione binaryWrite, ma lo fa sorvegliato da un blocco #ifdef Q_OS_WIN32. L'ho fatto in questo modo in modo da preservare la vecchia funzionalità su sistemi non Windows che non sembrano presentare questo problema (o no?). Si noti che questa correzione è valida solo per la scrittura stdout - se si vuole si può sempre applicarlo a stderr ma potrebbe non importa poi così tanto in quel caso.

Nel caso in cui si desideri solo un file binario precompilato (chi non lo sarebbe?), È possibile trovare phantomjs.exe con queste correzioni sul mio SkyDrive. La mia versione è di circa 19 MB mentre quella che ho scaricato in precedenza era solo di circa 6 MB, anche se ho seguito le istruzioni here, quindi dovrebbe andare bene.

+0

Questo è sorprendente, grazie mille per il vostro aiuto, il tempo e lo sforzo messi in questa risposta! – michaeltintiuc

0

È obbligatorio stampare il pdf su stdout? Non potresti cambiare il codice per:

var page = require('webpage').create(), 
system = require('system'), 
address; 

address = system.args[1]; 
output = system.args[2]; 
page.paperSize = {format: 'A4'}; 

page.open(address, function (status) { 
    if (status !== 'success') { 
     console.log('Unable to load the address!'); 
     phantom.exit(); 
    } else { 
     window.setTimeout(function() { 
      page.render(output, { format: 'pdf' }); 
      phantom.exit(); 
     }, 1000); 
    } 
}); 

e usarlo in questo modo:

phantomjs rasterize.js http://google.com test.pdf 
+0

Questo è quello che stavo facendo come un lavoro, ma la mia idea era quella di creare pdf al volo. Spingendo i dati abck e forth tra node-webkit push e phantomjs. – michaeltintiuc

+0

Darò un'occhiata profonda quindi, probabilmente c'è un personaggio che sta rovinando la struttura del PDF. –

7

Sì, è vero che ISO-8859-1 è la codifica predefinita per QT quindi sarà necessario aggiungere il parametro richiesto alla riga di comando --output-encoding = ISO-8859-1 in modo che l'output del pdf non essere danneggiato

vale a dire

rasterize.js phantomjs.exe --output-encoding = ISO-8859-1 < input.html> output.pdf

e rasterize.js assomiglia a questo (testato, funziona sia per Unix e Windows)

var page = require('webpage').create(), 
system = require('system'); 

page.viewportSize = {width: 600, height: 600}; 
page.paperSize = {format: 'A4', orientation: system.args[1], margin: '1cm'}; 

page.content = system.stdin.read(); 

window.setTimeout(function() { 
    try { 
     page.render('/dev/stdout', {format: 'pdf'}); 
    } 
    catch (e) { 
     console.log(e.message + ';;' + output_file); 
    } 
    phantom.exit(); 
}, 1000); 

o in alternativa è possibile impostare la codifica utilizzando stdout e se si sta leggendo da UTF-8 flusso allora potrebbe essere necessario impostare codifica stdin pure;

system.stdout.setEncoding('ISO-8859-1'); 
system.stdin.setEncoding('UTF-8'); 
page.content = system.stdin.read(); 
+1

Grazie amico, pazzo come una domanda così vecchia ha ricevuto una nuova risposta, grazie per il vostro tempo! Per un po 'non stavo lavorando a quel progetto, ma lo rivisiterò presto. – michaeltintiuc

+1

'system.stdout.setEncoding ('ISO-8859-1');' <- questa riga mi ha salvato dopo ore di debug. Grazie mille per questa risposta !! – Khan

+0

@ Khan Nessun problema :) – Pinchy