Added simple prerender functions to estimate seciton dimensions

Added width-overflow check. Program will downsize the fontSize down untill all lines are readable
Moved config options into the song object
Fixes #3
This commit is contained in:
Marco van Dijk 2021-07-09 00:55:45 +02:00
parent 55daf981cc
commit db9633a855
4 changed files with 106 additions and 61 deletions

View File

@ -36,7 +36,7 @@ def initConfig():
'metaFontWeight': 8,
'lyricfontfamily': 'fonts/CourierPrime-Regular.ttf',
'tablaturefontfamliy': 'fonts/CourierPrime-Bold.ttf',
'songFontWeight': 14,
'songFontWeight': 18,
'imageWidth': 595, 'imageHeight': 842, # A4 at 72dpi
'backgroundColour': '255,255,255',
'fontColour': '0,0,0',

View File

@ -14,6 +14,8 @@
# - Move read functions to separate input functions (also to support more types of inputs)
import re
import lib.config
from PIL import ImageFont
"""!@brief Removes empty lines and makes sure every line ends with \r\n
@param inputString raw txt input
@ -82,12 +84,43 @@ class Section:
self.rawData = ""
# Flag for succesfully parsed
self.isParsed = False
# Expected dimensions of this section
self.expectedWidth = -1
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):
lineIterator = 0
amountOfLines = len(self.lyrics)
heightSum = 0
maxWidth = 0
# consider section title
headerWidth, headerHeight = fontTablature.getsize(self.header)
heightSum += headerHeight
maxWidth = headerWidth
while lineIterator < amountOfLines:
# Get chord&lyric line dimensions
lyricTextWidth, lyricTextHeight = fontLyrics.getsize(self.lyrics[lineIterator])
tablatureTextWidth, chordTextHeight = fontTablature.getsize(self.tablatures[lineIterator])
heightSum += lyricTextHeight + chordTextHeight
if lyricTextWidth > maxWidth:
maxWidth = lyricTextWidth
if tablatureTextWidth > maxWidth:
maxWidth = tablatureTextWidth
lineIterator += 1
self.expectedWidth = maxWidth
self.expectedHeight = heightSum
"""!@brief Converts raw buffered data into separate Lyric and tablature lines
@return None
"""
# Parses self.rawData into lyrics and tablature strings
def parseMe(self):
def initSections(self):
isFirstLine = True
# Input sections may have tablature-only or lyric-only sections
# So we have to insert empty lines if we have subsequent tablature or lyric lines
@ -150,11 +183,51 @@ class Song:
self.rawData = ""
# Flag for succesfully parsed
self.isParsed = False
configObj = lib.config.config['output']
self.topMargin = int(configObj['topMargin'])
self.fontColour = tuple(int(var) for var in configObj['fontColour'].split(','))
self.backgroundColour = tuple(int(var) for var in configObj['backgroundColour'].split(','))
self.metadataColour = tuple(int(var) for var in configObj['metadataColour'].split(','))
self.imageWidth = int(configObj['imageWidth'])
self.imageHeight = int(configObj['imageHeight'])
self.leftMargin = int(configObj['leftMargin'])
self.fontMetadata = ImageFont.truetype(configObj['metafontfamily'], int(configObj['metaFontWeight']))
self.fontSize = int(configObj['songFontWeight'])
self.fontLyrics = ImageFont.truetype(configObj['lyricfontfamily'], self.fontSize)
self.fontTablature = ImageFont.truetype(configObj['tablaturefontfamliy'], self.fontSize)
self.configObj = configObj
"""!@brief Calculates the expected dimensions of all sections
@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
"""!@brief Resizes all sections by a specified amount
Also recalculates all section sizes afterwards
@param mutator amount of fontSize to add/dec from current font size
@return None
"""
def resizeAllSections(self, mutator):
self.fontSize += mutator
self.fontLyrics = ImageFont.truetype(self.configObj['lyricfontfamily'], self.fontSize)
self.fontTablature = ImageFont.truetype(self.configObj['tablaturefontfamliy'], self.fontSize)
self.prerenderSections()
"""!@brief Parses self.rawData into Section objects and metadata
@return None
"""
def parseMe(self):
def initSections(self):
# Get raw data
self.rawData = readSourceFile(self.inputFile)
# Clean up input
@ -194,7 +267,7 @@ class Song:
#print("set rawData of '{}' to this section".format(thisSection.rawData))
parseData = parseData[delimiterIndex:]
# Finally parse section data
thisSection.parseMe()
thisSection.initSections()
if thisSection.isParsed:
self.sections.append(thisSection)
else:

12
main.py
View File

@ -34,12 +34,20 @@ def main():
songs = lib.initSongs.getSongObjects()
# Convert all songs into sections
for song in songs:
song.parseMe()
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)
# 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(song.inputFile, targetDirectory))
# Write out metadata and sections, as many as can fit on one page
output2img.outputToImage(targetDirectory, song)

View File

