From 9b007ca4bf042f9915faeef0f87e4aefcc40b4d3 Mon Sep 17 00:00:00 2001
From: Roberto Fernandez Bautista <roberto.fer.bau@gmail.com>
Date: Sun, 3 Jul 2022 16:19:55 +0100
Subject: [PATCH] ADDED: Go to page hyperlinks in schematic editor (virtual
 page numbers)

The problem is that "real" page numbers can be duplicated, so we work around it
by using virtual page numbers instead which are guaranteed unique.

Example "goto:3" will go to page 3. If customised page numbers are used such as
a, b, c, then to go to page b, we should specify goto:2 (i.e. the virtual page
number).
---
 common/eda_text.cpp                | 28 +++++++++++++-
 common/plotters/PDF_plotter.cpp    | 61 +++++++++++++++++++++---------
 eeschema/sch_text.cpp              |  2 +-
 eeschema/sch_textbox.cpp           |  2 +-
 include/eda_text.h                 | 16 ++++++++
 include/plotters/plotter.h         |  4 +-
 include/plotters/plotters_pslike.h |  8 ++--
 7 files changed, 95 insertions(+), 26 deletions(-)

diff --git a/common/eda_text.cpp b/common/eda_text.cpp
index 50e7024246..f08c56d07c 100644
--- a/common/eda_text.cpp
+++ b/common/eda_text.cpp
@@ -983,7 +983,33 @@ bool EDA_TEXT::ValidateHyperlink( const wxString& aURL )
 {
     wxURL url;
 
-    return aURL.IsEmpty() || url.SetURL( aURL ) == wxURL_NOERR;
+    return aURL.IsEmpty() || url.SetURL( aURL ) == wxURL_NOERR || IsGotoPageHyperlink( aURL );
+}
+
+
+bool EDA_TEXT::IsGotoPageHyperlink( const wxString& aURL, int* aDestination )
+{
+    wxString dest;
+
+    if( !aURL.StartsWith( "goto:", &dest ) )
+        return false;
+
+    long num;
+    bool retval = dest.ToLong( &num );
+
+    if( !retval || num < 0 )
+        return false;
+
+    if( aDestination )
+        *aDestination = static_cast<int>( num );
+
+    return true;
+}
+
+
+wxString EDA_TEXT::GotoPageHyperlinkString( const int& aDestination )
+{
+    return wxString::Format( "goto:%d", aDestination );
 }
 
 
diff --git a/common/plotters/PDF_plotter.cpp b/common/plotters/PDF_plotter.cpp
index e738a1eb27..0a2456ea35 100644
--- a/common/plotters/PDF_plotter.cpp
+++ b/common/plotters/PDF_plotter.cpp
@@ -34,6 +34,7 @@
 #include <wx/zstream.h>
 
 #include <advanced_config.h>
+#include <eda_text.h> // for IsGotoPageHyperlink
 #include <ignore.h>
 #include <macros.h>
 #include <trigo.h>
@@ -741,8 +742,8 @@ void PDF_PLOTTER::ClosePage()
         closePdfObject();
     }*/
 
-    // Write out all "goto" hyperlinks for the page as link annotations (compatible with pdf 1.0)
-    for( const std::pair<BOX2I, wxString>& linkPair : m_urlHyperlinksInPage )
+    // Allocate all hyperlink objects for the page and calculate their position in user space coordinates
+    for( const std::pair<BOX2I, wxString>& linkPair : m_hyperlinksInPage )
     {
         const BOX2I&    box = linkPair.first;
         const wxString& url = linkPair.second;
@@ -754,12 +755,11 @@ void PDF_PLOTTER::ClosePage()
         userSpaceBox.SetOrigin( bottomLeft );
         userSpaceBox.SetEnd( topRight );
 
-        hyperlinkHandles.push_back( startPdfObject() );
+        hyperlinkHandles.push_back( allocPdfObject() );
 
-        m_urlHyperlinksHandles.insert( { hyperlinkHandles.back(), { userSpaceBox, url } } );
+        m_hyperlinkHandles.insert( { hyperlinkHandles.back(), { userSpaceBox, url } } );
     }
