[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi-commits] [lmi] master 408ba18 156/156: Merge branch 'gwc-no-xslfo'
From: |
Greg Chicares |
Subject: |
[lmi-commits] [lmi] master 408ba18 156/156: Merge branch 'gwc-no-xslfo' [387] |
Date: |
Tue, 30 Jan 2018 17:22:36 -0500 (EST) |
branch: master
commit 408ba18d6d140fb8d6bda522e5a4cbd19e6c2be6
Merge: 70fb246 47bddbf
Author: Gregory W. Chicares <address@hidden>
Commit: Gregory W. Chicares <address@hidden>
Merge branch 'gwc-no-xslfo' [387]
---
Makefile.am | 21 +-
contract_numbers.mst | 46 +
cover.mst | 148 ++
dollar_units.mst | 32 +
emit_ledger.cpp | 2 +
group_quote_pdf_gen_wx.cpp | 556 ++----
html.cpp | 128 ++
html.hpp | 329 ++++
imprimatur.mst | 40 +
interpolate_string.cpp | 296 +++
interpolate_string.hpp | 69 +
interpolate_string_test.cpp | 233 +++
ledger.hpp | 5 +
ledger_evaluator.cpp | 864 ++++++++
ledger_evaluator.hpp | 59 +
ledger_pdf.cpp | 56 +
ledger_xsl.hpp => ledger_pdf.hpp | 13 +-
ledger_xsl.hpp => ledger_pdf_generator.cpp | 34 +-
ledger_pdf_generator.hpp | 63 +
ledger_pdf_generator_wx.cpp | 2940 ++++++++++++++++++++++++++++
ledger_xsl.cpp | 4 +-
ledger_xsl.hpp | 2 +-
main_wx.cpp | 1 +
main_wx_test.cpp | 1 +
nasd_assumption_detail.mst | 29 +
nasd_basic.mst | 35 +
nasd_column_headings.mst | 96 +
nasd_footer_lower.mst | 40 +
nasd_footer_upper.mst | 34 +
nasd_header.mst | 29 +
nasd_header_lower.mst | 84 +
nasd_header_upper.mst | 39 +
nasd_notes1.mst | 137 ++
nasd_notes2.mst | 244 +++
nasd_supp.mst | 29 +
nasd_supp_report.mst | 28 +
objects.make | 22 +-
ledger_xsl.hpp => output_mode.hpp | 27 +-
pdf_writer_wx.cpp | 249 +++
pdf_writer_wx.hpp | 100 +
reg_column_headings.mst | 202 ++
reg_d_group_basic.mst | 36 +
reg_d_group_column_headings.mst | 56 +
reg_d_group_footer_lower.mst | 40 +
reg_d_group_footer_upper.mst | 33 +
reg_d_group_header.mst | 29 +
reg_d_group_header_lower.mst | 77 +
reg_d_group_header_upper.mst | 39 +
reg_d_group_narr_summary.mst | 123 ++
reg_d_group_narr_summary2.mst | 161 ++
reg_d_group_supp_report.mst | 28 +
reg_d_indiv_cover_page.mst | 130 ++
reg_d_indiv_curr.mst | 34 +
reg_d_indiv_curr_irr.mst | 34 +
reg_d_indiv_footer_lower.mst | 40 +
reg_d_indiv_footer_upper.mst | 28 +
reg_d_indiv_guar_irr.mst | 34 +
reg_d_indiv_header.mst | 90 +
reg_d_indiv_notes1.mst | 135 ++
reg_d_indiv_notes2.mst | 56 +
reg_d_indiv_notes3.mst | 186 ++
reg_d_indiv_supp_report.mst | 32 +
reg_footer.mst | 40 +
reg_footer_disclaimer.mst | 32 +
reg_header.mst | 131 ++
reg_narr_summary.mst | 203 ++
reg_narr_summary2.mst | 258 +++
reg_numeric_summary.mst | 203 ++
reg_supp_report.mst | 28 +
reg_tabular_details.mst | 29 +
reg_tabular_details2.mst | 27 +
test_coding_rules.cpp | 20 +-
workhorse.make | 2 +-
wx_table_generator.cpp | 168 +-
wx_table_generator.hpp | 59 +-
75 files changed, 9536 insertions(+), 451 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index aad985a..060d5ff 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -110,6 +110,7 @@ TESTS = \
test_ieee754 \
test_input_seq \
test_input \
+ test_interpolate_string \
test_irc7702a \
test_istream_to_string \
test_loads \
@@ -172,6 +173,7 @@ libskeleton_la_SOURCES = \
illustration_document.cpp \
illustration_view.cpp \
input_sequence_entry.cpp \
+ ledger_pdf_generator_wx.cpp \
main_common.cpp \
mec_document.cpp \
mec_view.cpp \
@@ -180,6 +182,7 @@ libskeleton_la_SOURCES = \
multidimgrid_tools.cpp \
mvc_controller.cpp \
mvc_view.cpp \
+ pdf_writer_wx.cpp \
policy_document.cpp \
policy_view.cpp \
preferences_view.cpp \
@@ -316,6 +319,7 @@ liblmi_common_sources = \
global_settings.cpp \
group_values.cpp \
group_quote_pdf_gen.cpp \
+ html.cpp \
illustrator.cpp \
input.cpp \
input_harmonization.cpp \
@@ -325,13 +329,15 @@ liblmi_common_sources = \
input_sequence_parser.cpp \
input_xml_io.cpp \
interest_rates.cpp \
+ interpolate_string.cpp \
ledger.cpp \
ledger_base.cpp \
+ ledger_evaluator.cpp \
ledger_invariant.cpp \
+ ledger_pdf.cpp \
+ ledger_pdf_generator.cpp \
ledger_text_formats.cpp \
ledger_variant.cpp \
- ledger_xml_io.cpp \
- ledger_xsl.cpp \
ledgervalues.cpp \
license.cpp \
loads.cpp \
@@ -775,6 +781,11 @@ test_input_LDADD = \
$(BOOST_LIBS) \
$(XMLWRAPP_LIBS)
+test_interpolate_string_SOURCES = \
+ $(common_test_objects) \
+ interpolate_string.cpp \
+ interpolate_string_test.cpp
+
test_irc7702a_SOURCES = \
$(common_test_objects) \
ihs_irc7702a.cpp \
@@ -1150,6 +1161,7 @@ noinst_HEADERS = \
group_quote_pdf_gen.hpp \
group_values.hpp \
handle_exceptions.hpp \
+ html.hpp \
icon_monger.hpp \
ieee754.hpp \
ihs_irc7702.hpp \
@@ -1166,13 +1178,16 @@ noinst_HEADERS = \
input_sequence_interval.hpp \
input_sequence_parser.hpp \
interest_rates.hpp \
+ interpolate_string.hpp \
istream_to_string.hpp \
ledger.hpp \
ledger_base.hpp \
+ ledger_evaluator.hpp \
ledger_invariant.hpp \
+ ledger_pdf.hpp \
+ ledger_pdf_generator.hpp \
ledger_text_formats.hpp \
ledger_variant.hpp \
- ledger_xsl.hpp \
ledgervalues.hpp \
license.hpp \
lmi.hpp \
diff --git a/contract_numbers.mst b/contract_numbers.mst
new file mode 100644
index 0000000..b4142bc
--- /dev/null
+++ b/contract_numbers.mst
@@ -0,0 +1,46 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{#HasMasterContract}}
+ Master contract:
+ {{#HasPolicyNumber}}
+ {{^Composite}}
+ {{MasterContractNumberAbbrev15}}
+ {{/Composite}}
+ {{#Composite}}
+ {{MasterContractNumberAbbrev30}}
+ {{/Composite}}
+ {{/HasPolicyNumber}}
+ {{^HasPolicyNumber}}
+ {{MasterContractNumberAbbrev30}}
+ {{/HasPolicyNumber}}
+{{/HasMasterContract}}
+{{#HasPolicyNumber}}
+ {{^Composite}}
+ Contract number:
+ {{#HasMasterContract}}
+ {{ContractNumberAbbrev15}}
+ {{/HasMasterContract}}
+ {{^HasMasterContract}}
+ {{ContractNumberAbbrev30}}
+ {{/HasMasterContract}}
+ {{/Composite}}
+{{/HasPolicyNumber}}
diff --git a/cover.mst b/cover.mst
new file mode 100644
index 0000000..f99f76d
--- /dev/null
+++ b/cover.mst
@@ -0,0 +1,148 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<table width="100%">
+ <!--
+ Rows such as this are used as separators between visible table elements,
+ this is unfortunately necessary because wxHTML doesn't support
+ margin/padding on table elements.
+ -->
+ <tr><td> </td></tr>
+ <tr><td> </td></tr>
+ <tr><td> </td></tr>
+ <tr><td> </td></tr>
+ <tr><td> </td></tr>
+ <tr>
+ <td align="center">
+ <font size="+4">
+ <b>{{PolicyMktgName}}</b>
+ </font>
+ </td>
+ </tr>
+ <tr>
+ </tr>
+ <tr>
+ <td align="center">
+ <font size="+4">
+ <b>
+ {{#IsInforce}}
+ In Force Life Insurance Illustration
+ {{/IsInforce}}
+ {{^IsInforce}}
+ Life Insurance Illustration
+ {{/IsInforce}}
+ </b>
+ </font>
+ </td>
+ </tr>
+ <tr><td> </td></tr>
+ <tr><td> </td></tr>
+ <tr><td> </td></tr>
+ <tr><td> </td></tr>
+ <tr>
+ <td align="center">
+ <font size="+2">
+ <b>Prepared for:</b>
+ </font>
+ </td>
+ </tr>
+ <tr>
+ <td align="center">
+ <font size="+2">
+ {{#Composite}}
+ {{CorpName}}
+ {{/Composite}}
+ {{^Composite}}
+ {{Insured1}}
+ {{/Composite}}
+ </font>
+ </td>
+ </tr>
+ <tr><td> </td></tr>
+ <tr><td> </td></tr>
+ <tr>
+ <td align="center">
+ <font size="+2">
+ <b>Presented by:</b>
+ </font>
+ </td>
+ </tr>
+ <tr>
+ <td align="center">
+ <font size="+2">
+ {{ProducerName}}
+ </font>
+ </td>
+ </tr>
+ <tr>
+ <td align="center">
+ <font size="+2">
+ {{ProducerStreet}}
+ </font>
+ </td>
+ </tr>
+ <tr>
+ <td align="center">
+ <font size="+2">
+ {{ProducerCity}}
+ </font>
+ </td>
+ </tr>
+ <tr><td> </td></tr>
+ <tr><td> </td></tr>
+ <tr>
+ <td align="center">
+ <font size="+2">
+ {{date_prepared}}
+ </font>
+ </td>
+ </tr>
+ <tr><td> </td></tr>
+ <tr><td> </td></tr>
+ <tr><td> </td></tr>
+ <tr><td> </td></tr>
+</table>
+
+<!--
+This is ugly and error-prone, but there is no way to put the paragraph below at
+the bottom of the page currently, so we just insert enough white space to push
+it roughly where we want it to appear.
+-->
+<br></br>
+<br></br>
+<br></br>
+<br></br>
+<br></br>
+<br></br>
+
+<p align="center"><scaled_image inv_factor="0.6"
src="company_logo.png"></scaled_image></p>
+
+<br></br>
+<br></br>
+<br></br>
+
+<font size="-1">
+
+<p align="center">{{MarketingNameFootnote}}</p>
+
+<p>{{StateMarketingImprimatur}}</p>
+
+</font>
diff --git a/dollar_units.mst b/dollar_units.mst
new file mode 100644
index 0000000..8f888d9
--- /dev/null
+++ b/dollar_units.mst
@@ -0,0 +1,32 @@
+{{!
+ Define a fragment common to several pages.
+
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<p align="center">
+<font size="-1">
+Values shown are in
+{{#HasScaleUnit}}
+{{ScaleUnit}}s of
+{{/HasScaleUnit}}
+dollars
+</font>
+</p>
diff --git a/emit_ledger.cpp b/emit_ledger.cpp
index 82d3249..2dddb93 100644
--- a/emit_ledger.cpp
+++ b/emit_ledger.cpp
@@ -30,7 +30,9 @@
#include "file_command.hpp"
#include "group_quote_pdf_gen.hpp"
#include "ledger.hpp"
+#include "ledger_pdf.hpp"
#include "ledger_text_formats.hpp"
+// PDF !! Expunge the next line:
#include "ledger_xsl.hpp"
#include "miscellany.hpp" // ios_out_trunc_binary()
#include "path_utility.hpp" // unique_filepath()
diff --git a/group_quote_pdf_gen_wx.cpp b/group_quote_pdf_gen_wx.cpp
index 2221777..8c5bed5 100644
--- a/group_quote_pdf_gen_wx.cpp
+++ b/group_quote_pdf_gen_wx.cpp
@@ -28,6 +28,7 @@
#include "calendar_date.hpp" // jdn_t()
#include "data_directory.hpp" // AddDataDir()
#include "force_linking.hpp"
+#include "html.hpp"
#include "ledger.hpp"
#include "ledger_invariant.hpp"
#include "ledger_text_formats.hpp" // ledger_format()
@@ -36,6 +37,7 @@
#include "miscellany.hpp" // split_into_lines()
#include "oecumenic_enumerations.hpp" // oenum_format_style
#include "path_utility.hpp" // fs::path inserter
+#include "pdf_writer_wx.hpp"
#include "version.hpp"
#include "wx_table_generator.hpp"
#include "wx_utility.hpp" // ConvertDateToWx()
@@ -44,10 +46,7 @@
#include <boost/filesystem/path.hpp>
#include <wx/datetime.h>
-#include <wx/html/htmlcell.h>
-#include <wx/html/winpars.h>
#include <wx/image.h>
-#include <wx/pdfdc.h>
#include <cstring> // strstr()
#include <limits>
@@ -61,119 +60,28 @@ LMI_FORCE_LINKING_IN_SITU(group_quote_pdf_generator_wx)
namespace
{
-enum enum_output_mode
- {e_output_normal
- ,e_output_measure_only
- };
-
-/// Escape special XML characters in the given string, ensuring that it appears
-/// correctly inside HTML element contents. Notice that we don't need to escape
-/// quotes here as we never use the result of this function inside an HTML
-/// attribute, only inside HTML elements.
-
-wxString escape_for_html_elem(std::string const& s)
-{
- wxString const u = wxString::FromUTF8(s.c_str());
-
- wxString z;
- z.reserve(u.length());
- for(auto const& i : u)
- {
- switch(i.GetValue())
- {
- case '<': z += "<" ; break;
- case '>': z += ">" ; break;
- case '&': z += "&"; break;
- default : z += i ;
- }
- }
- return z;
-}
-
-/// Namespace for helpers used for HTML generation.
-
-namespace html
-{
-
-/// Namespace for the support HTML tags.
-///
-/// Tags are only used as template arguments, so they don't need to be defined,
-/// just declared -- and tag_info below specialized for them.
+/// Transform 's' -> '<br><br>s', but return empty string unchanged.
-namespace tag
+html::text brbr(std::string const& s)
{
+ using namespace html;
-struct b;
-struct br;
-
-} // namespace tag
-
-template<typename T>
-struct tag_info;
-
-template<>
-struct tag_info<tag::b>
-{
- static char const* get_name() { return "b"; }
- static bool has_end() { return true; }
-};
-
-template<>
-struct tag_info<tag::br>
-{
- static char const* get_name() { return "br"; }
- static bool has_end() { return false; }
-};
-
-} // namespace html
-
-/// Wrap the given text in an HTML tag if it is not empty, otherwise just
-/// return an empty string.
-///
-/// For the tags without matching closing tags, such as e.g. "<br>", wrapping
-/// the text means just prepending the tag to it. This is still done only if
-/// the text is not empty.
-
-template<typename T>
-wxString wrap_if_not_empty(wxString const& html)
-{
- wxString result;
- if(!html.empty())
- {
- result << '<' << html::tag_info<T>::get_name() << '>' << html;
- if(html::tag_info<T>::has_end())
- {
- result << "</" << html::tag_info<T>::get_name() << '>';
- }
- }
-
- return result;
+ return s.empty()
+ ? text()
+ : tag::br + tag::br + text::from(s)
+ ;
}
-/// Transform 'html' -> '<br><br>html', but return empty string unchanged.
+/// Transform 's' -> '<br><br><b>s</b>', but return empty string unchanged.
-wxString brbr(std::string const& html)
+html::text brbrb(std::string const& s)
{
- return
- wrap_if_not_empty<html::tag::br>
- (wrap_if_not_empty<html::tag::br>
- (escape_for_html_elem(html)
- )
- );
-}
-
-/// Transform 'html' -> '<br><br><b>html</b>', but return empty string
unchanged.
+ using namespace html;
-wxString brbrb(std::string const& html)
-{
- return
- wrap_if_not_empty<html::tag::br>
- (wrap_if_not_empty<html::tag::br>
- (wrap_if_not_empty<html::tag::b>
- (escape_for_html_elem(html)
- )
- )
- );
+ return s.empty()
+ ? text()
+ : tag::br + tag::br + tag::b(text::from(s))
+ ;
}
/// Generate HTML representation of a field name and value in an HTML table.
@@ -181,18 +89,27 @@ wxString brbrb(std::string const& html)
/// The HTML fragment generated by this function contains two <td> tags with
/// the given contents.
-wxString name_value_as_html_table_data
+html::text name_value_as_html_table_data
(std::string const& name
,std::string const& value
)
{
- return wxString::Format
- ("<td nowrap align=\"right\"><b>%s%s </b></td>"
- "<td>%s </td>"
- ,escape_for_html_elem(name)
- ,(value.empty() ? "" : ":")
- ,escape_for_html_elem(value)
- );
+ using namespace html;
+
+ auto const nbsp2 = text::nbsp() + text::nbsp();
+
+ return
+ tag::td[attr::nowrap][attr::align("right")]
+ (tag::b
+ (text::from(name))
+ (text::from(value.empty() ? "" : ":"))
+ (nbsp2)
+ )
+ +
+ tag::td
+ (text::from(value))
+ (nbsp2 + nbsp2)
+ ;
}
/// Simple description of a custom field, consisting of a non-empty name and a
@@ -301,100 +218,6 @@ wxImage load_image(char const* file)
return image;
}
-/// Output an image at the given scale into the PDF.
-///
-/// The scale specifies how many times the image should be shrunk:
-/// scale > 1 makes the image smaller, while scale < 1 makes it larger.
-///
-/// Updates pos_y by increasing it by the height of the specified
-/// image at the given scale.
-
-void output_image
- (wxPdfDC& pdf_dc
- ,wxImage const& image
- ,char const* image_name
- ,double scale
- ,int x
- ,int* pos_y
- ,enum_output_mode output_mode = e_output_normal
- )
-{
- int const y = wxRound(image.GetHeight() / scale);
-
- switch(output_mode)
- {
- case e_output_normal:
- {
- // Use wxPdfDocument API directly as wxDC doesn't provide a way to
- // set the image scale at PDF level and also because passing via
- // wxDC wastefully converts wxImage to wxBitmap only to convert it
- // back to wxImage when embedding it into the PDF.
- wxPdfDocument* const pdf_doc = pdf_dc.GetPdfDocument();
- LMI_ASSERT(pdf_doc);
-
- pdf_doc->SetImageScale(scale);
- pdf_doc->Image(image_name, image, x, *pos_y);
- pdf_doc->SetImageScale(1);
- }
- break;
- case e_output_measure_only:
- // Do nothing.
- break;
- default:
- {
- alarum() << "Case " << output_mode << " not found." << LMI_FLUSH;
- }
- }
-
- *pos_y += y;
-}
-
-/// 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).
-
-int output_html
- (wxHtmlWinParser& html_parser
- ,int x
- ,int y
- ,int width
- ,wxString const& html
- ,enum_output_mode output_mode = e_output_normal
- )
-{
- std::unique_ptr<wxHtmlContainerCell> const cell
- (static_cast<wxHtmlContainerCell*>(html_parser.Parse(html))
- );
- LMI_ASSERT(cell);
-
- cell->Layout(width);
- switch(output_mode)
- {
- case e_output_normal:
- {
- wxHtmlRenderingInfo rendering_info;
- cell->Draw
- (*html_parser.GetDC()
- ,x
- ,y
- ,0
- ,std::numeric_limits<int>::max()
- ,rendering_info
- );
- }
- break;
- case e_output_measure_only:
- // Do nothing.
- break;
- default:
- {
- alarum() << "Case " << output_mode << " not found." << LMI_FLUSH;
- }
- }
-
- return cell->GetHeight();
-}
-
enum enum_group_quote_columns
{e_col_number
,e_col_name
@@ -450,52 +273,46 @@ class group_quote_pdf_generator_wx
void save(std::string const& output_filename) override;
private:
- // These margins are arbitrary and can be changed to conform to subjective
+ // This value is arbitrary and can be changed to conform to subjective
// preferences.
- static int const horz_margin = 24;
- static int const vert_margin = 36;
- static int const vert_skip = 12;
+ static int const vert_skip = 12;
// Ctor is private as it is only used by do_create().
group_quote_pdf_generator_wx() = default;
- // Generate the PDF once we have all the data.
- void do_generate_pdf(wxPdfDC& pdf_dc);
-
// Compute the number of pages needed by the table rows in the output given
// the space remaining on the first page, the heights of the header, one
// table row and the footer and the last row position.
// Remaining space contains the space on the first page on input and is
// updated with the space remaining on the last page on output.
int compute_pages_for_table_rows
- (int* remaining_space
+ (pdf_writer_wx& pdf_writer
+ ,int* remaining_space
,int header_height
,int row_height
,int last_row_y
);
void output_page_number_and_version
- (wxPdfDC& pdf_dc
+ (pdf_writer_wx& pdf_writer
,int total_pages
,int current_page
);
void output_image_header
- (wxPdfDC& pdf_dc
+ (pdf_writer_wx& pdf_writer
,int* pos_y
);
void output_document_header
- (wxPdfDC& pdf_dc
- ,wxHtmlWinParser& html_parser
+ (pdf_writer_wx& pdf_writer
,int* pos_y
);
void output_aggregate_values
- (wxPdfDC& pdf_dc
+ (pdf_writer_wx& pdf_writer
,wx_table_generator& table_gen
,int* pos_y
);
void output_footer
- (wxPdfDC& pdf_dc
- ,wxHtmlWinParser& html_parser
+ (pdf_writer_wx& pdf_writer
,int* pos_y
,enum_output_mode output_mode = e_output_normal
);
@@ -514,7 +331,7 @@ class group_quote_pdf_generator_wx
std::string premium_mode_;
std::string contract_state_;
std::string effective_date_;
- wxString footer_html_;
+ html::text footer_html_;
// Dynamically-determined fields.
std::string elected_riders_;
@@ -561,24 +378,6 @@ class group_quote_pdf_generator_wx
};
totals_data totals_;
- struct page_metrics
- {
- page_metrics()
- :width_(0)
- {
- }
-
- void initialize(wxDC const& dc)
- {
- total_size_ = dc.GetSize();
- width_ = total_size_.x - 2 * horz_margin;
- }
-
- wxSize total_size_;
- int width_;
- };
- page_metrics page_;
-
int row_num_ {0};
int individual_selection_ {99};
};
@@ -858,59 +657,20 @@ void group_quote_pdf_generator_wx::add_ledger(Ledger
const& ledger)
void group_quote_pdf_generator_wx::save(std::string const& output_filename)
{
- // Create a wxPrintData object just to describe the paper to use.
- wxPrintData print_data;
- print_data.SetOrientation(wxLANDSCAPE);
- print_data.SetPaperId(wxPAPER_LETTER);
- print_data.SetFilename(output_filename);
-
- wxPdfDC pdf_dc(print_data);
- page_.initialize(pdf_dc);
- do_generate_pdf(pdf_dc);
- pdf_dc.EndDoc();
-}
-
-void group_quote_pdf_generator_wx::do_generate_pdf(wxPdfDC& pdf_dc)
-{
- // Ensure that the output is independent of the current display resolution:
- // it seems that this is only the case with the PDF map mode and wxDC mode
- // different from wxMM_TEXT.
- pdf_dc.SetMapModeStyle(wxPDF_MAPMODESTYLE_PDF);
-
- // For simplicity, use points for everything: font sizers are expressed in
- // them anyhow, so it's convenient to use them for everything else too.
- pdf_dc.SetMapMode(wxMM_POINTS);
-
- pdf_dc.StartDoc(wxString()); // Argument is not used.
- pdf_dc.StartPage();
-
- // 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(8).Family(wxFONTFAMILY_SWISS).FaceName("Helvetica")
- );
-
- // Create an HTML parser to allow easily adding HTML contents to the
output.
- wxHtmlWinParser html_parser(nullptr);
- html_parser.SetDC(&pdf_dc);
- html_parser.SetStandardFonts
- (pdf_dc.GetFont().GetPointSize()
- ,"Helvetica"
- ,"Courier"
- );
+ pdf_writer_wx pdf_writer(output_filename, wxLANDSCAPE);
int pos_y = 0;
- output_image_header(pdf_dc, &pos_y);
+ output_image_header(pdf_writer, &pos_y);
pos_y += 2 * vert_skip;
- output_document_header(pdf_dc, html_parser, &pos_y);
+ output_document_header(pdf_writer, &pos_y);
pos_y += 2 * vert_skip;
wx_table_generator table_gen
- (pdf_dc
- ,horz_margin
- ,page_.width_
+ (pdf_writer.dc()
+ ,pdf_writer.get_horz_margin()
+ ,pdf_writer.get_page_width()
);
// Some of the table columns don't need to be shown if all the values in
@@ -976,21 +736,22 @@ void
group_quote_pdf_generator_wx::do_generate_pdf(wxPdfDC& pdf_dc)
table_gen.add_column(header, cd.widest_text_);
}
- output_aggregate_values(pdf_dc, table_gen, &pos_y);
+ output_aggregate_values(pdf_writer, table_gen, &pos_y);
int const y_before_header = pos_y;
table_gen.output_header(&pos_y);
int const header_height = pos_y - y_before_header;
int y_after_footer = pos_y;
- output_footer(pdf_dc, html_parser, &y_after_footer, e_output_measure_only);
+ output_footer(pdf_writer, &y_after_footer, e_output_measure_only);
int const footer_height = y_after_footer - pos_y;
- int const last_row_y = page_.total_size_.y - vert_margin;
+ int const last_row_y = pdf_writer.get_page_bottom();
int remaining_space = last_row_y - pos_y;
int total_pages = compute_pages_for_table_rows
- (&remaining_space
+ (pdf_writer
+ ,&remaining_space
,header_height
,table_gen.row_height()
,last_row_y
@@ -1013,38 +774,39 @@ void
group_quote_pdf_generator_wx::do_generate_pdf(wxPdfDC& pdf_dc)
if(last_row_y <= pos_y)
{
- output_page_number_and_version(pdf_dc, total_pages, current_page);
+ output_page_number_and_version(pdf_writer, total_pages,
current_page);
current_page++;
- pdf_dc.StartPage();
+ pdf_writer.dc().StartPage();
- pos_y = vert_margin;
+ pos_y = pdf_writer.get_vert_margin();
table_gen.output_header(&pos_y);
}
}
if(footer_on_its_own_page)
{
- output_page_number_and_version(pdf_dc, total_pages, current_page);
+ output_page_number_and_version(pdf_writer, total_pages, current_page);
current_page++;
- pdf_dc.StartPage();
+ pdf_writer.dc().StartPage();
- pos_y = vert_margin;
+ pos_y = pdf_writer.get_vert_margin();
}
else
{
pos_y += 2 * vert_skip;
}
- output_footer(pdf_dc, html_parser, &pos_y);
+ output_footer(pdf_writer, &pos_y);
LMI_ASSERT(current_page == total_pages);
- output_page_number_and_version(pdf_dc, total_pages, current_page);
+ output_page_number_and_version(pdf_writer, total_pages, current_page);
}
int group_quote_pdf_generator_wx::compute_pages_for_table_rows
- (int* remaining_space
+ (pdf_writer_wx& pdf_writer
+ ,int* remaining_space
,int header_height
,int row_height
,int last_row_y
@@ -1060,7 +822,8 @@ int
group_quote_pdf_generator_wx::compute_pages_for_table_rows
// rest of them.
remaining_rows -= max_rows_on_first_page;
- int const page_area_y = last_row_y - vert_margin - header_height;
+ int const first_row_y = pdf_writer.get_vert_margin() + header_height;
+ int const page_area_y = last_row_y - first_row_y;
int const rows_per_page = page_area_y / row_height;
total_pages += (remaining_rows + rows_per_page - 1) / rows_per_page;
*remaining_space = page_area_y;
@@ -1073,18 +836,20 @@ int
group_quote_pdf_generator_wx::compute_pages_for_table_rows
}
void group_quote_pdf_generator_wx::output_page_number_and_version
- (wxPdfDC& pdf_dc
+ (pdf_writer_wx& pdf_writer
,int total_pages
,int current_page
)
{
wxRect const footer_area
- (horz_margin
- ,page_.total_size_.y - vert_margin
- ,page_.width_
- ,vert_margin
+ (pdf_writer.get_horz_margin()
+ ,pdf_writer.get_page_bottom()
+ ,pdf_writer.get_page_width()
+ ,pdf_writer.get_vert_margin()
);
+ auto& pdf_dc = pdf_writer.dc();
+
pdf_dc.DrawLabel
(wxString::Format("System version: %s", LMI_VERSION)
,footer_area
@@ -1099,7 +864,7 @@ void
group_quote_pdf_generator_wx::output_page_number_and_version
}
void group_quote_pdf_generator_wx::output_image_header
- (wxPdfDC& pdf_dc
+ (pdf_writer_wx& pdf_writer
,int* pos_y
)
{
@@ -1110,15 +875,17 @@ void group_quote_pdf_generator_wx::output_image_header
}
// Set the scale to fit the image to the document width.
- double const
- scale = static_cast<double>(banner_image.GetWidth()) /
page_.total_size_.x;
+ double const image_width = banner_image.GetWidth();
+ double const scale = image_width / pdf_writer.get_total_width();
int const pos_top = *pos_y;
- output_image(pdf_dc, banner_image, "banner", scale, 0, pos_y);
+ pdf_writer.output_image(banner_image, "banner", scale, 0, pos_y);
+
+ auto& pdf_dc = pdf_writer.dc();
wxDCFontChanger set_bigger_font(pdf_dc, pdf_dc.GetFont().Scaled(1.5));
wxDCTextColourChanger set_white_text(pdf_dc, *wxWHITE);
- // Don't use escape_for_html_elem() here: instead, call
+ // Don't use html::text::from() here: instead, call
// wxString::FromUTF8() directly, e.g., to preserve literal '&'.
wxString const image_text
(wxString::FromUTF8(report_data_.short_product_.c_str())
@@ -1128,7 +895,7 @@ void group_quote_pdf_generator_wx::output_image_header
pdf_dc.DrawLabel
(image_text
,wxRect
- (wxPoint(horz_margin, (pos_top + *pos_y) / 2),
+ (wxPoint(pdf_writer.get_horz_margin(), (pos_top + *pos_y) / 2),
pdf_dc.GetMultiLineTextExtent(image_text)
)
,wxALIGN_CENTER_HORIZONTAL
@@ -1136,45 +903,53 @@ void group_quote_pdf_generator_wx::output_image_header
}
void group_quote_pdf_generator_wx::output_document_header
- (wxPdfDC& pdf_dc
- ,wxHtmlWinParser& html_parser
+ (pdf_writer_wx& pdf_writer
,int* pos_y
)
{
- wxString const title_html = wxString::Format
- ("<table width=\"100%%\">"
- "<tr>"
- "<td align=\"center\"><i><font size=\"+1\">%s</font></i></td>"
- "</tr>"
- "<tr>"
- "<td align=\"center\"><i>Prepared Date: %s</i></td>"
- "</tr>"
- "<tr>"
- "<td align=\"center\"><i>Prepared By: %s</i></td>"
- "</tr>"
- "</table>"
- ,escape_for_html_elem(report_data_.company_)
- ,wxDateTime::Today().FormatDate()
- ,escape_for_html_elem(report_data_.prepared_by_)
+ using namespace html;
+
+ auto title_html =
+ tag::table[attr::width("100%")]
+ (tag::tr
+ (tag::td[attr::align("center")]
+ (tag::i
+ (tag::font[attr::size("+1")]
+ (text::from(report_data_.company_)
+ )
+ )
+ )
+ )
+ )
+ (tag::tr
+ (tag::td[attr::align("center")]
+ (tag::i
+ (text::from
+ ("Prepared Date: "
+ +wxDateTime::Today().FormatDate().ToStdString()
+ )
+ )
+ )
+ )
+ )
+ (tag::tr
+ (tag::td[attr::align("center")]
+ (tag::i
+ (text::from("Prepared By: " +
report_data_.prepared_by_)
+ )
+ )
+ )
+ );
+
+ pdf_writer.output_html
+ (pdf_writer.get_horz_margin()
+ ,*pos_y
+ ,pdf_writer.get_page_width() / 2
+ ,title_html
);
- output_html(html_parser, horz_margin, *pos_y, page_.width_ / 2,
title_html);
-
- // Build the summary table with all the mandatory fields.
- wxString summary_html =
- "<table width=\"100%\" cellspacing=\"0\" cellpadding=\"0\">"
- // This extra top empty row works around a bug in wxHTML
- // table positioning code: it uses the provided ordinate
- // coordinate as a base line of the first table line and
- // not as its top, as it ought to, so without this line
- // the rectangle drawn below wouldn't contain the header.
- "<tr>"
- "<td align=\"center\" colspan=\"4\"> </td>"
- "</tr>"
- "<tr>"
- "<td align=\"center\" colspan=\"4\"><font size=\"+1\">Plan Details
Summary</font></td>"
- "</tr>"
- ;
+ // Build the summary table with all the mandatory fields, starting by
+ // building the (partly) dynamic fields rows part.
// Add fixed fields first, then any additional ones,
// in left-to-right then top-to-bottom order.
@@ -1196,34 +971,66 @@ void group_quote_pdf_generator_wx::output_document_header
std::vector<extra_summary_field> const& f = report_data_.extra_fields_;
fields.insert(fields.end(), f.begin(), f.end());
- bool parity = true;
- for(auto const& i : fields)
+ text fields_html;
+ for(std::size_t i = 0; i < fields.size(); i += 2)
{
- summary_html += parity ? "<tr>" : "";
- summary_html += name_value_as_html_table_data(i.name, i.value);
- summary_html += parity ? "" : "</tr>";
- parity = !parity;
+ auto row_html = name_value_as_html_table_data
+ (fields[i].name, fields[i].value
+ )
+ ;
+
+ if(i + 1 < fields.size())
+ {
+ row_html += name_value_as_html_table_data
+ (fields[i + 1].name, fields[i + 1].value
+ )
+ ;
+ }
+
+ fields_html += tag::tr(row_html);
}
- summary_html += parity ? "" : "</tr>";
// Finally close the summary table.
- summary_html += "</table>";
+ auto const summary_html =
+ tag::table[attr::width("100%")]
+ [attr::cellspacing("0")]
+ [attr::cellpadding("0")]
+ // This extra top empty row works around a bug in wxHTML
+ // table positioning code: it uses the provided ordinate
+ // coordinate as a base line of the first table line and
+ // not as its top, as it ought to, so without this line
+ // the rectangle drawn below wouldn't contain the header.
+ (tag::tr
+ (tag::td[attr::align("center")][attr::colspan("4")]
+ (text::nbsp())
+ )
+ )
+ (tag::tr
+ (tag::td[attr::align("center")][attr::colspan("4")]
+ (tag::font[attr::size("+1")]
+ (text::from("Plan Details Summary"))
+ )
+ )
+ )
+ (fields_html
+ )
+ ;
- int const summary_height = output_html
- (html_parser
- ,horz_margin + page_.width_ / 2
+ int const summary_height = pdf_writer.output_html
+ (pdf_writer.get_horz_margin() + pdf_writer.get_page_width() / 2
,*pos_y
- ,page_.width_ / 2
+ ,pdf_writer.get_page_width() / 2
,summary_html
);
// wxHTML tables don't support "frame" attribute, so draw the border around
// the table manually.
+ auto& pdf_dc = pdf_writer.dc();
pdf_dc.SetBrush(*wxTRANSPARENT_BRUSH);
pdf_dc.DrawRectangle
- (horz_margin + page_.width_ / 2
+ (pdf_writer.get_horz_margin() + pdf_writer.get_page_width() / 2
,*pos_y
- ,page_.width_ / 2
+ ,pdf_writer.get_page_width() / 2
,summary_height
);
@@ -1231,7 +1038,7 @@ void group_quote_pdf_generator_wx::output_document_header
}
void group_quote_pdf_generator_wx::output_aggregate_values
- (wxPdfDC& pdf_dc
+ (pdf_writer_wx& pdf_writer
,wx_table_generator& table_gen
,int* pos_y
)
@@ -1248,6 +1055,8 @@ void group_quote_pdf_generator_wx::output_aggregate_values
table_gen.output_vert_separator(e_col_number, y);
table_gen.output_vert_separator(e_col_number, y_next);
+ auto& pdf_dc = pdf_writer.dc();
+
// Render "Census" in bold.
wxDCFontChanger set_bold_font(pdf_dc, pdf_dc.GetFont().Bold());
pdf_dc.DrawLabel
@@ -1364,8 +1173,7 @@ void group_quote_pdf_generator_wx::output_aggregate_values
}
void group_quote_pdf_generator_wx::output_footer
- (wxPdfDC& pdf_dc
- ,wxHtmlWinParser& html_parser
+ (pdf_writer_wx& pdf_writer
,int* pos_y
,enum_output_mode output_mode
)
@@ -1375,18 +1183,24 @@ void group_quote_pdf_generator_wx::output_footer
{
// Arbitrarily scale down the logo by a factor of 2 to avoid making it
// too big.
- output_image(pdf_dc, logo_image, "company_logo", 2.0, horz_margin,
pos_y, output_mode);
+ pdf_writer.output_image
+ (logo_image
+ ,"company_logo"
+ ,2.0
+ ,pdf_writer.get_horz_margin()
+ ,pos_y
+ ,output_mode
+ );
*pos_y += vert_skip;
}
- wxString const footer_html = "<p>" + report_data_.footer_html_ + "</p>";
+ auto footer_html = html::tag::p(report_data_.footer_html_);
- *pos_y += output_html
- (html_parser
- ,horz_margin
+ *pos_y += pdf_writer.output_html
+ (pdf_writer.get_horz_margin()
,*pos_y
- ,page_.width_
+ ,pdf_writer.get_page_width()
,footer_html
,output_mode
);
diff --git a/html.cpp b/html.cpp
new file mode 100644
index 0000000..c3427ee
--- /dev/null
+++ b/html.cpp
@@ -0,0 +1,128 @@
+// Utilities for representing and generating HTML.
+//
+// Copyright (C) 2017, 2018 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#include "pchfile.hpp"
+
+#include "html.hpp"
+
+#include <cstring>
+
+namespace html
+{
+
+namespace attr
+{
+
+extern attribute const align ("align");
+extern attribute const cellpadding ("cellpadding");
+extern attribute const cellspacing ("cellspacing");
+extern attribute const colspan ("colspan");
+extern attribute const nowrap ("nowrap");
+extern attribute const size ("size");
+extern attribute const valign ("valign");
+extern attribute const width ("width");
+
+} // namespace attr
+
+namespace tag
+{
+
+extern element const b ("b");
+extern void_element const br ("br");
+extern element const font ("font");
+extern element const i ("i");
+extern element const p ("p");
+extern element const table ("table");
+extern element const td ("td");
+extern element const tr ("tr");
+
+} // namespace tag
+
+std::string attribute::as_string() const
+{
+ std::string s(name_);
+ if(!value_.empty())
+ {
+ s += "=";
+ // TODO: Escape quotes.
+ s += value_;
+ }
+ return s;
+}
+
+namespace detail
+{
+
+std::string any_element::get_start() const
+{
+ std::string s("<");
+ // Extra +1 for the space before attributes, even if it's not needed.
+ s.reserve(1 + std::strlen(name_) + 1 + attributes_.length() + 1);
+ s += name_;
+ if(!attributes_.empty())
+ {
+ s += " ";
+ s += attributes_;
+ }
+ s += ">";
+ return s;
+}
+
+void any_element::update_attributes(attribute const& attr)
+{
+ if(attributes_.empty())
+ {
+ attributes_ = attr.as_string();
+ }
+ else
+ {
+ attributes_ += " ";
+ attributes_ += attr.as_string();
+ }
+}
+
+} // namespace detail
+
+void element::update_contents(std::string&& contents)
+{
+ if(contents_.empty())
+ {
+ contents_ = std::move(contents);
+ }
+ else
+ {
+ contents_ += contents;
+ }
+}
+
+element::operator text() const
+{
+ std::string s(get_start());
+ s.reserve(s.length() + contents_.length() + 2 + std::strlen(name_) + 1);
+ s += contents_;
+ s += "</";
+ s += name_;
+ s += ">";
+
+ return text::from_html(std::move(s));
+}
+
+} // namespace html
diff --git a/html.hpp b/html.hpp
new file mode 100644
index 0000000..8ebb071
--- /dev/null
+++ b/html.hpp
@@ -0,0 +1,329 @@
+// Utilities for representing and generating HTML.
+//
+// Copyright (C) 2017, 2018 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#ifndef html_hpp
+#define html_hpp
+
+#include "config.hpp"
+
+#include "so_attributes.hpp"
+
+#include <string>
+#include <utility> // std::move
+
+/// Namespace for helpers used for HTML generation.
+///
+/// Main idea is to avoid generating HTML using raw strings, which is error
+/// prone and difficult to read and maintain. One source of errors is
+/// forgetting to escape special characters, such as "<" or "&", and html::text
+/// class helps with this by providing from() method doing it automatically.
+///
+/// Another one is forgetting to close a tag (or closing a wrong one) and while
+/// html::text is too low level to help with this, html::element can be used
+/// for structured HTML generation, which guarantees that the result is
+/// well-formed. By using predefined constants in html::tag and html::attr
+/// namespaces, typos in the element names can also be automatically avoided.
+namespace html
+{
+
+/// Represents a piece of text containing HTML.
+///
+/// This is a separate type for type safety, e.g. to avoid passing raw,
+/// unescaped, strings to a function expecting HTML (or, less catastrophically,
+/// but still wrongly, passing already escaped HTML to a function doing
+/// escaping internally).
+///
+/// As it still needs to be converted to a string sooner or later to be really
+/// used, it does provide a conversion -- but it can be used only once.
+class text
+{
+ public:
+ // This type has value semantics.
+ text() = default;
+ text(text const&) = default;
+ text(text&&) = default;
+ text& operator=(text const&) = default;
+ text& operator=(text&&) = default;
+
+ /// Escape special XML characters in the given string, ensuring that it
+ /// appears correctly inside HTML element contents. Notice that we don't
+ /// need to escape quotes here as we never use the result of this function
+ /// inside an HTML attribute, only inside HTML elements.
+ static text from(std::string const& s)
+ {
+ std::string z;
+ z.reserve(s.length());
+ for(auto const& c : s)
+ {
+ switch(c)
+ {
+ case '<': z += "<" ; break;
+ case '>': z += ">" ; break;
+ case '&': z += "&"; break;
+ default : z += c ;
+ }
+ }
+
+ return text{std::move(z)};
+ }
+
+ /// Use the given string with HTML inside it directly. No escaping is done
+ /// by this ctor.
+ static text from_html(std::string s)
+ {
+ return text{std::move(s)};
+ }
+
+ /// Just a symbolic name for a non breaking space HTML entiry.
+ static text nbsp()
+ {
+ return text::from_html(" ");
+ }
+
+ /// Append another text fragment to this one.
+ ///
+ /// This method allows chained invocation for appending more than one
+ /// fragment at once.
+ text& operator+=(text const& t)
+ {
+ m_html += t.m_html;
+
+ return *this;
+ }
+
+ std::string const& as_html() const&
+ {
+ return m_html;
+ }
+
+ std::string&& as_html() &&
+ {
+ return std::move(m_html);
+ }
+
+ private:
+ // This move ctor is private and does not perform any escaping.
+ explicit text(std::string&& html)
+ :m_html{html}
+ {
+ }
+
+ std::string m_html;
+};
+
+/// Represents a single attribute of an HTML element.
+class attribute
+{
+ public:
+ explicit attribute(char const* name)
+ :name_{name}
+ {
+ }
+
+ attribute operator()(std::string value) const
+ {
+ return attribute(name_, std::move(value));
+ }
+
+ std::string as_string() const;
+
+ private:
+ attribute(char const* name, std::string&& value)
+ :name_{name}
+ ,value_{std::move(value)}
+ {
+ }
+
+ char const* const name_;
+ std::string const value_;
+};
+
+namespace detail
+{
+
+class LMI_SO any_element
+{
+ public:
+ /// Ctor should only be used with literal strings as argument.
+ explicit any_element(char const* name)
+ :name_(name)
+ {
+ }
+
+ protected:
+ // Return the opening tag of the element, with attributes, if any.
+ std::string get_start() const;
+
+ // Add the given attribute to our attributes string.
+ void update_attributes(attribute const& attr);
+
+ char const* const name_;
+
+ private:
+ std::string attributes_;
+};
+
+} // namespace detail
+
+/// Represents a normal HTML element which can have content inside it.
+///
+/// This class uses the so called fluent API model in which calls to its
+/// different methods return the object itself and so can be chained together.
+/// For example (assuming an implicit "using namespace html"):
+///
+/// auto para_with_link =
+/// tag::p[attr::align("center")]
+/// (text("Link to "))
+/// (tag::a[attr::href("http://lmi.nongnu.org/")]
+/// (text::from("lmi project page"))
+/// )
+/// ;
+
+class LMI_SO element : private detail::any_element
+{
+ public:
+ /// Ctor should only be used with literal strings as argument.
+ explicit element(char const* name)
+ :detail::any_element(name)
+ {
+ }
+
+ element(element const&) = default;
+ element(element&&) = default;
+
+ /// Add an attribute.
+ element operator[](attribute const& attr) const&
+ {
+ element e{*this};
+ e.update_attributes(attr);
+ return e;
+ }
+
+ element&& operator[](attribute const& attr) &&
+ {
+ update_attributes(attr);
+ return std::move(*this);
+ }
+
+ /// Add inner contents.
+ element operator()(text contents) const&
+ {
+ element e{*this};
+ e.update_contents(std::move(contents).as_html());
+ return e;
+ }
+
+ element&& operator()(text contents) &&
+ {
+ update_contents(std::move(contents).as_html());
+ return std::move(*this);
+ }
+
+ /// Convert to HTML text with this element and its contents.
+ ///
+ /// This implicit conversion operator is not really dangerous as it is
+ /// normal to represent an HTML element as HTML text and it's very
+ /// convenient to have it as it allows to accept either another element or
+ /// text in our own operator() and also use operator+() defined below to
+ /// concatenate HTML elements without having to convert them to text
+ /// beforehand.
+ operator text() const;
+
+ private:
+ void update_contents(std::string&& contents);
+
+ std::string contents_;
+};
+
+/// Represents a void HTML element which can't have anything inside it.
+class void_element : private detail::any_element
+{
+ public:
+ explicit void_element(char const* name)
+ :detail::any_element(name)
+ {
+ }
+
+ void_element(void_element const&) = default;
+ void_element(void_element&&) = default;
+
+ void_element operator[](attribute const& attr) const&
+ {
+ void_element e{*this};
+ e.update_attributes(std::move(attr));
+ return e;
+ }
+
+ void_element&& operator[](attribute const& attr) &&
+ {
+ update_attributes(std::move(attr));
+ return std::move(*this);
+ }
+
+ operator text() const
+ {
+ return text::from_html(get_start());
+ }
+};
+
+/// Namespace for HTML attributes.
+
+namespace attr
+{
+
+extern LMI_SO attribute const align;
+extern LMI_SO attribute const cellpadding;
+extern LMI_SO attribute const cellspacing;
+extern LMI_SO attribute const colspan;
+extern LMI_SO attribute const nowrap;
+extern LMI_SO attribute const size;
+extern LMI_SO attribute const valign;
+extern LMI_SO attribute const width;
+
+} // namespace attr
+
+/// Namespace for HTML tags.
+
+namespace tag
+{
+
+extern LMI_SO element const b;
+extern LMI_SO void_element const br;
+extern LMI_SO element const font;
+extern LMI_SO element const i;
+extern LMI_SO element const p;
+extern LMI_SO element const table;
+extern LMI_SO element const td;
+extern LMI_SO element const tr;
+
+} // namespace tag
+
+inline
+text operator+(text t1, text const& t2)
+{
+ text t{std::move(t1)};
+ t += t2;
+ return t;
+}
+
+} // namespace html
+
+#endif // html_hpp
diff --git a/imprimatur.mst b/imprimatur.mst
new file mode 100644
index 0000000..ff83332
--- /dev/null
+++ b/imprimatur.mst
@@ -0,0 +1,40 @@
+{{!
+ This template defines the compliance tracking number used in a few
+ different places to avoid duplicating this logic in all of them.
+
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{#IsInforce}}
+ {{#Composite}}
+ {{ImprimaturInforceComposite}}
+ {{/Composite}}
+ {{^Composite}}
+ {{ImprimaturInforce}}
+ {{/Composite}}
+{{/IsInforce}}
+{{^IsInforce}}
+ {{#Composite}}
+ {{ImprimaturPresaleComposite}}
+ {{/Composite}}
+ {{^Composite}}
+ {{ImprimaturPresale}}
+ {{/Composite}}
+{{/IsInforce}}
diff --git a/interpolate_string.cpp b/interpolate_string.cpp
new file mode 100644
index 0000000..a372230
--- /dev/null
+++ b/interpolate_string.cpp
@@ -0,0 +1,296 @@
+// Interpolate string containing embedded variable references.
+//
+// Copyright (C) 2017, 2018 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#include "pchfile.hpp"
+
+#include "interpolate_string.hpp"
+
+#include "alert.hpp"
+
+#include <stack>
+#include <stdexcept>
+#include <vector>
+
+namespace
+{
+
+// Information about a single section that we're currently in.
+struct section_info
+{
+ section_info(std::string const& name, bool active)
+ :name_(name)
+ ,active_(active)
+ {
+ }
+
+ // Name of the section, i.e. the part after "#".
+ //
+ // TODO: In C++14 this could be replaced with string_view which would
+ // save on memory allocations without compromising safety, as we know
+ // that the input string doesn't change during this function execution.
+ std::string const name_;
+
+ // If true, output section contents, otherwise simply eat it.
+ bool const active_;
+
+ // Note: we could also store the position of the section start here to
+ // improve error reporting. Currently this is done as templates we use
+ // are small and errors shouldn't be difficult to find even without the
+ // exact position, but this could change in the future.
+};
+
+// The only context we need is the stack of sections entered so far.
+using context = std::stack<section_info, std::vector<section_info>>;
+
+// The real interpolation recursive function, called by the public one to do
+// all the work.
+void do_interpolate_string_in_context
+ (char const* s
+ ,lookup_function const& lookup
+ ,std::string& out
+ ,context& sections
+ ,std::string const& partial = std::string()
+ ,int recursion_level = 0
+ )
+{
+ // Guard against too deep recursion to avoid crashing on code using too
+ // many nested partials (either unintentionally, e.g. due to including a
+ // partial from itself, or maliciously).
+ //
+ // The maximum recursion level is chosen completely arbitrarily, the only
+ // criteria are that it shouldn't be too big to crash due to stack overflow
+ // before it is reached nor too small to break legitimate use cases.
+ if(recursion_level >= 100)
+ {
+ alarum()
+ << "Nesting level too deep while expanding the partial \""
+ << partial
+ << "\""
+ << std::flush
+ ;
+ }
+
+ // Check if the output is currently active or suppressed because we're
+ // inside an inactive section.
+ auto const is_active = [§ions]()
+ {
+ return sections.empty() || sections.top().active_;
+ };
+
+ for(char const* p = s; *p; ++p)
+ {
+ // As we know that the string is NUL-terminated, it is safe to check
+ // the next character.
+ if(p[0] == '{' && p[1] == '{')
+ {
+ std::string name;
+ auto const pos_start = p - s + 1;
+ for(p += 2;; ++p)
+ {
+ if(*p == '\0')
+ {
+ alarum()
+ << "Unmatched opening brace at position "
+ << pos_start
+ << std::flush
+ ;
+ }
+
+ if(p[0] == '}' && p[1] == '}')
+ {
+ switch(name.empty() ? '\0' : name[0])
+ {
+ case '#':
+ case '^':
+ {
+ auto const real_name = name.substr(1);
+ // If we're inside a disabled section, it doesn't
+ // matter whether this one is active or not.
+ bool active = is_active();
+ if(active)
+ {
+ auto const value = lookup
+ (real_name
+ ,interpolate_lookup_kind::section
+ );
+ if(value == "1")
+ {
+ active = true;
+ }
+ else if(value == "0")
+ {
+ active = false;
+ }
+ else
+ {
+ alarum()
+ << "Invalid value '"
+ << value
+ << "' of section '"
+ << real_name
+ << "' at position "
+ << pos_start
+ << ", only \"0\" or \"1\" allowed"
+ << std::flush
+ ;
+ }
+
+ if(name[0] == '^')
+ {
+ active = !active;
+ }
+ }
+
+ sections.emplace(real_name, active);
+ }
+ break;
+
+ case '/':
+ if(sections.empty())
+ {
+ alarum()
+ << "Unexpected end of section '"
+ << name.substr(1)
+ << "' at position "
+ << pos_start
+ << " without previous section start"
+ << std::flush
+ ;
+ }
+ if(name.compare(1, std::string::npos,
sections.top().name_) != 0)
+ {
+ alarum()
+ << "Unexpected end of section '"
+ << name.substr(1)
+ << "' at position "
+ << pos_start
+ << " while inside the section '"
+ << sections.top().name_
+ << "'"
+ << std::flush
+ ;
+ }
+ sections.pop();
+ break;
+
+ case '>':
+ if(is_active())
+ {
+ auto const& real_name = name.substr(1);
+
+ do_interpolate_string_in_context
+ (lookup
+ (real_name
+ ,interpolate_lookup_kind::partial
+ ).c_str()
+ ,lookup
+ ,out
+ ,sections
+ ,real_name
+ ,recursion_level + 1
+ );
+ }
+ break;
+
+ case '!':
+ // This is a comment, we just ignore it completely.
+ break;
+
+ default:
+ if(is_active())
+ {
+ // We don't check here if name is not empty, as
+ // there is no real reason to do it. Empty
+ // variable name may seem strange, but why not
+ // allow using "{{}}" to insert something into
+ // the interpolated string, after all?
+ out += lookup
+ (name
+ ,interpolate_lookup_kind::variable
+ );
+ }
+ }
+
+ // We consume two characters here ("}}"), not one, as in a
+ // usual loop iteration.
+ ++p;
+ break;
+ }
+
+ if(p[0] == '{' && p[1] == '{')
+ {
+ // We don't allow nested interpolations, so this can only
+ // be result of an error, e.g. a forgotten "}}" somewhere.
+ alarum()
+ << "Unexpected nested interpolation at position "
+ << pos_start
+ << " (outer interpolation starts at "
+ << (p - s - 1 - name.length())
+ << ")"
+ << std::flush
+ ;
+ }
+
+ // We don't impose any restrictions on the kind of characters
+ // that can occur in the variable names neither because there
+ // just doesn't seem to be anything to gain from it.
+ name += *p;
+ }
+ }
+ else if(is_active())
+ {
+ out += *p;
+ }
+ }
+}
+
+} // Unnamed namespace.
+
+std::string interpolate_string
+ (char const* s
+ ,lookup_function const& lookup
+ )
+{
+ std::string out;
+
+ // This is probably not going to be enough as replacements of the
+ // interpolated variables tend to be longer than the variables names
+ // themselves, but it's difficult to estimate the resulting string length
+ // any better than this.
+ out.reserve(strlen(s));
+
+ // The stack contains all the sections that we're currently in.
+ std::stack<section_info, std::vector<section_info>> sections;
+
+ do_interpolate_string_in_context(s, lookup, out, sections);
+
+ if(!sections.empty())
+ {
+ alarum()
+ << "Unclosed section '"
+ << sections.top().name_
+ << "'"
+ << std::flush
+ ;
+ }
+
+ return out;
+}
diff --git a/interpolate_string.hpp b/interpolate_string.hpp
new file mode 100644
index 0000000..7515553
--- /dev/null
+++ b/interpolate_string.hpp
@@ -0,0 +1,69 @@
+// Interpolate string containing embedded variable references.
+//
+// Copyright (C) 2017, 2018 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#ifndef interpolate_string_hpp
+#define interpolate_string_hpp
+
+#include "config.hpp"
+
+#include "so_attributes.hpp"
+
+#include <functional>
+#include <string>
+
+enum class interpolate_lookup_kind
+{
+ variable,
+ section,
+ partial
+};
+
+using lookup_function
+ = std::function<std::string (std::string const&, interpolate_lookup_kind)>;
+
+/// Interpolate string containing embedded variable references.
+///
+/// Return the input string after replacing all {{variable}} references in it
+/// with the value of the variable as returned by the provided function. The
+/// syntax is a (strict) subset of Mustache templates, the following features
+/// are supported:
+/// - Simple variable expansion for {{variable}}.
+/// - Conditional expansion using {{#variable}}...{{/variable}}.
+/// - Negated checks of the form {{^variable}}...{{/variable}}.
+/// - Partials support, i.e. {{>filename}}.
+///
+/// The following features are explicitly _not_ supported:
+/// - HTML escaping: this is done by a separate html::text class.
+/// - Separate types: 0/1 is false/true, anything else is an error.
+/// - Lists/section iteration (not needed yet).
+/// - Lambdas, comments, delimiter changes: omitted for simplicity.
+///
+/// To allow embedding literal "{{" fragment into the returned string, create a
+/// pseudo-variable expanding to these characters as its expansion, there is no
+/// built-in way to escape them.
+///
+/// Throw if the lookup function throws or if the string uses invalid syntax.
+std::string LMI_SO interpolate_string
+ (char const* s
+ ,lookup_function const& lookup
+ );
+
+#endif // interpolate_string_hpp
diff --git a/interpolate_string_test.cpp b/interpolate_string_test.cpp
new file mode 100644
index 0000000..cccd486
--- /dev/null
+++ b/interpolate_string_test.cpp
@@ -0,0 +1,233 @@
+// Interpolate string containing embedded variable references.
+//
+// Copyright (C) 2017, 2018 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#include "pchfile.hpp"
+
+#include "interpolate_string.hpp"
+
+#include "test_tools.hpp"
+
+int test_main(int, char*[])
+{
+ auto const test_interpolate = [](char const* s)
+ {
+ return interpolate_string
+ (s
+ ,[](std::string const& k, interpolate_lookup_kind) { return k; }
+ );
+ };
+
+ // Check that basic interpolation works.
+ BOOST_TEST_EQUAL( test_interpolate(""), "" );
+ BOOST_TEST_EQUAL( test_interpolate("literal"), "literal" );
+ BOOST_TEST_EQUAL( test_interpolate("{{foo}}"), "foo" );
+ BOOST_TEST_EQUAL( test_interpolate("{{foo}}bar"), "foobar" );
+ BOOST_TEST_EQUAL( test_interpolate("foo{{}}bar"), "foobar" );
+ BOOST_TEST_EQUAL( test_interpolate("foo{{bar}}"), "foobar" );
+ BOOST_TEST_EQUAL( test_interpolate("{{foo}}{{bar}}"), "foobar" );
+
+ // Comments should be just ignored.
+ BOOST_TEST_EQUAL( test_interpolate("{{! ignore me}}"), "" );
+ BOOST_TEST_EQUAL( test_interpolate("{{! too}}{{x}}"), "x" );
+ BOOST_TEST_EQUAL( test_interpolate("{{x}}{{!also}}"), "x" );
+
+ // Sections.
+ auto const section_test = [](char const* str)
+ {
+ return interpolate_string
+ (str
+ ,[](std::string const& s, interpolate_lookup_kind) -> std::string
+ {
+ if(s == "var0") return "0";
+ if(s == "var1") return "1";
+ if(s == "var" ) return "" ;
+
+ throw std::runtime_error("no such variable '" + s + "'");
+ }
+ );
+ };
+
+ BOOST_TEST_EQUAL( section_test("x{{#var1}}y{{/var1}}z"), "xyz" );
+ BOOST_TEST_EQUAL( section_test("x{{#var0}}y{{/var0}}z"), "xz" );
+ BOOST_TEST_EQUAL( section_test("x{{^var0}}y{{/var0}}z"), "xyz" );
+ BOOST_TEST_EQUAL( section_test("x{{^var1}}y{{/var1}}z"), "xz" );
+
+ BOOST_TEST_EQUAL
+ (section_test("a{{#var1}}b{{#var1}}c{{/var1}}d{{/var1}}e")
+ ,"abcde"
+ );
+ BOOST_TEST_EQUAL
+ (section_test("a{{#var1}}b{{#var0}}c{{/var0}}d{{/var1}}e")
+ ,"abde"
+ );
+ BOOST_TEST_EQUAL
+ (section_test("a{{^var1}}b{{#var0}}c{{/var0}}d{{/var1}}e")
+ ,"ae"
+ );
+ BOOST_TEST_EQUAL
+ (section_test("a{{^var1}}b{{^var0}}c{{/var0}}d{{/var1}}e")
+ ,"ae"
+ );
+
+ // Partials.
+ auto const partial_test = [](char const* str)
+ {
+ return interpolate_string
+ (str
+ ,[](std::string const& s, interpolate_lookup_kind) -> std::string
+ {
+ if(s == "header") return "[header with {{var}}]";
+ if(s == "footer") return "[footer with {{var}}]";
+ if(s == "nested") return "[header with {{>footer}}]";
+ if(s == "recursive") return "{{>recursive}}";
+ if(s == "sec" ) return "1" ;
+ if(s == "var" ) return "variable" ;
+
+ throw std::runtime_error("no such variable '" + s + "'");
+ }
+ );
+ };
+
+ BOOST_TEST_EQUAL
+ (partial_test("{{>header}}")
+ ,"[header with variable]"
+ );
+
+ BOOST_TEST_EQUAL
+ (partial_test("{{>header}}{{var}} in body{{>footer}}")
+ ,"[header with variable]variable in body[footer with variable]"
+ );
+
+ BOOST_TEST_EQUAL
+ (partial_test("{{#sec}}{{>header}}{{/sec}}")
+ ,"[header with variable]"
+ );
+
+ BOOST_TEST_EQUAL
+ (partial_test("only{{^sec}}{{>header}}{{/sec}}{{>footer}}")
+ ,"only[footer with variable]"
+ );
+
+ BOOST_TEST_EQUAL
+ (partial_test("{{>nested}}")
+ ,"[header with [footer with variable]]"
+ );
+
+ BOOST_TEST_THROW
+ (partial_test("{{>recursive}}")
+ ,std::runtime_error
+ ,lmi_test::what_regex("Nesting level too deep")
+ );
+
+ BOOST_TEST_EQUAL
+ (partial_test("no {{^sec}}{{>recursive}}{{/sec}} problem")
+ ,"no problem"
+ );
+
+ // Some special cases.
+ BOOST_TEST_EQUAL
+ (interpolate_string
+ ("{{expanded}}"
+ ,[](std::string const& s, interpolate_lookup_kind) -> std::string
+ {
+ if(s == "expanded")
+ {
+ return "{{unexpanded}}";
+ }
+ throw std::runtime_error("no such variable '" + s + "'");
+ }
+ )
+ ,"{{unexpanded}}"
+ );
+
+ // Check that the kind of variable being expanded is correct.
+ BOOST_TEST_EQUAL
+ (interpolate_string
+ ("{{>test}}"
+ "{{#section1}}{{^section0}}{{variable}}{{/section0}}{{/section1}}"
+ ,[](std::string const& s, interpolate_lookup_kind kind) ->
std::string
+ {
+ switch(kind)
+ {
+ case interpolate_lookup_kind::variable:
+ return "value of " + s;
+
+ case interpolate_lookup_kind::section:
+ // Get rid of the "section" prefix.
+ return s.substr(7);
+
+ case interpolate_lookup_kind::partial:
+ return s + " partial included\n";
+ }
+
+ throw std::runtime_error("invalid lookup kind");
+ }
+ )
+ ,"test partial included\nvalue of variable"
+ );
+
+ // Should throw if the input syntax is invalid.
+ BOOST_TEST_THROW
+ (test_interpolate("{{x")
+ ,std::runtime_error
+ ,lmi_test::what_regex("Unmatched opening brace")
+ );
+ BOOST_TEST_THROW
+ (test_interpolate("{{x{{y}}}}")
+ ,std::runtime_error
+ ,lmi_test::what_regex("Unexpected nested interpolation")
+ );
+ BOOST_TEST_THROW
+ (section_test("{{#var1}}")
+ ,std::runtime_error
+ ,lmi_test::what_regex("Unclosed section 'var1'")
+ );
+ BOOST_TEST_THROW
+ (section_test("{{^var0}}")
+ ,std::runtime_error
+ ,lmi_test::what_regex("Unclosed section 'var0'")
+ );
+ BOOST_TEST_THROW
+ (section_test("{{/var1}}")
+ ,std::runtime_error
+ ,lmi_test::what_regex("Unexpected end of section")
+ );
+ BOOST_TEST_THROW
+ (section_test("{{#var1}}{{/var0}}")
+ ,std::runtime_error
+ ,lmi_test::what_regex("Unexpected end of section")
+ );
+
+ // Or because the lookup function throws.
+ BOOST_TEST_THROW
+ (interpolate_string
+ ("{{x}}"
+ ,[](std::string const& s, interpolate_lookup_kind) -> std::string
+ {
+ throw std::runtime_error("no such variable '" + s + "'");
+ }
+ )
+ ,std::runtime_error
+ ,"no such variable 'x'"
+ );
+
+ return EXIT_SUCCESS;
+}
diff --git a/ledger.hpp b/ledger.hpp
index 32f7f48..0bd8821 100644
--- a/ledger.hpp
+++ b/ledger.hpp
@@ -24,6 +24,7 @@
#include "config.hpp"
+#include "ledger_evaluator.hpp"
#include "mc_enum_type_enums.hpp"
#include "so_attributes.hpp"
#include "xml_lmi.hpp"
@@ -102,6 +103,10 @@ class LMI_SO Ledger
unsigned int CalculateCRC() const;
void Spew(std::ostream& os) const;
+ ledger_evaluator make_evaluator() const;
+
+ // PDF !! Expunge the following six function declarations:
+
void read (xml::element const&);
void write(xml::element&) const;
int class_version() const;
diff --git a/ledger_evaluator.cpp b/ledger_evaluator.cpp
new file mode 100644
index 0000000..0a81492
--- /dev/null
+++ b/ledger_evaluator.cpp
@@ -0,0 +1,864 @@
+// Ledger evaluator returning values of all ledger fields.
+//
+// Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013,
2014, 2015, 2016, 2017, 2018 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#include "pchfile.hpp"
+
+#include "ledger_evaluator.hpp"
+
+#include "alert.hpp"
+#include "authenticity.hpp"
+#include "calendar_date.hpp"
+#include "configurable_settings.hpp"
+#include "contains.hpp"
+#include "global_settings.hpp"
+#include "handle_exceptions.hpp"
+#include "ledger.hpp"
+#include "ledger_invariant.hpp"
+#include "ledger_text_formats.hpp" // ledger_format()
+#include "ledger_variant.hpp"
+#include "map_lookup.hpp"
+#include "mc_enum_aux.hpp" // mc_e_vector_to_string_vector()
+#include "miscellany.hpp" // each_equal(), lmi_array_size()
+#include "oecumenic_enumerations.hpp"
+#include "value_cast.hpp"
+#include "version.hpp"
+
+#include <algorithm> // transform()
+#include <functional> // minus
+#include <unordered_map>
+#include <utility> // pair
+
+namespace
+{
+int const n = 7;
+
+char const* char_p_suffixes[n] =
+ {"_Current" // mce_run_gen_curr_sep_full
+ ,"_Guaranteed" // mce_run_gen_guar_sep_full
+ ,"_Midpoint" // mce_run_gen_mdpt_sep_full
+ ,"_CurrentZero" // mce_run_gen_curr_sep_zero
+ ,"_GuaranteedZero" // mce_run_gen_guar_sep_zero
+ ,"_CurrentHalf" // mce_run_gen_curr_sep_half
+ ,"_GuaranteedHalf" // mce_run_gen_guar_sep_half
+ };
+
+std::vector<std::string> const suffixes
+ (char_p_suffixes
+ ,char_p_suffixes + n
+ );
+
+typedef std::unordered_map<std::string, std::pair<int,oenum_format_style>>
format_map_t;
+typedef std::unordered_map<std::string, std::string> title_map_t;
+
+// For all numbers (so-called 'scalars' and 'vectors', but not
+// 'strings') grabbed from all ledgers, look for a format. If one
+// is found, use it to turn the number into a string. If not, and
+// the field is named in unavailable(), then it's ignored. Otherwise,
+// format_exists() displays a warning and ignores the field (because
+// throwing an exception would cause only the first warning to be
+// displayed).
+//
+// Rationale: Silently falling back on some default format can't be
+// right, because it masks defects that should be fixed: no default
+// can be universally appropriate.
+//
+// For names formed as
+// basename + '_' + suffix
+// only the basename is used as a map key. Lookups in the format map
+// are strict, as they must be, else one key like "A" would match
+// anything beginning with that letter.
+//
+// Some of the unavailable fields could easily be made available
+// someday; perhaps others should be eliminated from class Ledger.
+
+bool unavailable(std::string const& s)
+{
+ static std::string const a[] =
+ {"DateOfBirthJdn" // used by group quotes
+ ,"EffDateJdn" // used by group quotes
+ ,"ListBillDateJdn" // probably not needed
+ ,"InforceAsOfDateJdn" // probably not needed
+ ,"InitDacTaxRate" // used by PrintRosterTabDelimited(); not
cents
+ ,"InitPremTaxRate" // used by PrintRosterTabDelimited(); not
cents
+ ,"SubstdTable" // probably not needed
+ ,"InitMlyPolFee" // used by PrintRosterTabDelimited()
+ ,"InitTgtPremHiLoadRate" // used by PrintRosterTabDelimited(); not
cents
+ };
+ static std::vector<std::string> const v(a, a + lmi_array_size(a));
+ return contains(v, s);
+}
+
+bool format_exists
+ (std::string const& s
+ ,std::string const& suffix
+ ,format_map_t const& m
+ )
+{
+ if(contains(m, s))
+ {
+ return true;
+ }
+ else if(unavailable(s))
+ {
+ return false;
+ }
+ else
+ {
+ warning() << "No format found for " << s << suffix << LMI_FLUSH;
+ return false;
+ }
+}
+
+} // Unnamed namespace.
+
+std::string ledger_evaluator::operator()(std::string const& scalar) const
+{
+ return map_lookup(scalars_, scalar);
+}
+
+std::string ledger_evaluator::operator()
+ (std::string const& vector
+ ,std::size_t index
+ ) const
+{
+ return map_lookup(vectors_, vector).at(index);
+}
+
+ledger_evaluator Ledger::make_evaluator() const
+{
+ title_map_t title_map;
+
+// Can't seem to get a literal into the output.
+
+// Original: title_map["AttainedAge" ] = "
             
End of   Year Age";
+// No good: title_map["AttainedAge" ] = "
& & & & & & & & & & & & & 
End of & & Year Age";
+// No good: title_map["AttainedAge" ] = "
End of Year Age";
+// No good: title_map["AttainedAge" ] = "
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
End of &nbsp;&nbsp;Year Age";
+// No good: title_map["AttainedAge" ] = "<![CDATA[
             
End of   Year Age]]>";
+// No good: title_map["AttainedAge" ] = " ááááááááááááá
End of ááYear Age";
+// No good: title_map["AttainedAge" ] = "
             
End of   Year Age";
+
+// Here are the columns to be listed in the user interface
+// as well as their corresponding titles.
+
+ // Current and guaranteed variants are generally given for columns
+ // that vary by basis. Some offer only a current variant because
+ // they are defined only on a current basis--experience-rating
+ // columns, e.g.
+
+ title_map["AVGenAcct_CurrentZero" ] = "Curr
Charges\nAccount\nValue\nGen Acct";
+ title_map["AVGenAcct_GuaranteedZero" ] = "Guar
Charges\nAccount\nValue\nGen Acct";
+ title_map["AVRelOnDeath_Current" ] =
"Account\nValue\nReleased\non Death";
+ title_map["AVSepAcct_CurrentZero" ] = "Curr Charges\n0%
Account\nValue\nSep Acct";
+ title_map["AVSepAcct_GuaranteedZero" ] = "Guar Charges\n0%
Account\nValue\nSep Acct";
+ title_map["AcctVal_Current" ] = "Curr Account\nValue";
+ title_map["AcctVal_CurrentZero" ] = "Curr Charges\n0%
Account\nValue";
+ title_map["AcctVal_Guaranteed" ] = "Guar Account\nValue";
+ title_map["AcctVal_GuaranteedZero" ] = "Guar Charges\n0%
Account\nValue";
+ title_map["AddonCompOnAssets" ] = "Additional\nComp
on\nAssets";
+ title_map["AddonCompOnPremium" ] = "Additional\nComp
on\nPremium";
+ title_map["AddonMonthlyFee" ] = "Additional\nMonthly\nFee";
+ title_map["AnnGAIntRate_Current" ] = "Curr Ann\nGen Acct\nInt
Rate";
+ title_map["AnnGAIntRate_Guaranteed" ] = "Guar Ann\nGen Acct\nInt
Rate";
+ title_map["AnnHoneymoonValueRate_Current" ] = "Curr
Ann\nHoneymoon\nValue Rate";
+ title_map["AnnHoneymoonValueRate_Guaranteed"] = "Guar
Ann\nHoneymoon\nValue Rate";
+ title_map["AnnPostHoneymoonRate_Current" ] = "Curr
Post\nHoneymoon\nRate";
+ title_map["AnnPostHoneymoonRate_Guaranteed" ] = "Guar
Post\nHoneymoon\nRate";
+ title_map["AnnSAIntRate_Current" ] = "Curr Ann\nSep Acct\nInt
Rate";
+ title_map["AnnSAIntRate_Guaranteed" ] = "Guar Ann\nSep Acct\nInt
Rate";
+ title_map["AttainedAge" ] = "End of\nYear Age";
+ title_map["AvgDeathBft_Current" ] = "Curr Avg\nDeath\nBenefit";
+ title_map["AvgDeathBft_Guaranteed" ] = "Guar Avg\nDeath\nBenefit";
+ title_map["BaseDeathBft_Current" ] = "Curr
Base\nDeath\nBenefit";
+ title_map["BaseDeathBft_Guaranteed" ] = "Guar
Base\nDeath\nBenefit";
+ title_map["COICharge_Current" ] = "Curr COI\nCharge";
+ title_map["COICharge_Guaranteed" ] = "Guar COI\nCharge";
+ title_map["CSVNet_Current" ] = "Curr Net\nCash\nSurr
Value";
+ title_map["CSVNet_CurrentZero" ] = "Curr Charges\n0% Net
Cash\nSurr Value";
+ title_map["CSVNet_Guaranteed" ] = "Guar Net\nCash\nSurr
Value";
+ title_map["CSVNet_GuaranteedZero" ] = "Guar Charges\n0% Net
Cash\nSurr Value";
+ title_map["CV7702_Current" ] = "Curr 7702\nCash Value";
+ title_map["CV7702_Guaranteed" ] = "Guar 7702\nCash Value";
+ title_map["ClaimsPaid_Current" ] = "Curr\nClaims\nPaid";
+ title_map["ClaimsPaid_Guaranteed" ] = "Guar\nClaims\nPaid";
+ title_map["CorpTaxBracket" ] = "Corp Tax\nBracket";
+ title_map["CorridorFactor" ] = "Corridor\nFactor";
+ title_map["CurrMandE" ] =
"Mortality\nand\nExpense\nCharge";
+ title_map["DBOpt" ] = "Death\nBenefit\nOption";
+ title_map["DacTaxLoad_Current" ] = "Curr DAC\nTax\nLoad";
+ title_map["DacTaxLoad_Guaranteed" ] = "Guar DAC\nTax\nLoad";
+ title_map["DacTaxRsv_Current" ] = "Curr DAC\nTax\nReserve";
+ title_map["DacTaxRsv_Guaranteed" ] = "Guar DAC\nTax\nReserve";
+ title_map["DeathProceedsPaid_Current" ] = "Curr
Death\nProceeds\nPaid";
+ title_map["DeathProceedsPaid_Guaranteed" ] = "Guar
Death\nProceeds\nPaid";
+ title_map["EOYDeathBft_Current" ] = "Curr EOY\nDeath\nBenefit";
+ title_map["EOYDeathBft_Guaranteed" ] = "Guar EOY\nDeath\nBenefit";
+ title_map["EeGrossPmt" ] = "EE Gross\nPayment";
+ title_map["EeModalMinimumPremium" ] = "EE
Modal\nMinimum\nPremium";
+ title_map["EeMode" ] = "EE\nPayment\nMode";
+// TODO ?? This can't be a mode. I don't know how it differs from 'EeGrossPmt'
above.
+ title_map["EePmt" ] = "EE\nPayment\nMode";
+ title_map["ErGrossPmt" ] = "ER Gross\nPayment";
+ title_map["ErModalMinimumPremium" ] = "ER
Modal\nMinimum\nPremium";
+ title_map["ErMode" ] = "ER\nPayment\nMode";
+// TODO ?? This can't be a mode. I don't know how it differs from 'ErGrossPmt'
above.
+ title_map["ErPmt" ] = "ER\nPayment\nMode";
+ title_map["ExpenseCharges_Current" ] = "Curr\nExpense\nCharge";
+ title_map["ExpenseCharges_Guaranteed" ] = "Guar\nExpense\nCharge";
+ title_map["ExperienceReserve_Current" ] =
"Experience\nRating\nReserve";
+ title_map["GptForceout" ] = "Forceout";
+ title_map["GrossIntCredited_Current" ] = "Curr
Gross\nInt\nCredited";
+ title_map["GrossIntCredited_Guaranteed" ] = "Guar
Gross\nInt\nCredited";
+ title_map["GrossPmt" ] = "Premium\nOutlay";
+ title_map["HoneymoonValueSpread" ] = "Honeymoon\nValue\nSpread";
+ title_map["IndvTaxBracket" ] = "EE Tax\nBracket";
+ title_map["InforceLives" ] = "BOY\nLives\nInforce";
+ title_map["IrrCsv_Current" ] = "Curr IRR\non CSV";
+ title_map["IrrCsv_Guaranteed" ] = "Guar IRR\non CSV";
+ title_map["IrrDb_Current" ] = "Curr IRR\non DB";
+ title_map["IrrDb_Guaranteed" ] = "Guar IRR\non DB";
+ title_map["KFactor_Current" ] = "Experience\nRating\nK
Factor";
+ title_map["LoanIntAccrued_Current" ] = "Curr Loan\nInt\nAccrued";
+ title_map["LoanIntAccrued_Guaranteed" ] = "Guar Loan\nInt\nAccrued";
+ title_map["MlyGAIntRate_Current" ] = "Curr Monthly\nGen
Acct\nInt Rate";
+ title_map["MlyGAIntRate_Guaranteed" ] = "Guar Monthly\nGen
Acct\nInt Rate";
+ title_map["MlyHoneymoonValueRate_Current" ] = "Curr
Monthly\nHoneymoon\nValue Rate";
+ title_map["MlyHoneymoonValueRate_Guaranteed"] = "Guar
Monthly\nHoneymoon\nValue Rate";
+ title_map["MlyPostHoneymoonRate_Current" ] = "Curr
Monthly\nPost\nHoneymoon\nRate";
+ title_map["MlyPostHoneymoonRate_Guaranteed" ] = "Guar
Monthly\nPost\nHoneymoon\nRate";
+ title_map["MlySAIntRate_Current" ] = "Curr Monthly\nSep
Acct\nInt Rate";
+ title_map["MlySAIntRate_Guaranteed" ] = "Guar Monthly\nSep
Acct\nInt Rate";
+ title_map["ModalMinimumPremium" ] = "Modal\nMinimum\nPremium";
+ title_map["AnnualFlatExtra" ] = "Annual\nFlat\nExtra";
+// title_map["NaarForceout" ] = "Forced\nWithdrawal\ndue
to\nNAAR Limit";
+ title_map["NetCOICharge_Current" ] = "Experience\nRating\nNet
COI\nCharge";
+ title_map["NetClaims_Current" ] = "Curr Net\nClaims";
+ title_map["NetClaims_Guaranteed" ] = "Guar Net\nClaims";
+ title_map["NetIntCredited_Current" ] = "Curr Net\nInt\nCredited";
+ title_map["NetIntCredited_Guaranteed" ] = "Guar Net\nInt\nCredited";
+ title_map["NetPmt_Current" ] = "Curr Net\nPayment";
+ title_map["NetPmt_Guaranteed" ] = "Guar Net\nPayment";
+ title_map["NetWD" ] = "Withdrawal";
+ title_map["NewCashLoan" ] = "Annual Loan";
+ title_map["Outlay" ] = "Net Outlay";
+ title_map["PartMortTableMult" ] =
"Partial\nMortality\nMuliplier";
+ title_map["PolicyFee_Current" ] = "Curr\nPolicy\nFee";
+ title_map["PolicyFee_Guaranteed" ] = "Guar\nPolicy\nFee";
+ title_map["PolicyYear" ] = "Policy\nYear";
+ title_map["PrefLoanBalance_Current" ] = "Curr\nPreferred\nLoan
Bal";
+ title_map["PrefLoanBalance_Guaranteed" ] = "Guar\nPreferred\nLoan
Bal";
+ title_map["PremTaxLoad_Current" ] = "Curr\nPremium\nTax Load";
+ title_map["PremTaxLoad_Guaranteed" ] = "Guar\nPremium\nTax Load";
+// Excluded because it's unimplemented:
+// title_map["ProducerCompensation" ] = "Producer\nCompensation";
+ title_map["ProjectedCoiCharge_Current" ] =
"Experience\nRating\nProjected\nCOI Charge";
+ title_map["RefundableSalesLoad" ] = "Refundable\nSales\nLoad";
+ title_map["RiderCharges_Current" ] = "Curr Rider\nCharges";
+ title_map["Salary" ] = "Salary";
+ title_map["SepAcctCharges_Current" ] = "Curr Sep\nAcct\nCharges";
+ title_map["SepAcctCharges_Guaranteed" ] = "Guar Sep\nAcct\nCharges";
+ title_map["SpecAmt" ] = "Specified\nAmount";
+ title_map["SpecAmtLoad_Current" ] = "Curr Spec\nAmt Load";
+ title_map["SpecAmtLoad_Guaranteed" ] = "Guar Spec\nAmt Load";
+ title_map["SurrChg_Current" ] = "Curr Surr\nCharge";
+ title_map["SurrChg_Guaranteed" ] = "Guar Surr\nCharge";
+ title_map["TermPurchased_Current" ] = "Curr
Term\nAmt\nPurchased";
+ title_map["TermPurchased_Guaranteed" ] = "Guar
Term\nAmt\nPurchased";
+ title_map["TermSpecAmt" ] = "Term\nSpecified\nAmount";
+ title_map["TgtPrem" ] = "Target\nPremium";
+ title_map["TotalIMF" ] = "Total\nInvestment\nMgt
Fee";
+ title_map["TotalLoanBalance_Current" ] = "Curr
Total\nLoan\nBalance";
+ title_map["TotalLoanBalance_Guaranteed" ] = "Guar
Total\nLoan\nBalance";
+
+ // TODO ?? Titles ought to be read from an external file that
+ // permits flexible customization. Compliance might require that
+ // 'AcctVal_Current' be called "Cash Value" for one policy form,
+ // and "Account Value" for another, in order to match the terms
+ // used in the contract exactly. Therefore, these titles probably
+ // belong in the product database, which permits variation by
+ // product--though it does not accommodate strings as this is
+ // written in 2006-07. DATABASE !! So consider adding them there
+ // when the database is revamped.
+
+// Here's my top-level analysis of the formatting specification.
+//
+// Formats
+//
+// F0: zero decimals
+// F1: zero decimals, commas
+// F2: two decimals, commas
+// F3: scaled by 100, zero decimals, with '%' at end:
+// F4: scaled by 100, two decimals, with '%' at end:
+//
+// Presumably all use commas as thousands-separators, so that
+// an IRR of 12345.67% would be formatted as "12,345.67%".
+//
+// So the differences are:
+// 'precision' (number of decimal places)
+// percentage (scaled by 100, '%' at end) or not
+// and therefore F0 is equivalent to F1
+
+ std::pair<int,oenum_format_style> f1(0, oe_format_normal);
+ std::pair<int,oenum_format_style> f2(2, oe_format_normal);
+ std::pair<int,oenum_format_style> f3(0, oe_format_percentage);
+ std::pair<int,oenum_format_style> f4(2, oe_format_percentage);
+
+ format_map_t format_map;
+
+// > Special Formatting for Scalar Items
+// >
+// F4: scaled by 100, two decimals, with '%' at end:
+// > Format as percentage "0.00%"
+// >
+ format_map["GuarMaxMandE" ] = f4;
+ format_map["InitAnnGenAcctInt" ] = f4;
+ format_map["InitAnnLoanCredRate" ] = f4;
+ format_map["InitAnnLoanDueRate" ] = f4;
+ format_map["InitAnnSepAcctCurrGross0Rate" ] = f4;
+ format_map["InitAnnSepAcctCurrGrossHalfRate" ] = f4;
+ format_map["InitAnnSepAcctCurrNet0Rate" ] = f4;
+ format_map["InitAnnSepAcctCurrNetHalfRate" ] = f4;
+ format_map["InitAnnSepAcctGrossInt" ] = f4;
+ format_map["InitAnnSepAcctGuarGross0Rate" ] = f4;
+ format_map["InitAnnSepAcctGuarGrossHalfRate" ] = f4;
+ format_map["InitAnnSepAcctGuarNet0Rate" ] = f4;
+ format_map["InitAnnSepAcctGuarNetHalfRate" ] = f4;
+ format_map["InitAnnSepAcctNetInt" ] = f4;
+ format_map["PostHoneymoonSpread" ] = f4;
+ format_map["Preferred" ] = f4;
+ format_map["PremTaxRate" ] = f4;
+
+// F3: scaled by 100, zero decimals, with '%' at end:
+// > Format as percentage with no decimal places (##0%)
+ format_map["SalesLoadRefundRate0" ] = f3;
+ format_map["SalesLoadRefundRate1" ] = f3;
+ format_map["GenAcctAllocationPercent" ] = f3;
+ format_map["GenAcctAllocationComplementPercent"] = f3;
+
+// >
+// F2: two decimals, commas
+// > Format as a number with thousand separators and two decimal places
(#,###,###.00)
+// >
+ format_map["CurrentCoiMultiplier" ] = f2;
+ format_map["EeListBillPremium" ] = f2;
+ format_map["ErListBillPremium" ] = f2;
+ format_map["GuarPrem" ] = f2;
+ format_map["InforceTaxBasis" ] = f2;
+ format_map["InforceUnloanedAV" ] = f2;
+ format_map["InitGLP" ] = f2;
+ format_map["InitGSP" ] = f2;
+ format_map["InitPrem" ] = f2;
+ format_map["InitSevenPayPrem" ] = f2;
+ format_map["InitTgtPrem" ] = f2;
+ format_map["InitMinPrem" ] = f2;
+ format_map["ListBillPremium" ] = f2;
+ format_map["ModalMinimumDumpin" ] = f2;
+// >
+// F1: zero decimals, commas
+// > Format as a number with thousand separators and no decimal places
(#,###,###)
+// >
+ format_map["Age" ] = f1;
+ format_map["AllowDbo3" ] = f1;
+ format_map["AvgFund" ] = f1;
+ format_map["ChildRiderAmount" ] = f1;
+ format_map["CustomFund" ] = f1;
+ format_map["Dumpin" ] = f1;
+ format_map["EndtAge" ] = f1;
+ format_map["External1035Amount" ] = f1;
+ format_map["GenAcctAllocation" ] = f1;
+ format_map["GenderBlended" ] = f1;
+ format_map["GenderDistinct" ] = f1;
+ format_map["Has1035ExchCharge" ] = f1;
+ format_map["HasADD" ] = f1;
+ format_map["HasChildRider" ] = f1;
+ format_map["HasHoneymoon" ] = f1;
+ format_map["HasSalesLoadRefund" ] = f1;
+ format_map["HasSpouseRider" ] = f1;
+ format_map["HasSupplSpecAmt" ] = f1;
+ format_map["HasTerm" ] = f1;
+ format_map["HasWP" ] = f1;
+ format_map["InforceIsMec" ] = f1;
+ format_map["InforceMonth" ] = f1;
+ format_map["InforceYear" ] = f1;
+ format_map["InitBaseSpecAmt" ] = f1;
+ format_map["InitTermSpecAmt" ] = f1;
+ format_map["InitTotalSA" ] = f1;
+ format_map["Internal1035Amount" ] = f1;
+ format_map["IsInforce" ] = f1;
+ format_map["IsMec" ] = f1;
+ format_map["LapseMonth" ] = f1;
+ format_map["LapseYear" ] = f1;
+ format_map["MaxDuration" ] = f1;
+ format_map["MecMonth" ] = f1;
+ format_map["MecYear" ] = f1;
+ format_map["NoLapse" ] = f1;
+ format_map["NoLapseAlwaysActive" ] = f1;
+ format_map["NoLapseMinAge" ] = f1;
+ format_map["NoLapseMinDur" ] = f1;
+ format_map["RetAge" ] = f1;
+ format_map["SmokerBlended" ] = f1;
+ format_map["SmokerDistinct" ] = f1;
+ format_map["SplitFundAllocation" ] = f1;
+ format_map["SplitMinPrem" ] = f1;
+ format_map["SpouseIssueAge" ] = f1;
+ format_map["SupplementalReport" ] = f1;
+ format_map["UseExperienceRating" ] = f1;
+ format_map["GroupIndivSelection" ] = f1;
+ format_map["UsePartialMort" ] = f1;
+
+// > Vector Formatting
+// >
+// > Here are the vectors enumerated
+// >
+// F3: scaled by 100, zero decimals, with '%' at end:
+// > Format as percentage with no decimal places (##0%)
+// >
+ format_map["CorridorFactor" ] = f3;
+ format_map["FundAllocations" ] = f3;
+ format_map["MaleProportion" ] = f3;
+ format_map["NonsmokerProportion" ] = f3;
+ format_map["PartMortTableMult" ] = f3;
+
+// >
+// F4: scaled by 100, two decimals, with '%' at end:
+// > Format as percentage with two decimal places (##0.00%)
+// >
+ format_map["AnnGAIntRate" ] = f4;
+ format_map["AnnHoneymoonValueRate" ] = f4;
+ format_map["AnnPostHoneymoonRate" ] = f4;
+ format_map["AnnSAIntRate" ] = f4;
+ format_map["CashFlowIRR" ] = f4;
+ format_map["CorpTaxBracket" ] = f4;
+ format_map["CurrMandE" ] = f4;
+ format_map["HoneymoonValueSpread" ] = f4;
+ format_map["IndvTaxBracket" ] = f4;
+ format_map["InforceHMVector" ] = f4;
+
+ format_map["IrrCsv_Current" ] = f4;
+ format_map["IrrCsv_CurrentZero" ] = f4;
+ format_map["IrrCsv_Guaranteed" ] = f4;
+ format_map["IrrCsv_GuaranteedZero" ] = f4;
+ format_map["IrrDb_Current" ] = f4;
+ format_map["IrrDb_CurrentZero" ] = f4;
+ format_map["IrrDb_Guaranteed" ] = f4;
+ format_map["IrrDb_GuaranteedZero" ] = f4;
+
+ format_map["MlyGAIntRate" ] = f4;
+ format_map["MlyHoneymoonValueRate" ] = f4;
+ format_map["MlyPostHoneymoonRate" ] = f4;
+ format_map["MlySAIntRate" ] = f4;
+ format_map["TotalIMF" ] = f4;
+// >
+// F0: zero decimals
+// > Format as a number no thousand separator or decimal point (##0%)
+// >
+ format_map["AttainedAge" ] = f1;
+ format_map["Duration" ] = f1;
+ format_map["LapseYears" ] = f1;
+ format_map["PolicyYear" ] = f1;
+// >
+// F2: two decimals, commas
+// > Format as a number with thousand separators and two decimal places
(#,###,###.00)
+// >
+ format_map["AddonMonthlyFee" ] = f2;
+// TODO ?? The precision of 'InforceLives' and 'KFactor' is inadequate.
+// Is every other format OK?
+ format_map["InforceLives" ] = f2;
+ format_map["KFactor" ] = f2;
+ format_map["AnnualFlatExtra" ] = f2;
+// >
+// F1: zero decimals, commas
+// > Format as a number with thousand separators and no decimal places
(#,###,##0)
+// >
+ format_map["AcctVal" ] = f1;
+ format_map["AccumulatedPremium" ] = f1;
+ format_map["AddonCompOnAssets" ] = f1;
+ format_map["AddonCompOnPremium" ] = f1;
+ format_map["AvgDeathBft" ] = f1;
+ format_map["AVGenAcct" ] = f1;
+ format_map["AVRelOnDeath" ] = f1;
+ format_map["AVSepAcct" ] = f1;
+ format_map["BaseDeathBft" ] = f1;
+ format_map["BOYAssets" ] = f1;
+ format_map["ClaimsPaid" ] = f1;
+ format_map["COICharge" ] = f1;
+ format_map["Composite" ] = f1;
+ format_map["CSVNet" ] = f1;
+ format_map["CV7702" ] = f1;
+ format_map["DacTaxLoad" ] = f1;
+ format_map["DacTaxRsv" ] = f1;
+ format_map["DeathProceedsPaid" ] = f1;
+ format_map["EeGrossPmt" ] = f1;
+ format_map["EeModalMinimumPremium" ] = f1;
+// format_map["EeMode" ] = f1; // Not numeric.
+ format_map["EePmt" ] = f1;
+ format_map["EOYDeathBft" ] = f1;
+ format_map["ErGrossPmt" ] = f1;
+ format_map["ErModalMinimumPremium" ] = f1;
+// format_map["ErMode" ] = f1; // Not numeric.
+ format_map["ErPmt" ] = f1;
+ format_map["ExpenseCharges" ] = f1;
+ format_map["ExperienceReserve" ] = f1;
+ format_map["FundNumbers" ] = f1;
+ format_map["GptForceout" ] = f1;
+ format_map["GrossIntCredited" ] = f1;
+ format_map["GrossPmt" ] = f1;
+ format_map["Loads" ] = f1;
+ format_map["LoanInt" ] = f1;
+ format_map["LoanIntAccrued" ] = f1;
+ format_map["ModalMinimumPremium" ] = f1;
+ format_map["NaarForceout" ] = f1;
+ format_map["NetClaims" ] = f1;
+ format_map["NetCOICharge" ] = f1;
+ format_map["NetIntCredited" ] = f1;
+ format_map["NetPmt" ] = f1;
+ format_map["NetWD" ] = f1;
+ format_map["NewCashLoan" ] = f1;
+ format_map["Outlay" ] = f1;
+ format_map["PolicyFee" ] = f1;
+ format_map["PrefLoanBalance" ] = f1;
+ format_map["PremTaxLoad" ] = f1;
+ format_map["ProducerCompensation" ] = f1;
+ format_map["ProjectedCoiCharge" ] = f1;
+ format_map["RefundableSalesLoad" ] = f1;
+ format_map["RiderCharges" ] = f1;
+ format_map["Salary" ] = f1;
+ format_map["SepAcctCharges" ] = f1;
+ format_map["SpecAmt" ] = f1;
+ format_map["SpecAmtLoad" ] = f1;
+ format_map["SpouseRiderAmount" ] = f1;
+ format_map["SurrChg" ] = f1;
+ format_map["TermPurchased" ] = f1;
+ format_map["TermSpecAmt" ] = f1;
+ format_map["TgtPrem" ] = f1;
+ format_map["TotalLoanBalance" ] = f1;
+
+ // This is a little tricky. We have some stuff that
+ // isn't in the maps inside the ledger classes. We're going to
+ // stuff it into a copy of the invariant-ledger class's data.
+ // To avoid copying, we'll use pointers to the data. Most of
+ // this stuff is invariant anyway, so that's a reasonable
+ // place to put it.
+ //
+ // First we make a copy of the invariant ledger:
+
+ double_vector_map vectors = ledger_invariant_->AllVectors;
+ scalar_map scalars = ledger_invariant_->AllScalars;
+ string_map strings = ledger_invariant_->Strings;
+
+ // Now we add the stuff that wasn't in the invariant
+ // ledger's class's maps (indexable by name). Because we're
+ // working with maps of pointers, we need pointers here.
+ //
+ // The IRRs are the worst of all.
+
+ if(!ledger_invariant_->IsInforce)
+ {
+ ledger_invariant_->CalculateIrrs(*this);
+ }
+ vectors["IrrCsv_GuaranteedZero" ] = &ledger_invariant_->IrrCsvGuar0 ;
+ vectors["IrrDb_GuaranteedZero" ] = &ledger_invariant_->IrrDbGuar0 ;
+ vectors["IrrCsv_CurrentZero" ] = &ledger_invariant_->IrrCsvCurr0 ;
+ vectors["IrrDb_CurrentZero" ] = &ledger_invariant_->IrrDbCurr0 ;
+ vectors["IrrCsv_Guaranteed" ] = &ledger_invariant_->IrrCsvGuarInput;
+ vectors["IrrDb_Guaranteed" ] = &ledger_invariant_->IrrDbGuarInput ;
+ vectors["IrrCsv_Current" ] = &ledger_invariant_->IrrCsvCurrInput;
+ vectors["IrrDb_Current" ] = &ledger_invariant_->IrrDbCurrInput ;
+
+// GetMaxLength() is max *composite* length.
+// int max_length = GetMaxLength();
+ double MaxDuration = ledger_invariant_->EndtAge - ledger_invariant_->Age;
+ scalars["MaxDuration"] = &MaxDuration;
+ int max_duration = static_cast<int>(MaxDuration);
+
+ std::vector<double> PolicyYear;
+ std::vector<double> AttainedAge;
+
+ PolicyYear .resize(max_duration);
+ AttainedAge.resize(max_duration);
+
+ int issue_age = static_cast<int>(ledger_invariant_->Age);
+ for(int j = 0; j < max_duration; ++j)
+ {
+ PolicyYear[j] = 1 + j;
+ AttainedAge[j] = 1 + j + issue_age;
+ }
+
+// TODO ?? An attained-age column is meaningless in a composite. So
+// are several others--notably those affected by partial mortaility.
+ vectors["AttainedAge"] = &AttainedAge;
+ vectors["PolicyYear" ] = &PolicyYear ;
+
+ std::vector<double> InitAnnLoanDueRate(max_duration);
+ std::fill
+ (InitAnnLoanDueRate.begin()
+ ,InitAnnLoanDueRate.end()
+ ,ledger_invariant_->GetInitAnnLoanDueRate()
+ );
+ vectors["InitAnnLoanDueRate"] = &InitAnnLoanDueRate;
+
+ vectors["InforceLives"] = &ledger_invariant_->InforceLives;
+
+ vectors["FundNumbers" ] = &ledger_invariant_->FundNumbers ;
+ vectors["FundAllocations"] = &ledger_invariant_->FundAllocations;
+
+ // The Ledger object should contain a basic minimal set of columns
+ // from which others may be derived. It must be kept small because
+ // its size imposes a practical limit on the number of lives that
+ // can be run as part of a single census.
+ //
+ // TODO ?? A really good design would give users the power to
+ // define and store their own derived-column definitions. For now,
+ // however, code changes are required, and this is as appropriate
+ // a place as any to make them.
+
+ LedgerInvariant const& Invar = GetLedgerInvariant();
+ LedgerVariant const& Curr_ = GetCurrFull();
+ LedgerVariant const& Guar_ = GetGuarFull();
+
+ std::vector<double> PremiumLoads(max_duration);
+ std::vector<double> AdminCharges(max_duration);
+ for(int j = 0; j < max_duration; ++j)
+ {
+ PremiumLoads[j] = Invar.GrossPmt[j] - Curr_.NetPmt[j];
+ AdminCharges[j] = Curr_.SpecAmtLoad[j] + Curr_.PolicyFee[j];
+ }
+
+ vectors ["PremiumLoads"] = &PremiumLoads;
+ format_map["PremiumLoads"] = f1;
+ vectors ["AdminCharges"] = &AdminCharges;
+ format_map["AdminCharges"] = f1;
+
+ // ET !! Easier to write as
+ // std::vector<double> NetDeathBenefit =
+ // Curr_.EOYDeathBft - Curr_.TotalLoanBalance;
+ std::vector<double> NetDeathBenefit(Curr_.EOYDeathBft);
+ std::transform
+ (NetDeathBenefit.begin()
+ ,NetDeathBenefit.end()
+ ,Curr_.TotalLoanBalance.begin()
+ ,NetDeathBenefit.begin()
+ ,std::minus<double>()
+ );
+ vectors ["NetDeathBenefit"] = &NetDeathBenefit;
+ title_map ["NetDeathBenefit"] = "Net\nDeath\nBenefit";
+ format_map["NetDeathBenefit"] = f1;
+
+ std::vector<double> SupplDeathBft_Current (Curr_.TermPurchased);
+ std::vector<double> SupplDeathBft_Guaranteed(Guar_.TermPurchased);
+ vectors ["SupplDeathBft_Current" ] = &SupplDeathBft_Current;
+ vectors ["SupplDeathBft_Guaranteed"] = &SupplDeathBft_Guaranteed;
+ title_map ["SupplDeathBft_Current" ] = "Curr Suppl\nDeath\nBenefit";
+ title_map ["SupplDeathBft_Guaranteed"] = "Guar Suppl\nDeath\nBenefit";
+ format_map["SupplDeathBft_Current" ] = f1;
+ format_map["SupplDeathBft_Guaranteed"] = f1;
+
+ std::vector<double> SupplSpecAmt(Invar.TermSpecAmt);
+ vectors ["SupplSpecAmt" ] = &SupplSpecAmt;
+ title_map ["SupplSpecAmt" ] = "Suppl\nSpecified\nAmount";
+ format_map["SupplSpecAmt" ] = f1;
+
+ // [End of derived columns.]
+
+ double Composite = is_composite();
+ scalars["Composite"] = &Composite;
+
+ double NoLapse =
+ 0 != ledger_invariant_->NoLapseMinDur
+ || 0 != ledger_invariant_->NoLapseMinAge
+ ;
+ scalars["NoLapse"] = &NoLapse;
+
+ std::string LmiVersion(LMI_VERSION);
+ calendar_date prep_date;
+
+ // Skip authentication for non-interactive regression testing.
+ if(!global_settings::instance().regression_testing())
+ {
+ authenticate_system();
+ }
+ else
+ {
+ // For regression tests,
+ // - use an invariant string as version
+ // - use EffDate as date prepared
+ // in order to avoid gratuitous failures.
+ LmiVersion = "Regression testing";
+
prep_date.julian_day_number(static_cast<int>(ledger_invariant_->EffDateJdn));
+ }
+
+ strings["LmiVersion"] = &LmiVersion;
+
+ std::string PrepYear = value_cast<std::string>(prep_date.year());
+ std::string PrepMonth = month_name(prep_date.month());
+ std::string PrepDay = value_cast<std::string>(prep_date.day());
+
+ strings["PrepYear" ] = &PrepYear;
+ strings["PrepMonth"] = &PrepMonth;
+ strings["PrepDay" ] = &PrepDay;
+
+ double HasSalesLoadRefund =
+ !each_equal(ledger_invariant_->RefundableSalesLoad, 0.0);
+ double SalesLoadRefundRate0 = ledger_invariant_->RefundableSalesLoad[0];
+ double SalesLoadRefundRate1 = ledger_invariant_->RefundableSalesLoad[1];
+
+ scalars["HasSalesLoadRefund" ] = &HasSalesLoadRefund ;
+ scalars["SalesLoadRefundRate0"] = &SalesLoadRefundRate0;
+ scalars["SalesLoadRefundRate1"] = &SalesLoadRefundRate1;
+
+ double GenAcctAllocation = ledger_invariant_->GenAcctAllocation;
+ double GenAcctAllocationComplement = 1. - GenAcctAllocation;
+
+ scalars["GenAcctAllocationPercent" ] = &GenAcctAllocation;
+ scalars["GenAcctAllocationComplementPercent"] =
&GenAcctAllocationComplement;
+
+ std::string ScaleUnit = ledger_invariant_->ScaleUnit();
+ strings["ScaleUnit"] = &ScaleUnit;
+
+ double InitTotalSA =
+ ledger_invariant_->InitBaseSpecAmt
+ + ledger_invariant_->InitTermSpecAmt
+ ;
+ scalars["InitTotalSA"] = &InitTotalSA;
+
+ // Maps to hold the results of formatting numeric data.
+
+ std::unordered_map<std::string, std::string> stringscalars;
+ std::unordered_map<std::string, std::vector<std::string>> stringvectors;
+
+ stringvectors["FundNames"] = ledger_invariant_->FundNames;
+
+ // Map the data, formatting it as necessary.
+
+ // First we'll get the invariant stuff--the copy we made,
+ // along with all the stuff we plugged into it above.
+ {
+ std::string suffix = "";
+ for(auto const& j : scalars)
+ {
+ if(format_exists(j.first, suffix, format_map))
+ stringscalars[j.first + suffix] = ledger_format(*j.second,
format_map[j.first]);
+ }
+ for(auto const& j : strings)
+ {
+ stringscalars[j.first + suffix] = *j.second;
+ }
+ for(auto const& j : vectors)
+ {
+ if(format_exists(j.first, suffix, format_map))
+ stringvectors[j.first + suffix] = ledger_format(*j.second,
format_map[j.first]);
+ }
+ }
+
+// stringscalars["GuarMaxMandE"] = ledger_format(*scalars["GuarMaxMandE"],
2, true);
+// stringvectors["CorridorFactor"] =
ledger_format(*vectors["CorridorFactor"], 0, true);
+// stringscalars["InitAnnGenAcctInt_Current"] =
ledger_format(*scalars["InitAnnGenAcctInt_Current"], 0, true);
+
+ // That was the tricky part. Now it's all downhill.
+
+ for(auto const& i : ledger_map_->held())
+ {
+ std::string suffix = suffixes[i.first];
+ for(auto const& j : i.second.AllScalars)
+ {
+// scalars[j.first + suffix] = j.second;
+ if(format_exists(j.first, suffix, format_map))
+ stringscalars[j.first + suffix] = ledger_format(*j.second,
format_map[j.first]);
+ }
+ for(auto const& j : i.second.Strings)
+ {
+ strings[j.first + suffix] = j.second;
+ }
+ for(auto const& j : i.second.AllVectors)
+ {
+// vectors[j.first + suffix] = j.second;
+ if(format_exists(j.first, suffix, format_map))
+ stringvectors[j.first + suffix] = ledger_format(*j.second,
format_map[j.first]);
+ }
+ }
+
+ stringvectors["EeMode"] =
mc_e_vector_to_string_vector(ledger_invariant_->EeMode);
+ stringvectors["ErMode"] =
mc_e_vector_to_string_vector(ledger_invariant_->ErMode);
+ stringvectors["DBOpt"] =
mc_e_vector_to_string_vector(ledger_invariant_->DBOpt );
+
+// TODO ?? Here I copied some stuff from the ledger class files: the
+// parts that speak of odd members that aren't in those class's
+// maps. This may reveal incomplete or incorrect systems analysis.
+
+// Invariant
+//
+// // Special-case vectors (not <double>, or different length than others).
+// EeMode .reserve(Length);
+// ErMode .reserve(Length);
+// DBOpt .reserve(Length);
+//
+// std::vector<int> FundNumbers; [not handled yet]
+// std::vector<std::string> FundNames; [not handled yet]
+// std::vector<int> FundAllocs; [not handled yet]
+//
+// std::vector<double> InforceLives;
+//
+// // Special-case strings.
+// std::string EffDate; [furnished as PrepYear, PrepMonth, PrepDay]
+//
+// Variant
+//
+// [None of these are stored, and I think none is wanted.]
+//
+// // special cases
+// int Length;
+// mcenum_gen_basis GenBasis_;
+// mcenum_sep_basis SepBasis_;
+// bool FullyInitialized; // I.e. by Init(BasicValues const*
b)
+
+ if(ledger_invariant_->SupplementalReport)
+ {
+ std::vector<std::string> SupplementalReportColumns;
+
SupplementalReportColumns.push_back(ledger_invariant_->SupplementalReportColumn00);
+
SupplementalReportColumns.push_back(ledger_invariant_->SupplementalReportColumn01);
+
SupplementalReportColumns.push_back(ledger_invariant_->SupplementalReportColumn02);
+
SupplementalReportColumns.push_back(ledger_invariant_->SupplementalReportColumn03);
+
SupplementalReportColumns.push_back(ledger_invariant_->SupplementalReportColumn04);
+
SupplementalReportColumns.push_back(ledger_invariant_->SupplementalReportColumn05);
+
SupplementalReportColumns.push_back(ledger_invariant_->SupplementalReportColumn06);
+
SupplementalReportColumns.push_back(ledger_invariant_->SupplementalReportColumn07);
+
SupplementalReportColumns.push_back(ledger_invariant_->SupplementalReportColumn08);
+
SupplementalReportColumns.push_back(ledger_invariant_->SupplementalReportColumn09);
+
SupplementalReportColumns.push_back(ledger_invariant_->SupplementalReportColumn10);
+
SupplementalReportColumns.push_back(ledger_invariant_->SupplementalReportColumn11);
+
+ // Eventually customize the report name.
+ stringscalars["SupplementalReportTitle"] = "Supplemental Report";
+
+ std::vector<std::string> SupplementalReportColumnsTitles;
+
SupplementalReportColumnsTitles.reserve(SupplementalReportColumns.size());
+
+ for(auto const& j : SupplementalReportColumns)
+ {
+ SupplementalReportColumnsTitles.push_back(title_map[j]);
+ }
+
+ stringvectors["SupplementalReportColumnsNames"] =
std::move(SupplementalReportColumns);
+ stringvectors["SupplementalReportColumnsTitles"] =
std::move(SupplementalReportColumnsTitles);
+ }
+
+ return ledger_evaluator(std::move(stringscalars),
std::move(stringvectors));
+}
diff --git a/ledger_evaluator.hpp b/ledger_evaluator.hpp
new file mode 100644
index 0000000..23bc7cc
--- /dev/null
+++ b/ledger_evaluator.hpp
@@ -0,0 +1,59 @@
+// Ledger evaluator returning values of all ledger fields.
+//
+// Copyright (C) 2017, 2018 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#ifndef ledger_evaluator_hpp
+#define ledger_evaluator_hpp
+
+#include "config.hpp"
+
+#include "so_attributes.hpp"
+
+#include <cstddef> // size_t
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+/// Class allowing to retrieve the string representation of any scalar or
+/// vector stored in a ledger.
+class LMI_SO ledger_evaluator
+{
+ public:
+ std::string operator()(std::string const& scalar) const;
+ std::string operator()(std::string const& vector, std::size_t index) const;
+
+ private:
+ using all_scalars = std::unordered_map<std::string, std::string
>;
+ using all_vectors =
std::unordered_map<std::string,std::vector<std::string>>;
+
+ // Objects of this class can only be created by Ledger::make_evaluator().
+ ledger_evaluator(all_scalars&& scalars, all_vectors&& vectors)
+ :scalars_(scalars)
+ ,vectors_(vectors)
+ {
+ }
+
+ all_scalars const scalars_;
+ all_vectors const vectors_;
+
+ friend class Ledger;
+};
+
+#endif // ledger_evaluator_hpp
diff --git a/ledger_pdf.cpp b/ledger_pdf.cpp
new file mode 100644
index 0000000..d9703ec
--- /dev/null
+++ b/ledger_pdf.cpp
@@ -0,0 +1,56 @@
+// Ledger PDF generation.
+//
+// Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013,
2014, 2015, 2016, 2017, 2018 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#include "pchfile.hpp"
+
+#include "ledger_pdf.hpp"
+
+#include "configurable_settings.hpp"
+#include "global_settings.hpp" // PDF !! expunge
+#include "ledger.hpp"
+#include "ledger_pdf_generator.hpp"
+#include "ledger_xsl.hpp" // PDF !! expunge
+#include "path_utility.hpp"
+
+/// Write ledger as pdf.
+
+std::string write_ledger_as_pdf(Ledger const& ledger, fs::path const& filepath)
+{
+ // PDF !! Expunge this conditional block:
+ if(global_settings::instance().ash_nazg())
+ {
+ // Execute both the new and the old code so that their results
+ // may be compared.
+ write_ledger_as_pdf_via_xsl(ledger, filepath);
+ }
+
+ throw_if_interdicted(ledger);
+
+ fs::path print_dir(configurable_settings::instance().print_directory());
+ // PDF !! Either orthodox_filename() should be used here, or its
+ // use should be reconsidered everywhere else.
+ fs::path pdf_out_file = unique_filepath(print_dir / filepath, ".pdf");
+
+ auto const pdf = ledger_pdf_generator::create();
+ pdf->write(ledger, pdf_out_file);
+
+ return pdf_out_file.string();
+}
diff --git a/ledger_xsl.hpp b/ledger_pdf.hpp
similarity index 78%
copy from ledger_xsl.hpp
copy to ledger_pdf.hpp
index dd2af40..5158f54 100644
--- a/ledger_xsl.hpp
+++ b/ledger_pdf.hpp
@@ -1,6 +1,6 @@
-// Ledger xsl operations.
+// Ledger PDF generation.
//
-// Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013,
2014, 2015, 2016, 2017, 2018 Gregory W. Chicares.
+// Copyright (C) 2017, 2018 Gregory W. Chicares.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@@ -19,8 +19,8 @@
// email: <address@hidden>
// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
-#ifndef ledger_xsl_hpp
-#define ledger_xsl_hpp
+#ifndef ledger_pdf_hpp
+#define ledger_pdf_hpp
#include "config.hpp"
@@ -32,7 +32,4 @@ class Ledger;
std::string write_ledger_as_pdf(Ledger const&, fs::path const&);
-fs::path xsl_filepath(Ledger const&);
-
-#endif // ledger_xsl_hpp
-
+#endif // ledger_pdf_hpp
diff --git a/ledger_xsl.hpp b/ledger_pdf_generator.cpp
similarity index 53%
copy from ledger_xsl.hpp
copy to ledger_pdf_generator.cpp
index dd2af40..54f6543 100644
--- a/ledger_xsl.hpp
+++ b/ledger_pdf_generator.cpp
@@ -1,6 +1,6 @@
-// Ledger xsl operations.
+// Generate PDF files with ledger data.
//
-// Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013,
2014, 2015, 2016, 2017, 2018 Gregory W. Chicares.
+// Copyright (C) 2017, 2018 Gregory W. Chicares.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@@ -19,20 +19,28 @@
// email: <address@hidden>
// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
-#ifndef ledger_xsl_hpp
-#define ledger_xsl_hpp
+#include "pchfile.hpp"
-#include "config.hpp"
+#include "ledger_pdf_generator.hpp"
-#include <boost/filesystem/path.hpp>
+#include "callback.hpp"
-#include <string>
+namespace
+{
+callback<ledger_pdf_generator::creator_type>
+ group_quote_pdf_generator_create_callback;
+} // Unnamed namespace.
-class Ledger;
+typedef ledger_pdf_generator::creator_type FunctionPointer;
+template<> FunctionPointer callback<FunctionPointer>::function_pointer_ =
nullptr;
-std::string write_ledger_as_pdf(Ledger const&, fs::path const&);
-
-fs::path xsl_filepath(Ledger const&);
-
-#endif // ledger_xsl_hpp
+bool ledger_pdf_generator::set_creator(creator_type f)
+{
+ group_quote_pdf_generator_create_callback.initialize(f);
+ return true;
+}
+std::shared_ptr<ledger_pdf_generator> ledger_pdf_generator::create()
+{
+ return group_quote_pdf_generator_create_callback()();
+}
diff --git a/ledger_pdf_generator.hpp b/ledger_pdf_generator.hpp
new file mode 100644
index 0000000..b047179
--- /dev/null
+++ b/ledger_pdf_generator.hpp
@@ -0,0 +1,63 @@
+// Generate PDF files with ledger data.
+//
+// Copyright (C) 2017, 2018 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#ifndef ledger_pdf_generator_hpp
+#define ledger_pdf_generator_hpp
+
+#include "config.hpp"
+
+#include "so_attributes.hpp"
+
+#include <boost/filesystem/path.hpp>
+
+#include <memory> // std::shared_ptr
+
+class Ledger;
+
+/// Abstract base class for generating PDFs with ledger data.
+///
+/// Although there is currently only a single concrete implementation of this
+/// abstract base class and no other implementations are planned, splitting the
+/// PDF generation functionality into an abstract base and the concrete derived
+/// class is still needed because the former is part of liblmi while the latter
+/// uses wxPdfDocument and other wx facilities and is only part of libskeleton.
+
+class LMI_SO ledger_pdf_generator
+{
+ public:
+ typedef std::shared_ptr<ledger_pdf_generator> (*creator_type)();
+
+ static bool set_creator(creator_type);
+ static std::shared_ptr<ledger_pdf_generator> create();
+
+ virtual ~ledger_pdf_generator() = default;
+
+ virtual void write(Ledger const& ledger, fs::path const& output) = 0;
+
+ protected:
+ ledger_pdf_generator() = default;
+
+ private:
+ ledger_pdf_generator(ledger_pdf_generator const&) = delete;
+ ledger_pdf_generator& operator=(ledger_pdf_generator const&) = delete;
+};
+
+#endif // ledger_pdf_generator_hpp
diff --git a/ledger_pdf_generator_wx.cpp b/ledger_pdf_generator_wx.cpp
new file mode 100644
index 0000000..cbaec4e
--- /dev/null
+++ b/ledger_pdf_generator_wx.cpp
@@ -0,0 +1,2940 @@
+// Generate PDF files with ledger data using wxPdfDocument library.
+//
+// Copyright (C) 2017, 2018 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#include "pchfile_wx.hpp"
+
+#include "ledger_pdf_generator.hpp"
+
+#include "alert.hpp"
+#include "assert_lmi.hpp"
+#include "authenticity.hpp"
+#include "bourn_cast.hpp"
+#include "calendar_date.hpp"
+#include "data_directory.hpp" // AddDataDir()
+#include "force_linking.hpp"
+#include "html.hpp"
+#include "interpolate_string.hpp"
+#include "istream_to_string.hpp"
+#include "ledger.hpp"
+#include "ledger_evaluator.hpp"
+#include "ledger_invariant.hpp"
+#include "ledger_variant.hpp"
+#include "miscellany.hpp" // lmi_tolower()
+#include "pdf_writer_wx.hpp"
+#include "version.hpp"
+#include "wx_table_generator.hpp"
+
+#include <wx/pdfdc.h>
+
+#include <wx/image.h>
+#include <wx/log.h>
+
+#include <wx/html/m_templ.h>
+
+#include <cstdint> // SIZE_MAX
+#include <fstream>
+#include <map>
+#include <memory>
+#include <sstream>
+#include <stdexcept>
+#include <type_traits> // std::conditional
+#include <vector>
+
+LMI_FORCE_LINKING_IN_SITU(ledger_pdf_generator_wx)
+
+namespace
+{
+
+// Colour used for lines and border in the generated illustrations.
+const wxColour HIGHLIGHT_COL(0x00, 0x2f, 0x6c);
+
+// This function is also provided in <boost/algorithm/string/predicate.hpp>,
+// but it's arguably not worth adding dependency on this Boost library just for
+// this function.
+inline
+bool starts_with(std::string const& s, char const* prefix)
+{
+ return s.compare(0, strlen(prefix), prefix) == 0;
+}
+
+// Helper enums identifying the possible {Guaranteed,Current}{Zero,}
+// combinations.
+enum class base
+ {guaranteed
+ ,current
+ };
+
+enum class interest_rate
+ {zero
+ ,non_zero
+ };
+
+// And functions to retrieve their string representation.
+std::string base_suffix(base guar_or_curr)
+{
+ switch(guar_or_curr)
+ {
+ case base::guaranteed: return "Guaranteed";
+ case base::current: return "Current" ;
+ }
+ throw "Unreachable--unknown base value";
+}
+
+std::string ir_suffix(interest_rate zero_or_not)
+{
+ switch(zero_or_not)
+ {
+ case interest_rate::zero: return "Zero";
+ case interest_rate::non_zero: return "" ;
+ }
+ throw "Unreachable--unknown interest_rate value";
+}
+
+// Helper class grouping functions for dealing with interpolating strings
+// containing variable references.
+class html_interpolator
+{
+ public:
+ // Ctor takes the object used to interpolate the variables not explicitly
+ // defined using add_variable().
+ explicit html_interpolator(ledger_evaluator&& evaluator)
+ :evaluator_(evaluator)
+ {
+ }
+
+ // This function is provided to be able to delegate to it in custom
+ // interpolation functions, but usually shouldn't be called directly, just
+ // use operator() below instead.
+ std::string interpolation_func
+ (std::string const& s
+ ,interpolate_lookup_kind kind
+ ) const
+ {
+ switch(kind)
+ {
+ case interpolate_lookup_kind::variable:
+ case interpolate_lookup_kind::section:
+ return expand_html(s).as_html();
+
+ case interpolate_lookup_kind::partial:
+ return load_partial_from_file(s);
+ }
+
+ throw std::runtime_error("invalid lookup kind");
+ }
+
+ // A method which can be used to interpolate an HTML string containing
+ // references to the variables defined for this illustration. The general
+ // syntax is the same as in the global interpolate_string() function, i.e.
+ // variables are of the form "{{name}}" and section of the form
+ // "{{#name}}..{{/name}}" or "{{^name}}..{{/name}}" are also allowed and
+ // their contents is included in the expansion if and only if the variable
+ // with the given name has value "1" for the former or "0" for the latter.
+ //
+ // The variable names recognized by this function are either those defined
+ // by ledger_evaluator, i.e. scalar and vector fields of the ledger, or any
+ // variables explicitly defined by add_variable() calls.
+ html::text operator()(char const* s) const
+ {
+ return html::text::from_html
+ (interpolate_string
+ (s
+ ,[this]
+ (std::string const& str
+ ,interpolate_lookup_kind kind
+ )
+ {
+ return interpolation_func(str, kind);
+ }
+ )
+ );
+ }
+
+ html::text operator()(std::string const& s) const
+ {
+ return (*this)(s.c_str());
+ }
+
+ // Add a variable, providing either its raw text or already escaped HTML
+ // representation. Boolean values are converted to strings "0" or "1" as
+ // expected.
+ void add_variable(std::string const& name, html::text const& value)
+ {
+ vars_[name] = value;
+ }
+
+ void add_variable(std::string const& name, std::string const& value)
+ {
+ add_variable(name, html::text::from(value));
+ }
+
+ void add_variable(std::string const& name, int value)
+ {
+ std::ostringstream oss;
+ oss << value;
+ add_variable(name, oss.str());
+ }
+
+ void add_variable(std::string const& name, bool value)
+ {
+ add_variable(name, std::string(value ? "1" : "0"));
+ }
+
+ // Detect, at compile-time, mistaken attempts to add floating point
+ // variables: all those are only available from ledger_evaluator as they
+ // must be formatted correctly.
+ void add_variable(std::string const& name, double value) = delete;
+
+ // Test a boolean variable: the value must be "0" or "1", which is mapped
+ // to false or true respectively. Anything else results in an exception.
+ bool test_variable(std::string const& name) const
+ {
+ auto const z = expand_html(name).as_html();
+ return
+ z == "1" ? true
+ : z == "0" ? false
+ : throw std::runtime_error
+ ("Variable '" + name + "' has non-boolean value '" + z + "'"
+ )
+ ;
+ }
+
+ // Return the value of a single scalar variable.
+ std::string evaluate(std::string const& name) const
+ {
+ return evaluator_(name);
+ }
+
+ // Return a single value of a vector variable.
+ std::string evaluate(std::string const& name, std::size_t index) const
+ {
+ return evaluator_(name, index);
+ }
+
+ // Interpolate the contents of the given external template.
+ //
+ // This is exactly the same as interpolating "{{>template_name}}" string
+ // but a bit more convenient to use and simpler to read.
+ html::text expand_template(std::string const& template_name) const
+ {
+ return (*this)("{{>" + template_name + "}}");
+ }
+
+ private:
+ // The expansion function used with interpolate_string().
+ html::text expand_html(std::string const& s) const
+ {
+ // Check our own variables first:
+ auto const it = vars_.find(s);
+ if(it != vars_.end())
+ {
+ return it->second;
+ }
+
+ // Then look in the ledger, either as a scalar or a vector depending on
+ // whether it has "[index]" part or not.
+ if(!s.empty() && *s.rbegin() == ']')
+ {
+ auto const open_pos = s.find('[');
+ if(open_pos == std::string::npos)
+ {
+ throw std::runtime_error
+ ("Variable '" + s + "' doesn't have the expected '['"
+ );
+ }
+
+ char* stop = nullptr;
+ auto const index = std::strtoul(s.c_str() + open_pos + 1, &stop,
10);
+
+ // Conversion must have stopped at the closing bracket character
+ // and also check for overflow (notice that index == SIZE_MAX
+ // doesn't, in theory, need to indicate overflow, but in practice
+ // we're never going to have valid indices close to this number).
+ if(stop != s.c_str() + s.length() - 1 || index >= SIZE_MAX)
+ {
+ throw std::runtime_error
+ ("Index of vector variable '" + s + "' is not a valid
number"
+ );
+ }
+
+ // Cast below is valid because of the check for overflow above.
+ return html::text::from
+ (evaluator_
+ (s.substr(0, open_pos)
+ ,static_cast<std::size_t>(index)
+ )
+ );
+ }
+
+ return html::text::from(evaluator_(s));
+ }
+
+ std::string load_partial_from_file(std::string const& file) const
+ {
+ std::ifstream ifs(AddDataDir(file + ".mst"));
+ if(!ifs)
+ {
+ alarum()
+ << "Template file \""
+ << file
+ << ".mst\" not found."
+ << std::flush
+ ;
+ }
+ std::string partial;
+ istream_to_string(ifs, partial);
+ return partial;
+ }
+
+ // Object used for variables expansion.
+ ledger_evaluator const evaluator_;
+
+ // Variables defined for all pages of this illustration.
+ std::map<std::string, html::text> vars_;
+};
+
+// A slightly specialized table generator for the tables used in the
+// illustrations.
+class illustration_table_generator : public wx_table_generator
+{
+ public:
+ static int const rows_per_group = 5;
+
+ explicit illustration_table_generator(pdf_writer_wx& writer)
+ :wx_table_generator
+ (writer.dc()
+ ,writer.get_horz_margin()
+ ,writer.get_page_width()
+ )
+ {
+ use_condensed_style();
+ align_right();
+ }
+
+ // Return the amount of vertical space taken by separator lines in the
+ // table headers.
+ int get_separator_line_height() const
+ {
+ // This is completely arbitrary and chosen just because it seems to
+ // look well.
+ return row_height() / 2;
+ }
+};
+
+// A helper mix-in class for pages using tables which is also reused by the
+// custom wxHtmlCell showing a table.
+//
+// Derived classes must provide get_table_columns() and may also override
+// should_show_column() to hide some of these columns dynamically and then can
+// use create_table_generator() to obtain the generator object that can be used
+// to render a table with the specified columns.
+class using_illustration_table
+{
+ protected:
+ // Description of a single table column.
+ struct illustration_table_column
+ {
+ std::string const variable_name;
+ std::string const label;
+ std::string const widest_text;
+ };
+
+ using illustration_table_columns = std::vector<illustration_table_column>;
+
+ // Must be overridden to return the description of the table columns.
+ virtual illustration_table_columns const& get_table_columns() const = 0;
+
+ // May be overridden to return false if the given column shouldn't be shown
+ // for the specific ledger values (currently used to exclude individual
+ // columns from composite illustrations).
+ virtual bool should_show_column(Ledger const& ledger, int column) const
+ {
+ stifle_warning_for_unused_value(ledger);
+ stifle_warning_for_unused_value(column);
+ return true;
+ }
+
+ // Useful helper for creating the table generator using the columns defined
+ // by the separate (and simpler to implement) get_table_columns() pure
+ // virtual method.
+ illustration_table_generator create_table_generator
+ (Ledger const& ledger
+ ,pdf_writer_wx& writer
+ ) const
+ {
+ // Set the smaller font used for all tables before creating the table
+ // generator which uses the DC font for its measurements.
+ auto& dc = writer.dc();
+ auto font = dc.GetFont();
+ font.SetPointSize(9);
+ dc.SetFont(font);
+
+ illustration_table_generator table(writer);
+
+ // But set the highlight colour for drawing separator lines after
+ // creating it to override its default pen.
+ dc.SetPen(HIGHLIGHT_COL);
+
+ int column = 0;
+ for(auto const& i : get_table_columns())
+ {
+ std::string label;
+ if(should_show_column(ledger, column++))
+ {
+ label = i.label;
+ }
+ //else: Leave the label empty to avoid showing the column.
+
+ table.add_column(label, i.widest_text);
+ }
+
+ return table;
+ }
+};
+
+// Base class for our custom HTML cells providing a way to pass them
+// information about the PDF document being generated and the ledger used to
+// generate it.
+class html_cell_for_pdf_output : public wxHtmlCell
+{
+ public:
+ // Before using this class a pdf_context_setter object needs to be
+ // instantiated (and remain alive for as long as this class is used).
+ class pdf_context_setter
+ {
+ public:
+ // References passed to the ctor must have lifetime greater than that
+ // of this object itself.
+ explicit pdf_context_setter
+ (Ledger const& ledger
+ ,pdf_writer_wx& writer
+ ,html_interpolator const& interpolate_html
+ )
+ {
+ html_cell_for_pdf_output::pdf_context_for_html_output.set
+ (&ledger
+ ,&writer
+ ,&interpolate_html
+ );
+ }
+
+ ~pdf_context_setter()
+ {
+ html_cell_for_pdf_output::pdf_context_for_html_output.set
+ (nullptr
+ ,nullptr
+ ,nullptr
+ );
+ }
+ };
+
+ protected:
+ // This is ugly, but we have to use a global variable to make pdf_writer_wx
+ // and wxDC objects used by the main code accessible to this cell class,
+ // there is no way to pass them as parameters through wxHTML machinery.
+ //
+ // To at least make it a little bit safer to deal with this, the variable
+ // itself is private and a public pdf_context_setter class is provided to
+ // actually set it.
+ class pdf_context
+ {
+ public:
+ void set
+ (Ledger const* ledger
+ ,pdf_writer_wx* writer
+ ,html_interpolator const* interpolate_html
+ )
+ {
+ ledger_ = ledger;
+ writer_ = writer;
+ interpolate_html_ = interpolate_html;
+ }
+
+ Ledger const& ledger() const
+ {
+ LMI_ASSERT(ledger_);
+ return *ledger_;
+ }
+
+ pdf_writer_wx& writer() const
+ {
+ LMI_ASSERT(writer_);
+ return *writer_;
+ }
+
+ html_interpolator const& interpolate_html() const
+ {
+ LMI_ASSERT(interpolate_html_);
+ return *interpolate_html_;
+ }
+
+ private:
+ Ledger const* ledger_ = nullptr;
+ pdf_writer_wx* writer_ = nullptr;
+ html_interpolator const* interpolate_html_ = nullptr;
+ };
+
+ // Small helper to check that we're using the expected DC and, also, acting
+ // as a sink for the never used parameters of Draw().
+ void draw_check_precondition
+ (wxDC& dc
+ ,int view_y1
+ ,int view_y2
+ ,wxHtmlRenderingInfo& info
+ )
+ {
+ // The DC passed to this function is supposed to be the same as the one
+ // associated with the writer we will use for rendering, but check that
+ // this is really so in order to avoid unexpectedly drawing the table
+ // on something else.
+ LMI_ASSERT(&dc == &pdf_context_for_html_output.writer().dc());
+
+ // There is no need to optimize drawing by restricting it to the
+ // currently shown positions, we always render the cell entirely.
+ stifle_warning_for_unused_value(view_y1);
+ stifle_warning_for_unused_value(view_y2);
+
+ // We don't care about rendering state as we don't support interactive
+ // selection anyhow.
+ stifle_warning_for_unused_value(info);
+ }
+
+ static pdf_context pdf_context_for_html_output;
+
+ friend pdf_context_setter;
+};
+
+html_cell_for_pdf_output::pdf_context
+html_cell_for_pdf_output::pdf_context_for_html_output;
+
+// Define scaffolding for a custom HTML "scaled_image" tag which must be used
+// instead of the standard "a" in order to allow specifying the scaling factor
+// that we want to use for the image in the PDF. Unfortunately this can't be
+// achieved by simply using "width" and/or "height" attributes of the "a" tag
+// because their values can only be integers which is not precise enough to
+// avoid (slightly but noticeably) distorting the image due to the aspect ratio
+// being not quite right.
+
+class scaled_image_cell : public html_cell_for_pdf_output
+{
+ public:
+ scaled_image_cell
+ (wxImage const& image
+ ,wxString const& src
+ ,double scale_factor
+ )
+ :image_(image)
+ ,src_(src)
+ ,scale_factor_(scale_factor)
+ {
+ m_Width = wxRound(image.GetWidth () / scale_factor);
+ m_Height = wxRound(image.GetHeight() / scale_factor);
+ }
+
+ // Override the base class method to actually render the image.
+ void Draw
+ (wxDC& dc
+ ,int x
+ ,int y
+ ,int view_y1
+ ,int view_y2
+ ,wxHtmlRenderingInfo& info
+ ) override
+ {
+ draw_check_precondition(dc, view_y1, view_y2, info);
+
+ auto& writer = pdf_context_for_html_output.writer();
+
+ x += m_PosX;
+
+ int pos_y = y + m_PosY;
+ writer.output_image(image_, src_.utf8_str(), scale_factor_, x, &pos_y);
+ }
+
+ private:
+ wxImage const image_;
+ wxString const src_;
+ double const scale_factor_;
+};
+
+TAG_HANDLER_BEGIN(scaled_image, "SCALED_IMAGE")
+ TAG_HANDLER_PROC(tag)
+ {
+ wxString src;
+ if (!tag.GetParamAsString("SRC", &src))
+ {
+ throw std::runtime_error
+ ("missing mandatory \"src\" attribute of \"scaled_image\" tag"
+ );
+ }
+
+ // The scale factor is optional.
+ double scale_factor = 1.;
+
+ // But if it is given, we currently specify its inverse in HTML just
+ // because it so happens that for the scale factors we use the inverse
+ // can be expressed exactly in decimal notation, while the factor
+ // itself can't. In principle, the converse could also happen and we
+ // might add support for "factor" attribute too in this case. Or we
+ // could use separate "numerator" and "denominator" attributes. But for
+ // now implement just the bare minimum of what we need.
+ wxString inv_factor_str;
+ if (tag.GetParamAsString("INV_FACTOR", &inv_factor_str))
+ {
+ double inv_factor = 0.;
+ if (!inv_factor_str.ToCDouble(&inv_factor) || inv_factor == 0.)
+ {
+ throw std::runtime_error
+ ( "invalid value for \"inv_factor\" attribute of "
+ "\"scaled_image\" tag: \""
+ + inv_factor_str.ToStdString()
+ + "\""
+ );
+ }
+
+ scale_factor = 1./inv_factor;
+ }
+
+ wxImage image;
+ // Disable error logging, we'll simply ignore the tag if the image is
+ // not present.
+ {
+ wxLogNull noLog;
+ image.LoadFile(src);
+ }
+
+ if (image.IsOk())
+ {
+ m_WParser->GetContainer()->InsertCell
+ (new scaled_image_cell(image, src, scale_factor)
+ );
+ }
+
+ // This tag isn't supposed to have any inner contents, so return true
+ // to not even try parsing it.
+ return true;
+ }
+TAG_HANDLER_END(scaled_image)
+
+class pdf_illustration;
+
+// Base class for all logical illustration pages.
+//
+// A single logical page may result in multiple physical pages of output, e.g.
+// if it contains a table not fitting on one page, but mostly these page
+// objects correspond to a single physical page of the resulting illustration.
+class page
+{
+ public:
+ page() = default;
+
+ // Pages are not value-like objects, so prohibit copying them.
+ page(page const&) = delete;
+ page& operator=(page const&) = delete;
+
+ // Make base class dtor virtual.
+ virtual ~page() = default;
+
+ // Associate the illustration object using this page with it.
+ //
+ // This object is not passed as a ctor argument because it would be
+ // redundant, instead it is associated with the page when it's added to an
+ // illustration. This method is supposed to be called only once and only by
+ // pdf_illustration this page is being added to.
+ void illustration(pdf_illustration const& illustration)
+ {
+ LMI_ASSERT(!illustration_);
+
+ illustration_ = &illustration;
+ }
+
+ // Called before rendering any pages to prepare for doing this, e.g. by
+ // computing the number of pages needed.
+ //
+ // This method must not draw anything on the wxDC, it is provided only for
+ // measurement purposes.
+ virtual void pre_render
+ (Ledger const& ledger
+ ,pdf_writer_wx& writer
+ ,html_interpolator const& interpolate_html
+ )
+ {
+ stifle_warning_for_unused_value(ledger);
+ stifle_warning_for_unused_value(writer);
+ stifle_warning_for_unused_value(interpolate_html);
+ }
+
+ // Render this page contents.
+ virtual void render
+ (Ledger const& ledger
+ ,pdf_writer_wx& writer
+ ,html_interpolator const& interpolate_html
+ ) = 0;
+
+ protected:
+ // Helper method for rendering the contents of the given external template,
+ // which is expected to be found in the file with the provided name and
+ // ".mst" extension in the data directory.
+ //
+ // Return the height of the page contents.
+ int render_page_template
+ (std::string const& template_name
+ ,pdf_writer_wx& writer
+ ,html_interpolator const& interpolate_html
+ )
+ {
+ return writer.output_html
+ (writer.get_horz_margin()
+ ,writer.get_vert_margin()
+ ,writer.get_page_width()
+ ,interpolate_html.expand_template(template_name)
+ );
+ }
+
+ // The associated illustration, which will be non-null by the time our
+ // virtual methods such as pre_render() and render() are called.
+ pdf_illustration const* illustration_ = nullptr;
+};
+
+// Base class for the different kinds of illustrations.
+//
+// This object contains pages, added to it using its add() method, as well as
+// illustration-global data registered as variables with html_interpolator and
+// so available for the pages when expanding the external templates defining
+// their contents.
+class pdf_illustration : protected html_interpolator
+{
+ public:
+ pdf_illustration(Ledger const& ledger
+ ,fs::path const& output
+ )
+ :html_interpolator(ledger.make_evaluator())
+ ,writer_(output.string(), wxPORTRAIT, &html_font_sizes)
+ ,ledger_(ledger)
+ {
+ init_variables();
+ }
+
+ // Make base class dtor virtual.
+ virtual ~pdf_illustration() = default;
+
+ // Add a page.
+ //
+ // This is a template just in order to save on writing std::make_unique<>()
+ // in the code using it to make it slightly shorter.
+ template<typename T, typename... Args>
+ void add(Args&&... args)
+ {
+ auto page = std::make_unique<T>(std::forward<Args>(args)...);
+ page->illustration(*this);
+ pages_.emplace_back(std::move(page));
+ }
+
+ // Render all pages.
+ void render_all()
+ {
+ html_cell_for_pdf_output::pdf_context_setter
+ set_pdf_context(ledger_, writer_, *this);
+
+ for(auto const& page : pages_)
+ {
+ page->pre_render(ledger_, writer_, *this);
+ }
+
+ bool first = true;
+ for(auto const& page : pages_)
+ {
+ if(first)
+ {
+ // We shouldn't start a new page before the very first one.
+ first = false;
+ }
+ else
+ {
+ // Do start a new physical page before rendering all the
+ // subsequent pages (notice that a page is also free to call
+ // StartPage() from its render()).
+ writer_.dc().StartPage();
+ }
+
+ page->render(ledger_, writer_, *this);
+ }
+ }
+
+ // Methods to be implemented by the derived classes to indicate which
+ // templates should be used for the upper (above the separating line) and
+ // the lower parts of the footer. The upper template name may be empty if
+ // it is not used at all.
+ //
+ // Notice that the upper footer template name can be overridden at the page
+ // level, the methods here define the default for all illustration pages.
+ //
+ // These methods are used by the pages deriving from page_with_footer.
+ virtual std::string get_upper_footer_template_name() const = 0;
+ virtual std::string get_lower_footer_template_name() const = 0;
+
+ protected:
+ // Explicitly retrieve the base class.
+ html_interpolator const& get_interpolator() const {return *this;}
+
+ // Helper for abbreviating a string to at most the given length (in bytes).
+ static std::string abbreviate_if_necessary(std::string s, size_t len)
+ {
+ if(s.length() > len && len > 3)
+ {
+ s.replace(len - 3, std::string::npos, "...");
+ }
+
+ return s;
+ }
+
+ // Helper for creating abbreviated variables in the derived classes: such
+ // variables have the name based on the name of the original variable with
+ // "Abbrev" and "len" appended to it and their value is at most "len" bytes
+ // long.
+ void add_abbreviated_variable(std::string const& var, size_t len)
+ {
+ add_variable
+ (var + "Abbrev" + std::to_string(len)
+ ,abbreviate_if_necessary(evaluate(var), len)
+ );
+ }
+
+ private:
+ // Define variables that can be used when interpolating pages contents.
+ void init_variables()
+ {
+ // The variables defined here are used by all, or at least more than
+ // one, illustration kinds. Variables only used in the templates of a
+ // single illustration type should be defined in the corresponding
+ // derived pdf_illustration_xxx class instead.
+
+ add_variable
+ ("date_prepared"
+ , html::text::from(evaluate("PrepMonth"))
+ + html::text::nbsp()
+ + html::text::from(evaluate("PrepDay"))
+ + html::text::from(", ")
+ + html::text::from(evaluate("PrepYear"))
+ );
+
+ auto indent = html::text::nbsp();
+ add_variable("Space1", indent);
+
+ indent += indent;
+ add_variable("Space2", indent);
+
+ indent += indent;
+ add_variable("Space4", indent);
+
+ indent += indent;
+ add_variable("Space8", indent);
+
+ indent += indent;
+ add_variable("Space16", indent);
+
+ indent += indent;
+ add_variable("Space32", indent);
+
+ indent += indent;
+ add_variable("Space64", indent);
+
+ auto const& invar = ledger_.GetLedgerInvariant();
+
+ add_abbreviated_variable("CorpName", 60);
+ add_abbreviated_variable("Insured1", 30);
+
+ // Define the variables needed by contract_numbers template.
+ add_variable
+ ("HasMasterContract"
+ ,!invar.MasterContractNumber.empty()
+ );
+ add_variable
+ ("HasPolicyNumber"
+ ,!invar.ContractNumber.empty()
+ );
+
+ size_t const full_abbrev_length = 30;
+ add_abbreviated_variable("MasterContractNumber", full_abbrev_length);
+ add_abbreviated_variable("MasterContractNumber", full_abbrev_length /
2);
+ add_abbreviated_variable("ContractNumber", full_abbrev_length);
+ add_abbreviated_variable("ContractNumber", full_abbrev_length / 2);
+
+ add_variable
+ ("HasComplianceTrackingNumber"
+ ,expand_template("imprimatur")
+ .as_html().find_first_not_of(" \n")
+ != std::string::npos
+ );
+
+ add_variable
+ ("HasScaleUnit"
+ ,!invar.ScaleUnit().empty()
+ );
+
+ add_variable
+ ("DefnLifeInsIsGPT"
+ ,invar.DefnLifeIns == "GPT"
+ );
+
+ add_variable
+ ("MecYearPlus1"
+ ,bourn_cast<int>(invar.MecYear) + 1
+ );
+
+ add_variable
+ ("UWTypeIsMedical"
+ ,invar.UWType == "Medical"
+ );
+
+ add_variable
+ ("UWClassIsRated"
+ ,invar.UWClass == "Rated"
+ );
+
+ auto const& policy_name = invar.PolicyLegalName;
+ add_variable
+ ("GroupCarveout"
+ ,policy_name == "Group Flexible Premium Adjustable Life Insurance
Certificate"
+ );
+
+ auto const& state_abbrev = invar.GetStatePostalAbbrev();
+ add_variable
+ ("StateIsCarolina"
+ ,state_abbrev == "NC" || state_abbrev == "SC"
+ );
+
+ add_variable
+ ("StateIsMaryland"
+ ,state_abbrev == "MD"
+ );
+ }
+
+ // This array stores the non-default font sizes that are used to make it
+ // simpler to replicate the existing illustrations.
+ static std::array<int, 7> const html_font_sizes;
+
+ // Writer object used for the page metrics and higher level functions.
+ pdf_writer_wx writer_;
+
+ // Source of the data.
+ Ledger const& ledger_;
+
+ // All the pages of this illustration.
+ std::vector<std::unique_ptr<page>> pages_;
+};
+
+std::array<int, 7> const pdf_illustration::html_font_sizes
+ {
+ { 8
+ , 9
+ ,10
+ ,12
+ ,14
+ ,18
+ ,20
+ }
+ };
+
+// Cover page used by several different illustration kinds.
+class cover_page : public page
+{
+ public:
+ void render
+ (Ledger const& /* ledger */
+ ,pdf_writer_wx& writer
+ ,html_interpolator const& interpolate_html
+ ) override
+ {
+ int const height_contents = render_page_template
+ ("cover"
+ ,writer
+ ,interpolate_html
+ );
+
+ // There is no way to draw a border around the page contents in wxHTML
+ // currently, so do it manually.
+ auto& dc = writer.dc();
+
+ dc.SetPen(wxPen(HIGHLIGHT_COL, 2));
+ dc.SetBrush(*wxTRANSPARENT_BRUSH);
+
+ dc.DrawRectangle
+ (writer.get_horz_margin()
+ ,writer.get_vert_margin()
+ ,writer.get_page_width()
+ ,height_contents
+ );
+ }
+};
+
+// Base class for all pages with a footer.
+class page_with_footer : public page
+{
+ public:
+ // Override pre_render() to compute footer_top_ which is needed in the
+ // derived classes overridden get_extra_pages_needed().
+ void pre_render
+ (Ledger const& /* ledger */
+ ,pdf_writer_wx& writer
+ ,html_interpolator const& interpolate_html
+ ) override
+ {
+ auto const frame_horz_margin = writer.get_horz_margin();
+ auto const frame_width = writer.get_page_width();
+
+ // We implicitly assume here that get_footer_lower_html() result
+ // doesn't materially depend on the exact value of the page number as
+ // we don't know its definitive value here yet. In theory, this doesn't
+ // need to be true, e.g. we may later discover that 10 pages are needed
+ // instead of 9 and the extra digit might result in a line wrapping on
+ // a new line and this increasing the footer height, but in practice
+ // this doesn't risk happening and taking into account this possibility
+ // wouldn't be simple at all, so just ignore this possibility.
+ auto footer_height = writer.output_html
+ (frame_horz_margin
+ ,0
+ ,frame_width
+ ,get_footer_lower_html(interpolate_html)
+ ,e_output_measure_only
+ );
+
+ auto const& upper_template = get_upper_footer_template_name();
+ if(!upper_template.empty())
+ {
+ footer_height += writer.output_html
+ (frame_horz_margin
+ ,0
+ ,frame_width
+ ,interpolate_html.expand_template(upper_template)
+ ,e_output_measure_only
+ );
+
+ // Leave a gap between the upper part of the footer and the main
+ // page contents to separate them in absence of a separator line
+ // which delimits the lower part.
+ footer_height += writer.dc().GetCharHeight();
+ }
+
+ footer_top_ = writer.get_page_bottom() - footer_height;
+ }
+
+ void render
+ (Ledger const& /* ledger */
+ ,pdf_writer_wx& writer
+ ,html_interpolator const& interpolate_html
+ ) override
+ {
+ auto const frame_horz_margin = writer.get_horz_margin();
+ auto const frame_width = writer.get_page_width();
+
+ auto& dc = writer.dc();
+
+ auto y = footer_top_;
+
+ auto const& upper_template = get_upper_footer_template_name();
+ if(!upper_template.empty())
+ {
+ y += dc.GetCharHeight();
+
+ y += writer.output_html
+ (frame_horz_margin
+ ,y
+ ,frame_width
+ ,interpolate_html.expand_template(upper_template)
+ );
+ }
+
+ writer.output_html
+ (frame_horz_margin
+ ,y
+ ,frame_width
+ ,get_footer_lower_html(interpolate_html)
+ );
+
+ dc.SetPen(HIGHLIGHT_COL);
+ dc.DrawLine
+ (frame_horz_margin
+ ,y
+ ,frame_width + frame_horz_margin
+ ,y
+ );
+ }
+
+ protected:
+ // Helper for the derived pages to get the vertical position of the footer.
+ // Notice that it can only be used after calling our pre_render() method
+ // as this is where it is computed.
+ int get_footer_top() const
+ {
+ LMI_ASSERT(footer_top_ != 0);
+
+ return footer_top_;
+ }
+
+ private:
+ // Method to be overridden in the base class which should actually return
+ // the page number or equivalent string (e.g. "Appendix").
+ virtual std::string get_page_number() const = 0;
+
+ // This method forwards to the illustration by default, but can be
+ // overridden to define a page-specific footer if necessary.
+ virtual std::string get_upper_footer_template_name() const
+ {
+ return illustration_->get_upper_footer_template_name();
+ }
+
+ // This method uses get_page_number() and returns the HTML wrapping it
+ // and other fixed information appearing in the lower part of the footer.
+ html::text get_footer_lower_html(html_interpolator const&
interpolate_html) const
+ {
+ auto const page_number_str = get_page_number();
+
+ auto const templ = illustration_->get_lower_footer_template_name();
+
+ // Use our own interpolation function to handle the special
+ // "page_number" variable that is replaced with the actual
+ // (possibly dynamic) page number.
+ return html::text::from_html
+ (interpolate_string
+ (("{{>" + templ + "}}").c_str()
+ ,[page_number_str, interpolate_html]
+ (std::string const& s
+ ,interpolate_lookup_kind kind
+ ) -> std::string
+ {
+ if(s == "page_number")
+ {
+ return page_number_str;
+ }
+
+ return interpolate_html.interpolation_func(s, kind);
+ }
+ )
+ );
+ }
+
+ int footer_top_ = 0;
+};
+
+// Base class for attachment pages.
+class attachment_page : public page_with_footer
+{
+ private:
+ std::string get_page_number() const override
+ {
+ return "Attachment";
+ }
+};
+
+// Base class for all pages showing the page number in the footer.
+//
+// In addition to actually providing page_with_footer with the correct string
+// to show in the footer, this class implicitly handles the page count by
+// incrementing it whenever a new object of this class is pre-rendered.
+class numbered_page : public page_with_footer
+{
+ public:
+ // Must be called before creating the first numbered page.
+ static void start_numbering()
+ {
+ last_page_number_ = 0;
+ }
+
+ numbered_page()
+ {
+ // This assert would fail if start_numbering() hadn't been called
+ // before creating a numbered page, as it should be.
+ LMI_ASSERT(last_page_number_ >= 0);
+ }
+
+ void pre_render
+ (Ledger const& ledger
+ ,pdf_writer_wx& writer
+ ,html_interpolator const& interpolate_html
+ ) override
+ {
+ page_with_footer::pre_render(ledger, writer, interpolate_html);
+
+ this_page_number_ = ++last_page_number_;
+
+ extra_pages_ = get_extra_pages_needed
+ (ledger
+ ,writer
+ ,interpolate_html
+ );
+
+ LMI_ASSERT(extra_pages_ >= 0);
+
+ last_page_number_ += extra_pages_;
+ }
+
+ ~numbered_page() override
+ {
+ // Check that next_page() was called the expected number of times.
+ // Unfortunately we can't use LMI_ASSERT() in the (noexcept) dtor, so
+ // use warning() instead.
+ if(extra_pages_)
+ {
+ warning()
+ << "Logic error: "
+ << extra_pages_
+ << " missing extra pages."
+ << LMI_FLUSH
+ ;
+ }
+ }
+
+ protected:
+ void next_page(pdf_writer_wx& writer)
+ {
+ // This method may only be called if we had reserved enough physical
+ // pages for this logical pages by overriding get_extra_pages_needed().
+ LMI_ASSERT(extra_pages_ > 0);
+
+ writer.dc().StartPage();
+
+ this_page_number_++;
+ extra_pages_--;
+ }
+
+ private:
+ // Derived classes may override this method if they may need more than one
+ // physical page to show their contents.
+ virtual int get_extra_pages_needed
+ (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;
+ }
+
+ std::string get_page_number() const override
+ {
+ std::ostringstream oss;
+ oss << "Page " << this_page_number_ << " of " << last_page_number_;
+ return oss.str();
+ }
+
+ static int last_page_number_;
+ int this_page_number_ = 0;
+ int extra_pages_ = 0;
+};
+
+// Initial value is invalid, use start_numbering() to change it.
+int numbered_page::last_page_number_ = -1;
+
+// Simplest possible page which is entirely defined by its external template
+// whose name must be specified when constructing it.
+class standard_page : public numbered_page
+{
+ public:
+ // Accept only string literals as template names, there should be no need
+ // to use anything else.
+ template<int N>
+ explicit standard_page(char const (&page_template_name)[N])
+ :page_template_name_(page_template_name)
+ {
+ }
+
+ void render
+ (Ledger const& ledger
+ ,pdf_writer_wx& writer
+ ,html_interpolator const& interpolate_html
+ ) override
+ {
+ numbered_page::render(ledger, writer, interpolate_html);
+
+ render_page_template(page_template_name_, writer, interpolate_html);
+ }
+
+ private:
+ char const* const page_template_name_;
+};
+
+// Helper classes used to show the numeric summary table. The approach used
+// here is to define a custom HTML tag (<numeric_summary_table>) and use the
+// existing illustration_table_generator to replace it with the actual table
+// when rendering.
+//
+// Notice that we currently make the simplifying assumption that this table is
+// always short enough so that everything fits on the same page as it would be
+// much more complicated to handle page breaks in the table in the middle of a
+// page (page_with_tabular_report below handles them only for the table at the
+// bottom of the page, after all the other contents, and this is already more
+// complicated and can't be done with just a custom HTML tag as we do it here).
+
+// An HTML cell showing the contents of the numeric summary table.
+class numeric_summary_table_cell
+ :public html_cell_for_pdf_output
+ ,private using_illustration_table
+{
+ public:
+ numeric_summary_table_cell()
+ {
+ m_Height = render_or_measure(0, e_output_measure_only);
+ }
+
+ // Override the base class method to actually render the table.
+ void Draw
+ (wxDC& dc
+ ,int x
+ ,int y
+ ,int view_y1
+ ,int view_y2
+ ,wxHtmlRenderingInfo& info
+ ) override
+ {
+ draw_check_precondition(dc, view_y1, view_y2, info);
+
+ // We ignore the horizontal coordinate which is always 0 for this cell
+ // anyhow.
+ stifle_warning_for_unused_value(x);
+
+ render_or_measure(y + m_PosY, e_output_normal);
+ }
+
+ private:
+ enum
+ {column_policy_year
+ ,column_premium_outlay
+ ,column_guar_account_value
+ ,column_guar_cash_surr_value
+ ,column_guar_death_benefit
+ ,column_separator_guar_non_guar
+ ,column_mid_account_value
+ ,column_mid_cash_surr_value
+ ,column_mid_death_benefit
+ ,column_separator_mid_curr
+ ,column_curr_account_value
+ ,column_curr_cash_surr_value
+ ,column_curr_death_benefit
+ ,column_max
+ };
+
+ illustration_table_columns const& get_table_columns() const override
+ {
+ static illustration_table_columns const columns =
+ {{ "PolicyYear" , "Policy\nYear" , "999" }
+ ,{ "GrossPmt" , "Premium\nOutlay" , "999,999" }
+ ,{ "AcctVal_Guaranteed" , "Account\nValue" , "999,999" }
+ ,{ "CSVNet_Guaranteed" , "Cash Surr\nValue" , "999,999" }
+ ,{ "EOYDeathBft_Guaranteed" , "Death\nBenefit" , "9,999,999" }
+ ,{ "" , " " , "-" }
+ ,{ "AcctVal_Midpoint" , "Account\nValue" , "999,999" }
+ ,{ "CSVNet_Midpoint" , "Cash Surr\nValue" , "999,999" }
+ ,{ "EOYDeathBft_Midpoint" , "Death\nBenefit" , "9,999,999" }
+ ,{ "" , " " , "-" }
+ ,{ "AcctVal_Current" , "Account\nValue" , "999,999" }
+ ,{ "CSVNet_Current" , "Cash Surr\nValue" , "999,999" }
+ ,{ "EOYDeathBft_Current" , "Death\nBenefit" , "9,999,999" }
+ };
+
+ return columns;
+ }
+
+ int render_or_measure(int pos_y, enum_output_mode output_mode)
+ {
+ auto const& ledger = pdf_context_for_html_output.ledger();
+ auto& writer = pdf_context_for_html_output.writer();
+
+ illustration_table_generator
+ table{create_table_generator(ledger, writer)};
+
+ // Output multiple rows of headers.
+
+ // Make a copy because we want pos_y to be modified only once, not
+ // twice, by both output_super_header() calls.
+ auto y_copy = pos_y;
+ table.output_super_header
+ ("Guaranteed Values"
+ ,column_guar_account_value
+ ,column_separator_guar_non_guar
+ ,&y_copy
+ ,output_mode
+ );
+ table.output_super_header
+ ("Non-Guaranteed Values"
+ ,column_mid_account_value
+ ,column_max
+ ,&pos_y
+ ,output_mode
+ );
+
+ pos_y += table.get_separator_line_height();
+ table.output_horz_separator
+ (column_guar_account_value
+ ,column_separator_guar_non_guar
+ ,pos_y
+ ,output_mode
+ );
+ table.output_horz_separator
+ (column_mid_account_value
+ ,column_max
+ ,pos_y
+ ,output_mode
+ );
+
+ y_copy = pos_y;
+ table.output_super_header
+ ("Midpoint Values"
+ ,column_mid_account_value
+ ,column_separator_mid_curr
+ ,&y_copy
+ ,output_mode
+ );
+
+ table.output_super_header
+ ("Current Values"
+ ,column_curr_account_value
+ ,column_max
+ ,&pos_y
+ ,output_mode
+ );
+
+ pos_y += table.get_separator_line_height();
+ table.output_horz_separator
+ (column_mid_account_value
+ ,column_separator_mid_curr
+ ,pos_y
+ ,output_mode
+ );
+
+ table.output_horz_separator
+ (column_curr_account_value
+ ,column_max
+ ,pos_y
+ ,output_mode
+ );
+
+ table.output_header(&pos_y, output_mode);
+
+ pos_y += table.get_separator_line_height();
+ table.output_horz_separator(0, column_max, pos_y, output_mode);
+
+ // And now the table values themselves.
+ auto const& columns = get_table_columns();
+ std::vector<std::string> values(columns.size());
+
+ auto const& invar = ledger.GetLedgerInvariant();
+ auto const& interpolate_html =
pdf_context_for_html_output.interpolate_html();
+
+ int const year_max =
pdf_context_for_html_output.ledger().GetMaxLength();
+ int const age_last = 70;
+ std::array<int, 4> const summary_years =
+ {{5, 10, 20, age_last - bourn_cast<int>(invar.Age)}
+ };
+ for(auto const& year : summary_years)
+ {
+ // Skip row if it doesn't exist. For instance, if the issue
+ // age is 85 and the contract remains in force until age 100,
+ // then there is no twentieth duration and no age-70 row.
+ if(!(0 < year && year <= year_max))
+ {
+ continue;
+ }
+
+ // Last row, showing the values for "Age 70" normally, needs to be
+ // handled specially.
+ bool const is_last_row = &year == &summary_years.back();
+
+ // For composite ledgers, "Age" doesn't make sense and so this row
+ // should be just skipped for them.
+ if(is_last_row && ledger.is_composite())
+ {
+ continue;
+ }
+
+ switch(output_mode)
+ {
+ case e_output_measure_only:
+ pos_y += table.row_height();
+ break;
+
+ case e_output_normal:
+ for(std::size_t col = 0; col < columns.size(); ++col)
+ {
+ std::string const variable_name =
columns[col].variable_name;
+
+ // According to regulations, we need to replace the
+ // policy year in the last row with the age.
+ if(col == column_policy_year)
+ {
+ if(is_last_row)
+ {
+ std::ostringstream oss;
+ oss << "Age " << age_last;
+ values[col] = oss.str();
+ continue;
+ }
+ }
+
+ // Special hack for the dummy columns whose value is
always
+ // empty as it's used only as separator.
+ values[col] = variable_name.empty()
+ ? std::string{}
+ : interpolate_html.evaluate(variable_name, year -
1)
+ ;
+ }
+
+ table.output_row(&pos_y, values.data());
+ break;
+ }
+ }
+
+ return pos_y;
+ }
+};
+
+// Custom tag which is replaced by the numeric summary table.
+TAG_HANDLER_BEGIN(numeric_summary_table, "NUMERIC_SUMMARY_TABLE")
+ TAG_HANDLER_PROC(tag)
+ {
+ // The tag argument would be useful if we defined any parameters for
+ // it, but currently we don't.
+ stifle_warning_for_unused_value(tag);
+
+ m_WParser->GetContainer()->InsertCell(new
numeric_summary_table_cell());
+
+ // This tag isn't supposed to have any inner contents, so return true
+ // to not even try parsing it.
+ return true;
+ }
+TAG_HANDLER_END(numeric_summary_table)
+
+// In wxWidgets versions prior to 3.1.1, there is an extra semicolon at the end
+// of TAGS_MODULE_BEGIN() expansion resulting in a warning with -pedantic used
+// by lmi, so suppress this warning here (this could be removed once 3.1.1 is
+// required).
+wxGCC_WARNING_SUPPRESS(pedantic)
+
+TAGS_MODULE_BEGIN(lmi_illustration)
+ TAGS_MODULE_ADD(scaled_image)
+ TAGS_MODULE_ADD(numeric_summary_table)
+TAGS_MODULE_END(lmi_illustration)
+
+wxGCC_WARNING_RESTORE(pedantic)
+
+// Numeric summary page appears twice, once as a normal page and once as an
+// attachment, with the only difference being that the base class is different,
+// so make it a template to avoid duplicating the code.
+
+// Just a helper alias.
+template<bool is_attachment>
+using numbered_or_attachment_base = typename std::conditional
+ <is_attachment
+ ,attachment_page
+ ,numbered_page
+ >::type;
+
+template<bool is_attachment>
+class reg_numeric_summary_or_attachment_page
+ : public numbered_or_attachment_base<is_attachment>
+{
+ public:
+ void render
+ (Ledger const& ledger
+ ,pdf_writer_wx& writer
+ ,html_interpolator const& interpolate_html
+ ) override
+ {
+ numbered_or_attachment_base<is_attachment>::render
+ (ledger
+ ,writer
+ ,interpolate_html
+ );
+
+ this->render_page_template
+ ("reg_numeric_summary"
+ ,writer
+ ,interpolate_html
+ );
+ }
+};
+
+// Helper base class for pages showing a table displaying values for all
+// contract years after some fixed content.
+class page_with_tabular_report
+ :public numbered_page
+ ,protected using_illustration_table
+{
+ public:
+ void render
+ (Ledger const& ledger
+ ,pdf_writer_wx& writer
+ ,html_interpolator const& interpolate_html
+ ) override
+ {
+ numbered_page::render(ledger, writer, interpolate_html);
+
+ illustration_table_generator
+ table{create_table_generator(ledger, writer)};
+
+ auto const& columns = get_table_columns();
+
+ // Just some cached values used inside the loop below.
+ auto const row_height = table.row_height();
+ auto const page_bottom = get_footer_top();
+ auto const rows_per_group =
illustration_table_generator::rows_per_group;
+ std::vector<std::string> values(columns.size());
+
+ // The table may need several pages, loop over them.
+ int const year_max = ledger.GetMaxLength();
+ for(int year = 0; year < year_max; ++year)
+ {
+ int pos_y = render_or_measure_fixed_page_part
+ (table
+ ,writer
+ ,interpolate_html
+ ,e_output_normal
+ );
+
+ for(; year < year_max; ++year)
+ {
+ for(std::size_t col = 0; col < columns.size(); ++col)
+ {
+ std::string const variable_name =
columns[col].variable_name;
+
+ // Special hack for the dummy columns used in some reports,
+ // whose value is always empty as it's used only as
+ // separator.
+ values[col] = variable_name.empty()
+ ? std::string{}
+ : interpolate_html.evaluate(variable_name, year)
+ ;
+ }
+
+ table.output_row(&pos_y, values.data());
+
+ if((year + 1) % rows_per_group == 0)
+ {
+ // We need a group break.
+ pos_y += row_height;
+
+ // And possibly a page break, which will be necessary if
we don't
+ // have enough space for another full group because we
don't want
+ // to have page breaks in the middle of a group.
+ if(pos_y >= page_bottom - rows_per_group*row_height)
+ {
+ next_page(writer);
+ numbered_page::render(ledger, writer,
interpolate_html);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ protected:
+ // Must be overridden to return the template containing the fixed page
part.
+ virtual std::string get_fixed_page_contents_template_name() const = 0;
+
+ // May be overridden to render (only if output_mode is e_output_normal)
+ // the extra headers just above the regular table headers.
+ //
+ // If this function does anything, it must show the first super-header at
+ // pos_y and update it to account for the added lines. The base class
+ // version does nothing.
+ virtual void render_or_measure_extra_headers
+ (illustration_table_generator& table
+ ,html_interpolator const& interpolate_html
+ ,int* pos_y
+ ,enum_output_mode output_mode
+ ) const
+ {
+ stifle_warning_for_unused_value(table);
+ stifle_warning_for_unused_value(interpolate_html);
+ stifle_warning_for_unused_value(pos_y);
+ stifle_warning_for_unused_value(output_mode);
+ }
+
+ private:
+ // Render (only if output_mode is e_output_normal) the fixed page part and
+ // (in any case) return the vertical coordinate of its bottom, where the
+ // tabular report starts.
+ int render_or_measure_fixed_page_part
+ (illustration_table_generator& table
+ ,pdf_writer_wx& writer
+ ,html_interpolator const& interpolate_html
+ ,enum_output_mode output_mode
+ ) const
+ {
+ int pos_y = writer.get_vert_margin();
+
+ pos_y += writer.output_html
+ (writer.get_horz_margin()
+ ,pos_y
+ ,writer.get_page_width()
+ ,interpolate_html.expand_template
+ (get_fixed_page_contents_template_name()
+ )
+ ,output_mode
+ );
+
+ render_or_measure_extra_headers
+ (table
+ ,interpolate_html
+ ,&pos_y
+ ,output_mode
+ );
+
+ table.output_header(&pos_y, output_mode);
+
+ pos_y += table.get_separator_line_height();
+ table.output_horz_separator
+ (0
+ ,table.columns_count()
+ ,pos_y
+ ,output_mode
+ );
+
+ return pos_y;
+ }
+
+ // Override the base class method as the table may overflow onto the next
+ // page(s).
+ int get_extra_pages_needed
+ (Ledger const& ledger
+ ,pdf_writer_wx& writer
+ ,html_interpolator const& interpolate_html
+ ) const override
+ {
+ illustration_table_generator
+ table{create_table_generator(ledger, writer)};
+
+ int const pos_y = render_or_measure_fixed_page_part
+ (table
+ ,writer
+ ,interpolate_html
+ ,e_output_measure_only
+ );
+
+ int const rows_per_page = (get_footer_top() - pos_y) /
table.row_height();
+
+ int const rows_per_group =
illustration_table_generator::rows_per_group;
+
+ if(rows_per_page < rows_per_group)
+ {
+ // We can't afford to continue in this case as we can never output
+ // the table as the template simply doesn't leave enough space for
+ // it on the page.
+ throw std::runtime_error("no space left for tabular report");
+ }
+
+ // Each group actually takes rows_per_group+1 rows because of the
+ // separator row between groups, hence the second +1, but there is no
+ // need for the separator after the last group, hence the first +1.
+ int const groups_per_page = (rows_per_page + 1) / (rows_per_group + 1);
+
+ // But we are actually interested in the number of years per page and
+ // not the number of groups.
+ int const years_per_page = groups_per_page * rows_per_group;
+
+ // Finally determine how many pages we need to show all the years.
+ return ledger.GetMaxLength() / years_per_page;
+ }
+};
+
+class reg_tabular_detail_page : public page_with_tabular_report
+{
+ private:
+ enum
+ {column_policy_year
+ ,column_end_of_year_age
+ ,column_premium_outlay
+ ,column_guar_account_value
+ ,column_guar_cash_surr_value
+ ,column_guar_death_benefit
+ ,column_dummy_separator
+ ,column_curr_account_value
+ ,column_curr_cash_surr_value
+ ,column_curr_death_benefit
+ ,column_max
+ };
+
+ std::string get_fixed_page_contents_template_name() const override
+ {
+ return "reg_tabular_details";
+ }
+
+ std::string get_upper_footer_template_name() const override
+ {
+ return "reg_footer_disclaimer";
+ }
+
+ void render_or_measure_extra_headers
+ (illustration_table_generator& table
+ ,html_interpolator const& interpolate_html
+ ,int* pos_y
+ ,enum_output_mode output_mode
+ ) const override
+ {
+ stifle_warning_for_unused_value(interpolate_html);
+
+ // Make a copy because we want the real pos_y to be modified only once,
+ // not twice, by both output_super_header() calls.
+ auto pos_y_copy = *pos_y;
+ table.output_super_header
+ ("Guaranteed Values"
+ ,column_guar_account_value
+ ,column_dummy_separator
+ ,&pos_y_copy
+ ,output_mode
+ );
+ table.output_super_header
+ ("Non-Guaranteed Values"
+ ,column_curr_account_value
+ ,column_max
+ ,pos_y
+ ,output_mode
+ );
+
+ *pos_y += table.get_separator_line_height();
+ table.output_horz_separator
+ (column_guar_account_value
+ ,column_dummy_separator
+ ,*pos_y
+ ,output_mode
+ );
+ table.output_horz_separator
+ (column_curr_account_value
+ ,column_max
+ ,*pos_y
+ ,output_mode
+ );
+ }
+
+ illustration_table_columns const& get_table_columns() const override
+ {
+ static illustration_table_columns const columns =
+ {{ "PolicyYear" , "Policy\nYear" , "999" }
+ ,{ "AttainedAge" , "End of\nYear Age" , "999" }
+ ,{ "GrossPmt" , "Premium\nOutlay" , "999,999" }
+ ,{ "AcctVal_Guaranteed" , "Account\nValue" , "999,999" }
+ ,{ "CSVNet_Guaranteed" , "Cash Surr\nValue" , "999,999" }
+ ,{ "EOYDeathBft_Guaranteed" , "Death\nBenefit" , "9,999,999" }
+ ,{ "" , " " , "----" }
+ ,{ "AcctVal_Current" , "Account\nValue" , "999,999" }
+ ,{ "CSVNet_Current" , "Cash Surr\nValue" , "999,999" }
+ ,{ "EOYDeathBft_Current" , "Death\nBenefit" , "9,999,999" }
+ };
+
+ return columns;
+ }
+
+ bool should_show_column(Ledger const& ledger, int column) const override
+ {
+ // One column should be hidden for composite ledgers.
+ return column != column_end_of_year_age || !ledger.is_composite();
+ }
+};
+
+class reg_tabular_detail2_page : public page_with_tabular_report
+{
+ private:
+ enum
+ {column_policy_year
+ ,column_end_of_year_age
+ ,column_ill_crediting_rate
+ ,column_selected_face_amount
+ ,column_max
+ };
+
+ std::string get_fixed_page_contents_template_name() const override
+ {
+ return "reg_tabular_details2";
+ }
+
+ std::string get_upper_footer_template_name() const override
+ {
+ return "reg_footer_disclaimer";
+ }
+
+ illustration_table_columns const& get_table_columns() const override
+ {
+ static illustration_table_columns const columns =
+ {{ "PolicyYear" , "Policy\nYear" ,
"999" }
+ ,{ "AttainedAge" , "End of\nYear Age" ,
"999" }
+ ,{ "AnnGAIntRate_Current", "Illustrated\nCrediting Rate",
"99.99%" }
+ ,{ "SpecAmt" , "Selected\nFace Amount" ,
"999,000,000" }
+ };
+
+ return columns;
+ }
+
+ bool should_show_column(Ledger const& ledger, int column) const override
+ {
+ // One column should be hidden for composite ledgers.
+ return column != column_end_of_year_age || !ledger.is_composite();
+ }
+};
+
+// Class for pages showing supplemental report after the fixed template
+// contents. It can be either used directly or further derived from, e.g. to
+// override some of its inherited virtual methods such as
+// get_upper_footer_template_name() as done below.
+class standard_supplemental_report : public page_with_tabular_report
+{
+ public:
+ explicit standard_supplemental_report
+ (html_interpolator const& interpolate_html
+ ,std::string const& page_template
+ )
+ :columns_(build_columns(interpolate_html))
+ ,page_template_(page_template)
+ {
+ }
+
+ private:
+ illustration_table_columns const& get_table_columns() const override
+ {
+ return columns_;
+ }
+
+ std::string get_fixed_page_contents_template_name() const override
+ {
+ return page_template_;
+ }
+
+ // Helper function used by the ctor to initialize the const columns_ field.
+ illustration_table_columns build_columns
+ (html_interpolator const& interpolate_html
+ )
+ {
+ constexpr std::size_t max_columns = 12;
+ std::string const empty_column_name("[none]");
+
+ illustration_table_columns columns;
+ for(std::size_t i = 0; i < max_columns; ++i)
+ {
+ auto name =
interpolate_html.evaluate("SupplementalReportColumnsNames", i);
+ if(name != empty_column_name)
+ {
+ // We currently don't have the field width information for
+ // arbitrary fields, so use fixed width that should be
+ // sufficient for almost all of them.
+ columns.emplace_back
+ (illustration_table_column
+ {std::move(name)
+
,interpolate_html.evaluate("SupplementalReportColumnsTitles", i)
+ ,"999,999"
+ }
+ );
+ }
+ }
+
+ return columns;
+ }
+
+ illustration_table_columns const columns_ ;
+ std::string const page_template_;
+};
+
+class reg_supplemental_report : public standard_supplemental_report
+{
+ public:
+ explicit reg_supplemental_report(html_interpolator const& interpolate_html)
+ :standard_supplemental_report(interpolate_html, "reg_supp_report")
+ {
+ }
+
+ private:
+ std::string get_upper_footer_template_name() const override
+ {
+ return "reg_footer_disclaimer";
+ }
+};
+
+// Regular illustration.
+class pdf_illustration_regular : public pdf_illustration
+{
+ public:
+ pdf_illustration_regular(Ledger const& ledger
+ ,fs::path const& output
+ )
+ :pdf_illustration(ledger, output)
+ {
+ auto const& invar = ledger.GetLedgerInvariant();
+ auto const& policy_name = invar.PolicyLegalName;
+ auto const& state_abbrev = invar.GetStatePostalAbbrev();
+
+ // Define variables specific to this illustration which doesn't use the
+ // standard 60/30 lengths for whatever reason.
+ add_abbreviated_variable("CorpName", 50);
+ add_abbreviated_variable("Insured1", 50);
+
+ add_variable
+ ("ModifiedSinglePremium"
+ ,starts_with(policy_name, "Single") && state_abbrev == "MA"
+ );
+
+ add_variable
+ ("ModifiedSinglePremium0"
+ ,starts_with(policy_name, "Modified")
+ );
+
+ add_variable
+ ("ModifiedSinglePremiumOrModifiedSinglePremium0"
+ , test_variable("ModifiedSinglePremium")
+ ||test_variable("ModifiedSinglePremium0")
+ );
+
+ add_variable
+ ("SinglePremium"
+ ,starts_with(policy_name, "Single") || starts_with(policy_name,
"Modified")
+ );
+
+ add_variable
+ ("GroupExperienceRating"
+ ,policy_name == "Group Flexible Premium Adjustable Life Insurance
Policy"
+ );
+
+ // Variable representing the premium payment frequency with the
+ // appropriate indefinite article preceding it, e.g. "an annual"
+ // or "a monthly".
+ auto const mode0 = invar.InitErMode;
+ if(!mode0.empty())
+ {
+ auto const mode0_first = lmi_tolower(mode0[0]);
+ add_variable
+ ("ErModeLCWithArticle"
+ ,(strchr("aeiou", mode0_first) ? "an" : "a") + mode0.substr(1)
+ );
+ }
+
+ add_variable
+ ("HasProducerCity"
+ ,invar.ProducerCity != "0"
+ );
+
+ add_variable
+ ("HasInterestDisclaimer"
+ ,!invar.InterestDisclaimer.empty()
+ );
+
+ add_variable
+ ("HasGuarPrem"
+ ,invar.GuarPrem != 0
+ );
+
+ add_variable
+ ("StateIsIllinois"
+ ,state_abbrev == "IL"
+ );
+
+ add_variable
+ ("StateIsTexas"
+ ,state_abbrev == "TX"
+ );
+
+ add_variable
+ ("StateIsIllinoisOrTexas"
+ ,state_abbrev == "IL" || state_abbrev == "TX"
+ );
+
+ add_variable
+ ("UltimateInterestRate"
+ ,evaluate("AnnGAIntRate_Current", invar.InforceYear + 1)
+ );
+
+ auto const max_duration = invar.EndtAge - invar.Age;
+ auto const lapse_year_guaruanteed = ledger.GetGuarFull().LapseYear;
+ auto const lapse_year_midpoint = ledger.GetMdptFull().LapseYear;
+ auto const lapse_year_current = ledger.GetCurrFull().LapseYear;
+
+ add_variable
+ ("LapseYear_Guaranteed_LT_MaxDuration"
+ ,lapse_year_guaruanteed < max_duration
+ );
+
+ add_variable
+ ("LapseYear_Guaranteed_Plus1"
+ ,bourn_cast<int>(lapse_year_guaruanteed) + 1
+ );
+
+ add_variable
+ ("LapseYear_Midpoint_LT_MaxDuration"
+ ,lapse_year_midpoint < max_duration
+ );
+
+ add_variable
+ ("LapseYear_Midpoint_Plus1"
+ ,bourn_cast<int>(lapse_year_midpoint) + 1
+ );
+
+ add_variable
+ ("LapseYear_Current_LT_MaxDuration"
+ ,lapse_year_current < max_duration
+ );
+
+ add_variable
+ ("LapseYear_Current_Plus1"
+ ,bourn_cast<int>(lapse_year_current) + 1
+ );
+
+ // Add all the pages.
+ add<cover_page>();
+ numbered_page::start_numbering();
+ add<standard_page>("reg_narr_summary");
+ add<standard_page>("reg_narr_summary2");
+ add<standard_page>("reg_column_headings");
+ if(!invar.IsInforce)
+ {
+ add<reg_numeric_summary_or_attachment_page<false>>();
+ }
+ add<reg_tabular_detail_page>();
+ add<reg_tabular_detail2_page>();
+ if(invar.SupplementalReport)
+ {
+ add<reg_supplemental_report>(get_interpolator());
+ }
+ if(!invar.IsInforce)
+ {
+ add<reg_numeric_summary_or_attachment_page<true>>();
+ }
+ }
+
+ std::string get_upper_footer_template_name() const override
+ { return {}; }
+ std::string get_lower_footer_template_name() const override
+ { return "reg_footer"; }
+};
+
+// Common base class for basic illustration pages using the same columns in
+// both NASD and private group placement illustrations.
+class page_with_basic_tabular_report : public page_with_tabular_report
+{
+ private:
+ // This method must be overridden to return the text of the super-header
+ // used for all pairs of "cash surrogate value" and "death benefit"
+ // columns. The return value is subject to HTML interpolation and so may
+ // contain {{variables}} and also can be multiline but, if so, it must have
+ // the same number of lines for all input arguments.
+ //
+ // The base and interest_rate arguments can be used to construct the full
+ // name of the variable appropriate for the current column pair, with the
+ // help of base_suffix() and ir_suffix() functions.
+ virtual std::string get_two_column_header
+ (base guar_or_curr
+ ,interest_rate zero_or_not
+ ) const = 0;
+
+ enum
+ {column_policy_year
+ ,column_end_of_year_age
+ ,column_premium_outlay
+ ,column_guar0_cash_surr_value
+ ,column_guar0_death_benefit
+ ,column_separator_guar0_guar
+ ,column_guar_cash_surr_value
+ ,column_guar_death_benefit
+ ,column_separator_guar_curr0
+ ,column_curr0_cash_surr_value
+ ,column_curr0_death_benefit
+ ,column_separator_curr0_curr
+ ,column_curr_cash_surr_value
+ ,column_curr_death_benefit
+ ,column_max
+ };
+
+ illustration_table_columns const& get_table_columns() const override
+ {
+ static illustration_table_columns const columns =
+ {{ "PolicyYear" , "Policy\nYear" , "999"
}
+ ,{ "AttainedAge" , "End of\nYear Age" , "999"
}
+ ,{ "GrossPmt" , "Premium\nOutlay" , "999,999"
}
+ ,{ "CSVNet_GuaranteedZero" , "Cash Surr\nValue" , "999,999"
}
+ ,{ "EOYDeathBft_GuaranteedZero" , "Death\nBenefit" , "9,999,999"
}
+ ,{ "" , " " , "-"
}
+ ,{ "CSVNet_Guaranteed" , "Cash Surr\nValue" , "999,999"
}
+ ,{ "EOYDeathBft_Guaranteed" , "Death\nBenefit" , "9,999,999"
}
+ ,{ "" , " " , "-"
}
+ ,{ "CSVNet_CurrentZero" , "Cash Surr\nValue" , "999,999"
}
+ ,{ "EOYDeathBft_CurrentZero" , "Death\nBenefit" , "9,999,999"
}
+ ,{ "" , " " , "-"
}
+ ,{ "CSVNet_Current" , "Cash Surr\nValue" , "999,999"
}
+ ,{ "EOYDeathBft_Current" , "Death\nBenefit" , "9,999,999"
}
+ };
+
+ return columns;
+ }
+
+ bool should_show_column(Ledger const& ledger, int column) const override
+ {
+ // One column should be hidden for composite ledgers.
+ return column != column_end_of_year_age || !ledger.is_composite();
+ }
+
+ void render_or_measure_extra_headers
+ (illustration_table_generator& table
+ ,html_interpolator const& interpolate_html
+ ,int* pos_y
+ ,enum_output_mode output_mode
+ ) const override
+ {
+ // Output the first super header row.
+
+ auto pos_y_copy = *pos_y;
+ table.output_super_header
+ ("Using guaranteed charges"
+ ,column_guar0_cash_surr_value
+ ,column_separator_guar_curr0
+ ,pos_y
+ ,output_mode
+ );
+
+ *pos_y = pos_y_copy;
+ table.output_super_header
+ ("Using current charges"
+ ,column_curr0_cash_surr_value
+ ,column_max
+ ,pos_y
+ ,output_mode
+ );
+
+ *pos_y += table.get_separator_line_height();
+ table.output_horz_separator
+ (column_guar0_cash_surr_value
+ ,column_separator_guar_curr0
+ ,*pos_y
+ ,output_mode
+ );
+ table.output_horz_separator
+ (column_curr0_cash_surr_value
+ ,column_max
+ ,*pos_y
+ ,output_mode
+ );
+
+ // Output the second super header row which is composed of three
+ // physical lines.
+
+ // This function outputs all lines of a single header, corresponding to
+ // the "Guaranteed" or "Current", "Zero" or not, column and returns the
+ // vertical position below the header.
+ auto const output_two_column_super_header = [=,&table]
+ (base guar_or_curr
+ ,interest_rate zero_or_not
+ ,std::size_t begin_column
+ ) -> int
+ {
+ std::size_t end_column = begin_column + 2;
+ LMI_ASSERT(end_column <= column_max);
+
+ auto y = *pos_y;
+
+ auto const header = get_two_column_header
+ (guar_or_curr
+ ,zero_or_not
+ );
+ table.output_super_header
+ (interpolate_html(header).as_html()
+ ,begin_column
+ ,end_column
+ ,&y
+ ,output_mode
+ );
+
+ y += table.get_separator_line_height();
+ table.output_horz_separator
+ (begin_column
+ ,end_column
+ ,y
+ ,output_mode
+ );
+
+ return y;
+ };
+
+ output_two_column_super_header
+ (base::guaranteed
+ ,interest_rate::zero
+ ,column_guar0_cash_surr_value
+ );
+
+ output_two_column_super_header
+ (base::guaranteed
+ ,interest_rate::non_zero
+ ,column_guar_cash_surr_value
+ );
+
+ output_two_column_super_header
+ (base::current
+ ,interest_rate::zero
+ ,column_curr0_cash_surr_value
+ );
+
+ *pos_y = output_two_column_super_header
+ (base::current
+ ,interest_rate::non_zero
+ ,column_curr_cash_surr_value
+ );
+ }
+};
+
+class nasd_basic : public page_with_basic_tabular_report
+{
+ private:
+ std::string get_fixed_page_contents_template_name() const override
+ {
+ return "nasd_basic";
+ }
+
+ std::string get_two_column_header
+ (base guar_or_curr
+ ,interest_rate zero_or_not
+ ) const override
+ {
+ std::ostringstream oss;
+ oss
+ << "{{InitAnnSepAcctGrossInt_"
+ << base_suffix(guar_or_curr)
+ << ir_suffix(zero_or_not)
+ << "}} "
+ << "Assumed Sep Acct\n"
+ << "Gross Rate* "
+ << "({{InitAnnSepAcctNetInt_"
+ << base_suffix(guar_or_curr)
+ << ir_suffix(zero_or_not)
+ << "}} net)\n"
+ << "{{InitAnnGenAcctInt_"
+ << base_suffix(guar_or_curr)
+ << "}} GPA rate"
+ ;
+ return oss.str();
+ }
+};
+
+class nasd_supplemental : public page_with_tabular_report
+{
+ private:
+ enum
+ {column_policy_year
+ ,column_end_of_year_age
+ ,column_er_gross_payment
+ ,column_ee_gross_payment
+ ,column_premium_outlay
+ ,column_admin_charge
+ ,column_premium_tax_load
+ ,column_dac_tax_load
+ ,column_er_min_premium
+ ,column_ee_min_premium
+ ,column_net_premium
+ ,column_cost_of_insurance_charges
+ ,column_curr_account_value
+ ,column_curr_cash_surr_value
+ ,column_curr_death_benefit
+ ,column_max
+ };
+
+ std::string get_fixed_page_contents_template_name() const override
+ {
+ return "nasd_supp";
+ }
+
+ illustration_table_columns const& get_table_columns() const override
+ {
+ static illustration_table_columns const columns =
+ {{ "PolicyYear" , "Policy\nYear" ,
"999" }
+ ,{ "AttainedAge" , "End of\nYear Age" ,
"999" }
+ ,{ "ErGrossPmt" , "ER Gross\nPayment" ,
"999,999" }
+ ,{ "EeGrossPmt" , "EE Gross\nPayment" ,
"999,999" }
+ ,{ "GrossPmt" , "Premium\nOutlay" ,
"999,999" }
+ ,{ "PolicyFee_Current" , "Admin\nCharge" ,
"999,999" }
+ ,{ "PremTaxLoad_Current" , "Premium\nTax Load" ,
"999,999" }
+ ,{ "DacTaxLoad_Current" , "DAC\nTax Load" ,
"999,999" }
+ ,{ "ErModalMinimumPremium", "ER Modal\nMinimum\nPremium" ,
"999,999" }
+ ,{ "EeModalMinimumPremium", "EE Modal\nMinimum\nPremium" ,
"999,999" }
+ ,{ "NetPmt_Current" , "Net\nPremium" ,
"999,999" }
+ ,{ "COICharge_Current" , "Cost of\nInsurance\nCharges",
"999,999" }
+ ,{ "AcctVal_Current" , "Current\nAccount\nValue" ,
"999,999" }
+ ,{ "CSVNet_Current" , "Current\nCash Surr\nValue" ,
"999,999" }
+ ,{ "EOYDeathBft_Current" , "Current\nDeath\nBenefit" ,
"9,999,999" }
+ };
+
+ return columns;
+ }
+
+ bool should_show_column(Ledger const& ledger, int column) const override
+ {
+ auto const& invar = ledger.GetLedgerInvariant();
+
+ // The supplemental page in NASD illustrations exists in two versions:
+ // default one and one with split premiums. Hide columns that are not
+ // needed for the current illustration.
+ switch(column)
+ {
+ case column_end_of_year_age:
+ // This column doesn't make sense for composite ledgers.
+ return !ledger.is_composite();
+
+ case column_admin_charge:
+ case column_premium_tax_load:
+ case column_dac_tax_load:
+ // These columns only appear in non-split premiums case.
+ return invar.SplitMinPrem == 0.;
+
+ case column_er_gross_payment:
+ case column_ee_gross_payment:
+ case column_er_min_premium:
+ case column_ee_min_premium:
+ // While those only appear in split premiums case.
+ return invar.SplitMinPrem == 1.;
+
+ case column_policy_year:
+ case column_premium_outlay:
+ case column_net_premium:
+ case column_cost_of_insurance_charges:
+ case column_curr_account_value:
+ case column_curr_cash_surr_value:
+ case column_curr_death_benefit:
+ case column_max:
+ // These columns are common to both cases and never hidden.
+ break;
+ }
+
+ return true;
+ }
+};
+
+class nasd_assumption_detail : public page_with_tabular_report
+{
+ private:
+ enum
+ {column_policy_year
+ ,column_end_of_year_age
+ ,column_sep_acct_crediting_rate
+ ,column_gen_acct_crediting_rate
+ ,column_m_and_e
+ ,column_ee_payment_mode
+ ,column_er_payment_mode
+ ,column_assumed_loan_interest
+ ,column_max
+ };
+
+ std::string get_fixed_page_contents_template_name() const override
+ {
+ return "nasd_assumption_detail";
+ }
+
+ illustration_table_columns const& get_table_columns() const override
+ {
+ static illustration_table_columns const columns =
+ {{ "PolicyYear" , "Policy\nYear" ,
"999" }
+ ,{ "AttainedAge" , "End of\nYear Age" ,
"999" }
+ ,{ "AnnSAIntRate_Current", "Sep Acct Net\nInv Rate" ,
"99.99%" }
+ ,{ "AnnGAIntRate_Current", "Gen Acct\nCurrent Rate" ,
"99.99%" }
+ ,{ "CurrMandE" , "M&E" ,
"99.99%" }
+ ,{ "EeMode" , "Indiv\nPmt Mode" ,
"Semiannual" }
+ ,{ "ErMode" , "Corp\nPmt Mode" ,
"Semiannual" }
+ ,{ "InitAnnLoanDueRate" , "Assumed\nLoan Interest" ,
"99.99%" }
+ };
+
+ return columns;
+ }
+
+ // Notice that there is no need to override should_show_column() in this
+ // class as this page is not included in composite illustrations and hence
+ // all of its columns, including the "AttainedAge" one, are always shown.
+};
+
+// NASD illustration.
+class pdf_illustration_nasd : public pdf_illustration
+{
+ public:
+ pdf_illustration_nasd
+ (Ledger const& ledger
+ ,fs::path const& output
+ )
+ :pdf_illustration(ledger, output)
+ {
+ auto const& invar = ledger.GetLedgerInvariant();
+
+ // Define variables specific to this illustration.
+ if(!invar.ContractName.empty())
+ {
+ std::string s = invar.ContractName;
+ for(auto& c : s)
+ {
+ c = lmi_tolower(c);
+ }
+ s[0] = lmi_toupper(s[0]);
+
+ add_variable("ContractNameCap", s);
+ }
+
+ add_variable
+ ("UWTypeIsGuaranteedIssueInTexasWithFootnote"
+ ,invar.UWType == "Guaranteed issue"
+ );
+
+ add_variable
+ ("HasTermOrSupplSpecAmt"
+ ,test_variable("HasTerm") || test_variable("HasSupplSpecAmt")
+ );
+
+ auto const& state_abbrev = invar.GetStatePostalAbbrev();
+ add_variable
+ ("StateIsNewYork"
+ ,state_abbrev == "NY"
+ );
+
+ // Add all the pages.
+ add<cover_page>();
+ numbered_page::start_numbering();
+ add<nasd_basic>();
+ add<nasd_supplemental>();
+ add<standard_page>("nasd_column_headings");
+ add<standard_page>("nasd_notes1");
+ add<standard_page>("nasd_notes2");
+ if(!ledger.is_composite())
+ {
+ add<nasd_assumption_detail>();
+ }
+ if(invar.SupplementalReport)
+ {
+ add<standard_supplemental_report>
+ (get_interpolator()
+ ,"nasd_supp_report"
+ );
+ }
+ }
+
+ std::string get_upper_footer_template_name() const override
+ {
+ return "nasd_footer_upper";
+ }
+
+ std::string get_lower_footer_template_name() const override
+ {
+ return "nasd_footer_lower";
+ }
+};
+
+// Basic illustration page of the private group placement illustration.
+class reg_d_group_basic : public page_with_basic_tabular_report
+{
+ private:
+ std::string get_fixed_page_contents_template_name() const override
+ {
+ return "reg_d_group_basic";
+ }
+
+ std::string get_two_column_header
+ (base guar_or_curr
+ ,interest_rate zero_or_not
+ ) const override
+ {
+ std::ostringstream oss;
+ oss
+ << "{{InitAnnSepAcctGrossInt_"
+ << base_suffix(guar_or_curr)
+ << ir_suffix(zero_or_not)
+ << "}} "
+ << "Hypothetical Gross\n"
+ << "Return ({{InitAnnSepAcctNetInt_"
+ << base_suffix(guar_or_curr)
+ << ir_suffix(zero_or_not)
+ << "}} net)"
+ ;
+ return oss.str();
+ }
+};
+
+// Private group placement illustration.
+class pdf_illustration_reg_d_group : public pdf_illustration
+{
+ public:
+ pdf_illustration_reg_d_group
+ (Ledger const& ledger
+ ,fs::path const& output
+ )
+ :pdf_illustration(ledger, output)
+ {
+ // Define variables specific to this illustration.
+ auto const& invar = ledger.GetLedgerInvariant();
+
+ add_variable
+ ("MecYearIs0"
+ ,invar.MecYear == 0
+ );
+
+ // Add all the pages.
+ add<cover_page>();
+ numbered_page::start_numbering();
+ add<reg_d_group_basic>();
+ add<standard_page>("reg_d_group_column_headings");
+ add<standard_page>("reg_d_group_narr_summary");
+ add<standard_page>("reg_d_group_narr_summary2");
+ if(invar.SupplementalReport)
+ {
+ add<standard_supplemental_report>
+ (get_interpolator()
+ ,"reg_d_group_supp_report"
+ );
+ }
+ }
+
+ std::string get_upper_footer_template_name() const override
+ {
+ return "reg_d_group_footer_upper";
+ }
+
+ std::string get_lower_footer_template_name() const override
+ {
+ return "reg_d_group_footer_lower";
+ }
+};
+
+// This page exists in two almost identical versions, one using guaranteed and
+// the other one using current values, use a base class to share the common
+// parts.
+class reg_d_individual_irr_base : public page_with_tabular_report
+{
+ private:
+ enum
+ {column_policy_year
+ ,column_end_of_year_age
+ ,column_premium_outlay
+ ,column_zero_cash_surr_value
+ ,column_zero_death_benefit
+ ,column_zero_irr_surr_value
+ ,column_zero_irr_death_benefit
+ ,column_separator
+ ,column_nonzero_cash_surr_value
+ ,column_nonzero_death_benefit
+ ,column_nonzero_irr_surr_value
+ ,column_nonzero_irr_death_benefit
+ ,column_max
+ };
+
+ // Must be overridden to return the base being used.
+ virtual base get_base() const = 0;
+
+ bool should_show_column(Ledger const& ledger, int column) const override
+ {
+ // One column should be hidden for composite ledgers.
+ return column != column_end_of_year_age || !ledger.is_composite();
+ }
+
+ void render_or_measure_extra_headers
+ (illustration_table_generator& table
+ ,html_interpolator const& interpolate_html
+ ,int* pos_y
+ ,enum_output_mode output_mode
+ ) const override
+ {
+ std::ostringstream header_zero;
+ header_zero
+ << "{{InitAnnSepAcctGrossInt_"
+ << base_suffix(get_base())
+ << ir_suffix(interest_rate::zero)
+ << "}} Hypothetical Rate of\n"
+ << "Return*"
+ ;
+
+ auto pos_y_copy = *pos_y;
+ table.output_super_header
+ (interpolate_html(header_zero.str()).as_html()
+ ,column_zero_cash_surr_value
+ ,column_zero_irr_surr_value
+ ,pos_y
+ ,output_mode
+ );
+
+ std::ostringstream header_nonzero;
+ header_nonzero
+ << "{{InitAnnSepAcctGrossInt_"
+ << base_suffix(get_base())
+ << ir_suffix(interest_rate::non_zero)
+ << "}} Hypothetical Rate of\n"
+ << "Return*"
+ ;
+
+ *pos_y = pos_y_copy;
+ table.output_super_header
+ (interpolate_html(header_nonzero.str()).as_html()
+ ,column_nonzero_cash_surr_value
+ ,column_nonzero_irr_surr_value
+ ,pos_y
+ ,output_mode
+ );
+
+ *pos_y += table.get_separator_line_height();
+ table.output_horz_separator
+ (column_zero_cash_surr_value
+ ,column_zero_irr_surr_value
+ ,*pos_y
+ ,output_mode
+ );
+ table.output_horz_separator
+ (column_nonzero_cash_surr_value
+ ,column_nonzero_irr_surr_value
+ ,*pos_y
+ ,output_mode
+ );
+ }
+};
+
+class reg_d_individual_guar_irr : public reg_d_individual_irr_base
+{
+ private:
+ base get_base() const override
+ {
+ return base::guaranteed;
+ }
+
+ std::string get_fixed_page_contents_template_name() const override
+ {
+ return "reg_d_indiv_guar_irr";
+ }
+
+ illustration_table_columns const& get_table_columns() const override
+ {
+ static illustration_table_columns const columns =
+ {{ "PolicyYear" , "Policy\nYear" ,
"999" }
+ ,{ "AttainedAge" , "End of\nYear Age" ,
"999" }
+ ,{ "GrossPmt" , "Premium\nOutlay" ,
"999,999" }
+ ,{ "CSVNet_GuaranteedZero" , "Cash Surr\nValue" ,
"999,999" }
+ ,{ "EOYDeathBft_GuaranteedZero" , "Death\nBenefit" ,
"9,999,999" }
+ ,{ "IrrCsv_GuaranteedZero" , "IRR on\nSurr Value" ,
"99.99%" }
+ ,{ "IrrDb_GuaranteedZero" , "IRR on\nDeath Bft" ,
"99.99%" }
+ ,{ "" , " " ,
"-" }
+ ,{ "CSVNet_Guaranteed" , "Cash Surr\nValue" ,
"999,999" }
+ ,{ "EOYDeathBft_Guaranteed" , "Death\nBenefit" ,
"9,999,999" }
+ ,{ "IrrCsv_Guaranteed" , "IRR on\nSurr Value" ,
"99.99%" }
+ ,{ "IrrDb_Guaranteed" , "IRR on\nDeath Bft" ,
"99.99%" }
+ };
+
+ return columns;
+ }
+};
+
+class reg_d_individual_curr_irr : public reg_d_individual_irr_base
+{
+ private:
+ base get_base() const override
+ {
+ return base::current;
+ }
+
+ std::string get_fixed_page_contents_template_name() const override
+ {
+ return "reg_d_indiv_curr_irr";
+ }
+
+ illustration_table_columns const& get_table_columns() const override
+ {
+ static illustration_table_columns const columns =
+ {{ "PolicyYear" , "Policy\nYear" ,
"999" }
+ ,{ "AttainedAge" , "End of\nYear Age" ,
"999" }
+ ,{ "GrossPmt" , "Premium\nOutlay" ,
"999,999" }
+ ,{ "CSVNet_CurrentZero" , "Cash Surr\nValue" ,
"999,999" }
+ ,{ "EOYDeathBft_CurrentZero" , "Death\nBenefit" ,
"9,999,999" }
+ ,{ "IrrCsv_CurrentZero" , "IRR on\nSurr Value" ,
"99.99%" }
+ ,{ "IrrDb_CurrentZero" , "IRR on\nDeath Bft" ,
"99.99%" }
+ ,{ "" , " " ,
"-" }
+ ,{ "CSVNet_Current" , "Cash Surr\nValue" ,
"999,999" }
+ ,{ "EOYDeathBft_Current" , "Death\nBenefit" ,
"9,999,999" }
+ ,{ "IrrCsv_Current" , "IRR on\nSurr Value" ,
"99.99%" }
+ ,{ "IrrDb_Current" , "IRR on\nDeath Bft" ,
"99.99%" }
+ };
+
+ return columns;
+ }
+};
+
+class reg_d_individual_curr : public page_with_tabular_report
+{
+ private:
+ enum
+ {column_policy_year
+ ,column_end_of_year_age
+ ,column_premium_outlay
+ ,column_premium_loads
+ ,column_admin_charges
+ ,column_curr_mortality_charges
+ ,column_curr_asset_charges
+ ,column_curr_investment_income
+ ,column_curr_account_value
+ ,column_curr_cash_surr_value
+ ,column_curr_death_benefit
+ ,column_max
+ };
+
+ std::string get_fixed_page_contents_template_name() const override
+ {
+ return "reg_d_indiv_curr";
+ }
+
+ illustration_table_columns const& get_table_columns() const override
+ {
+ static illustration_table_columns const columns =
+ {{ "PolicyYear" , "Policy\nYear" , "999" }
+ ,{ "AttainedAge" , "End of\nYear Age" , "999" }
+ ,{ "GrossPmt" , "Premium\nOutlay" , "999,999" }
+ ,{ "PremiumLoads" , "Premium\nLoads" , "999,999" }
+ ,{ "AdminCharges" , "Admin\nCharges" , "999,999" }
+ ,{ "COICharge_Current" , "Mortality\nCharges", "999,999" }
+ ,{ "SepAcctCharges_Current" , "Asset\nCharges" , "999,999" }
+ ,{ "GrossIntCredited_Current", "Investment\nIncome", "999,999" }
+ ,{ "AcctVal_Current" , "Account\nValue" , "999,999" }
+ ,{ "CSVNet_Current" , "Cash\nSurr Value" , "999,999" }
+ ,{ "EOYDeathBft_Current" , "Death\nBenefit" , "9,999,999" }
+ };
+
+ return columns;
+ }
+
+ bool should_show_column(Ledger const& ledger, int column) const override
+ {
+ // One column should be hidden for composite ledgers.
+ return column != column_end_of_year_age || !ledger.is_composite();
+ }
+
+ void render_or_measure_extra_headers
+ (illustration_table_generator& table
+ ,html_interpolator const& interpolate_html
+ ,int* pos_y
+ ,enum_output_mode output_mode
+ ) const override
+ {
+ table.output_super_header
+ (interpolate_html
+ ("{{InitAnnSepAcctGrossInt_Guaranteed}} Hypothetical Rate of
Return*"
+ ).as_html()
+ ,column_curr_investment_income
+ ,column_max
+ ,pos_y
+ ,output_mode
+ );
+
+ *pos_y += table.get_separator_line_height();
+ table.output_horz_separator
+ (column_curr_investment_income
+ ,column_max
+ ,*pos_y
+ ,output_mode
+ );
+ }
+};
+
+// Private individual placement illustration.
+class pdf_illustration_reg_d_individual : public pdf_illustration
+{
+ public:
+ pdf_illustration_reg_d_individual
+ (Ledger const& ledger
+ ,fs::path const& output
+ )
+ :pdf_illustration(ledger, output)
+ {
+ auto const& invar = ledger.GetLedgerInvariant();
+
+ // Define variables specific to this illustration.
+ add_abbreviated_variable("CorpName", 140);
+ add_abbreviated_variable("Insured1", 140);
+
+ // Add all the pages.
+ numbered_page::start_numbering();
+ add<standard_page>("reg_d_indiv_cover_page");
+ add<reg_d_individual_guar_irr>();
+ add<reg_d_individual_curr_irr>();
+ add<reg_d_individual_curr>();
+ add<standard_page>("reg_d_indiv_notes1");
+ add<standard_page>("reg_d_indiv_notes2");
+ add<standard_page>("reg_d_indiv_notes3");
+ if(invar.SupplementalReport)
+ {
+ add<standard_supplemental_report>
+ (get_interpolator()
+ ,"reg_d_indiv_supp_report"
+ );
+ }
+ }
+
+ std::string get_upper_footer_template_name() const override
+ {
+ return "reg_d_indiv_footer_upper";
+ }
+
+ std::string get_lower_footer_template_name() const override
+ {
+ return "reg_d_indiv_footer_lower";
+ }
+};
+
+class ledger_pdf_generator_wx : public ledger_pdf_generator
+{
+ public:
+ static std::shared_ptr<ledger_pdf_generator> do_create()
+ {
+ return std::make_shared<ledger_pdf_generator_wx>();
+ }
+
+ ledger_pdf_generator_wx() = default;
+ ledger_pdf_generator_wx(ledger_pdf_generator_wx const&) = delete;
+ ledger_pdf_generator_wx& operator=(ledger_pdf_generator_wx const&) =
delete;
+
+ void write(Ledger const& ledger, fs::path const& output) override;
+
+ private:
+};
+
+void ledger_pdf_generator_wx::write
+ (Ledger const& ledger
+ ,fs::path const& output
+ )
+{
+ std::unique_ptr<pdf_illustration> pdf_ill;
+
+ auto const z = ledger.ledger_type();
+ switch(z)
+ {
+ case mce_ill_reg:
+ pdf_ill = std::make_unique<pdf_illustration_regular>(ledger,
output);
+ break;
+ case mce_nasd:
+ pdf_ill = std::make_unique<pdf_illustration_nasd>(ledger, output);
+ break;
+ case mce_group_private_placement:
+ pdf_ill = std::make_unique<pdf_illustration_reg_d_group>(ledger,
output);
+ break;
+ case mce_individual_private_placement:
+ pdf_ill =
std::make_unique<pdf_illustration_reg_d_individual>(ledger, output);
+ break;
+ default:
+ alarum() << "Unknown ledger type '" << z << "'." << LMI_FLUSH;
+ }
+
+ pdf_ill->render_all();
+}
+
+volatile bool ensure_setup = ledger_pdf_generator::set_creator
+ (ledger_pdf_generator_wx::do_create
+ );
+
+} // Unnamed namespace.
diff --git a/ledger_xsl.cpp b/ledger_xsl.cpp
index 99a721e..bdbecb4 100644
--- a/ledger_xsl.cpp
+++ b/ledger_xsl.cpp
@@ -94,11 +94,13 @@ fs::path xsl_filepath(Ledger const& ledger)
/// filenames must be transformed is that apache fop is java, and
/// java is "portable".
-std::string write_ledger_as_pdf(Ledger const& ledger, fs::path const& filepath)
+std::string write_ledger_as_pdf_via_xsl(Ledger const& ledger, fs::path const&
filepath)
{
throw_if_interdicted(ledger);
fs::path print_dir(configurable_settings::instance().print_directory());
+ // Old implementation: write to a distinctive subdirectory.
+ print_dir = print_dir / "old";
fs::path real_filepath(orthodox_filename(filepath.leaf()));
LMI_ASSERT(fs::portable_name(real_filepath.string()));
diff --git a/ledger_xsl.hpp b/ledger_xsl.hpp
index dd2af40..6f99f31 100644
--- a/ledger_xsl.hpp
+++ b/ledger_xsl.hpp
@@ -30,7 +30,7 @@
class Ledger;
-std::string write_ledger_as_pdf(Ledger const&, fs::path const&);
+std::string write_ledger_as_pdf_via_xsl(Ledger const&, fs::path const&);
fs::path xsl_filepath(Ledger const&);
diff --git a/main_wx.cpp b/main_wx.cpp
index 8a96920..6f9ad4a 100644
--- a/main_wx.cpp
+++ b/main_wx.cpp
@@ -46,6 +46,7 @@
LMI_FORCE_LINKING_EX_SITU(alert_wx)
LMI_FORCE_LINKING_EX_SITU(file_command_wx)
LMI_FORCE_LINKING_EX_SITU(group_quote_pdf_generator_wx)
+LMI_FORCE_LINKING_EX_SITU(ledger_pdf_generator_wx)
LMI_FORCE_LINKING_EX_SITU(progress_meter_wx)
LMI_FORCE_LINKING_EX_SITU(system_command_wx)
diff --git a/main_wx_test.cpp b/main_wx_test.cpp
index 28b7211..64ec510 100644
--- a/main_wx_test.cpp
+++ b/main_wx_test.cpp
@@ -56,6 +56,7 @@
LMI_FORCE_LINKING_EX_SITU(file_command_wx)
LMI_FORCE_LINKING_EX_SITU(group_quote_pdf_generator_wx)
+LMI_FORCE_LINKING_EX_SITU(ledger_pdf_generator_wx)
LMI_FORCE_LINKING_EX_SITU(progress_meter_wx)
LMI_FORCE_LINKING_EX_SITU(system_command_wx)
diff --git a/nasd_assumption_detail.mst b/nasd_assumption_detail.mst
new file mode 100644
index 0000000..761b74b
--- /dev/null
+++ b/nasd_assumption_detail.mst
@@ -0,0 +1,29 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>nasd_header_upper}}
+<p align="center">Illustration Assumption Detail</p>
+{{>nasd_header_lower}}
+
+<br></br>
+<br></br>
+
+{{! Illustration assumption detail table is created from C++ code }}
diff --git a/nasd_basic.mst b/nasd_basic.mst
new file mode 100644
index 0000000..a0c4122
--- /dev/null
+++ b/nasd_basic.mst
@@ -0,0 +1,35 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>nasd_header_upper}}
+{{#IsInforce}}
+ <p align="center">
+ In Force Illustration
+ </p>
+{{/IsInforce}}
+{{>nasd_header_lower}}
+
+{{>dollar_units}}
+
+<br></br>
+<br></br>
+
+{{! Basic illustration table is created from C++ code }}
diff --git a/nasd_column_headings.mst b/nasd_column_headings.mst
new file mode 100644
index 0000000..71cc54a
--- /dev/null
+++ b/nasd_column_headings.mst
@@ -0,0 +1,96 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>nasd_header}}
+
+<p align="center"><b>Column Definitions and Key Terms</b></p>
+
+<p>
+<b>Account Value</b>
+<br>
+{{AccountValueFootnote}}
+</p>
+
+<p>
+<b>Cash Surrender Value</b>
+<br>
+{{CashSurrValueFootnote}}
+</p>
+
+<p>
+<b>Death Benefit</b>
+<br>
+{{DeathBenefitFootnote}}
+</p>
+
+{{^Composite}}
+<p>
+<b>End of Year Age</b>
+<br>
+{{AttainedAgeFootnote}}
+</p>
+{{/Composite}}
+
+{{#GroupCarveout}}
+<p>
+<b>Gross Payment</b>
+<br>
+{{GrossPremiumFootnote}}
+</p>
+{{/GroupCarveout}}
+
+<p>
+<b>Gross Rate</b>
+<br>
+{{GrossRateFootnote}}
+</p>
+
+{{#GroupCarveout}}
+<p>
+<b>Minimum Premium</b>
+<br>
+{{InitialPremiumFootnote}}
+</p>
+{{/GroupCarveout}}
+
+<p>
+<b>Net Premium</b>
+<br>
+{{NetPremiumFootnote}}
+</p>
+
+<p>
+<b>Net Rate</b>
+<br>
+{{NetRateFootnote}}
+</p>
+
+<p>
+<b>Policy Year</b>
+<br>
+{{PolicyYearFootnote}}
+</p>
+
+<p>
+<b>Premium Outlay</b>
+<br>
+{{OutlayFootnote}}
+</p>
diff --git a/nasd_footer_lower.mst b/nasd_footer_lower.mst
new file mode 100644
index 0000000..f404919
--- /dev/null
+++ b/nasd_footer_lower.mst
@@ -0,0 +1,40 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<font size="-2">
+
+<table width="100%" cellspacing="0" cellpadding="0">
+ <tr>
+ <td colspan="3"> </td>
+ </tr>
+ <tr>
+ <td>{{InsCoName}}</td>
+ <td align="center">Date Prepared: {{date_prepared}}</td>
+ <td align="right">Policy Form: {{PolicyForm}}</td>
+ </tr>
+ <tr>
+ <td>{{InsCoAddr}}</td>
+ <td align="center">System Version: {{LmiVersion}}</td>
+ <td align="right">{{page_number}}</td>
+ </tr>
+</table>
+
+</font>
diff --git a/nasd_footer_upper.mst b/nasd_footer_upper.mst
new file mode 100644
index 0000000..e551e54
--- /dev/null
+++ b/nasd_footer_upper.mst
@@ -0,0 +1,34 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<font size="-2">
+
+<p>
+* This illustration is based on the assumed Gross Rate shown.
+The Net Rate is provided for information purposes only.
+<br>
+** {{PremAllocationFootnote}}
+<br>
+See the Explanatory Notes for important {{ContractName}} information.
+This illustration is not complete without all pages.
+</p>
+
+</font>
diff --git a/nasd_header.mst b/nasd_header.mst
new file mode 100644
index 0000000..4af4e2b
--- /dev/null
+++ b/nasd_header.mst
@@ -0,0 +1,29 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{!
+ The header is split in two parts, upper and lower one, only to allow
+ inserting the title between them, as a couple of pages do. But we still
+ provide this "full header" template for the pages that don't have any
+ title to make them simpler.
+}}
+{{>nasd_header_upper}}
+{{>nasd_header_lower}}
diff --git a/nasd_header_lower.mst b/nasd_header_lower.mst
new file mode 100644
index 0000000..1e7c856
--- /dev/null
+++ b/nasd_header_lower.mst
@@ -0,0 +1,84 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<br>
+<br>
+
+<table width="100%" cellspacing="0" cellpadding="0" valign="top">
+ <tr>
+ <td>
+ {{#Composite}}
+ {{CorpNameAbbrev60}}
+ {{/Composite}}
+ {{^Composite}}
+ {{Insured1Abbrev30}}
+ {{/Composite}},
+ {{Gender}} {{Smoker}} rates, Age {{Age}}<br>
+ {{#Composite}}
+ Composite<br>
+ {{/Composite}}
+ {{ContractNameCap}}: {{PolicyMktgName}}<br>
+ {{#GroupCarveout}}
+ Minimum Initial Premium: ${{InitMinPrem}}<br>
+ {{/GroupCarveout}}
+ First Year Premium: ${{InitPrem}}<br>
+ {{^Composite}}
+ {{#UWTypeIsMedical}}
+ Fully underwritten,
+ {{/UWTypeIsMedical}}
+ {{^UWTypeIsMedical}}
+ {{#UWTypeIsGuaranteedIssueInTexasWithFootnote}}
+ Substandard ***,
+ {{/UWTypeIsGuaranteedIssueInTexasWithFootnote}}
+ {{^UWTypeIsGuaranteedIssueInTexasWithFootnote}}
+ {{UWType}},
+ {{/UWTypeIsGuaranteedIssueInTexasWithFootnote}}
+ {{/UWTypeIsMedical}}
+ {{UWClass}}, Initial Death Benefit Option: {{InitDBOpt}}<br>
+ {{#UWClassIsRated}}
+ Table Rating: {{SubstandardTable}}<br>
+ {{/UWClassIsRated}}
+ {{/Composite}}
+ {{>contract_numbers}}
+ </td>
+ <td>
+ Assumed Premium Allocation:**<br>
+ Separate Account:
{{GenAcctAllocationComplementPercent}}<br>
+ Guaranteed Principal Account (GPA):
{{GenAcctAllocationPercent}}<br>
+ Initial
+ {{#HasTermOrSupplSpecAmt}}
+ Total
+ {{/HasTermOrSupplSpecAmt}}
+ Face Amount: ${{InitTotalSA}}<br>
+ {{#HasTermOrSupplSpecAmt}}
+ Initial Base Face Amount: ${{InitBaseSpecAmt}}<br>
+ Initial
+ {{#HasTerm}}
+ Term Face Amount:
+ {{/HasTerm}}
+ {{#HasSupplSpecAmt}}
+ Supplemental Face Amount:
+ {{/HasSupplSpecAmt}}
+ ${{InitTermSpecAmt}}<br>
+ {{/HasTermOrSupplSpecAmt}}
+ </td>
+ </tr>
+</table>
diff --git a/nasd_header_upper.mst b/nasd_header_upper.mst
new file mode 100644
index 0000000..b7fd5ae
--- /dev/null
+++ b/nasd_header_upper.mst
@@ -0,0 +1,39 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<scaled_image inv_factor="0.36" src="company_logo.png"></scaled_image>
+
+<br></br>
+<br></br>
+
+<p>
+The purpose of this illustration is to show how the performance
+of the underlying investment account could affect the
+{{ContractName}} account value and death benefit.
+<b>
+These hypothetical returns do not reflect past performance
+and are not predictive of future results. Actual results could be
+less than or greater than the hypothetical results
+and in all likelihood will vary from year to year.
+</b>
+</p>
+
+<br></br>
diff --git a/nasd_notes1.mst b/nasd_notes1.mst
new file mode 100644
index 0000000..38c461c
--- /dev/null
+++ b/nasd_notes1.mst
@@ -0,0 +1,137 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>nasd_header}}
+
+<font size="-1">
+
+<p align="center"><b>IMPORTANT TAX DISCLOSURE</b></p>
+
+<p>
+As illustrated, this {{ContractName}}
+{{#IsMec}}
+ becomes
+{{/IsMec}}
+{{^IsMec}}
+ would not become
+{{/IsMec}}
+a Modified Endowment Contract (MEC)
+under the Internal Revenue Code
+{{#IsMec}}
+ in year {{MecYearPlus1}}
+{{/IsMec}}
+To the extent of gain in the {{ContractName}}, loans, distributions
+and withdrawals from a MEC are subject to income tax
+and may also trigger a penalty tax.
+</p>
+
+{{^IsInforce}}
+ <p>
+ The initial 7-pay premium limit is ${{InitSevenPayPrem}}.
+ </p>
+{{/IsInforce}}
+
+<p>
+No tax charge is made to the Separate Account. However,
+such a charge may be made in the future.
+</p>
+
+<p>
+<b>
+The information contained in this illustration is not written
+or intended as tax or legal advice.
+Neither {{InsCoShortName}}
+nor any of its employees or representatives are authorized
+to give tax or legal advice. For more information pertaining
+to the tax consequences of purchasing or owning this
+{{ContractName}},
+consult with your own independent tax or legal counsel.
+</b>
+</p>
+
+<br></br>
+
+<p align="center"><b>EXPLANATORY NOTES</b></p>
+
+<p>
+{{PolicyMktgName}} is a {{PolicyLegalName}} issued by {{InsCoName}}.
+{{ProductDescription}}
+</p>
+
+<p>
+{{NoVanishPremiumFootnote}}
+</p>
+
+{{#NoLapse}}
+ {{^StateIsNewYork}}
+ <p>
+ {{NoLapseProvisionName}}:
+ The {{NoLapseProvisionName}} is a lapse protection feature. If met,
+ this test allows your {{ContractName}} to stay in force for a period of
+ time even if there is insufficient {{AvName}} Value to cover the
+ {{AvName}} Value charges. Refer to your {{ContractName}} for specific
+ requirements of meeting the {{NoLapseProvisionName}}.
+ </p>
+ {{/StateIsNewYork}}
+{{/NoLapse}}
+
+{{#NoLapseAlwaysActive}}
+ <p>
+ No-Lapse Guarantee: The {{ContractName}} will remain in force after the
+ first premium has been paid, even if there is insufficient {{AvName}} Value
+ to cover the monthly charges provided that the insured is not in a
+ substandard rating class and the {{ContractName}} debt does not exceed
+ {{AvName}} Value.
+ </p>
+{{/NoLapseAlwaysActive}}
+
+<p>
+The definition of life insurance elected for this
+{{ContractName}} is the
+{{#DefnLifeInsIsGPT}}
+ guideline premium test. The guideline single premium is
+ ${{InitGSP}} and the guideline
+ level premium is ${{InitGLP}}.
+{{/DefnLifeInsIsGPT}}
+{{^DefnLifeInsIsGPT}}
+ cash value accumulation test.
+{{/DefnLifeInsIsGPT}}
+</p>
+
+{{#DefnLifeInsIsGPT}}
+<p>
+{{GptFootnote}}
+</p>
+{{/DefnLifeInsIsGPT}}
+
+<p>
+{{InsCoName}} has the right to promptly refund any amount of premium paid if it
+will increase the net amount at risk (referred to in the {{ContractName}} as
+the Amount of Insurance that Requires a Charge).
+</p>
+
+<p>
+Premium payments are assumed to be made at the beginning of the year. Account
+values, cash surrender values, and death benefits are illustrated as of the end
+of the year. {{MinimumPremiumFootnote}}
+</p>
+
+</font>
diff --git a/nasd_notes2.mst b/nasd_notes2.mst
new file mode 100644
index 0000000..399d064
--- /dev/null
+++ b/nasd_notes2.mst
@@ -0,0 +1,244 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>nasd_header}}
+
+<font size="-1">
+
+<p align="center"><b>EXPLANATORY NOTES</b></p>
+
+<p>
+{{GuarMortalityFootnote}}
+</p>
+
+<p>
+This illustration assumes death of the insured at
+age {{EndtAge}}.
+</p>
+
+{{#StateIsCarolina}}
+ <p>
+ In the states of North Carolina and South Carolina, Guaranteed Issue
+ Underwriting is referred to as "Limited Underwriting" and Simplified Issue
+ Underwriting is referred to as "Simplified Underwriting".
+ </p>
+{{/StateIsCarolina}}
+
+{{#StateIsMaryland}}
+ <p>
+ In the state of Maryland, Guaranteed Issue Underwriting
+ is referred to as "Nonstandard Limited Underwriting"
+ and Simplified Issue Underwriting is referred to as
+ "Nonstandard Simplified Underwriting".
+ </p>
+{{/StateIsMaryland}}
+
+<p>
+{{LoanFootnote}}
+</p>
+
+<p>
+{{PortabilityFootnote}}
+</p>
+
+{{#HasTerm}}
+ <p>
+ {{TermFootnote}}
+ </p>
+{{/HasTerm}}
+
+{{#HasSupplSpecAmt}}
+ <p>
+ {{TermFootnote}}
+ </p>
+{{/HasSupplSpecAmt}}
+
+{{#HasWP}}
+ <p>
+ {{WaiverFootnote}}
+ </p>
+{{/HasWP}}
+
+<p>
+{{AccelBftRiderFootnote}}
+</p>
+
+{{#DefnLifeInsIsGPT}}
+ <p>
+ {{OverloanRiderFootnote}}
+ </p>
+{{/DefnLifeInsIsGPT}}
+
+{{#HasADD}}
+ <p>
+ {{ADDFootnote}}
+ </p>
+{{/HasADD}}
+
+{{#HasChildRider}}
+ <p>
+ The ${{ChildRiderAmount}} {{ChildFootnote}}
+ </p>
+{{/HasChildRider}}
+
+{{#HasSpouseRider}}
+ <p>
+ The ${{SpouseRiderAmount}} {{SpouseFootnote}}
+ </p>
+{{/HasSpouseRider}}
+
+{{#UsePartialMort}}
+ <p>
+ This illustration reflects the client's mortality assumption
+ of {{PartMortTableMult[1]}} of the {{PartMortTableName}}
+ table with all deaths at the end of the year.
+ </p>
+{{/UsePartialMort}}
+
+{{#GenderBlended}}
+ <p>
+ Custom blending of cost of insurance charges is based
+ on the plan's assumed distribution of initial selected
+ face amount by gender and tobacco use. This illustration
+ assumes that the distribution remains constant
+ in future years. Custom blending is available only on plans
+ of 100 or more lives. Custom blend assumptions:
+ {{#SmokerBlended}}
+ tobacco = {{SmokerPct}};
+ {{/SmokerBlended}}
+ {{^SmokerBlended}}
+ no blending by tobacco use;
+ {{/SmokerBlended}}
+ male = {{MalePct}}.
+ </p>
+{{/GenderBlended}}
+{{#SmokerBlended}}
+ <p>
+ Custom blending of cost of insurance charges is based
+ on the plan's assumed distribution of initial selected
+ face amount by gender and tobacco use. This illustration
+ assumes that the distribution remains constant
+ in future years. Custom blending is available only on plans
+ of 100 or more lives. Custom blend assumptions:
+ tobacco = {{SmokerPct}};
+ {{#GenderBlended}}
+ male = {{MalePct}}.
+ {{/GenderBlended}}
+ {{^GenderBlended}}
+ no blending by gender.
+ {{/GenderBlended}}
+ </p>
+{{/SmokerBlended}}
+
+<p>
+The state of issue is
+{{StatePostalAbbrev}}.
+</p>
+
+{{#IsInforce}}
+ <p>
+ This illustration assumes a beginning account value of
+ ${{InforceUnloanedAV}} as
+ of the date of this illustration.
+ </p>
+
+ <p>
+ This illustration assumes a beginning cost basis of
+ ${{InforceTaxBasis}} as
+ of the date of this illustration; the actual cost basis
+ may be higher or lower. Consult the Home Office for cost
+ basis information.
+ </p>
+{{/IsInforce}}
+
+{{#HasComplianceTrackingNumber}}
+ <p>
+ Compliance tracking number: {{>imprimatur}}
+ </p>
+{{/HasComplianceTrackingNumber}}
+
+{{#UWTypeIsGuaranteedIssueInTexasWithFootnote}}
+ <p>
+ *** This policy is classified as substandard guaranteed issue
+ per the requirements of the Texas Insurance Department.
+ </p>
+{{/UWTypeIsGuaranteedIssueInTexasWithFootnote}}
+
+<p align="center"><b>GUARANTEED PRINCIPAL ACCOUNT</b></p>
+
+<p>
+The Guaranteed Principal Account (GPA) has a guaranteed
+minimum annual interest rate of
+{{InitAnnGenAcctInt_Guaranteed}}.
+Guarantees are based on the claims-paying ability of the
+issuing company or companies.
+</p>
+
+<p align="center"><b>SEPARATE ACCOUNT</b></p>
+
+<p>
+{{HypotheticalRatesFootnote}}
+</p>
+
+{{^Composite}}
+ <p>
+ This illustration is based on total Separate Account
+ fund expenses of {{TotalIMF[1]}}.
+ </p>
+ {{#AvgFund}}
+ <p>
+ Investment management fees are based on equal
+ initial allocations among the available funds.
+ </p>
+ {{/AvgFund}}
+ {{#CustomFund}}
+ <p>
+ Investment management fees are based on an initial allocation
+ of 100% to a custom fund selected by the purchaser.
+ </p>
+ {{/CustomFund}}
+{{/Composite}}
+
+<p>
+<b>
+This material must be preceded or accompanied by the current prospectus for the
+{{PolicyMktgName}} Insurance {{ContractName}} and the prospectuses (or summary
+prospectuses, if available) for its underlying investment choices. Before
+purchasing a {{ContractName}}, you should carefully consider the investment
+objectives, risks, charges and expenses of the {{ContractName}} and its
+underlying investment choices. Please read the prospectuses carefully before
+investing or sending money.
+</b>
+</p>
+
+<p>
+{{SubsidiaryFootnote}}
+</p>
+
+<p>
+Securities offered through registered representatives of
+{{CoUnderwriter}}
+{{CoUnderwriterAddress}}
+or a broker-dealer that has a selling agreement with
+{{MainUnderwriter}}.
+</p>
+
+</font>
diff --git a/nasd_supp.mst b/nasd_supp.mst
new file mode 100644
index 0000000..706a1ef
--- /dev/null
+++ b/nasd_supp.mst
@@ -0,0 +1,29 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>nasd_header}}
+
+{{>dollar_units}}
+
+<br></br>
+<br></br>
+
+{{! Supplemental illustration table is created from C++ code }}
diff --git a/nasd_supp_report.mst b/nasd_supp_report.mst
new file mode 100644
index 0000000..ed7bc7d
--- /dev/null
+++ b/nasd_supp_report.mst
@@ -0,0 +1,28 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>nasd_header_upper}}
+<p align="center">{{SupplementalReportTitle}}</p>
+{{>nasd_header_lower}}
+
+{{>dollar_units}}
+
+{{! The supplemental report table itself is generated from C++ code }}
diff --git a/objects.make b/objects.make
index aeeb181..f01b04c 100644
--- a/objects.make
+++ b/objects.make
@@ -174,6 +174,11 @@ cli_objects := \
# Illustrations: files shared by the antediluvian and production branches.
+# PDF !! Expunge these two object files
+# ledger_xml_io.o
+# ledger_xsl.o
+# from the list below.
+
common_common_objects := \
$(boost_filesystem_objects) \
$(xmlwrapp_objects) \
@@ -205,6 +210,7 @@ common_common_objects := \
global_settings.o \
group_quote_pdf_gen.o \
group_values.o \
+ html.o \
illustrator.o \
input.o \
input_harmonization.o \
@@ -214,13 +220,17 @@ common_common_objects := \
input_sequence_parser.o \
input_xml_io.o \
interest_rates.o \
+ interpolate_string.o \
ledger.o \
ledger_base.o \
+ ledger_evaluator.o \
ledger_invariant.o \
- ledger_text_formats.o \
- ledger_variant.o \
+ ledger_pdf.o \
+ ledger_pdf_generator.o \
ledger_xml_io.o \
ledger_xsl.o \
+ ledger_text_formats.o \
+ ledger_variant.o \
ledgervalues.o \
license.o \
loads.o \
@@ -322,6 +332,7 @@ skeleton_objects := \
illustration_document.o \
illustration_view.o \
input_sequence_entry.o \
+ ledger_pdf_generator_wx.o \
main_common.o \
mec_document.o \
mec_view.o \
@@ -330,6 +341,7 @@ skeleton_objects := \
multidimgrid_tools.o \
mvc_controller.o \
mvc_view.o \
+ pdf_writer_wx.o \
policy_document.o \
policy_view.o \
preferences_view.o \
@@ -421,6 +433,7 @@ unit_test_targets := \
ieee754_test \
input_sequence_test \
input_test \
+ interpolate_string_test \
irc7702a_test \
istream_to_string_test \
loads_test \
@@ -690,6 +703,11 @@ input_test$(EXEEXT): \
xml_lmi.o \
yare_input.o \
+interpolate_string_test$(EXEEXT): \
+ $(common_test_objects) \
+ interpolate_string.o \
+ interpolate_string_test.o \
+
irc7702a_test$(EXEEXT): \
$(boost_filesystem_objects) \
$(common_test_objects) \
diff --git a/ledger_xsl.hpp b/output_mode.hpp
similarity index 65%
copy from ledger_xsl.hpp
copy to output_mode.hpp
index dd2af40..c074280 100644
--- a/ledger_xsl.hpp
+++ b/output_mode.hpp
@@ -1,6 +1,6 @@
-// Ledger xsl operations.
+// Output mode enum used in PDF generation helpers.
//
-// Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013,
2014, 2015, 2016, 2017, 2018 Gregory W. Chicares.
+// Copyright (C) 2017, 2018 Gregory W. Chicares.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@@ -19,20 +19,17 @@
// email: <address@hidden>
// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
-#ifndef ledger_xsl_hpp
-#define ledger_xsl_hpp
+#ifndef output_mode_hpp
+#define output_mode_hpp
#include "config.hpp"
-#include <boost/filesystem/path.hpp>
-
-#include <string>
-
-class Ledger;
-
-std::string write_ledger_as_pdf(Ledger const&, fs::path const&);
-
-fs::path xsl_filepath(Ledger const&);
-
-#endif // ledger_xsl_hpp
+/// Convenient enum used with functions that can either actually render
+/// something or just pretend doing it in order to compute the space that would
+/// be taken by it, in the layout phase.
+enum enum_output_mode
+ {e_output_normal
+ ,e_output_measure_only
+ };
+#endif // output_mode_hpp
diff --git a/pdf_writer_wx.cpp b/pdf_writer_wx.cpp
new file mode 100644
index 0000000..82740a8
--- /dev/null
+++ b/pdf_writer_wx.cpp
@@ -0,0 +1,249 @@
+// PDF generation helpers.
+//
+// Copyright (C) 2017, 2018 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#include "pchfile_wx.hpp"
+
+#include "pdf_writer_wx.hpp"
+
+#include "alert.hpp"
+#include "global_settings.hpp"
+#include "html.hpp"
+
+#include <wx/filesys.h>
+#include <wx/html/htmlcell.h>
+
+namespace
+{
+
+// These margins are arbitrary and can be changed to conform to subjective
+// preferences.
+constexpr int horz_margin = 24;
+constexpr int vert_margin = 36;
+
+wxPrintData make_print_data
+ (wxString const& output_filename
+ ,wxPrintOrientation orientation
+ )
+{
+ wxPrintData print_data;
+ print_data.SetPaperId(wxPAPER_LETTER);
+ print_data.SetFilename(output_filename);
+ print_data.SetOrientation(orientation);
+ return print_data;
+}
+
+} // Unnamed namespace.
+
+pdf_writer_wx::pdf_writer_wx
+ (wxString const& output_filename
+ ,wxPrintOrientation orientation
+ ,std::array<int, 7> const* html_font_sizes
+ )
+ :print_data_ {make_print_data(output_filename, orientation)}
+ ,pdf_dc_ {print_data_}
+ ,html_parser_ {nullptr}
+ ,total_page_size_ {pdf_dc_.GetSize()}
+{
+ // Ensure that the output is independent of the current display resolution:
+ // it seems that this is only the case with the PDF map mode and wxDC mode
+ // different from wxMM_TEXT.
+ pdf_dc_.SetMapModeStyle(wxPDF_MAPMODESTYLE_PDF);
+
+ // For simplicity, use points for everything: font sizers are expressed in
+ // them anyhow, so it's convenient to use them for everything else too.
+ pdf_dc_.SetMapMode(wxMM_POINTS);
+
+ pdf_dc_.StartDoc(wxString()); // Argument is not used.
+ pdf_dc_.StartPage();
+
+ // 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
+ (html_font_sizes
+ ? html_font_sizes->at(2)
+ : 8
+ )
+ .Family(wxFONTFAMILY_SWISS)
+ .FaceName("Helvetica")
+ );
+
+ // Create an HTML parser to allow easily adding HTML contents to the
output.
+ html_parser_.SetDC(&pdf_dc_);
+ if(html_font_sizes)
+ {
+ html_parser_.SetFonts
+ ("Helvetica"
+ ,"Courier"
+ ,html_font_sizes->data()
+ );
+ }
+ else
+ {
+ html_parser_.SetStandardFonts
+ (pdf_dc_.GetFont().GetPointSize()
+ ,"Helvetica"
+ ,"Courier"
+ );
+ }
+
+ // Create the virtual file system object for loading images referenced from
+ // HTML and interpret relative paths from the data directory.
+ html_vfs_.reset(new wxFileSystem());
+ html_vfs_->ChangePathTo
+ (global_settings::instance().data_directory().string()
+ ,true /* argument is a directory, not file path */
+ );
+ html_parser_.SetFS(html_vfs_.get());
+}
+
+/// Output an image at the given scale into the PDF.
+///
+/// The scale specifies how many times the image should be shrunk:
+/// scale > 1 makes the image smaller, while scale < 1 makes it larger.
+///
+/// Updates pos_y by increasing it by the height of the specified
+/// image at the given scale.
+
+void pdf_writer_wx::output_image
+ (wxImage const& image
+ ,char const* image_name
+ ,double scale
+ ,int x
+ ,int* pos_y
+ ,enum_output_mode output_mode
+ )
+{
+ int const y = wxRound(image.GetHeight() / scale);
+
+ switch(output_mode)
+ {
+ case e_output_normal:
+ {
+ // Use wxPdfDocument API directly as wxDC doesn't provide a way to
+ // set the image scale at PDF level and also because passing via
+ // wxDC wastefully converts wxImage to wxBitmap only to convert it
+ // back to wxImage when embedding it into the PDF.
+ wxPdfDocument* const pdf_doc = pdf_dc_.GetPdfDocument();
+ LMI_ASSERT(pdf_doc);
+
+ pdf_doc->SetImageScale(scale);
+ pdf_doc->Image(image_name, image, x, *pos_y);
+ pdf_doc->SetImageScale(1);
+ }
+ break;
+ case e_output_measure_only:
+ // Do nothing.
+ break;
+ default:
+ {
+ alarum() << "Case " << output_mode << " not found." << LMI_FLUSH;
+ }
+ }
+
+ *pos_y += y;
+}
+
+/// 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).
+
+int pdf_writer_wx::output_html
+ (int x
+ ,int y
+ ,int width
+ ,html::text const& html
+ ,enum_output_mode output_mode
+ )
+{
+ // We don't really want to change the font, but to preserve the current DC
+ // font which is changed by rendering the HTML contents.
+ wxDCFontChanger preserve_font(pdf_dc_, wxFont());
+
+ auto const html_str = wxString::FromUTF8(html.as_html());
+ std::unique_ptr<wxHtmlContainerCell> const cell
+ (static_cast<wxHtmlContainerCell*>(html_parser_.Parse(html_str))
+ );
+ LMI_ASSERT(cell);
+
+ cell->Layout(width);
+ switch(output_mode)
+ {
+ case e_output_normal:
+ {
+ wxHtmlRenderingInfo rendering_info;
+ cell->Draw
+ (pdf_dc_
+ ,x
+ ,y
+ ,0
+ ,std::numeric_limits<int>::max()
+ ,rendering_info
+ );
+ }
+ break;
+ case e_output_measure_only:
+ // Do nothing.
+ break;
+ default:
+ {
+ alarum() << "Case " << output_mode << " not found." << LMI_FLUSH;
+ }
+ }
+
+ return cell->GetHeight();
+}
+
+int pdf_writer_wx::get_horz_margin() const
+{
+ return horz_margin;
+}
+
+int pdf_writer_wx::get_vert_margin() const
+{
+ return vert_margin;
+}
+
+int pdf_writer_wx::get_page_width() const
+{
+ return total_page_size_.x - 2 * horz_margin;
+}
+
+int pdf_writer_wx::get_total_width() const
+{
+ return total_page_size_.x;
+}
+
+int pdf_writer_wx::get_page_height() const
+{
+ return total_page_size_.y - 2 * vert_margin;
+}
+
+int pdf_writer_wx::get_page_bottom() const
+{
+ return total_page_size_.y - vert_margin;
+}
+
+pdf_writer_wx::~pdf_writer_wx()
+{
+ // This will finally generate the PDF file.
+ pdf_dc_.EndDoc();
+}
diff --git a/pdf_writer_wx.hpp b/pdf_writer_wx.hpp
new file mode 100644
index 0000000..73f6e61
--- /dev/null
+++ b/pdf_writer_wx.hpp
@@ -0,0 +1,100 @@
+// PDF generation helpers.
+//
+// Copyright (C) 2017, 2018 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#ifndef pdf_writer_wx_hpp
+#define pdf_writer_wx_hpp
+
+#include "config.hpp"
+
+#include "assert_lmi.hpp"
+#include "output_mode.hpp"
+
+#include <wx/html/winpars.h>
+
+#include <wx/pdfdc.h>
+
+#include <array>
+#include <memory> // std::unique_ptr
+
+class wxFileSystem;
+
+namespace html { class text; }
+
+class pdf_writer_wx
+{
+ public:
+ // Optional html_font_sizes array allows to override default font sizes for
+ // the standard HTML3 fonts (1..7).
+ pdf_writer_wx
+ (wxString const& output_filename
+ ,wxPrintOrientation orientation
+ ,std::array<int, 7> const* html_font_sizes = nullptr
+ );
+
+ pdf_writer_wx(pdf_writer_wx const&) = delete;
+ pdf_writer_wx& operator=(pdf_writer_wx const&) = delete;
+
+ ~pdf_writer_wx();
+
+ // High level functions which should be preferably used if possible.
+ int output_html
+ (int x
+ ,int y
+ ,int width
+ ,html::text const& html
+ ,enum_output_mode output_mode = e_output_normal
+ );
+
+ void output_image
+ (wxImage const& image
+ ,char const* image_name
+ ,double scale
+ ,int x
+ ,int* pos_y
+ ,enum_output_mode output_mode = e_output_normal
+ );
+
+ // Accessors allowing to use lower level wxDC API directly.
+ wxDC& dc() { return pdf_dc_; }
+
+ // Page metrics: the page width and height are the size of the page region
+ // reserved for the normal contents, excluding horizontal and vertical
+ // margins. Total width and height include the margins.
+ int get_horz_margin() const;
+ int get_vert_margin() const;
+ int get_page_width() const;
+ int get_total_width() const;
+ int get_page_height() const;
+ int get_page_bottom() const;
+
+ private:
+ wxPrintData print_data_;
+ wxPdfDC pdf_dc_;
+
+ // Order is potentially important here: html_parser_ uses html_vfs_, so
+ // must be declared after it in order to be destroyed before it.
+ std::unique_ptr<wxFileSystem> html_vfs_;
+ wxHtmlWinParser html_parser_;
+
+ wxSize const total_page_size_;
+};
+
+#endif // pdf_writer_wx_hpp
diff --git a/reg_column_headings.mst b/reg_column_headings.mst
new file mode 100644
index 0000000..79191f5
--- /dev/null
+++ b/reg_column_headings.mst
@@ -0,0 +1,202 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<!-- No header for this page, just the logo -->
+<scaled_image inv_factor="0.36" src="company_logo.png"></scaled_image>
+
+<p align="center">
+Column Headings and Key Terms Used in This Illustration
+</p>
+<font size="-1">
+<p>
+<b>{{AvName}} Value:</b>
+The accumulation at interest of the net premiums paid,
+{{#SinglePremium}}
+less any withdrawals,
+{{/SinglePremium}}
+less any monthly charges deducted.
+</p>
+<p>
+<b>{{CsvName}} Value:</b>
+{{AvName}} Value less policy debt.
+{{#Has1035ExchCharge}}
+{{CashSurrValueFootnote}}
+{{/Has1035ExchCharge}}
+</p>
+{{^IsInforce}}
+<p>
+<b>Current Illustrated Crediting Rate:</b>
+{{CreditingRateFootnote}}
+</p>
+{{/IsInforce}}
+<p>
+<b>Current Values:</b>
+Values assuming current interest crediting rates
+and current monthly charges. These values are not guaranteed
+and are based on the assumption that premium is paid
+as illustrated.
+</p>
+<p>
+<b>Death Benefit:</b>
+The amount of benefit provided by the Death Benefit Option
+in effect on the date of death, prior to adjustments
+for policy debt and monthly charges payable to the date of death.
+<!--
+Presumably the description of death benefit options should be moved into
+'ProductDescription' (as has already been done in other '.xsl' files).
+-->
+</p>
+<p>
+<b>Death Benefit Option 1:</b>
+Option in which the death benefit is equal to the selected
+face amount of the contract on the date of death of the insured,
+or if greater the {{AvName}} Value
+{{#HasSalesLoadRefund}}
+plus the refund of sales loads (if applicable)
+{{/HasSalesLoadRefund}}
+on the insured's date of death multiplied by the minimum face
+amount percentage for the insured's attained age at death
+(minimum face amount). Please refer to the contract
+for a detailed schedule of death benefit factors.
+</p>
+<!-- Group Experience Rating Logic -->
+{{^GroupExperienceRating}}
+<p>
+<b>Death Benefit Option 2:</b>
+Option in which the death benefit is equal to the selected
+face amount of the contract
+plus the {{AvName}} Value
+on the date of death of the insured, or if greater,
+the {{AvName}} Value
+{{#HasSalesLoadRefund}}
+plus the refund of sales loads (if applicable)
+{{/HasSalesLoadRefund}}
+on the insured's date of death multiplied
+by the death benefit factor for the insured's attained age
+at death (minimum face amount). Please refer to the contract
+for a detailed schedule of death benefit factors.
+{{/GroupExperienceRating}}
+<!-- Group Experience Rating Logic -->
+{{#GroupExperienceRating}}
+</p>
+<p>
+<b>Experience Rating Risk Charge:</b>
+Applies only to certain experience rated groups.
+This charge is based on the cost of insurance charges
+assessed during the certificate year. It may be assessed against
+the account value once per certificate anniversary date
+and upon surrender of the group policy.
+</p>
+{{/GroupExperienceRating}}
+<!-- Group Experience Rating Logic -->
+{{^GroupExperienceRating}}
+{{#Has1035ExchCharge}}
+<p>
+<b>Exchange Charge:</b>
+{{ExchangeChargeFootnote1}}
+</p>
+{{/Has1035ExchCharge}}
+{{/GroupExperienceRating}}
+
+<!-- Single Premium Logic -->
+{{^SinglePremium}}
+<p>
+<b>Flexible Premiums:</b>
+Premiums that may be increased, reduced, or not paid,
+if the account value is sufficient to cover the monthly charges.
+{{/SinglePremium}}
+</p>
+<p>
+<b>Guaranteed Values:</b>
+Values assuming the guaranteed crediting rate
+and the guaranteed maximum monthly charges. These values
+are based on the assumption that premium is paid as illustrated.
+</p>
+<p>
+<b>MEC:</b>
+Modified Endowment Contract - this classification is given
+to a contract in violation of TAMRA
+(Technical and Miscellaneous Revenue Act), which limits
+the amount of premium that can be paid into a life
+insurance contract. To the extent of gain in the contract, loans,
+distributions and withdrawals from a MEC are subject
+to income tax and may also trigger a tax penalty.
+</p>
+{{^IsInforce}}
+<p>
+<b>Midpoint Values:</b>
+Values assuming interest rates that are the average
+of the illustrated current crediting rates
+and the guaranteed minimum interest rate, and monthly charges
+that are the average of the current monthly charges
+and the guaranteed monthly charges.
+These values are not guaranteed and are based on the assumption
+that premium is paid as illustrated.
+</p>
+{{/IsInforce}}
+<!-- Single Premium Logic -->
+{{#ModifiedSinglePremiumOrModifiedSinglePremium0}}
+<p>
+<b>Modified Single Premium:</b>
+After the single premium is paid, additional payment
+under this policy will only be accepted for repayment
+of policy debt, payment required to keep the policy
+from lapsing, or payment required to reinstate the policy.
+</p>
+{{/ModifiedSinglePremiumOrModifiedSinglePremium0}}
+<p>
+<b>Monthly Charges:</b>
+The monthly charges for the following month which include:
+cost of insurance, plus face amount charges (if applicable),
+plus the administrative charge shown
+on the contract schedule page.
+</p>
+<p>
+<b>Premium Outlay:</b>
+The amount of premium assumed to be paid by the contract owner
+or other premium payor.
+</p>
+<p>
+<b>Selected Face Amount:</b>
+The Selected Face Amount may be decreased upon written application
+satisfactory to {{InsCoName}}. A
+requested decrease is allowed only once per Policy Year, and the
+resulting Selected Face Amount after decrease must meet the
+minimum defined in your policy.
+</p>
+<!-- Single Premium Logic -->
+{{#SinglePremium}}
+{{#ModifiedSinglePremiumOrModifiedSinglePremium0}}
+<p>
+<b>Single Premium:</b>
+After the single premium is paid, additional payment
+under this policy will only be accepted for repayment
+of policy debt, payment required to keep the policy
+from lapsing, or payment required to reinstate the policy.
+</p>
+{{/ModifiedSinglePremiumOrModifiedSinglePremium0}}
+<!-- Single Premium Logic -->
+<p>
+<b>Ultimate Illustrated Crediting Rate:</b>
+{{UltCreditingRateFootnote}}
+{{/SinglePremium}}
+</p>
+</font>
diff --git a/reg_d_group_basic.mst b/reg_d_group_basic.mst
new file mode 100644
index 0000000..ac47311
--- /dev/null
+++ b/reg_d_group_basic.mst
@@ -0,0 +1,36 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>reg_d_group_header_upper}}
+<p align="center">
+{{#IsInforce}}
+ In Force
+{{/IsInforce}}
+Basic Illustration
+</p>
+{{>reg_d_group_header_lower}}
+
+{{>dollar_units}}
+
+<br></br>
+<br></br>
+
+{{! Basic illustration table is created from C++ code }}
diff --git a/reg_d_group_column_headings.mst b/reg_d_group_column_headings.mst
new file mode 100644
index 0000000..63d8834
--- /dev/null
+++ b/reg_d_group_column_headings.mst
@@ -0,0 +1,56 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>reg_d_group_header}}
+
+<p align="center"><b>Column Definitions</b></p>
+
+<p>
+<b>Policy Year</b>
+<br>
+{{PolicyYearFootnote}}
+</p>
+
+{{^Composite}}
+ <p>
+ <b>End of Year Age</b>
+ <br>
+ {{AttainedAgeFootnote}}
+ </p>
+{{/Composite}}
+
+<p>
+<b>Premium Outlay</b>
+<br>
+{{OutlayFootnote}}
+</p>
+
+<p>
+<b>Cash Surrender Value</b>
+<br>
+{{CashSurrValueFootnote}}
+</p>
+
+<p>
+<b>Death Benefit</b>
+<br>
+{{DeathBenefitFootnote}}
+</p>
diff --git a/reg_d_group_footer_lower.mst b/reg_d_group_footer_lower.mst
new file mode 100644
index 0000000..f404919
--- /dev/null
+++ b/reg_d_group_footer_lower.mst
@@ -0,0 +1,40 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<font size="-2">
+
+<table width="100%" cellspacing="0" cellpadding="0">
+ <tr>
+ <td colspan="3"> </td>
+ </tr>
+ <tr>
+ <td>{{InsCoName}}</td>
+ <td align="center">Date Prepared: {{date_prepared}}</td>
+ <td align="right">Policy Form: {{PolicyForm}}</td>
+ </tr>
+ <tr>
+ <td>{{InsCoAddr}}</td>
+ <td align="center">System Version: {{LmiVersion}}</td>
+ <td align="right">{{page_number}}</td>
+ </tr>
+</table>
+
+</font>
diff --git a/reg_d_group_footer_upper.mst b/reg_d_group_footer_upper.mst
new file mode 100644
index 0000000..5ccb8bd
--- /dev/null
+++ b/reg_d_group_footer_upper.mst
@@ -0,0 +1,33 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<font size="-2">
+
+<p>
+* This illustration is based on the assumed Gross Rate shown.
+The Net Rate is provided for information purposes only.
+</p>
+<p>
+See the Narrative Summary for important policy information.
+This illustration is not complete without all pages.
+</p>
+
+</font>
diff --git a/reg_d_group_header.mst b/reg_d_group_header.mst
new file mode 100644
index 0000000..2fb8b4e
--- /dev/null
+++ b/reg_d_group_header.mst
@@ -0,0 +1,29 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{!
+ The header is split in two parts, upper and lower one, only to allow
+ inserting the title between them, as a couple of pages do. But we still
+ provide this "full header" template for the pages that don't have any
+ title to make them simpler.
+}}
+{{>reg_d_group_header_upper}}
+{{>reg_d_group_header_lower}}
diff --git a/reg_d_group_header_lower.mst b/reg_d_group_header_lower.mst
new file mode 100644
index 0000000..c8310cf
--- /dev/null
+++ b/reg_d_group_header_lower.mst
@@ -0,0 +1,77 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<br>
+<br>
+
+<table width="100%" cellspacing="0" cellpadding="0" valign="top">
+ <tr>
+ <td>
+ {{#Composite}}
+ {{CorpNameAbbrev60}}
+ {{/Composite}}
+ {{^Composite}}
+ {{Insured1Abbrev30}}
+ {{/Composite}},
+ {{Gender}} {{Smoker}} rates, Age {{Age}}<br>
+ {{#Composite}}
+ Composite<br>
+ {{/Composite}}
+ Contract: {{PolicyMktgName}}<br>
+ Initial Premium: ${{InitPrem}}<br>
+ {{#GroupCarveout}}
+ Minimum Initial Premium: ${{InitMinPrem}}<br>
+ {{/GroupCarveout}}
+ First Year Premium: ${{InitPrem}}<br>
+ {{^Composite}}
+ {{#UWTypeIsMedical}}
+ Fully underwritten,
+ {{/UWTypeIsMedical}}
+ {{^UWTypeIsMedical}}
+ {{UWType}},
+ {{/UWTypeIsMedical}}
+ {{UWClass}}, Initial Death Benefit Option: {{InitDBOpt}}<br>
+ {{/Composite}}
+ {{#HasWP}}
+ Waiver of Monthly Charges Rider elected.<br>
+ {{/HasWP}}
+ {{>contract_numbers}}
+ </td>
+ <td>
+ Assumed Gross Rate: {{InitAnnSepAcctGrossInt_Current}}
+ ({{InitAnnSepAcctNetInt_Current}} Net)*<br>
+ Initial
+ {{#HasTerm}}
+ Total
+ {{/HasTerm}}
+ Face Amount: ${{InitTotalSA}}<br>
+ {{#HasTerm}}
+ Initial Base Face Amount: ${{InitBaseSpecAmt}}<br>
+ Initial Term Face Amount: ${{InitTermSpecAmt}}<br>
+ {{/HasTerm}}
+ {{^Composite}}
+ {{#UWClassIsRated}}
+ Table Rating: {{SubstandardTable}}<br>
+ {{/UWClassIsRated}}
+ {{/Composite}}
+ </td>
+ </tr>
+</table>
diff --git a/reg_d_group_header_upper.mst b/reg_d_group_header_upper.mst
new file mode 100644
index 0000000..4dd5b04
--- /dev/null
+++ b/reg_d_group_header_upper.mst
@@ -0,0 +1,39 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<scaled_image inv_factor="0.36" src="company_logo.png"></scaled_image>
+
+<br></br>
+<br></br>
+
+<p>
+The purpose of this illustration is to show how the performance
+of the underlying separate account divisions could affect
+the contract account value and death benefit.
+<b>
+The hypothetical returns do not reflect past performance
+and are not predictive of future results. Actual results
+could be less than or greater than the hypothetical results
+and in all likelihood will vary year to year.
+</b>
+</p>
+
+<br></br>
diff --git a/reg_d_group_narr_summary.mst b/reg_d_group_narr_summary.mst
new file mode 100644
index 0000000..33e51bc
--- /dev/null
+++ b/reg_d_group_narr_summary.mst
@@ -0,0 +1,123 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>reg_d_group_header_upper}}
+<p align="center">Narrative Summary</p>
+{{>reg_d_group_header_lower}}
+
+<p>
+{{PolicyMktgName}} is a {{PolicyLegalName}} issued by {{InsCoName}}.
+{{ProductDescription}}
+</p>
+
+<p>
+<u>Stable Value Feature</u> {{StableValueFootnote}}
+</p>
+
+<p>
+This illustration assumes Initial Death Benefit Option {{InitDBOpt}}.
+</p>
+
+<p>
+This policy is only available to entities that meet the definition
+of "accredited investor" or "qualified purchaser" under applicable
+Federal securities laws. Purchase of this policy is suitable
+only for entities of substantial economic means. Each prospective
+purchaser will be required to represent that it is familiar
+with and understands the fundamental risks and financial hazards
+of purchasing the policy. Each prospective purchaser must
+also represent that it meets minimum financial and other
+suitability standards.
+</p>
+
+<p>
+{{NoVanishPremiumFootnote}}
+</p>
+
+<p>
+The definition of life insurance elected for this contract
+is the
+{{#DefnLifeInsIsGPT}}
+ guideline premium test. The guideline single premium
+ is ${{InitGSP}}
+ and the guideline level premium
+ is ${{InitGLP}}.
+{{/DefnLifeInsIsGPT}}
+{{^DefnLifeInsIsGPT}}
+ cash value accumulation test.
+{{/DefnLifeInsIsGPT}}
+</p>
+
+<p>
+{{InsCoName}}
+{{RejectPremiumFootnote}}
+</p>
+
+<p>
+Account values, cash surrender values, and death benefits
+are illustrated as of the end of the year.
+</p>
+
+<p>
+PLEASE READ THE FOLLOWING IMPORTANT TAX DISCLOSURE
+<br>
+The initial 7-pay premium limit is ${{InitSevenPayPrem}}.
+As illustrated, this contract
+{{#IsMec}}
+ fails
+{{/IsMec}}
+{{^IsMec}}
+ passes
+{{/IsMec}}
+the seven-pay test defined in Section 7702A
+of the Internal Revenue Code and therefore
+{{#IsMec}}
+ {{^MecYearIs0}}
+ becomes a Modified Endowment Contract (MEC) in policy year
+ {{MecYearPlus1}}.
+ {{/MecYearIs0}}
+ {{#MecYearIs0}}
+ is a Modified Endowment Contract (MEC).
+ {{/MecYearIs0}}
+ As a MEC, any loans or withdrawals are taxed to the extent
+ of any gain in the contract, and an additional 10% penalty
+ tax will apply to the taxable portion of the loan
+ or withdrawal.
+{{/IsMec}}
+{{^IsMec}}
+ is not a Modified Endowment Contract (MEC).
+ Subsequent changes in the contract, including but not limited
+ to increases and decreases in premiums or benefits, may cause
+ the contract to be retested and may result in the contract
+ becoming a MEC.
+{{/IsMec}}
+</p>
+
+<p>
+<b>
+This illustration is not written or intended as tax
+or legal advice and may not be relied on for purposes
+of avoiding any federal tax penalties. For more information
+pertaining to the tax consequences of purchasing
+or owning this policy, consult with your own independent tax
+or legal counsel.
+</b>
+</p>
diff --git a/reg_d_group_narr_summary2.mst b/reg_d_group_narr_summary2.mst
new file mode 100644
index 0000000..696c778
--- /dev/null
+++ b/reg_d_group_narr_summary2.mst
@@ -0,0 +1,161 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>reg_d_group_header_upper}}
+<p align="center">Narrative Summary</p>
+{{>reg_d_group_header_lower}}
+
+<p>
+{{GuarMortalityFootnote}}
+</p>
+
+<p>
+The loan interest rate may be fixed or adjustable as elected
+by the policy owner.
+</p>
+
+<p>
+{{#UseExperienceRating}}
+ {{ExpRatingFootnote}}
+{{/UseExperienceRating}}
+{{^UseExperienceRating}}
+ The illustration does not assume any mortality
+ experience rating.
+{{/UseExperienceRating}}
+</p>
+
+<p>
+<b>
+This illustration may only be used when preceded or accompanied
+by the offering memorandum for {{PolicyLegalName}} ({{PolicyMktgName}})
+insurance contract and its underlying investment choices.
+For a complete listing of the underlying investment choices,
+please refer to the offering memorandum. Before purchasing
+a variable life insurance contract, investors should
+carefully consider the investment objectives, risks, charges
+and expenses of the variable life insurance contract
+and its underlying investment choices. Please read
+the offering memorandum carefully before investing
+or sending money.
+</b>
+</p>
+
+{{#HasTerm}}
+ <p>
+ {{TermFootnote}}
+ </p>
+{{/HasTerm}}
+
+{{#HasWP}}
+ <p>
+ {{WaiverFootnote}}
+ </p>
+{{/HasWP}}
+
+{{#UsePartialMort}}
+ <p>
+ This illustration reflects an initial mortality assumption
+ of {{PartMortTableMult[1]}} of the {{PartMortTableName}} table
+ with all deaths at the end of the year.
+ </p>
+{{/UsePartialMort}}
+
+{{#GenderBlended}}
+ <p>
+ {{MortalityBlendFootnote}}
+ Custom blend assumptions:
+ {{#SmokerBlended}}
+ tobacco = {{SmokerPct}};
+ {{/SmokerBlended}}
+ {{^SmokerBlended}}
+ no blending by tobacco use;
+ {{/SmokerBlended}}
+ male = {{MalePct}}.
+ </p>
+{{/GenderBlended}}
+{{^GenderBlended}}
+ {{#SmokerBlended}}
+ <p>
+ {{MortalityBlendFootnote}}
+ Custom blend assumptions:
+ tobacco = {{SmokerPct}};
+ {{#GenderBlended}}
+ male = {{MalePct}}.
+ {{/GenderBlended}}
+ {{^GenderBlended}}
+ no blending by gender.
+ {{/GenderBlended}}
+ </p>
+ {{/SmokerBlended}}
+{{/GenderBlended}}
+
+<p>
+The state of issue is {{StatePostalAbbrev}}.
+</p>
+
+{{#HasComplianceTrackingNumber}}
+ <p>
+ Compliance tracking number: {{>imprimatur}}
+ </p>
+{{/HasComplianceTrackingNumber}}
+
+<p>
+SEPARATE ACCOUNT
+</p>
+
+<p>
+{{HypotheticalRatesFootnote}}
+</p>
+
+<p>
+This illustration is based on total Separate Account fund expenses
+of {{TotalIMF[1]}}.
+</p>
+
+<p>
+<b>
+This illustration may not reflect your actual tax
+and accounting consequences and is not intended as tax advice
+nor may it be relied on for purposes of avoiding any federal
+tax penalties. Consult professional tax advisors for tax advice.
+</b>
+</p>
+
+<p>
+Placement Agents:
+{{CoUnderwriter}} serves as the
+placement agent for contracts sold by its registered
+representatives.
+ {{MainUnderwriter}}
+serves as the placement agent for contracts sold by registered
+representatives of other broker-dealers that have entered into
+distribution agreements with
+ {{MainUnderwriter}}.
+</p>
+
+<p>
+{{CoUnderwriter}} and
+ {{MainUnderwriter}} are
+subsidiaries of {{InsCoName}}
+({{InsCoShortName}}) and are
+located at {{InsCoStreet}}.
+</p>
+</p>
diff --git a/reg_d_group_supp_report.mst b/reg_d_group_supp_report.mst
new file mode 100644
index 0000000..31dcfba
--- /dev/null
+++ b/reg_d_group_supp_report.mst
@@ -0,0 +1,28 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>reg_d_group_header_upper}}
+<p align="center">{{SupplementalReportTitle}}</p>
+{{>reg_d_group_header_lower}}
+
+{{>dollar_units}}
+
+{{! The supplemental report table itself is generated from C++ code }}
diff --git a/reg_d_indiv_cover_page.mst b/reg_d_indiv_cover_page.mst
new file mode 100644
index 0000000..1de1f64
--- /dev/null
+++ b/reg_d_indiv_cover_page.mst
@@ -0,0 +1,130 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<br></br>
+<br></br>
+<br></br>
+<br></br>
+<br></br>
+<br></br>
+<br></br>
+<br></br>
+<br></br>
+
+<p align="center">
+<b>
+
+<font size="+3">
+{{PolicyMktgName}}
+</font>
+
+<br></br>
+<br></br>
+
+<font size="+2">
+Hypothetical Life Insurance Illustration
+</font>
+
+<br></br>
+<br></br>
+<br></br>
+<br></br>
+
+<font size="+1">
+Prepared by: {{InsCoShortName}}
+
+<br></br>
+<br></br>
+
+Prepared for:
+{{^Composite}}
+ {{Insured1Abbrev140}}
+{{/Composite}}
+{{^Composite}}
+ {{CorpNameAbbrev140}}
+{{/Composite}}
+
+<br></br>
+<br></br>
+<br></br>
+<br></br>
+
+Date Prepared: {{date_prepared}}
+
+</font>
+</b>
+</p>
+
+<br></br>
+<br></br>
+<br></br>
+<br></br>
+<br></br>
+<br></br>
+<br></br>
+<br></br>
+
+<font size="-1">
+
+<p>
+The purpose of this illustration is to show how hypothetical rates
+of return will affect policy account value. These hypothetical
+returns do not reflect past performance and are not predictive
+of future results. Actual results could be less than or greater
+than the hypothetical rates and in all likelihood will vary from
+year to year.
+</p>
+
+<p>
+<b>
+This material must be preceded or accompanied by the current
+Confidential Private Placement Memorandum for {{PolicyMktgName}}
+and the current prospectuses (or summary prospectuses,
+if available) and private placement memoranda
+for its underlying investment choices. Investors should
+carefully consider the investment objectives, risks,
+charges and expenses of the policy and its underlying investment
+choices. Please read the prospectuses and private placement
+memoranda carefully before investing or sending money.
+</b>
+</p>
+
+<p>
+{{PolicyMktgName}} {{IssuingCompanyFootnote}}
+</p>
+
+<p>
+Placement Agents: {{PlacementAgentFootnote}}
+</p>
+
+<p>
+{{SubsidiaryFootnote}}
+</p>
+
+<p>
+{{MarketingNameFootnote}}
+</p>
+
+<p>
+Compliance tracking number: {{>imprimatur}}
+</p>
+
+</font>
diff --git a/reg_d_indiv_curr.mst b/reg_d_indiv_curr.mst
new file mode 100644
index 0000000..f30b922
--- /dev/null
+++ b/reg_d_indiv_curr.mst
@@ -0,0 +1,34 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>reg_d_indiv_header}}
+
+<font size="-1">
+
+<p align="center">
+End of Year Policy Values using Current Charges
+</p>
+
+{{>dollar_units}}
+
+</font>
+
+{{! The current charges values table is generated from C++ code }}
diff --git a/reg_d_indiv_curr_irr.mst b/reg_d_indiv_curr_irr.mst
new file mode 100644
index 0000000..4235b46
--- /dev/null
+++ b/reg_d_indiv_curr_irr.mst
@@ -0,0 +1,34 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>reg_d_indiv_header}}
+
+<font size="-1">
+
+<p align="center">
+End of Year Policy Values using Current Charges
+</p>
+
+{{>dollar_units}}
+
+</font>
+
+{{! The IRR values table is generated from C++ code }}
diff --git a/reg_d_indiv_footer_lower.mst b/reg_d_indiv_footer_lower.mst
new file mode 100644
index 0000000..07b749e
--- /dev/null
+++ b/reg_d_indiv_footer_lower.mst
@@ -0,0 +1,40 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<font size="-2">
+
+<table width="100%" cellspacing="0" cellpadding="0">
+ <tr>
+ <td colspan="3"> </td>
+ </tr>
+ <tr>
+ <td>{{InsCoName}}</td>
+ <td align="center">System Version: {{LmiVersion}}</td>
+ <td align="right">Policy Form: {{PolicyForm}}</td>
+ </tr>
+ <tr>
+ <td>{{InsCoAddr}}</td>
+ <td> </td>
+ <td align="right">{{page_number}}</td>
+ </tr>
+</table>
+
+</font>
diff --git a/reg_d_indiv_footer_upper.mst b/reg_d_indiv_footer_upper.mst
new file mode 100644
index 0000000..e82a17c
--- /dev/null
+++ b/reg_d_indiv_footer_upper.mst
@@ -0,0 +1,28 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<font size="-2">
+
+<p>
+This illustration is not complete unless all pages as noted below are included.
+</p>
+
+</font>
diff --git a/reg_d_indiv_guar_irr.mst b/reg_d_indiv_guar_irr.mst
new file mode 100644
index 0000000..7f2e6d2
--- /dev/null
+++ b/reg_d_indiv_guar_irr.mst
@@ -0,0 +1,34 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>reg_d_indiv_header}}
+
+<font size="-1">
+
+<p align="center">
+End of Year Policy Values using Guaranteed Charges
+</p>
+
+{{>dollar_units}}
+
+</font>
+
+{{! The IRR values table is generated from C++ code }}
diff --git a/reg_d_indiv_header.mst b/reg_d_indiv_header.mst
new file mode 100644
index 0000000..3bb9a39
--- /dev/null
+++ b/reg_d_indiv_header.mst
@@ -0,0 +1,90 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<scaled_image inv_factor="0.36" src="company_logo.png"></scaled_image>
+
+<font size="-1">
+
+<p align="center">
+{{^IsInforce}}
+ Illustration for Flexible Premium Variable Adjustable
+ Life Insurance Contract.
+{{/IsInforce}}
+{{#IsInforce}}
+ In Force Illustration for Flexible Premium Variable Adjustable
+ Life Insurance Contract.
+{{/IsInforce}}
+
+<br></br>
+<br></br>
+
+The purpose of the Illustration is to show how the performance of the
+underlying separate account divisions could affect the policy's
+account values and death benefits. This Illustration is hypothetical
+and may not be used to project or predict investment results.
+</p>
+
+<br></br>
+<br></br>
+
+<table width="100%" cellspacing="0" cellpadding="0" valign="top">
+ <tr>
+ <td width="60%">
+ Date Prepared: {{date_prepared}}<br>
+ {{#Composite}}
+ Composite of individuals<br>
+ {{/Composite}}
+ {{^Composite}}
+ Prepared for: {{Insured1Abbrev30}}<br>
+ Age: {{Age}}<br>
+ Issue State: {{StatePostalAbbrev}}<br>
+ {{/Composite}}
+ Selected Face Amount: ${{InitTotalSA}}<br>
+ Initial Death Benefit Option: {{InitDBOpt}}<br>
+ </td>
+ <td width="40%">
+ Policy: {{PolicyMktgName}}<br>
+ {{^Composite}}
+ Underwriting Type:
+ {{#UWTypeIsMedical}}
+ Fully underwritten<br>
+ {{/UWTypeIsMedical}}
+ {{^UWTypeIsMedical}}
+ {{UWType}}<br>
+ {{/UWTypeIsMedical}}
+
+ Rate Classification: {{Gender}}, {{Smoker}}, {{UWClass}}<br>
+
+ {{#UWClassIsRated}}
+ Table Rating: {{SubstandardTable}}<br>
+ {{!
+ Flats don't require "Rated" class; and does this value
actually print anyway?
+ These questions apply as well to the original:
+
http://svn.savannah.nongnu.org/viewvc/lmi/trunk/individual_private_placement.xsl?annotate=696&root=lmi&pathrev=3585
+ }}
+ Initial Annual Flat Extra: {{AnnualFlatExtra[1]}} per
1,000<br>
+ {{/UWClassIsRated}}
+ {{/Composite}}
+ </td>
+ </tr>
+</table>
+
+</font>
diff --git a/reg_d_indiv_notes1.mst b/reg_d_indiv_notes1.mst
new file mode 100644
index 0000000..0a79bec
--- /dev/null
+++ b/reg_d_indiv_notes1.mst
@@ -0,0 +1,135 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{! No header on this page, but still use the logo. }}
+<scaled_image inv_factor="0.36" src="company_logo.png"></scaled_image>
+
+<p align="center"><font size="+1"><b>Explanatory Notes</b></font></p>
+
+<font size="-1">
+
+<p>
+<font size="+1"><b><u>The Illustration</u></b></font>
+</p>
+
+<p>
+This illustration is not a policy or an offer or solicitation
+to purchase a policy. Offers are made only through the
+Confidential Private Placement Memorandum ("the PPM") for {{PolicyMktgName}}.
+</p>
+
+<p>
+ {{ProductDescription}}.
+</p>
+
+<br></br>
+<br></br>
+
+<p>
+<font size="+1"><b><u>The Policy</u></b></font>
+</p>
+
+<p>
+<b>Policy Requirements:</b>
+This policy is only available to persons who are deemed
+accredited investors and qualified purchasers under applicable
+federal securities laws. The policy owner must be able to bear
+the risk of loss of the entire investment in the policy. The
+policy owner must be familiar with and understand the fundamental
+risks and financial hazards of investing in the policy, and be
+willing to represent as such to {{InsCoShortName}}.
+</p>
+
+<p>
+<b>Maximum Net Amount at Risk Limitation:</b> {{MaxNaarFootnote}}
+</p>
+
+<p>
+<b>Monthly Charges:</b> {{MonthlyChargesFootnote}}
+</p>
+
+<p>
+<b>Surrender Charges:</b> {{PremTaxSurrChgFootnote}}
+</p>
+
+<br></br>
+<br></br>
+
+<p>
+<font size="+1"><b><u>Column Definitions</u></b></font>
+</p>
+
+<p>
+<b>Account Value:</b> {{AccountValueFootnote}}
+</p>
+
+<p>
+<b>Administrative Charge:</b> {{PolicyFeeFootnote}}
+</p>
+
+<p>
+<b>Asset Charges:</b> {{AssetChargeFootnote}}
+</p>
+
+<p>
+<b>Cash Surrender Value:</b> {{CashSurrValueFootnote}}
+</p>
+
+<p>
+<b>Death Benefit:</b> {{DeathBenefitFootnote}}
+</p>
+
+<p>
+<b>End of Year Age:</b> {{AttainedAgeFootnote}}
+</p>
+
+<p>
+<b>Investment Income:</b> {{InvestmentIncomeFootnote}}
+</p>
+
+<p>
+<b>IRR on Death Benefit:</b> {{IrrDbFootnote}}
+</p>
+
+<p>
+<b>IRR on Surrender Value:</b> {{IrrCsvFootnote}}
+</p>
+
+<p>
+<b>Mortality Charges:</b> {{MortalityChargesFootnote}}
+</p>
+
+<p>
+<b>Policy Year:</b> {{PolicyYearFootnote}}
+</p>
+
+<p>
+<b>Premium Loads:</b> The applicable state premium tax, deferred acquisition
+cost tax charge, sales load (if any), separate account administrative charge
+(if any), and other charges incurred as a result of retaining an unaffiliated
+money manager (if any).
+</p>
+
+<p>
+<b>Premium Outlay:</b> {{OutlayFootnote}}
+</p>
+
+</font>
diff --git a/reg_d_indiv_notes2.mst b/reg_d_indiv_notes2.mst
new file mode 100644
index 0000000..7f7c327
--- /dev/null
+++ b/reg_d_indiv_notes2.mst
@@ -0,0 +1,56 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{! No header on this page, but still use the logo. }}
+<scaled_image inv_factor="0.36" src="company_logo.png"></scaled_image>
+
+<p align="center"><font size="+1"><b>Explanatory Notes</b></font></p>
+
+<font size="-1">
+
+<p>
+<font size="+1"><b><u>Separate Account</u></b></font>
+</p>
+
+<p>
+{{HypotheticalRatesFootnote}}
+</p>
+
+<br></br>
+<br></br>
+
+<p>
+<font size="+1"><b><u>Withdrawals and Policy Loans</u></b></font>
+</p>
+
+<p>
+If applicable, withdrawals and policy loans will appear on a
+supplemental report at the end of the illustration. Withdrawals
+and policy loans are assumed to be taken at the beginning of the
+applicable policy year. The illustration and supplemental report
+reflect a fixed policy loan interest rate of {{InitAnnLoanDueRate}}.
+</p>
+
+<p>
+{{LoanAndWithdrawalFootnote}}
+</p>
+
+</font>
diff --git a/reg_d_indiv_notes3.mst b/reg_d_indiv_notes3.mst
new file mode 100644
index 0000000..f8d1adb
--- /dev/null
+++ b/reg_d_indiv_notes3.mst
@@ -0,0 +1,186 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{! No header on this page, but still use the logo. }}
+<scaled_image inv_factor="0.36" src="company_logo.png"></scaled_image>
+
+<p align="center"><font size="+1"><b>Explanatory Notes</b></font></p>
+
+<font size="-1">
+
+<p>
+<font size="+1"><b><u>Important Tax Disclosures</u></b></font>
+</p>
+
+<p>
+The definition of life insurance elected for this policy is
+{{#DefnLifeInsIsGPT}}
+ the guideline premium test. The guideline single premium
+ is ${{InitGSP}}
+ and the guideline level premium
+ is ${{InitGLP}}.
+{{/DefnLifeInsIsGPT}}
+{{^DefnLifeInsIsGPT}}
+ the cash value accumulation test.
+{{/DefnLifeInsIsGPT}}
+</p>
+
+<p>
+The initial 7-pay premium limit is ${{InitSevenPayPrem}}.
+As illustrated, this policy
+{{#IsMec}}
+ fails
+{{/IsMec}}
+{{^IsMec}}
+ passes
+{{/IsMec}}
+the seven-pay test defined in Section 7702A
+of the Internal Revenue Code and therefore
+{{#IsMec}}
+ becomes a Modified Endowment Contract (MEC)
+ in policy year {{MecYearPlus1}}.
+{{/IsMec}}
+{{^IsMec}}
+ is not a Modified Endowment Contract (MEC). Subsequent changes
+ to the policy, including but not limited to increases
+ and decreases in premiums or benefits, may cause the policy
+ to be retested and may result in the policy becoming a MEC.
+{{/IsMec}}
+</p>
+
+<p>
+If a policy is a MEC, withdrawals and policy loans are taxed to
+the extent of any gain in the policy, and an additional penalty
+tax may apply to the taxable portion of the distribution.
+</p>
+
+<p>
+<b>
+The information provided in this illustration is not written or
+intended as tax or legal advice and may not be relied on for
+purposes of avoiding any federal tax penalties.
+{{InsCoShortName}}, its employees
+and representatives are not authorized to give tax or legal advice.
+Individuals are encouraged to seek advice from their own tax or
+legal counsel.
+</b>
+</p>
+
+{{^IsInforce}}
+
+<br>
+<br>
+<br>
+
+<p align="center">
+<font size="+2"><b>Certification Statements</b></font>
+</p>
+
+<p>
+<br>
+<br>
+POLICY OWNER / APPLICANT<br>
+
+I have received a copy of this illustration, and I understand
+that any non-guaranteed charges illustrated are subject
+to change and could be either higher or lower. Additionally,
+I have been informed by my agent that these values
+are not guaranteed.
+</p>
+
+<br>
+<br>
+<br>
+<br>
+
+<table cellspacing="0" cellpadding="0" valign="top">
+ <tr>
+ <td>
+ <u>{{Space64}}{{Space32}}</u>
+ </td>
+ <td>
+ {{Space32}}
+ </td>
+ <td>
+ <u>{{Space32}}</u>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ POLICY OWNER OR APPLICANT SIGNATURE
+ </td>
+ <td>
+ {{Space32}}
+ </td>
+ <td>
+ DATE
+ </td>
+ </tr>
+</table>
+
+<br>
+<br>
+<br>
+<br>
+<br>
+
+<p>
+AGENT / AUTHORIZED REPRESENTATIVE<br>
+
+I certify that this illustration has been presented
+to the applicant, and that I have explained that any
+non-guaranteed charges illustrated are subject to change. I have
+made no statements that are inconsistent with the illustration.
+</p>
+
+<br>
+<br>
+<br>
+<br>
+
+<table cellspacing="0" cellpadding="0" valign="top">
+ <tr>
+ <td>
+ <u>{{Space64}}{{Space32}}</u>
+ </td>
+ <td>
+ {{Space32}}
+ </td>
+ <td>
+ <u>{{Space32}}</u>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ AGENT OR AUTHORIZED REPRESENTATIVE
+ </td>
+ <td>
+ {{Space32}}
+ </td>
+ <td>
+ DATE
+ </td>
+ </tr>
+</table>
+
+{{/IsInforce}}
+
+</font>
diff --git a/reg_d_indiv_supp_report.mst b/reg_d_indiv_supp_report.mst
new file mode 100644
index 0000000..81ad63c
--- /dev/null
+++ b/reg_d_indiv_supp_report.mst
@@ -0,0 +1,32 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>reg_d_indiv_header}}
+
+<font size="-1">
+
+<p align="center">{{SupplementalReportTitle}}</p>
+
+</font>
+
+{{>dollar_units}}
+
+{{! The supplemental report table itself is generated from C++ code }}
diff --git a/reg_footer.mst b/reg_footer.mst
new file mode 100644
index 0000000..f4d0dce
--- /dev/null
+++ b/reg_footer.mst
@@ -0,0 +1,40 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<font size="-2">
+
+<table width="100%" cellspacing="0" cellpadding="0">
+ <tr>
+ <td colspan="3"> </td>
+ </tr>
+ <tr>
+ <td>Date Prepared: {{date_prepared}}</td>
+ <td align="center">{{page_number}}</td>
+ <td align="right">{{InsCoName}}</td>
+ </tr>
+ <tr>
+ <td>System Version: {{LmiVersion}}</td>
+ <td> </td>
+ <td align="right">{{>imprimatur}}</td>
+ </tr>
+</table>
+
+</font>
diff --git a/reg_footer_disclaimer.mst b/reg_footer_disclaimer.mst
new file mode 100644
index 0000000..f97e6da
--- /dev/null
+++ b/reg_footer_disclaimer.mst
@@ -0,0 +1,32 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<font size="-2">
+
+{{^IsInforce}}
+ {{NonGuaranteedFootnote}}
+{{/IsInforce}}
+{{#IsInforce}}
+ {{InforceNonGuaranteedFootnote0}}<br>
+ <b>{{InforceNonGuaranteedFootnote3}}</b>
+{{/IsInforce}}
+
+</font>
diff --git a/reg_header.mst b/reg_header.mst
new file mode 100644
index 0000000..edd29de
--- /dev/null
+++ b/reg_header.mst
@@ -0,0 +1,131 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<scaled_image inv_factor="0.36" src="company_logo.png"></scaled_image>
+
+<font size=-1>
+<p align="center">
+ {{#IsInforce}}
+ LIFE INSURANCE IN FORCE BASIC ILLUSTRATION
+ {{/IsInforce}}
+ {{^IsInforce}}
+ LIFE INSURANCE BASIC ILLUSTRATION
+ {{/IsInforce}}
+
+ <br>{{InsCoName}}
+ <br>Presented by: {{ProducerName}}
+ <br>{{ProducerStreet}}
+ {{#HasProducerCity}}
+ <br>{{ProducerCity}}
+ {{/HasProducerCity}}
+</p>
+<p>
+
+</p>
+<table width="100%" cellspacing="0" cellpadding="0" valign="top">
+ <tr>
+ <td width="60%">
+ Prepared for:<br>
+ Group Name:
{{CorpNameAbbrev50}}<br>
+ {{#Composite}}
+ Composite
Illustration<br>
+ {{/Composite}}
+ {{^Composite}}
+ Insured:
{{Insured1Abbrev50}}<br>
+ Age:
{{Age}}<br>
+ {{/Composite}}
+ Product: {{PolicyForm}} {{PolicyMktgName}}<br>
+ {{#ModifiedSinglePremium}}
+ Modified Single Premium Adjustable Life Insurance Policy
+ {{/ModifiedSinglePremium}}
+ {{^ModifiedSinglePremium}}
+ {{PolicyLegalName}}
+ {{/ModifiedSinglePremium}}
+ <br>
+
+ {{^IsInforce}}
+ {{^SinglePremium}}
+ Initial Premium:
+ {{/SinglePremium}}
+ {{#SinglePremium}}
+ Single Premium:
+ {{/SinglePremium}}
+ ${{InitPrem}}
+ {{/IsInforce}}
+ <br>
+
+ {{^Composite}}
+ Initial Death Benefit Option: {{InitDBOpt}}<br>
+ {{/Composite}}
+ {{>contract_numbers}}
+ </td>
+ <td width="40%">
+ Initial {{#HasTerm}}Total{{/HasTerm}}
+ Selected Face Amount: ${{InitTotalSA}}<br>
+
+ {{#HasTerm}}
+ Initial Base Face Amount: ${{InitBaseSpecAmt}}<br>
+ Initial Term Face Amount: ${{InitTermSpecAmt}}<br>
+ {{/HasTerm}}
+
+ Guaranteed Crediting Rate: {{InitAnnGenAcctInt_Guaranteed}}<br>
+
+ Current Illustrated Crediting Rate:
+ {{#InforceYear}}
+ {{UltimateInterestRate}}
+ {{/InforceYear}}
+ {{^InforceYear}}
+ {{InitAnnGenAcctInt_Current}}
+ {{/InforceYear}}
+ <br>
+
+ {{#SinglePremium}}
+ {{#InforceYearLE4}}
+ Ultimate Illustrated Crediting Rate:
+ {{#ModifiedSinglePremium0}}
+ {{AnnGAIntRate_Current[11]}}
+ {{/ModifiedSinglePremium0}}
+ {{^ModifiedSinglePremium0}}
+ {{AnnGAIntRate_Current[6]}}
+ {{/ModifiedSinglePremium0}}
+ <br>
+ {{/InforceYearLE4}}
+ {{/SinglePremium}}
+
+ {{^Composite}}
+ Underwriting Type:
+ {{#UWTypeIsMedical}}
+ Fully underwritten
+ {{/UWTypeIsMedical}}
+ {{^UWTypeIsMedical}}
+ {{UWType}}
+ {{/UWTypeIsMedical}}
+ <br>
+ {{/Composite}}
+
+ Rate Classification: {{UWClass}}, {{Smoker}}, {{Gender}}<br>
+ {{#UWClassIsRated}}
+ Table Rating: {{SubstandardTable}}<br>
+ {{/UWClassIsRated}}
+ </td>
+ </tr>
+</table>
+</font>
diff --git a/reg_narr_summary.mst b/reg_narr_summary.mst
new file mode 100644
index 0000000..0752d6b
--- /dev/null
+++ b/reg_narr_summary.mst
@@ -0,0 +1,203 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>reg_header}}
+
+<p align="center">NARRATIVE SUMMARY</p>
+
+<font size="-1">
+
+<p>
+{{^SinglePremium}}
+ {{PolicyMktgName}} is a
+ {{#GroupExperienceRating}}group{{/GroupExperienceRating}}
+ {{#GroupCarveout}}group{{/GroupCarveout}}
+ flexible premium adjustable life insurance contract.
+ {{#GroupExperienceRating}}
+ It is a no-load policy and is intended for large case sales.
+ It is primarily marketed to financial institutions
+ to fund certain corporate liabilities.
+ {{/GroupExperienceRating}}
+ It features accumulating account values, adjustable benefits,
+ and flexible premiums.
+{{/SinglePremium}}
+{{#SinglePremium}}
+ {{#ModifiedSinglePremiumOrModifiedSinglePremium0}}
+ {{PolicyMktgName}}
+ is a modified single premium adjustable life
+ insurance contract. It features accumulating
+ account values, adjustable benefits, and single premium.
+ {{/ModifiedSinglePremiumOrModifiedSinglePremium0}}
+ {{^ModifiedSinglePremiumOrModifiedSinglePremium0}}
+ {{PolicyMktgName}}
+ is a single premium adjustable life insurance contract.
+ It features accumulating account values,
+ adjustable benefits, and single premium.
+ {{/ModifiedSinglePremiumOrModifiedSinglePremium0}}
+{{/SinglePremium}}
+</p>
+
+{{^IsInforce}}
+ <p>
+ Coverage may be available on a Guaranteed Standard Issue basis.
+ All proposals are based on case characteristics and must
+ be approved by the {{InsCoShortName}}
+ Home Office. For details regarding underwriting
+ and coverage limitations refer to your offer letter
+ or contact your {{InsCoShortName}} representative.
+ </p>
+{{/IsInforce}}
+
+<p>
+{{AvName}} {{MonthlyChargesPaymentFootnote}}
+</p>
+
+<p>
+{{^SinglePremium}}
+ Premiums are assumed to be paid on {{ErModeLCWithArticle}}
+ basis and received at the beginning of the contract year.
+{{/SinglePremium}}
+{{#SinglePremium}}
+ The single premium is assumed to be paid at the beginning
+ of the contract year.
+{{/SinglePremium}}
+
+{{AvName}} Values, {{CsvName}} Values,
+and death benefits are illustrated as of the end
+of the contract year. The method we use to allocate
+overhead expenses is the fully allocated expense method.
+</p>
+
+{{^SinglePremium}}
+ <p>
+ In order to guarantee coverage to age
+ {{EndtAge}}, {{ErModeLCWithArticle}} premium
+ {{#HasGuarPrem}}
+ of ${{GuarPrem}} must be paid.
+ {{/HasGuarPrem}}
+ {{^HasGuarPrem}}
+ is defined.
+ {{/HasGuarPrem}}
+ This amount is based on the guaranteed monthly charges
+ and the guaranteed interest crediting rate.
+ {{#DefnLifeInsIsGPT}}
+ This premium may be in excess of the maximum premium allowable
+ in order to qualify this policy as life insurance.
+ {{/DefnLifeInsIsGPT}}
+ </p>
+{{/SinglePremium}}
+
+<p>
+Loaned amounts of the {{AvName}}
+Value will be credited a rate equal to the loan interest rate less
+a spread, guaranteed not to exceed
+{{#GroupCarveout}}
+1.25%.
+{{/GroupCarveout}}
+{{^GroupCarveout}}
+3.00%.
+{{/GroupCarveout}}
+</p>
+
+{{#HasTerm}}
+ <p>
+ The term rider provides the option to purchase monthly
+ term insurance on the life of the insured. The term rider
+ selected face amount supplements the selected face amount
+ of the contract. If the term rider is attached, the policy
+ to which it is attached may have a lower annual cutoff premium
+ and, as a result, the lower overall sales loads paid may be
+ lower than a contract having the same total face amount,
+ but with no term rider.
+ {{#NoLapse}}
+ Also, the lapse protection feature of the contract's
+ {{NoLapseProvisionName}}
+ does not apply to the term rider's selected face amount.
+ {{/NoLapse}}
+ </p>
+{{/HasTerm}}
+
+{{#HasWP}}
+ <p>
+ The Waiver of Monthly Charges Rider provides for waiver
+ of monthly charges in the event of the disability
+ of the insured that begins before attained age 65
+ and continues for at least 6 months, as described in the rider.
+ An additional charge is associated with this rider. Please refer
+ to your contract for specific provisions and a detailed schedule
+ of charges.
+ </p>
+{{/HasWP}}
+
+{{#HasADD}}
+ <p>
+ The Accidental Death benefit provides an additional benefit
+ if death is due to accident. An additional charge is associated
+ with this rider. Please refer to your contract
+ for specific provisions and a detailed schedule of charges.
+ </p>
+{{/HasADD}}
+
+<p>
+The definition of life insurance for this contract is the
+{{#DefnLifeInsIsGPT}}
+ guideline premium test. The guideline single premium
+ is ${{InitGSP}}
+ and the guideline level premium
+ is ${{InitGLP}}
+{{/DefnLifeInsIsGPT}}
+{{^DefnLifeInsIsGPT}}
+ cash value accumulation test.
+{{/DefnLifeInsIsGPT}}
+</p>
+
+<p>
+This is an illustration only. An illustration is not intended
+to predict actual performance. Interest rates
+and values set forth in the illustration are not guaranteed.
+</p>
+
+<p>
+{{^StateIsTexas}}
+ This illustration assumes that the currently illustrated
+ non-guaranteed elements will continue unchanged
+ for all years shown. This is not likely to occur
+ and actual results may be more or less favorable than shown.
+ The non-guaranteed benefits and values are not guaranteed
+ and are based on assumptions such as interest credited
+ and current monthly charges, which are subject to change by
+ {{InsCoName}}.
+{{/StateIsTexas}}
+{{#StateIsTexas}}
+ This illustration is based on both non-guaranteed
+ and guaranteed assumptions. Non-guaranteed assumptions
+ include interest rates and monthly charges.
+ This illustration assumes that the currently illustrated
+ non-guaranteed elements will continue unchanged
+ for all years shown. This is not likely to occur
+ and actual results may be more or less favorable than shown.
+ Factors that may affect future policy performance include
+ the company's expectations for future mortality, investments,
+ persistency, profits and expenses.
+{{/StateIsTexas}}
+</p>
+
+</font>
diff --git a/reg_narr_summary2.mst b/reg_narr_summary2.mst
new file mode 100644
index 0000000..4e667dd
--- /dev/null
+++ b/reg_narr_summary2.mst
@@ -0,0 +1,258 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+<!-- No header for this page, just the logo -->
+<scaled_image inv_factor="0.36" src="company_logo.png"></scaled_image>
+
+<p align="center">NARRATIVE SUMMARY (Continued)</p>
+
+<br></br>
+
+<font size="-1">
+
+{{#HasSalesLoadRefund}}
+ <p>
+ Sales Load Refund: We will refund a portion of the sales load
+ to you, as part of your {{CsvName}}
+ Value, if you surrender your contract within the first two
+ policy years. In policy year 1, we will refund
+ {{SalesLoadRefundRate0}}
+ of the first contract year sales load collected
+ and in contract year 2, we will refund
+ {{SalesLoadRefundRate1}}
+ of the first contract year sales load collected.
+ </p>
+{{/HasSalesLoadRefund}}
+
+{{#NoLapse}}
+ <p>
+ {{NoLapseProvisionName}}:
+ The contract will remain in force after the first premium
+ has been paid, even if there is insufficient
+ {{AvName}} Value
+ to cover the monthly charges provided that the insured
+ is not in a substandard rating class and the policy debt
+ does not exceed {{AvName}} Value.
+ </p>
+{{/NoLapse}}
+
+<p>
+{{GuarMortalityFootnote}}
+</p>
+
+<p>
+This illustration assumes death of the insured at age {{EndtAge}}.
+</p>
+
+<p>
+The loan interest rate is fixed at {{InitAnnLoanDueRate}} per year.
+</p>
+
+<p>
+The state of issue is {{StatePostalAbbrev}}.
+</p>
+
+<p>
+This illustration assumes an initial Selected Face Amount of
+${{InitBaseSpecAmt}}. Selected Face
+Amount reductions assumed in this illustration (if any) are shown
+in the Tabular Detail.
+</p>
+
+{{#IsInforce}}
+ <p>
+ This illustration assumes a beginning account value of
+ ${{InforceUnloanedAV}} as of the date of this illustration.
+ </p>
+
+ <p>
+ This illustration assumes a beginning cost basis of
+ ${{InforceTaxBasis}} as
+ of the date of this illustration; the actual cost basis
+ may be higher or lower. Consult the Home Office for cost
+ basis information.
+ </p>
+{{/IsInforce}}
+
+{{#Composite}}
+ <p>
+ Please see the attached census, listing the face amounts,
+ underwriting classes and issue ages for individual participants.
+ </p>
+{{/Composite}}
+
+{{#StateIsCarolina}}
+ <p>
+ In the states of North Carolina and South Carolina,
+ Guaranteed Issue Underwriting is referred
+ to as "Limited Underwriting" and Simplified
+ Issue Underwriting is referred to as "Simplified Underwriting".
+ </p>
+{{/StateIsCarolina}}
+
+{{#StateIsMaryland}}
+ <p>
+ In the state of Maryland, Guaranteed Issue Underwriting
+ is referred to as "Nonstandard Limited Underwriting"
+ and Simplified Issue Underwriting is referred to as
+ "Nonstandard Simplified Underwriting".
+ </p>
+{{/StateIsMaryland}}
+
+{{#GroupExperienceRating}}
+ <p>
+ We may assess a Market Value Adjustment upon a surrender
+ of the certificate when the surrender proceeds are intended
+ to be applied to an insurance policy issued by an insurer
+ unaffiliated with
+ {{InsCoShortName}}
+ with an intent to qualify the exchange as a tax free exchange
+ under IRC section 1035.
+ </p>
+
+ {{^UseExperienceRating}}
+ <p>
+ This illustration does not reflect experience rating.
+ </p>
+ {{/UseExperienceRating}}
+
+ <p>
+ The guaranteed values reflect the maximum charges permitted
+ by the contract, which may include an Experience Rating
+ Risk Charge.
+ </p>
+
+ <p>
+ No Experience Rating Risk Charge or a distribution
+ of an Experience Rating Reserve Credit is reflected
+ in the current, non-guaranteed values. Actual charges
+ and credits will be based on the actual experience of the group.
+ </p>
+{{/GroupExperienceRating}}
+
+{{#Has1035ExchCharge}}
+ <p>
+ {{^SinglePremium}}
+ Upon surrender of this policy, where the surrender
+ proceeds are intended to be applied to an insurance policy
+ or certificate issued in conjunction with an intent
+ to qualify the exchange as a tax free exchange
+ under Section 1035 of the Internal Revenue Code,
+ we may assess an Exchange Charge. The Exchange Charge
+ is the greater of the Market Value Adjustment Charge
+ and the Percentage of Premium Charge. In the states
+ of Florida or Indiana, the Exchange charge
+ (referred to as Assignment Charge in Florida)
+ will be the Percentage of Premium Charge only.
+ The Exchange Charge will potentially reduce
+ the surrender proceeds, but will never increase
+ the surrender proceeds. Please refer to your policy
+ for details.
+ {{/SinglePremium}}
+ {{#SinglePremium}}
+ Upon surrender of this policy, where the surrender proceeds
+ are intended to be applied to an insurance policy
+ or certificate issued in conjunction with an intent
+ to qualify the exchange as a tax free exchange
+ under Section 1035 of the Internal Revenue Code (1035
+ Exchange), we may assess an Exchange Charge. The Exchange
+ Charge will potentially reduce the surrender proceeds,
+ but will never increase the surrender proceeds.
+ Please refer to your policy for details.
+ {{/SinglePremium}}
+ </p>
+{{/Has1035ExchCharge}}
+
+{{#HasSpouseRider}}
+ <p>
+ The ${{SpouseRiderAmount}} Spouse
+ rider provides term life insurance on the spouse
+ (issue age {{SpouseIssueAge}})
+ for a limited duration, for an extra charge.
+ Please refer to your contract for specific provisions
+ and a detailed schedule of charges.
+ </p>
+{{/HasSpouseRider}}
+
+{{#HasChildRider}}
+ <p>
+ The ${{ChildRiderAmount}} Child
+ rider provides term life insurance on the insured's children
+ for a limited duration, for an extra charge. Please refer
+ to your contract for specific provisions
+ and a detailed schedule of charges.
+ </p>
+{{/HasChildRider}}
+
+<p>{{SurrenderFootnote}}</p>
+
+<p>{{FundRateFootnote}}</p>
+
+<p> {{FundRateFootnote0}}</p>
+
+<p> {{FundRateFootnote1}}</p>
+
+</font>
+
+<p align="center">IMPORTANT TAX DISCLOSURE</p>
+
+<font size="-1">
+
+<p>
+As illustrated, this contract
+{{#IsMec}}
+ becomes
+{{/IsMec}}
+{{^IsMec}}
+ would not become
+{{/IsMec}}
+a Modified Endowment Contract (MEC)
+under the Internal Revenue Code
+{{#IsMec}}
+in year {{MecYearPlus1}}
+{{/IsMec}}
+To the extent of gain in the contract, loans, distributions
+and withdrawals from a MEC are subject to income tax
+and may also trigger a penalty tax.
+</p>
+
+{{^SinglePremium}}
+ {{^IsInforce}}
+ <p>
+ The initial 7-pay premium limit is ${{InitSevenPayPrem}}.
+ </p>
+ {{/IsInforce}}
+{{/SinglePremium}}
+
+<p>
+<b>
+The information contained in this illustration is not written
+or intended as tax or legal advice, and may not be relied upon
+for purposes of avoiding any federal tax penalties.
+Neither {{InsCoShortName}} nor any
+of its employees or representatives are authorized to give tax
+or legal advice. For more information pertaining
+to the tax consequences of purchasing or owning this policy,
+consult with your own independent tax or legal counsel.
+</b>
+</p>
+
+</font>
diff --git a/reg_numeric_summary.mst b/reg_numeric_summary.mst
new file mode 100644
index 0000000..4e953b7
--- /dev/null
+++ b/reg_numeric_summary.mst
@@ -0,0 +1,203 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>reg_header}}
+
+<p align="center">Numeric Summary</p>
+
+{{>dollar_units}}
+
+<font size="-1">
+
+<br></br>
+<br></br>
+
+<numeric_summary_table></numeric_summary_table>
+
+<br></br>
+<br></br>
+
+<!--
+This is a workaround for what looks like a wxHTML bug, without explicitly
+resetting the alignment here, the next paragraph would be centered, somehow
+keeping the center alignment of the last "p" tag above.
+-->
+<p align="left"></p>
+
+{{#Composite}}
+<p>
+ The year of policy lapse on a guaranteed, midpoint
+ and current basis is not depicted in the above table of values
+ for this composite illustration because it is not applicable
+ on a case basis.
+</p>
+{{/Composite}}
+
+{{#LapseYear_Guaranteed_LT_MaxDuration}}
+<p>
+ Additional premium will be required
+ in year {{LapseYear_Guaranteed_Plus1}}
+ or contract will lapse based on guaranteed monthly charges
+ and interest rate.
+</p>
+{{/LapseYear_Guaranteed_LT_MaxDuration}}
+
+{{#LapseYear_Midpoint_LT_MaxDuration}}
+<p>
+ Additional premium will be required
+ in year {{LapseYear_Midpoint_Plus1}}
+ or contract will lapse based on midpoint monthly charges
+ and interest rate.
+</p>
+{{/LapseYear_Midpoint_LT_MaxDuration}}
+
+{{#LapseYear_Current_LT_MaxDuration}}
+<p>
+ Additional premium will be required
+ in year {{LapseYear_Current_Plus1}}
+ or contract will lapse based on current monthly charges
+ and interest rate.
+</p>
+{{/LapseYear_Current_LT_MaxDuration}}
+
+{{#IsMec}}
+<p>
+ IMPORTANT TAX DISCLOSURE: This is a Modified Endowment Contract.
+ Please refer to the Narrative Summary for additional information.
+</p>
+{{/IsMec}}
+
+<br>
+<br>
+
+<p align="center">Certification Statements</p>
+ <p>
+ CONTRACT OWNER / APPLICANT
+ </p>
+ {{#HasInterestDisclaimer}}
+ <p>
+ I understand that at the present time higher current interest rates
+ are credited for policies with case premiums in the amount
+ of {{InterestDisclaimer}}
+ </p>
+ {{/HasInterestDisclaimer}}
+ <p>
+ {{#StateIsIllinoisOrTexas}}
+ {{#StateIsIllinois}}
+ I have received a copy of this illustration and understand
+ that this illustration assumes that the currently illustrated
+ non-guaranteed elements will continue unchanged
+ for all years shown. This is not likely to occur,
+ and actual results may be more or less favorable than those shown.
+ {{/StateIsIllinois}}
+ {{#StateIsTexas}}
+ A copy of this illustration has been provided
+ to the Applicant/Policyowner.
+ {{/StateIsTexas}}
+ {{/StateIsIllinoisOrTexas}}
+ {{^StateIsIllinoisOrTexas}}
+ I have received a copy of this illustration, and I understand
+ that any non-guaranteed elements illustrated are subject
+ to change and could be either higher or lower. Additionally,
+ I have been informed by my agent that these values
+ are not guaranteed.
+ {{/StateIsIllinoisOrTexas}}
+ </p>
+ <br>
+ <br>
+ <table cellspacing="0" cellpadding="0" valign="top">
+ <tr>
+ <td>
+ <u>{{Space64}}{{Space32}}</u>
+ </td>
+ <td>
+ {{Space32}}
+ </td>
+ <td>
+ <u>{{Space32}}</u>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ CONTRACT OWNER OR APPLICANT SIGNATURE
+ </td>
+ <td>
+ {{Space32}}
+ </td>
+ <td>
+ DATE
+ </td>
+ </tr>
+ </table>
+ <br>
+ <br>
+ <p>
+ AGENT / AUTHORIZED REPRESENTATIVE
+ </p>
+ <p>
+ {{#StateIsIllinoisOrTexas}}
+ {{#StateIsIllinois}}
+ I have informed the applicant or policyowner
+ that this illustration assumes that the currently illustrated
+ non-guaranteed elements will continue unchanged
+ for all years shown. This is not likely to occur,
+ and actual results may be more or less favorable than those
shown.
+ {{/StateIsIllinois}}
+ {{#StateIsTexas}}
+ A copy of this illustration has been provided
+ to the Applicant/Policyowner.
+ {{/StateIsTexas}}
+ {{/StateIsIllinoisOrTexas}}
+ {{^StateIsIllinoisOrTexas}}
+ I certify that this illustration has been presented
+ to the applicant, and that I have explained
+ that any non-guaranteed elements illustrated
+ are subject to change. I have made no statements
+ that are inconsistent with the illustration.
+ {{/StateIsIllinoisOrTexas}}
+ </p>
+ <br>
+ <br>
+ <table cellspacing="0" cellpadding="0" valign="top">
+ <tr>
+ <td>
+ <u>{{Space64}}{{Space32}}</u>
+ </td>
+ <td>
+ {{Space32}}
+ </td>
+ <td>
+ <u>{{Space32}}</u>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ AGENT OR AUTHORIZED REPRESENTATIVE
+ </td>
+ <td>
+ {{Space32}}
+ </td>
+ <td>
+ DATE
+ </td>
+ </tr>
+ </table>
+</font>
diff --git a/reg_supp_report.mst b/reg_supp_report.mst
new file mode 100644
index 0000000..1352417
--- /dev/null
+++ b/reg_supp_report.mst
@@ -0,0 +1,28 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>reg_header}}
+
+<p align="center">{{SupplementalReportTitle}}</p>
+
+{{>dollar_units}}
+
+{{! The supplemental report table itself is generated from C++ code }}
diff --git a/reg_tabular_details.mst b/reg_tabular_details.mst
new file mode 100644
index 0000000..c955c08
--- /dev/null
+++ b/reg_tabular_details.mst
@@ -0,0 +1,29 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>reg_header}}
+
+<p align="center">Tabular Detail</p>
+
+{{>dollar_units}}
+
+<br></br>
+<br></br>
diff --git a/reg_tabular_details2.mst b/reg_tabular_details2.mst
new file mode 100644
index 0000000..3dbf744
--- /dev/null
+++ b/reg_tabular_details2.mst
@@ -0,0 +1,27 @@
+{{!
+ Copyright (C) 2017, 2018 Gregory W. Chicares.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+ http://savannah.nongnu.org/projects/lmi
+ email: <address@hidden>
+ snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+}}
+
+{{>reg_header}}
+
+<p align="center">Tabular Detail, continued</p>
+
+<br></br>
+<br></br>
diff --git a/test_coding_rules.cpp b/test_coding_rules.cpp
index ce98024..81974e8 100644
--- a/test_coding_rules.cpp
+++ b/test_coding_rules.cpp
@@ -49,6 +49,7 @@ std::string my_taboo_indulgence(); // See
'my_test_coding_rules.cpp'.
std::map<std::string, bool> my_taboos(); // See 'my_test_coding_rules.cpp'.
+// Sort these enumerators alphabetically.
enum enum_phylum
{e_no_phylum = 0
,e_binary = 1 << 0
@@ -63,14 +64,15 @@ enum enum_phylum
,e_log = 1 << 9
,e_make = 1 << 10
,e_md5 = 1 << 11
- ,e_patch = 1 << 12
- ,e_rates = 1 << 13
- ,e_relax_ng = 1 << 14
- ,e_script = 1 << 15
- ,e_synopsis = 1 << 16
- ,e_touchstone = 1 << 17
- ,e_xml_input = 1 << 18
- ,e_xml_other = 1 << 19
+ ,e_mustache = 1 << 12
+ ,e_patch = 1 << 13
+ ,e_rates = 1 << 14
+ ,e_relax_ng = 1 << 15
+ ,e_script = 1 << 16
+ ,e_synopsis = 1 << 17
+ ,e_touchstone = 1 << 18
+ ,e_xml_input = 1 << 19
+ ,e_xml_other = 1 << 20
};
enum enum_kingdom
@@ -171,6 +173,7 @@ file::file(std::string const& file_path)
: ".html" == extension() ? e_html
: ".make" == extension() ? e_make
: ".md5sums" == extension() ? e_md5
+ : ".mst" == extension() ? e_mustache
: ".patch" == extension() ? e_patch
: ".rates" == extension() ? e_rates
: ".rnc" == extension() ? e_relax_ng
@@ -669,6 +672,7 @@ void check_defect_markers(file const& f)
&& "IHS " != z[1]
&& "INELEGANT " != z[1]
&& "INPUT " != z[1]
+ && "PDF " != z[1]
&& "PORT " != z[1]
&& "SOMEDAY " != z[1]
&& "TAXATION " != z[1]
diff --git a/workhorse.make b/workhorse.make
index 55c446d..3ddb6e2 100644
--- a/workhorse.make
+++ b/workhorse.make
@@ -896,7 +896,7 @@ test_dir := $(exec_prefix)/test
touchstone_dir := $(exec_prefix)/touchstone
data_files := \
- $(wildcard $(addprefix $(srcdir)/,*.ico *.png *.xml *.xrc *.xsd *.xsl)) \
+ $(wildcard $(addprefix $(srcdir)/,*.ico *.mst *.png *.xml *.xrc *.xsd
*.xsl)) \
help_files := \
$(wildcard $(addprefix $(srcdir)/,*.html)) \
diff --git a/wx_table_generator.cpp b/wx_table_generator.cpp
index 237fbf2..8ebe75d 100644
--- a/wx_table_generator.cpp
+++ b/wx_table_generator.cpp
@@ -53,6 +53,7 @@ wx_table_generator::wx_table_generator
,total_width_(total_width)
,char_height_(dc_.GetCharHeight())
,row_height_((4 * char_height_ + 2) / 3) // Arbitrarily use 1.333 line
spacing.
+ ,column_margin_(dc_.GetTextExtent("M").x)
,has_column_widths_(false)
,max_header_lines_(1)
{
@@ -64,6 +65,18 @@ wx_table_generator::wx_table_generator
dc_.SetPen(pen);
}
+void wx_table_generator::use_condensed_style()
+{
+ row_height_ = char_height_;
+ draw_separators_ = false;
+ use_bold_headers_ = false;
+}
+
+void wx_table_generator::align_right()
+{
+ align_right_ = true;
+}
+
void wx_table_generator::add_column
(std::string const& header
,std::string const& widest_text
@@ -77,7 +90,11 @@ void wx_table_generator::add_column
}
else
{
- wxDCFontChanger set_header_font(dc_, get_header_font());
+ wxDCFontChanger set_header_font(dc_);
+ if(use_bold_headers_)
+ {
+ set_header_font.Set(get_header_font());
+ }
// Set width to the special value of 0 for the variable width columns.
width = widest_text.empty() ? 0 : dc_.GetTextExtent(widest_text).x;
@@ -94,8 +111,7 @@ void wx_table_generator::add_column
{
increase_to_if_smaller(width,
dc_.GetMultiLineTextExtent(header).x);
- // Add roughly 1 em margins on both sides.
- width += dc_.GetTextExtent("MM").x;
+ width += 2*column_margin_;
}
}
@@ -157,6 +173,8 @@ void
wx_table_generator::do_compute_column_widths_if_necessary()
return;
}
+ has_column_widths_ = true;
+
int num_expand = 0;
int total_fixed = 0;
@@ -179,7 +197,43 @@ void
wx_table_generator::do_compute_column_widths_if_necessary()
if(total_width_ < total_fixed)
{
- warning() << "Not enough space for all fixed columns." << LMI_FLUSH;
+ auto const overflow = total_fixed - total_width_;
+
+ // If we have only fixed columns, try to make them fit by decreasing
+ // the margins around them if this can help, assuming that we can
+ // reduce them by up to half if really needed.
+ if(!num_expand)
+ {
+ int const num_columns = columns_.size();
+ auto const overflow_per_column =
+ (overflow + num_columns - 1)/num_columns;
+ if(overflow_per_column <= column_margin_)
+ {
+ for(auto& i : columns_)
+ {
+ if(i.is_hidden())
+ {
+ continue;
+ }
+
+ i.width_ -= overflow_per_column;
+ }
+
+ column_margin_ -= (overflow_per_column + 1)/2;
+
+ // We condensed the columns enough to make them fit, so no need
+ // for the warning and we don't have any expanding columns, so
+ // we're done.
+ return;
+ }
+ }
+
+ warning()
+ << "Not enough space for all fixed columns: "
+ << overflow
+ << " more pixels needed."
+ << LMI_FLUSH
+ ;
return;
}
@@ -201,8 +255,6 @@ void
wx_table_generator::do_compute_column_widths_if_necessary()
}
}
}
-
- has_column_widths_ = true;
}
void wx_table_generator::do_output_values
@@ -216,7 +268,10 @@ void wx_table_generator::do_output_values
int const y_text = y + char_height_;
y += row_height_;
- do_output_vert_separator(x, y_top, y);
+ if(draw_separators_)
+ {
+ do_output_vert_separator(x, y_top, y);
+ }
std::size_t const num_columns = columns_.size();
for(std::size_t col = 0; col < num_columns; ++col)
@@ -233,21 +288,31 @@ void wx_table_generator::do_output_values
if(!s.empty())
{
int x_text = x;
- if(ci.is_centered_)
+
+ if(align_right_)
{
- // Centre the text for the columns configured to do it.
- x_text += (width - dc_.GetTextExtent(s).x) / 2;
+ x_text += width - dc_.GetTextExtent(s).x;
}
else
{
- // Otherwise just offset it by ~1 em.
- x_text += dc_.GetTextExtent("M").x;
- }
+ if(ci.is_centered_)
+ {
+ // Centre the text for the columns configured to do it.
+ x_text += (width - dc_.GetTextExtent(s).x) / 2;
+ }
+ else
+ {
+ x_text += column_margin_;
+ }
+ }
dc_.DrawText(s, x_text, y_text);
}
x += width;
- do_output_vert_separator(x, y_top, y);
+ if(draw_separators_)
+ {
+ do_output_vert_separator(x, y_top, y);
+ }
}
}
@@ -269,8 +334,17 @@ void wx_table_generator::output_horz_separator
(std::size_t begin_column
,std::size_t end_column
,int y
+ ,enum_output_mode output_mode
)
{
+ switch(output_mode)
+ {
+ case e_output_normal:
+ break;
+ case e_output_measure_only:
+ return;
+ }
+
LMI_ASSERT(begin_column < end_column);
LMI_ASSERT(end_column <= columns_.size());
@@ -287,11 +361,24 @@ void wx_table_generator::output_horz_separator
do_output_horz_separator(x1, x2, y);
}
-void wx_table_generator::output_header(int* pos_y)
+void wx_table_generator::output_header(int* pos_y, enum_output_mode
output_mode)
{
+ switch(output_mode)
+ {
+ case e_output_normal:
+ break;
+ case e_output_measure_only:
+ *pos_y += max_header_lines_ * row_height_;
+ return;
+ }
+
do_compute_column_widths_if_necessary();
- wxDCFontChanger set_header_font(dc_, get_header_font());
+ wxDCFontChanger set_header_font(dc_);
+ if(use_bold_headers_)
+ {
+ set_header_font.Set(get_header_font());
+ }
// Split headers in single lines and fill up the entire columns*lines 2D
// matrix, using empty strings for the headers with less than the maximal
@@ -328,9 +415,47 @@ void wx_table_generator::output_header(int* pos_y)
}
// Finally draw the separators above and (a double one) below them.
- do_output_horz_separator(left_margin_, x, y_top );
- do_output_horz_separator(left_margin_, x, *pos_y - 1);
- do_output_horz_separator(left_margin_, x, *pos_y );
+ if(draw_separators_)
+ {
+ do_output_horz_separator(left_margin_, x, y_top );
+ do_output_horz_separator(left_margin_, x, *pos_y - 1);
+ do_output_horz_separator(left_margin_, x, *pos_y );
+ }
+}
+
+void wx_table_generator::output_super_header
+ (std::string const& header
+ ,std::size_t begin_column
+ ,std::size_t end_column
+ ,int* pos_y
+ ,enum_output_mode output_mode
+ )
+{
+ std::vector<std::string> const lines(split_into_lines(header));
+
+ switch(output_mode)
+ {
+ case e_output_normal:
+ break;
+ case e_output_measure_only:
+ *pos_y += row_height_*lines.size();
+ return;
+ }
+
+ // We don't have a function for getting the rectangle of a span of columns,
+ // but we can reuse the existing text_rect() if we just increase its width
+ // by the width of all the extra (i.e. not counting the starting one)
+ // columns in this span.
+ auto rect = text_rect(begin_column, *pos_y);
+ rect.width += do_get_cell_x(end_column) - do_get_cell_x(begin_column + 1);
+
+ for(auto const& line : lines)
+ {
+ dc_.DrawLabel(line, rect, wxALIGN_CENTER_HORIZONTAL);
+
+ rect.y += row_height_;
+ *pos_y += row_height_;
+ }
}
void wx_table_generator::output_row
@@ -341,7 +466,10 @@ void wx_table_generator::output_row
int x = left_margin_;
do_output_values(x, *pos_y, values);
- do_output_horz_separator(left_margin_, x, *pos_y);
+ if(draw_separators_)
+ {
+ do_output_horz_separator(left_margin_, x, *pos_y);
+ }
}
void wx_table_generator::output_highlighted_cell
diff --git a/wx_table_generator.hpp b/wx_table_generator.hpp
index 515a3ce..e9df872 100644
--- a/wx_table_generator.hpp
+++ b/wx_table_generator.hpp
@@ -24,6 +24,8 @@
#include "config.hpp"
+#include "output_mode.hpp"
+
#include <wx/dc.h>
#include <wx/font.h>
@@ -52,19 +54,34 @@ class wx_table_generator
// Adds a column to the table. The total number of added columns determines
// the number of the expected value in output_row() calls.
//
- // Providing an empty header suppresses the table display, while still
- // taking into account in output_row(), providing a convenient way to hide
- // a single column without changing the data representation.
+ // Providing an empty header suppresses the column display, while still
+ // taking it into account in output_row(), providing a convenient way to
+ // hide a single column without changing the data representation.
//
// Each column must either have a fixed width, specified as the width of
// the longest text that may appear in this column, or be expandable
// meaning that the rest of the page width is allocated to it which will be
// the case if widest_text is empty.
+ //
// Notice that column headers may be multiline strings.
void add_column(std::string const& header, std::string const& widest_text);
// Render the headers at the given position and update it.
- void output_header(int* pos_y);
+ void output_header
+ (int* pos_y
+ ,enum_output_mode output_mode = e_output_normal
+ );
+
+ // Render a super-header, i.e. a header spanning over several columns. The
+ // columns range is specified as a close/open interval, as usual in C++.
+ // The header string may be multiline, just as with normal headers.
+ void output_super_header
+ (std::string const& header
+ ,std::size_t begin_column
+ ,std::size_t end_column
+ ,int* pos_y
+ ,enum_output_mode output_mode = e_output_normal
+ );
// Render a row with the given values at the given position and update it.
// The values here can be single-line only and there must be exactly the
@@ -79,6 +96,9 @@ class wx_table_generator
,std::string const& value
);
+ // Return the number of columns.
+ std::size_t columns_count() const {return columns_.size();}
+
// Return the height of a single table row.
int row_height() const {return row_height_;}
@@ -94,9 +114,10 @@ class wx_table_generator
// Output a horizontal separator line across the specified columns,
// using the usual C++ close/open interval convention.
void output_horz_separator
- (std::size_t begin_column
- ,std::size_t end_column
- ,int y
+ (std::size_t begin_column
+ ,std::size_t end_column
+ ,int y
+ ,enum_output_mode output_mode = e_output_normal
);
// Output a vertical separator line before the given column. Notice that
@@ -104,6 +125,15 @@ class wx_table_generator
// output a separator after the last column.
void output_vert_separator(std::size_t before_column, int y);
+ // Use condensed style: don't draw separators between rows and make them
+ // smaller.
+ void use_condensed_style();
+
+ // By default, columns are centered if they have fixed size or left-aligned
+ // otherwise. By calling this method, this alignment auto-detection is
+ // turned off and all columns are right-aligned.
+ void align_right();
+
private:
// Return the font used for the headers.
wxFont get_header_font() const;
@@ -128,7 +158,8 @@ class wx_table_generator
// These values could be recomputed, but cache them for performance.
int const char_height_;
- int const row_height_;
+ int row_height_;
+ int column_margin_;
struct column_info
{
@@ -160,6 +191,18 @@ class wx_table_generator
// Maximal number of lines in any column header, initially 1 but can be
// higher if multiline headers are used.
std::size_t max_header_lines_;
+
+ // If false, separator lines are not drawn automatically (they can still be
+ // drawn by calling output_horz_separator() or output_vert_separator()
+ // explicitly).
+ bool draw_separators_ = true;
+
+ // If true, headers are drawn in bold.
+ bool use_bold_headers_ = true;
+
+ // If true, force right alignment for all columns instead of centering them
+ // automatically if they have fixed size.
+ bool align_right_ = false;
};
#endif // wx_table_generator_hpp
- [lmi-commits] [lmi] master 81ccdc8 129/156: Remove the never used elements of the columns title map, (continued)
- [lmi-commits] [lmi] master 81ccdc8 129/156: Remove the never used elements of the columns title map, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 2478fbc 010/156: Start implementing PDF illustrations directly, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 9d24cd9 024/156: Compilation fix for C++11 but not C++14 compilers, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 1229745 020/156: Make add_body_paragraph() even more convenient to use, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 14aca9e 142/156: Replace underscores in title map with explicit line breaks, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 5241f0f 045/156: Fix some typos in wx_table_generator::add_column() comment, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 47bddbf 155/156: Merge branch 'vz-no-xslfo' into gwc-no-xslfo, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master a1342a9 106/156: Fix supplemental NASD illustration page for split premiums, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master ec0b298 110/156: Add NASD illustration second explanatory notes page, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 80423d2 134/156: Improve appearance of lower header part in NASD illustrations, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 408ba18 156/156: Merge branch 'gwc-no-xslfo' [387],
Greg Chicares <=
- [lmi-commits] [lmi] master de5d0b8 060/156: Inline output_and_check_for_page_break() function, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 5421d85 002/156: Factor out pdf_writer_wx from group_quote_pdf_gen_wx, Greg Chicares, 2018/01/30