From f788945cf4e1975a8a1161f4795c12034d39b5c3 Mon Sep 17 00:00:00 2001 From: Roberto Fernandez Bautista Date: Sun, 15 May 2022 20:35:00 +0100 Subject: [PATCH] Add pdf output to hyperlinks --- common/plotters/PDF_plotter.cpp | 81 ++++++++++++++++++++++++++---- include/plotters/plotter.h | 11 ++++ include/plotters/plotters_pslike.h | 3 ++ 3 files changed, 85 insertions(+), 10 deletions(-) diff --git a/common/plotters/PDF_plotter.cpp b/common/plotters/PDF_plotter.cpp index eae84fb315..c022503789 100644 --- a/common/plotters/PDF_plotter.cpp +++ b/common/plotters/PDF_plotter.cpp @@ -700,17 +700,67 @@ void PDF_PLOTTER::ClosePage() // Close the page stream (and compress it) closePdfStream(); - // Emit the page object and put it in the page list for later - m_pageHandles.push_back( startPdfObject() ); - /* Page size is in 1/72 of inch (default user space units) Works like the bbox in postscript but there is no need for swapping the sizes, since PDF doesn't require a portrait page. We use the MediaBox but PDF has lots of other less used boxes to use */ + const double PTsPERMIL = 0.072; + VECTOR2D psPaperSize = VECTOR2D( m_pageInfo.GetSizeMils() ) * PTsPERMIL; - const double BIGPTsPERMIL = 0.072; - VECTOR2I psPaperSize = m_pageInfo.GetSizeMils(); + auto iuToPdfUserSpace = [&]( const VECTOR2I& aCoord ) -> VECTOR2D + { + VECTOR2D retval = VECTOR2D( aCoord ) * PTsPERMIL / SCH_IU_PER_MILS; + // PDF y=0 is at bottom of page, invert coordinate + retval.y = psPaperSize.y - retval.y; + return retval; + }; + + // Handle annotations (at the moment only "link" type objects) + std::vector hyperlinkHandles; + + // Write out all hyperlinks for the page as annotation links + for( const std::pair& linkPair : m_urlHyperlinks ) + { + const BOX2I& box = linkPair.first; + const wxString& url = linkPair.second; + + VECTOR2D bottomLeft = iuToPdfUserSpace( box.GetPosition() ); + VECTOR2D topRight = iuToPdfUserSpace( box.GetEnd() ); + + hyperlinkHandles.push_back( startPdfObject() ); + fprintf( m_outputFile, + "<< /Type /Annot\n" + " /Subtype /Link\n" + " /Rect[%g %g %g %g] /Border[16 16 1]\n" + " /A << /Type /Action /S /URI /URI %s >>\n" + ">>\n", + bottomLeft.x, bottomLeft.y, topRight.x, topRight.y, + encodeStringForPlotter( url ).c_str() ); + closePdfObject(); + } + + int hyperLinkArrayHandle = -1; + + // If we have added any annotation links, create an array containing all the objects + if( hyperlinkHandles.size() > 0 ) + { + hyperLinkArrayHandle = startPdfObject(); + bool isFirst = true; + + fprintf( m_outputFile, "[%d 0 R", hyperlinkHandles[0] ); + + for( auto it = hyperlinkHandles.begin() + 1; it != hyperlinkHandles.end(); ++it ) + { + fprintf( m_outputFile, " %d 0 R", *it ); + } + + fputs( "]\n", m_outputFile ); + closePdfObject(); + } + + // Emit the page object and put it in the page list for later + m_pageHandles.push_back( startPdfObject() ); fprintf( m_outputFile, "<<\n" @@ -719,14 +769,19 @@ void PDF_PLOTTER::ClosePage() "/Resources <<\n" " /ProcSet [/PDF /Text /ImageC /ImageB]\n" " /Font %d 0 R >>\n" - "/MediaBox [0 0 %d %d]\n" - "/Contents %d 0 R\n" - ">>\n", + "/MediaBox [0 0 %g %g]\n" + "/Contents %d 0 R\n", m_pageTreeHandle, m_fontResDictHandle, - int( ceil( psPaperSize.x * BIGPTsPERMIL ) ), - int( ceil( psPaperSize.y * BIGPTsPERMIL ) ), + psPaperSize.x, + psPaperSize.y, m_pageStreamHandle ); + + if( hyperlinkHandles.size() > 0 ) + fprintf( m_outputFile, "/Annots %d 0 R", hyperLinkArrayHandle ); + + fputs( ">>\n", m_outputFile ); + closePdfObject(); // Mark the page stream as idle @@ -956,3 +1011,9 @@ void PDF_PLOTTER::Text( const VECTOR2I& aPos, aBold, aMultilineAllowed, aFont ); } + +void PDF_PLOTTER::HyperlinkBoxURL( const BOX2I& aBox, const wxString& aDestinationURL ) +{ + m_urlHyperlinks.push_back( std::make_pair( aBox, aDestinationURL ) ); +} + diff --git a/include/plotters/plotter.h b/include/plotters/plotter.h index 3fe5278876..393e4768af 100644 --- a/include/plotters/plotter.h +++ b/include/plotters/plotter.h @@ -436,6 +436,17 @@ public: KIFONT::FONT* aFont, void* aData = nullptr ); + /** + * Create a clickable hyperlink with a rectangular click area + * + * @aBox is the rectangular click target + * @aDestinationURL is the target + */ + virtual void HyperlinkBoxURL( const BOX2I& aBox, const wxString& aDestinationURL ) + { + // NOP for most plotters. + } + /** * Draw a marker (used for the drill map). */ diff --git a/include/plotters/plotters_pslike.h b/include/plotters/plotters_pslike.h index caacc9c644..ddbc2ed8cd 100644 --- a/include/plotters/plotters_pslike.h +++ b/include/plotters/plotters_pslike.h @@ -352,6 +352,8 @@ public: KIFONT::FONT* aFont = nullptr, void* aData = nullptr ) override; + virtual void HyperlinkBoxURL( const BOX2I& aBox, const wxString& aDestinationURL ) override; + /** * PDF images are handles as inline, not XObject streams... */ @@ -414,6 +416,7 @@ protected: wxString m_workFilename; FILE* m_workFile; ///< Temporary file to construct the stream before zipping std::vector m_xrefTable; ///< The PDF xref offset table + std::vector> m_urlHyperlinks; ///< List of loaded URLs so far };