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;
fputs( "endstream\n", m_outputFile );
fputs( "\nendstream\n", m_outputFile );
closePdfObject();
// 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
m_pageHandles.push_back( startPdfObject() );
int pageHandle = startPdfObject();
m_pageHandles.push_back( pageHandle );
fprintf( m_outputFile,
"<<\n"
@ -809,15 +810,44 @@ void PDF_PLOTTER::ClosePage()
// Mark the page stream as idle
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
m_hyperlinksInPage.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
m_xrefTable.clear();
@ -826,11 +856,16 @@ bool PDF_PLOTTER::StartPlot( const wxString& aPageNumber )
m_hyperlinkMenusInPage.clear();
m_hyperlinkHandles.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
to make the file binary from the beginning (the important thing is
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 */
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
with the page stream for page 1. Other more important stuff is written
at the end */
StartPage( aPageNumber );
StartPage(aPageNumber);
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()
{
wxASSERT( m_outputFile );
@ -1054,6 +1208,7 @@ bool PDF_PLOTTER::EndPlot()
">>\n", (long) m_pageHandles.size() );
closePdfObject();
// The info dictionary
int infoDictHandle = startPdfObject();
char date_buf[250];
@ -1080,16 +1235,37 @@ bool PDF_PLOTTER::EndPlot()
fputs( ">>\n", m_outputFile );
closePdfObject();
// Let's dump in the outline
int outlineHandle = emitOutline();
// The catalog, at last
int catalogHandle = startPdfObject();
fprintf( m_outputFile,
"<<\n"
"/Type /Catalog\n"
"/Pages %d 0 R\n"
"/Version /1.5\n"
"/PageMode /UseNone\n"
"/PageLayout /SinglePage\n"
">>\n", m_pageTreeHandle );
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,
"<<\n"
"/Type /Catalog\n"
"/Pages %d 0 R\n"
"/Version /1.5\n"
"/PageMode /UseNone\n"
"/PageLayout /SinglePage\n"
">>\n",
m_pageTreeHandle );
}
closePdfObject();
/* Emit the xref table (format is crucial to the byte, each entry must
@ -1188,4 +1364,10 @@ void PDF_PLOTTER::HyperlinkBox( const BOX2I& aBox, const wxString& aDestinationU
void PDF_PLOTTER::HyperlinkMenu( const BOX2I& aBox, const std::vector<wxString>& 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->EndBlock( nullptr );
aPlotter->ComponentBookmark( GetBoundingBox(),
GetField( REFERENCE_FIELD )->GetShownText() );
}
}

View File

@ -448,6 +448,17 @@ public:
// 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).
*/

View File

@ -352,6 +352,8 @@ public:
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...
*/
@ -359,6 +361,38 @@ public:
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,
const EDA_ANGLE& aEndAngle, int aRadius,
@ -410,6 +444,23 @@ protected:
*/
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_fontResDictHandle; ///< Font resource dictionary
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
std::map<int, std::pair<BOX2D, wxString>> m_hyperlinkHandles;
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->ComponentBookmark( footprint->GetBoundingBox(), footprint->GetReference() );
}
// Plot vias on copper layers, and if aPlotOpt.GetPlotViaOnMaskLayer() is true,