ADDED: Add outline support to schematic PDF and PCB plot

This commit is contained in:
Marek Roszko 2022-09-24 21:45:48 -04:00
parent 44c2782d39
commit 6f8205235f
5 changed files with 268 additions and 15 deletions

View File

@ -660,7 +660,7 @@ void PDF_PLOTTER::closePdfStream()
} }
delete[] inbuf; delete[] inbuf;
fputs( "endstream\n", m_outputFile ); fputs( "\nendstream\n", m_outputFile );
closePdfObject(); closePdfObject();
// Writing the deferred length as an indirect object // Writing the deferred length as an indirect object
@ -782,7 +782,8 @@ void PDF_PLOTTER::ClosePage()
} }
// Emit the page object and put it in the page list for later // Emit the page object and put it in the page list for later
m_pageHandles.push_back( startPdfObject() ); int pageHandle = startPdfObject();
m_pageHandles.push_back( pageHandle );
fprintf( m_outputFile, fprintf( m_outputFile,
"<<\n" "<<\n"
@ -809,15 +810,44 @@ void PDF_PLOTTER::ClosePage()
// Mark the page stream as idle // Mark the page stream as idle
m_pageStreamHandle = 0; m_pageStreamHandle = 0;
OUTLINE_NODE* pageOutlineNode = addOutlineNode(
m_outlineRoot.get(), -1, wxString::Format( _( "Page %s" ), m_pageNumbers.back() ) );
OUTLINE_NODE* componentOutlineNode = addOutlineNode( pageOutlineNode, -1, _( "Components" ) );
// let's reorg the symbol bookmarks under a page handle
for( const std::pair<BOX2I, wxString>& bookmarkPair : m_componentBookmarksInPage )
{
const BOX2I& box = bookmarkPair.first;
const wxString& ref = bookmarkPair.second;
VECTOR2I bottomLeft = iuToPdfUserSpace( box.GetPosition() );
VECTOR2I topRight = iuToPdfUserSpace( box.GetEnd() );
int actionHandle = emitGoToAction( pageHandle, bottomLeft, topRight );
addOutlineNode( componentOutlineNode, actionHandle, ref );
}
std::sort( componentOutlineNode->children.begin(), componentOutlineNode->children.end(),
[]( const OUTLINE_NODE* a, const OUTLINE_NODE* b ) -> bool
{
return a->title < b->title;
} );
// Clean up // Clean up
m_hyperlinksInPage.clear(); m_hyperlinksInPage.clear();
m_hyperlinkMenusInPage.clear(); m_hyperlinkMenusInPage.clear();
m_componentBookmarksInPage.clear();
} }
bool PDF_PLOTTER::StartPlot( const wxString& aPageNumber ) bool PDF_PLOTTER::StartPlot(const wxString& aPageNumber)
{ {
wxASSERT( m_outputFile ); wxASSERT(m_outputFile);
// First things first: the customary null object // First things first: the customary null object
m_xrefTable.clear(); m_xrefTable.clear();
@ -826,11 +856,16 @@ bool PDF_PLOTTER::StartPlot( const wxString& aPageNumber )
m_hyperlinkMenusInPage.clear(); m_hyperlinkMenusInPage.clear();
m_hyperlinkHandles.clear(); m_hyperlinkHandles.clear();
m_hyperlinkMenuHandles.clear(); m_hyperlinkMenuHandles.clear();
m_componentBookmarksInPage.clear();
m_outlineRoot.release();
m_totalOutlineNodes = 0;
m_outlineRoot = std::make_unique<OUTLINE_NODE>();
/* The header (that's easy!). The second line is binary junk required /* The header (that's easy!). The second line is binary junk required
to make the file binary from the beginning (the important thing is to make the file binary from the beginning (the important thing is
that they must have the bit 7 set) */ that they must have the bit 7 set) */
fputs( "%PDF-1.5\n%\200\201\202\203\n", m_outputFile ); fputs("%PDF-1.5\n%\200\201\202\203\n", m_outputFile);
/* Allocate an entry for the page tree root, it will go in every page parent entry */ /* Allocate an entry for the page tree root, it will go in every page parent entry */
m_pageTreeHandle = allocPdfObject(); m_pageTreeHandle = allocPdfObject();
@ -842,11 +877,130 @@ bool PDF_PLOTTER::StartPlot( const wxString& aPageNumber )
/* Now, the PDF is read from the end, (more or less)... so we start /* 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 with the page stream for page 1. Other more important stuff is written
at the end */ at the end */
StartPage( aPageNumber ); StartPage(aPageNumber);
return true; return true;
} }
int PDF_PLOTTER::emitGoToAction( int aPageHandle, const VECTOR2I& aBottomLeft,
const VECTOR2I& aTopRight )
{
int actionHandle = allocPdfObject();
startPdfObject( actionHandle );
fprintf( m_outputFile,
"<</S /GoTo /D [%d 0 R /FitR %d %d %d %d]\n"
">>\n",
aPageHandle, aBottomLeft.x, aBottomLeft.y, aTopRight.x, aTopRight.y );
closePdfObject();
return actionHandle;
}
void PDF_PLOTTER::emitOutlineNode( OUTLINE_NODE* node, int parentHandle, int nextNode,
int prevNode )
{
int nodeHandle = node->entryHandle;
int prevHandle = -1;
int nextHandle = -1;
for( std::vector<OUTLINE_NODE*>::iterator it = node->children.begin();
it != node->children.end(); it++ )
{
if( it >= node->children.end() - 1 )
{
nextHandle = -1;
}
else
{
nextHandle = ( *( it + 1 ) )->entryHandle;
}
emitOutlineNode( *it, nodeHandle, nextHandle, prevHandle );
prevHandle = ( *it )->entryHandle;
}
if( parentHandle != -1 ) // -1 for parentHandle is the outline root itself which is handed elsewhere
{
startPdfObject( nodeHandle );
fprintf( m_outputFile,
"<< /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 );
}
if( prevNode > 0 )
{
fprintf( m_outputFile, " /Prev %d 0 R\n", prevNode );
}
if( node->children.size() > 0 )
{
fprintf( m_outputFile, " /Count %lld\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 );
}
fputs( ">>\n", m_outputFile );
closePdfObject();
}
}
PDF_PLOTTER::OUTLINE_NODE* PDF_PLOTTER::addOutlineNode( OUTLINE_NODE* aParent, int aActionHandle,
const wxString& aTitle )
{
OUTLINE_NODE *node = aParent->AddChild( aActionHandle, aTitle, allocPdfObject() );
m_totalOutlineNodes++;
return node;
}
int PDF_PLOTTER::emitOutline()
{
if( m_outlineRoot->children.size() > 0 )
{
// declare the outline object
m_outlineRoot->entryHandle = allocPdfObject();
emitOutlineNode( m_outlineRoot.get(), -1, -1, -1 );
startPdfObject( m_outlineRoot->entryHandle );
fprintf( m_outputFile,
"<< /Type /Outlines\n"
" /Count %d\n"
" /First %d 0 R\n"
" /Last %d 0 R\n"
">>\n",
m_totalOutlineNodes,
m_outlineRoot->children.front()->entryHandle,
m_outlineRoot->children.back()->entryHandle
);
closePdfObject();
return m_outlineRoot->entryHandle;
}
return -1;
}
bool PDF_PLOTTER::EndPlot() bool PDF_PLOTTER::EndPlot()
{ {
wxASSERT( m_outputFile ); wxASSERT( m_outputFile );
@ -1054,6 +1208,7 @@ bool PDF_PLOTTER::EndPlot()
">>\n", (long) m_pageHandles.size() ); ">>\n", (long) m_pageHandles.size() );
closePdfObject(); closePdfObject();
// The info dictionary // The info dictionary
int infoDictHandle = startPdfObject(); int infoDictHandle = startPdfObject();
char date_buf[250]; char date_buf[250];
@ -1080,8 +1235,27 @@ bool PDF_PLOTTER::EndPlot()
fputs( ">>\n", m_outputFile ); fputs( ">>\n", m_outputFile );
closePdfObject(); closePdfObject();
// Let's dump in the outline
int outlineHandle = emitOutline();
// The catalog, at last // The catalog, at last
int catalogHandle = startPdfObject(); int catalogHandle = startPdfObject();
if( outlineHandle > 0 )
{
fprintf( m_outputFile,
"<<\n"
"/Type /Catalog\n"
"/Pages %d 0 R\n"
"/Version /1.5\n"
"/PageMode /UseOutlines\n"
"/Outlines %d 0 R\n"
"/PageLayout /SinglePage\n"
">>\n",
m_pageTreeHandle,
outlineHandle );
}
else
{
fprintf( m_outputFile, fprintf( m_outputFile,
"<<\n" "<<\n"
"/Type /Catalog\n" "/Type /Catalog\n"
@ -1089,7 +1263,9 @@ bool PDF_PLOTTER::EndPlot()
"/Version /1.5\n" "/Version /1.5\n"
"/PageMode /UseNone\n" "/PageMode /UseNone\n"
"/PageLayout /SinglePage\n" "/PageLayout /SinglePage\n"
">>\n", m_pageTreeHandle ); ">>\n",
m_pageTreeHandle );
}
closePdfObject(); closePdfObject();
/* Emit the xref table (format is crucial to the byte, each entry must /* Emit the xref table (format is crucial to the byte, each entry must
@ -1189,3 +1365,9 @@ void PDF_PLOTTER::HyperlinkMenu( const BOX2I& aBox, const std::vector<wxString>&
{ {
m_hyperlinkMenusInPage.push_back( std::make_pair( aBox, aDestURLs ) ); m_hyperlinkMenusInPage.push_back( std::make_pair( aBox, aDestURLs ) );
} }
void PDF_PLOTTER::ComponentBookmark( const BOX2I& aLocation, const wxString& aSymbolReference )
{
m_componentBookmarksInPage.push_back( std::make_pair( aLocation, aSymbolReference ) );
}

View File

@ -2065,6 +2065,9 @@ void SCH_SYMBOL::Plot( PLOTTER* aPlotter, bool aBackground ) const
aPlotter->HyperlinkMenu( GetBoundingBox(), properties ); aPlotter->HyperlinkMenu( GetBoundingBox(), properties );
aPlotter->EndBlock( nullptr ); aPlotter->EndBlock( nullptr );
aPlotter->ComponentBookmark( GetBoundingBox(),
GetField( REFERENCE_FIELD )->GetShownText() );
} }
} }

View File

@ -448,6 +448,17 @@ public:
// NOP for most plotters. // NOP for most plotters.
} }
/**
* Create a bookmark to a symbol
*
* @aBox is the bounding box of the symbol
* @aSymbolReference is the symbol schematic ref
*/
virtual void ComponentBookmark( const BOX2I& aBox, const wxString& aSymbolReference )
{
// NOP for most plotters.
}
/** /**
* Draw a marker (used for the drill map). * Draw a marker (used for the drill map).
*/ */

View File

@ -352,6 +352,8 @@ public:
void HyperlinkMenu( const BOX2I& aBox, const std::vector<wxString>& aDestURLs ) override; void HyperlinkMenu( const BOX2I& aBox, const std::vector<wxString>& aDestURLs ) override;
void ComponentBookmark( const BOX2I& aLocation, const wxString& aSymbolReference ) override;
/** /**
* PDF images are handles as inline, not XObject streams... * PDF images are handles as inline, not XObject streams...
*/ */
@ -359,6 +361,38 @@ public:
protected: protected:
struct OUTLINE_NODE
{
int actionHandle; //< Handle to action
wxString title; //< Title of outline node
int entryHandle; //< Allocated handle for this outline entry
std::vector<OUTLINE_NODE*> children;
OUTLINE_NODE* AddChild( int aActionHandle, const wxString& aTitle, int aEntryHandle )
{
OUTLINE_NODE* child = new OUTLINE_NODE
{
aActionHandle, aTitle, aEntryHandle
};
children.push_back( child );
return child;
}
};
/**
* Adds a new outline node entry
*
* The PDF object handle is automacially allocated
*
* @param aParent Parent node to append the new node to
* @param aActionHandle The handle of an action that may be performed on click, set to -1 for no action
* @param aTitle Title of node to display
*/
OUTLINE_NODE* addOutlineNode( OUTLINE_NODE* aParent, int aActionHandle,
const wxString& aTitle );
virtual void Arc( const VECTOR2I& aCenter, const EDA_ANGLE& aStartAngle, virtual void Arc( const VECTOR2I& aCenter, const EDA_ANGLE& aStartAngle,
const EDA_ANGLE& aEndAngle, int aRadius, const EDA_ANGLE& aEndAngle, int aRadius,
@ -410,6 +444,23 @@ protected:
*/ */
void closePdfStream(); void closePdfStream();
/**
* Starts emitting the outline object
*/
int emitOutline();
/**
* Emits a outline item object and recurses into any children
*/
void emitOutlineNode( OUTLINE_NODE* aNode, int aParentHandle, int aNextNode, int aPrevNode );
/**
* Emits an action object that instructs a goto coordinates on a page
*
* @return Generated action handle
*/
int emitGoToAction( int aPageHandle, const VECTOR2I& aBottomLeft, const VECTOR2I& aTopRight );
int m_pageTreeHandle; ///< Handle to the root of the page tree object int m_pageTreeHandle; ///< Handle to the root of the page tree object
int m_fontResDictHandle; ///< Font resource dictionary int m_fontResDictHandle; ///< Font resource dictionary
std::vector<int> m_pageHandles; ///< Handles to the page objects std::vector<int> m_pageHandles; ///< Handles to the page objects
@ -429,6 +480,11 @@ protected:
///< Handles for all the hyperlink objects that will be deferred ///< Handles for all the hyperlink objects that will be deferred
std::map<int, std::pair<BOX2D, wxString>> m_hyperlinkHandles; std::map<int, std::pair<BOX2D, wxString>> m_hyperlinkHandles;
std::map<int, std::pair<BOX2D, std::vector<wxString>>> m_hyperlinkMenuHandles; std::map<int, std::pair<BOX2D, std::vector<wxString>>> m_hyperlinkMenuHandles;
std::vector<std::pair<BOX2I, wxString>> m_componentBookmarksInPage;
std::unique_ptr<OUTLINE_NODE> m_outlineRoot; ///< Root outline node
int m_totalOutlineNodes; ///< Total number of outline nodes
}; };

View File

@ -524,6 +524,7 @@ void PlotStandardLayer( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask,
} }
aPlotter->EndBlock( nullptr ); aPlotter->EndBlock( nullptr );
aPlotter->ComponentBookmark( footprint->GetBoundingBox(), footprint->GetReference() );
} }
// Plot vias on copper layers, and if aPlotOpt.GetPlotViaOnMaskLayer() is true, // Plot vias on copper layers, and if aPlotOpt.GetPlotViaOnMaskLayer() is true,