lmi-commits
[Top][All Lists]
Advanced

[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};



reply via email to

[Prev in Thread] Current Thread [Next in Thread]