wkhtmltopdf在PDF上没有显示页脚。

huangapple go评论93阅读模式
英文:

wkhtmltopdf not displaying footer on PDF

问题

我正在使用wkhtmltopdf库生成PDF,但是当它在服务器上托管时,无法在PDF文档中获取页脚。在我的本地系统上,它可以正常工作。我使用以下代码设置页脚:

func HTMLtoPDF(html string, someID int64, templatePath string) (*wkhtml.PDFGenerator, error) {
    pdfg, err := wkhtml.NewPDFGenerator()
    if err != nil {
        return nil, err
    }
    page := wkhtml.NewPageReader(strings.NewReader(util.FilterSmartQuotesEtc(html)))
    tempPath := path.Clean(fmt.Sprintf("%v/%s", templatePath, "footer.html"))
    footerFilePath, err := getFooterFilePath(tempPath, someID)
    if err != nil {
        return nil, err
    }
    page.FooterHTML.Set(footerFilePath)
    
    pdfg.AddPage(page)
    pdfg.MarginTop.Set(10)
    pdfg.MarginBottom.Set(10)

    // Create PDF document in internal buffer
    err = pdfg.Create()

    if err != nil {
        return nil, err
    }

    return pdfg, nil
}

func getFooterFilePath(templatePath string, someID int64) (string, error) {
    tmpl, err := htmlTemplate.ParseFiles(templatePath)
    if err != nil {
        return "", err
    }

    var buf bytes.Buffer
    data := FooterData {
        SomeID: someID,
    }
    err = tmpl.Execute(&buf, data)
    if err != nil {
        return "", err
    }

    // Save the rendered HTML to a temporary file
    tmpFile, err := ioutil.TempFile("", "footer-*.html")
    if err != nil {
        return "", err
    }

    if _, err := tmpFile.Write(buf.Bytes()); err != nil {
        return "", err
    }
    if err := tmpFile.Close(); err != nil {
        return "", err
    }
    return tmpFile.Name(), nil
}

我已经验证了在getFooterFilePath函数中创建了临时页脚文件,并且我的someID变量也被替换了。我通过记录生成的临时页脚文件并在日志中查看内容来进行了检查。
我是否做错了什么?非常感谢任何帮助。

英文:

I am using wkhtmltopdf library to generate PDF and I cannot get the footer on the PDF doc when it is hosted on the server. It works properly on my local system. I am using below code to set the footer.

func HTMLtoPDF(html string, someID int64, templatePath string) (*wkhtml.PDFGenerator, error) {
pdfg, err := wkhtml.NewPDFGenerator()
if err != nil {
return nil, err
}
page := wkhtml.NewPageReader(strings.NewReader(util.FilterSmartQuotesEtc(html)))
tempPath := path.Clean(fmt.Sprintf("%v/%s", templatePath, "footer.html"))
footerFilePath, err := getFooterFilePath(tempPath, someID)
if err != nil {
return nil, err
}
page.FooterHTML.Set(footerFilePath)
pdfg.AddPage(page)
pdfg.MarginTop.Set(10)
pdfg.MarginBottom.Set(10)
// Create PDF document in internal buffer
err = pdfg.Create()
if err != nil {
return nil, err
}
return pdfg, nil
}
func getFooterFilePath(templatePath string, someID int64) (string, error) {
tmpl, err := htmlTemplate.ParseFiles(templatePath)
if err != nil {
return "", err
}
var buf bytes.Buffer
data :=	FooterData {
SomeID: someID,
}
err = tmpl.Execute(&buf, data)
if err != nil {
return "", err
}
// Save the rendered HTML to a temporary file
tmpFile, err := ioutil.TempFile("", "footer-*.html")
if err != nil {
return "", err
}
if _, err := tmpFile.Write(buf.Bytes()); err != nil {
return "", err
}
if err := tmpFile.Close(); err != nil {
return "", err
}
return tmpFile.Name(), nil
}

I have verfied that the temp footer file is getting created inside getFooterFilePath function and my someID variable is also substituted. I checked this by logging the generated temp footer file and I can see the content in the logs.
Am I doing anything wrong? Any help is really appreciated

答案1

得分: 1

wkhtmltopdf具有--replace <name> <value>选项,用于在页眉和页脚中替换namevalue(可重复)。这是创建动态页眉和页脚的推荐方法。希望这可以解决问题。参考链接:https://wkhtmltopdf.org/usage/wkhtmltopdf.txt。

