From afe176abe2c7e65e1b1e91904753e93268756460 Mon Sep 17 00:00:00 2001
From: Alex Shvartzkop <dudesuchamazing@gmail.com>
Date: Tue, 4 Jul 2023 08:57:26 +0300
Subject: [PATCH] Optimize PDF plot data size for property popups; fix unicode
 escape.

Adds a document-level action JSInit, which defines ShM function,
allowing more compact data representation. Also reduces whitespaces.
---
 common/plotters/PDF_plotter.cpp    | 104 +++++++++++++++++++----------
 common/string_utils.cpp            |   2 +-
 include/plotters/plotters_pslike.h |   3 +-
 3 files changed, 70 insertions(+), 39 deletions(-)

diff --git a/common/plotters/PDF_plotter.cpp b/common/plotters/PDF_plotter.cpp
index 4519b11653..79a0854ad2 100644
--- a/common/plotters/PDF_plotter.cpp
+++ b/common/plotters/PDF_plotter.cpp
@@ -909,6 +909,8 @@ bool PDF_PLOTTER::StartPlot( const wxString& aPageNumber, const wxString& aPageN
        (it *could* be inherited via the Pages tree */
     m_fontResDictHandle = allocPdfObject();
 
+    m_jsNamesHandle = allocPdfObject();
+
     /* Now, the PDF is read from the end, (more or less)... so we start
        with the page stream for page 1. Other more important stuff is written
        at the end */
@@ -980,31 +982,32 @@ void PDF_PLOTTER::emitOutlineNode( OUTLINE_NODE* node, int parentHandle, int nex
         startPdfObject( nodeHandle );
 
         fprintf( m_outputFile,
-                 "<< /Title %s\n"
-                 "   /Parent %d 0 R\n",
+                 "<<\n"
+                 "/Title %s\n"
+                 "/Parent %d 0 R\n",
                  encodeStringForPlotter(node->title ).c_str(),
                  parentHandle);
 
         if( nextNode > 0 )
         {
-            fprintf( m_outputFile, "   /Next %d 0 R\n", nextNode );
+            fprintf( m_outputFile, "/Next %d 0 R\n", nextNode );
         }
 
         if( prevNode > 0 )
         {
-            fprintf( m_outputFile, "   /Prev %d 0 R\n", prevNode );
+            fprintf( m_outputFile, "/Prev %d 0 R\n", prevNode );
         }
 
         if( node->children.size() > 0 )
         {
-            fprintf( m_outputFile, "   /Count %zd\n", -1 * node->children.size() );
-            fprintf( m_outputFile, "   /First %d 0 R\n", node->children.front()->entryHandle );
-            fprintf( m_outputFile, "   /Last %d 0 R\n", node->children.back()->entryHandle );
+            fprintf( m_outputFile, "/Count %zd\n", -1 * node->children.size() );
+            fprintf( m_outputFile, "/First %d 0 R\n", node->children.front()->entryHandle );
+            fprintf( m_outputFile, "/Last %d 0 R\n", node->children.back()->entryHandle );
         }
 
         if( node->actionHandle != -1 )
         {
-            fprintf( m_outputFile, "   /A %d 0 R\n", node->actionHandle );
+            fprintf( m_outputFile, "/A %d 0 R\n", node->actionHandle );
         }
 
         fputs( ">>\n", m_outputFile );
@@ -1114,10 +1117,11 @@ bool PDF_PLOTTER::EndPlot()
         startPdfObject( linkHandle );
 
         fprintf( m_outputFile,
-                 "<< /Type /Annot\n"
-                 "   /Subtype /Link\n"
-                 "   /Rect [%g %g %g %g]\n"
-                 "   /Border [16 16 0]\n",
+                 "<<\n"
+                 "/Type /Annot\n"
+                 "/Subtype /Link\n"
+                 "/Rect [%g %g %g %g]\n"
+                 "/Border [16 16 0]\n",
                  box.GetLeft(), box.GetBottom(), box.GetRight(), box.GetTop() );
 
         wxString pageNumber;
@@ -1130,7 +1134,7 @@ bool PDF_PLOTTER::EndPlot()
                 if( m_pageNumbers[ii] == pageNumber )
                 {
                     fprintf( m_outputFile,
-                             "   /Dest [%d 0 R /FitB]\n"
+                             "/Dest [%d 0 R /FitB]\n"
                              ">>\n",
                              m_pageHandles[ii] );
 
@@ -1142,15 +1146,14 @@ bool PDF_PLOTTER::EndPlot()
             if( !pageFound )
             {
                 // destination page is not being plotted, assign the NOP action to the link
-                fprintf( m_outputFile,
-                         "   /A << /Type /Action /S /NOP >>\n"
-                         ">>\n" );
+                fprintf( m_outputFile, "/A << /Type /Action /S /NOP >>\n"
+                                       ">>\n" );
             }
         }
         else
         {
             fprintf( m_outputFile,
-                     "   /A << /Type /Action /S /URI /URI %s >>\n"
+                     "/A << /Type /Action /S /URI /URI %s >>\n"
                      ">>\n",
                      encodeStringForPlotter( url ).c_str() );
         }
@@ -1162,7 +1165,7 @@ bool PDF_PLOTTER::EndPlot()
     {
         const BOX2D&                 box = menuPair.first;
         const std::vector<wxString>& urls = menuPair.second;
-        wxString                     js = wxT( "var aParams = [ " );
+        wxString                     js = wxT( "ShM([\n" );
 
         for( const wxString& url : urls )
         {
@@ -1174,7 +1177,7 @@ bool PDF_PLOTTER::EndPlot()
                 {
                     wxString href = property.substr( property.Find( "http:" ) );
 
-                    js += wxString::Format( wxT( "{ cName: '%s', cReturn: '%s' }, " ),
+                    js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ),
                                             EscapeString( property, CTX_JS_STR ),
                                             EscapeString( href, CTX_JS_STR ) );
                 }
@@ -1182,13 +1185,13 @@ bool PDF_PLOTTER::EndPlot()
                 {
                     wxString href = property.substr( property.Find( "https:" ) );
 
-                    js += wxString::Format( wxT( "{ cName: '%s', cReturn: '%s' }, " ),
+                    js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ),
                                             EscapeString( property, CTX_JS_STR ),
                                             EscapeString( href, CTX_JS_STR ) );
                 }
                 else
                 {
-                    js += wxString::Format( wxT( "{ cName: '%s', cReturn: null }, " ),
+                    js += wxString::Format( wxT( "[\"%s\"],\n" ),
                                             EscapeString( property, CTX_JS_STR ) );
                 }
             }
@@ -1202,7 +1205,7 @@ bool PDF_PLOTTER::EndPlot()
                     {
                         wxString menuText = wxString::Format( _( "Show Page %s" ), pageNumber );
 
-                        js += wxString::Format( wxT( "{ cName: '%s', cReturn: '#%d' }, " ),
+                        js += wxString::Format( wxT( "[\"%s\", \"#%d\"],\n" ),
                                                 EscapeString( menuText, CTX_JS_STR ),
                                                 static_cast<int>( ii ) );
                         break;
@@ -1213,33 +1216,58 @@ bool PDF_PLOTTER::EndPlot()
             {
                 wxString menuText = wxString::Format( _( "Open %s" ), url );
 
-                js += wxString::Format( wxT( "{ cName: '%s', cReturn: '%s' }, " ),
+                js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ),
                                         EscapeString( menuText, CTX_JS_STR ),
                                         EscapeString( url, CTX_JS_STR ) );
             }
         }
 
-        js += wxT( "]; " );
-
-        js += wxT( "var cChoice = app.popUpMenuEx.apply\\( app, aParams \\); " );
-        js += wxT( "if\\( cChoice != null && cChoice.substring\\( 0, 1 \\) == '#' \\)"
-                   "    this.pageNum = parseInt\\( cChoice.slice\\( 1 \\) \\); " );
-        js += wxT( "else if\\( cChoice != null && cChoice.substring\\( 0, 4 \\) == 'http' \\)"
-                   "    app.launchURL\\( cChoice \\);" );
+        js += wxT( "]);" );
 
         startPdfObject( menuHandle );
 
         fprintf( m_outputFile,
-                 "<< /Type /Annot\n"
-                 "   /Subtype /Link\n"
-                 "   /Rect [%g %g %g %g]\n"
-                 "   /Border [16 16 0]\n",
+                 "<<\n"
+                 "/Type /Annot\n"
+                 "/Subtype /Link\n"
+                 "/Rect [%g %g %g %g]\n"
+                 "/Border [16 16 0]\n",
                  box.GetLeft(), box.GetBottom(), box.GetRight(), box.GetTop() );
 
         fprintf( m_outputFile,
-                 "   /A << /Type /Action /S /JavaScript /JS (%s) >>\n"
+                 "/A << /Type /Action /S /JavaScript /JS %s >>\n"
                  ">>\n",
-                 js.ToStdString().c_str() );
+                 encodeStringForPlotter( js ).c_str() );
+
+        closePdfObject();
+    }
+
+    {
+        startPdfObject( m_jsNamesHandle );
+
+        wxString js = R"JS(
+function ShM(aEntries) {
+    var aParams = [];
+    for (var i in aEntries) {
+        aParams.push({
+            cName: aEntries[i][0],
+            cReturn: aEntries[i][1]
+        })
+    }
+
+    var cChoice = app.popUpMenuEx.apply(app, aParams);
+    if (cChoice != null && cChoice.substring(0, 1) == '#') this.pageNum = parseInt(cChoice.slice(1));
+    else if (cChoice != null && cChoice.substring(0, 4) == 'http') app.launchURL(cChoice);
+}
+)JS";
+
+        fprintf( m_outputFile,
+                 "<< /JavaScript\n"
+                 " << /Names\n"
+                 "    [ (JSInit) << /Type /Action /S /JavaScript /JS %s >> ]\n"
+                 " >>\n"
+                 ">>\n",
+                 encodeStringForPlotter( js ).c_str() );
 
         closePdfObject();
     }
