Ответ 1
Это была такая PITA, неудивительно, что все сторонние решения стоят 500 долларов за разработчика.
Хорошей новостью является то, что в Open XML SDK недавно добавлена поддержка .Net Standard, поэтому, похоже, вам повезло с форматом .docx
.
Плохая новость: на данный момент нет большого выбора для библиотек PDF в .NET Core. Поскольку это не похоже на то, что вы хотите заплатить за него, и вы не можете легально использовать стороннюю услугу, у нас нет иного выбора, кроме как самим кататься.
Основная проблема заключается в преобразовании содержимого документа Word в формат PDF. Одним из популярных способов является чтение Docx в HTML и его экспорт в PDF. Трудно было найти, но есть версия .Net Core OpenXMLSDK-PowerTools, которая поддерживает преобразование 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 в качестве проекта запуска. Запустите консольный проект:
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 является допустимым документом с текстом, в противном случае вы можете получить сообщение об ошибке:
указанный пакет недействителен. основная часть отсутствует
Если вы запустите проект, вы увидите, что 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, поскольку это значительно увеличит пропускную способность. Вы можете хранить HTML в файловой системе, облаке или в дБ, используя технологию VPP.
HTML в PDF
Следующее, что нам нужно сделать, это передать HTML в DinkToPdf. Загрузите решение DinkToPdf (90 МБ). Создайте решение - потребуется время, чтобы все пакеты были восстановлены и решение скомпилировано.
ВАЖНО:
Библиотека DinkToPdf требует файл libwkhtmltox.so и libwkhtmltox.dll в корневом каталоге вашего проекта, если вы хотите работать в Linux и Windows. Там также есть файл libwkhtmltox.dylib для Mac, если он вам нужен.
Эти библиотеки находятся в папке v0.12.4. В зависимости от вашего компьютера, 32 или 64 бит, скопируйте 3 файла в папку DinkToPdf-master\DinkToPfd.TestConsoleApp\bin\Debug\netcoreapp1.1.
ВАЖНО 2:
Убедитесь, что у вас установлен libgdiplus в вашем образе Docker или на вашем Linux-компьютере. Библиотека libwkhtmltox.so зависит от этого.
Установите DinkToPfd.TestConsoleApp в качестве проекта автозагрузки и измените файл Program.cs так, чтобы он считывал htmlContent из файла HTML, сохраненного с помощью Open-Xml-PowerTools, вместо текста Lorium Ipsom.
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 vs PDF довольно впечатляющий, и я сомневаюсь, что многие люди заметят много различий (особенно если они никогда не увидят оригинал):
Ps. Я понимаю, что вы хотели конвертировать .doc
и .docx
в PDF. Я бы посоветовал сделать сервис самостоятельно, чтобы конвертировать .doc в docx с использованием определенной несерверной технологии Windows/Microsoft. Формат doc является двоичным и не предназначен для автоматизации работы офиса на стороне сервера.