这是示例代码:

package main

import (
	"os"
	"strconv"
	"strings"

	wkhtml "github.com/SebastiaanKlippert/go-wkhtmltopdf"
)

func HTMLtoPDF(html string, someID int64) (*wkhtml.PDFGenerator, error) {
	pdfg, err := wkhtml.NewPDFGenerator()
	if err != nil {
		return nil, err
	}
	page := wkhtml.NewPageReader(strings.NewReader(html))

	page.FooterHTML.Set("footer.html")

	// 以下参数将传递给footer.html中的查询。
	page.Replace.Set("hello", "world")
	page.Replace.Set("my_id", strconv.Itoa(int(someID)))

	pdfg.AddPage(page)
	pdfg.MarginTop.Set(10)
	pdfg.MarginBottom.Set(10)

	err = pdfg.Create()

	if err != nil {
		return nil, err
	}

	return pdfg, nil
}

func main() {
	pdfg, err := HTMLtoPDF(html, 123457789)
	if err != nil {
		panic(err)
	}

	err = os.WriteFile("temp.pdf", pdfg.Bytes(), 0o644)
	if err != nil {
		panic(err)
	}
}

var html = `
<html>
    <body style="font-size:100pt;">
        Hello world!
    </body>
</html>
`

这是footer.html文件的内容:

<!DOCTYPE html>
<html>
  <head>
    <script>
      function subst() {
        var vars = {};
        var query_strings_from_url = document.location.search
          .substring(1)
          .split('&');
        for (var query_string in query_strings_from_url) {
          if (query_strings_from_url.hasOwnProperty(query_string)) {
            var temp_var = query_strings_from_url[query_string].split('=', 2);
            vars[temp_var[0]] = decodeURI(temp_var[1]);
          }
        }
        var css_selector_classes = [
          // 这些值由wkhtmltopdf提供。
          'page',
          'frompage',
          'topage',
          'webpage',
          'section',
          'subsection',
          'date',
          'isodate',
          'time',
          'title',
          'doctitle',
          'sitepage',
          'sitepages',

          // 这些值由"replace"选项提供。
          'hello',
          'my_id',
        ];
        for (var css_class in css_selector_classes) {
          if (css_selector_classes.hasOwnProperty(css_class)) {
            var element = document.getElementsByClassName(
              css_selector_classes[css_class]
            );
            for (var j = 0; j < element.length; ++j) {
              element[j].textContent = vars[css_selector_classes[css_class]];
            }
          }
        }
      }
    </script>
  </head>
  <body style="border: 0; margin: 0" onload="subst()">
    <table style="border-top: 1px solid black; width: 100%">
      <tr>
        <td class="section"></td>
        <td class="hello"></td>
        <td class="my_id"></td>
        <td style="text-align: right">
          Page <span class="page"></span> of <span class="topage"></span>
        </td>
      </tr>
    </table>
  </body>
</html>

如果上述方法不起作用,或者您想找出使用临时文件方法出现了什么问题,我们可以先进行以下操作以缩小问题范围:

  1. 运行strace -f /path/to/the/app > log.txt来跟踪系统调用和信号。-f选项使其跟踪子进程(wkhtmltopdf在子进程中运行)。

  2. 上一步的日志应包含传递给wkhtmltopdf命令的选项。直接运行带有相同选项的wkhtmltopdf命令,看看是否有任何区别。

    1. 如果页脚正确呈现,则Go代码中可能有问题。可能是临时文件尚未准备好使用。
    2. 如果没有任何区别,由于相同的命令在您的本地机器上运行正常,这可能与环境有关。也许服务器上的wkhtmltopdf版本有问题;也许服务器没有所需的字体。

请提供log.txt和直接运行wkhtmltopdf的结果,以便我们决定下一步该怎么做。

英文:

wkhtmltopdf has the --replace &lt;name&gt; &lt;value&gt; option to replace name with value in header and footer (repeatable). This is the recommended way to create a dynamic header and footer. Hopefully it will workaround the issue. Reference: https://wkhtmltopdf.org/usage/wkhtmltopdf.txt.

Here is the demo:

package main

import (
	&quot;os&quot;
	&quot;strconv&quot;
	&quot;strings&quot;

	wkhtml &quot;github.com/SebastiaanKlippert/go-wkhtmltopdf&quot;
)

