Просматривая различные форумы и вопросы, связанные со стеком, я не нашел ответа со сложным решением проблемы специальных символов. Я попытался предоставить один в обмен на довольно длинный ответ на вопрос. Надеюсь, это кому-нибудь поможет ...
Я использовал XMLWorker
из SourceForge , когда HtmlWorker
стал ограниченным. Проблема со спецсимволами осталась задуманной. Я нашел два решения, которые действительно работают и могут использоваться как по отдельности, так и в сочетании.
HTML & CSS решение
Для каждого задействованного тега необходимо указать стиль семейства шрифтов, чтобы правильно интерпретировать его методом ParseXHtml
(я не уверен, почему здесь не работает наследование стилей вложенных тегов, но, похоже, это не так или нет не полностью).
Это решение позволяет изменять полученный PDF-файл только на основе HTML-кода, поэтому могут иметь место некоторые сценарии без перекомпиляции кода.
Упрощенный код (для приложения MVC) будет выглядеть так:
Контроллер:
public FileStreamResult GetPdf()
{
const string CONTENT_TYPE = "application/pdf"
var fileName = "mySimple.pdf";
var html = GetViewPageHtmlCode();
//the way how to capture view HTML are described in other threads, e.g. [here][2]
var css = Server.MapPath("~/Content/Pdf.css");
using (var capturedActionStream = new MemoryStream(USED_ENCODING.GetBytes(html)))
{
using (var cssFile = new FileStream(css), FileMode.Open))
{
var memoryStream = new MemoryStream();
//to create landscape, use PageSize.A4.Rotate() for pageSize
var document = new Document(PageSize.A4, 30, 30, 10, 10);
var writer = PdfWriter.GetInstance(document, memoryStream);
var worker = XMLWorkerHelper.GetInstance();
document.Open();
worker.ParseXHtml(writer, document, capturedActionStream, cssFile);
writer.CloseStream = false;
document.Close();
memoryStream.Position = 0;
//to enforce file download
HttpContext.Response.AddHeader(
"Content-Disposition",
String.Format("attachment; filename={0}", fileName));
var wrappedPdf = new FileStreamResult(memoryStream, CONTENT_TYPE);
return wrappedPdf;
}
}
}
CSS:
body {
background-color: white;
font-size: .85em;
font-family: Arial;
margin: 0;
padding: 0;
color: black;
}
p, ul {
margin-bottom: 20px;
line-height: 1.6em;
}
div, span {
font-family: Arial;
}
h1, h2, h3, h4, h5, h6 {
font-size: 1.5em;
color: #000;
font-family: Arial;
}
Просмотр макета
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Pdf.css")" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="page">
<div id="main">
@RenderBody()
</div>
</div>
</body>
</html>
Просмотр страницы
@{
ViewBag.Title = "PDF page title"
}
<h1>@ViewBag.Title</h1>
<p>
ěščřžýáíéů ĚŠČŘŽÝÁÍÉŮ
</p>
Решение для замены шрифтов внутри кода
В этом решении шрифт, возвращаемый IFontProvider
, изменяется на тот, который содержит (правильное) представление специальных символов и используется кодировка BaseFont.IDENTITY_H. Преимущество такого подхода заключается в том, что используется ровно один шрифт. Это также недостаток рода.
Кроме того, в этом решении предполагается, что шрифт является частью проекта (файлы * .ttf, помещенные в папку Content/Fonts
).
В качестве альтернативы шрифты могут быть получены из расположения шрифтов Windows: Environment.GetFolderPath(Environment.SpecialFolder.Fonts)
- это требует знания (или строгого убеждения) шрифтов, установленных на сервере, или контроля над сервером
FontProvider
(свыше FontFactory
)
Я позволил себе немного расширить решение Грегора S , которое предоставляет более сложную FontFactory, которую можно использовать для различных HTML-шаблонов, проталкиваемых через XMLWorker.
public class CustomFontFactory : FontFactoryImp
{
public const Single DEFAULT_FONT_SIZE = 12;
public const Int32 DEFAULT_FONT_STYLE = 0;
public static readonly BaseColor DEFAULT_FONT_COLOR = BaseColor.BLACK;
public String DefaultFontPath { get; private set; }
public String DefaultFontEncoding { get; private set; }
public Boolean DefaultFontEmbedding { get; private set; }
public Single DefaultFontSize { get; private set; }
public Int32 DefaultFontStyle { get; private set; }
public BaseColor DefaultFontColor { get; private set; }
public Boolean ReplaceEncodingWithDefault { get; set; }
public Boolean ReplaceEmbeddingWithDefault { get; set; }
public Boolean ReplaceFontWithDefault { get; set; }
public Boolean ReplaceSizeWithDefault { get; set; }
public Boolean ReplaceStyleWithDefault { get; set; }
public Boolean ReplaceColorWithDefault { get; set; }
public BaseFont DefaultBaseFont { get; protected set; }
public CustomFontFactory(
String defaultFontFilePath,
String defaultFontEncoding = BaseFont.IDENTITY_H,
Boolean defaultFontEmbedding = BaseFont.EMBEDDED,
Single? defaultFontSize = null,
Int32? defaultFontStyle = null,
BaseColor defaultFontColor = null,
Boolean automaticalySetReplacementForNullables = true)
{
//set default font properties
DefaultFontPath = defaultFontFilePath;
DefaultFontEncoding = defaultFontEncoding;
DefaultFontEmbedding = defaultFontEmbedding;
DefaultFontColor = defaultFontColor == null
? DEFAULT_FONT_COLOR
: defaultFontColor;
DefaultFontSize = defaultFontSize.HasValue
? defaultFontSize.Value
: DEFAULT_FONT_SIZE;
DefaultFontStyle = defaultFontStyle.HasValue
? defaultFontStyle.Value
: DEFAULT_FONT_STYLE;
//set default replacement options
ReplaceFontWithDefault = false;
ReplaceEncodingWithDefault = true;
ReplaceEmbeddingWithDefault = false;
if (automaticalySetReplacementForNullables)
{
ReplaceSizeWithDefault = defaultFontSize.HasValue;
ReplaceStyleWithDefault = defaultFontStyle.HasValue;
ReplaceColorWithDefault = defaultFontColor != null;
}
//define default font
DefaultBaseFont = BaseFont.CreateFont(DefaultFontPath, DefaultFontEncoding, DefaultFontEmbedding);
//register system fonts
FontFactory.RegisterDirectories();
}
protected Font GetBaseFont(Single size, Int32 style, BaseColor color)
{
var baseFont = new Font(DefaultBaseFont, size, style, color);
return baseFont;
}
public override Font GetFont(String fontname, String encoding, Boolean embedded, Single size, Int32 style, BaseColor color, Boolean cached)
{
//eventually replace expected font properties
size = ReplaceSizeWithDefault
? DefaultFontSize
: size;
style = ReplaceStyleWithDefault
? DefaultFontStyle
: style;
encoding = ReplaceEncodingWithDefault
? DefaultFontEncoding
: encoding;
embedded = ReplaceEmbeddingWithDefault
? DefaultFontEmbedding
: embedded;
//get font
Font font = null;
if (ReplaceFontWithDefault)
{
font = GetBaseFont(
size,
style,
color);
}
else
{
font = FontFactory.GetFont(
fontname,
encoding,
embedded,
size,
style,
color,
cached);
if (font.BaseFont == null)
font = GetBaseFont(
size,
style,
color);
}
return font;
}
}
* * Контроллер тысяча сорок-девять
private const String DEFAULT_FONT_LOCATION = "~/Content/Fonts";
private const String DEFAULT_FONT_NAME = "arialn.ttf";
public FileStreamResult GetPdf()
{
const string CONTENT_TYPE = "application/pdf"
var fileName = "mySimple.pdf";
var html = GetViewPageHtmlCode();
//the way how to capture view HTML are described in other threads, e.g.
var css = Server.MapPath("~/Content/Pdf.css");
using (var capturedActionStream = new MemoryStream(USED_ENCODING.GetBytes(html)))
{
using (var cssFile = new FileStream(css), FileMode.Open))
{
var memoryStream = new MemoryStream();
var document = new Document(PageSize.A4, 30, 30, 10, 10);
//to create landscape, use PageSize.A4.Rotate() for pageSize
var writer = PdfWriter.GetInstance(document, memoryStream);
var worker = XMLWorkerHelper.GetInstance();
var defaultFontPath = Server
.MapPath(Path
.Combine(
DEFAULT_FONT_LOCATION,
DEFAULT_FONT_NAME));
var fontProvider = new CustomFontFactory(defaultFontPath);
document.Open();
worker.ParseXHtml(writer, document, capturedActionStream, cssFile, fontProvider);
writer.CloseStream = false;
document.Close();
memoryStream.Position = 0;
//to enforce file download
HttpContext.Response.AddHeader(
"Content-Disposition",
String.Format("attachment; filename={0}", fileName));
var wrappedPdf = new FileStreamResult(memoryStream, CONTENT_TYPE);
return wrappedPdf;
}
}
}
CSS:
body {
background-color: white;
font-size: .85em;
font-family: "Trebuchet MS", Verdana, Helvetica, Sans-Serif;
margin: 0;
padding: 0;
color: black;
}
p, ul {
margin-bottom: 20px;
line-height: 1.6em;
}
h1, h2, h3, h4, h5, h6 {
font-size: 1.5em;
color: #000;
}
Просмотр макета
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Pdf.css")" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="page">
<div id="main">
@RenderBody()
</div>
</div>
</body>
</html>
Просмотр страницы
@{
ViewBag.Title = "PDF page title"
}
<h1>@ViewBag.Title</h1>
<p>
ěščřžýáíéů ĚŠČŘŽÝÁÍÉŮ
</p>
Другие полезные (ре) источники: