2014-11-13 17 views
18

Ich arbeite an einer Anwendung (Android 4.4 - API 20), wo ich einen Bericht im HTML-Format erzeuge. Ich verwende das WebView-Objekt, um den Bericht in meiner App anzuzeigen.Android 4.4 Drucken als PDF ohne Benutzereinbindung

Was ich gerne tun könnte, ist diese WebView in ein PDF-Dokument zu konvertieren.

Ich konnte es mit PdfDocument konvertieren und .draw auf der Seite aus dem WebView-Objekt. Ich speichere die Datei, und das funktioniert, außer dass das Ergebnis ein Einzelseitendokument ist. Es gibt keine Seitenumbrüche.

 View content = (View) webView; 
     PrintAttributes pdfPrintAttrs = new PrintAttributes.Builder(). 
       setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME). 
       setMediaSize(PrintAttributes.MediaSize.NA_LETTER.asLandscape()). 
       setResolution(new Resolution("zooey", PRINT_SERVICE, 300, 300)). 
       setMinMargins(PrintAttributes.Margins.NO_MARGINS). 
       build(); 
     PdfDocument document = new PrintedPdfDocument(mContext,pdfPrintAttrs); 
     PageInfo pageInfo = new PageInfo.Builder(webView.getMeasuredWidth(), webView.getContentHeight(), 1).create(); 
     Page page = document.startPage(pageInfo); 
     content.draw(page.getCanvas()); 
     document.finishPage(page); 

Wenn ich es ändern, so dass ich die PrintedPdfDocumet verwenden, und geben Sie nicht die Seiteninfo erhalte ich nur den sichtbaren Teil des WebView-Objekt.

 View content = (View) webView; 
     PrintAttributes pdfPrintAttrs = new PrintAttributes.Builder(). 
       setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME). 
       setMediaSize(PrintAttributes.MediaSize.NA_LETTER.asLandscape()). 
       setResolution(new Resolution("zooey", PRINT_SERVICE, 300, 300)). 
       setMinMargins(PrintAttributes.Margins.NO_MARGINS). 
       build(); 
     PrintedPdfDocument document = new PrintedPdfDocument(mContext,pdfPrintAttrs); 
     Page page = document.startPage(0); 
     content.draw(page.getCanvas()); 
     document.finishPage(page); 

Wenn ich die Printmanager verwenden und einen Druckeradapter aus dem WebView-Objekt mit createPrintDocumentAdapter erstellen, kann ich die „Speicher als PDF“ Option auswählen und die resultierende PDF-Datei hat die Seitenumbrüche wie ich in der CSS angeben von die ursprüngliche Webseite.

 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE); 
     PrintDocumentAdapter printAdapter = webView.createPrintDocumentAdapter(); 
     String jobName = getString(R.string.app_name) + " Report " 
       + reportName; 
     PrintAttributes printAttrs = new PrintAttributes.Builder(). 
       setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME). 
       setMediaSize(PrintAttributes.MediaSize.NA_LETTER.asLandscape()). 
       setMinMargins(PrintAttributes.Margins.NO_MARGINS). 
       build(); 
     PrintJob printJob = printManager.print(jobName, printAdapter, 
       printAttrs); 

Meine Frage ist: kann ich angeben, dass ich die Printmanager möchte ein „Speichern als PDF“ auszuführen und den Namen und den Ort der resultierenden Datei zur Verfügung stellen, so dass es keine Interaktion mit dem Benutzer ist?

Oder: Gibt es eine Möglichkeit, mein WebView-Objekt in ein PDF zu konvertieren und Seitenumbrüche zu ermöglichen.

+0

keine Antwort wirklich, aber haben einen Blick auf diese Bibliothek, hat hat mir geholfen, bevor http://sourceforge.net/projects/itext/ – Janpan

+0

Ich dachte, dass iText erfordert, dass Sie eine Lizenz erwerben, um es zu verwenden. Ist das der Fall? – VingInMedina

+0

Nein, nur wenn Sie es kommerziell nutzen und Ihre App verkaufen wollen usw. Ich benutze es in einem oder zwei meiner Projekte, das ist einfach nur für mich im Büro. "Der Kauf einer kommerziellen Lizenz ist obligatorisch, sobald Sie mit dem Vertrieb der iText-Software in Ihrem Produkt oder der Bereitstellung in einem Netzwerk beginnen, ohne den Quellcode Ihrer eigenen Anwendungen unter der AGPL-Lizenz offenzulegen." Sehen Sie den Rest hier http://itextpdf.com/agpl – Janpan

Antwort

2