func HTMLtoPDF(html string, someID int64) (*wkhtml.PDFGenerator, error) {
	pdfg, err := wkhtml.NewPDFGenerator()
	if err != nil {
		return nil, err
	}
	page := wkhtml.NewPageReader(strings.NewReader(html))

	page.FooterHTML.Set(&quot;footer.html&quot;)

	// the following parameters are passed to footer.html in the query.
	page.Replace.Set(&quot;hello&quot;, &quot;world&quot;)
	page.Replace.Set(&quot;my_id&quot;, strconv.Itoa(int(someID)))

	pdfg.AddPage(page)
	pdfg.MarginTop.Set(10)
	pdfg.MarginBottom.Set(10)

	err = pdfg.Create()

	if err != nil {
		return nil, err
	}

	return pdfg, nil
}

func main() {
	pdfg, err := HTMLtoPDF(html, 123457789)
	if err != nil {
		panic(err)
	}

	err = os.WriteFile(&quot;temp.pdf&quot;, pdfg.Bytes(), 0o644)
	if err != nil {
		panic(err)
	}
}

var html = `
&lt;html&gt;
    &lt;body style=&quot;font-size:100pt;&quot;&gt;
        Hello world!
    &lt;/body&gt;
&lt;/html&gt;
`

And here is the footer.html file:

&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;script&gt;
      function subst() {
        var vars = {};
        var query_strings_from_url = document.location.search
          .substring(1)
          .split(&#39;&amp;&#39;);
        for (var query_string in query_strings_from_url) {
          if (query_strings_from_url.hasOwnProperty(query_string)) {
            var temp_var = query_strings_from_url[query_string].split(&#39;=&#39;, 2);
            vars[temp_var[0]] = decodeURI(temp_var[1]);
          }
        }
        var css_selector_classes = [
          // these values are provided by wkhtmltopdf.
          &#39;page&#39;,
          &#39;frompage&#39;,
          &#39;topage&#39;,
          &#39;webpage&#39;,
          &#39;section&#39;,
          &#39;subsection&#39;,
          &#39;date&#39;,
          &#39;isodate&#39;,
          &#39;time&#39;,
          &#39;title&#39;,
          &#39;doctitle&#39;,
          &#39;sitepage&#39;,
          &#39;sitepages&#39;,

          // these values are provided by the &quot;replace&quot; option.
          &#39;hello&#39;,
          &#39;my_id&#39;,
        ];
        for (var css_class in css_selector_classes) {
          if (css_selector_classes.hasOwnProperty(css_class)) {
            var element = document.getElementsByClassName(
              css_selector_classes[css_class]
            );
            for (var j = 0; j &lt; element.length; ++j) {
              element[j].textContent = vars[css_selector_classes[css_class]];
            }
          }
        }
      }
    &lt;/script&gt;
  &lt;/head&gt;
  &lt;body style=&quot;border: 0; margin: 0&quot; onload=&quot;subst()&quot;&gt;
    &lt;table style=&quot;border-top: 1px solid black; width: 100%&quot;&gt;
      &lt;tr&gt;
        &lt;td class=&quot;section&quot;&gt;&lt;/td&gt;
        &lt;td class=&quot;hello&quot;&gt;&lt;/td&gt;
        &lt;td class=&quot;my_id&quot;&gt;&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;
          Page &lt;span class=&quot;page&quot;&gt;&lt;/span&gt; of &lt;span class=&quot;topage&quot;&gt;&lt;/span&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
    &lt;/table&gt;
  &lt;/body&gt;
&lt;/html&gt;

If the above approach does not work or you're interested in finding out what's wrong with the temp file approach, we can do these things to narrow down the issue first:

  1. run strace -f /path/to/the/app &gt; log.txt to trace system calls and signals. The -f option makes it trace child processes (wkhtmltopdf is run in a child process).

  2. the log from the last step should contains the options passed to the wkhtmltopdf command. Run the wkhtmltopdf command with the same options directly, see if it makes any difference.

    1. if the footer is rendered correctly, then there is something wrong in the Go code. It could be the temp file is not ready for use yet.
    2. if it does not make any difference, since the same command works in your local machine, this could be an issue related to the environment. Maybe the version of wkhtmltopdf on the server is buggy; maybe the server does not have the required fonts.

Please provide log.txt and the result of running wkhtmltopdf directly, so that we can decide what to do next.

huangapple
  • 本文由 发表于 2023年6月6日 19:47:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/76414325.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定