Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Extend QPdfWriter for adding PDF widget annotation field
Forum Updated to NodeBB v4.3 + New Features

Extend QPdfWriter for adding PDF widget annotation field

Scheduled Pinned Locked Moved Solved General and Desktop
8 Posts 2 Posters 699 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • E Offline
    E Offline
    EelcoPeacs
    wrote on last edited by
    #1

    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

    Christian EhrlicherC 1 Reply Last reply
    0
    • E EelcoPeacs

      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

      Christian EhrlicherC Online
      Christian EhrlicherC Online
      Christian Ehrlicher
      Lifetime Qt Champion
      wrote on last edited by
      #2

      I've absolutely no knowledget about the pdf stuff but isn't fntPtr the font you're looking for?

      Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
      Visit the Qt Academy at https://academy.qt.io/catalog

      E 1 Reply Last reply
      0
      • Christian EhrlicherC Christian Ehrlicher

        I've absolutely no knowledget about the pdf stuff but isn't fntPtr the font you're looking for?

        E Offline
        E Offline
        EelcoPeacs
        wrote on last edited by
        #3

        @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 EhrlicherC 1 Reply Last reply
        0
        • E EelcoPeacs

          @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 EhrlicherC Online
          Christian EhrlicherC Online
          Christian Ehrlicher
          Lifetime Qt Champion
          wrote on last edited by
          #4

          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.

          Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
          Visit the Qt Academy at https://academy.qt.io/catalog

          E 1 Reply Last reply
          0
          • Christian EhrlicherC Christian Ehrlicher

            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.

            E Offline
            E Offline
            EelcoPeacs
            wrote on last edited by
            #5

            @Christian-Ehrlicher
            I did that and got hopelessly stuck, unfortunately

            E 1 Reply Last reply
            0
            • E EelcoPeacs

              @Christian-Ehrlicher
              I did that and got hopelessly stuck, unfortunately

              E Offline
              E Offline
              EelcoPeacs
              wrote on last edited by
              #6

              @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
              E 1 Reply Last reply
              0
              • E EelcoPeacs

                @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
                E Offline
                E Offline
                EelcoPeacs
                wrote on last edited by Christian Ehrlicher
                #7

                @EelcoPeacs
                I just added a feature suggestion to the Qt bug report for this, QTBUG-120351

                Christian EhrlicherC 1 Reply Last reply
                1
                • E EelcoPeacs has marked this topic as solved on
                • E EelcoPeacs

                  @EelcoPeacs
                  I just added a feature suggestion to the Qt bug report for this, QTBUG-120351

                  Christian EhrlicherC Online
                  Christian EhrlicherC Online
                  Christian Ehrlicher
                  Lifetime Qt Champion
                  wrote on last edited by
                  #8

                  @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.

                  Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
                  Visit the Qt Academy at https://academy.qt.io/catalog

                  1 Reply Last reply
                  0

                  • Login

                  • Login or register to search.
                  • First post
                    Last post
                  0
                  • Categories
                  • Recent
                  • Tags
                  • Popular
                  • Users
                  • Groups
                  • Search
                  • Get Qt Extensions
                  • Unsolved