Xamarin中基于Razor模板自动将HTML转PDF的替代方案求助
Got it, let's figure out how to automate converting your WebView-loaded HTML to a PDF without relying on manual print dialogs. Your current code uses the system print flow, but we can tweak that to write directly to a file instead. Here are two solid approaches tailored for your Xamarin.Android setup:
Approach 1: Customize PrintDocumentAdapter to Write to PDF File
Instead of passing the PrintDocumentAdapter to the system PrintManager (which triggers the print dialog), we can wrap it to intercept the output stream and save it directly to a PDF file. This leverages the system's built-in HTML-to-PDF rendering and pagination logic, making it ideal for multi-page content.
Step-by-Step Implementation:
- Create a wrapper class for
PrintDocumentAdapterthat redirects the output to your target file:
public class FilePrintAdapter : PrintDocumentAdapter { private readonly PrintDocumentAdapter _originalAdapter; private readonly string _outputFilePath; public FilePrintAdapter(PrintDocumentAdapter originalAdapter, string outputFilePath) { _originalAdapter = originalAdapter; _outputFilePath = outputFilePath; } public override void OnLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal, LayoutResultCallback callback, Bundle extras) { _originalAdapter.OnLayout(oldAttributes, newAttributes, cancellationSignal, callback, extras); } public override void OnWrite(PageRange[] pages, ParcelFileDescriptor destination, CancellationSignal cancellationSignal, WriteResultCallback callback) { // Write the print output directly to our target PDF file using var targetStream = new FileStream(_outputFilePath, FileMode.Create); using var sourceStream = new FileStream(destination.FileDescriptor, FileAccess.ReadWrite); // Copy the rendered content to our file sourceStream.CopyTo(targetStream); // Notify the system we're done writing callback.OnWriteFinished(pages); } }
- Modify your existing code to use this wrapper, and ensure you wait for the WebView to fully load:
// Load your HTML into the WebView as before Control.LoadDataWithBaseURL("file:///android_asset/", new razorModel { Model = Model.MyModel.Instance }.GenerateString(), "text/html", "UTF-8", null); // Wait for the WebView to finish rendering content (critical!) Control.SetWebViewClient(new WebViewClient { public override void OnPageFinished(WebView view, string url) { base.OnPageFinished(view, url); // Define your output path (use app-specific storage for Android 10+ to avoid permission issues) string fileName = $"MyPrint_{Guid.NewGuid()}.pdf"; string outputPath = Path.Combine(Application.Context.GetExternalFilesDir(null).AbsolutePath, fileName); // Set up print attributes (adjust size, resolution, margins as needed) var printAttributes = new PrintAttributes.Builder() .SetMediaSize(PrintAttributes.MediaSize.IsoA4) .SetResolution(new PrintAttributes.Resolution("pdf_res", Android.Print.PrintAttributes.ResolutionQualityHigh, 600, 600)) .SetMinMargins(PrintAttributes.Margins.NoMargins) .Build(); // Create the custom adapter and trigger the conversion var originalAdapter = view.CreatePrintDocumentAdapter(fileName); var fileAdapter = new FilePrintAdapter(originalAdapter, outputPath); // Trigger layout and write flow fileAdapter.OnLayout(null, printAttributes, null, new LayoutCallback(fileAdapter), null); } }); // Helper callback to start writing after layout completes private class LayoutCallback : PrintDocumentAdapter.LayoutResultCallback { private readonly FilePrintAdapter _adapter; public LayoutCallback(FilePrintAdapter adapter) => _adapter = adapter; public override void OnLayoutFinished(PrintDocumentInfo info, bool changed) { base.OnLayoutFinished(info, changed); // Use a dummy descriptor to trigger the write process using var dummyDescriptor = ParcelFileDescriptor.Open(new File(Path.GetTempFileName()), ParcelFileMode.ReadWrite); _adapter.OnWrite(new[] { PageRange.AllPages }, dummyDescriptor, null, new WriteCallback()); } } // Helper callback to handle post-write actions private class WriteCallback : PrintDocumentAdapter.WriteResultCallback { public override void OnWriteFinished(PageRange[] pages) { base.OnWriteFinished(pages); // Add your post-processing logic here (e.g., notify user, upload file) Console.WriteLine("PDF saved successfully!"); } }
Key Notes:
- Wait for WebView load: Always trigger PDF generation in
OnPageFinishedto ensure all HTML, CSS, and images are fully rendered. - Storage permissions: For Android 10+, use app-specific storage (as shown) to avoid needing
WRITE_EXTERNAL_STORAGEpermission.
Approach 2: Manual Rendering with PdfDocument
If you need fine-grained control over the PDF output, you can use Android's PdfDocument to draw the WebView content directly onto PDF pages. This works best for single-page content, as multi-page pagination requires custom logic.
Example Code:
Control.SetWebViewClient(new WebViewClient { public override void OnPageFinished(WebView view, string url) { base.OnPageFinished(view, url); // Initialize PdfDocument with A4 page size (595x842 points) var document = new PdfDocument(); var pageInfo = new PdfDocument.PageInfo.Builder(595, 842, 1).Create(); var page = document.StartPage(pageInfo); // Draw the WebView content onto the PDF page var canvas = page.Canvas; float scaleFactor = Math.Min(595f / view.Width, 842f / view.Height); canvas.Scale(scaleFactor, scaleFactor); view.Draw(canvas); // Finish the page and save the document document.FinishPage(page); string outputPath = Path.Combine(Application.Context.GetExternalFilesDir(null).AbsolutePath, $"MyPrint_{Guid.NewGuid()}.pdf"); using var stream = new FileStream(outputPath, FileMode.Create); document.WriteTo(stream); document.Close(); Console.WriteLine("PDF saved via manual rendering!"); } });
Caveat:
- For long documents, you'll need to calculate content height, split it into multiple pages, and adjust the canvas position for each page—this can get complex compared to the first approach.
Both methods eliminate manual print dialogs and automate the PDF generation process. The first approach is recommended for most cases, as it handles pagination and rendering reliably using the system's built-in tools.
内容的提问来源于stack exchange,提问作者micael cunha

