这太痛苦了,难怪所有第三方解决方案都向每位开发者收取 500 美元。
好消息是Open XML SDK recently added support for .Net Standard,所以看起来你很幸运使用.docx 格式。
坏消息目前.NET Core 上的 PDF 生成库没有太多选择。由于您似乎不想付费,而且您不能合法使用第三方服务,所以我们别无选择,只能自己推出。
主要问题是将 Word 文档内容转换为 PDF。一种流行的方法是将 Docx 读入 HTML 并将其导出为 PDF。很难找到,但是 OpenXMLSDK-PowerTools 的 .Net Core 版本支持将 Docx 转换为 HTML。拉取请求“即将被接受”,您可以从这里获取:
https://github.com/OfficeDev/Open-Xml-PowerTools/tree/abfbaac510d0d60e2f492503c60ef897247716cf
现在我们可以将文档内容提取为 HTML,我们需要将其转换为 PDF。有一些库可以将 HTML 转换为 PDF,例如 DinkToPdf 是 Webkit HTML 到 PDF 库 libwkhtmltox 的跨平台包装器。
我认为 DinkToPdf 比 https://code.msdn.microsoft.com/How-to-export-HTML-to-PDF-c5afd0ce 好
Docx 到 HTML
让我们把它放在一起,下载 OpenXMLSDK-PowerTools .Net Core 项目并构建它(仅 OpenXMLPowerTools.Core 和 OpenXMLPowerTools.Core.Example - 忽略其他项目)。
将 OpenXMLPowerTools.Core.Example 设置为 StartUp 项目。在项目中添加一个 Word 文档(例如 test.docx)并设置这个 docx 文件属性Copy To Output = If Newer
运行控制台项目:
static void Main(string[] args)
{
var source = Package.Open(@"test.docx");
var document = WordprocessingDocument.Open(source);
HtmlConverterSettings settings = new HtmlConverterSettings();
XElement html = HtmlConverter.ConvertToHtml(document, settings);
Console.WriteLine(html.ToString());
var writer = File.CreateText("test.html");
writer.WriteLine(html.ToString());
writer.Dispose();
Console.ReadLine();
确保 test.docx 是包含一些文本的有效 word 文档,否则您可能会收到错误:
指定的包无效。主要部分不见了
如果您运行该项目,您将看到 HTML 看起来几乎与 Word 文档中的内容一模一样:
但是,如果您尝试使用带有图片或链接的 Word 文档,您会发现它们丢失或损坏。
这篇 CodeProject 文章解决了这些问题:https://www.codeproject.com/Articles/1162184/Csharp-Docx-to-HTML-to-Docx
我必须更改 static Uri FixUri(string brokenUri) 方法以返回 Uri 并添加用户友好的错误消息。
static void Main(string[] args)
{
var fileInfo = new FileInfo(@"c:\temp\MyDocWithImages.docx");
string fullFilePath = fileInfo.FullName;
string htmlText = string.Empty;
try
{
htmlText = ParseDOCX(fileInfo);
}
catch (OpenXmlPackageException e)
{
if (e.ToString().Contains("Invalid Hyperlink"))
{
using (FileStream fs = new FileStream(fullFilePath,FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
UriFixer.FixInvalidUri(fs, brokenUri => FixUri(brokenUri));
}
htmlText = ParseDOCX(fileInfo);
}
}
var writer = File.CreateText("test1.html");
writer.WriteLine(htmlText.ToString());
writer.Dispose();
}
public static Uri FixUri(string brokenUri)
{
string newURI = string.Empty;
if (brokenUri.Contains("mailto:"))
{
int mailToCount = "mailto:".Length;
brokenUri = brokenUri.Remove(0, mailToCount);
newURI = brokenUri;
}
else
{
newURI = " ";
}
return new Uri(newURI);
}
public static string ParseDOCX(FileInfo fileInfo)
{
try
{
byte[] byteArray = File.ReadAllBytes(fileInfo.FullName);
using (MemoryStream memoryStream = new MemoryStream())
{
memoryStream.Write(byteArray, 0, byteArray.Length);
using (WordprocessingDocument wDoc =
WordprocessingDocument.Open(memoryStream, true))
{
int imageCounter = 0;
var pageTitle = fileInfo.FullName;
var part = wDoc.CoreFilePropertiesPart;
if (part != null)
pageTitle = (string)part.GetXDocument()
.Descendants(DC.title)
.FirstOrDefault() ?? fileInfo.FullName;
WmlToHtmlConverterSettings settings = new WmlToHtmlConverterSettings()
{
AdditionalCss = "body { margin: 1cm auto; max-width: 20cm; padding: 0; }",
PageTitle = pageTitle,
FabricateCssClasses = true,
CssClassPrefix = "pt-",
RestrictToSupportedLanguages = false,
RestrictToSupportedNumberingFormats = false,
ImageHandler = imageInfo =>
{
++imageCounter;
string extension = imageInfo.ContentType.Split('/')[1].ToLower();
ImageFormat imageFormat = null;
if (extension == "png") imageFormat = ImageFormat.Png;
else if (extension == "gif") imageFormat = ImageFormat.Gif;
else if (extension == "bmp") imageFormat = ImageFormat.Bmp;
else if (extension == "jpeg") imageFormat = ImageFormat.Jpeg;
else if (extension == "tiff")
{
extension = "gif";
imageFormat = ImageFormat.Gif;
}
else if (extension == "x-wmf")
{
extension = "wmf";
imageFormat = ImageFormat.Wmf;
}
if (imageFormat == null) return null;
string base64 = null;
try
{
using (MemoryStream ms = new MemoryStream())
{
imageInfo.Bitmap.Save(ms, imageFormat);
var ba = ms.ToArray();
base64 = System.Convert.ToBase64String(ba);
}
}
catch (System.Runtime.InteropServices.ExternalException)
{ return null; }
ImageFormat format = imageInfo.Bitmap.RawFormat;
ImageCodecInfo codec = ImageCodecInfo.GetImageDecoders()
.First(c => c.FormatID == format.Guid);
string mimeType = codec.MimeType;
string imageSource =
string.Format("data:{0};base64,{1}", mimeType, base64);
XElement img = new XElement(Xhtml.img,
new XAttribute(NoNamespace.src, imageSource),
imageInfo.ImgStyleAttribute,
imageInfo.AltText != null ?
new XAttribute(NoNamespace.alt, imageInfo.AltText) : null);
return img;
}
};
XElement htmlElement = WmlToHtmlConverter.ConvertToHtml(wDoc, settings);
var html = new XDocument(new XDocumentType("html", null, null, null),
htmlElement);
var htmlString = html.ToString(SaveOptions.DisableFormatting);
return htmlString;
}
}
}
catch
{
return "The file is either open, please close it or contains corrupt data";
}
}
您可能需要 System.Drawing.Common NuGet 包才能使用 ImageFormat
现在我们可以获取图片了:
如果您只想在网络浏览器中显示 Word .docx 文件,最好不要将 HTML 转换为 PDF,因为这会显着增加带宽。您可以使用 VPP 技术将 HTML 存储在文件系统、云或 dB 中。
HTML 转 PDF
接下来我们需要做的是将 HTML 传递给 DinkToPdf。下载 DinkToPdf (90 MB) 解决方案。构建解决方案 - 恢复所有包并编译解决方案需要一段时间。
重要提示:
如果您想在 Linux 和 Windows 上运行,DinkToPdf 库需要项目根目录中的 libwkhtmltox.so 和 libwkhtmltox.dll 文件。如果需要,还有一个适用于 Mac 的 libwkhtmltox.dylib 文件。
这些 DLL 位于 v0.12.4 文件夹中。根据您的 PC(32 位或 64 位),将 3 个文件复制到 DinkToPdf-master\DinkToPfd.TestConsoleApp\bin\Debug\netcoreapp1.1 文件夹。
重要提示 2:
确保您已在 Docker 映像或 Linux 机器上安装了 libgdiplus。 libwkhtmltox.so 库依赖于它。
将 DinkToPfd.TestConsoleApp 设置为 StartUp 项目并更改 Program.cs 文件以从使用 Open-Xml-PowerTools 而不是 Lorium Ipsom 文本保存的 HTML 文件中读取 htmlContent。
var doc = new HtmlToPdfDocument()
{
GlobalSettings = {
ColorMode = ColorMode.Color,
Orientation = Orientation.Landscape,
PaperSize = PaperKind.A4,
},
Objects = {
new ObjectSettings() {
PagesCount = true,
HtmlContent = File.ReadAllText(@"C:\TFS\Sandbox\Open-Xml-PowerTools-abfbaac510d0d60e2f492503c60ef897247716cf\ToolsTest\test1.html"),
WebSettings = { DefaultEncoding = "utf-8" },
HeaderSettings = { FontSize = 9, Right = "Page [page] of [toPage]", Line = true },
FooterSettings = { FontSize = 9, Right = "Page [page] of [toPage]" }
}
}
};
Docx 与 PDF 的结果令人印象深刻,我怀疑很多人会找出许多不同之处(尤其是如果他们从未看过原件的话):
附言。我知道您想将.doc 和.docx 都转换为PDF。我建议自己制作一项服务,使用特定的非服务器 Windows/Microsoft 技术将 .doc 转换为 docx。文档格式为二进制,不适用于server side automation of office。