[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi-commits] [lmi] master c56a67f 09/15: Implement and use simple HTML
From: |
Greg Chicares |
Subject: |
[lmi-commits] [lmi] master c56a67f 09/15: Implement and use simple HTML output pagination |
Date: |
Fri, 27 Jul 2018 17:23:16 -0400 (EDT) |
branch: master
commit c56a67fa8f2ced6feae7a962f3ec4580df6e5ddd
Author: Vadim Zeitlin <address@hidden>
Commit: Gregory W. Chicares <address@hidden>
Implement and use simple HTML output pagination
Use wxHtmlDCRenderer to paginate HTML output (note that this requires
wxWidgets commit b9b6ccb80461fb694130e046e9f23d3919ff3ecb or later) and
provide pdf_writer_wx::output_html() overload allowing to output only a
part of the template contents.
Use the new paginate_html() and this overload in ledger PDF generation
code to allow "standard" logical pages to occupy more than one physical
page in the output.
---
ledger_pdf_generator_wx.cpp | 65 +++++++++++++++++++------
pdf_writer_wx.cpp | 115 +++++++++++++++++++++++++++++++++++++++-----
pdf_writer_wx.hpp | 18 +++++++
3 files changed, 170 insertions(+), 28 deletions(-)
diff --git a/ledger_pdf_generator_wx.cpp b/ledger_pdf_generator_wx.cpp
index a38c878..d72f03b 100644
--- a/ledger_pdf_generator_wx.cpp
+++ b/ledger_pdf_generator_wx.cpp
@@ -1166,14 +1166,7 @@ class numbered_page : public page_with_footer
(Ledger const& ledger
,pdf_writer_wx& writer
,html_interpolator const& interpolate_html
- ) const
- {
- stifle_warning_for_unused_value(ledger);
- stifle_warning_for_unused_value(writer);
- stifle_warning_for_unused_value(interpolate_html);
-
- return 0;
- }
+ ) = 0;
std::string get_page_number() const override
{
@@ -1209,18 +1202,58 @@ class standard_page : public numbered_page
,html_interpolator const& interpolate_html
) override
{
- numbered_page::render(ledger, writer, interpolate_html);
+ // Page HTML must have been already set by get_extra_pages_needed().
+ LMI_ASSERT(!page_html_.empty());
- writer.output_html
- (writer.get_horz_margin()
- ,writer.get_vert_margin()
- ,writer.get_page_width()
- ,interpolate_html.expand_template(page_template_name_)
- );
+ int last_page_break = 0;
+ for(auto const& page_break : page_break_positions_)
+ {
+ if(last_page_break != 0)
+ {
+ next_page(writer);
+ }
+
+ numbered_page::render(ledger, writer, interpolate_html);
+
+ writer.output_html
+ (writer.get_horz_margin()
+ ,writer.get_vert_margin()
+ ,writer.get_page_width()
+ ,page_html_
+ ,last_page_break
+ ,page_break
+ );
+
+ last_page_break = page_break;
+ }
}
private:
+ int get_extra_pages_needed
+ (Ledger const& /* ledger */
+ ,pdf_writer_wx& writer
+ ,html_interpolator const& interpolate_html
+ ) override
+ {
+ page_html_ = wxString::FromUTF8
+ (interpolate_html.expand_template(page_template_name_).as_html()
+ );
+
+ page_break_positions_ = writer.paginate_html
+ (writer.get_page_width()
+ ,get_footer_top() - writer.get_vert_margin()
+ ,page_html_
+ );
+
+ // The cast is safe, we're never going to have more than INT_MAX
+ // pages and if we, somehow, do, the caller checks that this function
+ // returns a positive value.
+ return static_cast<int>(page_break_positions_.size()) - 1;
+ }
+
char const* const page_template_name_;
+ wxString page_html_;
+ std::vector<int> page_break_positions_;
};
// Helper classes used to show the numeric summary table. The approach used
@@ -1661,7 +1694,7 @@ class page_with_tabular_report
(Ledger const& ledger
,pdf_writer_wx& writer
,html_interpolator const& interpolate_html
- ) const override
+ ) override
{
wx_table_generator table_gen{create_table_generator(ledger, writer)};
diff --git a/pdf_writer_wx.cpp b/pdf_writer_wx.cpp
index f394467..5e6903d 100644
--- a/pdf_writer_wx.cpp
+++ b/pdf_writer_wx.cpp
@@ -31,9 +31,9 @@
#include <wx/filesys.h>
#include <wx/html/htmlcell.h>
+#include <wx/html/htmprint.h>
#include <exception> // uncaught_exceptions()
-#include <limits>
namespace
{
@@ -55,6 +55,15 @@ wxPrintData make_print_data
return print_data;
}
+// Ensure that we call SetFonts() with consistent parameters both on
+// wxHtmlWinParser and wxHtmlDCRenderer by using the same helper function to do
+// it for both of them.
+template <typename T>
+void DoSetFonts(T& html_object, pdf_writer_wx::html_font_sizes const&
font_sizes)
+{
+ html_object.SetFonts("Helvetica", "Courier", font_sizes.data());
+}
+
} // Unnamed namespace.
pdf_writer_wx::pdf_writer_wx
@@ -65,6 +74,7 @@ pdf_writer_wx::pdf_writer_wx
:print_data_ {make_print_data(output_filename, orientation)}
,pdf_dc_ {print_data_}
,html_parser_ {nullptr}
+ ,html_font_sizes_ {font_sizes}
,total_page_size_ {pdf_dc_.GetSize()}
{
// Ensure that the output is independent of the current display resolution:
@@ -86,7 +96,7 @@ pdf_writer_wx::pdf_writer_wx
// Use a standard PDF Helvetica font (without embedding any custom fonts in
// the generated file, the only other realistic choice is Times New Roman).
pdf_dc_.SetFont
- (wxFontInfo(font_sizes.at(2))
+ (wxFontInfo(html_font_sizes_.at(2))
.Family(wxFONTFAMILY_SWISS)
.FaceName("Helvetica")
);
@@ -172,15 +182,64 @@ void pdf_writer_wx::output_image
*pos_y += y;
}
+/// Compute vertical page break positions needed when outputting the given HTML
+/// contents into pages of the given height.
+///
+/// If the entire contents fits on a single page, the returned vector has a
+/// single element equal to page_height. More generally, the size of the
+/// returned vector is the number of pages needed for output.
+///
+/// Note that page_height is passed as parameter here because it can be smaller
+/// than the value returned by our get_total_height() if headers or footers are
+/// used. And page_width is used for consistency, even if currently it's always
+/// the same as get_page_width().
+
+std::vector<int> pdf_writer_wx::paginate_html
+ (int page_width
+ ,int page_height
+ ,wxString const& html_str
+ )
+{
+ wxHtmlDCRenderer renderer;
+ renderer.SetDC(&dc());
+ renderer.SetSize(page_width, page_height);
+ DoSetFonts(renderer, html_font_sizes_);
+
+ // Note that we parse HTML twice: here and then again in output_html(),
+ // which is inefficient. Unfortunately currently there is no way to share
+ // neither the parser, nor the result of calling Parse() between
+ // wxHtmlDCRenderer and output_html().
+ renderer.SetHtmlText(html_str);
+
+ std::vector<int> page_breaks;
+ for(int pos = 0;;)
+ {
+ pos = renderer.FindNextPageBreak(pos);
+ if(pos == wxNOT_FOUND)
+ break;
+ page_breaks.push_back(pos);
+ }
+
+ return page_breaks;
+}
+
/// Render, or just pretend rendering in order to measure it, the given HTML
/// contents at the specified position wrapping it at the given width.
+///
/// Return the height of the output (using this width).
+///
+/// Note the difference between "x" and "y" parameters, which specify the
+/// position in the output DC, and "from" and "to" ones which contain the
+/// starting and ending coordinates in the virtual view of the entire HTML
+/// document: the HTML element at the position "from" will appear at "y".
int pdf_writer_wx::output_html
(int x
,int y
,int width
- ,html::text&& html
+ ,wxString const& html_str
+ ,int from
+ ,int to
,oenum_render_or_only_measure output_mode
)
{
@@ -190,7 +249,6 @@ int pdf_writer_wx::output_html
// font which is changed by rendering the HTML contents.
wxDCFontChanger preserve_font(pdf_dc_, wxFont());
- auto const html_str = wxString::FromUTF8(std::move(html).as_html());
std::unique_ptr<wxHtmlContainerCell> const cell
(static_cast<wxHtmlContainerCell*>(html_parser_.Parse(html_str))
);
@@ -201,15 +259,17 @@ int pdf_writer_wx::output_html
{
case oe_render:
{
+ // Even though wxHtmlCell::Draw() omits drawing of the cells
+ // entirely outside of the visible vertical range, we still need to
+ // clip rendering to this range explicitly as a partially visible
+ // cell could extend beyond the "to" boundary if we didn't do it.
+ wxDCClipper clip(pdf_dc_, x, y, width, to - from);
+
wxHtmlRenderingInfo rendering_info;
- cell->Draw
- (pdf_dc_
- ,x
- ,y
- ,0
- ,std::numeric_limits<int>::max()
- ,rendering_info
- );
+
+ // "Scroll" the cell upwards by "from" by subtracting it from the
+ // vertical position.
+ cell->Draw(pdf_dc_, x, y - from, y, y + to - from, rendering_info);
}
break;
case oe_only_measure:
@@ -220,6 +280,37 @@ int pdf_writer_wx::output_html
return cell->GetHeight();
}
+/// Convenient overload when rendering, or measuring, HTML text known to fit on
+/// a single page.
+///
+/// In this case "from" and "to" parameters are not needed and we can take
+/// html::text directly as it won't be used any more.
+
+int pdf_writer_wx::output_html
+ (int x
+ ,int y
+ ,int width
+ ,html::text&& html
+ ,oenum_render_or_only_measure output_mode
+ )
+{
+ int const height = output_html
+ (x
+ ,y
+ ,width
+ ,wxString::FromUTF8(std::move(html).as_html())
+ ,0
+ ,get_total_height()
+ ,output_mode
+ );
+
+ // Should have fit on this page, otherwise this is not the right overload
+ // to use -- call paginate_html() and the generic overload above instead.
+ LMI_ASSERT(height <= get_total_height() - y);
+
+ return height;
+}
+
int pdf_writer_wx::get_horz_margin() const
{
return horz_margin;
diff --git a/pdf_writer_wx.hpp b/pdf_writer_wx.hpp
index d9ecf00..613d6b2 100644
--- a/pdf_writer_wx.hpp
+++ b/pdf_writer_wx.hpp
@@ -61,6 +61,22 @@ class pdf_writer_wx
// Wherever possible, use the following high-level functions
// instead of working at a lower level with the dc() accessor.
+ std::vector<int> paginate_html
+ (int page_width
+ ,int page_height
+ ,wxString const& html_str
+ );
+
+ int output_html
+ (int x
+ ,int y
+ ,int width
+ ,wxString const& html_str
+ ,int from
+ ,int to
+ ,oenum_render_or_only_measure output_mode = oe_render
+ );
+
int output_html
(int x
,int y
@@ -102,6 +118,8 @@ class pdf_writer_wx
std::unique_ptr<wxFileSystem> html_vfs_;
wxHtmlWinParser html_parser_;
+ html_font_sizes const html_font_sizes_;
+
wxSize const total_page_size_;
bool save_has_been_called_{false};
- [lmi-commits] [lmi] master 005b3eb 06/15: Replace pdf_writer_wx::get_page_height() with get_total_height(), (continued)
- [lmi-commits] [lmi] master 005b3eb 06/15: Replace pdf_writer_wx::get_page_height() with get_total_height(), Greg Chicares, 2018/07/27
- [lmi-commits] [lmi] master 28e3c42 11/15: Remove workarounds for wx 3.1.1 warnings not relevant any more, Greg Chicares, 2018/07/27
- [lmi-commits] [lmi] master b6b6827 03/15: Do call EndPage() in PDF generation code, Greg Chicares, 2018/07/27
- [lmi-commits] [lmi] master a86d90a 02/15: Use more recent wxPdfDocument fixing the text origin bug, Greg Chicares, 2018/07/27
- [lmi-commits] [lmi] master 554eb23 13/15: Forbid page breaks inside paragraphs in the illustrations, Greg Chicares, 2018/07/27
- [lmi-commits] [lmi] master 271db22 14/15: Fix over eager assert in pdf_writer_wx::output_html(), Greg Chicares, 2018/07/27
- [lmi-commits] [lmi] master 12d84c9 15/15: Fix regression in font sizes used for the group quotes PDF, Greg Chicares, 2018/07/27
- [lmi-commits] [lmi] master 2bd38fa 12/15: Use standard <img> tag instead of <scaled_image> in templates, Greg Chicares, 2018/07/27
- [lmi-commits] [lmi] master 4cfcfe4 07/15: Refactor: remove render_page_template() helper function, Greg Chicares, 2018/07/27
- [lmi-commits] [lmi] master e821acd 05/15: Require specifying font sizes when using pdf_writer_wx, Greg Chicares, 2018/07/27
- [lmi-commits] [lmi] master c56a67f 09/15: Implement and use simple HTML output pagination,
Greg Chicares <=
- [lmi-commits] [lmi] master f03ff64 10/15: Merge first two notes pages of regular individual illustrations, Greg Chicares, 2018/07/27