diff --git a/lib/config.py b/lib/config.py index 8376881..e5e079c 100644 --- a/lib/config.py +++ b/lib/config.py @@ -36,7 +36,7 @@ def initConfig(): 'metaFontWeight': 8, 'lyricfontfamily': 'fonts/CourierPrime-Regular.ttf', 'tablaturefontfamliy': 'fonts/CourierPrime-Bold.ttf', - 'songFontWeight': 18, + 'songFontWeight': 14, 'imageWidth': 595, 'imageHeight': 842, # A4 at 72dpi 'backgroundColour': '255,255,255', 'fontColour': '0,0,0', diff --git a/lib/dataStructures.py b/lib/dataStructures.py index 8438f6a..ebb3a89 100644 --- a/lib/dataStructures.py +++ b/lib/dataStructures.py @@ -89,9 +89,6 @@ class Section: self.expectedHeight = -1 """!@brief Calculates dimensions of rendered text - This function calculates the dimensions of each line of text - the section contains and sets the internal variables - @param section lib.dataStructures.Section object @return None """ def calculateSectionDimensions(self, fontTablature, fontLyrics): @@ -165,6 +162,13 @@ class Section: self.lyrics.append("") self.isParsed = True +"""!@brief Class containing Sections which fit on 1 page +""" +class Page: + def __init__(self): + self.sections = [] + self.totalHeight = -1 + """!@brief Class containing Song specific data """ class Song: @@ -179,8 +183,12 @@ class Song: self.sections = [] # Meta info: the text before the first section self.metadata = "" + self.metadataWidth = -1 + self.metadataHeight = -1 # String of entire input self.rawData = "" + # List of pages, which contain sections which fit on a page + self.pages = [] # Flag for succesfully parsed self.isParsed = False configObj = lib.config.config['output'] @@ -197,21 +205,25 @@ class Song: self.fontTablature = ImageFont.truetype(configObj['tablaturefontfamliy'], self.fontSize) self.configObj = configObj - """!@brief Calculates the expected dimensions of all sections + + """!@brief Calculates dimensions of metadata + @param section lib.dataStructures.Section object @return None """ - def prerenderSections(self): - for section in self.sections: - section.calculateSectionDimensions(self.fontTablature, self.fontLyrics) - - """!@brief Checks whether we are overflowing on the width of the page - @return True if everything OK, False if overflowing - """ - def checkOverflowX(self): - for section in self.sections: - if section.expectedWidth > self.imageWidth: - return False - return True + def calculateMetadataDimensions(self): + # metadata starts topMargin removed from top + currentHeight = self.topMargin + maxWidth = 0 + for line in self.metadata.split('\n'): + line = line.rstrip() + if not line: + continue + metadataTextWidth, metadataTextHeight = self.fontMetadata.getsize(line) + if metadataTextWidth > maxWidth: + maxWidth = metadataTextWidth + currentHeight += metadataTextHeight + self.metadataWidth = maxWidth + self.metadataHeight = currentHeight """!@brief Resizes all sections by a specified amount Also recalculates all section sizes afterwards @@ -223,6 +235,63 @@ class Song: self.fontLyrics = ImageFont.truetype(self.configObj['lyricfontfamily'], self.fontSize) self.fontTablature = ImageFont.truetype(self.configObj['tablaturefontfamliy'], self.fontSize) self.prerenderSections() + + """!@brief Calculates the expected dimensions of all sections + @return None + """ + def prerenderSections(self): + self.calculateMetadataDimensions() + for section in self.sections: + section.calculateSectionDimensions(self.fontTablature, self.fontLyrics) + + """!@brief Calculates the expected dimensions of all sections + @return None + """ + def fitSectionsByWidth(self): + self.prerenderSections() + while not self.checkOverflowX(): + #print("Overflowing on width of the page. Decreasing font size by 2...") + self.resizeAllSections(-2) + + """!@brief Checks whether we are overflowing on the width of the page + @return True if everything OK, False if overflowing + """ + def checkOverflowX(self): + for section in self.sections: + if section.expectedWidth > self.imageWidth: + return False + return True + + """!@brief Fits current sections into pages + @return None + """ + def sectionsToPages(self): + self.prerenderSections() + # First page contains metadata + currentHeight = self.topMargin + currentHeight += self.metadataHeight + currentHeight += self.topMargin + curPage = Page() + # Now fit all sections + for section in self.sections: + if (section.expectedHeight == -1 or section.expectedWidth == -1): + print("Warning: this file was not processed correctly. The expected dimensions are not set") + # See if the section would fit on the current page - if it does not, we have a filled page + if currentHeight + section.expectedHeight > self.imageHeight: + curPage.totalHeight = currentHeight + self.pages.append(curPage) + currentHeight = self.topMargin + curPage = Page() + # Add setion header size and size of lines of data + headerWidth, headerHeight = self.fontTablature.getsize(section.header) + currentHeight += headerHeight + currentHeight += section.expectedHeight + curPage.sections.append(section) + # Margin between each section + currentHeight += self.topMargin + # No more sections left, so the current buffered image is ready to be written to file + curPage.totalHeight = currentHeight + self.pages.append(curPage) """!@brief Parses self.rawData into Section objects and metadata @return None diff --git a/main.py b/main.py index 55e90e5..3320a32 100644 --- a/main.py +++ b/main.py @@ -37,17 +37,17 @@ def main(): print("Start parsing of file '{}'...".format(song.inputFile)) # Initialise internal data structures song.initSections() - # Prerender: calculate the expected dimensions for each section - song.prerenderSections() - # While we overflow on X: resize all sections down and recalculate - while not song.checkOverflowX(): - #print("Overflowing on width of the page. Decreasing font size by 2...") - song.resizeAllSections(-2) + # Fit all sections on each page, resizes down if it does not fit on width + song.fitSectionsByWidth() + # Prerender: calculate Pages, and move sections into Pages + song.sectionsToPages() + # Optimalisation: check for whitespace, check verhouding of whitespace&first section on next page + # TODO # Parse as PNG a4 if song.isParsed: # Create subdirectory where we will output our images targetDirectory = song.outputLocation + "-a4-png" - print("Successfully parsed file. Writing output to '{}'".format(song.inputFile, targetDirectory)) + print("Successfully parsed file. Writing output to '{}'".format(targetDirectory)) # Write out metadata and sections, as many as can fit on one page output2img.outputToImage(targetDirectory, song) diff --git a/output2img.py b/output2img.py index 44aa183..847459e 100644 --- a/output2img.py +++ b/output2img.py @@ -54,50 +54,44 @@ def outputToImage(folderLocation, songObj): currentHeight += metadataTextHeight # Margin between metadata and the first section currentHeight += songObj.topMargin - # Iterate over each section - # NOTE: sections might be split into lists of pages containing a list of sections - # This change will occur when we add an arranger which resizes sections to fit pages better - for section in songObj.sections: - # Reset section specific variables - lineIterator = 0 - amountOfLines = len(section.lyrics) - if (amountOfLines != len(section.tablatures)): - print("Cannot write this section to file, since it was not processed correctly. There are {} tablature lines and {} lyric lines. Aborting...".format(len(section.chords), amountOfLines)) - return - if (section.expectedHeight == -1 or section.expectedWidth == -1): - print("Cannot write this section to file, since it was not processed correctly. The expected dimensions are not set. Aborting...") - return - # See if the section would fit on the current page - if it does not, write current buffered image & make the next image ready - if currentHeight + section.expectedHeight > songObj.imageHeight: - #print("overflow! starting with a new image") - outputLocation = folderLocation + "/" + str(imageNumber) + ".png" - imageNumber += 1 - a4image.save(outputLocation) - currentHeight = songObj.topMargin - a4image = Image.new('RGB',(songObj.imageWidth, songObj.imageHeight),(songObj.backgroundColour)) - draw = ImageDraw.Draw(a4image) - # write section title - headerWidth, headerHeight = songObj.fontTablature.getsize(section.header) - draw.text((songObj.leftMargin,currentHeight), section.header, fill=songObj.fontColour, font=songObj.fontTablature) - currentHeight += headerHeight - # Write each line tablature&lyric data - while lineIterator < amountOfLines: - #print("Printing tablatures line {} and lyrics line {}".format(section.tablatures[lineIterator], section.lyrics[lineIterator])) - # Get tablatures&lyric line - lyricTextWidth, lyricTextHeight = songObj.fontLyrics.getsize(section.lyrics[lineIterator]) - tablatureTextWidth, tablatureTextHeight = songObj.fontTablature.getsize(section.tablatures[lineIterator]) - # add to image file - draw.text((songObj.leftMargin,currentHeight), section.tablatures[lineIterator], fill=songObj.fontColour, font=songObj.fontTablature) - currentHeight += tablatureTextHeight - draw.text((songObj.leftMargin,currentHeight), section.lyrics[lineIterator], fill=songObj.fontColour, font=songObj.fontLyrics) - currentHeight += lyricTextHeight - lineIterator += 1 - #print("currentheight={}".format(currentHeight)) - # Margin between each section - currentHeight += songObj.topMargin - # No more sections left, so the current buffered image is ready to be written to file - outputLocation = folderLocation + "/" + str(imageNumber) + ".png" - a4image.save(outputLocation) + # Draw all pages + for page in songObj.pages: + for section in page.sections: + # Reset section specific variables + lineIterator = 0 + amountOfLines = len(section.lyrics) + if (amountOfLines != len(section.tablatures)): + print("Cannot write this section to file, since it was not processed correctly. There are {} tablature lines and {} lyric lines. Aborting...".format(len(section.chords), amountOfLines)) + return + if (section.expectedHeight == -1 or section.expectedWidth == -1): + print("Cannot write this section to file, since it was not processed correctly. The expected dimensions are not set. Aborting...") + return + # write section title + headerWidth, headerHeight = songObj.fontTablature.getsize(section.header) + draw.text((songObj.leftMargin,currentHeight), section.header, fill=songObj.fontColour, font=songObj.fontTablature) + currentHeight += headerHeight + # Write each line tablature&lyric data + while lineIterator < amountOfLines: + #print("Printing tablatures line {} and lyrics line {}".format(section.tablatures[lineIterator], section.lyrics[lineIterator])) + # Get tablatures&lyric line + lyricTextWidth, lyricTextHeight = songObj.fontLyrics.getsize(section.lyrics[lineIterator]) + tablatureTextWidth, tablatureTextHeight = songObj.fontTablature.getsize(section.tablatures[lineIterator]) + # add to image file + draw.text((songObj.leftMargin,currentHeight), section.tablatures[lineIterator], fill=songObj.fontColour, font=songObj.fontTablature) + currentHeight += tablatureTextHeight + draw.text((songObj.leftMargin,currentHeight), section.lyrics[lineIterator], fill=songObj.fontColour, font=songObj.fontLyrics) + currentHeight += lyricTextHeight + lineIterator += 1 + #print("currentheight={}".format(currentHeight)) + # Margin between each section + currentHeight += songObj.topMargin + # Got all sections in the page, so write it + outputLocation = folderLocation + "/" + str(imageNumber) + ".png" + a4image.save(outputLocation) + a4image = Image.new('RGB',(songObj.imageWidth, songObj.imageHeight),(songObj.backgroundColour)) + draw = ImageDraw.Draw(a4image) + currentHeight = songObj.topMargin + imageNumber += 1