ADDED: Add outline support to schematic PDF and PCB plot
This commit is contained in:
parent
44c2782d39
commit
6f8205235f
|
@ -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 ) );
|
||||||
|
}
|
||||||
|
|
|
@ -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() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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).
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue