使用itextsharp xmlworker将html转换为pdf并垂直写入文本

时间:2016-03-23 13:52:20

标签: c# html asp.net itextsharp xmlworker

是否有可能在xmlworker中实现自下而上的文字方向编写?我想在表格中使用它。 我的代码是

     <table border=1>
     <tr>
     <td style="padding-right:18px">
          <p style="writing-mode:sideways-lr;text-align:center">First</p</td>
     <td style="padding-right:18px">
          <p style="writing-mode:sideways-lr;text-align:center">Second</p></td></tr>
     <tr><td><p style="text-align:center">1</p>  </td>
         <td><p style="text-align:center">2</p></td> 
     </tr>
        </table>

但是从html转换为pdf后它无法正常工作。文本FIRST和SECOND不是从下到上的方向。

3 个答案:

答案 0 :(得分:8)

这是一个非常有趣的问题,所以+1问题。

第一步是查找iTextSharp XML Worker是否支持HTML td标记。可以在iTextSharp.tool.xml.html.Tags的源代码中找到映射。在那里,您发现td已映射到iTextSharp.tool.xml.html.table.TableData,这使得实现自定义标记处理器的工作变得更加容易。即我们需要做的就是从类中继承并覆盖End()

public class TableDataProcessor : TableData
{
    /*
     * a **very** simple implementation of the CSS writing-mode property:
     * https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode
     */
    bool HasWritingMode(IDictionary<string, string> attributeMap)
    {
        bool hasStyle = attributeMap.ContainsKey("style");
        return hasStyle
                && attributeMap["style"].Split(new char[] { ';' })
                .Where(x => x.StartsWith("writing-mode:"))
                .Count() > 0
            ? true : false;
    }

    public override IList<IElement> End(
        IWorkerContext ctx,
        Tag tag,
        IList<IElement> currentContent)
    {
        var cells = base.End(ctx, tag, currentContent);
        var attributeMap = tag.Attributes;
        if (HasWritingMode(attributeMap))
        {
            var pdfPCell = (PdfPCell) cells[0];
            // **always** 'sideways-lr'
            pdfPCell.Rotation = 90;
        }
        return cells;
    }
}

正如内联评论中所述,这是一个非常简单的实现,可满足您的特定需求。您需要添加额外的逻辑来支持任何其他writing-mode CSS property value,并包括任何健全性检查。

更新

根据@Daniel留下的评论,在将CSS转换为HTML时,不清楚如何添加自定义PDF。首先是更新的HTML:

string XHTML = @"
<h1>Table with Vertical Text</h1>
<table><tr>
<td style='writing-mode:sideways-lr;text-align:center;width:40px;'>First</td>
<td style='writing-mode:sideways-lr;text-align:center;width:40px;'>Second</td></tr>
<tr><td style='text-align:center'>1</td>
<td style='text-align:center'>2</td></tr></table>

<h1>Table <u>without</u> Vertical Text</h1>
<table width='50%'>
<tr><td class='light-yellow'>0</td></tr>
<tr><td>1</td></tr>
<tr><td class='light-yellow'>2</td></tr>
<tr><td>3</td></tr>
</table>";

然后是一小段自定义CSS:

string CSS = @"
    body {font-size: 12px;}
    table {border-collapse:collapse; margin:8px;}
    .light-yellow {background-color:#ffff99;}
    td {border:1px solid #ccc;padding:4px;}
";

稍微有些困难的部分是额外的设置 - 你不能使用SO中常见的简单开箱即用XMLWorkerHelper.GetInstance().ParseXHtml()。这是一个简单的帮助方法,可以帮助您入门:

public void ConvertHtmlToPdf(string xHtml, string css)
{
    using (var stream = new FileStream(OUTPUT_FILE, FileMode.Create))
    {
        using (var document = new Document())
        {
            var writer = PdfWriter.GetInstance(document, stream);
            document.Open();

            // instantiate custom tag processor and add to `HtmlPipelineContext`.
            var tagProcessorFactory = Tags.GetHtmlTagProcessorFactory();
            tagProcessorFactory.AddProcessor(
                new TableDataProcessor(), 
                new string[] { HTML.Tag.TD }
            );
            var htmlPipelineContext = new HtmlPipelineContext(null);
            htmlPipelineContext.SetTagFactory(tagProcessorFactory);

            var pdfWriterPipeline = new PdfWriterPipeline(document, writer);
            var htmlPipeline = new HtmlPipeline(htmlPipelineContext, pdfWriterPipeline);

            // get an ICssResolver and add the custom CSS
            var cssResolver = XMLWorkerHelper.GetInstance().GetDefaultCssResolver(true);
            cssResolver.AddCss(css, "utf-8", true);
            var cssResolverPipeline = new CssResolverPipeline(
                cssResolver, htmlPipeline
            );

            var worker = new XMLWorker(cssResolverPipeline, true);
            var parser = new XMLParser(worker);
            using (var stringReader = new StringReader(xHtml))
            {
                parser.Parse(stringReader);
            }
        }
    }
}

不是重复上面示例代码的解释,而是see the documentation(iText删除了文档,链接到Wayback Machine),以便更好地了解为什么需要以这种方式设置解析器。

另请注意:

  1. XML Worker 不支持支持所有CSS2 / CSS3属性,因此可能需要尝试哪些有效或无效PDF查看浏览器中显示的HTML。
  2. HTML代码段删除了p代码,因为该样式可以直接应用于td代码。
  3. 内联width属性。如果省略,则列将是可变宽度,如果文本已水平渲染,则匹配。
  4. 使用iTextSharp和XML Worker版本5.5.9进行测试以下是更新结果:

    enter image description here

答案 1 :(得分:0)

public void addHtmlToPdf(Document document, PdfWriter writer, String html) {
    PdfPTable table = new PdfPTable(1);
    PdfPCell cell = new PdfPCell();
    ElementList list = XMLWorkerHelper.ParseToElementList(html, null);
    foreach(IElement element in list) {
        cell.AddElement(element);
    }
    table.AddCell(cell);
    document.Add(table);
}

替代utf8:

public void addHtmlToPdf_Utf8(Document document, PdfWriter writer, String html) 
{
    XMLWorkerHelper xml = XMLWorkerHelper.GetInstance();
    xml.ParseXHtml(writer, document, stringToStream(html), System.Text.Encoding.UTF8);
}
public Stream stringToStream(string txt) {
    var stream = new MemoryStream();
    var w = new StreamWriter(stream);
    w.Write(txt);
    w.Flush();
    stream.Position = 0;
    return stream;
}

答案 2 :(得分:0)

如何根据表格位置在pdf中呈现表格内的图像标签? - @kuujinbo

<table cellpadding="30" class="table table-striped" id="StudentInfoListTable">
                        <tr>
                            <td><img src="@Model.img" alt="" height="200" width="200" /></td>
                            <td class="middle"></td>
                            <td>LICENSE ID - @Html.DisplayFor(m => m.LicenseID)</td>
                        </tr>
                        <tr>
                            <td>NAME: </td>
                            <td class="middle"></td>
                            <td>@Html.DisplayFor(m => m.Name)</td>
                        </tr>
                        <tr>
                            <td>@Html.DisplayFor(m => m.Relo) </td>
                            <td class="middle"></td>
                            <td>@Html.DisplayFor(m => m.careOf)</td>
                        </tr>
                        <tr>
                            <td>GENDER: </td>
                            <td class="middle"></td>
                            <td>@Html.DisplayFor(m => m.gender)</td>
                        </tr>
                        <tr>
                            <td>BLOOD GROUP: </td>
                            <td class="middle"></td>
                            <td>@Html.DisplayFor(m => m.blood)</td>
                        </tr>
                        <tr>
                            <td>DATE OF BIRTH: </td>
                            <td class="middle"></td>
                            <td>@Html.DisplayFor(m => m.date)</td>
                        </tr>
                        <tr>
                            <td>CONTACT NO: </td>
                            <td class="middle"></td>
                            <td>@Html.DisplayFor(m => m.Mobile)</td>
                        </tr>
                        <tr>
                            <td>ADDRESS: </td>
                            <td class="middle"></td>
                            <td>@Html.DisplayFor(m => m.Address)</td>
                        </tr>
                        <tr>
                            <td>Signature </td>
                            <td class="middle"></td>
                            <td><img src="@Model.sign" alt="" style="background-color:white;"/></td>
                        </tr>
                    </table>