class Pdftk (Manager) : # Shared values xmlConfFile = 'pdftk.xml' def __init__(self, project, cfg, cType) : '''Do the primary initialization for this manager.''' super(Pdftk, 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 = 'pdftk' 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() # Get config objs self.projectConfig = self.proj_config.projectConfig self.layoutConfig = self.proj_config.layoutConfig self.userConfig = self.project.userConfig # Some config settings self.pdfViewerCmd = self.tools.getPdfViewerCommand(self.userConfig, self.projectConfig) self.useBackground = self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useBackground']) 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) # Make any dependent folders if needed if not os.path.isdir(self.local.projGidFolder) : os.makedirs(self.local.projGidFolder) # Log messages for this module self.errorCodes = { '0000' : ['MSG', 'Messages for the pdftk module.'], '5010' : ['ERR', 'Subprocess failed with this error: <<1>>'], '5020' : ['MSG', 'PDF merge was successful. Output = <<1>>'], '5200' : ['ERR', 'Rendered file not found: <<1>>'], '5210' : ['WRN', 'PDF viewing is disabled.'], '5720' : ['MSG', 'Saved rendered file to: [<<1>>]'], '5730' : ['ERR', 'Failed to save rendered file to: [<<1>>]'] } ############################################################################### ############################ Manager Level Functions ########################## ############################################################################### ######################## Error Code Block Series = 1000 ####################### ############################################################################### def sourceListFromCidList (self, cidList) : '''Return a sourceList derived from a given cidList.''' sourceList = [] for cid in cidList : sourceList.append(self.sourceFromCid(cid)) return sourceList def sourceFromCid (self, cid) : '''Return the full path with file name derived from a valid cid.''' return os.path.join(self.local.projComponentFolder, cid, cid + '.pdf') ############################################################################### ################################# Main Function ############################### ############################################################################### ######################## Error Code Block Series = 5000 ####################### ############################################################################### def run (self, gid, cidList, pgRange, override, save) : '''This will check all the dependencies for a group and then use pdftk 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 = '' viewFile = '' if not cidList : cidList = self.projectConfig['Groups'][gid]['cidList'] # Make a list of files that pdftk will merge together sourceList = self.sourceListFromCidList(cidList) # Merge the files cmd = ['pdftk'] + sourceList + ['cat', 'output', self.local.gidPdfFile] # import pdb; pdb.set_trace() # No return from pdftk is good, we can continue on if not subprocess.call(cmd) : # 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['5730'], [saveFileName]) else : self.log.writeToLog(self.errorCodes['5720'], [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['5730'], [saveFileName]) else : self.log.writeToLog(self.errorCodes['5720'], [saveFileName]) # Once we know the file is successfully generated, add a background if defined 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) # 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 self.log.writeToLog(self.errorCodes['5020'], [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) self.log.writeToLog(self.errorCodes['5020'], [self.tools.fName(viewFile)]) 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['5010'], [str(e)]) else : self.log.writeToLog(self.errorCodes['5210']) else : self.log.writeToLog(self.errorCodes['5200'], [self.tools.fName(viewFile)]) # If we made it this far, return True return True
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 ProjBackground (object) : def __init__(self, pid, gid = None) : '''Intitate the whole class and create the object.''' # import pdb; pdb.set_trace() self.pid = pid self.gid = gid self.local = ProjLocal(pid, gid) self.tools = Tools() self.proj_config = Config(pid, gid) self.proj_config.getProjectConfig() self.proj_config.getLayoutConfig() self.projectConfig = self.proj_config.projectConfig self.layoutConfig = self.proj_config.layoutConfig self.log = ProjLog(pid) self.user = UserConfig() self.userConfig = self.user.userConfig self.projHome = os.path.join(os.environ['RAPUMA_PROJECTS'], self.pid) self.mmToPx = 72 / 25.4 # For debugging purposes a switch can be set here for verbose # message output via the terminal self.debugMode = self.tools.str2bool(self.userConfig['System']['debugging']) # Log messages for this module self.errorCodes = { '0000' : ['MSG', 'Placeholder message'], '1110' : ['MSG', 'File exsits: [<<1>>]. Use \"force\" to remove it.'], '1280' : ['ERR', 'Failed to merge background file with command: [<<1>>]. This is the error: [<<2>>]'], '1290' : ['ERR', 'Failed to convert background file [<<1>>]. Error: [<<2>>] The command was: [<<3>>]'], '1300' : ['MSG', 'Background merge operation in process, please wait...'], '1305' : ['MSG', 'Adding document information, please wait...'], '1310' : ['WRN', 'Failed to add background component: [<<1>>] with error: [<<2>>]'], '1320' : ['MSG', 'New background created.'] } ############################################################################### ############################### Basic Functions ############################### ############################################################################### ######################## Error Code Block Series = 1000 ####################### ############################################################################### def turnOnBackground (self) : '''Change the layout config settings to turn on the background.''' if not self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useBackground']) : self.layoutConfig['DocumentFeatures']['useBackground'] = True self.tools.writeConfFile(self.layoutConfig) def turnOffBackground (self) : '''Change the layout config settings to turn off the background.''' if self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useBackground']) : self.layoutConfig['DocumentFeatures']['useBackground'] = False self.tools.writeConfFile(self.layoutConfig) def turnOnDocInfo (self) : '''Change the layout config settings to turn on the doc info in the background.''' if not self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useDocInfo']) : self.layoutConfig['DocumentFeatures']['useDocInfo'] = True self.tools.writeConfFile(self.layoutConfig) def turnOffDocInfo (self) : '''Change the layout config settings to turn off the doc info in the background.''' if self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useDocInfo']) : self.layoutConfig['DocumentFeatures']['useDocInfo'] = False self.tools.writeConfFile(self.layoutConfig) def addBackground (self, target) : '''Add a background (watermark) to a rendered PDF file. This will figure out what the background is to be composed of and create a master background page. Using force will cause it to be remade.''' # Do a quick check if the background needs to be remade # The background normally is not remade if one already exists. # If one is there, it can be remade if regenerate is set to # to True. Obviously, if one is not there, it will be made. if self.tools.str2bool(self.layoutConfig['DocumentFeatures']['regenerateBackground']) : self.createBackground() else : # If there isn't one, make it if not os.path.exists(self.local.backgroundFile) : self.createBackground() # Merge target with the project's background file in the Illustraton folder self.log.writeToLog(self.errorCodes['1300']) # Create a special name for the file with the background # Then merge and save it viewFile = self.tools.alterFileName(target, 'view') shutil.copy(self.tools.mergePdfFilesPdftk(self.centerOnPrintPage(target), self.local.backgroundFile), viewFile) # Not returning a file name would mean it failed if os.path.exists(viewFile) : return viewFile def addDocInfo (self, target) : '''Add (merge) document information to the rendered target doc.''' # Initialize the process docInfoText = self.layoutConfig['DocumentFeatures']['docInfoText'] timestamp = self.tools.tStamp() if self.gid : headerLine = self.pid + ' / ' + self.gid + ' / ' + timestamp else : headerLine = self.pid + ' / ' + timestamp svgFile = tempfile.NamedTemporaryFile().name ## RENDERED PAGE DIMENSIONS (body) # This can be determined with the pyPdf element # "pdf.getPage(0).mediaBox", which returns a # RectangleObject([0, 0, Width, Height]). The # width and height are in points. Hopefully we will # always be safe by measuring the gidPdfFile size. #pdf = PdfFileReader(open(self.local.gidPdfFile,'rb')) #var2 = pdf.getPage(0).mediaBox #trimWidth = float(var2.getWidth()) #trimHeight = float(var2.getHeight()) trimWidth = float(self.layoutConfig['PageLayout']['pageWidth']) trimHeight = float(self.layoutConfig['PageLayout']['pageHeight']) # Printer page size pps = self.printerPageSize() ppsWidth = pps[0] ppsHeight = pps[1] ppsCenter = ppsWidth/2 # Write out SVG document text with codecs.open(svgFile, 'wb') as fbackgr : # open file for writing fbackgr.write( '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width = "''' + str(ppsWidth) + '''" height = "''' + str(ppsHeight) + '''"> <g><text x = "''' + str(ppsCenter) + '''" y = "''' + str(20) + '''" style="font-family:DejaVu Sans;font-style:regular;font-size:8;text-anchor:middle;fill:#000000;fill-opacity:1">''' + headerLine + '''</text></g> <g><text x = "''' + str(ppsCenter) + '''" y = "''' + str(ppsHeight-30) + '''" style="font-family:DejaVu Sans;font-style:regular;font-size:8;text-anchor:middle;fill:#000000;fill-opacity:1">''' + self.tools.fName(target) + '''</text></g> <g><text x = "''' + str(ppsCenter) + '''" y = "''' + str(ppsHeight-20) + '''" style="font-family:DejaVu Sans;font-style:regular;font-size:8;text-anchor:middle;fill:#000000;fill-opacity:1">''' + docInfoText + '''</text></g> </svg>''') # Merge target with the background self.log.writeToLog(self.errorCodes['1305']) # Create a special name for the file with the background viewFile = self.tools.alterFileName(target, 'view') # Compare the new file name with the target to see if we are # already working with a background file if viewFile == target : # If the target is a BG file all we need to do is merge it self.tools.mergePdfFilesPdftk(viewFile, self.tools.convertSvgToPdfRsvg(svgFile)) else : # If not a BG file, we need to be sure the target is the same # size as the print page to merge with pdftk shutil.copy(self.tools.mergePdfFilesPdftk(self.centerOnPrintPage(target), self.tools.convertSvgToPdfRsvg(svgFile)), viewFile) # Not returning a file name would mean it failed if os.path.exists(viewFile) : return viewFile ##### Background Creation Functions ##### def createBackground (self) : '''Create a background file. This will overwrite any existing background file and will add each recognized background type found in the bacgroundComponents config setting.''' self.createBlankBackground() # Add each component to the blank background file for comp in self.layoutConfig['DocumentFeatures']['backgroundComponents'] : try : getattr(self, 'merge' + comp.capitalize())() except Exception as e : self.log.writeToLog(self.errorCodes['1310'],[comp,str(e)]) pass self.log.writeToLog(self.errorCodes['1320']) return True def createBlankBackground (self) : '''Create a blank background page according to the print page size specified.''' # Set the temp svg file name svgFile = tempfile.NamedTemporaryFile().name # Printer page size pps = self.printerPageSize() ppsWidth = pps[0] ppsHeight = pps[1] # Be sure there is an illustrations folder in place if not os.path.isdir(self.local.projIllustrationFolder) : os.mkdir(self.local.projIllustrationFolder) # Write out SVG document text with codecs.open(svgFile, 'wb') as fbackgr : # open file for writing fbackgr.write( '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width = "''' + str(ppsWidth) + '''" height = "''' + str(ppsHeight)+ '''"> </svg>''') shutil.copy(self.tools.convertSvgToPdfRsvg(svgFile), self.local.backgroundFile) def mergeWatermark (self) : '''Create a watermark file and return the file name. Using force will cause any exsiting versions to be recreated.''' # import pdb; pdb.set_trace() # Initialize the process pubProg = "Rapuma" watermarkText = self.layoutConfig['DocumentFeatures']['watermarkText'] svgFile = tempfile.NamedTemporaryFile().name ## RENDERED PAGE DIMENSIONS (body) # Trim page width [px] trimWidth = round(self.mmToPx * float(self.layoutConfig['PageLayout']['pageWidth']),1) # Trim page height [px] trimHeight = round(self.mmToPx * float(self.layoutConfig['PageLayout']['pageHeight']),1) # Printer page size [px] (ppsWidth, ppsHeight) = self.printerPageSize() pageX = ppsWidth/2 pageY = ppsHeight/2 # Write out SVG document text with codecs.open(svgFile, 'wb') as fbackgr : # open file for writing fbackgr.write( '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width = "''' + str(ppsWidth) + '''" height = "''' + str(ppsHeight) + '''"> <g><text x = "''' + str(pageX-trimWidth*.42) + '''" y = "''' + str(pageY-trimHeight*0.39) + '''" style="font-family:DejaVu Sans;font-style:regular;font-size:32;text-anchor:start;fill:#d5d5f5;fill-opacity:1">''' + str(pubProg) + ''' <tspan x = "''' + str(pageX) + '''" y = "''' + str(pageY - trimHeight*0.22) + '''" style="text-anchor:middle">''' + str(pubProg) + '''</tspan> <tspan x = "''' + str(pageX+trimWidth*0.42)+ '''" y = "''' + str(pageY-trimHeight*0.05) + '''" style="text-anchor:end">''' + str(pubProg) + '''</tspan> <tspan x = "''' + str(pageX) + '''" y = "''' + str(pageY+trimHeight*0.12) + '''" style="text-anchor:middle">''' + str(pubProg) + '''</tspan> <tspan x = "''' + str(pageX-trimWidth*0.42) + '''" y = "''' + str(pageY+trimHeight*0.29) + '''" style="text-anchor:start">''' + str(pubProg) + '''</tspan> <tspan x = "''' + str(pageX+trimWidth*0.49) + '''" y = "''' + str(pageY+trimHeight*0.455) + '''" style="font-weight:bold;font-size:68;text-anchor:end">''' + watermarkText + ''' </tspan> </text></g></svg>''') # Convert the temp svg to pdf and merge into backgroundFile results = self.tools.mergePdfFilesPdftk(self.local.backgroundFile, self.tools.convertSvgToPdfRsvg(svgFile)) if os.path.isfile(results) : return True def mergeCropmarks (self) : '''Merge cropmarks on to the background page.''' # Initialize the process svgFile = tempfile.NamedTemporaryFile().name ## RENDERED PAGE DIMENSIONS (body) # Trim page width [px] trimWidth = round(self.mmToPx * float(self.layoutConfig['PageLayout']['pageWidth']),1) # Trim page height [px] trimHeight = round(self.mmToPx * float(self.layoutConfig['PageLayout']['pageHeight']),1) # Printer page size [px] pps = self.printerPageSize() ppsWidth = pps[0] ppsHeight = pps[1] with codecs.open(svgFile, 'wb') as fbackgr : # starting lines of SVG xml fbackgr.write( '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width = "''' + str(ppsWidth) + '''" height = "''' + str(ppsHeight) + '''">\n''') # vertical top left fbackgr.write( '''<path d = "m''' + str((ppsWidth - trimWidth)/2) + ''',''' + str((ppsHeight - trimHeight)/2 - 32.0) + ''',v27," style="stroke:#000000;stroke-width:.2"/>\n''') # vertical bottom left fbackgr.write( '''<path d = "m''' + str((ppsWidth - trimWidth)/2) + ''',''' + str((ppsHeight + trimHeight)/2 + 5.0) + ''',v27" style="stroke:#000000;stroke-width:.2" />\n''') # vertical bottom right fbackgr.write( '''<path d = "m''' + str((ppsWidth + trimWidth)/2) + ''',''' + str((ppsHeight - trimHeight)/2 - 32.0) + ''',v27" style="stroke:#000000;stroke-width:.2"/>\n''') # vertical top right fbackgr.write( '''<path d = "m''' + str((ppsWidth + trimWidth)/2) + ''',''' + str((ppsHeight + trimHeight)/2 + 5.0) + ''',v27" style="stroke:#000000;stroke-width:.2" />\n''') # horzontal top left fbackgr.write( '''<path d =" m''' + str((ppsWidth - trimWidth)/2 - 32.0) + ''',''' + str((ppsHeight - trimHeight)/2) + ''',h27" style="stroke:#000000;stroke-width:.2" />\n''') # horzontal top right fbackgr.write( '''<path d =" m''' + str((ppsWidth + trimWidth)/2 + 5.0) + ''',''' + str((ppsHeight - trimHeight)/2) + ''',h27" style="stroke:#000000;stroke-width:.2" />\n''') # horzontal bottom right fbackgr.write( '''<path d =" m''' + str((ppsWidth - trimWidth)/2 - 32.0) + ''',''' + str((ppsHeight + trimHeight)/2) + ''',h27" style="stroke:#000000;stroke-width:.2" />\n''') # horzontal bottom left fbackgr.write( '''<path d =" m''' + str((ppsWidth + trimWidth)/2 +5.0) + ''',''' + str((ppsHeight + trimHeight)/2) + ''',h27" style="stroke:#000000;stroke-width:.2" />\n''') fbackgr.write( '''</svg>''') # Convert the temp svg to pdf and merge into backgroundFile results = self.tools.mergePdfFilesPdftk(self.local.backgroundFile, self.tools.convertSvgToPdfRsvg(svgFile)) if os.path.isfile(results) : return True def mergePagebox (self) : '''Merge a page box with the background page to be used for proof reading.''' # Initialize the process svgFile = tempfile.NamedTemporaryFile().name ## RENDERED PAGE DIMENSIONS (body) # Trim page width [px] trimWidth = round(self.mmToPx * float(self.layoutConfig['PageLayout']['pageWidth']),1) # Trim page height [px] trimHeight = round(self.mmToPx * float(self.layoutConfig['PageLayout']['pageHeight']),1) # Printer page size [px] pps = self.printerPageSize() ppsWidth = pps[0] ppsHeight = pps[1] with codecs.open(svgFile, 'wb') as fbackgr : # starting lines of SVG xml fbackgr.write( '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width = "''' + str(ppsWidth) + '''" height = "''' + str(ppsHeight) + '''">''') # rectangle fbackgr.write( '''<rect x = "''' + str((ppsWidth - trimWidth)/2) + '''" y= "''' + str((ppsHeight - trimHeight)/2) + '''" height = "''' + str(trimHeight) + '''" width = "''' + str(trimWidth) + '''" style = "fill:none;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:.2"/> </svg>''') # Convert the temp svg to pdf and merge into backgroundFile results = self.tools.mergePdfFilesPdftk(self.local.backgroundFile, self.tools.convertSvgToPdfRsvg(svgFile)) if os.path.isfile(results) : return True # Depricated #def makeBgFileName (self, orgName) : #'''Alter the file name to reflect the fact it has a background #added to it. This assumes the file only has a single extention. #If that's not the case we're hosed.''' #name = orgName.split('.')[0] #ext = orgName.split('.')[1] ## Just in case this is the second pass #name = name.replace('-bg', '') #return name + '-bg.' + ext def centerOnPrintPage (self, contents) : '''Center a PDF file on the printerPageSize page. GhostScript is the only way to do this at this point.''' # import pdb; pdb.set_trace() tmpFile = tempfile.NamedTemporaryFile().name # Get the size of our printer page (ppsWidth, ppsHeight) = self.printerPageSize() (wo, ho) = self.getPageOffset() pageOffset = str(wo) + ' ' + str(ho) # Assemble the GhostScript command cmd = [ 'gs', '-o', tmpFile, '-sDEVICE=pdfwrite', '-dQUIET', '-dDEVICEWIDTHPOINTS=' + str(ppsWidth), '-dDEVICEHEIGHTPOINTS=' + str(ppsHeight), '-dFIXEDMEDIA', '-c', '<</PageOffset [' + str(pageOffset) + ']>>', 'setpagedevice', '-f', contents] if self.debugMode : self.tools.terminal('Debug Mode On: \centerOnPrintPage() command: ' + str(cmd)) # Run the process try: subprocess.call(cmd) # Return the name of the temp PDF return tmpFile except Exception as e : self.log.writeToLog(self.errorCodes['1280'], [str(cmd), str(e)]) # Return the temp file name for further processing def printerPageSize (self) : '''Return the width and height of the printer page size in points. Only US Letter and A4 are supported. If not specified return the page trim size.''' printerPageSizeCode = self.layoutConfig['PageLayout']['printerPageSizeCode'].lower() ## STANDARD PRINTER PAGE SIZES # The output page (printer page) is what the typeset page will be placed on # with a watermark behind it. There are only two page sizes supported. # They are A4 and US Letter. We will determine the size by the ID code # found in the layout.conf file. However, in some cases, for example # during the layout process, the trim size is what is needed. If # one of the two supported pages sizes are not used, this will defult # to the trim size. if printerPageSizeCode == 'a4' : return float(595), float(842) elif printerPageSizeCode == 'letter' : return float(612), float(792) else : # Just default to the page trim size (assumed mm coming in) factor mmToPx = 72 / 25.4 return float(int(self.layoutConfig['PageLayout']['pageWidth']) * self.mmToPx), float(int(self.layoutConfig['PageLayout']['pageHeight']) * self.mmToPx) def getPageOffset (self) : '''Return the amount of horizontal and vertical offset that will enable the page trim size to be centered on the printer page. If something other than A4 or Letter is being used, the offset returned will be zero as this only supports those two sizes. The offset is based on the starting point being the lower-left side corner of the page. ''' # Get the printer page size printerPageSizeCode = self.layoutConfig['PageLayout']['printerPageSizeCode'].lower() # Get the page trim size (assuming mm input)factor mmToPx = 72 / 25.4 trimWidth = int(self.layoutConfig['PageLayout']['pageWidth']) * self.mmToPx trimHeight = int(self.layoutConfig['PageLayout']['pageHeight']) * self.mmToPx # The trim size of the content page can never be bigger than # the printer page size. If so, the offset is 0 if printerPageSizeCode == 'a4' or printerPageSizeCode == 'letter' : (bw, bh) = self.printerPageSize() wo = float((bw/2)-(trimWidth/2)) ho = float((bh/2)-(trimHeight/2)) return wo, ho else : return 0, 0
class ProjBinding (object) : def __init__(self, pid) : '''Do the primary initialization for this manager.''' self.pid = pid self.tools = Tools() self.user = UserConfig() self.userConfig = self.user.userConfig self.config = Config(pid) self.pg_back = ProjBackground(self.pid) self.config.getProjectConfig() self.config.getLayoutConfig() self.projectConfig = self.config.projectConfig self.layoutConfig = self.config.layoutConfig self.useBackground = self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useBackground']) self.useDocInfo = self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useDocInfo']) self.projHome = os.path.join(os.environ['RAPUMA_PROJECTS'], self.pid) self.local = ProjLocal(self.pid) self.log = ProjLog(self.pid) self.pdfViewerCmd = self.tools.getPdfViewerCommand(self.userConfig, self.projectConfig) # Log messages for this module self.errorCodes = { '0205' : ['MSG', 'Unassigned message.'], '0210' : ['MSG', 'No contents are specified for binding.'], '0215' : ['ERR', 'Failed to bind contents into the [<<1>>] fille. Got error: [<<2>>]'], '0220' : ['ERR', 'Could not copy [<<1>>] temp file to [<<2>>] saved binding file.'], '0230' : ['MSG', 'Completed proccessing on the [<<1>>] binding file.'], '0235' : ['ERR', 'Failed to complete proccessing on the [<<1>>] binding file.'], '0240' : ['LOG', 'Recorded [<<1>>] rendered pages in the [<<2>>] binding file.'], '0260' : ['ERR', 'PDF viewer failed with this error: [<<1>>]'], '0265' : ['ERR', 'Rendered file not found: <<1>>'], '0270' : ['WRN', 'PDF viewing is disabled.'], '0280' : ['ERR', 'GS PDF file merge failed with this error: [<<1>>]'], '0300' : ['MSG', 'File binding operation in process, please wait...'] } ############################################################################### ############################## Binding Functions ############################## ############################################################################### ######################## Error Code Block Series = 200 ######################## ############################################################################### # import pdb; pdb.set_trace() def bind (self, save = False) : '''Bind all groups in the order they are indicated by group bindOrder settings. Note, because binding spans groups and the main body (project.py) mainly just works on one group at a time, this has to be called from outside project and project needs to be reinitialized each time a group is rendered from here.''' # import pdb; pdb.set_trace() # Get the order of the groups to be bound. bindOrder = {} # Put a safty in here in case there are no groups yet if not self.projectConfig.has_key('Groups') : return False # Build the bindOrder dict with ord num as key and file name as value for gid in self.projectConfig['Groups'].keys() : if not self.projectConfig['Groups'][gid].has_key('bindingOrder') : self.projectConfig['Groups'][gid]['bindingOrder'] = 0 self.tools.writeConfFile(self.projectConfig) if int(self.projectConfig['Groups'][gid]['bindingOrder']) > 0 : gidPdfFile = os.path.join(self.local.projComponentFolder, gid, gid + '.pdf') # bindOrder[self.projectConfig['Groups'][gid]['bindingOrder']] = self.projectConfig['Groups'][gid]['bindingFile'] bindOrder[self.projectConfig['Groups'][gid]['bindingOrder']] = gidPdfFile bindGrpNum = len(bindOrder) # Need not keep going if nothing was found if bindGrpNum == 0 : self.log.writeToLog(self.errorCodes['0210']) return False # Make an ordered key list keyList = bindOrder.keys() keyList.sort() # Output the bind files in order according to the list we made fileList = [] for key in keyList : fileList.append(bindOrder[key]) # First merge the master pages together tempFile = self.mergePdfFilesGs(fileList) # Now add background and doc info if requested bgFile = '' if self.useBackground : bgFile = self.pg_back.addBackground(tempFile) if self.useDocInfo : if bgFile : bgFile = self.pg_back.addDocInfo(bgFile) else : bgFile = self.pg_back.addDocInfo(tempFile) # If we are saving this make a name for it if save : bindFileName = self.pid + '_contents_' + self.tools.ymd() # Save this to the Deliverable folder (Make sure there is one) if not os.path.isdir(self.local.projDeliverableFolder) : os.makedirs(self.local.projDeliverableFolder) bindFile = os.path.join(self.local.projDeliverableFolder, bindFileName + '.pdf') if os.path.exists(bgFile) : if shutil.copy(bgFile, bindFile) : self.log.writeToLog(self.errorCodes['0220'], [bgFile,bindFile]) else : if shutil.copy(tempFile, bindFile) : self.log.writeToLog(self.errorCodes['0220'], [tempFile,bindFile]) # Direct to viewFile viewFile = bindFile else : if os.path.exists(bgFile) : viewFile = bgFile else : viewFile = tempFile # Binding should have been successful, report it now self.log.writeToLog(self.errorCodes['0230'], [viewFile]) # View the file if os.path.isfile(viewFile) : if self.pdfViewerCmd : # Add the file to the viewer command self.pdfViewerCmd.append(viewFile) # Run the viewer 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['0260'], [str(e)]) else : self.log.writeToLog(self.errorCodes['0270']) else : self.log.writeToLog(self.errorCodes['0265'], [self.tools.fName(viewFile)]) def mergePdfFilesGs (self, sourceList) : '''Using GS, merge multiple PDF files into one. The advantage of using Gs is that the index will be maintained. pdftk strips out the index, which is bad...''' # This is our working file for this operation tempFile = tempfile.NamedTemporaryFile().name + '.pdf' # FIXME: Note/Warning, the use "-dPDFSETTINGS=/prepress" can cause an issue when # rendering. A segmentation fault may occur. This will cause the bind feature # to fail because gs failed. The segmentation fault seems to occure from graphics # embedded in the PDF that are either too big or not a true grayscale, but rather # RGB. The /prepress setting will then fail which means the doc will fail to test # out right in PDF x1a tests. # There does not seem to be any good way around this at this time. With more # development time and perhaps better options, we might someday be able to get # the bind process to be completely automated and be able to pass all Adobe tests # for x1a and other issues. As there is not any real demand for Rapuma, it isn't # worth putting any more effort into this section of code. Therefore, the # /prepress setting will be dropped from here and the user will be required to # Run any output through an Adobe product to get output that will be acceptable # to Adobe. Adobe wins, I quite. :-( # This command structure was in production until the segmenation fault issue was found: # cmd = ['gs', '-dBATCH', '-dNOPAUSE', '-q', '-sDEVICE=pdfwrite', '-dPDFSETTINGS=/prepress', '-sOutputFile=' + tempFile] # For issues with segmentation faults, the following should help # with debugging. Adjust as needed. for more info go to: # http://ghostscript.com/doc/8.54/Use.htm#Debugging # cmd = ['gs', '-dBATCH', '-dNOPAUSE', '-q', '-sDEVICE=pdfwrite', '-sOutputFile=' + tempFile, '-dPDFWRDEBUG', '-E']; print cmd # This is the non-x1a compatable command that will normally be used cmd = ['gs', '-dBATCH', '-dNOPAUSE', '-q', '-sDEVICE=pdfwrite', '-sOutputFile=' + tempFile] # Continueing on, now add the files we want to bind together cmd = cmd + sourceList # Now bind the files try : self.log.writeToLog(self.errorCodes['0300']) subprocess.call(cmd) # Return our file name for further processing return tempFile except Exception as e : # If we don't succeed, we should probably quite here self.log.writeToLog(self.errorCodes['0280'], [str(e)])
class ProjIllustration (object) : def __init__(self, pid, gid) : '''Do the primary initialization for this class.''' self.pid = pid self.gid = gid self.tools = Tools() self.local = ProjLocal(pid) self.user = UserConfig() self.userConfig = self.user.userConfig self.proj_config = Config(pid, gid) self.proj_config.getProjectConfig() self.proj_config.getLayoutConfig() self.proj_config.getIllustrationConfig() self.projectConfig = self.proj_config.projectConfig self.layoutConfig = self.proj_config.layoutConfig self.illustrationConfig = self.proj_config.illustrationConfig self.cType = self.projectConfig['Groups'][gid]['cType'] self.Ctype = self.cType.capitalize() self.log = ProjLog(pid) self.backgroundTypes = ['watermark', 'lines'] # Folder paths self.projComponentFolder = self.local.projComponentFolder self.projIllustrationFolder = self.local.projIllustrationFolder self.projConfFolder = self.local.projConfFolder # Log messages for this module self.errorCodes = { '0000' : ['MSG', 'Placeholder message'], '0010' : ['ERR', 'Component type [<<1>>] not recognized!'], '0210' : ['WRN', 'Cannot copy [<<1>>] into the project Illustration folder. File already exists. Please use remove or update commands if the file needs to be replaced.'], '0220' : ['MSG', 'Copied [<<1>>] into the project Illustration folder.'], '0230' : ['ERR', 'Failed to Copy [<<1>>] into the project Illustration folder.'], '0240' : ['MSG', 'Illustration add operation complete!'], '0265' : ['LOG', 'Piclist file for [<<1>>] has been created.'], '0270' : ['WRN', 'Illustration file [<<1>>] not found in Illustration folder.'], '0280' : ['ERR', 'There was a problem trying to import the illustration file(s). A possible cause could be that there was no \\fig markers in the source imported into the project. I\'m just say\'n.'], '1010' : ['MSG', 'Removed illustration file [<<1>>] from project Illustration folder.'], '1020' : ['LOG', 'Request to removed illustration file [<<1>>] from project Illustration folder. File not found. Operation not complete'], '1030' : ['MSG', 'Illustration remove operation complete!'], '2010' : ['MSG', 'Updated illustration file [<<1>>] in project Illustration folder.'], '2020' : ['ERR', 'Update failed, file [<<1>>] not found in project Illustration folder. Use add illustration command to install the illustration.'], '2030' : ['ERR', 'Update failed on file [<<1>>]. Copy proceedure failed.'], '2040' : ['MSG', 'Illustration update operation complete!'] } ############################################################################### ############################ Illustration Functions ########################### ############################################################################### ######################## Error Code Block Series = 0200 ####################### ############################################################################### def addIllustrationFiles (self, path) : '''Import all the illustrations for a group that are found in the given path. This assumes illustrations are being used in one or more components in this group. Assumes valid path. Will fail if a copy doesn't succeed. If the file is already there, give a warning and do not copy.''' # import pdb; pdb.set_trace() try : for i in self.illustrationConfig[self.gid].keys() : cid = self.illustrationConfig[self.gid][i]['bid'] fileName = self.illustrationConfig[self.gid][i]['fileName'] target = os.path.join(self.projIllustrationFolder, fileName) # Check to see if the target exists if not os.path.isfile(target) : source = os.path.join(path, fileName) if os.path.isfile(source) : # Make sure we have a target dir if not os.path.isdir(self.projIllustrationFolder) : os.makedirs(self.projIllustrationFolder) # Copy in the source to the project if not shutil.copy(source, target) : self.log.writeToLog(self.errorCodes['0220'], [self.tools.fName(source)]) # Double check that it happened if not os.path.isfile(target) : self.log.writeToLog(self.errorCodes['0230'], [self.tools.fName(source)]) else : self.log.writeToLog(self.errorCodes['0210'], [self.tools.fName(target)]) # If nothing above failed, we can return True now self.log.writeToLog(self.errorCodes['0240']) return True except : self.log.writeToLog(self.errorCodes['0280']) return False def missingIllustrations (self, bid) : '''Check for any missing illustration files for this component and report them. The assumption is that this component is supposed to have one or more illustrations in it so it should be used under hasIllustrations().''' missing = 0 for i in self.illustrationConfig[self.gid].keys() : if self.illustrationConfig[self.gid][i]['bid'] == bid : fileName = self.illustrationConfig[self.gid][i]['fileName'] target = os.path.join(self.local.projIllustrationFolder, fileName) if not os.path.exists(target) : self.log.writeToLog(self.errorCodes['0270'], [fileName]) missing +=1 # Returning True if something was missing if missing > 0 : return True def hasIllustrations (self, bid) : '''Return True if this component as any illustration associated with it.''' # Adjustment for Map cType if self.cType == 'map' : bid = 'map' # If we are missing the bid we fail (gracefully) try : for i in self.illustrationConfig[self.gid].keys() : if self.illustrationConfig[self.gid][i]['bid'] == bid : return True except : return False def getCidPiclistFile (self, cid) : '''Return the full path of the cName working text illustration file. This assumes the cName is valid.''' return os.path.join(self.local.projComponentFolder, cid, cid + '_base.' + self.cType + '.piclist') def createPiclistFile (self, cid) : '''Look in the cid for \fig data. Extract it from the cid and use it to create a piclist file for this specific cid. If there is no \fig data no piclist file will be made.''' # import pdb; pdb.set_trace() # Right now, this is the only place we need the macroConfig so # we will just make it local for now self.proj_config.getMacroConfig() macroConfig = self.proj_config.macroConfig cType = self.projectConfig['Groups'][self.gid]['cType'] if cType == 'usfm' : piclistFile = self.getCidPiclistFile(cid) elif cType == 'map' : piclistFile = self.getCidPiclistFile(self.gid) else : self.log.writeToLog(self.errorCodes['0010'], [cType]) macPackId = self.projectConfig['CompTypes'][cType.capitalize()]['macroPackage'] cvSep = macroConfig['Macros'][macPackId]['Illustrations']['chapterVerseSeperator'] thisRef = '' trueCid = cid obj = {} # Change cid for map cType # Note: The piclist IDs must be three characters long but need not be recognized USFM # file IDs. As such, we adjust the code to recognize 'map' as our ID for map rendering # operations. This seems to work for now. # if self.cType == 'map' : # cid = 'map' with codecs.open(piclistFile, "w", encoding='utf_8') as writeObject : writeObject.write('% This is an auto-generated usfmTex piclist file for this project.\n') writeObject.write('% Do not bother editing this file.\n\n') for i in self.illustrationConfig[self.gid].keys() : obj = self.illustrationConfig[self.gid][i] thisRef = '' # Filter out if needed with this if not self.tools.str2bool(obj['useThisIllustration']) : continue # Is a caption going to be used on this illustration? caption = '' if self.illustrationConfig[self.gid][i]['bid'] == cid : if self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useCaptions']) \ and self.tools.str2bool(self.illustrationConfig[self.gid][i]['useThisCaption']) : if obj['caption'] : caption = obj['caption'] # Work out if we want a caption reference or not for this illustration if self.illustrationConfig[self.gid][i]['bid'] == cid : if self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useCaptionReferences']) \ and self.tools.str2bool(self.illustrationConfig[self.gid][i]['useThisCaptionRef']) : if obj['location'] : thisRef = obj['location'] else : thisRef = obj['chapter'] + cvSep + obj['verse'] # If we made it this far we can output the line writeObject.write(obj['bid'] + ' ' + obj['chapter'] + '.' + obj['verse'] + \ ' |' + obj['fileName'] + '|' + obj['width'] + '|' + obj['position'] + \ '|' + obj['scale'] + '|' + obj['copyright'] + '|' + caption + '|' + thisRef + ' \n') # Report to log self.log.writeToLog(self.errorCodes['0265'], [trueCid]) return True ############################################################################### ######################## Illustration Remove Functions ######################## ############################################################################### ######################## Error Code Block Series = 1000 ####################### ############################################################################### def removeIllustrationFiles (self) : '''Remove all the illustration files from the illustration folder for a group. This does not affect the illustration.conf file.''' for i in self.illustrationConfig[self.gid].keys() : cid = self.illustrationConfig[self.gid][i]['bid'] fileName = self.illustrationConfig[self.gid][i]['fileName'] target = os.path.join(self.projIllustrationFolder, fileName) # Check to see if the target exists, then delete, skip if not there if os.path.exists(target) : os.remove(target) self.log.writeToLog(self.errorCodes['1010'], [self.tools.fName(target)]) else : self.log.writeToLog(self.errorCodes['1020'], [self.tools.fName(target)]) # Report and return self.log.writeToLog(self.errorCodes['1030']) return True ############################################################################### ######################## Illustration Update Functions ######################## ############################################################################### ######################## Error Code Block Series = 2000 ####################### ############################################################################### def updateIllustrationFiles (self, path) : '''Update all the illustrations in a group from path as referenced in the illustration.conf file. Skip any referenced but not in path.''' for i in self.illustrationConfig[self.gid].keys() : cid = self.illustrationConfig[self.gid][i]['bid'] fileName = self.illustrationConfig[self.gid][i]['fileName'] target = os.path.join(self.projIllustrationFolder, fileName) source = os.path.join(path, fileName) # Check to see if the source exists, proceed if it does if os.path.isfile(source) : if os.path.isfile(target) : # Copy the source to the project if not shutil.copy(source, target) : self.log.writeToLog(self.errorCodes['2010'], [self.tools.fName(source)]) else : self.log.writeToLog(self.errorCodes['2030'], [self.tools.fName(source)]) else : self.log.writeToLog(self.errorCodes['2020'], [self.tools.fName(source)]) # Report and return self.log.writeToLog(self.errorCodes['2040']) return True
class ProjDiagnose (object) : def __init__(self, pid, gid = None) : '''Intitate the whole class and create the object.''' # import pdb; pdb.set_trace() self.pid = pid self.gid = gid self.local = ProjLocal(pid, gid) self.tools = Tools() self.proj_config = Config(pid, gid) self.proj_config.getProjectConfig() self.proj_config.getLayoutConfig() self.layoutConfig = self.proj_config.layoutConfig self.user = UserConfig() self.userConfig = self.user.userConfig self.log = ProjLog(pid) # to [px] is 72/25.4 self.mmToPx = 72 / 25.4 # page width [px] self.paperPxWidth = round(self.mmToPx * float(self.layoutConfig['PageLayout']['pageWidth']),1) # page height [px] self.paperPxHeight = round(self.mmToPx * float(self.layoutConfig['PageLayout']['pageHeight']),1) # Log messages for this module self.errorCodes = { '0000' : ['MSG', 'Placeholder message'], '1310' : ['WRN', 'Failed to add diagnostic component: [<<1>>] with error: [<<2>>]'] } ############################################################################### ############################### Create Functions ############################## ############################################################################### ######################## Error Code Block Series = 1000 ####################### ############################################################################### def turnOnDiagnostic (self) : '''Change the layout config settings to turn on the diagnostic layer.''' if not self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useDiagnostic']) : self.layoutConfig['DocumentFeatures']['useDiagnostic'] = True self.tools.writeConfFile(self.layoutConfig) def turnOffDiagnostic (self) : '''Change the layout config settings to turn off the diagnostic layer.''' if self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useDiagnostic']) : self.layoutConfig['DocumentFeatures']['useDiagnostic'] = False self.tools.writeConfFile(self.layoutConfig) def addTransparency (self, target, force = False) : '''Add a transparent layer to a rendered PDF file. This will add in diagnosing format issues. Using force will cause any existing layer file to be remade.''' # Do a quick check if the transparency needs to be remade # The transparency normally is not remade if one already exists. # If one is there, it can be remade in two ways, with a force # or a regenerate command. if force : self.createDiagnostic() elif self.tools.str2bool(self.layoutConfig['DocumentFeatures']['regenerateTransparency']) : self.createDiagnostic() else : # If there isn't one, make it if not os.path.exists(self.local.diagnosticFile) : self.createDiagnostic() # Create a special temp named file for the target tmpTarget = tempfile.NamedTemporaryFile().name # Copy the target to the tmpTarget shutil.copy(target, tmpTarget) # Overlay the transparency diagnostic file over the tmpTarget self.tools.mergePdfFilesPdftk(tmpTarget, self.local.diagnosticFile) # Create a special name for the file with the background # Then merge and save it viewFile = self.tools.alterFileName(target, 'view') # Copy the results back to the target (should be done now) shutil.copy(tmpTarget, viewFile) # Not returning a file name would mean it failed if os.path.exists(viewFile) : return viewFile def createDiagnostic (self) : '''Create a diagnostic transparency (file) that will be superimposed over the page contents to help diagnose format issues. This will overwrite any existing transparency file and will add each recognoized diagnostic type found in the diagnosticComponents config setting.''' # import pdb; pdb.set_trace() self.createBlankTransparency() # Add each component to the blank transparency file for comp in self.layoutConfig['DocumentFeatures']['diagnosticComponents'] : try : getattr(self, 'merge' + comp.capitalize())() except Exception as e : self.log.writeToLog(self.errorCodes['1310'],[comp,str(e)]) pass return True def createBlankTransparency (self) : '''Create a blank background page according to the trim size specified.''' # Set the temp svg file name svgFile = tempfile.NamedTemporaryFile().name # Be sure there is an illustrations folder in place if not os.path.isdir(self.local.projIllustrationFolder) : os.mkdir(self.local.projIllustrationFolder) # Write out SVG document text with codecs.open(svgFile, 'wb') as fbackgr : # open file for writing fbackgr.write( '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width = "''' + str(self.paperPxWidth) + '''" height = "''' + str(self.paperPxHeight)+ '''"> </svg>''') shutil.copy(self.tools.convertSvgToPdfRsvg(svgFile), self.local.diagnosticFile) ############################################################################### ############################# Component Functions ############################# ############################################################################### ######################## Error Code Block Series = 2000 ####################### ############################################################################### def mergeLeading (self) : '''Create a diagnostic page component that has lines to indicate the text leading. This will be superimposed over the contents of the trim page, not in the background like a watermark, etc.''' # Initialize the process svgFile = tempfile.NamedTemporaryFile().name # PAGE DIMENSIONS # The page dimensions extracted from layoutConfig are in [mm] and # must be converted to pixels [px], the conversion factor for [mm] # bodyFontSize [px] bodyFontSize = self.layoutConfig['TextElements']['bodyFontSize'] bodyFontPxSize = round(float(bodyFontSize) * 72/72.27,3) # bodyTextLeading [px] bodyTextLeading = self.layoutConfig['TextElements']['bodyTextLeading'] bodyTextPxLeading = round(float(bodyTextLeading) * 72/72.27,3) # top margin [px] topMargin = self.layoutConfig['PageLayout']['topMargin'] topPxMargin = round(self.mmToPx * float(topMargin),1) # outside margin [px] outsideMargin = self.layoutConfig['PageLayout']['outsideMargin'] outsidePxMargin = round(self.mmToPx * float(outsideMargin),1) #inside margin [px] insideMargin = self.layoutConfig['PageLayout']['insideMargin'] insidePxMargin = round(self.mmToPx * float(outsideMargin),1) # bottom margin [px] bottomMargin = self.layoutConfig['PageLayout']['bottomMargin'] bottomPxMargin = round(self.mmToPx * float(bottomMargin),1) # width of the body text textPxWidth = self.paperPxWidth - (outsidePxMargin + insidePxMargin) # Create the svg file with codecs.open(svgFile, 'wb') as fbackgr : # open file for writing # starting lines of SVG xml fbackgr.write( '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width = "''' + str(self.paperPxWidth)+ '''" height = "'''+str(self.paperPxHeight) + '''"> \n <!--RECTANGLE OF MARGINS-->\n''') fbackgr.write( '''<rect x = "''' + str(outsidePxMargin) + '''" y= "''' + str(topPxMargin) + '''" height = "''' + str(self.paperPxHeight - topPxMargin - bottomPxMargin) + '''" width = "''' + str(textPxWidth) + '''" style = "fill:none;fill-opacity:1;stroke:#ffc800;stroke-opacity:1;stroke-width:.2"/> \n <!--START OF LINEGRID-->\n''') fbackgr.write( '''<path d= "m ''' + str(outsidePxMargin * 0.75-1) + "," + str(topPxMargin + bodyFontPxSize) + " " + str(textPxWidth + outsidePxMargin * 0.25)+ ''',0''') # filling the space between the top line and bottom margin, starting at distance # counter num = 0 up to the but not including the total of num x leading # equals the distance between top and bottom margin num = 0 while (num < int(round(self.paperPxHeight - bottomPxMargin - topPxMargin)/bodyTextPxLeading)): # lines are drawn in zigzag pattern: RTL when num is even and LTR when odd if num%2 == 0: fbackgr.write( ''' m 0, ''' + str(bodyTextPxLeading) + " -" + str(textPxWidth + outsidePxMargin * 0.25)+ ''',0''') else: fbackgr.write( ''' m 0, ''' + str(bodyTextPxLeading) + " " + str(textPxWidth + outsidePxMargin * 0.25)+ ''',0''') num = num +1 # draw all lines with following style fbackgr.write( '''" style="stroke-width:0.2px;stroke:#ffc800;stroke-opacity:1"/> \n <!--LINE NUMBERS-->\n''') # add line number '1' to top line just left of margin fbackgr.write( '''<text x="''' + str(outsidePxMargin * 0.75-2) + '''" y="''' + str(topPxMargin + bodyFontPxSize-3) + '''" style="font-family: Charis SIL;font-style:italic;font-size:7;fill:#760076"> 1''') # add line numbers to all lines down to bottom margin, starting with line number # counter linecount = 2, the distance counter runs from '0' till one short of # the quotient (distance between top and bottom margin)/bodyTextPxLeading num = 0 # line counter linenumber = 2 # line number while (num < int(round(self.paperPxHeight - bottomPxMargin - topPxMargin)/bodyTextPxLeading)): fbackgr.write( '''<tspan x="''' + str(outsidePxMargin * 0.75-2) + '''" dy="''' + str(bodyTextPxLeading) + '''">''' + str(linenumber) + '''</tspan>''') linenumber = linenumber +1 num = num +1 fbackgr.write('''</text> \n <!--LINEGRID CAPTION--> <text x="36" y="''' + str(self.paperPxHeight - bottomPxMargin+10) + '''" style="font-family: Charis SIL;font-style:italic;font-size:7;fill:#ffc800">page size: ''' + str(int(self.paperPxWidth/72*25.4+.5)) + ''' x ''' + str(int(self.paperPxHeight/72*25.4+.5)) + ''' mm ; font size: ''' + str(bodyFontSize) + ''' pt; leading: ''' + str(bodyTextLeading) + ''' pt</text> \n <!--PURPLE LINES TOP AND BOTTOM MARGINS--> <path d="M ''' + str(outsidePxMargin) + "," + str(topPxMargin) + " " + str(textPxWidth + outsidePxMargin) + "," + str(topPxMargin) + '''" style="fill:#ffffff;fill-opacity:1;stroke-width:0.4px;stroke:#760076;stroke-opacity:1"/> <path d="M ''' + str(outsidePxMargin) + "," + str(self.paperPxHeight - bottomPxMargin) + " " + str(textPxWidth + outsidePxMargin) + "," + str(self.paperPxHeight - bottomPxMargin) + '''" style="fill:#ffffff;fill-opacity:1;stroke-width:0.4px;stroke:#760076;stroke-opacity:1"/> </svg>''') # Convert the lines background component to PDF leadingPdf = self.tools.convertSvgToPdfRsvg(svgFile) # Merge leadingPdf with existing transparency results = self.tools.mergePdfFilesPdftk(self.local.diagnosticFile, leadingPdf) # Test and return if good if os.path.isfile(results) : return True
class ProjBinding (object) : def __init__(self, pid) : '''Do the primary initialization for this manager.''' self.pid = pid self.tools = Tools() self.user = UserConfig() self.userConfig = self.user.userConfig self.config = Config(pid) self.pg_back = ProjBackground(self.pid) self.config.getProjectConfig() self.config.getLayoutConfig() self.projectConfig = self.config.projectConfig self.layoutConfig = self.config.layoutConfig self.useBackground = self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useBackground']) self.useDocInfo = self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useDocInfo']) self.projHome = os.path.join(self.userConfig['Resources']['projects'], self.pid) self.local = ProjLocal(self.pid) self.log = ProjLog(self.pid) self.pdfViewerCmd = self.userConfig['System']['pdfViewerCommand'] # Log messages for this module self.errorCodes = { '0205' : ['MSG', 'Unassigned message.'], '0210' : ['MSG', 'No contents are specified for binding.'], '0215' : ['ERR', 'Failed to bind contents into the [<<1>>] fille. Got error: [<<2>>]'], '0220' : ['ERR', 'Could not copy [<<1>>] temp file to [<<2>>] saved binding file.'], '0230' : ['MSG', 'Completed proccessing on the [<<1>>] binding file.'], '0235' : ['ERR', 'Failed to complete proccessing on the [<<1>>] binding file.'], '0240' : ['LOG', 'Recorded [<<1>>] rendered pages in the [<<2>>] binding file.'], '0260' : ['ERR', 'PDF viewer failed with this error: [<<1>>]'], '0265' : ['ERR', 'Rendered file not found: <<1>>'], '0270' : ['WRN', 'PDF viewing is disabled.'], '0280' : ['ERR', 'GS PDF file merge failed with this error: [<<1>>]'], '0300' : ['MSG', 'File binding operation in process, please wait...'] } ############################################################################### ############################## Binding Functions ############################## ############################################################################### ######################## Error Code Block Series = 200 ######################## ############################################################################### # import pdb; pdb.set_trace() def bind (self, save = False) : '''Bind all groups in the order they are indicated by group bindOrder settings. Note, because binding spans groups and the main body (project.py) mainly just works on one group at a time, this has to be called from outside project and project needs to be reinitialized each time a group is rendered from here.''' # import pdb; pdb.set_trace() # Get the order of the groups to be bound. bindOrder = {} # Put a safty in here in case there are no groups yet if not self.projectConfig.has_key('Groups') : return False # Build the bindOrder dict with ord num as key and file name as value for gid in self.projectConfig['Groups'].keys() : if not self.projectConfig['Groups'][gid].has_key('bindingOrder') : self.projectConfig['Groups'][gid]['bindingOrder'] = 0 self.tools.writeConfFile(self.projectConfig) if int(self.projectConfig['Groups'][gid]['bindingOrder']) > 0 : gidPdfFile = os.path.join(self.local.projComponentFolder, gid, gid + '.pdf') # bindOrder[self.projectConfig['Groups'][gid]['bindingOrder']] = self.projectConfig['Groups'][gid]['bindingFile'] bindOrder[self.projectConfig['Groups'][gid]['bindingOrder']] = gidPdfFile bindGrpNum = len(bindOrder) # Need not keep going if nothing was found if bindGrpNum == 0 : self.log.writeToLog(self.errorCodes['0210']) return False # Make an ordered key list keyList = bindOrder.keys() keyList.sort() # Output the bind files in order according to the list we made fileList = [] for key in keyList : fileList.append(bindOrder[key]) # First merge the master pages together tempFile = self.mergePdfFilesGs(fileList) # Now add background and doc info if requested bgFile = '' if self.useBackground : bgFile = self.pg_back.addBackground(tempFile) if self.useDocInfo : if bgFile : bgFile = self.pg_back.addDocInfo(bgFile) else : bgFile = self.pg_back.addDocInfo(tempFile) # If we are saving this make a name for it if save : bindFileName = self.pid + '_contents_' + self.tools.ymd() # Save this to the Deliverable folder (Make sure there is one) if not os.path.isdir(self.local.projDeliverableFolder) : os.makedirs(self.local.projDeliverableFolder) bindFile = os.path.join(self.local.projDeliverableFolder, bindFileName + '.pdf') if os.path.exists(bgFile) : if shutil.copy(bgFile, bindFile) : self.log.writeToLog(self.errorCodes['0220'], [bgFile,bindFile]) else : if shutil.copy(tempFile, bindFile) : self.log.writeToLog(self.errorCodes['0220'], [tempFile,bindFile]) # Direct to viewFile viewFile = bindFile else : if os.path.exists(bgFile) : viewFile = bgFile else : viewFile = tempFile # Binding should have been successful, report it now self.log.writeToLog(self.errorCodes['0230'], [viewFile]) # View the file if os.path.isfile(viewFile) : if not len(self.pdfViewerCmd[0]) == 0 : # Add the file to the viewer command self.pdfViewerCmd.append(viewFile) # Run the viewer 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['0260'], [str(e)]) else : self.log.writeToLog(self.errorCodes['0270']) else : self.log.writeToLog(self.errorCodes['0265'], [self.tools.fName(viewFile)]) def mergePdfFilesGs (self, sourceList) : '''Using GS, merge multiple PDF files into one. The advantage of using Gs is that the index will be maintained. pdftk strips out the index, which is bad...''' # This is our working file for this operation tempFile = tempfile.NamedTemporaryFile().name + '.pdf' # FIXME: Note/Warning, the use "-dPDFSETTINGS=/prepress" can cause an issue when # rendering. A segmentation fault may occur. This will cause the bind feature # to fail and gs will fail. There does not seem to be any way around this # as getting feedback from gs while processing is not possible, or at least # not very easy. It will have to stay this way for now. cmd = ['gs', '-dBATCH', '-dNOPAUSE', '-q', '-sDEVICE=pdfwrite', '-dPDFSETTINGS=/prepress', '-sOutputFile=' + tempFile] # If there issues with segmentation faults, the following should help with debugging # cmd = ['gs', '-dBATCH', '-dNOPAUSE', '-q', '-sDEVICE=pdfwrite', '-sOutputFile=' + tempFile] # Now add the files we want to bind together cmd = cmd + sourceList # Now bind the files try : self.log.writeToLog(self.errorCodes['0300']) subprocess.call(cmd) # Return our file name for further processing return tempFile except Exception as e : # If we don't succeed, we should probably quite here self.log.writeToLog(self.errorCodes['0280'], [str(e)])