<commit_before>import neume_dict
from glyphs import Glyph, GlyphLine

from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.pagesizes import letter
import reportlab.lib.colors as colors

import sys
import xml.etree.ElementTree as ET
import re

class Cursor:
    def __init__(self,x=0,y=0):
        self.x = x
        self.y = y
        
class Kassia:
    """Base class for package"""
    def __init__(self,fileName,outFile = "out.pdf"):
        self.fileName = fileName # input file
        self.outFile = outFile # output file
        try:
            open(fileName,"r")
            fileReadable = True
        except IOError:
            fileReadable = False
            print "Not readable"
        
        if fileReadable:
            self.parseFile()
            self.createPDF()
        
    def parseFile(self):
        try:
            bnmlTree = ET.parse(self.fileName)
            bnml = bnmlTree.getroot()
            self.bnml = bnml   
               
        except ET.ParseError:
            print "Failed to parse XML file"
            
    def createPDF(self):
        """Create PDF output file"""
        # Parse page layout and formatting
        if (self.bnml is not None):
            margin_attrib = self.bnml.attrib
            margin_attrib = self.fill_page_dict(margin_attrib)

        self.paperSize = letter
        self.topMargin = margin_attrib.get('top_margin', 72)
        self.bottomMargin = margin_attrib.get('bottom_margin', 72)
        self.leftMargin = margin_attrib.get('left_margin', 72)
        self.rightMargin = margin_attrib.get('right_margin', 72)
        self.lineHeight = margin_attrib.get('line_height', 72)
        self.lineWidth = self.paperSize[0] - (self.leftMargin + self.rightMargin)

        psalticaTTF = "fonts/EZ Psaltica.TTF"
        oxeiaTTF    = "fonts/EZ Oxeia.ttf"
        omegaTTF    = "fonts/EZ Omega.ttf"
        pdfmetrics.registerFont(TTFont("EZPsaltica",psalticaTTF,asciiReadable=True))
        pdfmetrics.registerFont(TTFont("EZOxeia",oxeiaTTF))
        pdfmetrics.registerFont(TTFont("EZOmega",omegaTTF))
        
        c = canvas.Canvas(self.outFile,pagesize = letter)
        vSpace = self.paperSize[1] - self.topMargin
        vert_pos = self.paperSize[1] - self.topMargin

        # For each tropar
        for troparion in self.bnml.iter('troparion'):
            # Draw title if there is one
            title_elem = troparion.find('title')
            if (title_elem is not None):
                title_text = title_elem.text.strip()
                title_attrib = title_elem.attrib
                title_attrib = self.fill_title_dict(title_attrib)

                c.setFillColor(title_attrib['color'])
                
                vert_pos -= title_attrib['font_size'] + 10
                c.setFont(title_attrib['font'],title_attrib['font_size'])
                c.drawCentredString(self.paperSize[0]/2,vert_pos,title_text)
                vert_pos -= 36
            c.setFillColor(colors.black)

            # Get attributes for neumes
            neume_elem = troparion.find('neumes')
            if (neume_elem is not None):
                neumesText = " ".join(neume_elem.text.strip().split())
                neume_attrib = neume_elem.attrib
                neume_attrib = self.fill_neume_dict(neume_attrib)

            # Get attributes for lyrics
            lyric_elem = troparion.find('lyrics')
            if (lyric_elem is not None):
                lyricsText = " ".join(lyric_elem.text.strip().split())
                lyric_attrib = lyric_elem.attrib
                lyric_attrib = self.fill_lyric_dict(lyric_attrib)

            firstLineOffset = 0     # Offset from dropcap char
            lineSpacing = 72

            neumeChunks = neume_dict.chunkNeumes(neumesText)
            gArray = self.makeGlyphArray(neumeChunks,lyricsText)
            gArray = self.line_break2(gArray,firstLineOffset)
            
            for ga in gArray:
                # TO DO: check if cursor has reached the end of the page
                c.setFont(neume_attrib['font'],neume_attrib['font_size'])
                xpos = self.leftMargin + ga.neumePos
                ypos = vert_pos - (ga.lineNum + 1)*lineSpacing
                c.drawString(xpos,ypos, ga.neumes)

                lyricOffset = 10

                if (ga.lyrics):
                    ypos -= lyricOffset
                    xpos = self.leftMargin + ga.lyricPos
                    c.setFont(lyric_attrib['font'],lyric_attrib['font_size'])
                    #if (ga.lyrics[-1] == "_"):
                    #    ga.lyrics += "_"
                    c.drawString(xpos,ypos,ga.lyrics)



            
        c.showPage()
        try:
            c.save()
        except IOError:
            print "Could not save file"
            
            
    def makeGlyphArray(self,neumeChunks,lyrics = None):
        lyricArray = re.split(' ',lyrics)
        i, lPtr = 0, 0
        gArray = []
        while(i < len(neumeChunks)):
            # Grab next chunk
            nc = neumeChunks[i]
            if (neume_dict.takesLyric(nc[0])):
                # chunk needs a lyric
                if (lPtr < len(lyricArray)):
                    lyr = lyricArray[lPtr]
                else:
                    lyr = ' '
                lPtr += 1
                g = Glyph(neumes=neume_dict.translate(nc),lyrics=lyr)
                # To Do: see if lyric ends with '_' and if lyrics are
                # wider than the neume, then combine with next chunk
            else: 
                # no lyric needed
                g = Glyph(neume_dict.translate(nc))
            g.calc_width()
            
            gArray.append(g)
            i += 1
        return gArray

        
    def line_break2(self,glyphArray,firstLineOffset):
        """Break neumes and lyrics into lines, currently greedy
        Rework this to return a list of lines!!!!"""
        cr = Cursor(firstLineOffset,0)

        # should be able to override these params in xml
        charSpace = 2 # avg spacing between characters
        lineWidth = self.lineWidth

        nlines = 0
        for g in glyphArray:
            if (cr.x + g.width) >= lineWidth:
                cr.x = 0
                nlines += 1
            g.lineNum = nlines
            adjLyricPos, adjNeumePos = 0, 0
            if g.nWidth >= g.lWidth:
                # center text
                adjLyricPos = (g.width - g.lWidth) / 2.
                
            else:
                # center neume
                adjNeumePos = (g.width - g.nWidth) / 2.

            g.neumePos = cr.x + adjNeumePos
            g.lyricPos = cr.x + adjLyricPos
            cr.x += g.width + charSpace

        return glyphArray

            
    def linebreak(self,neumes,lyrics = None):
        """Break neumes and lyrics into lines"""
        cr = Cursor(0,0)
        lyricArray = re.split(' ',lyrics)
        # If lyric spans multiple neumes
        #   then see if lyric is wider than span
        #   else see if width of glypch is max of neume and lyric
        charSpace = 4 # default space between characters
        textOffset = 20 # default space lyrics appear below neumes
        #neumeArray = neume_dict.translate(neumes).split(' ')
        neumeArray = neumes.split(' ')
        neumePos = []
        lyricPos = []
        lyricIdx = 0
        for neume in neumeArray:
            #print("Neume length: " + str(pdfmetrics.stringWidth(neume,'EZPsaltica',24)))
            nWidth = pdfmetrics.stringWidth(neume_dict.translate(neume),'EZPsaltica',self.nFontSize)
            if nWidth > 1.0: # if it's not a gorgon or other small symbol
                # Neume might take lyric
                if lyricIdx < len(lyricArray):
                    lyr = lyricArray[lyricIdx]
                else:
                    lyr = ""
                lWidth = pdfmetrics.stringWidth(lyr,lyric_attrib['font'],lyric_attrib['font_size'])
                # Glyph width will be the max of the two if lyric isn't stretched out
                # across multiple neumes
                addLyric = False
                #if (lyr[-1] != "_") & (neume_dict.takesLyric(neume)):
                if (neume_dict.takesLyric(neume)):
                    glWidth = max(nWidth,lWidth)
                    lyricIdx += 1
                    addLyric = True
                else:
                    glWidth = nWidth
                if (glWidth + cr.x) >= self.lineWidth: # line break
                    cr.x, cr.y = 0, cr.y - self.lineHeight
                    # does it take a lyric syllable?
                    neumePos.append((cr.x,cr.y,neume_dict.translate(neume)))
                else: # no line break
                    # does it take a lyric syllable?
                    neumePos.append((cr.x,cr.y,neume_dict.translate(neume)))
                if (addLyric):
                    lyricPos.append((cr.x,cr.y-textOffset,lyr))
                cr.x += glWidth + charSpace
                
            else:
                # offsets for gorgon
                # offsets for apli
                # offset for kentima
                # offsets for omalon
                # offsets for antikenoma
                # offsets for eteron
                neumePos.append((cr.x - charSpace,cr.y,neume_dict.translate(neume)))
            
        #print neumePos
        return neumePos, lyricPos

    def fill_page_dict(self, page_dict):
        for attrib_name in page_dict:
            if page_dict[attrib_name]:
                page_dict[attrib_name] = int(page_dict[attrib_name])
        return page_dict

    def fill_title_dict(self, title_dict):
        if not title_dict.has_key('color'):
           title_dict['color'] = colors.Color(0,0,1,1)
        else:
            """parse the color"""
            if title_dict['color'] == "blue":
                title_dict['color'] = colors.blue
            elif re.match("#[0-9a-fA-F]{6}",title_dict['color']):
                col = [z/255. for z in hex_to_rgb(title_dict['color'])]
                title_dict['color'] = colors.Color(col[0],col[1],col[2],1)
            else:
                title_dict['color'] = color.black
        
        if not title_dict.has_key('font_size'):
            title_dict['font_size'] = 14
        else:
            title_dict['font_size'] = int(title_dict['font_size'])
        
        if not title_dict.has_key('font'):
            title_dict['font'] = "EZOmega"

        return title_dict

    def fill_lyric_dict(self, lyric_dict):
        if not lyric_dict.has_key('font_size'):
            lyric_dict['font_size'] = 14
        else:
            lyric_dict['font_size'] = int(lyric_dict['font_size'])

        if not lyric_dict.has_key('font'):
            lyric_dict['font'] = "EZOmega"

        return lyric_dict

    def fill_neume_dict(self, neume_dict):
        if not neume_dict.has_key('font_size'):
            neume_dict['font_size'] = 20
        else:
            neume_dict['font_size'] = int(neume_dict['font_size'])

        if not neume_dict.has_key('font'):
            neume_dict['font'] = "EZPsaltica"

        return neume_dict