Nicht sicher, ob dies Ihre Seitenumbruchprobleme löst, aber haben Sie erwogen, die Open-Source-Bibliothek wkHTMLtoPDF (http://wkhtmltopdf.org/) für die Konvertierung von HTML zu PDF zu verwenden? Wir haben es ausgiebig genutzt, indem wir einen Micro-Service erstellt haben, an den wir den HTML-Code übergeben, ihn dann in PDF umwandeln lassen und den Link zum PDF zurücksenden oder die PDF-Datei (abhängig von der Größe) zurückgeben. Ich weiß, dass die Verwendung eines externen Dienstes für die Konvertierung ein Problem sein kann (oder vielleicht hast du keinen Internetzugang vom Gerät aus), aber wenn das kein Problem ist, könnte dies eine Option sein. Möglicherweise stehen auch andere APIs für diese Konvertierung zur Verfügung. Eine solche API ist Neutrino API. Es gibt viele andere - Sie APIs suchen eine dieser API Suchmaschinen:

  1. apis.io
  2. Progammable Web
  3. Public APIs
0

Ich hatte das gleiche Problem, und ich löste es durch manuelles Durchlaufen Sie den Lebenszyklus von PrintDocumentAdapter (onStart, onLayout, onWrite und onFinish). Es ist ein bisschen schwierig, da Sie Teile der versteckten Android-API benötigen.

Ich plane, eine Bibliothek von diesem zu erstellen, aber es wird einige Wochen dauern, bis ich genug Zeit habe. Ich werde meine Antwort aktualisieren, wenn die Bibliothek fertig ist.

+0

Das klingt wirklich interessant. Ich freue mich auf Ihre Lösung! – VingInMedina

+0

@Andreas machst du Fortschritte? – osayilgan

+0

Entschuldigung für die späte Antwort. Ich werde bis Ende nächster Woche alles umgestalten und es auf GitHub teilen. – Andreas

8

Es könnte eine späte Antwort sein, aber ich brauchte auch eine ähnliche Lösung mit Print Framework bis jetzt, und ich habe das PDF-Dokument in Seiten mit dem unten stehenden Code aufgeteilt.

Soweit ich sehen kann, können Sie nicht wirklich das WebView oder Pdf-Dokument Ihre PDF-Datei auf intelligente Weise in Seiten aufteilen (nicht schneiden Sie den Text oder das Bild). Was wir jedoch tun können, ist Seiten in einem Verhältnis von A4 oder Letter zu erstellen, so dass es in gedrucktes Papierformat passt.

Aber es gibt ein anderes Problem, mit dem ich konfrontiert bin. Der folgende Code funktioniert wie erwartet in Android 4.4, aber nicht in späteren Versionen. In Android-L wird nur der sichtbare Teil von WebView in die Pdf-Datei gezeichnet, aber weiße leere Seiten für den Rest des HTML in WebView.

Nach documentation,

public static void enableSlowWholeDocumentDraw()

Für Anwendungen, die L-Release-Targeting, WebView hat ein neues Standardverhalten, den Speicherplatzbedarf verringert und die Leistung erhöht durch die intelligente Wahl des Teils das HTML-Dokument, das gezeichnet werden muss. Diese Optimierungen sind für die Entwickler transparent. Doch unter bestimmten Umständen möchten ein App-Entwickler können sie deaktivieren:

  • Wenn eine App verwendet OnDraw (Canvas) eigene Zeichnung zu tun und greift auf Teile der Seite, die Art und Weise außerhalb des sichtbaren Teil der Seite ist.
  • Wenn eine App capturePicture() verwendet, um ein sehr großes HTML-Dokument zu erfassen. Beachten Sie, dass capturePicture eine veraltete API ist.

Das Zeichnen des gesamten HTML-Dokuments hat erhebliche Leistungskosten. Diese Methode sollte aufgerufen werden, bevor WebViews erstellt werden.

Ich habe ein Bug Report erstellt und auf einem ähnlichen Bug-Report kommentiert HERE, aber keine Antwort bisher. Aber bis dahin können Sie den folgenden Code verwenden.

/** 
* Creates a PDF Multi Page Document depending on the Ratio of Letter Size. 
* This method does not close the Document. It should be Closed after writing Pdf Document to a File. 
* 
* @return 
*/ 
private PdfDocument createMultiPagePdfDocument(int webViewWidth, int webViewHeight) { 

    /* Find the Letter Size Height depending on the Letter Size Ratio and given Page Width */ 
    int letterSizeHeight = getLetterSizeHeight(webViewWidth); 

    PdfDocument document = new PrintedPdfDocument(getActivity(), getPrintAttributes()); 

    final int numberOfPages = (webViewHeight/letterSizeHeight) + 1; 

    for (int i = 0; i < numberOfPages; i++) { 

     int webMarginTop = i*letterSizeHeight; 

     PdfDocument.PageInfo pageInfo = new PdfDocument.PageInfo.Builder(webViewWidth, letterSizeHeight, i+1).create(); 
     PdfDocument.Page page = document.startPage(pageInfo); 

     /* Scale Canvas */ 
     page.getCanvas().translate(0, -webMarginTop); 
     mWebView.draw(page.getCanvas()); 

     document.finishPage(page); 
    } 

    return document; 
} 


/** 
* Calculates the Letter Size Paper's Height depending on the LetterSize Dimensions and Given width. 
* 
* @param width 
* @return 
*/ 
private int getLetterSizeHeight(int width) { 
    return (int)((float)(11*width)/8.5); 
} 
+0

+1 für die Lösung, aber keine Abhilfe dafür gefunden oder nicht? Ich meine den Bug in Android L und höher Implementierung? –

+0

Nicht wirklich. Es sollte noch ausstehen. – osayilgan

0

Nachdem mit diesem Problem enormen Zeitaufwand, benutzte ich DexMaker nicht öffentlich abstrakt Rückrufe zu implementieren und kam mit dieser:

@Override 
protected void onPreExecute() { 
    super.onPreExecute(); 
    printAdapter = webView.createPrintDocumentAdapter(); 
} 

@Override 
protected Void doInBackground(Void... voids) { 

    File file = new File(pdfPath); 
    if (file.exists()) { 
     file.delete(); 
    } 
    try { 
     file.createNewFile(); 

     // get file descriptor 
     descriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); 

     // create print attributes 
     PrintAttributes attributes = new PrintAttributes.Builder() 
       .setMediaSize(PrintAttributes.MediaSize.ISO_A4) 
       .setResolution(new PrintAttributes.Resolution("id", PRINT_SERVICE, 300, 300)) 
       .setColorMode(PrintAttributes.COLOR_MODE_COLOR) 
       .setMinMargins(new PrintAttributes.Margins(0, 0, 0, 0)) 
       .build(); 
     ranges = new PageRange[]{new PageRange(1, numberPages)}; 

     // dexmaker cache folder 
     cacheFolder = new File(context.getFilesDir() +"/etemp/"); 

     printAdapter.onStart(); 

     printAdapter.onLayout(attributes, attributes, new CancellationSignal(), getLayoutResultCallback(new InvocationHandler() { 
      @Override 
      public Object invoke(Object o, Method method, Object[] objects) throws Throwable { 

       if (method.getName().equals("onLayoutFinished")) { 
        onLayoutSuccess(); 
       } else { 
        Log.e(TAG, "Layout failed"); 
        pdfCallback.onPdfFailed(); 
       } 
       return null; 
      } 
     }, cacheFolder), new Bundle()); 
    } catch (IOException e) { 
     e.printStackTrace(); 
     Log.e(TAG, e != null ? e.getMessage() : "PrintPdfTask unknown error"); 
    } 
    return null; 
} 

