Exporting a PDF document
In this chapter, we discuss how to create a PDF document from our HOOPS Visualize application. The aim to is create the document shown below. It contains some information about the model, an embedded 3D model and of course a table.

We will introduce HOOPS Publish and explain how to use it from HOOPS Visualize. For an introduction to HOOPS Publish in the context of HOOPS Visualize, please refer to this section of the HOOPS Visualize Documentation.
For a more in depth introduction to HOOPS Publish itself, please refer to the HOOPS Publish document which can be found here.
Adding HOOPS Publish to the project
By default, the base MFC sandbox doesn’t include HOOPS Publish. You will need to take three steps to ensure it is included in the project:
Add
USING_PUBLISH
to the preprocessor definitions in the C/C++ section of the project settingsAdd hps_sprk_publish.lib to the Linker Additional Dependencies section of the project setting
Ensure that you have installed a copy of the 3rd-party utility TableToPDF (see below). This is essential for the table export to work.
TableToPDF
TableToPDF is a utility that creates PDF tables from HTML. The table implementation uses an add-on to HOOPS Publish (TableToPDF) which is provided for free by Tech Soft 3D. The add-on is using components that are licensed under GNU LGPL terms. Consequently, the usage of tables using HOOPS Publish add-on requires your application to comply with LGPL requirements. TableToPDF can be downloaded at http://developer.techsoft3d.com/add-ons/tabletopdf/. The deployment process is simple and just requires to copy the provided DLLs in HOOPS Publish binaries folder.
Creating a report builder class
We begin by defining a class to encapsulate the PDF report building functionality, which we’ll call IFCReportBuilder. This class will take an IFCQuantity
object and generate a PDF report based on that. The full class declaration is shown below. In addition to storing the parameters we pass in with the member functions, we have also declared some internal string variables which are used during the report building phase.
class IFCReportBuilder
{
IFCQuantity *_mpQuantity;
HPS::CADModel _cadModel;
HPS::UTF8 _reportPath;
std::string _strTableStyle;
std::string _strTableBegin;
std::string _strTableContent;
std::string _strTableEnd;
void InitializeTable();
void InsertTableRow(IFCQuantityRecord &record);
void InsertTableTotal();
void CreatePDF( std::string &strTable);
public:
IFCReportBuilder();
~IFCReportBuilder();
void SetQuantities(IFCQuantity * pQuants);
void SetCADModel(HPS::CADModel &cadModel);
bool BuildReport( HPS::UTF8 &path);
void ClearAll();
};
Implementation overview
Before we get into the details, let’s first discuss what our PDF document contains and which HOOPS Publish object types we will be working with.
At the highest level, we will be creating a PDF Document. For this simple example, we just use a single page.
The PDF page contains a page title, information about the model, a 3D view containing the model, and a table of quantities. In summary, we will be working with the following objects:
PDF Document
PDF Page
Text Object
3D Annotation
Table
In the context of HOOPS Visualize and the Component classes, these are defined by the following kits:
HPS::Publish::AnnotationKit
HPS::Publish::PageKit
HPS::Publish::DocumentKit
HPS::Publish::TableKit
HPS::Publish::TextKit
For each, there is also a key to access the object. For instance, in the case of a Document, HPS::Publish::DocumentKey
.
Defining a page and its contents
Using HOOPS Publish, you typically define the contents of a PDF document using kits and create the objects when adding them to a page. To begin, lets set a layout for the page, then we can position objects in the page with respect to that.
pageKit.SetFormat(HPS::Publish::Page::Format::A4); // 595 x 842 pts
pageKit.SetOrientation(HPS::Publish::Page::Orientation::Portrait);
Creating a text object
The simplest object to create is a plain text label. We use this for the page title as shown below:
textKit.SetColor(RGBColor(0.25, 0.25, 0.25))
.SetFont(HPS::Publish::Text::Font::Name::Helvetica)
.SetSize(24)
.SetText("Dodgy Construction Company");
pageKit.AddText(textKit, IntRectangle(50, 350, 805, 830));
Additionally, using the CAD model we passed in, let’s display its name.
std::string strModelName = "Model:" + _cadModel.GetName();
textKit.SetColor(RGBColor(0.25, 0.25, 0.25))
.SetFont(HPS::Publish::Text::Font::Name::Helvetica)
.SetSize(12)
.SetText(strModelName.c_str());
pageKit.AddText(textKit, IntRectangle(50, 350, 788, 800));
As you can see, the textKit
allows us to control various attributes of the text, such as the font, color, and size. One thing to be aware of is the page origin is located at the lower left side of the page.
Inserting a 3D model
Inserting a 3D model into the page, using default settings, is also simple:
annotationKit.SetSource(_cadModel);
annotationKit.SetPRCBRepCompression(Publish::PRC::BRepCompression::Medium); // set B-rep compression to Medium
annotationKit.SetPRCTessellationCompression(true); // use tessellation compression
pageKit.SetAnnotation(annotationKit, IntRectangle(50, 562, 480, 780));
This code will create an embedded 3D object in the PDF page, encoded as a PRC data object. This is the same data type used by HOOPS Exchange. In this case, we select tessellation only.
In fact, we really only need to set the source, that is, the CAD model. However, it is possible to control more aspects of how we want to display such as the initial camera and poster image. The latter is used when the 3D object is not activated or the document printed.
Please refer to this section of the documentation for more details: https://docs.techsoft3d.com/hps/latest/build/prog_guide/0905_publish_integration.html.
Inserting a table
Creating the table is the most complex part of our tutorial example. We’re going to create a fixed position plain text table, although its possible to create tables with interactive fields and also interactive scrolling tables.
To begin, here is the code which inserts the table into the page:
tableKit.SetHTML(strTable.c_str()); // specify HTML source
tableKit.SetHTMLStyle(_strTableStyle.c_str(), HPS::Publish::Source::Type::Code); // specify CSS source
pageKit.AddTable(tableKit, IntRectangle(50, 450, 50, 450));
The parameters to the table are two strings - one representing the table content, and the other representing the table style. We’re using HTML to define the contents and the layout of the table. This is where the 3rd-party library TableToPDF comes in. This utility takes the HTML and creates a PDF object. Therefore, the main work for us to do is to create the HTML and CSS strings which define the table.
Building the report
To return to our class definition, we defined three functions. Two simply save the parameters to the report. The function BuildReport
is where most work takes place.
bool IFCReportBuilder::BuildReport( HPS::UTF8 & path)
{
if (0 == _mpQuantity)
return false;
_reportPath = path;
for (auto qr : _mpQuantity->GetQuantities())
InsertTableRow(qr);
InsertTableTotal();
std::string strTable = _strTableBegin + _strTableContent + _strTableEnd;
HPS::Publish::DocumentKit documentKit;
CreatePDF( strTable );
return true;
}
As can be seen, this function assembles an HTML string representing the table contents. It in turns calls the following functions:
void IFCReportBuilder::InsertTableRow(IFCQuantityRecord & qr)
{
std::stringstream ss;
ss << "<tr> <td>" << qr._componentName.GetBytes() << "</td>" <<
"<td >" << qr._componentType.GetBytes() << "</td>" <<
"<td >" << qr.GetComponentQuantity() << "</td>"
"<td >" << qr._unitCost << "</td> "
"<td >" << qr._totalCost << "</td> </tr>";
_strTableContent += ss.str();
}
void IFCReportBuilder::InsertTableTotal()
{
float totalCost = 0;
for (auto qr : _mpQuantity->GetQuantities())
totalCost += qr._totalCost;
std::stringstream ss;
ss << "<tr> <td>" << "Total" << "</td>" <<
"<td >" << "" << "</td>" <<
"<td >" << "" << "</td>"
"<td >" << ""<< "</td> "
"<td >" << "$" << totalCost << "</td> </tr>";
_strTableContent += ss.str();
}
Once the string is assembled, it is used as a parameter to the function CreatePDF
. The full source for CreatePDF
is shown here:
void IFCReportBuilder::CreatePDF(std::string & strTable)
{
HPS::Publish::ViewKit viewKit; // corresponds to a particular view of the model
HPS::Publish::ArtworkKit artworkKit; // container for Javascript, PMI, and views
HPS::Publish::AnnotationKit annotationKit; // represents the model as an object in the PDF
HPS::Publish::PageKit pageKit; // corresponds to a page inside the PDF
HPS::Publish::DocumentKit documentKit; // corresponds to the document itself as a whole
HPS::Publish::TableKit tableKit;
HPS::Publish::TextKit textKit;
//! [page_format]
pageKit.SetFormat(HPS::Publish::Page::Format::A4); // 595 x 842 pts
pageKit.SetOrientation(HPS::Publish::Page::Orientation::Portrait);
//! [page_format]
//
// Add some header information
//
//! [text_label]
textKit.SetColor(RGBColor(0.25, 0.25, 0.25))
.SetFont(HPS::Publish::Text::Font::Name::Helvetica)
.SetSize(24)
.SetText("Dodgy Construction Company");
pageKit.AddText(textKit, IntRectangle(50, 350, 805, 830));
//! [text_label]
//
// Lets add the name of the model
//
//! [model_name]
std::string strModelName = "Model:" + _cadModel.GetName();
textKit.SetColor(RGBColor(0.25, 0.25, 0.25))
.SetFont(HPS::Publish::Text::Font::Name::Helvetica)
.SetSize(12)
.SetText(strModelName.c_str());
pageKit.AddText(textKit, IntRectangle(50, 350, 788, 800));
//! [model_name]
//
// Put in a logo image
//
HPS::Publish::ImageKit imageKit;
imageKit.SetFile("C:\\Temp\\symbol.bmp");
imageKit.SetFormat(HPS::Publish::Image::Format::BMP);
imageKit.SetSize(256, 256);
HPS::UTF8 filename;
pageKit.AddImage(imageKit, IntRectangle(0,200, 300, 500));
imageKit.ShowFile(filename);
//
// Content Max Height = 780
//
//! [insert_model]
annotationKit.SetSource(_cadModel);
annotationKit.SetPRCBRepCompression(Publish::PRC::BRepCompression::Medium); // set B-rep compression to Medium
annotationKit.SetPRCTessellationCompression(true); // use tessellation compression
pageKit.SetAnnotation(annotationKit, IntRectangle(50, 562, 480, 780));
//! [insert_model]
#define USE_SLIDE_TABLE
#ifndef USE_SLIDE_TABLE
//! [add_table]
tableKit.SetHTML(strTable.c_str()); // specify HTML source
tableKit.SetHTMLStyle(_strTableStyle.c_str(), HPS::Publish::Source::Type::Code); // specify CSS source
pageKit.AddTable(tableKit, IntRectangle(50, 450, 50, 450));
//! [add_table]
#else
HPS::Publish::SlideTableKit slide_table;
slide_table.SetHTML(strTable.c_str()); // specify HTML source
slide_table.SetHTMLStyle(_strTableStyle.c_str(), HPS::Publish::Source::Type::Code); // specify CSS source
// buttons are specified by name using ButtonKit::SetName
slide_table.SetButtons("previous_button", "next_button");
pageKit.AddSlideTable(slide_table, IntRectangle(50, 450, 500, 750));
#endif
#ifdef USE_DOCUKIT
HPS::Publish::File::ExportPDF(documentKit, "c:\\temp\\_test2.pdf", Publish::ExportOptionsKit());
#else
HPS::Publish::DocumentKey myDocumentKey = HPS::Publish::File::CreateDocument(NULL);
myDocumentKey.AddPage(pageKit);
try
{
HPS::Publish::File::ExportPDF(myDocumentKey, _reportPath.GetBytes());
}
catch (HPS::Exception e)
{
myDocumentKey.Delete();
throw e;
}
myDocumentKey.Delete();
#endif
}
Creating the document
At the end of CreatePDF
, you can see that we create a new document and add the page we defined. Finally, we ask HOOPS Publish to create the PDF file on disk. There are a variety of exceptions which can be thrown by HOOPS Publish. We trap them here to tidy things up, but then throw the exception to the calling code to handle the UI notifications.
CSS creation
One thing we didn’t cover is how the table layout gets created. For this sample we just create a fixed layout defined called from the class constructor:
IFCReportBuilder::IFCReportBuilder()
{
_mpQuantity = 0;
InitializeTable();
}
void IFCReportBuilder::InitializeTable()
{
_strTableStyle = "<style type=\"text/css\"> \
table.gridtable {\
font-family: helvetica;\
font-size:10pt;\
text-align: left;\
border-width: 0pt;\
border-collapse: collapse;\
width:500pt; \
}\
table.gridtable th {\
border-width: 0pt;\
border-style: solid;\
background-color: #dedede;\
padding: 2pt;\
height:12pt;\
min-width:40pt; \
max-width:180pt; \
width:150pt; \
}\
table.gridtable td {\
border-width: 0pt;\
border-style: solid;\
background-color: #ffffff;\
padding: 2pt;\
height:12pt;\
min-width:40pt; \
max-width:180pt; \
width:150pt; \
}\
table.gridtable td.link {\
text-decoration:underline;\
color:blue;\
}\
table.gridtable td.pass {\
background-color: rgb(0,255,0);\
}\
table.gridtable td.fail {\
background-color: rgb(255,0,0);\
}\
</style>;";
_strTableBegin = "<table class=\"gridtable\"><tr> <th>Component Name</th> <th>Type</th> <th>Quantity</th> <th>Unit Cost</th><th>Total</th></tr>";
_strTableEnd = "</table>";
}
Connecting to the UI
When we created the quantities dialog, we added a button to create a PDF report. We need to attach an event handler to the button then call the report builder functionality.
It is relatively simple code, the majority of which is spent asking for a file path for the report. One thing to note is we trap any exceptions coming from the report builder, and in particular display a message.
void CHPSComponentQuantitiesDlg::OnBnClickedBtnQuantityReport()
{
CHPSDoc * pDoc = static_cast<CHPSDoc *>(static_cast<CFrameWnd *>(AfxGetApp()->m_pMainWnd)->GetActiveDocument());
HPS::CADModel model = pDoc->GetCADModel();
CString defaultExt(".pdf");
CString defaultName(model.GetName());
CString extensions("PDFDocument |*.pdf");
CFileDialog pdfDlg(FALSE, defaultExt, defaultName, OFN_CREATEPROMPT | OFN_EXPLORER | OFN_OVERWRITEPROMPT , extensions );
auto result = pdfDlg.DoModal();
if (result != IDOK)
return;
IFCReportBuilder builder;
builder.SetQuantities(mpQuantities);
builder.SetCADModel(model);
HPS::UTF8 pdfPath(pdfDlg.GetPathName());
try
{
builder.BuildReport( pdfPath);
}
catch (HPS::Exception e)
{
CString msg(e.what());
MessageBox( _T("Exception Creating PDF"), msg, MB_ICONWARNING | MB_OK);
}
}