Project

General

Profile

Actions

Support #3042

open

Convert widgets from page to PDF

Added by Anonymous almost 10 years ago. Updated almost 10 years ago.

Status:
Feedback
Priority:
Normal
Assignee:
Target version:
-
Start date:
05/01/2014
Due date:
% Done:

0%

Estimated time:

Description

Hello fellow WT users. I have a problem with placing a WTable into a PDF. I am using the PDF sample code from the widget gallery, and have been trying to make it place a WTable I have into the PDF.

http://www.webtoolkit.eu/widgets/media/pdf-output

The "PDF Images" samples work fine.

The "Rendering HTML to PDF" is where I have problems.

I am using the example exactly as it appears on the page, with the only addition being a WContainerWidget pointer for the items that I want in the PDF.

I do not understand what I need to pass into the renderPdf function as the WString that will be passed into the WPdfRenderer render function, I have tried examining the html of the widget gallery code to compare with my own, and mimic what the gallery is doing, but everything has so far failed to make a PDF.

This is what the URL looks like after I press the "Create Pdf" button, it displays a blank page with an empty pdf "Loading" message at the bottom left.

http://127.0.0.1:8080/ui/report.pdf?wtd=Psx1WfLVpsvVDpxW&request=resource&resource=oylm6i6&rand=1

//This is the code I have executing on my main page, just a group box with a few labels, a button for the sample pdf, a chart, and another button for the more advanced sample pdf(this one is broken).
    //PDF Images sample/////////////////////////////////////////////////////////////////////////
    Wt::WContainerWidget* container = table_container;
    Wt::WResource* sample_pdf = new SamplePdfResource(container);
    Wt::WPushButton* button1 = new Wt::WPushButton("Create pdf sample", container);
    button1->setLink(sample_pdf);
    //PDF Images sample chart///////////////////////////////////////////////////////////////////
    Wt::Chart::WCartesianChart* chart = CreateCartesianChart(table_container);
    Wt::WPainter* p = new Wt::WPainter((Wt::WPdfImage*)sample_pdf);
    chart->paint(*p);
    std::ofstream f("chart.pdf", std::ios::out | std::ios::binary);
    sample_pdf->write(f);
    //GroupBox to be placed into PDF////////////////////////////////////////////////////////////
    Wt::WGroupBox* group_box = new Wt::WGroupBox("Example Group Box", table_container);
    group_box->addStyleClass("example");
    Wt::WText* text = new Wt::WText("This is a text sample within the group box 1", group_box);
    text            = new Wt::WText("This is a text sample within the group box 2", group_box);
    text            = new Wt::WText("This is a text sample within the group box 3", group_box);
    //The pdf to take the GroupBox//////////////////////////////////////////////////////////////
    Wt::WPushButton* button = new Wt::WPushButton("Create pdf", container);
    Wt::WResource* pdf = new ReportResource(group_box, container);
    button->setLink(pdf);
    ////////////////////////////////////////////////////////////////////////////////////////////

//Below is the "Rendering HTML to PDF" code from the widget gallery, with the new container widget for a target.
namespace {
    void HPDF_STDCALL error_handler(HPDF_STATUS error_no, HPDF_STATUS detail_no,
               void *user_data) {
    fprintf(stderr, "libharu error: error_no=%04X, detail_no=%d\n",
        (unsigned int) error_no, (int) detail_no);
    }
}

class ReportResource : public Wt::WResource
{
public:
  ReportResource(Wt::WContainerWidget* target, Wt::WObject* parent = 0)
    : Wt::WResource(parent),
    _target(NULL)
  {
    suggestFileName("report.pdf");
    _target = target;
  }

  virtual void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
  {
    response.setMimeType("application/pdf");

    HPDF_Doc pdf = HPDF_New(error_handler, 0);

    // Note: UTF-8 encoding (for TrueType fonts) is only available since libharu 2.3.0 !
    HPDF_UseUTFEncodings(pdf);

    renderReport(pdf);

    HPDF_SaveToStream(pdf);
    unsigned int size = HPDF_GetStreamSize(pdf);
    HPDF_BYTE *buf = new HPDF_BYTE[size];
    HPDF_ReadFromStream (pdf, buf, &size);
    HPDF_Free(pdf);
    response.out().write((char*)buf, size);
    delete[] buf;
  }

private:

