class Xetex (Manager) : # Shared values xmlConfFile = 'xetex.xml' def __init__(self, project, cfg, cType) : '''Do the primary initialization for this manager.''' super(Xetex, self).__init__(project, cfg) # import pdb; pdb.set_trace() # Create all the values we can right now for this manager. # Others will be created at run time when we know the cid. self.tools = Tools() self.project = project self.local = project.local self.log = project.log self.cfg = cfg self.pid = project.projectIDCode self.gid = project.gid self.cType = cType self.Ctype = cType.capitalize() self.mType = project.projectMediaIDCode self.renderer = 'xetex' self.manager = self.cType + '_' + self.renderer.capitalize() self.managers = project.managers self.pg_back = ProjBackground(self.pid, self.gid) self.fmt_diagnose = ProjDiagnose(self.pid, self.gid) self.proj_config = Config(self.pid, self.gid) self.proj_config.getProjectConfig() self.proj_config.getLayoutConfig() self.proj_config.getFontConfig() self.proj_config.getMacroConfig() # Bring in some manager objects we will need self.proj_font = ProjFont(self.pid) self.proj_illustration = ProjIllustration(self.pid, self.gid) self.proj_hyphenation = ProjHyphenation(self.pid, self.gid) self.usfmData = UsfmData() self.cidChapNumDict = self.usfmData.cidChapNumDict() self.cidPtIdDict = self.usfmData.cidPtIdDict() # Get config objs self.projectConfig = self.proj_config.projectConfig self.layoutConfig = self.proj_config.layoutConfig self.fontConfig = self.proj_config.fontConfig self.macroConfig = self.proj_config.macroConfig self.userConfig = self.project.userConfig self.macPackId = self.projectConfig['CompTypes'][self.Ctype]['macroPackage'] # Some config settings self.pdfViewerCmd = self.tools.getPdfViewerCommand(self.userConfig, self.projectConfig) self.sourceEditor = self.projectConfig['CompTypes'][self.Ctype]['sourceEditor'] self.useBackground = self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useBackground']) self.useDiagnostic = self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useDiagnostic']) self.useDocInfo = self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useDocInfo']) # Get settings for this component self.managerSettings = self.projectConfig['Managers'][self.manager] for k, v in self.managerSettings.iteritems() : if v == 'True' or v == 'False' : setattr(self, k, self.tools.str2bool(v)) else : setattr(self, k, v) # Set some Booleans (this comes after persistant values are set) # Setting hyphenation is a 2 step process, first check global, then group self.useHyphenation = False if self.tools.str2bool(self.projectConfig['ProjectInfo']['hyphenationOn']) : if self.tools.str2bool(self.projectConfig['Groups'][self.gid]['useHyphenation']) : self.useHyphenation = True # In case the macro is not installed we need to skip over this try : self.chapNumOffSingChap = self.tools.str2bool(self.macroConfig['Macros'][self.macPackId]['ChapterVerse']['omitChapterNumberOnSingleChapterBook']) except : self.chapNumOffSingChap = None # Make any dependent folders if needed if not os.path.isdir(self.local.projGidFolder) : os.makedirs(self.local.projGidFolder) # Record some error codes # FIXME: much more needs to be done with this self.xetexErrorCodes = { 0 : 'Rendering succeful.', 256 : 'Something really awful happened.' } # Log messages for this module self.errorCodes = { '1005' : ['ERR', 'PDF viewer failed with error: [<<1>>]'], '1010' : ['ERR', 'Style file [<<1>>] could not be created.'], '1040' : ['LOG', 'Created: [<<1>>]'], '0420' : ['WRN', 'TeX settings file has been frozen for debugging purposes.'], '0440' : ['LOG', 'Created: [<<1>>]'], '0460' : ['LOG', 'Settings changed in [<<1>>], [<<2>>] needed to be recreated.'], '0465' : ['LOG', 'File: [<<1>>] missing, created a new one.'], '0470' : ['ERR', 'Macro package [<<1>>] is not recognized by the system.'], '0600' : ['MSG', '<<1>> cannot be viewed, PDF viewer turned off.'], '0610' : ['LOG', 'Recorded [<<1>>] rendered pages in the [<<2>>] group.'], '0615' : ['ERR', 'XeTeX failed to execute with error: <<1>>'], '0617' : ['ERR', 'XeTeX failed to execute with this error: [<<1>>]'], '0620' : ['DBG', 'xetex command in <<1>>: <<2>> <<3>>'], '0625' : ['MSG', 'Rendering of [<<1>>] successful.'], '0630' : ['ERR', 'Rendering [<<1>>] was unsuccessful. <<2>> (<<3>>)'], '0635' : ['ERR', 'XeTeX error code [<<1>>] not understood by Rapuma.'], '0650' : ['ERR', 'Component type [<<1>>] not supported!'], '0690' : ['MSG', 'Dependent files unchanged, rerendering of [<<1>>] un-necessary.'], '0695' : ['MSG', 'Routing <<1>> to PDF viewer.'], '0700' : ['ERR', 'Rendered file not found: <<1>>'], '0710' : ['WRN', 'PDF viewing is disabled.'], '0720' : ['MSG', 'Saved rendered file to: [<<1>>]'], '0730' : ['ERR', 'Failed to save rendered file to: [<<1>>]'], '1000' : ['WRN', 'XeTeX debugging is set to [<<1>>]. These are the paths XeTeX is seeing: [<<2>>]'], '1090' : ['ERR', 'Invalid value [<<1>>] used for XeTeX debugging. Must use an integer of 0, 1, 2, 4, 8, 16, or 32'] } # FIXME: It would be good if we could do a check for dependent files here ############################################################################### ############################ Manager Level Functions ########################## ############################################################################### ######################## Error Code Block Series = 1000 ####################### ############################################################################### def checkStartPageNumber (self) : '''Adjust page number for the current group. The current logic is if there is no number in the startPageNumber setting, we can put one in there as a suggestion. If there is already one there, the user will be responsible for seeing that it is correct.''' # import pdb; pdb.set_trace() try : # Simply try to return anything that is in the field cStrPgNo = self.projectConfig['Groups'][self.gid]['startPageNumber'] if cStrPgNo != '' : return cStrPgNo except : # If nothing is there, we'll make a suggestion pGrp = str(self.projectConfig['Groups'][self.gid]['precedingGroup']) if pGrp == 'None' : self.projectConfig['Groups'][self.gid]['startPageNumber'] = 1 self.tools.writeConfFile(self.projectConfig) return '1' else : # Calculate the suggested number based on the preceeding group try : cStrPgNo = str(self.projectConfig['Groups'][self.gid]['startPageNumber']) except : cStrPgNo = 1 self.projectConfig['Groups'][self.gid]['startPageNumber'] = 1 try : pGrpPgs = int(self.projectConfig['Groups'][pGrp]['totalPages']) pGrpStrPgNo = int(self.projectConfig['Groups'][pGrp]['startPageNumber']) except : # FIXME: Maybe this could go out and find out exactly how many pages were in the preceeding group pGrpPgs = 1 pGrpStrPgNo = 1 self.projectConfig['Groups'][pGrp]['totalPages'] = 1 self.projectConfig['Groups'][pGrp]['startPageNumber'] = 1 # Whether this is right or wrong set it the way it is self.projectConfig['Groups'][self.gid]['startPageNumber'] = (pGrpStrPgNo + pGrpPgs) self.tools.writeConfFile(self.projectConfig) return self.projectConfig['Groups'][pGrp]['startPageNumber'] def makeExtFile (self, fileName, description) : '''Generic function to create an extension file if one does not already exist.''' if not os.path.exists(fileName) : with codecs.open(fileName, "w", encoding='utf_8') as writeObject : writeObject.write(self.tools.makeFileHeader(fileName, description, False)) self.log.writeToLog(self.errorCodes['1040'], [self.tools.fName(fileName)]) return True def makeCmpExtTexFileOn (self, fileName) : '''Create a component TeX extention macro "on" file for a specified component. A matching "off" file will be created as well.''' description = 'This is a component (on) TeX macro extension file which may override any macros \ which were loaded for this rendering process. This file is read just before the component \ working file. After the component is rendered, the accompanying off TeX file will be \ loaded which will turn off any modified macro commands that this TeX file has set. The \ user must edit this file in order for it to work right.' return self.makeExtFile(fileName, description) def makeCmpExtTexFileOff (self, fileName) : '''Create a component TeX extention macro "off" file for a specified component. This is to match the "on" file that was created.''' description = 'This is a component (off) style extension file which overrides the settings \ that were loaded for this rendering process just prior to loading the component working \ file. The commands in this style file will off-set the "on" settings causing the macro to \ render as it did before the "on" styles were loaded. The user must edit this file for it \ to work properly.' return self.makeExtFile(fileName, description) def makeCmpExtStyFileOn (self, fileName) : '''Create a component style extentions "on" file for a specified component. A matching "off" file will be created as well.''' description = 'This is a component (on) style extension file which overrides any settings \ which were loaded for this rendering process. This file is read just before the component \ working file. After the component is rendered, the accompanying off style file will be \ loaded which will turn off any modified style commands that this style file has set. The \ user must edit this file in order for it to work right.' return self.makeExtFile(fileName, description) def makeCmpExtStyFileOff (self, fileName) : '''Create a component style extentions "off" file for a specified component. This is to match the "on" file that was created.''' description = 'This is a component (off) style extension file which overrides the settings \ that were loaded for this rendering process just prior to loading the component working \ file. The commands in this style file will off-set the "on" settings causing the macro to \ render as it did before the "on" styles were loaded. The user must edit this file for it \ to work properly.' return self.makeExtFile(fileName, description) def makeGrpExtTexFile (self) : '''Create a group TeX extentions file for a specified group.''' description = 'This is the group TeX extention macro file which overrides settings in \ the global TeX extension macro file.' return self.makeExtFile(self.local.grpExtTexFile, description) def makeGrpExtStyFile (self) : '''Create a group Style extentions file to a specified group.''' description = 'This is the group style extention file which overrides settings in \ the main default component extentions settings style file.' return self.makeExtFile(self.local.grpExtStyFile, description) ############################################################################### ############################# DEPENDENCY FUNCTIONS ############################ ############################################################################### ######################## Error Code Block Series = 0400 ####################### ############################################################################### def makeSettingsTexFile (self) : '''Create the primary TeX settings file.''' # import pdb; pdb.set_trace() description = 'This is the primary TeX settings file for the ' + self.gid + ' group. \ It is auto-generated so editing can be a rather futile exercise. This is unless you \ set freezeTexSettings to True in the XeTeX manager configuration of the project.conf \ file. Doing that will prevent the file from being remade. However, no configuration \ changes will be reflected in the static settings file. Use this with care.' # Setting for internal testing outputTest = False # Check for freezeTexSettings in project.conf if self.projectConfig['Managers'][self.cType + '_Xetex'].has_key('freezeTexSettings') and \ self.tools.str2bool(self.projectConfig['Managers'][self.cType + '_Xetex']['freezeTexSettings']) : self.log.writeToLog(self.errorCodes['0420']) return False def appendLine(line, realVal) : '''Use this to shorten the code and look for listy things.''' if type(line) == list : for s in line : linesOut.append(self.proj_config.processNestedPlaceholders(s, realVal)) else : linesOut.append(self.proj_config.processNestedPlaceholders(line, realVal)) # Open a fresh settings file with codecs.open(self.local.macSettingsFile, "w", encoding='utf_8') as writeObject : writeObject.write(self.tools.makeFileHeader(self.local.macSettingsFileName, description)) # Build a dictionary from the default XML settings file # Create a dict that contains only the data we need here macPackDict = self.tools.xmlFileToDict(self.local.macPackConfXmlFile) for sections in macPackDict['root']['section'] : for section in sections : secItem = sections[section] linesOut = [] if type(secItem) is list : if outputTest : print sections['sectionID'] linesOut.append('% ' + sections['sectionID'].upper()) for setting in secItem : for k in setting.keys() : if k == 'texCode' : if outputTest : print '\t', setting['key'] realVal = self.macroConfig['Macros'][self.macPackId][sections['sectionID']][setting['key']] # Test any boolDepends that this setting might have if setting.has_key('boolDepend') : result = [] if type(setting['boolDepend']) == list : for i in setting['boolDepend'] : result.append(self.affirm(i)) else : result.append(self.affirm(setting['boolDepend'])) # If 'None' didn't end up in the list, that means # every bool tested good so we can output the line if None not in result : if outputTest : print '\t', setting.get(k) appendLine(setting['texCode'], realVal) # Normal setting output elif setting.get(k) : if setting.get(k) != None : # We filter out zero values here (But what if we need one of them?) if not self.proj_config.processNestedPlaceholders(realVal) == '0' : if outputTest : print '\t', setting.get(k) appendLine(setting['texCode'], realVal) # Only write out sections that have something in them if len(linesOut) > 1 : writeObject.write('\n') for line in linesOut : writeObject.write(line + '\n') # Continue here with injecting the font settings which are guided by # the config file because the XML source(s) could vary writeObject.write('\n% INSTALLED FONTS\n') installedFonts = self.fontConfig['Fonts'].keys() cTypeFont = self.projectConfig['CompTypes'][self.cType.capitalize()]['fontName'] for font in installedFonts : if font == cTypeFont : # Output the primary font for key in self.fontConfig['Fonts'][font]['TexMapping']['PrimaryFont'].keys() : writeObject.write(self.proj_config.processNestedPlaceholders(self.fontConfig['Fonts'][font]['TexMapping']['PrimaryFont'][key]) + '\n') # Output the seconday settings as well for this font for key in self.fontConfig['Fonts'][font]['TexMapping']['SecondaryFont'].keys() : writeObject.write(self.proj_config.processNestedPlaceholders(self.fontConfig['Fonts'][font]['TexMapping']['SecondaryFont'][key]) + '\n') else : # There can only be one primary font, this is not it for key in self.fontConfig['Fonts'][font]['TexMapping']['SecondaryFont'].keys() : writeObject.write(self.proj_config.processNestedPlaceholders(self.fontConfig['Fonts'][font]['TexMapping']['SecondaryFont'][key]) + '\n') writeObject.write('\n') # Die here if testing if outputTest : self.tools.dieNow() # Report finished if not return True def affirm (self, boolDependInfo) : '''Affirm by returning True if the actual bool matches its state setting. Returning 'None' will cause a setting to be skipped.''' realBool = self.returnConfRefValue(boolDependInfo['#text']).lower() if boolDependInfo['@state'].lower() == realBool.lower() : return True def returnConfRefValue (self, ref) : '''Return the value of a given config reference. The ref syntax is as follows: [config:configObj|section|key]. This should be able to recuse as deep as necessary.''' # import pdb; pdb.set_trace() ref = ref.lstrip('[').rstrip(']') (holderType, holderKey) = ref.split(':', 1) if holderType.lower() == 'config' : val = holderKey.split('|') dct = ['self.' + val[0]] val.remove(val[0]) for i in val : i = self.proj_config.processNestedPlaceholders(i, '') dct.append('["' + i + '"]') return eval(''.join(dct)) def makeGidTexFile (self, cidList) : '''Create the main gid TeX control file.''' description = 'This is the group TeX control file. XeTeX will \ read this file to get all of links to other instructions (macros) \ needed to render the group, or a component of a group.' # Since a render run could contain any number of components # in any order, we will remake this file on every run. No need # for dependency checking if os.path.exists(self.local.gidTexFile) : os.remove(self.local.gidTexFile) # Create the main TeX settings file (made on every run) self.makeSettingsTexFile() # Start writing out the gid.tex file. Check/make dependencies as we go. # If we fail to make a dependency it will die and report during that process. # We bring in each element in the order necessary with codecs.open(self.local.gidTexFile, "w", encoding='utf_8') as gidTexObject : # Write out the file header gidTexObject.write(self.tools.makeFileHeader(self.local.gidTexFileName, description)) # First bring in the main macro file gidTexObject.write('\\input \"' + self.local.primaryMacroFile + '\"\n') # Check for a preStyle extension file and load if it is there if os.path.exists(self.local.preStyTexExtFile) : gidTexObject.write('\\input \"' + self.local.preStyTexExtFile + '\"\n') ######## # FIXME? To avoid problems with the usfmTex marginalverses macro code, we bring # in the stylesheets now. Will this cause any problems with other macPacks? ######## # Load style files (default and extention come with the package) gidTexObject.write('\\stylesheet{' + self.local.defaultStyFile + '}\n') # Load the global style extensions gidTexObject.write('\\stylesheet{' + self.local.glbExtStyFile + '}\n') # Load the group style extensions (if needed) if self.projectConfig['Groups'][self.gid].has_key('useGrpStyOverride') and self.tools.str2bool(self.projectConfig['Groups'][self.gid]['useGrpStyOverride']) : self.makeGrpExtStyFile() gidTexObject.write('\\stylesheet{' + self.local.grpExtStyFile + '}\n') # Load the settings (usfmTex: if marginalverses, load code in this) gidTexObject.write('\\input \"' + self.local.macSettingsFile + '\"\n') # Load the TeX macro extensions for this macro package gidTexObject.write('\\input \"' + self.local.extTexFile + '\"\n') # Load the group TeX macro extensions (if needed) if self.projectConfig['Groups'][self.gid].has_key('useGrpTexOverride') and self.tools.str2bool(self.projectConfig['Groups'][self.gid]['useGrpTexOverride']) : self.makeGrpExtTexFile() gidTexObject.write('\\input \"' + self.local.grpExtTexFile + '\"\n') # Load hyphenation data if needed if self.useHyphenation : # This is the main hyphenation settings file, this must be loaded first gidTexObject.write('\\input \"' + self.proj_hyphenation.projHyphSetTexFile + '\"\n') # This is the character definition file for hyphenation, this should be loaded second gidTexObject.write('\\input \"' + self.proj_hyphenation.projHyphCharTexFile + '\"\n') # This is the exception words list (all the hyphenated words), this is loaded last gidTexObject.write('\\input \"' + self.proj_hyphenation.projHyphExcTexFile + '\"\n') # If this is less than a full group render, just go with default pg num (1) if cidList == self.projectConfig['Groups'][self.gid]['cidList'] : # Check if this setting is there startPageNumber = self.checkStartPageNumber() if startPageNumber > 1 : gidTexObject.write('\\pageno = ' + str(startPageNumber) + '\n') # Insert Document properties and x1a compliant info if needed gidTexObject.write(self.makeXoneACompliant()) # Now add in each of the components for cid in cidList : # Output files and commands for usfm cType if self.cType == 'usfm' : cidSource = os.path.join(self.local.projComponentFolder, cid, self.project.groups[self.gid].makeFileNameWithExt(cid)) cidTexFileOn = os.path.join(self.local.projTexFolder, self.gid + '-' + cid + '-On-ext.tex') cidTexFileOff = os.path.join(self.local.projTexFolder, self.gid + '-' + cid + '-Off-ext.tex') cidStyFileOn = os.path.join(self.local.projStyleFolder, self.gid + '-' + cid + '-On-ext.sty') cidStyFileOff = os.path.join(self.local.projStyleFolder, self.gid + '-' + cid + '-Off-ext.sty') # Check to see if a TeX macro override is needed if self.projectConfig['Groups'][self.gid].has_key('compTexOverrideList') and cid in self.projectConfig['Groups'][self.gid]['compTexOverrideList'] : self.makeCmpExtTexFileOn(cidTexFileOn) gidTexObject.write('\\input \"' + cidTexFileOn + '\"\n') # Check to see if a style override is needed (if so create "on" file) if self.projectConfig['Groups'][self.gid].has_key('compStyOverrideList') and cid in self.projectConfig['Groups'][self.gid]['compStyOverrideList'] : self.makeCmpExtStyFileOn(cidStyFileOn) gidTexObject.write('\\stylesheet{' + cidStyFileOn + '}\n') # Check for short books add omit statement if self.chapNumOffSingChap and self.cidChapNumDict[cid] == 1 : gidTexObject.write('\\OmitChapterNumbertrue\n') # Add the working file here gidTexObject.write('\\ptxfile{' + cidSource + '}\n') # Check again for short books turn off omit statement if self.chapNumOffSingChap and self.cidChapNumDict[cid] == 1 : gidTexObject.write('\\OmitChapterNumberfalse\n') # Check for for style override and add the "Off" style file here if self.projectConfig['Groups'][self.gid].has_key('compStyOverrideList') and cid in self.projectConfig['Groups'][self.gid]['compStyOverrideList'] : self.makeCmpExtStyFileOn(cidStyFileOff) gidTexObject.write('\\stylesheet{' + cidStyFileOff + '}\n') # Check for for TeX macro override and add the "Off" TeX file here if self.projectConfig['Groups'][self.gid].has_key('compTexOverrideList') and cid in self.projectConfig['Groups'][self.gid]['compTexOverrideList'] : self.makeCmpExtTexFileOff(cidTexFileOff) gidTexObject.write('\\input \"' + cidTexFileOff + '\"\n') else : self.log.writeToLog(self.errorCodes['0650'], [self.cType]) # This can only hapen once in the whole process, this marks the end gidTexObject.write('\\bye\n') return True def makeXoneACompliant (self) : '''Insert the necessary TeX code into the header to give the appearance of being PDF x1-a compliant. If the feature is turned off then it will only inject relevant document properties that are good to have included no mater what. XeTeX output is x1-a for the most part, it just doesn't brag about it. The output here mostly works. :-) ''' # import pdb; pdb.set_trace() allLines = '' # Set some of the vars for the output title = self.projectConfig['ProjectInfo']['projectTitle'] subject = self.projectConfig['ProjectInfo']['projectDescription'] author = ''.join(self.projectConfig['ProjectInfo']['translators']) creator = ''.join(self.projectConfig['ProjectInfo']['typesetters']) # I don't think this next bit is not right, what does +7 mean anyway? # It works for CDT time anyway, which I thought was -6 offSet = "+07\'00\'" # To get the date stamp right, we strip out all the non-number # characters so we are left with: yyyymmddhhmmss comp = self.projectConfig['ProjectInfo']['projectCreateDate'].replace('-', '').replace(':', '').replace(' ', '') cDate = 'D:' + comp + offSet mDate = 'D:' + self.tools.fullFileTimeStamp() + offSet lines = [ '\special{pdf:docinfo<<', '/Title(' + title + ')%', '/Subject(' + subject + ')%', '/Author(' + author + ')%', '/Creator(' + creator + ')%', '/CreationDate(' + cDate + ')%', '/ModDate(' + mDate + ')%', '/Producer(XeTeX with Rapuma)%', '/Trapped /False', '/GTS_PDFXVersion(PDF/X-1:2003)%', '/GTS_PDFXConformance(PDF/X-1a:2003)%', '>> }' ] # Add PDF header declairations for PDF X1-A:2003 compliance (default is True) if self.tools.str2bool(self.layoutConfig['DocumentFeatures']['pdfX1a']) : icc = os.path.join(self.local.rapumaConfigFolder, 'ps_cmyk.icc') # Now create the insert line list xtralines = [ '\special{pdf:fstream @OBJCVR (' + icc + ')}', '\special{pdf:put @OBJCVR <</N 4>>}', '%\special{pdf:close @OBJCVR}', '\special{pdf:docview <<', '/OutputIntents [ <<', '/Type/OutputIndent', '/S/GTS_PDFX', '/OutputCondition (An Unknown print device)', '/OutputConditionIdentifier (Custom)', '/DestOutputProfile @OBJCVR', '/RegistryName (http://www.color.og)', '>> ] >>}' ] lines = lines + xtralines # Whatever our output, process the lines for l in lines : allLines = allLines + l + ' \n' return allLines ############################################################################### ################################# Main Function ############################### ############################################################################### ######################## Error Code Block Series = 0600 ####################### ############################################################################### def run (self, gid, cidList, pgRange, override, save) : '''This will check all the dependencies for a group and then use XeTeX to render the whole group or a subset of components and even a page range in a single component.''' # import pdb; pdb.set_trace() # There must be a cidList. If one was not passed, default to # the group list cidListSubFileName = '' saveFile = '' saveFileName = '' if not cidList : cidList = self.projectConfig['Groups'][gid]['cidList'] else : # If there is a cidList, create an alternate ouput name. # This is so if the file is saved it will have a unique # name. the name needs to be ordered by ###-cid-gid. # We need to do this sooner than later. if len(cidList) > 1 : cidListSubFileName = '-'.join(cidList) else : cid = cidList[0] # Add a filler character to the ID cnid = "{:0>3}".format(self.cidPtIdDict[cid]) cidListSubFileName = cnid + '-' + cid # Create, if necessary, the gid.tex file # First, go through and make/update any dependency files self.makeSettingsTexFile() # Now make the gid main setting file self.makeGidTexFile(cidList) # Dynamically create a dependency list for the render process # Note: gidTexFile is remade on every run, do not test against that file dep = [self.local.extTexFile, self.local.projectConfFile, self.local.layoutConfFile, self.local.macroConfFile, self.local.illustrationConfFile, ] # Add component dependency files for cid in cidList : cidUsfm = self.project.groups[gid].getCidPath(cid) cidIlls = self.proj_illustration.getCidPiclistFile(cid) for f in [cidUsfm, cidIlls] : if os.path.exists(f) : dep.append(f) # Treat adjustment file separate if self.cType == 'usfm' : cidAdj = self.project.groups[gid].getCidAdjPath(cid) if os.path.exists(cidAdj) : dep.append(cidAdj) # Call the renderer # Create the environment that XeTeX will use. This will be temporarily set # by subprocess.call() just before XeTeX is run. texInputsLine = self.project.local.projHome + ':' \ + self.local.projStyleFolder + ':' \ + self.local.projTexFolder + ':' \ + self.local.projMacPackFolder + ':' \ + self.local.projMacroFolder + ':' \ + self.local.projGidFolder + ':.' # Create the environment dictionary that will be fed into subprocess.call() #envDict = dict(os.environ) envDict={} # These are project environment vars envDict['TEXINPUTS'] = texInputsLine # These are XeTeX environment vars that are run if the internal (fast) version # of XeTeX is being run, which is the default. If runExternalXetex is set to # False, the following special environment vars will be run. If set to true, # an external version of XeTeX, provided it is installed, will run with its own # environment vars set elsewhere runExternal = self.tools.str2bool(self.projectConfig['Managers'][self.cType + '_Xetex'].get('runExternalXetex', '')) if not runExternal : envDict['PATH'] = os.path.join(self.local.rapumaXetexFolder, 'bin', 'x86_64-linux') envDict['TEXMFCNF'] = os.path.join(self.local.rapumaXetexFolder, 'texmf-local', 'web2c') envDict['TEXFORMATS'] = os.path.join(self.local.rapumaXetexFolder, 'texmf-local', 'web2c', 'xetex') # To help with debugging the following hook has been added. This is not # something the user would ever use. It is only for developer diagnostics. # for infomation on what integers can be used refer to this URL: # http://www.dcs.ed.ac.uk/home/latex/Informatics/Obsolete/html/kpathsea/kpathsea.html debugXetex = self.projectConfig['Managers'][self.cType + '_Xetex'].get('debugKpse', None) if debugXetex : try : if int(debugXetex) > 0 : envDict['KPATHSEA_DEBUG'] = debugXetex self.log.writeToLog(self.errorCodes['1000'], [str(debugXetex), str(envDict)]) except : self.log.writeToLog(self.errorCodes['1090'], [debugXetex]) else : envDict.update(os.environ) # Create the XeTeX command argument list that subprocess.call() will run with # the environment vars we set above cmds = ['xetex', '-output-directory=' + self.local.projGidFolder, self.local.gidTexFile] # For debugging purposes, output the following DBG message if self.projectConfig['Managers'][self.cType + '_Xetex'].has_key('freezeTexSettings') and \ self.tools.str2bool(self.projectConfig['Managers'][self.cType + '_Xetex']['freezeTexSettings']) : self.log.writeToLog(self.errorCodes['0620'], [os.getcwd(), str(envDict), " ".join(cmds)]) # Run the XeTeX and collect the return code for analysis try : rCode = subprocess.call(cmds, env = envDict) # Analyse the return code if rCode == int(0) : self.log.writeToLog(self.errorCodes['0625'], [self.local.gidTexFileName]) elif rCode in self.xetexErrorCodes : self.log.writeToLog(self.errorCodes['0630'], [self.local.gidTexFileName, self.xetexErrorCodes[rCode], str(rCode)]) else : self.log.writeToLog(self.errorCodes['0635'], [str(rCode)]) except Exception as e : # If subprocess fails it might be because XeTeX did not execute # we will try to report back something useful self.log.writeToLog(self.errorCodes['0615'], [str(e)]) # Collect the page count and record in group (Write out at the end of the opp.) self.projectConfig['Groups'][gid]['totalPages'] = str(PdfFileReader(open(self.local.gidPdfFile)).getNumPages()) # Write out any changes made to the project.conf file that happened during this opp. self.tools.writeConfFile(self.projectConfig) # Pull out pages if requested (use the same file for output) if pgRange : self.tools.pdftkPullPages(self.local.gidPdfFile, self.local.gidPdfFile, pgRange) # The gidPdfFile is the residue of the last render and if approved, can be # used for the binding process. In regard to saving and file naming, the # gidPdfFile will be copied but never renamed. It must remain intact. # If the user wants to save this file or use a custom name, do that now if save and not override : saveFileName = self.pid + '_' + gid if cidListSubFileName : saveFileName = saveFileName + '_' + cidListSubFileName if pgRange : saveFileName = saveFileName + '_pg(' + pgRange + ')' # Add date stamp saveFileName = saveFileName + '_' + self.tools.ymd() # Add render file extention saveFileName = saveFileName + '.pdf' # Save this to the Deliverable folder (Make sure there is one) if not os.path.isdir(self.local.projDeliverableFolder) : os.makedirs(self.local.projDeliverableFolder) # Final file name and path saveFile = os.path.join(self.local.projDeliverableFolder, saveFileName) # Copy, no news is good news if shutil.copy(self.local.gidPdfFile, saveFile) : self.log.writeToLog(self.errorCodes['0730'], [saveFileName]) else : self.log.writeToLog(self.errorCodes['0720'], [saveFileName]) # If given, the override file name becomes the file name if override : saveFile = override # With shutil.copy(), no news is good news if shutil.copy(self.local.gidPdfFile, saveFile) : self.log.writeToLog(self.errorCodes['0730'], [saveFileName]) else : self.log.writeToLog(self.errorCodes['0720'], [saveFileName]) # Once we know the file is successfully generated, add a background if defined viewFile = '' if self.useBackground : if saveFile : viewFile = self.pg_back.addBackground(saveFile) else : viewFile = self.pg_back.addBackground(self.local.gidPdfFile) # Add a timestamp and doc info if requested in addition to background if self.useDocInfo : if saveFile : if os.path.isfile(viewFile) : viewFile = self.pg_back.addDocInfo(viewFile) else : viewFile = self.pg_back.addDocInfo(saveFile) else : if os.path.isfile(viewFile) : viewFile = self.pg_back.addDocInfo(viewFile) else : viewFile = self.pg_back.addDocInfo(self.local.gidPdfFile) # Add a diagnostic layer to the rendered output. Normally this is # not used with a normal background layer if self.useDiagnostic : if saveFile : viewFile = self.fmt_diagnose.addTransparency(saveFile) else : viewFile = self.fmt_diagnose.addTransparency(self.local.gidPdfFile) # To avoid confusion with file names, if this is a saved file, # and it has a background, we need to remove the original, non- # background file (remembering originals are kept in the group # Component folder), then rename the -view version to whatever # the saved name should be if save or override : if os.path.isfile(saveFile) and os.path.isfile(viewFile) : # First remove os.remove(saveFile) # Next rename os.rename(viewFile, saveFile) ##### Viewing ##### # First get the right file name to view if saveFile : # If there was a saveFile, that will be the viewFile viewFile = saveFile else : # The view file in this case is just temporary if not os.path.isfile(viewFile) : viewFile = self.local.gidPdfFile.replace(gid + '.pdf', gid + '-view.pdf') shutil.copy(self.local.gidPdfFile, viewFile) # Now view it if os.path.isfile(viewFile) : if self.pdfViewerCmd : # Add the file to the viewer command self.pdfViewerCmd.append(viewFile) # Run the XeTeX and collect the return code for analysis try : subprocess.Popen(self.pdfViewerCmd) return True except Exception as e : # If we don't succeed, we should probably quite here self.log.writeToLog(self.errorCodes['1005'], [str(e)]) else : self.log.writeToLog(self.errorCodes['0710']) else : self.log.writeToLog(self.errorCodes['0700'], [self.tools.fName(viewFile)]) # If we made it this far, return True return True
class ProjFont (object) : def __init__(self, pid) : '''Do the primary initialization for this class.''' self.pid = pid self.tools = Tools() self.user = UserConfig() self.log = ProjLog(pid) self.userConfig = self.user.userConfig self.proj_config = Config(pid) self.proj_config.getProjectConfig() self.projectConfig = self.proj_config.projectConfig self.proj_config.getFontConfig() self.fontConfig = self.proj_config.fontConfig self.local = ProjLocal(pid, self.projectConfig) # Load all font settings for use in this module if self.fontConfig : for k, v in self.fontConfig['GeneralSettings'].iteritems() : setattr(self, k, v) # Log messages for this module self.errorCodes = { 'FONT-000' : ['MSG', 'Font module messages'], 'FONT-005' : ['MSG', 'FONT-005 - Unassigned error message ID.'], 'FONT-015' : ['MSG', 'FONT-015 - Unassigned error message ID.'], 'FONT-020' : ['ERR', 'Failed to find font setting in ParaTExt project (.ssf file). A primary font must be set before this component can be successfully rendered.'], 'FONT-025' : ['ERR', 'No source editor was found for this project. Please enter this setting before continuing.'], 'FONT-042' : ['MSG', 'The [<<1>>] font setup information was added to project config'], 'FONT-050' : ['ERR', 'Halt! [<<1>>] not found. - font.copyInFont()'], 'FONT-070' : ['LOG', 'Copied the [<<1>>] font file into the project. - proj_font.copyInFont()'], 'FONT-100' : ['ERR', 'This function has not been implemented yet!. - proj_font.setGlyphMap()'], '0010' : ['LOG', 'Wrote out new font configuration (font.__init__())'], '1220' : ['ERR', 'The Font bundle file [<<1>>] could not be found. Process halted.'], '1235' : ['MSG', 'Font [<<1>>] has been installed into the project.'], '1237' : ['MSG', 'Font [<<1>>] has been updated.'], '1240' : ['ERR', 'Font bundle file [<<1>>] not found.'], '1241' : ['ERR', 'Font bundle [<<1>>] not found.'], '1245' : ['LOG', '<<1>> font setup information added to project config'], '1250' : ['ERR', 'The [<<1>>] font is apparently part of this project. Please remove before trying to re-add this font.'], '1260' : ['MSG', 'The <<1>> font bundle has been copied into the project font folder.'], '1262' : ['LOG', 'The <<1>> font bundle already exsits in the font folder.'], '1265' : ['ERR', 'Failed to extract the [<<1>>] font bundle into the project. Font install process failed.'], '1267' : ['LOG', 'The <<1>> font bundle has been copied into the project font folder.'], '1280' : ['MSG', 'Failed to install the font: [<<1>>] into the project.'], '1370' : ['LOG', 'Removed [<<1>>] font name from project component type: [<<2>>].'], '1380' : ['MSG', 'Removed the [<<1>>] font from the project.'], '1382' : ['MSG', 'Force switch was set (-f). This process has completely removed the [<<1>>] font and settings from the project. - proj_font.removeFont()'], '1390' : ['LOG', 'Removed the [<<1>>] font package.'], '1395' : ['MSG', 'Could not remove the [<<1>>] font package. It may be used by another group. Use force (-f) to remove the package from the font folder.'] } ############################################################################### ############################ Project Level Functions ########################## ############################################################################### ######################## Error Code Block Series = 1000 ####################### ############################################################################### def setGlyphMap (self, cType, font) : '''If needed, set the glyph map used for this component type font.''' self.log.writeToLog('FONT-100') def getFontIdFromFileName (self, fileName) : '''Return the font ID based on the file name''' # File name less ext is the font ID parts = len(fileName.split('.')) return '.'.join(fileName.split('.')[:parts-1]) def getFontIdFromSource (self, source) : '''Return the font ID based on the complete path and file name.''' # Get the file name from the path fileName = self.tools.fName(source) # Return the font ID return self.getFontIdFromFileName(fileName) def recordFont (self, fontId, cType=None) : '''Check for the exsitance of the specified font in the font folder. Then extract the meta data into the appropreate configurations.''' # import pdb; pdb.set_trace() # Set vars do initial checks metaDataSource = os.path.join(self.local.projFontFolder, fontId, fontId + '.xml') if not os.path.isfile(metaDataSource) : self.log.writeToLog(self.errorCodes['1240'], [fontId + '.xml', 'proj_font.recordFont():1240']) # Build the Fonts section in the config (if needed) self.tools.buildConfSection(self.fontConfig, 'Fonts') # (Re)Inject the font info into the macPack config file. fInfo = self.tools.getXMLSettings(metaDataSource) self.fontConfig['Fonts'][fontId] = fInfo.dict() # Save the settings now self.tools.writeConfFile(self.fontConfig) # If a component type was specified, record that as well if cType : self.projectConfig['CompTypes'][cType.capitalize()]['fontName'] = fontId self.tools.writeConfFile(self.projectConfig) return True def copyInFont (self, source) : '''Copy a font into a project. The font is bundled with other necessary components in a .zip file. If the font folder is already there we assume there is a font there and we do not proceed. The user will be prompted to remove the old one first.''' fontId = self.getFontIdFromSource(source) confXml = os.path.join(self.local.projFontFolder, fontId, fontId + '.xml') if not os.path.isfile(source) : self.log.writeToLog(self.errorCodes['1220'], [source]) # Install new copy if self.tools.pkgExtract(source, self.local.projFontFolder, confXml) : self.log.writeToLog(self.errorCodes['1260'], [self.tools.fName(source)]) return True else : self.log.writeToLog(self.errorCodes['1265'], [fontId]) return False def addFont (self, source, cType=None) : '''It is a three step process to install a font. This will both copy in a font and record it in one call. Do not try to install a substitute font. Path is assumed to exsist and contains the file name too.''' # import pdb; pdb.set_trace() fontId = self.getFontIdFromSource(source) # Check for existance, never copy over if self.isFont(fontId) : self.log.writeToLog(self.errorCodes['1250'], [fontId]) # Now install and record if self.copyInFont(source) and self.recordFont(fontId, cType) : self.log.writeToLog(self.errorCodes['1235'], [fontId]) return True else : self.log.writeToLog(self.errorCodes['1280'], [fontId]) return False def updateFontPack (self, source, cType=None) : '''Update a font package but do not change any of the existing settings. If there are setting issues (changes) it would be best to remove, then reinstall. A potential confusion point is that updating a font normally means changing the ID. Then updating actually becomes just installing a font.''' # import pdb; pdb.set_trace() # Get the font ID fontId = self.getFontIdFromSource(source) # Be sure the font is in the project if self.isFont(fontId) : # Remove old copy if os.path.exists(os.path.join(self.local.projFontFolder, fontId)) : shutil.rmtree(os.path.join(self.local.projFontFolder, fontId)) # Bring in a fresh copy if self.copyInFont(source) : # If a component type was specified, record that as well if cType : self.projectConfig['CompTypes'][cType.capitalize()]['fontName'] = fontId self.tools.writeConfFile(self.projectConfig) self.log.writeToLog(self.errorCodes['1237'], [fontId]) return True def removeFontSettings (self, fontId) : '''Remove the font settings from the project.''' # import pdb; pdb.set_trace() if self.fontConfig.has_key('Fonts') : if self.fontConfig['Fonts'].has_key(fontId) : del self.fontConfig['Fonts'][fontId] # Write out the new settings files self.tools.writeConfFile(self.fontConfig) self.log.writeToLog(self.errorCodes['1380'], [fontId]) # Check to see if this font is listed in any of the cTypes # If so, remove it. found = 0 for cType in self.projectConfig['CompTypes'].keys() : # It might be possible that the cType has no font settings, so we check if self.projectConfig['CompTypes'][cType.capitalize()].has_key('fontName') : if self.projectConfig['CompTypes'][cType.capitalize()]['fontName'] == fontId : self.projectConfig['CompTypes'][cType.capitalize()]['fontName'] = '' self.log.writeToLog(self.errorCodes['1370'], [fontId, cType]) found +=1 if found > 0 : self.tools.writeConfFile(self.projectConfig) return True def removeFontPack (self, fontId) : '''Remove a font from a component type which will virtually disconnect it from the calling component type. However, if the force switch is set, then remove the font, regardless as to if it is used by another component or not. This is useful for purging a font from a project but should be used with care.''' # Purge the font files and folder fontDir = os.path.join(self.local.projFontFolder, fontId) if os.path.exists(fontDir) : shutil.rmtree(fontDir) self.log.writeToLog(self.errorCodes['1390'], [fontId]) if self.removeFontSettings(fontId) : return True ############################################################################### ############################ Font Settings Functions ########################## ############################################################################### ######################## Error Code Block Series = 2000 ####################### ############################################################################### def isFont (self, fontId) : '''Return if it is varified that this font is part of the project.''' # First do a cusory check to see if at least the font folder is there fontDir = os.path.join(self.local.projFontFolder, fontId) if not os.path.exists(fontDir) : return False # If the above passed, check in the font config to see if it is listed if not self.fontConfig['Fonts'].has_key(fontId) : return False # If the two tests above passed it is probably there return True