English 中文(简体)
Tell abcPdf to scale the html to fit on a single pdf page
原标题:

I am using abcPdf to convert an HTML report into a pdf file. The pdf has to be a single landscape A4 page.

Do you know if there is any way to tell abcPdf to scale the HTML page to fit on a single page in the pdf? I tried using the Magnify() method, and it scales the content but still breaks it into pages, even though it would fit on one page. I ve been scratching my head on this for a while now, and am wondering if anyone has done it.

Here s the code I m using at the moment:

public byte[] UrlToPdf(string url, PageOrientation po)
{
    using (Doc theDoc = new Doc())
    {
        // When in landscape mode:
        // We use two transforms to apply a generic 90 degree rotation around
        // the center of the document and rotate the drawing rectangle by the same amount.
        if (po == PageOrientation.Landscape)
        {
            // apply a rotation transform
            double w = theDoc.MediaBox.Width;
            double h = theDoc.MediaBox.Height;
            double l = theDoc.MediaBox.Left;
            double b = theDoc.MediaBox.Bottom;
            theDoc.Transform.Rotate(90, l, b);
            theDoc.Transform.Translate(w, 0);

            // rotate our rectangle
            theDoc.Rect.Width = h;
            theDoc.Rect.Height = w;

            // To change the default orientation of the document we need to apply a rotation to the root page object.
            //By doing this we ensure that every page in the document is viewed rotated.
            int theDocID = Convert.ToInt32(theDoc.GetInfo(theDoc.Root, "Pages"));
            theDoc.SetInfo(theDocID, "/Rotate", "90");
        }

        theDoc.HtmlOptions.PageCacheEnabled = false;
        theDoc.HtmlOptions.AddForms = false;
        theDoc.HtmlOptions.AddLinks = false;
        theDoc.HtmlOptions.AddMovies = false;
        theDoc.HtmlOptions.FontEmbed = false;
        theDoc.HtmlOptions.UseResync = false;
        theDoc.HtmlOptions.UseVideo = false;
        theDoc.HtmlOptions.UseScript = false;
        theDoc.HtmlOptions.HideBackground = false;
        theDoc.HtmlOptions.Timeout = 60000;
        theDoc.HtmlOptions.BrowserWidth = 0;
        theDoc.HtmlOptions.ImageQuality = 101;

        // Add url to document.
        int theID = theDoc.AddImageUrl(url, true, 0, true);
        while (true)
        {
            if (!theDoc.Chainable(theID))
                break;
            theDoc.Page = theDoc.AddPage();
            theID = theDoc.AddImageToChain(theID);
        }
        //Flattening the pages (Whatever that means)
        for (int i = 1; i <= theDoc.PageCount; i++)
        {
            theDoc.PageNumber = i;
            theDoc.Flatten();
        }

        return theDoc.GetData();
    }
}
最佳回答

So here s how I solved this.

First of all, I needed the height of the HTML page to be passed to the pdf generating method, so I added this on the page to be pdf-ed:

<asp:HiddenField ID="hfHeight" runat="server" />

and in the code behind:

protected void Page_Init(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        string scriptKey = "WidhtHeightForPdf";
        if (!Page.ClientScript.IsClientScriptBlockRegistered(scriptKey))
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("<script>")
                .AppendLine("document.getElementById( " + hfHeight.ClientID + " ).value = document.body.clientHeight;")
                .AppendLine("</script>");

            Page.ClientScript.RegisterStartupScript(typeof(Page), scriptKey, sb.ToString());
        }
    }
}

Now, when I call the pdf generating method I can pass it the height of the HTML. Once I have the height it s all a matter of calculating the width of the pdf "viewport" such that the height fits on the pdf page:

int intHTMLWidth = height.Value * Convert.ToInt32(theDoc.Rect.Width / theDoc.Rect.Height);

And then specify the BrowserWidth parameter either through HtmlOptions of theDoc:

theDoc.HtmlOptions.BrowserWidth = intHTMLWidth;

or when adding the url to theDoc:

int theID = theDoc.AddImageUrl(url, true, intHTMLWidth, true);

EDIT: This solves the question, so I m going to mark it as an answer. Now the next thing to do is create the pdf in protrait or landscape mode based on the width and height of the HTML, so that maximum space is used on the pdf page.

问题回答

This might be a bit simpler

    /// <summary>
    /// Calculate the height of given html
    /// </summary>
    /// <param name="html"></param>
    /// <returns></returns>
    public int CalculateHeight(string html)
    {
        int id = _Document.AddImageHtml(html);
        int height = (int)(_Document.GetInfoInt(id, "ScrollHeight") * PixelToPointScale);
        _Document.Delete( id );
        return height;
    }

[edit] Well scrollHeight fails with ver8 this works though

    private int AddImageHtml(string html)
    {
        try
        {
            return _Document.AddImageHtml("<div id= pdfx-div-pdf-frame  class= abcpdf-tag-visible  style= abcpdf-tag-visible: true; border: 1px solid red >" + html + "</div>");
        }
        catch (Exception ex)
        {
            throw new Exception(html, ex);
        }
    }

    private double GetElementHeight(int id)
    {
        abcpdf.XRect[] tagRects = _Document.HtmlOptions.GetTagRects(id);
        string[] tagIds = _Document.HtmlOptions.GetTagIDs(id);

        for (int i=0;i<tagRects.Length;i++)
        {
            abcpdf.XRect rect = tagRects[i];
            string tagId = tagIds[i];
            if (string.Equals(tagId, "pdfx-div-pdf-frame", StringComparison.CurrentCultureIgnoreCase))
            {
                return rect.Height;
            }
        }
        return -1;
    }

In case you are using Gecko engine, this engine does not support GetInfoInt so we need write some javascript to get the height. Do a dummy render first to determine height and then set this height to original AbcDoc.

using (var tempDoc = new Doc())
                {
                    tempDoc.HtmlOptions.Engine = EngineType.Gecko;
                    tempDoc.HtmlOptions.Media = MediaType.Print;
                    tempDoc.HtmlOptions.UseScript = true;
                    if (width.HasValue)
                        tempDoc.HtmlOptions.BrowserWidth = width.Value;

                    tempDoc.HtmlOptions.OnLoadScript = " window.onbeforeprint = function () { document.documentElement.abcpdf = Math.max( document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight );}";
                    int theTempID = tempDoc.AddImageHtml(htmlData);

                    int height = Convert.ToInt32(tempDoc.HtmlOptions.GetScriptReturn(theTempID));

                    tempDoc.Clear();
                    tempDoc.Dispose();

                    theDoc.MediaBox.Height = height;
                    theDoc.Rect.String = theDoc.MediaBox.String;
                    theDoc.AddImageHtml(htmlData);
                }




相关问题
Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

How to Add script codes before the </body> tag ASP.NET

Heres the problem, In Masterpage, the google analytics code were pasted before the end of body tag. In ASPX page, I need to generate a script (google addItem tracker) using codebehind ClientScript ...

Transaction handling with TransactionScope

I am implementing Transaction using TransactionScope with the help this MSDN article http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.aspx I just want to confirm that is ...

System.Web.Mvc.Controller Initialize

i have the following base controller... public class BaseController : Controller { protected override void Initialize(System.Web.Routing.RequestContext requestContext) { if (...

Microsoft.Contracts namespace

For what it is necessary Microsoft.Contracts namespace in asp.net? I mean, in what cases I could write using Microsoft.Contracts;?

Separator line in ASP.NET

I d like to add a simple separator line in an aspx web form. Does anyone know how? It sounds easy enough, but still I can t manage to find how to do it.. 10x!

热门标签