  Wt::WContainerWidget* _target;

  void renderReport(HPDF_Doc pdf)
  {
    std::stringstream ss;
    _target->htmlText(ss);
    std::string out = ss.str();
    std::string out_id = _target->id();
    std::string out_parent_id = _target->parent()->id();

    renderPdf(Wt::WString::tr(???), pdf);
  }

  void renderPdf(const Wt::WString& html, HPDF_Doc pdf)
  {
    HPDF_Page page = HPDF_AddPage(pdf);
    HPDF_Page_SetSize(page, HPDF_PAGE_SIZE_A4, HPDF_PAGE_PORTRAIT);

    Wt::Render::WPdfRenderer renderer(pdf, page);
    renderer.setMargin(2.54);
    renderer.setDpi(96);
    renderer.render(html);
  }
};

//PDF Images code
class SamplePdfResource : public Wt::WPdfImage
{
public:
  SamplePdfResource(Wt::WObject *parent = 0)
    : Wt::WPdfImage(400, 300, parent)
  {
    suggestFileName("line.pdf");
    paint();
  }

private:
  void paint()
  {
    Wt::WPainter painter(this);

    Wt::WPen thickPen;
    thickPen.setWidth(5);
    painter.setPen(thickPen);
    painter.drawLine(50, 250, 150, 50);
    painter.drawLine(150, 50, 250, 50);

    painter.drawText(0, 0, 400, 300, Wt::AlignCenter | Wt::AlignTop, "Hello, PDF");
  }
};

//Cartesian chart initialization
Wt::Chart::WCartesianChart* CreateCartesianChart(Wt::WContainerWidget* parent)
{
Wt::WStandardItemModel *model = new Wt::WStandardItemModel(PERFORMANCE_HISTORY, 2, parent);

//Create the scatter plot.
Wt::Chart::WCartesianChart* chart = new Wt::Chart::WCartesianChart(parent);
//Give the chart an empty model to fill with data
chart->setModel(model);
//Set which column holds X data
chart->setXSeriesColumn(0);

//Get the axes
Wt::Chart::WAxis& x_axis  = chart->axis(Wt::Chart::Axis::XAxis);
Wt::Chart::WAxis& y1_axis = chart->axis(Wt::Chart::Axis::Y1Axis);
Wt::Chart::WAxis& y2_axis = chart->axis(Wt::Chart::Axis::Y2Axis);

//Modify axes attributes
x_axis.setRange(0, PERFORMANCE_HISTORY);
x_axis.setGridLinesEnabled(true);
x_axis.setLabelInterval(PERFORMANCE_HISTORY / 10);

y1_axis.setRange(0, 100);
y1_axis.setGridLinesEnabled(true);
y1_axis.setLabelInterval(10);

y2_axis.setRange(0, 100);
y2_axis.setVisible(true);
y2_axis.setLabelInterval(10);

//Set chart type
chart->setType(Wt::Chart::ChartType::ScatterPlot);

// Typically, for mathematical functions, you want the axes to cross at the 0 mark:
chart->axis(Wt::Chart::Axis::XAxis).setLocation(Wt::Chart::AxisValue::ZeroValue);
chart->axis(Wt::Chart::Axis::Y1Axis).setLocation(Wt::Chart::AxisValue::ZeroValue);
chart->axis(Wt::Chart::Axis::Y2Axis).setLocation(Wt::Chart::AxisValue::ZeroValue);

// Add the lines
Wt::Chart::WDataSeries s(1, Wt::Chart::SeriesType::LineSeries);
chart->addSeries(s);

//Size the display size of the chart, has no effect on scale
chart->resize(300, 300);

return chart;
}

Files

report (15).pdf (1.37 KB) report (15).pdf Anonymous, 05/13/2014 03:28 PM
report (17).pdf (1.37 KB) report (17).pdf Anonymous, 05/13/2014 03:28 PM
Actions #1