<commit_msg>Calculate glyph width based on font and size specified in xml.<commit_after>import neume_dict
from glyphs import Glyph, GlyphLine

from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.pagesizes import letter
import reportlab.lib.colors as colors

import sys
import xml.etree.ElementTree as ET
import re

class Cursor:
    def __init__(self,x=0,y=0):
        self.x = x
        self.y = y
        
class Kassia:
    """Base class for package"""
    def __init__(self,fileName,outFile = "out.pdf"):
        self.fileName = fileName # input file
        self.outFile = outFile # output file
        try:
            open(fileName,"r")
            fileReadable = True
        except IOError:
            fileReadable = False
            print "Not readable"
        
        if fileReadable:
            self.parseFile()
            self.createPDF()
        
    def parseFile(self):
        try:
            bnmlTree = ET.parse(self.fileName)
            bnml = bnmlTree.getroot()
            self.bnml = bnml   
               
        except ET.ParseError:
            print "Failed to parse XML file"
            
    def createPDF(self):
        """Create PDF output file"""
        # Parse page layout and formatting
        if (self.bnml is not None):
            margin_attrib = self.bnml.attrib
            margin_attrib = self.fill_page_dict(margin_attrib)

        self.paperSize = letter
        self.topMargin = margin_attrib.get('top_margin', 72)
        self.bottomMargin = margin_attrib.get('bottom_margin', 72)
        self.leftMargin = margin_attrib.get('left_margin', 72)
        self.rightMargin = margin_attrib.get('right_margin', 72)
        self.lineHeight = margin_attrib.get('line_height', 72)
        self.lineWidth = self.paperSize[0] - (self.leftMargin + self.rightMargin)

        psalticaTTF = "fonts/EZ Psaltica.TTF"
        oxeiaTTF    = "fonts/EZ Oxeia.ttf"
        omegaTTF    = "fonts/EZ Omega.ttf"
        pdfmetrics.registerFont(TTFont("EZPsaltica",psalticaTTF,asciiReadable=True))
        pdfmetrics.registerFont(TTFont("EZOxeia",oxeiaTTF))
        pdfmetrics.registerFont(TTFont("EZOmega",omegaTTF))

        # Set more defaults, will overwrite with xml later
        self.neumeFont = {}
        self.neumeFont['font'] = 'EZPsaltica'
        self.neumeFont['font_size'] = 20
        self.lyricFont = {}
        self.lyricFont['font'] = 'EZOmega'
        self.lyricFont['font_size'] = 12

        c = canvas.Canvas(self.outFile,pagesize = letter)
        vSpace = self.paperSize[1] - self.topMargin
        vert_pos = self.paperSize[1] - self.topMargin

        # For each tropar
        for troparion in self.bnml.iter('troparion'):
            # Draw title if there is one
            title_elem = troparion.find('title')
            if (title_elem is not None):
                title_text = title_elem.text.strip()
                title_attrib = title_elem.attrib
                title_attrib = self.fill_title_dict(title_attrib)

                c.setFillColor(title_attrib['color'])
                
                vert_pos -= title_attrib['font_size'] + 10
                c.setFont(title_attrib['font'],title_attrib['font_size'])
                c.drawCentredString(self.paperSize[0]/2,vert_pos,title_text)
                vert_pos -= 36
            c.setFillColor(colors.black)

            # Get attributes for neumes
            neume_elem = troparion.find('neumes')
            if (neume_elem is not None):
                neumesText = " ".join(neume_elem.text.strip().split())
                neume_attrib = neume_elem.attrib
                # Update the defaults with info from the xml
                self.neumeFont.update(self.fill_neume_dict(neume_attrib))

            # Get attributes for lyrics
            lyric_elem = troparion.find('lyrics')
            if (lyric_elem is not None):
                lyricsText = " ".join(lyric_elem.text.strip().split())
                lyric_attrib = lyric_elem.attrib
                self.lyricFont.update(self.fill_lyric_dict(lyric_attrib))

            firstLineOffset = 0     # Offset from dropcap char
            lineSpacing = 72

            neumeChunks = neume_dict.chunkNeumes(neumesText)
            gArray = self.makeGlyphArray(neumeChunks,lyricsText)
            gArray = self.line_break2(gArray,firstLineOffset)
            
            for ga in gArray:
                # TO DO: check if cursor has reached the end of the page
                c.setFont(self.neumeFont['font'],self.neumeFont['font_size'])
                xpos = self.leftMargin + ga.neumePos
                ypos = vert_pos - (ga.lineNum + 1)*lineSpacing
                c.drawString(xpos,ypos, ga.neumes)

                lyricOffset = 10

                if (ga.lyrics):
                    ypos -= lyricOffset
                    xpos = self.leftMargin + ga.lyricPos
                    c.setFont(self.lyricFont['font'],self.lyricFont['font_size'])
                    #if (ga.lyrics[-1] == "_"):
                    #    ga.lyrics += "_"
                    c.drawString(xpos,ypos,ga.lyrics)



            
        c.showPage()
        try:
            c.save()
        except IOError:
            print "Could not save file"
            
            
    def makeGlyphArray(self,neumeChunks,lyrics = None):
        lyricArray = re.split(' ',lyrics)
        i, lPtr = 0, 0
        gArray = []
        while(i < len(neumeChunks)):
            # Grab next chunk
            nc = neumeChunks[i]
            if (neume_dict.takesLyric(nc[0])):
                # chunk needs a lyric
                if (lPtr < len(lyricArray)):
                    lyr = lyricArray[lPtr]
                else:
                    lyr = ' '
                lPtr += 1
                g = Glyph(neumes=neume_dict.translate(nc),lyrics=lyr)
                # To Do: see if lyric ends with '_' and if lyrics are
                # wider than the neume, then combine with next chunk
            else: 
                # no lyric needed
                g = Glyph(neume_dict.translate(nc))
            g.calc_width(self.neumeFont['font'], self.neumeFont['font_size'], self.lyricFont['font'], self.lyricFont['font_size'])

            gArray.append(g)
            i += 1
        return gArray

        
    def line_break2(self,glyphArray,firstLineOffset):
        """Break neumes and lyrics into lines, currently greedy
        Rework this to return a list of lines!!!!"""
        cr = Cursor(firstLineOffset,0)

        # should be able to override these params in xml
        charSpace = 2 # avg spacing between characters
        lineWidth = self.lineWidth

        nlines = 0
        for g in glyphArray:
            if (cr.x + g.width) >= lineWidth:
                cr.x = 0
                nlines += 1
            g.lineNum = nlines
            adjLyricPos, adjNeumePos = 0, 0
            if g.nWidth >= g.lWidth:
                # center text
                adjLyricPos = (g.width - g.lWidth) / 2.
                
            else:
                # center neume
                adjNeumePos = (g.width - g.nWidth) / 2.

            g.neumePos = cr.x + adjNeumePos
            g.lyricPos = cr.x + adjLyricPos
            cr.x += g.width + charSpace

        return glyphArray

            
    def linebreak(self,neumes,lyrics = None):
        """Break neumes and lyrics into lines"""
        cr = Cursor(0,0)
        lyricArray = re.split(' ',lyrics)
        # If lyric spans multiple neumes
        #   then see if lyric is wider than span
        #   else see if width of glypch is max of neume and lyric
        charSpace = 4 # default space between characters
        textOffset = 20 # default space lyrics appear below neumes
        #neumeArray = neume_dict.translate(neumes).split(' ')
        neumeArray = neumes.split(' ')
        neumePos = []
        lyricPos = []
        lyricIdx = 0
        for neume in neumeArray:
            #print("Neume length: " + str(pdfmetrics.stringWidth(neume,'EZPsaltica',24)))
            nWidth = pdfmetrics.stringWidth(neume_dict.translate(neume),'EZPsaltica',self.nFontSize)
            if nWidth > 1.0: # if it's not a gorgon or other small symbol
                # Neume might take lyric
                if lyricIdx < len(lyricArray):
                    lyr = lyricArray[lyricIdx]
                else:
                    lyr = ""
                lWidth = pdfmetrics.stringWidth(lyr,lyric_attrib['font'],lyric_attrib['font_size'])
                # Glyph width will be the max of the two if lyric isn't stretched out
                # across multiple neumes
                addLyric = False
                #if (lyr[-1] != "_") & (neume_dict.takesLyric(neume)):
                if (neume_dict.takesLyric(neume)):
                    glWidth = max(nWidth,lWidth)
                    lyricIdx += 1
                    addLyric = True
                else:
                    glWidth = nWidth
                if (glWidth + cr.x) >= self.lineWidth: # line break
                    cr.x, cr.y = 0, cr.y - self.lineHeight
                    # does it take a lyric syllable?
                    neumePos.append((cr.x,cr.y,neume_dict.translate(neume)))
                else: # no line break
                    # does it take a lyric syllable?
                    neumePos.append((cr.x,cr.y,neume_dict.translate(neume)))
                if (addLyric):
                    lyricPos.append((cr.x,cr.y-textOffset,lyr))
                cr.x += glWidth + charSpace
                
            else:
                # offsets for gorgon
                # offsets for apli
                # offset for kentima
                # offsets for omalon
                # offsets for antikenoma
                # offsets for eteron
                neumePos.append((cr.x - charSpace,cr.y,neume_dict.translate(neume)))
            
        #print neumePos
        return neumePos, lyricPos

    def fill_page_dict(self, page_dict):
        for attrib_name in page_dict:
            if page_dict[attrib_name]:
                page_dict[attrib_name] = int(page_dict[attrib_name])
        return page_dict

    def fill_title_dict(self, title_dict):
        if not title_dict.has_key('color'):
           title_dict['color'] = colors.Color(0,0,1,1)
        else:
            """parse the color"""
            if title_dict['color'] == "blue":
                title_dict['color'] = colors.blue
            elif re.match("#[0-9a-fA-F]{6}",title_dict['color']):
                col = [z/255. for z in hex_to_rgb(title_dict['color'])]
                title_dict['color'] = colors.Color(col[0],col[1],col[2],1)
            else:
                title_dict['color'] = color.black
        
        if not title_dict.has_key('font_size'):
            title_dict['font_size'] = 14
        else:
            title_dict['font_size'] = int(title_dict['font_size'])
        
        if not title_dict.has_key('font'):
            title_dict['font'] = "EZOmega"

        return title_dict

    def fill_lyric_dict(self, lyric_dict):
        if not lyric_dict.has_key('font_size'):
            lyric_dict['font_size'] = 14
        else:
            lyric_dict['font_size'] = int(lyric_dict['font_size'])

        return lyric_dict

    def fill_neume_dict(self, neume_dict):
        if not neume_dict.has_key('font_size'):
            neume_dict['font_size'] = 20
        else:
            neume_dict['font_size'] = int(neume_dict['font_size'])

        return neume_dict

{'fraction_non_alphanumeric': 0.09722711267605634, 'fraction_numerical': 0.006954225352112676, 'mean_word_length': 1.661161864605294, 'pattern_counts': {'":': 2, '<': 9, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 7}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2440020', 'n_tokens_mistral': 7053, 'n_tokens_neox': 6675, 'n_words': 1469}