首先,虽然 HTML 和 PDF 是在同一时间创建的,但它们并不相关。 HTML 旨在传达更高级别的信息,例如段落和表格。虽然有一些方法可以控制它,但最终要由浏览器来绘制这些更高级别的概念。 PDF 旨在传达文档,并且文档必须在任何呈现的地方“看起来”都一样。
在 HTML 文档中,您可能有一个 100% 宽的段落,根据显示器的宽度,可能需要 2 行或 10 行,打印时可能需要 7 行,当您在电话可能需要20行。然而,PDF 文件必须独立于渲染设备,因此无论您的屏幕尺寸如何,它必须始终完全一样地渲染。
由于上述必须,PDF 不支持“表格”或“段落”等抽象内容。 PDF 支持三种基本的东西:文本、线条/形状和图像。 (还有其他的东西,比如注释和电影,但我在这里尽量保持简单。) 在 PDF 中,你不会说“这是一段,浏览器做你的事!”。相反,您会说,“使用这个确切的字体在这个确切的 X、Y 位置绘制这个文本,不用担心,我之前已经计算了文本的宽度,所以我知道它都适合这条线”。您也没有说“这是一张桌子”,而是说“在这个确切位置绘制此文本,然后在我之前计算的另一个确切位置绘制一个矩形,所以我知道它会出现在文本周围”。
其次,iText 和 iTextSharp 解析 HTML 和 CSS。而已。 ASP.Net、MVC、Razor、Struts、Spring 等都是 HTML 框架,但 iText/iTextSharp 100% 不知道它们。与 DataGridViews、Repeater、模板、视图等相同,它们都是特定于框架的抽象。从您选择的框架中获取 HTML 是 您的 责任,iText 不会帮助您。如果您收到 The document has no pages 的异常信息,或者您认为“iText 没有解析我的 HTML”,那么您几乎可以确定您是 don't actually have HTML,您只是认为自己在解析。
第三,已经存在多年的内置类是HTMLWorker,但它已被XMLWorker(Java/.Net)取代。 HTMLWorker 正在完成零工作,它不支持 CSS 文件并且仅对最基本的 CSS 属性和实际上 breaks on certain tags 提供有限支持。如果您没有看到HTML attribute or CSS property and value in this file,那么HTMLWorker 可能不支持它。 XMLWorker 有时可能会更复杂,但这些并发症也会make it more extensible。
下面是 C# 代码,展示了如何将 HTML 标记解析为 iText 抽象,这些抽象会自动添加到您正在处理的文档中。 C# 和 Java 非常相似,因此转换它应该相对容易。 Example #1 使用内置的 HTMLWorker 来解析 HTML 字符串。由于仅支持内联样式,class="headline" 会被忽略,但其他所有内容都应该可以正常工作。示例 #2 与第一个示例相同,只是它使用 XMLWorker 代替。示例 #3 还解析了简单的 CSS 示例。
//Create a byte array that will eventually hold our final PDF
Byte[] bytes;
//Boilerplate iTextSharp setup here
//Create a stream that we can write to, in this case a MemoryStream
using (var ms = new MemoryStream()) {
//Create an iTextSharp Document which is an abstraction of a PDF but **NOT** a PDF
using (var doc = new Document()) {
//Create a writer that's bound to our PDF abstraction and our stream
using (var writer = PdfWriter.GetInstance(doc, ms)) {
//Open the document for writing
doc.Open();
//Our sample HTML and CSS
var example_html = @"<p>This <em>is </em><span class=""headline"" style=""text-decoration: underline;"">some</span> <strong>sample <em> text</em></strong><span style=""color: red;"">!!!</span></p>";
var example_css = @".headline{font-size:200%}";
/**************************************************
* Example #1 *
* *
* Use the built-in HTMLWorker to parse the HTML. *
* Only inline CSS is supported. *
* ************************************************/
//Create a new HTMLWorker bound to our document
using (var htmlWorker = new iTextSharp.text.html.simpleparser.HTMLWorker(doc)) {
//HTMLWorker doesn't read a string directly but instead needs a TextReader (which StringReader subclasses)
using (var sr = new StringReader(example_html)) {
//Parse the HTML
htmlWorker.Parse(sr);
}
}
/**************************************************
* Example #2 *
* *
* Use the XMLWorker to parse the HTML. *
* Only inline CSS and absolutely linked *
* CSS is supported *
* ************************************************/
//XMLWorker also reads from a TextReader and not directly from a string
using (var srHtml = new StringReader(example_html)) {
//Parse the HTML
iTextSharp.tool.xml.XMLWorkerHelper.GetInstance().ParseXHtml(writer, doc, srHtml);
}
/**************************************************
* Example #3 *
* *
* Use the XMLWorker to parse HTML and CSS *
* ************************************************/
//In order to read CSS as a string we need to switch to a different constructor
//that takes Streams instead of TextReaders.
//Below we convert the strings into UTF8 byte array and wrap those in MemoryStreams
using (var msCss = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(example_css))) {
using (var msHtml = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(example_html))) {
//Parse the HTML
iTextSharp.tool.xml.XMLWorkerHelper.GetInstance().ParseXHtml(writer, doc, msHtml, msCss);
}
}
doc.Close();
}
}
//After all of the PDF "stuff" above is done and closed but **before** we
//close the MemoryStream, grab all of the active bytes from the stream
bytes = ms.ToArray();
}
//Now we just need to do something with those bytes.
//Here I'm writing them to disk but if you were in ASP.Net you might Response.BinaryWrite() them.
//You could also write the bytes to a database in a varbinary() column (but please don't) or you
//could pass them to another function for further PDF processing.
var testFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test.pdf");
System.IO.File.WriteAllBytes(testFile, bytes);
2017 年更新
对于 HTML 到 PDF 的需求有好消息。作为this answer showed,W3C 标准css-break-3 将解决这个问题...这是一个候选推荐,计划在今年经过测试后变成最终推荐。
正如print-css.rocks 所示,有一些解决方案不那么标准,带有 C# 插件。