1 /* This file is part of Shapes.
3 * Shapes is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
8 * Shapes is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with Shapes. If not, see <http://www.gnu.org/licenses/>.
16 * Copyright 2008 Henrik Tidefelt
19 #include "texlabelmanager.h"
20 #include "dynamicenvironment.h"
21 #include "simplepdfo.h"
22 #include "simplepdfi.h"
23 #include "shapesexceptions.h"
25 #include "texscanner.h"
36 #include <sys/types.h>
41 using namespace Shapes
;
47 void systemDebugMessage( const std::string
& msg
);
52 Kernel::TeXLabelManager::TeXLabelManager( )
53 : anyLabelMiss( false ), loadCount( 0 ), jobNumber( 0 ), documentclass( "article" ), lmodernT1( true ), utf8( true ), setupFinalized( false )
57 Kernel::TeXLabelManager::~TeXLabelManager( )
62 Kernel::TeXLabelManager::stringWithJobNumber( const std::string
& str
, bool withDirectory
) const
79 Kernel::TeXLabelManager::setup( const std::string
& inDir
, const std::string
& tmpDir
, const std::string
& texJobName
)
83 texJobName_
= texJobName
;
88 Kernel::TeXLabelManager::announce( const std::string
& str
, const Ast::SourceLocation
& loc
)
90 if( isAllBlank( str
.c_str( ) ) )
95 if( availableLabels
.find( safeSourceHash( str
) ) == availableLabels
.end( ) )
97 typedef typeof currentRequests RequestMapType
;
98 currentRequests
.insert( RequestMapType::value_type( str
, RequestLocation( true, loc
) ) );
103 RefCountPtr
< const Lang::Value
>
104 Kernel::TeXLabelManager::request( const std::string
& str
, const Ast::SourceLocation
& loc
, Kernel::PassedDyn dyn
)
106 if( isAllBlank( str
.c_str( ) ) )
108 return static_cast< RefCountPtr
< const Lang::Geometric2D
> >( Lang::THE_POINTPICTURE
);
111 allRequests
.insert( str
);
112 string hash
= safeSourceHash( str
);
113 MapType::iterator i
= availableLabels
.find( hash
);
114 if( i
== availableLabels
.end( ) )
118 typedef typeof currentRequests RequestMapType
;
119 currentRequests
.insert( RequestMapType::value_type( str
, RequestLocation( false, loc
) ) );
121 string extendedName
= stringWithJobNumber( texJobName_
) + ".pdf";
122 RefCountPtr
< ifstream
> iFile
= RefCountPtr
< ifstream
>( new ifstream( extendedName
.c_str( ) ) );
123 if( ! iFile
->good( ) )
125 throw Exceptions::InternalError( strrefdup( "Failed to open " + extendedName
+ " for input." ) );
128 i
= availableLabels
.find( hash
);
130 if( i
== availableLabels
.end( ) )
132 throw Exceptions::InternalError( strrefdup( "Failed to find generated label: " + str
) );
134 return static_cast< RefCountPtr
< const Lang::Geometric2D
> >( i
->second
->cloneWithState( dyn
->getGraphicsState( ), dyn
->getTeXBleed( ) ) );
141 typedef std::pair
< std::string
, Kernel::TeXLabelManager::RequestLocation
> T
;
142 class RequestsOrder
: public std::binary_function
< T
, T
, bool >
145 result_type
operator () ( const first_argument_type
& x1
, const second_argument_type
& x2
)
147 return x1
.first
< x2
.first
;
154 Kernel::TeXLabelManager::iterativeStartup( RefCountPtr
< std::istream
> labelsFile
)
158 loadLabels( labelsFile
);
160 /* Remove the labels already generated from the request set.
161 Since the available set is hashed, one must search the request set for elements in the available set, rather than vice versa.
162 Process in two steps: 1) find the intersection 2) remove it.
164 typedef typeof currentRequests T
;
166 for( T::iterator i
= currentRequests
.begin( ); i
!= currentRequests
.end( ); ++i
)
168 if( availableLabels
.find( safeSourceHash( i
->first
) ) != availableLabels
.end( ) )
170 removeSet
.insert( *i
);
174 set_difference( currentRequests
.begin( ), currentRequests
.end( ),
175 removeSet
.begin( ), removeSet
.end( ),
176 insert_iterator
< T
>( tmp
, tmp
.begin( ) ),
177 Kernel::RequestsOrder( ) );
178 currentRequests
= tmp
;
180 catch( const Exceptions::TeXSetupHasChanged
& ball
)
188 Kernel::TeXLabelManager::iterativeFinish( const std::string
& labelDBFilename
)
198 // We have only used old labels.
201 // Otherwise, the only generated pdf file with labels is a good database.
203 string lastJobFilename
= stringWithJobNumber( texJobName_
) + ".pdf";
206 ostringstream mvCommand
;
208 // Avoid invoking the mv command if the file does not exist.
210 if( stat( lastJobFilename
.c_str( ), & theStat
) != 0 )
216 if( lastJobFilename
== labelDBFilename
)
218 // Move would yield a warning since we're not really moving anything.
222 mvCommand
<< "mv '" << lastJobFilename
<< "' '" << labelDBFilename
<< "'";
223 Interaction::systemDebugMessage( mvCommand
.str( ) );
224 if( system( mvCommand
.str( ).c_str( ) ) != 0 )
232 for( std::set
< std::string
>::const_iterator i
= allRequests
.begin( );
233 i
!= allRequests
.end( );
236 typedef typeof currentRequests RequestMapType
;
237 currentRequests
.insert( RequestMapType::value_type( *i
, RequestLocation( true, Ast::THE_UNKNOWN_LOCATION
) ) );
243 string finalJobFilename
= stringWithJobNumber( texJobName_
) + ".pdf";
244 ostringstream mvCommand
;
246 // Avoid invoking the mv command if the file does not exist.
248 if( stat( finalJobFilename
.c_str( ), & theStat
) != 0 )
254 if( finalJobFilename
== labelDBFilename
)
256 // Move would yield a warning since we're not really moving anything.
260 mvCommand
<< "mv '" << finalJobFilename
<< "' '" << labelDBFilename
<< "'";
261 Interaction::systemDebugMessage( mvCommand
.str( ) );
262 if( system( mvCommand
.str( ).c_str( ) ) != 0 )
278 ClearOnExit( T
* container
)
279 : container_( container
)
283 container_
->clear( );
290 Kernel::TeXLabelManager::processRequests( )
292 string extendedName
= stringWithJobNumber( texJobName_
) + ".tex";
293 ofstream
texFile( extendedName
.c_str( ) );
294 if( ! texFile
.good( ) )
296 throw Exceptions::FileWriteOpenError( Ast::THE_UNKNOWN_LOCATION
, strrefdup( extendedName
), "(TeX source)" );
299 if( ! setupFinalized
)
304 texFile
<< setupCode
;
305 texFile
<< "\\btexetexthing{" << "Shapes setup info" << "}{" << safeSourceHash( setupCode
) << "}{-1}" << endl
;
308 size_t labelIndex
= 0;
309 typedef typeof currentRequests RequestMapType
;
310 for( RequestMapType::const_iterator i
= currentRequests
.begin( );
311 i
!= currentRequests
.end( );
314 assertBalanced( i
->first
, i
->second
);
315 texFile
<< "\\btexetexthing{" << i
->first
<< "}{" << safeSourceHash( i
->first
) << "}{" << labelIndex
<< "}" << endl
;
318 ClearOnExit
< typeof currentRequests
> autoClear( & currentRequests
);
320 texFile
<< "\\end{document}" << endl
;
322 pid_t latexProcess
= fork( );
323 if( latexProcess
== -1 )
325 throw Exceptions::InternalError( strrefdup( "Failed to fork a process for running LaTeX." ) );
328 if( latexProcess
== 0 ) /* This is the child */
330 /* The exec call below never returns, so the child process never leaves this if clause.
331 * Hence, there is no need to create a special else clasuse below.
333 Interaction::systemDebugMessage( "cd " + inDir_
);
334 if( chdir( inDir_
.c_str( ) ) != 0 )
339 throw Exceptions::InternalError( strrefdup( "Attempt to change to source directory resulted in the no-entry failure." ) );
341 throw Exceptions::InternalError( strrefdup( "Attempt to change to source directory resulted in the not-a-directory failure." ) );
343 throw Exceptions::InternalError( strrefdup( "Attempt to change to source directory resulted in the no-search-permission failure." ) );
345 throw Exceptions::InternalError( strrefdup( "Attempt to change to source directory resulted in the symbolic-link-loop failure." ) );
347 throw Exceptions::InternalError( strrefdup( "Attempt to change to source directory resulted in the I/O failure." ) );
349 throw Exceptions::InternalError( strrefdup( "Attempt to change to source directory resulted in an unclassified failure." ) );
352 string extendedName
= stringWithJobNumber( texJobName_
);
353 if( Interaction::pdfLaTeXInteractionTo_stderr
)
359 std::string stdout_filename
= extendedName
+ ".stdout";
360 int stdout_file
= open( stdout_filename
.c_str( ), O_WRONLY
| O_CREAT
,
362 if( stdout_file
== -1 )
364 throw Exceptions::FileWriteOpenError( Ast::THE_UNKNOWN_LOCATION
, strrefdup( stdout_filename
), "(destination for stdout from pdfLaTeX)" );
366 dup2( stdout_file
, 1 );
367 close( stdout_file
);
369 execlp( "pdflatex", "pdflatex", "-output-directory", tmpDir_
.c_str( ), "-interaction", "nonstopmode", extendedName
.c_str( ), static_cast< const char * >( 0 ) );
373 oss
<< "Recieved errno = " << errno
<< " from execlp call to pdfLaTeX." ;
374 throw Exceptions::InternalError( strrefdup( oss
) );
376 throw Exceptions::InternalError( strrefdup( "execlp call to pdfLaTeX returned with errno == 0." ) );
382 pid_t wpid
= waitpid( latexProcess
, & status
, 0 );
391 throw Exceptions::InternalError( strrefdup( "Signal recieved while waiting for pdfLaTeX process." ) );
395 throw Exceptions::InternalError( strrefdup( "waitpid failure when waiting for pdfLaTeX process." ) );
399 if( WIFEXITED( status
) )
401 if( WEXITSTATUS( status
) != 0 )
403 if( Interaction::pdfLaTeXInteractionTo_stderr
)
405 throw Exceptions::TeXLabelError( false, "(--tex-debug)", strrefdup( "The output from pdfLaTeX was written to stderr." ), RefCountPtr
< const char >( NullPtr
< const char >( ) ), Ast::THE_UNKNOWN_LOCATION
);
408 std::string stdoutFilename
= stringWithJobNumber( texJobName_
) + ".stdout";
409 std::ifstream
stdoutFile( stdoutFilename
.c_str( ) );
410 if( ! stdoutFile
.good( ) )
412 throw Exceptions::InternalError( strrefdup( "Failed to open the file where pdfLaTeX's output to stdout should have been saved: " + stdoutFilename
) );
414 parseTeXErrors( stdoutFile
);
418 else if( WIFSIGNALED( status
) )
421 oss
<< "pdfLaTeX was killed by the signal " << WTERMSIG( status
) << "." ;
422 throw Exceptions::InternalError( strrefdup( oss
) );
424 else if( WIFSTOPPED( status
) )
427 oss
<< "pdfLaTeX was stopped by the signal " << WSTOPSIG( status
) << "." ;
428 throw Exceptions::InternalError( strrefdup( oss
) );
431 { /* Non-standard case -- may never happen */
433 oss
<< "waitpid for the pdfLaTeX process resulted in the unexpected status 0x" << std::hex
<< status
<< "." ;
434 throw Exceptions::InternalError( strrefdup( oss
) );
441 Kernel::TeXLabelManager::loadLabels( RefCountPtr
< std::istream
> labelsFile
)
443 if( ! setupFinalized
)
448 RefCountPtr
< SimplePDF::PDF_in
> pdfi
= RefCountPtr
< SimplePDF::PDF_in
>( new SimplePDF::PDF_in( labelsFile
) );
450 Kernel::thePDFImporter
.importBtexEtexThings( pdfi
, & availableLabels
, setupCodeHash
);
454 Kernel::TeXLabelManager::setDocumentClass( const Ast::SourceLocation
& loc
, const char * str
)
456 assertNotSetupFinalized( loc
);
461 Kernel::TeXLabelManager::addDocumentOption( const Ast::SourceLocation
& loc
, const char * str
)
463 assertNotSetupFinalized( loc
);
464 documentoptions
.push_back( str
);
468 Kernel::TeXLabelManager::setlmodernT1( const Ast::SourceLocation
& loc
, bool val
)
470 assertNotSetupFinalized( loc
);
475 Kernel::TeXLabelManager::setutf8( const Ast::SourceLocation
& loc
, bool val
)
477 assertNotSetupFinalized( loc
);
482 Kernel::TeXLabelManager::addPreambleLine( const Ast::SourceLocation
& loc
, const char * line
)
484 assertNotSetupFinalized( loc
);
485 preamble
<< line
<< endl
;
489 Kernel::TeXLabelManager::addDocumentTopLine( const Ast::SourceLocation
& loc
, const char * line
)
491 assertNotSetupFinalized( loc
);
492 documentTop
<< line
<< endl
;
496 Kernel::TeXLabelManager::assertNotSetupFinalized( const Ast::SourceLocation
& loc
) const
500 throw Exceptions::TeXSetupTooLate( loc
);
506 Kernel::TeXLabelManager::safeSourceHash( const std::string
& str
)
510 oss
<< setfill( '0' );
511 for( const char * src
= str
.c_str( ); *src
!= '\0'; ++src
)
513 if( isspace( *src
) )
523 if( isalnum( *src
) )
528 sprintf( tmp
, "%02x", *reinterpret_cast< const unsigned char * >( src
) );
536 Kernel::TeXLabelManager::compileSetupCode( )
538 setupFinalized
= true;
541 res
<< "\\documentclass" ;
542 if( ! documentoptions
.empty( ) )
545 std::list
< std::string
>::const_iterator i
= documentoptions
.begin( );
547 for( ++i
; i
!= documentoptions
.end( ); ++i
)
553 res
<< "{" << documentclass
<< "}" << endl
;
554 res
<< "\\pdfminorversion=4" << endl
;
557 res
<< "\\usepackage{lmodern}" << endl
;
558 res
<< "\\usepackage[T1]{fontenc}" << endl
;
562 res
<< "\\usepackage[utf8]{inputenc}" << endl
;
564 res
<< preamble
.str( ) ;
565 res
<< "\\newcommand{\\btexetexthing}[3]{%" << endl
;
566 res
<< " \\message{[SHAPES LABEL: #3]}" << endl
;
567 res
<< " \\immediate\\pdfobj stream {#2}" << endl
;
568 res
<< " \\setbox0 = \\hbox{#1}%" << endl
;
569 res
<< " \\pdfxform" << endl
;
570 res
<< " attr {/TeXht /\\the\\ht0 \\space /TeXdp /\\the\\dp0 \\space /TeXwd /\\the\\wd0 \\space /TeXsrc \\the\\pdflastobj \\space 0 R}" << endl
;
571 res
<< " resources{ }" << endl
;
572 res
<< " 0" << endl
;
573 res
<< " \\shipout\\hbox{\\pdfrefxform\\pdflastxform}" << endl
;
576 res
<< "\\begin{document}" << endl
;
577 res
<< documentTop
.str( ) ;
578 setupCode
= res
.str( );
579 setupCodeHash
= safeSourceHash( setupCode
);
584 Kernel::TeXLabelManager::isAllBlank( const char * str
)
586 for( ; *str
!= '\0'; ++str
)
588 if( ! isspace( *str
) )
597 Kernel::TeXLabelManager::assertBalanced( const std::string
& str
, const Kernel::TeXLabelManager::RequestLocation
& loc
) const
599 static TeXScanner scanner
;
602 scanner
.check( str
);
604 catch( RefCountPtr
< const char > msg
)
608 throw Exceptions::StaticTeXLabelError( false, "label", msg
, RefCountPtr
< const char >( NullPtr
< const char >( ) ), loc
.loc_
);
612 throw Exceptions::TeXLabelError( false, "label", msg
, RefCountPtr
< const char >( NullPtr
< const char >( ) ), loc
.loc_
);
618 Kernel::TeXLabelManager::parseTeXErrors( std::istream
& interaction
)
621 const char KEY
[] = "[SHAPES LABEL: ";
622 size_t KEY_LEN
= strlen( KEY
);
624 for( std::getline( interaction
, line
); ! interaction
.eof( ); std::getline( interaction
, line
) )
626 if( line
.compare( 0, 1, "!" ) == 0 )
628 line
= line
.substr( 2 );
631 if( line
.compare( 0, KEY_LEN
, KEY
) == 0 )
634 lastLabel
= strtol( line
.c_str( ) + KEY_LEN
, & endp
, 10 );
637 throw Exceptions::InternalError( strrefdup( "Failed to scan the label number in: " + line
) );
641 throw Exceptions::InternalError( "Failed to find an error message in the output from the failing call to pdfLaTeX." );
644 std::ostringstream message
;
648 for( std::getline( interaction
, tmp
); ! interaction
.eof( ); std::getline( interaction
, tmp
) )
652 message
<< tmp
.substr( lineSize
) << std::endl
;
655 if( tmp
.compare( 0, 1, "!" ) == 0 )
659 if( tmp
.compare( 0, KEY_LEN
, KEY
) == 0 )
663 if( tmp
.compare( 0, 2, "l." ) == 0 )
666 int dummyLineNo
= strtol( tmp
.c_str( ) + 2, & endp
, 10 );
667 (void) dummyLineNo
; /* Avoid unused variable warning. */
670 throw Exceptions::InternalError( "Expected a space after the line number specification in the output frmo pdfLaTeX." );
673 lineSize
= endp
- tmp
.c_str( );
674 message
<< endp
<< std::endl
;
677 message
<< tmp
<< std::endl
;
681 if( lastLabel
== -2 )
683 throw Exceptions::StaticTeXLabelError( true, "preamble", strrefdup( line
), strrefdup( message
), Ast::THE_UNKNOWN_LOCATION
);
685 if( lastLabel
== -1 )
687 throw Exceptions::InternalError( strrefdup( "The TeX setup information caused an error: " + line
) );
691 typedef typeof currentRequests RequestMapType
;
692 for( RequestMapType::const_iterator i
= currentRequests
.begin( );
693 i
!= currentRequests
.end( );
696 if( labelIndex
== lastLabel
)
698 if( i
->second
.literal_
)
700 throw Exceptions::StaticTeXLabelError( true, "label", strrefdup( line
), strrefdup( message
), i
->second
.loc_
);
704 throw Exceptions::TeXLabelError( true, "label", strrefdup( line
), strrefdup( message
), i
->second
.loc_
);
709 /* Reaching here is an error.
711 std::ostringstream msg
;
712 msg
<< "The label index " << lastLabel
<< " was out of the range (" << currentRequests
.size( ) << ")" ;
713 throw Exceptions::InternalError( strrefdup( msg
) );