Extend QPdfWriter for adding PDF widget annotation field
-
wrote on 20 Dec 2023, 14:56 last edited by
We are in need of creating a PDF that contains a dynamic input field for a viewer to add text after the PDF has been created. This is not possible automatically create based on the UI definition, so I needed to add a function that can be used after the UI has been rendered. What I did was, create a UI with an empty QWidget item in it, render de UI with the QPdfWriter and then take the geometry coordinates from the widget and then use my added function to insert a PDF widget annotation field in the page:
void PMyPdfWriter::doPrint( QWidget * target ) { if ( target ) { QPdfWriter pdfWriter( "test.pdf" ); QPainter printer; pdfWriter.setPdfVersion( QPagedPaintDevice::PdfVersion_1_6 ); pdfWriter.setPageOrientation( QPageLayout::Landscape ); pdfWriter.setPageMargins( QMargins( 0, 0, 0, 0 ) ); pdfWriter.setResolution( 100 ); if ( printer.begin( &pdfWriter ) ) { QRect pageRect( pdfWriter.pageLayout().paintRectPixels( pdfWriter.resolution() ) ); QSize oldSize( target->size() ); Widget * widgetPtr = qobject_cast< Widget * >( target ); // Resize the widget to print into the PDF to the size of the PDF page target->resize( pageRect.size() ); // Render the widget content onto the PDF page target->render( &printer ); if ( widgetPtr ) { // Get the position of the (empty) widget from the just rendered widget to get its position on the PDF page QRect dynRect( widgetPtr->getDynamicArea() ); QWidget * dynWidgetPtr = widgetPtr->dynWidget(); // Now add the PDF widget annotation to the PDF page at the position of the (empty) widget pdfWriter.addDynamicField( dynRect, QLatin1String( "Enter text" ), 500, &(dynWidgetPtr->font()) ); } // Resize the widget back to its original size to keep the window the same target->resize( oldSize ); printer.end(); } } }
I have added the final function performing the action to the QPdfEngine class to eb able to add it tot he PDF page:
void QPdfEngine::addDynamicField( const QRect & rect, const QLatin1String & fieldLabel, int maxLength, const QFont * fntPtr, bool multiline ) { Q_D(QPdfEngine); const uint annot = d->addXrefEntry(-1); char buf[ 256 ]; const QRectF rr = d->pageMatrix().mapRect( rect ); d->xprintf( "<</FT/Tx" ); // Start the text field if ( multiline ) { d->xprintf( "/Ff 4096" ); // 4096 means bit 13 set, meaning multi-line textfield } if ( 0 < maxLength ) { d->xprintf( "/MaxLen %s", qt_int_to_string( maxLength, buf ) ); } d->xprintf("/Type/Annot/Subtype/Widget"); d->xprintf("/Rect ["); d->xprintf("%s ", qt_real_to_string(rr.left(), buf)); d->xprintf("%s ", qt_real_to_string(rr.top(), buf)); d->xprintf("%s ", qt_real_to_string(rr.right(), buf)); d->xprintf("%s", qt_real_to_string(rr.bottom(), buf)); d->xprintf("]"); if ( !fieldLabel.isEmpty() ) { d->xprintf("/T(%s)", fieldLabel.constData()); // text label that shall be displayed in the title bar of the annotation's popup window when open and active } d->xprintf("/DA(/Helv 8 Tf 0 g )"); // Needs to come from the Widget configured QFont. Need to get a QFontEngine object to be able to do this??? d->xprintf("/F 4\n"); // enable print flag // d->xprintf("/DR<</Font<</Helv 2 0 R>>>>"); // Not required but might be good to have based on the desired font, see note with "/DA" tag d->xprintf(">>\n>>\n"); d->xprintf("endobj\n"); d->currentPage->annotations.append(annot); }
This is working. The only problem I have with it is that I cannot get a font specified with it, see comment with the '/DA' and '/DR' tags. I need a QFontEngine object related to the QWidget for that to be able to add a QFontSubset object to the PDF engine and I cannot access that information because it is build to deep into Qt to access without having to add to much changes tot eh Qt library.
Anybody any suggestions on how to get this working?
Many thanks
-
We are in need of creating a PDF that contains a dynamic input field for a viewer to add text after the PDF has been created. This is not possible automatically create based on the UI definition, so I needed to add a function that can be used after the UI has been rendered. What I did was, create a UI with an empty QWidget item in it, render de UI with the QPdfWriter and then take the geometry coordinates from the widget and then use my added function to insert a PDF widget annotation field in the page:
void PMyPdfWriter::doPrint( QWidget * target ) { if ( target ) { QPdfWriter pdfWriter( "test.pdf" ); QPainter printer; pdfWriter.setPdfVersion( QPagedPaintDevice::PdfVersion_1_6 ); pdfWriter.setPageOrientation( QPageLayout::Landscape ); pdfWriter.setPageMargins( QMargins( 0, 0, 0, 0 ) ); pdfWriter.setResolution( 100 ); if ( printer.begin( &pdfWriter ) ) { QRect pageRect( pdfWriter.pageLayout().paintRectPixels( pdfWriter.resolution() ) ); QSize oldSize( target->size() ); Widget * widgetPtr = qobject_cast< Widget * >( target ); // Resize the widget to print into the PDF to the size of the PDF page target->resize( pageRect.size() ); // Render the widget content onto the PDF page target->render( &printer ); if ( widgetPtr ) { // Get the position of the (empty) widget from the just rendered widget to get its position on the PDF page QRect dynRect( widgetPtr->getDynamicArea() ); QWidget * dynWidgetPtr = widgetPtr->dynWidget(); // Now add the PDF widget annotation to the PDF page at the position of the (empty) widget pdfWriter.addDynamicField( dynRect, QLatin1String( "Enter text" ), 500, &(dynWidgetPtr->font()) ); } // Resize the widget back to its original size to keep the window the same target->resize( oldSize ); printer.end(); } } }
I have added the final function performing the action to the QPdfEngine class to eb able to add it tot he PDF page:
void QPdfEngine::addDynamicField( const QRect & rect, const QLatin1String & fieldLabel, int maxLength, const QFont * fntPtr, bool multiline ) { Q_D(QPdfEngine); const uint annot = d->addXrefEntry(-1); char buf[ 256 ]; const QRectF rr = d->pageMatrix().mapRect( rect ); d->xprintf( "<</FT/Tx" ); // Start the text field if ( multiline ) { d->xprintf( "/Ff 4096" ); // 4096 means bit 13 set, meaning multi-line textfield } if ( 0 < maxLength ) { d->xprintf( "/MaxLen %s", qt_int_to_string( maxLength, buf ) ); } d->xprintf("/Type/Annot/Subtype/Widget"); d->xprintf("/Rect ["); d->xprintf("%s ", qt_real_to_string(rr.left(), buf)); d->xprintf("%s ", qt_real_to_string(rr.top(), buf)); d->xprintf("%s ", qt_real_to_string(rr.right(), buf)); d->xprintf("%s", qt_real_to_string(rr.bottom(), buf)); d->xprintf("]"); if ( !fieldLabel.isEmpty() ) { d->xprintf("/T(%s)", fieldLabel.constData()); // text label that shall be displayed in the title bar of the annotation's popup window when open and active } d->xprintf("/DA(/Helv 8 Tf 0 g )"); // Needs to come from the Widget configured QFont. Need to get a QFontEngine object to be able to do this??? d->xprintf("/F 4\n"); // enable print flag // d->xprintf("/DR<</Font<</Helv 2 0 R>>>>"); // Not required but might be good to have based on the desired font, see note with "/DA" tag d->xprintf(">>\n>>\n"); d->xprintf("endobj\n"); d->currentPage->annotations.append(annot); }
This is working. The only problem I have with it is that I cannot get a font specified with it, see comment with the '/DA' and '/DR' tags. I need a QFontEngine object related to the QWidget for that to be able to add a QFontSubset object to the PDF engine and I cannot access that information because it is build to deep into Qt to access without having to add to much changes tot eh Qt library.
Anybody any suggestions on how to get this working?
Many thanks
I've absolutely no knowledget about the pdf stuff but isn't
fntPtr
the font you're looking for? -
I've absolutely no knowledget about the pdf stuff but isn't
fntPtr
the font you're looking for?wrote on 20 Dec 2023, 15:41 last edited by@Christian-Ehrlicher thanks for your response.
That fntPtr object is the font I want to add to the PDF. There is some code in the function QPdfEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti) that will embed the font into the PDF that can be used. Below Is a past of code that should do it. This will give a PDF object identifier that identifies the added font within the PDF. That identifier can than be used with the annotation field.
if (!embedFonts { QFontEngine *fe = ti.fontEngine; QFontEngine::FaceId face_id = fe->faceId(); QFontSubset *font = fonts.value(face_id, nullptr); if (!font) { font = new QFontSubset(fe, requestObject()); font->noEmbed = noEmbed; } fonts.insert(face_id, font); if (!currentPage->fonts.contains(font->object_id)) currentPage->fonts.append(font->object_id); }
But I don't have the QTextItemInt object that is needed to create the QFontSubset. And I don't know how to create it from the widget object I wasn't to set the widget annotation field for.
-
@Christian-Ehrlicher thanks for your response.
That fntPtr object is the font I want to add to the PDF. There is some code in the function QPdfEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti) that will embed the font into the PDF that can be used. Below Is a past of code that should do it. This will give a PDF object identifier that identifies the added font within the PDF. That identifier can than be used with the annotation field.
if (!embedFonts { QFontEngine *fe = ti.fontEngine; QFontEngine::FaceId face_id = fe->faceId(); QFontSubset *font = fonts.value(face_id, nullptr); if (!font) { font = new QFontSubset(fe, requestObject()); font->noEmbed = noEmbed; } fonts.insert(face_id, font); if (!currentPage->fonts.contains(font->object_id)) currentPage->fonts.append(font->object_id); }
But I don't have the QTextItemInt object that is needed to create the QFontSubset. And I don't know how to create it from the widget object I wasn't to set the widget annotation field for.
Ok, this looks not that easy. I currently have no idea except creating a breakpoint in QPdfEnginePrivate::drawTextItem() and take a deep dive into the internals of Qt to see where all the needed stuff is collected from. That's not a place where I've knowledge, sorry.
-
Ok, this looks not that easy. I currently have no idea except creating a breakpoint in QPdfEnginePrivate::drawTextItem() and take a deep dive into the internals of Qt to see where all the needed stuff is collected from. That's not a place where I've knowledge, sorry.
wrote on 20 Dec 2023, 17:47 last edited by@Christian-Ehrlicher
I did that and got hopelessly stuck, unfortunately -
@Christian-Ehrlicher
I did that and got hopelessly stuck, unfortunatelywrote on 21 Dec 2023, 15:22 last edited by@EelcoPeacs
Succeeded in finding the solution. The function to add the widget annotation field now looks like:void QPdfEngine::addDynamicField( const QRect & rect, const QLatin1String & fieldLabel, int maxLength, const QFont * fntPtr, bool multiline ) { Q_D(QPdfEngine); uint fontObjId = 0; qreal size = 0.0; if ( fntPtr && d->embedFonts ) { QFontEngine * engine = fntPtr->d->engineForScript(QChar::Script_Common); if ( engine ) { QFontEngineMulti * multiEngine = dynamic_cast< QFontEngineMulti * >( engine ); if ( multiEngine ) { engine = multiEngine->engine( 0 ); } if ( engine ) { QFontEngine::FaceId face_id = engine->faceId(); QFontSubset * font = d->fonts.value( face_id, nullptr ); if ( !font ) { font = new QFontSubset( engine, d->requestObject() ); font->noEmbed = false; } d->fonts.insert( face_id, font ); fontObjId = font->object_id; size = engine->fontDef.pixelSize; if ( !d->currentPage->fonts.contains( font->object_id ) ) { d->currentPage->fonts.append( font->object_id ); } } } } const uint annot = d->addXrefEntry(-1); char buf[ 256 ]; const QRectF rr = d->pageMatrix().mapRect( rect ); d->xprintf( "<</FT/Tx" ); // Start the text field if ( multiline ) { d->xprintf( "/Ff 4096" ); // 4096 means bit 13 set, meaning multi-line textfield } if ( 0 < maxLength ) { d->xprintf( "/MaxLen %s", qt_int_to_string( maxLength, buf ) ); } d->xprintf("/Type/Annot/Subtype/Widget"); d->xprintf("/Rect ["); d->xprintf("%s", qt_real_to_string(rr.left(), buf)); d->xprintf("%s", qt_real_to_string(rr.top(), buf)); d->xprintf("%s", qt_real_to_string(rr.right(), buf)); d->xprintf("%s", qt_real_to_string(rr.bottom(), buf)); d->xprintf("]"); if ( !fieldLabel.isEmpty() ) { d->xprintf("/T(%s)", fieldLabel.constData()); // Text label that will be displayed in the title bar of the annotation's popup window when open and active } d->xprintf("/F 4"); // Flags specifying varioud characteristics of the annotation. 4 means bit 3, meaning do print if ( 0 < fontObjId ) { d->xprintf( "/DA(/F%s", qt_int_to_string( fontObjId, buf ) ); // Arbitrary name that must match the name used with the /DR entry below d->xprintf( "%s", qt_int_to_string( size, buf ) ); // Set the size of the font d->xprintf( "Tf)", qt_int_to_string( size, buf ) ); // End with the Text font (Tf) keyword d->xprintf( "/DR<</Font<</F%s", qt_int_to_string( fontObjId, buf ) ); // Arbitrary name that must match the name given with the /DA entry above d->xprintf( "%s", qt_int_to_string( fontObjId, buf ) ); // Add reference to the embedded font resource d->xprintf( "0 R>>>>" ); // End the DR keyword specification } d->xprintf(">>\n>>\n"); d->xprintf("endobj\n"); d->currentPage->annotations.append(annot); }
The only thing is that an additional change to the QFont class is needed. The class QPdfEngine needs to be set as a friend class in the private section to be able to access the private 'd' member
friend class QPdfEngine; ```" I don't know if this 100% correct, but it does work and uses the style I set through the stylesheet
-
@EelcoPeacs
Succeeded in finding the solution. The function to add the widget annotation field now looks like:void QPdfEngine::addDynamicField( const QRect & rect, const QLatin1String & fieldLabel, int maxLength, const QFont * fntPtr, bool multiline ) { Q_D(QPdfEngine); uint fontObjId = 0; qreal size = 0.0; if ( fntPtr && d->embedFonts ) { QFontEngine * engine = fntPtr->d->engineForScript(QChar::Script_Common); if ( engine ) { QFontEngineMulti * multiEngine = dynamic_cast< QFontEngineMulti * >( engine ); if ( multiEngine ) { engine = multiEngine->engine( 0 ); } if ( engine ) { QFontEngine::FaceId face_id = engine->faceId(); QFontSubset * font = d->fonts.value( face_id, nullptr ); if ( !font ) { font = new QFontSubset( engine, d->requestObject() ); font->noEmbed = false; } d->fonts.insert( face_id, font ); fontObjId = font->object_id; size = engine->fontDef.pixelSize; if ( !d->currentPage->fonts.contains( font->object_id ) ) { d->currentPage->fonts.append( font->object_id ); } } } } const uint annot = d->addXrefEntry(-1); char buf[ 256 ]; const QRectF rr = d->pageMatrix().mapRect( rect ); d->xprintf( "<</FT/Tx" ); // Start the text field if ( multiline ) { d->xprintf( "/Ff 4096" ); // 4096 means bit 13 set, meaning multi-line textfield } if ( 0 < maxLength ) { d->xprintf( "/MaxLen %s", qt_int_to_string( maxLength, buf ) ); } d->xprintf("/Type/Annot/Subtype/Widget"); d->xprintf("/Rect ["); d->xprintf("%s", qt_real_to_string(rr.left(), buf)); d->xprintf("%s", qt_real_to_string(rr.top(), buf)); d->xprintf("%s", qt_real_to_string(rr.right(), buf)); d->xprintf("%s", qt_real_to_string(rr.bottom(), buf)); d->xprintf("]"); if ( !fieldLabel.isEmpty() ) { d->xprintf("/T(%s)", fieldLabel.constData()); // Text label that will be displayed in the title bar of the annotation's popup window when open and active } d->xprintf("/F 4"); // Flags specifying varioud characteristics of the annotation. 4 means bit 3, meaning do print if ( 0 < fontObjId ) { d->xprintf( "/DA(/F%s", qt_int_to_string( fontObjId, buf ) ); // Arbitrary name that must match the name used with the /DR entry below d->xprintf( "%s", qt_int_to_string( size, buf ) ); // Set the size of the font d->xprintf( "Tf)", qt_int_to_string( size, buf ) ); // End with the Text font (Tf) keyword d->xprintf( "/DR<</Font<</F%s", qt_int_to_string( fontObjId, buf ) ); // Arbitrary name that must match the name given with the /DA entry above d->xprintf( "%s", qt_int_to_string( fontObjId, buf ) ); // Add reference to the embedded font resource d->xprintf( "0 R>>>>" ); // End the DR keyword specification } d->xprintf(">>\n>>\n"); d->xprintf("endobj\n"); d->currentPage->annotations.append(annot); }
The only thing is that an additional change to the QFont class is needed. The class QPdfEngine needs to be set as a friend class in the private section to be able to access the private 'd' member
friend class QPdfEngine; ```" I don't know if this 100% correct, but it does work and uses the style I set through the stylesheet
wrote on 21 Dec 2023, 16:48 last edited by Christian Ehrlicher@EelcoPeacs
I just added a feature suggestion to the Qt bug report for this, QTBUG-120351 -
-
@EelcoPeacs
I just added a feature suggestion to the Qt bug report for this, QTBUG-120351@EelcoPeacs Maybe you can also apply a patch for it? If you won't/can't do it through gerrit then attach it to the bug report.
1/8