Updating the changelog in the VERSION file, and version_sync.
[shapes.git] / source / texlabelmanager.cc
blob754014aabeb32d7a72be68b7f3dc079a4585e2c3
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 if( lmodernT1 )
556 res << "\\usepackage{lmodern}" << endl ;
557 res << "\\usepackage[T1]{fontenc}" << endl ;
559 if( utf8 )
561 res << "\\usepackage[utf8]{inputenc}" << endl ;
563 res << preamble.str( ) ;
564 res << "\\newcommand{\\btexetexthing}[3]{%" << endl ;
565 res << " \\message{[SHAPES LABEL: #3]}" << endl ;
566 res << " \\immediate\\pdfobj stream {#2}" << endl ;
567 res << " \\setbox0 = \\hbox{#1}%" << endl ;
568 res << " \\pdfxform" << endl ;
569 res << " attr {/TeXht /\\the\\ht0 \\space /TeXdp /\\the\\dp0 \\space /TeXwd /\\the\\wd0 \\space /TeXsrc \\the\\pdflastobj \\space 0 R}" << endl ;
570 res << " resources{ }" << endl ;
571 res << " 0" << endl ;
572 res << " \\shipout\\hbox{\\pdfrefxform\\pdflastxform}" << endl ;
573 res << "}" << endl ;
575 res << "\\begin{document}" << endl ;
576 res << documentTop.str( ) ;
577 setupCode = res.str( );
578 setupCodeHash = safeSourceHash( setupCode );
582 bool
583 Kernel::TeXLabelManager::isAllBlank( const char * str )
585 for( ; *str != '\0'; ++str )
587 if( ! isspace( *str ) )
589 return false;
592 return true;
595 void
596 Kernel::TeXLabelManager::assertBalanced( const std::string & str, const Kernel::TeXLabelManager::RequestLocation & loc ) const
598 static TeXScanner scanner;
601 scanner.check( str );
603 catch( RefCountPtr< const char > msg )
605 if( loc.literal_ )
607 throw Exceptions::StaticTeXLabelError( false, "label", msg, RefCountPtr< const char >( NullPtr< const char >( ) ), loc.loc_ );
609 else
611 throw Exceptions::TeXLabelError( false, "label", msg, RefCountPtr< const char >( NullPtr< const char >( ) ), loc.loc_ );
616 void
617 Kernel::TeXLabelManager::parseTeXErrors( std::istream & interaction )
619 int lastLabel = -2;
620 const char KEY[] = "[SHAPES LABEL: ";
621 size_t KEY_LEN = strlen( KEY );
622 std::string line;
623 for( std::getline( interaction, line ); ! interaction.eof( ); std::getline( interaction, line ) )
625 if( line.compare( 0, 1, "!" ) == 0 )
627 line = line.substr( 2 );
628 goto found;
630 if( line.compare( 0, KEY_LEN, KEY ) == 0 )
632 char * endp;
633 lastLabel = strtol( line.c_str( ) + KEY_LEN, & endp, 10 );
634 if( *endp != ']' )
636 throw Exceptions::InternalError( strrefdup( "Failed to scan the label number in: " + line ) );
640 throw Exceptions::InternalError( "Failed to find an error message in the output from the failing call to pdfLaTeX." );
642 found:
643 std::ostringstream message;
645 std::string tmp;
646 size_t lineSize = 0;
647 for( std::getline( interaction, tmp ); ! interaction.eof( ); std::getline( interaction, tmp ) )
649 if( lineSize > 0 )
651 message << tmp.substr( lineSize ) << std::endl ;
652 break;
654 if( tmp.compare( 0, 1, "!" ) == 0 )
656 break;
658 if( tmp.compare( 0, KEY_LEN, KEY ) == 0 )
660 break;
662 if( tmp.compare( 0, 2, "l." ) == 0 )
664 char * endp;
665 // int dummyLineNo =
666 strtol( tmp.c_str( ) + 2, & endp, 10 );
667 if( *endp != ' ' )
669 throw Exceptions::InternalError( "Expected a space after the line number specification in the output frmo pdfLaTeX." );
671 ++endp;
672 lineSize = endp - tmp.c_str( );
673 message << endp << std::endl ;
674 continue;
676 message << tmp << std::endl ;
680 if( lastLabel == -2 )
682 throw Exceptions::StaticTeXLabelError( true, "preamble", strrefdup( line ), strrefdup( message ), Ast::THE_UNKNOWN_LOCATION );
684 if( lastLabel == -1 )
686 throw Exceptions::InternalError( strrefdup( "The TeX setup information caused an error: " + line ) );
689 int labelIndex = 0;
690 typedef typeof currentRequests RequestMapType;
691 for( RequestMapType::const_iterator i = currentRequests.begin( );
692 i != currentRequests.end( );
693 ++i, ++labelIndex )
695 if( labelIndex == lastLabel )
697 if( i->second.literal_ )
699 throw Exceptions::StaticTeXLabelError( true, "label", strrefdup( line ), strrefdup( message ), i->second.loc_ );
701 else
703 throw Exceptions::TeXLabelError( true, "label", strrefdup( line ), strrefdup( message ), i->second.loc_ );
708 /* Reaching here is an error.
710 std::ostringstream msg;
711 msg << "The label index " << lastLabel << " was out of the range (" << currentRequests.size( ) << ")" ;
712 throw Exceptions::InternalError( strrefdup( msg ) );