@ -12,36 +12,10 @@
# -
#
# @section todo TODO
# - A lot of this stuff is hardcoded. We want to write default fonts, sizes, colours, margins, dimensions, wanted amount of pages
# to a config file on first boot. Overwrite these if they get passed via CMD arguments (or manually by user)
# - Various prints should be printed at specific log levels, to easily switch between debug, info or warnings only
import lib.config
import os
from PIL import Image, ImageDraw, ImageFont
"""!@brief Calculates the height of rendered text
This function calculates the dimensions of each line of text
the section contains and returns the sum
@param section lib.dataStructures.Section object
@param fontTablature ImageFont.truetype object of font we are using for tablature lines
@param fontLyrics ImageFont.truetype object of font we are using for lyric lines
@return the total height of the section
"""
def calcSectionHeight(section, fontTablature, fontLyrics):
lineIterator = 0
amountOfLines = len(section.lyrics)
heightSum = 0
# consider section title
headerWidth, headerHeight = fontTablature.getsize(section.header)
heightSum += headerHeight
while lineIterator < amountOfLines:
# Get chord&lyric line dimensions
lyricTextWidth, lyricTextHeight = fontLyrics.getsize(section.lyrics[lineIterator])
tablatureTextWidth, chordTextHeight = fontTablature.getsize(section.tablatures[lineIterator])
heightSum += lyricTextHeight + chordTextHeight
lineIterator += 1
return heightSum
from PIL import Image, ImageDraw
"""!@brief Exports the song object to images
This function renders the metadata and sections
@ -50,7 +24,6 @@ def calcSectionHeight(section, fontTablature, fontLyrics):
It will overwrite existing images, but will not clear old images
@param folderLocation path to where we want the images
@param songObj lib.dataStructures.Song object
@param configObj configparser object
@return None
"""
def outputToImage(folderLocation, songObj):
@ -60,25 +33,13 @@ def outputToImage(folderLocation, songObj):
print("Directory " , folderLocation , " Created ")
#else:
#print("Directory " , folderLocation , " already exists")
configObj = lib.config.config['output']
topMargin = int(configObj['topMargin'])
fontColour = tuple(int(var) for var in configObj['fontColour'].split(','))
backgroundColour = tuple(int(var) for var in configObj['backgroundColour'].split(','))
metadataColour = tuple(int(var) for var in configObj['metadataColour'].split(','))
imageWidth = int(configObj['imageWidth'])
imageHeight = int(configObj['imageHeight'])
leftMargin = int(configObj['leftMargin'])
fontMetadata = ImageFont.truetype(configObj['metafontfamily'], int(configObj['metaFontWeight']))
fontLyrics = ImageFont.truetype(configObj['lyricfontfamily'], int(configObj['songFontWeight']))
fontTablature = ImageFont.truetype(configObj['tablaturefontfamliy'], int(configObj['songFontWeight']))
# Init image info
imageNumber = 1
currentHeight = topMargin
currentHeight = songObj.topMargin
# New Image
a4image = Image.new('RGB',(imageWidth, imageHeight),(backgroundColour))
a4image = Image.new('RGB',(songObj.imageWidth, songObj.imageHeight),(songObj.backgroundColour))
draw = ImageDraw.Draw(a4image)
# Write metadata
@ -88,11 +49,11 @@ def outputToImage(folderLocation, songObj):
if not line:
continue
#print("meta line '{}'".format(line))
metadataTextWidth, metadataTextHeight = fontMetadata.getsize(line)
draw.text((leftMargin,currentHeight), line, fill=metadataColour, font=fontMetadata)
metadataTextWidth, metadataTextHeight = songObj.fontMetadata.getsize(line)
draw.text((songObj.leftMargin,currentHeight), line, fill=songObj.metadataColour, font=songObj.fontMetadata)
currentHeight += metadataTextHeight
# Margin between metadata and the first section
currentHeight += topMargin
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
@ -103,34 +64,37 @@ def outputToImage(folderLocation, songObj):
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 + calcSectionHeight(section, fontTablature, fontLyrics) > imageHeight:
if currentHeight + section.expectedHeight > songObj.imageHeight:
#print("overflow! starting with a new image")
outputLocation = folderLocation + "/" + str(imageNumber) + ".png"
imageNumber += 1
a4image.save(outputLocation)
currentHeight = topMargin
a4image = Image.new('RGB',(imageWidth, imageHeight),(backgroundColour))
currentHeight = songObj.topMargin
a4image = Image.new('RGB',(songObj.imageWidth, songObj.imageHeight),(songObj.backgroundColour))
draw = ImageDraw.Draw(a4image)
# write section title
headerWidth, headerHeight = fontTablature.getsize(section.header)
draw.text((leftMargin,currentHeight), section.header, fill=fontColour, font=fontTablature)
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 = fontLyrics.getsize(section.lyrics[lineIterator])
tablatureTextWidth, tablatureTextHeight = fontTablature.getsize(section.tablatures[lineIterator])
lyricTextWidth, lyricTextHeight = songObj.fontLyrics.getsize(section.lyrics[lineIterator])
tablatureTextWidth, tablatureTextHeight = songObj.fontTablature.getsize(section.tablatures[lineIterator])
# add to image file
draw.text((leftMargin,currentHeight), section.tablatures[lineIterator], fill=fontColour, font=fontTablature)
draw.text((songObj.leftMargin,currentHeight), section.tablatures[lineIterator], fill=songObj.fontColour, font=songObj.fontTablature)
currentHeight += tablatureTextHeight
draw.text((leftMargin,currentHeight), section.lyrics[lineIterator], fill=fontColour, font=fontLyrics)
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 += topMargin
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)