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.

../../_images/publish_example.png

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:

  1. Add USING_PUBLISH to the preprocessor definitions in the C/C++ section of the project settings

  2. Add hps_sprk_publish.lib to the Linker Additional Dependencies section of the project setting

  3. 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);
	}
}