private void onLayoutSuccess() throws IOException { 
    PrintDocumentAdapter.WriteResultCallback callback = getWriteResultCallback(new InvocationHandler() { 
     @Override 
     public Object invoke(Object o, Method method, Object[] objects) throws Throwable { 
      if (method.getName().equals("onWriteFinished")) { 
       pdfCallback.onPdfCreated(); 
      } else { 
       Log.e(TAG, "Layout failed"); 
       pdfCallback.onPdfFailed(); 
      } 
      return null; 
     } 
    }, cacheFolder); 
    printAdapter.onWrite(ranges, descriptor, new CancellationSignal(), callback); 
} 


/** 
* Implementation of non public abstract class LayoutResultCallback obtained via DexMaker 
* @param invocationHandler 
* @param dexCacheDir 
* @return LayoutResultCallback 
* @throws IOException 
*/ 
public static PrintDocumentAdapter.LayoutResultCallback getLayoutResultCallback(InvocationHandler invocationHandler, 
                       File dexCacheDir) throws IOException { 
    return ProxyBuilder.forClass(PrintDocumentAdapter.LayoutResultCallback.class) 
      .dexCache(dexCacheDir) 
      .handler(invocationHandler) 
      .build(); 
} 

/** 
* Implementation of non public abstract class WriteResultCallback obtained via DexMaker 
* @param invocationHandler 
* @param dexCacheDir 
* @return LayoutResultCallback 
* @throws IOException 
*/ 
public static PrintDocumentAdapter.WriteResultCallback getWriteResultCallback(InvocationHandler invocationHandler, 
                       File dexCacheDir) throws IOException { 
    return ProxyBuilder.forClass(PrintDocumentAdapter.WriteResultCallback.class) 
      .dexCache(dexCacheDir) 
      .handler(invocationHandler) 
      .build(); 
}