Updated by Anonymous almost 10 years ago

Oops, forgot to note PERFORMANCE_HISTORY, it is just a #define PERFORMANCE_HISTORY 100

Actions #2

Updated by Koen Deforche almost 10 years ago

  • Status changed from New to Feedback
  • Assignee set to Koen Deforche

Hey,

You're almost there --- the following should do it:

std::stringstream ss;
_target->htmlText(ss);
renderPdf(Wt::WString::fromUTF8(ss.str()), pdf);

Regards,

koen

Updated by Anonymous almost 10 years ago

Koen Deforche wrote:

Hey,

You're almost there --- the following should do it:

[...]

Regards,

koen

Cool, that got the PDF export working properly, however the content does not look like it does on the page, it simply printed out the strings from the items but not the formatting, like the text size, or any extra lines. Attached is the PDF that resulted from the above code(15), and a second one(17) after I added WBreaks before the "This is a text sample within the group box #" texts to format it a bit.

Is there a way to completely mirror what is on the page into the PDF?

Actions #4

Updated by Anonymous almost 10 years ago

To clarify, I am using boost styling.

Actions #5

Updated by Wim Dumon almost 10 years ago

What is 'boost styling'?

If you want to style things through css style sheets, you have to call useStyleSheet() to tell the renderer what stylesheet has to be loaded.

From the manual:

The renderer is not CSS compliant (simply because it is still lacking
alot of features), but the subset of CSS that is supported is a pragmatic
choice. If things are lacking, let us known in the bug tracker.

Maybe things get more clear to us when you post the HTML you're sending in the PDF renderer, i.e. the actual argument of renderPdf().

Wim.

Actions #6

Updated by Anonymous almost 10 years ago

My apologies, that was a typo, I meant to say Bootstrap not boost... silly me. Here are the style sheets and theme I am using.

setTheme(new Wt::WBootstrapTheme());

wApp->useStyleSheet("Ventus/examples/simple/css/normalize.css");

wApp->useStyleSheet("Ventus/examples/simple/css/simple.css");

wApp->useStyleSheet("Ventus/build/ventus.css");

wApp->useStyleSheet("Ventus/examples/simple/css/browseralert.css");

Ventus may be found here https://github.com/rlamana/Ventus though the bulk of the styling is from the bootstrap theme.

Also, here is the html that is being sent to the PDF renderer. I formatted it in notepad for easier viewing.

PDF 15:

utf8_=\"

Example Group Box

This is a text sample within the group box 1

This is a text sample within the group box 2

This is a text sample within the group box 3

\" impl_=0x00000000

PDF 17:

utf8_=\"

Example Group Box



This is a text sample within the group box 1



This is a text sample within the group box 2



This is a text sample within the group box 3

\" impl_=0x00000000

I'm trying to have the PDF display the widgets that I pass to it the same way they appear on my styled page. From what Wim said in his post, it sounds like there is some way to do this, but the Wt::WResource does not have setTheme or useStyleSheet methods, or anything relating to styling that I see in the documentation.

Actions #7

Updated by Koen Deforche almost 10 years ago

Hey,

WPdfRenderer does not support all HTML/CSS functionality. It's been designed primarily to render text that is edited by using a WTextEdit.

It does have support for stylesheets, but only supports a subset of CSS selectors and CSS rules. CSS stylesheest like those of Twitter Bootstrap are not within its scope.

As Wim said you can load a custom stylesheet using WPdfRenderer::useStyleSheet(). It is not influenced by stylesheets loaded into the WApplication like you indicated above.

I am afraid you need to reduce your expectations however to use simple markup. As to specific widgets that are not being rendered at all (like fieldset/legend of a WGroupBox), I would suggest you file a feature request to add support --- this is usually not a lot of work.

Regards,

koen

Actions #8

Updated by Anonymous almost 10 years ago

I see, thank you for the help.

Actions #9

Updated by Anonymous almost 10 years ago

I created a feature request, http://redmine.webtoolkit.eu/issues/3175.

Thanks again for the help, loving WT :)

Actions

Also available in: Atom PDF