Update procedures
[shapes.git] / source / texlabelmanager.cc
blob6c6f804aa6595913fbe431ab5c59973cae3b597b
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
6 * any later version.
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"
24 #include "globals.h"
25 #include "texscanner.h"
27 #include <fstream>
28 #include <sstream>
29 #include <iomanip>
30 #include <algorithm>
31 #include <errno.h>
32 #include <stdio.h>
33 #include <fcntl.h>
34 #include <unistd.h>
35 #include <sys/wait.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <ctype.h>
40 using namespace std;
41 using namespace Shapes;
43 namespace Shapes
45 namespace Interaction
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 )
54 { }
57 Kernel::TeXLabelManager::~TeXLabelManager( )
58 { }
61 std::string
62 Kernel::TeXLabelManager::stringWithJobNumber( const std::string & str, bool withDirectory ) const
64 ostringstream res;
65 if( withDirectory )
67 res << tmpDir_ ;
69 res << str ;
70 if( jobNumber >= 0 )
72 res << jobNumber ;
74 return res.str( );
78 void
79 Kernel::TeXLabelManager::setup( const std::string & inDir, const std::string & tmpDir, const std::string & texJobName )
81 inDir_ = inDir;
82 tmpDir_ = tmpDir;
83 texJobName_ = texJobName;
87 void
88 Kernel::TeXLabelManager::announce( const std::string & str, const Ast::SourceLocation & loc )
90 if( isAllBlank( str.c_str( ) ) )
92 return;
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( ) )
116 ++jobNumber;
117 anyLabelMiss = true;
118 typedef typeof currentRequests RequestMapType;
119 currentRequests.insert( RequestMapType::value_type( str, RequestLocation( false, loc ) ) );
120 processRequests( );
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." ) );
127 loadLabels( iFile );
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( ) ) );
137 namespace Shapes
139 namespace Kernel
141 typedef std::pair< std::string, Kernel::TeXLabelManager::RequestLocation > T;
142 class RequestsOrder : public std::binary_function< T, T, bool >
144 public:
145 result_type operator () ( const first_argument_type & x1, const second_argument_type & x2 )
147 return x1.first < x2.first;
153 void
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;
165 T removeSet;
166 for( T::iterator i = currentRequests.begin( ); i != currentRequests.end( ); ++i )
168 if( availableLabels.find( safeSourceHash( i->first ) ) != availableLabels.end( ) )
170 removeSet.insert( *i );
173 T tmp;
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 )
182 /* never mind */
187 void
188 Kernel::TeXLabelManager::iterativeFinish( const std::string & labelDBFilename )
190 if( ! anyLabelMiss )
192 return;
194 if( loadCount == 1 )
196 if( jobNumber == 0 )
198 // We have only used old labels.
199 return;
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.
209 struct stat theStat;
210 if( stat( lastJobFilename.c_str( ), & theStat ) != 0 )
212 return;
216 if( lastJobFilename == labelDBFilename )
218 // Move would yield a warning since we're not really moving anything.
219 return;
222 mvCommand << "mv '" << lastJobFilename << "' '" << labelDBFilename << "'";
223 Interaction::systemDebugMessage( mvCommand.str( ) );
224 if( system( mvCommand.str( ).c_str( ) ) != 0 )
226 /* Never mind. */
229 return;
232 for( std::set< std::string >::const_iterator i = allRequests.begin( );
233 i != allRequests.end( );
234 ++i )
236 typedef typeof currentRequests RequestMapType;
237 currentRequests.insert( RequestMapType::value_type( *i, RequestLocation( true, Ast::THE_UNKNOWN_LOCATION ) ) );
239 jobNumber = -1;
240 processRequests( );
243 string finalJobFilename = stringWithJobNumber( texJobName_ ) + ".pdf";
244 ostringstream mvCommand;
246 // Avoid invoking the mv command if the file does not exist.
247 struct stat theStat;
248 if( stat( finalJobFilename.c_str( ), & theStat ) != 0 )
250 return;
254 if( finalJobFilename == labelDBFilename )
256 // Move would yield a warning since we're not really moving anything.
257 return;
260 mvCommand << "mv '" << finalJobFilename << "' '" << labelDBFilename << "'";
261 Interaction::systemDebugMessage( mvCommand.str( ) );
262 if( system( mvCommand.str( ).c_str( ) ) != 0 )
264 /* Never mind. */
269 namespace Shapes
271 namespace Kernel
273 template< class T >
274 class ClearOnExit
276 T * container_;
277 public:
278 ClearOnExit( T * container )
279 : container_( container )
281 ~ClearOnExit( )
283 container_->clear( );
289 void
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 )
301 compileSetupCode( );
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( );
312 ++i, ++labelIndex )
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 )
336 switch( errno )
338 case ENOENT:
339 throw Exceptions::InternalError( strrefdup( "Attempt to change to source directory resulted in the no-entry failure." ) );
340 case ENOTDIR:
341 throw Exceptions::InternalError( strrefdup( "Attempt to change to source directory resulted in the not-a-directory failure." ) );
342 case EACCES:
343 throw Exceptions::InternalError( strrefdup( "Attempt to change to source directory resulted in the no-search-permission failure." ) );
344 case ELOOP:
345 throw Exceptions::InternalError( strrefdup( "Attempt to change to source directory resulted in the symbolic-link-loop failure." ) );
346 case EIO:
347 throw Exceptions::InternalError( strrefdup( "Attempt to change to source directory resulted in the I/O failure." ) );
348 default:
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 )
355 dup2( 2, 1 );
357 else
359 std::string stdout_filename = extendedName + ".stdout";
360 int stdout_file = open( stdout_filename.c_str( ), O_WRONLY | O_CREAT,
361 S_IRUSR | S_IWUSR );
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 ) );
370 if( errno != 0 )
372 ostringstream oss;
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." ) );
379 while( true )
381 int status;
382 pid_t wpid = waitpid( latexProcess, & status, 0 );
383 if( wpid == 0 )
385 continue;
387 if( wpid == -1 )
389 if( errno == EINTR )
391 throw Exceptions::InternalError( strrefdup( "Signal recieved while waiting for pdfLaTeX process." ) );
393 else
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 );
416 break;
418 else if( WIFSIGNALED( status ) )
420 ostringstream oss;
421 oss << "pdfLaTeX was killed by the signal " << WTERMSIG( status ) << "." ;
422 throw Exceptions::InternalError( strrefdup( oss ) );
424 else if( WIFSTOPPED( status ) )
426 ostringstream oss;
427 oss << "pdfLaTeX was stopped by the signal " << WSTOPSIG( status ) << "." ;
428 throw Exceptions::InternalError( strrefdup( oss ) );
430 else
431 { /* Non-standard case -- may never happen */
432 ostringstream oss;
433 oss << "waitpid for the pdfLaTeX process resulted in the unexpected status 0x" << std::hex << status << "." ;
434 throw Exceptions::InternalError( strrefdup( oss ) );
440 void
441 Kernel::TeXLabelManager::loadLabels( RefCountPtr< std::istream > labelsFile )
443 if( ! setupFinalized )
445 compileSetupCode( );
447 ++loadCount;
448 RefCountPtr< SimplePDF::PDF_in > pdfi = RefCountPtr< SimplePDF::PDF_in >( new SimplePDF::PDF_in( labelsFile ) );
450 Kernel::thePDFImporter.importBtexEtexThings( pdfi, & availableLabels, setupCodeHash );
453 void
454 Kernel::TeXLabelManager::setDocumentClass( const Ast::SourceLocation & loc, const char * str )
456 assertNotSetupFinalized( loc );
457 documentclass = str;
460 void
461 Kernel::TeXLabelManager::addDocumentOption( const Ast::SourceLocation & loc, const char * str )
463 assertNotSetupFinalized( loc );
464 documentoptions.push_back( str );
467 void
468 Kernel::TeXLabelManager::setlmodernT1( const Ast::SourceLocation & loc, bool val )
470 assertNotSetupFinalized( loc );
471 lmodernT1 = val;
474 void
475 Kernel::TeXLabelManager::setutf8( const Ast::SourceLocation & loc, bool val )
477 assertNotSetupFinalized( loc );
478 utf8 = val;
481 void
482 Kernel::TeXLabelManager::addPreambleLine( const Ast::SourceLocation & loc, const char * line )
484 assertNotSetupFinalized( loc );
485 preamble << line << endl ;
488 void
489 Kernel::TeXLabelManager::addDocumentTopLine( const Ast::SourceLocation & loc, const char * line )
491 assertNotSetupFinalized( loc );
492 documentTop << line << endl ;
495 void
496 Kernel::TeXLabelManager::assertNotSetupFinalized( const Ast::SourceLocation & loc ) const
498 if( setupFinalized )
500 throw Exceptions::TeXSetupTooLate( loc );
505 std::string
506 Kernel::TeXLabelManager::safeSourceHash( const std::string & str )
508 ostringstream oss;
509 char tmp[3];
510 oss << setfill( '0' );
511 for( const char * src = str.c_str( ); *src != '\0'; ++src )
513 if( isspace( *src ) )
515 oss << "Eb" ;
516 continue;
518 if( *src == 'E' )
520 oss << "EE" ;
521 continue;
523 if( isalnum( *src ) )
525 oss << *src ;
526 continue;
528 sprintf( tmp, "%02x", *reinterpret_cast< const unsigned char * >( src ) );
529 oss << "Ex" << tmp ;
531 return oss.str( );
535 void
536 Kernel::TeXLabelManager::compileSetupCode( )
538 setupFinalized = true;
540 ostringstream res;
541 res << "\\documentclass" ;
542 if( ! documentoptions.empty( ) )
544 res << "[" ;
545 std::list< std::string >::const_iterator i = documentoptions.begin( );
546 res << *i ;
547 for( ++i; i != documentoptions.end( ); ++i )
549 res << "," << *i ;
551 res << "]" ;
553 res << "{" << documentclass << "}" << endl ;
554 res << "\\pdfminorversion=4" << endl ;
555 if( lmodernT1 )
557 res << "\\usepackage{lmodern}" << endl ;
558 res << "\\usepackage[T1]{fontenc}" << endl ;
560 if( utf8 )
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 ;
574 res << "}" << endl ;
576 res << "\\begin{document}" << endl ;
577 res << documentTop.str( ) ;
578 setupCode = res.str( );
579 setupCodeHash = safeSourceHash( setupCode );
583 bool
584 Kernel::TeXLabelManager::isAllBlank( const char * str )
586 for( ; *str != '\0'; ++str )
588 if( ! isspace( *str ) )
590 return false;
593 return true;
596 void
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 )
606 if( loc.literal_ )
608 throw Exceptions::StaticTeXLabelError( false, "label", msg, RefCountPtr< const char >( NullPtr< const char >( ) ), loc.loc_ );
610 else
612 throw Exceptions::TeXLabelError( false, "label", msg, RefCountPtr< const char >( NullPtr< const char >( ) ), loc.loc_ );
617 void
618 Kernel::TeXLabelManager::parseTeXErrors( std::istream & interaction )
620 int lastLabel = -2;
621 const char KEY[] = "[SHAPES LABEL: ";
622 size_t KEY_LEN = strlen( KEY );
623 std::string line;
624 for( std::getline( interaction, line ); ! interaction.eof( ); std::getline( interaction, line ) )
626 if( line.compare( 0, 1, "!" ) == 0 )
628 line = line.substr( 2 );
629 goto found;
631 if( line.compare( 0, KEY_LEN, KEY ) == 0 )
633 char * endp;
634 lastLabel = strtol( line.c_str( ) + KEY_LEN, & endp, 10 );
635 if( *endp != ']' )
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." );
643 found:
644 std::ostringstream message;
646 std::string tmp;
647 size_t lineSize = 0;
648 for( std::getline( interaction, tmp ); ! interaction.eof( ); std::getline( interaction, tmp ) )
650 if( lineSize > 0 )
652 message << tmp.substr( lineSize ) << std::endl ;
653 break;
655 if( tmp.compare( 0, 1, "!" ) == 0 )
657 break;
659 if( tmp.compare( 0, KEY_LEN, KEY ) == 0 )
661 break;
663 if( tmp.compare( 0, 2, "l." ) == 0 )
665 char * endp;
666 int dummyLineNo = strtol( tmp.c_str( ) + 2, & endp, 10 );
667 (void) dummyLineNo; /* Avoid unused variable warning. */
668 if( *endp != ' ' )
670 throw Exceptions::InternalError( "Expected a space after the line number specification in the output frmo pdfLaTeX." );
672 ++endp;
673 lineSize = endp - tmp.c_str( );
674 message << endp << std::endl ;
675 continue;
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 ) );
690 int labelIndex = 0;
691 typedef typeof currentRequests RequestMapType;
692 for( RequestMapType::const_iterator i = currentRequests.begin( );
693 i != currentRequests.end( );
694 ++i, ++labelIndex )
696 if( labelIndex == lastLabel )
698 if( i->second.literal_ )
700 throw Exceptions::StaticTeXLabelError( true, "label", strrefdup( line ), strrefdup( message ), i->second.loc_ );
702 else
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 ) );