Скажите abcPdf для масштабирования HTML, чтобы поместиться на одной странице PDF
Я использую abcPdf для преобразования отчета в формате HTML в файл PDF. PDF должен быть одной страницей формата A4.
Знаете ли вы, есть ли способ сказать abcPdf, чтобы масштабировать HTML-страницу, чтобы она помещалась на одной странице в PDF? Я попытался использовать метод Magnify(), и он масштабирует содержимое, но разбивает его на страницы, даже если он умещается на одной странице. Я почесал голову над этим некоторое время, и мне интересно, если кто-то сделал это.
Вот код, который я использую в данный момент:
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();
}
}
3 ответа
Вот как я это решил.
Прежде всего, мне нужно было передать высоту HTML-страницы методу генерации pdf, поэтому я добавил это на страницу pdf-ed:
<asp:HiddenField ID="hfHeight" runat="server" />
и в коде позади:
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());
}
}
}
Теперь, когда я вызываю метод генерации PDF, я могу передать ему высоту HTML. Как только у меня будет высота, все дело в вычислении ширины "окна просмотра" в формате pdf, чтобы высота помещалась на странице pdf:
int intHTMLWidth = height.Value * Convert.ToInt32(theDoc.Rect.Width / theDoc.Rect.Height);
А затем укажите параметр BrowserWidth либо через HtmlOptions
из theDoc
:
theDoc.HtmlOptions.BrowserWidth = intHTMLWidth;
или при добавлении URL к theDoc
:
int theID = theDoc.AddImageUrl(url, true, intHTMLWidth, true);
РЕДАКТИРОВАТЬ: Это решает вопрос, поэтому я собираюсь отметить его как ответ. Теперь следующее, что нужно сделать, это создать pdf в режиме protrait или альбомной ориентации на основе ширины и высоты HTML-кода, чтобы на странице pdf использовалось максимальное пространство.
Это может быть немного проще
/// <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] Ну, scrollHeight не работает с версией 8, хотя это работает
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;
}
В случае, если вы используете движок 'Gecko', этот движок не поддерживает 'GetInfoInt', поэтому нам нужно написать некоторый javascript для получения высоты. Сначала выполните фиктивный рендеринг, чтобы определить высоту, а затем установите для этой высоты исходный 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);
}