@@ -1303,10 +1331,12 @@ bool PDF_PLOTTER::EndPlot()
                  "/Version /1.5\n"
                  "/PageMode /UseOutlines\n"
                  "/Outlines %d 0 R\n"
+                 "/Names %d 0 R\n"
                  "/PageLayout /SinglePage\n"
                  ">>\n",
                  m_pageTreeHandle,
-                 outlineHandle );
+                 outlineHandle,
+                 m_jsNamesHandle );
     }
     else
     {
diff --git a/common/string_utils.cpp b/common/string_utils.cpp
index dda51e0de7..7588ee0a9f 100644
--- a/common/string_utils.cpp
+++ b/common/string_utils.cpp
@@ -203,7 +203,7 @@ wxString EscapeString( const wxString& aSource, ESCAPE_CONTEXT aContext )
             {
                 unsigned int code = c;
                 char buffer[16];
-                snprintf( buffer, sizeof(buffer), "\\\\u%4.4X", code );
+                snprintf( buffer, sizeof(buffer), "\\u%4.4X", code );
                 converted += buffer;
             }
             else
diff --git a/include/plotters/plotters_pslike.h b/include/plotters/plotters_pslike.h
index 5d8639cba2..89fadab94a 100644
--- a/include/plotters/plotters_pslike.h
+++ b/include/plotters/plotters_pslike.h
@@ -1,7 +1,7 @@
 /*
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
- * Copyright (C) 2016-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 2016-2023 KiCad Developers, see AUTHORS.txt for contributors.
  *
  * This program is free software: you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the
@@ -493,6 +493,7 @@ protected:
 
     int m_pageTreeHandle;           ///< Handle to the root of the page tree object
     int m_fontResDictHandle;        ///< Font resource dictionary
+    int m_jsNamesHandle;            ///< Handle for Names dictionary with JS
     std::vector<int> m_pageHandles; ///< Handles to the page objects
     int m_pageStreamHandle;         ///< Handle of the page content object
     int m_streamLengthHandle;       ///< Handle to the deferred stream length