-    //
-    // TODO use allocPdfObject()  !!
+
     int hyperLinkArrayHandle = -1;
 
     // If we have added any annotation links, create an array containing all the objects
@@ -807,8 +807,8 @@ void PDF_PLOTTER::ClosePage()
     // Mark the page stream as idle
     m_pageStreamHandle = 0;
 
-    //
-    m_urlHyperlinksInPage.clear();
+    // Clean up
+    m_hyperlinksInPage.clear();
 }
 
 
@@ -819,8 +819,8 @@ bool PDF_PLOTTER::StartPlot()
     // First things first: the customary null object
     m_xrefTable.clear();
     m_xrefTable.push_back( 0 );
-    m_urlHyperlinksInPage.clear();
-    m_urlHyperlinksHandles.clear();
+    m_hyperlinksInPage.clear();
+    m_hyperlinkHandles.clear();
 
     /* The header (that's easy!). The second line is binary junk required
        to make the file binary from the beginning (the important thing is
@@ -895,7 +895,7 @@ bool PDF_PLOTTER::EndPlot()
     fputs( ">>\n", m_outputFile );
     closePdfObject();
 
-    for( const std::pair<int,std::pair<BOX2D, wxString>>& handlePair : m_urlHyperlinksHandles )
+    for( const std::pair<int,std::pair<BOX2D, wxString>>& handlePair : m_hyperlinkHandles )
     {
         const int& linkhandle = handlePair.first;
         const std::pair<BOX2D, wxString>& linkpair = handlePair.second;
@@ -907,10 +907,37 @@ bool PDF_PLOTTER::EndPlot()
         fprintf( m_outputFile,
                  "<< /Type /Annot\n"
                  "   /Subtype /Link\n"
-                 "   /Rect[%g %g %g %g] /Border[16 16 1]\n"
-                 "   /Dest [%d 0 R] >>\n" // /D [3 0 R /FitR �4 399 199 533]
-                 ">>\n",
-                 box.GetLeft(), box.GetBottom(), box.GetRight(), box.GetTop(), m_pageHandles[0] );
+                 "   /Rect[%g %g %g %g] /Border[16 16 1]\n",
+                 box.GetLeft(), box.GetBottom(), box.GetRight(), box.GetTop() );
+
+        int pageIdx = -1;
+
+        if( EDA_TEXT::IsGotoPageHyperlink( url, &pageIdx ) )
+        {
+            if( pageIdx <= m_pageHandles.size() && pageIdx > 0 )
+            {
+                fprintf( m_outputFile,
+                         "   /Dest [%d 0 R] >>\n"
+                         ">>\n",
+                         m_pageHandles[pageIdx - 1] );
+                //todo: do we want to support specifying zoom factor/ position? e.g. /FitR
+            }
+            else
+            {
+                // destination page is not being plotted, assign the NOP action to the link
+                fprintf( m_outputFile,
+                         "   /A << /Type /Action /S /NOP >>\n"
+                         ">>\n" );
+            }
+        }
+        else
+        {
+            fprintf( m_outputFile,
+                     "   /A << /Type /Action /S /URI /URI %s >>\n"
+                     ">>\n",
+                     encodeStringForPlotter( url ).c_str() );
+        }
+
         closePdfObject();
     }
 
@@ -1056,8 +1083,8 @@ void PDF_PLOTTER::Text( const VECTOR2I&             aPos,
 }
 
 
-void PDF_PLOTTER::HyperlinkBoxURL( const BOX2I& aBox, const wxString& aDestinationURL )
+void PDF_PLOTTER::HyperlinkBox( const BOX2I& aBox, const wxString& aDestinationURL )
 {
-    m_urlHyperlinksInPage.push_back( std::make_pair( aBox, aDestinationURL ) );
+    m_hyperlinksInPage.push_back( std::make_pair( aBox, aDestinationURL ) );
 }
 
diff --git a/eeschema/sch_text.cpp b/eeschema/sch_text.cpp
index 44ca96282f..e826981611 100644
--- a/eeschema/sch_text.cpp
+++ b/eeschema/sch_text.cpp
@@ -485,7 +485,7 @@ void SCH_TEXT::Plot( PLOTTER* aPlotter, bool aBackground ) const
     }
 
     if( HasHyperlink() )
-        aPlotter->HyperlinkBoxURL( GetBoundingBox(), GetHyperlink() );
+        aPlotter->HyperlinkBox( GetBoundingBox(), GetHyperlink() );
 }
 
 
diff --git a/eeschema/sch_textbox.cpp b/eeschema/sch_textbox.cpp
index b89f8f8d67..999da78cb4 100644
--- a/eeschema/sch_textbox.cpp
+++ b/eeschema/sch_textbox.cpp
@@ -385,7 +385,7 @@ void SCH_TEXTBOX::Plot( PLOTTER* aPlotter, bool aBackground ) const
     }
 
     if( HasHyperlink() )
-        aPlotter->HyperlinkBoxURL( GetBoundingBox(), GetHyperlink() );
+        aPlotter->HyperlinkBox( GetBoundingBox(), GetHyperlink() );
 }
 
 
diff --git a/include/eda_text.h b/include/eda_text.h
index 0bf9a3b88b..a859c83e04 100644
--- a/include/eda_text.h
+++ b/include/eda_text.h
@@ -335,6 +335,22 @@ public:
      */
     static bool ValidateHyperlink( const wxString& aURL );
 
+    /**
+     * Check if aURL is a valid "goto" hyperlink.
+     *
+     * @param aURL String to validate
+     * @param aDestinationIdx optional. pointer to populate with the page index destination
+     * @return true if aURL is a valid hyperlink
+     */
+    static bool IsGotoPageHyperlink( const wxString& aURL, int* aDestination = nullptr );
+
+    /**
+     * Generate a hyperlink string that goes to the page number specified
+     * @param aDestination Virtual page number to go to. Note that the root sheet is 1.
+     * @return A hyperlink String that goes to the page number specified
+     */
+    static wxString GotoPageHyperlinkString( const int& aDestination );
+
 protected:
     /**
      * A hyperlink to a URL or file in the system. If empty, this text object is not a hyperlink
diff --git a/include/plotters/plotter.h b/include/plotters/plotter.h
index 393e4768af..b805bd3c94 100644
--- a/include/plotters/plotter.h
+++ b/include/plotters/plotter.h
@@ -440,9 +440,9 @@ public:
      * Create a clickable hyperlink with a rectangular click area
      *
      * @aBox is the rectangular click target
-     * @aDestinationURL is the target
+     * @aDestinationURL is the target URL
      */
-    virtual void HyperlinkBoxURL( const BOX2I& aBox, const wxString& aDestinationURL )
+    virtual void HyperlinkBox( const BOX2I& aBox, const wxString& aDestinationURL )
     {
         // NOP for most plotters.
     }
diff --git a/include/plotters/plotters_pslike.h b/include/plotters/plotters_pslike.h
index 1c784bb46b..74d3d64ae9 100644
--- a/include/plotters/plotters_pslike.h
+++ b/include/plotters/plotters_pslike.h
@@ -352,7 +352,7 @@ public:
                        KIFONT::FONT*               aFont = nullptr,
                        void*                       aData = nullptr ) override;
 
-    virtual void HyperlinkBoxURL( const BOX2I& aBox, const wxString& aDestinationURL ) override;
+    virtual void HyperlinkBox( const BOX2I& aBox, const wxString& aDestinationURL ) override;
 
     /**
      * PDF images are handles as inline, not XObject streams...
@@ -417,11 +417,11 @@ protected:
     FILE* m_workFile;               ///< Temporary file to construct the stream before zipping
     std::vector<long> m_xrefTable;  ///< The PDF xref offset table
 
-    ///< List of loaded URLs in current page
-    std::vector<std::pair<BOX2I, wxString>> m_urlHyperlinksInPage;
+    ///< List of loaded hyperlinks in current page
+    std::vector<std::pair<BOX2I, wxString>> m_hyperlinksInPage;
 
     ///< Handles for all the hyperlink objects that will be deferred
-    std::map<int, std::pair<BOX2D, wxString>> m_urlHyperlinksHandles;
+    std::map<int, std::pair<BOX2D, wxString>> m_hyperlinkHandles;
 };