Add 3d pdf functions to PDF_PLOTTER to reduce rebasing pain

This commit is contained in:
Marek Roszko 2025-05-11 11:36:05 -04:00
parent 57a3a3a2ec
commit 0268ed6d28
2 changed files with 252 additions and 46 deletions

View File

@ -48,6 +48,7 @@
#include <string_utils.h>
#include <fmt/format.h>
#include <fmt/chrono.h>
#include <fmt/ranges.h>
#include <plotters/plotters_pslike.h>
@ -549,17 +550,17 @@ int PDF_PLOTTER::allocPdfObject()
}
int PDF_PLOTTER::startPdfObject(int handle)
int PDF_PLOTTER::startPdfObject( int aHandle )
{
wxASSERT( m_outputFile );
wxASSERT( !m_workFile );
if( handle < 0)
handle = allocPdfObject();
if( aHandle < 0 )
aHandle = allocPdfObject();
m_xrefTable[handle] = ftell( m_outputFile );
fmt::println( m_outputFile, "{} 0 obj", handle );
return handle;
m_xrefTable[aHandle] = ftell( m_outputFile );
fmt::println( m_outputFile, "{} 0 obj", aHandle );
return aHandle;
}
@ -571,11 +572,11 @@ void PDF_PLOTTER::closePdfObject()
}
int PDF_PLOTTER::startPdfStream( int handle )
int PDF_PLOTTER::startPdfStream( int aHandle )
{
wxASSERT( m_outputFile );
wxASSERT( !m_workFile );
handle = startPdfObject( handle );
int handle = startPdfObject( aHandle );
// This is guaranteed to be handle+1 but needs to be allocated since
// you could allocate more object during stream preparation
@ -698,17 +699,20 @@ void PDF_PLOTTER::StartPage( const wxString& aPageNumber, const wxString& aPageN
// will be initialized to a the right value in pdf file by the first item to plot
m_currentPenWidth = 0;
// Open the content stream; the page object will go later
m_pageStreamHandle = startPdfStream();
if( !m_3dExportMode )
{
// Open the content stream; the page object will go later
m_pageStreamHandle = startPdfStream();
/* Now, until ClosePage *everything* must be wrote in workFile, to be
compressed later in closePdfStream */
/* Now, until ClosePage *everything* must be wrote in workFile, to be
compressed later in closePdfStream */
// Default graphic settings (coordinate system, default color and line style)
fmt::println( m_workFile,
"{:g} 0 0 {:g} 0 0 cm 1 J 1 j 0 0 0 rg 0 0 0 RG {:g} w",
0.0072 * plotScaleAdjX, 0.0072 * plotScaleAdjY,
userToDeviceSize( m_renderSettings->GetDefaultPenWidth() ) );
// Default graphic settings (coordinate system, default color and line style)
fmt::println( m_workFile,
"{:g} 0 0 {:g} 0 0 cm 1 J 1 j 0 0 0 rg 0 0 0 RG {:g} w",
0.0072 * plotScaleAdjX, 0.0072 * plotScaleAdjY,
userToDeviceSize( m_renderSettings->GetDefaultPenWidth() ) );
}
}
@ -790,10 +794,14 @@ void WriteImageSMaskStream( const wxImage& aImage, wxDataOutputStream& aOut )
void PDF_PLOTTER::ClosePage()
{
wxASSERT( m_workFile );
// non 3d exports need this
if( m_pageStreamHandle != -1 )
{
wxASSERT( m_workFile );
// Close the page stream (and compress it)
closePdfStream();
// Close the page stream (and compress it)
closePdfStream();
}
// Page size is in 1/72 of inch (default user space units). Works like the bbox in postscript
// but there is no need for swapping the sizes, since PDF doesn't require a portrait page.
@ -885,6 +893,14 @@ void PDF_PLOTTER::ClosePage()
closePdfObject();
}
int annot3DHandle = -1;
if( m_3dExportMode )
{
annot3DHandle = allocPdfObject();
}
// Emit the page object and put it in the page list for later
int pageHandle = startPdfObject();
m_pageHandles.push_back( pageHandle );
@ -893,28 +909,55 @@ void PDF_PLOTTER::ClosePage()
"<<\n"
"/Type /Page\n"
"/Parent {} 0 R\n"
"/Resources <<\n"
" /ProcSet [/PDF /Text /ImageC /ImageB]\n"
" /Font {} 0 R\n"
" /XObject {} 0 R >>\n"
"/MediaBox [0 0 {:g} {:g}]\n"
"/Contents {} 0 R\n",
"/MediaBox [0 0 {:g} {:g}]\n",
m_pageTreeHandle,
m_fontResDictHandle,
m_imgResDictHandle,
psPaperSize.x,
psPaperSize.y,
m_pageStreamHandle );
psPaperSize.y );
if( m_pageStreamHandle != -1 )
{
fmt::print( m_outputFile, "/Contents {} 0 R\n", m_pageStreamHandle );
}
std::vector<std::string> annots;
if( m_3dModelHandle != -1 )
{
annots.push_back( fmt::format( "{} 0 R", annot3DHandle ) );
}
if( hyperlinkHandles.size() > 0 )
fmt::print( m_outputFile, "/Annots {} 0 R", hyperLinkArrayHandle );
{
annots.push_back( fmt::format( "{} 0 R", hyperLinkArrayHandle ) );
}
fmt::print( m_outputFile, "/Annots [{}]\n", fmt::join( annots, " " ) );
fmt::print( m_outputFile, ">>\n" );
closePdfObject();
if( m_3dExportMode )
{
startPdfObject( annot3DHandle );
fmt::print( m_outputFile,
"<<\n"
"/Type /Annot\n"
"/Subtype /3D\n"
"/Rect [0 0 {:g} {:g}]\n"
"/NM (3D Annotation)\n"
"/3DD {} 0 R\n"
"/3DV 0\n"
"/3DA<</A/PO/D/PC/TB true/NP true>>\n"
"/3DI true\n"
"/P {} 0 R\n"
">>\n",
psPaperSize.x, psPaperSize.y, m_3dModelHandle, pageHandle );
closePdfObject();
}
// Mark the page stream as idle
m_pageStreamHandle = 0;
m_pageStreamHandle = -1;
int actionHandle = emitGoToAction( pageHandle );
PDF_PLOTTER::OUTLINE_NODE* parent_node = m_outlineRoot.get();
@ -1166,16 +1209,8 @@ int PDF_PLOTTER::emitOutline()
return -1;
}
bool PDF_PLOTTER::EndPlot()
void PDF_PLOTTER::endPlotEmitResources()
{
// We can end up here if there was nothing to plot
if( !m_outputFile )
return false;
// Close the current page (often the only one)
ClosePage();
/* We need to declare the resources we're using (fonts in particular)
The useful standard one is the Helvetica family. Adding external fonts
is *very* involved! */
@ -1526,6 +1561,21 @@ function ShM(aEntries) {
closePdfObject();
}
}
bool PDF_PLOTTER::EndPlot()
{
// We can end up here if there was nothing to plot
if( !m_outputFile )
return false;
// Close the current page (often the only one)
ClosePage();
if( !m_3dExportMode )
{
endPlotEmitResources();
}
/* The page tree: it's a B-tree but luckily we only have few pages!
So we use just an array... The handle was allocated at the beginning,
@ -1574,7 +1624,11 @@ function ShM(aEntries) {
closePdfObject();
// Let's dump in the outline
int outlineHandle = emitOutline();
int outlineHandle = -1;
if( !m_3dExportMode )
{
outlineHandle = emitOutline();
}
// The catalog, at last
int catalogHandle = startPdfObject();
@ -1800,3 +1854,134 @@ void PDF_PLOTTER::Bookmark( const BOX2I& aLocation, const wxString& aSymbolRefer
m_bookmarksInPage[aGroupName].push_back( std::make_pair( aLocation, aSymbolReference ) );
}
void PDF_PLOTTER::Plot3DModel( const wxString& aSourcePath,
const std::vector<PDF_3D_VIEW>& a3DViews )
{
std::map<float, int> m_fovMap;
std::vector<int> m_viewHandles;
for( const PDF_3D_VIEW& view : a3DViews )
{
int fovHandle = -1;
if( !m_fovMap.contains( view.m_fov ) )
{
fovHandle = allocPdfObject();
m_fovMap[view.m_fov] = fovHandle;
startPdfObject( fovHandle );
fmt::print( m_outputFile,
"<<\n"
"/FOV {}\n"
"/PS /Min\n"
"/Subtype /P\n"
">>\n",
view.m_fov );
closePdfObject();
}
else
{
fovHandle = m_fovMap[view.m_fov];
}
int viewHandle = allocPdfObject();
startPdfObject( viewHandle );
fmt::print( m_outputFile,
"<<\n"
"/Type /3DView\n"
"/XN ({})\n"
"/IN ({})\n"
"/MS /M\n"
"/C2W [{:f} {:f} {:f} {:f} {:f} {:f} {:f} {:f} {:f} {:f} {:f} {:f}]\n"
"/CO {:f}\n"
"/NR false\n"
"/BG<<\n"
"/Type /3DBG\n"
"/Subtype /SC\n"
"/CS /DeviceRGB\n"
"/C [1.000000 1.000000 1.000000]>>\n"
"/P {} 0 R\n"
"/LS<<\n"
"/Type /3DLightingScheme\n"
"/Subtype /CAD>>\n"
">>\n",
view.m_name, view.m_name, view.m_cameraMatrix[0],
view.m_cameraMatrix[1],
view.m_cameraMatrix[2], view.m_cameraMatrix[3], view.m_cameraMatrix[4],
view.m_cameraMatrix[5], view.m_cameraMatrix[6], view.m_cameraMatrix[7],
view.m_cameraMatrix[8], view.m_cameraMatrix[9], view.m_cameraMatrix[10],
view.m_cameraMatrix[11],
view.m_cameraCenter,
fovHandle );
closePdfObject();
m_viewHandles.push_back( viewHandle );
}
m_3dModelHandle = startPdfObject();
// so we can get remotely stuff the length afterwards
int modelLenHandle = allocPdfObject();
fmt::print( m_outputFile,
"<<\n"
"/Type /3D\n"
"/Subtype /U3D\n"
"/DV 0\n" );
fmt::print( m_outputFile, "/VA [" );
for( int viewHandle : m_viewHandles )
{
fmt::print( m_outputFile, "{} 0 R ", viewHandle );
}
fmt::print( m_outputFile, "]\n" );
fmt::print( m_outputFile,
"/Length {} 0 R\n"
"/Filter /FlateDecode\n"
">>\n", // Length is deferred
modelLenHandle );
fmt::println( m_outputFile, "stream" );
wxFFile outputFFile( m_outputFile );
fflush( m_outputFile );
long imgStreamStart = ftell( m_outputFile );
size_t model_stored_size = 0;
{
wxFFileOutputStream ffos( outputFFile );
wxZlibOutputStream zos( ffos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB );
wxDataOutputStream dos( zos );
wxFFileInputStream fileStream( aSourcePath );
if( !fileStream.IsOk() )
{
wxLogError( _( "Failed to open 3D model file: %s" ), aSourcePath );
}
const size_t bufferSize = 4096;
char buffer[bufferSize];
while( !fileStream.Eof() )
{
fileStream.Read( buffer, bufferSize );
zos.Write( buffer, fileStream.LastRead() );
}
}
fflush( m_outputFile );
model_stored_size = ftell( m_outputFile );
model_stored_size -= imgStreamStart; // Get the size of the compressed stream
fmt::print( m_outputFile, "\nendstream\n" );
closePdfObject();
startPdfObject( modelLenHandle );
fmt::println( m_outputFile, "{}", (unsigned) model_stored_size );
closePdfObject();
outputFFile.Detach(); // Don't close it
}

View File

@ -229,6 +229,15 @@ protected:
};
struct PDF_3D_VIEW
{
std::string m_name;
float m_cameraMatrix[12];
float m_cameraCenter;
float m_fov;
};
class PDF_PLOTTER : public PSLIKE_PLOTTER
{
public:
@ -238,10 +247,12 @@ public:
m_fontResDictHandle( 0 ),
m_imgResDictHandle( 0 ),
m_jsNamesHandle( 0 ),
m_pageStreamHandle( 0 ),
m_pageStreamHandle( -1 ),
m_streamLengthHandle( 0 ),
m_workFile( nullptr ),
m_totalOutlineNodes( 0 )
m_totalOutlineNodes( 0 ),
m_3dModelHandle( -1 ),
m_3dExportMode( false )
{
}
@ -367,6 +378,10 @@ public:
void Bookmark( const BOX2I& aBox, const wxString& aName,
const wxString& aGroupName = wxEmptyString ) override;
void Plot3DModel( const wxString& aSourcePath, const std::vector<PDF_3D_VIEW>& a3DViews );
void Set3DExport( bool aYes ) { m_3dExportMode = aYes; }
/**
* PDF images are handles as inline, not XObject streams...
*/
@ -444,7 +459,7 @@ protected:
* Open a new PDF object and returns the handle if the parameter is -1.
* Otherwise fill in the xref entry for the passed object
*/
int startPdfObject( int handle = -1 );
int startPdfObject( int aHandle = -1 );
/**
* Close the current PDF object
@ -458,7 +473,7 @@ protected:
* a lot of things, but for the moment we only handle page content.
* @eturn The object handle opened
*/
int startPdfStream( int handle = -1 );
int startPdfStream( int aHandle = -1 );
/**
* Finish the current PDF stream (writes the deferred length, too).
@ -483,6 +498,9 @@ protected:
int emitGoToAction( int aPageHandle, const VECTOR2I& aBottomLeft, const VECTOR2I& aTopRight );
int emitGoToAction( int aPageHandle );
void endPlotEmitResources();
int m_pageTreeHandle; ///< Handle to the root of the page tree object.
int m_fontResDictHandle; ///< Font resource dictionary.
int m_imgResDictHandle; ///< Image resource dictionary.
@ -515,6 +533,9 @@ protected:
std::unique_ptr<OUTLINE_NODE> m_outlineRoot; ///< Root outline node.
int m_totalOutlineNodes; ///< Total number of outline nodes.
int m_3dModelHandle;
bool m_3dExportMode;
};