#!/usr/bin/env python3
from __future__ import unicode_literals
'''Creates Latin script tone letters (pitch contours)'''
__url__ = 'https://github.com/silnrsi/pysilfont'
__copyright__ = 'Copyright (c) 2017 SIL International (https://www.sil.org)'
__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
__author__ = 'Victor Gaultney'

# Usage: psftoneletters ifont ofont
# Assumption is that the named tone letters already exist in the font,
# so this script is only to update (rebuild) them. New tone letter spaces
# in the font can be created with psfbuildcomp.py

# To Do
# Get parameters from lib.plist org.sil.lcg.toneLetters

# main input, output, and execution handled by pysilfont framework
from silfont.core import execute
import silfont.ufo as UFO

from robofab.world import OpenFont

from math import tan, radians, sqrt

suffix = '_toneletters'
argspec = [
   ('ifont',{'help': 'Input font file'}, {'type': 'filename'}),
   ('ofont',{'help': 'Output font file','nargs': '?' }, {'type': 'filename', 'def': "_"+suffix}),
   ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': suffix+'log'})]


def getParameters(font):
    global glyphHeight, marginFlatLeft, marginPointLeft, marginFlatRight, marginPointRight, contourWidth, marginDotLeft, marginDotRight, dotSpacing, italicAngle, radius, strokeHeight, strokeDepth, contourGap, fakeBottom, dotRadius, dotBCP, contourGapDot, fakeBottomDot, anchorHeight, anchorOffset

    source = font.lib.getval("org.sil.lcg.toneLetters")

    strokeThickness = int(source["strokeThickness"]) # total width of stroke (ideally an even number)
    glyphHeight = int(source["glyphHeight"]) # height, including overshoot
    glyphDepth = int(source["glyphDepth"]) # depth - essentially overshoot (typically negative)
    marginFlatLeft = int(source["marginFlatLeft"]) # left sidebearing for straight bar
    marginPointLeft = int(source["marginPointLeft"]) # left sidebearing for endpoints
    marginFlatRight = int(source["marginFlatRight"]) # left sidebearing for straight bar
    marginPointRight = int(source["marginPointRight"]) # left sidebearing for endpoints
    contourWidth = int(source["contourWidth"]) # this is how wide the contour portions are, from the middle
                            # of one end to the other, in the horizontal axis. The actual
                            # bounding box of the contours would then be this plus the
                            # strokeThickness.
    marginDotLeft = int(source["marginDotLeft"]) # left sidebearing for dots
    marginDotRight = int(source["marginDotRight"]) # right sidebearing for dots
    dotSize = int(source["dotSize"]) # the diameter of the dot, normally 150% of the stroke weight
                            #  (ideally an even number)
    dotSpacing = int(source["dotSpacing"]) # the space between the edge of the dot and the
                            # edge of the expanded stroke
    italicAngle = float(source["italicAngle"]) # angle of italic slant, 0 for upright

    radius = round(strokeThickness / 2)
    strokeHeight = glyphHeight - radius                # for the unexpanded stroke
    strokeDepth = glyphDepth + radius
    strokeLength = strokeHeight - strokeDepth
    contourGap = round(strokeLength / 4)            # gap between contour levels
    fakeBottom = strokeDepth - contourGap            # a false 'bottom' for building contours

    dotRadius = round(dotSize / 2)                    # this gets redefined during nine tone process
    dotBCP = round((dotSize / 2) * .55)                # this gets redefined during nine tone process
    contourGapDot = round(( (glyphHeight - dotRadius) - (glyphDepth + dotRadius) ) / 4)
    fakeBottomDot = (glyphDepth + dotRadius) - contourGapDot

    anchorHeight = [ 0 , strokeDepth , (strokeDepth + contourGap) , (strokeDepth + contourGap * 2) , (strokeHeight - contourGap) , strokeHeight ]
    anchorOffset = 20   # hardcoded for now

# drawing functions

def drawLine(glyph,startX,startY,endX,endY):

    dx = (endX - startX)                        # dx of original stroke
    dy = (endY - startY)                        # dy of original stroke
    len = sqrt( dx * dx + dy * dy )                # length of original stroke
    opp = round(dy * (radius / len))                    # offsets for on-curve points
    adj = round(dx * (radius / len))
    oppOff = round(opp * .55)                             # offsets for off-curve from on-curve
    adjOff = round(adj * .55)

    glyph.clearContours()

    pen = glyph.getPen()

    # print startX + opp, startY - adj

    pen.moveTo((startX + opp, startY - adj))
    pen.lineTo((endX + opp, endY - adj))        # first straight line

    bcp1x = endX + opp + adjOff
    bcp1y = endY - adj + oppOff
    bcp2x = endX + adj + oppOff
    bcp2y = endY + opp - adjOff
    pen.curveTo((bcp1x, bcp1y), (bcp2x, bcp2y), (endX + adj, endY + opp))

    bcp1x = endX + adj - oppOff
    bcp1y = endY + opp + adjOff
    bcp2x = endX - opp + adjOff
    bcp2y = endY + adj + oppOff
    pen.curveTo((bcp1x, bcp1y), (bcp2x, bcp2y), (endX - opp, endY + adj))

    pen.lineTo((startX - opp, startY + adj))    # second straight line

    bcp1x = startX - opp - adjOff
    bcp1y = startY + adj - oppOff
    bcp2x = startX - adj - oppOff
    bcp2y = startY - opp + adjOff
    pen.curveTo((bcp1x, bcp1y), (bcp2x, bcp2y), (startX - adj, startY - opp))

    bcp1x = startX - adj + oppOff
    bcp1y = startY - opp - adjOff
    bcp2x = startX + opp - adjOff
    bcp2y = startY - adj - oppOff
    pen.curveTo((bcp1x, bcp1y), (bcp2x, bcp2y), (startX + opp, startY - adj))
    # print startX + opp, startY - adj

    pen.closePath()


def drawDot(glyph,dotX,dotY):

    glyph.clearContours()

    pen = glyph.getPen()

    pen.moveTo((dotX, dotY - dotRadius))
    pen.curveTo((dotX + dotBCP, dotY - dotRadius), (dotX + dotRadius, dotY - dotBCP), (dotX + dotRadius, dotY))
    pen.curveTo((dotX + dotRadius, dotY + dotBCP), (dotX + dotBCP, dotY + dotRadius), (dotX, dotY + dotRadius))
    pen.curveTo((dotX - dotBCP, dotY + dotRadius), (dotX - dotRadius, dotY + dotBCP), (dotX - dotRadius, dotY))
    pen.curveTo((dotX - dotRadius, dotY - dotBCP), (dotX - dotBCP, dotY - dotRadius), (dotX, dotY - dotRadius))
    pen.closePath()


def adjItalX(aiX,aiY):
    newX = aiX + round(tan(radians(italicAngle)) * aiY)
    return newX


def buildComp(f,g,pieces,ancLevelLeft,ancLevelMidLeft,ancLevelMidRight,ancLevelRight):

    g.clear()
    g.width = 0

    for p in pieces:
        g.appendComponent(p, (g.width, 0))
        g.width += f[p].width

    if ancLevelLeft > 0:
        anc_nm = "_TL"
        anc_x = adjItalX(0,anchorHeight[ancLevelLeft])
        if g.name[0:7] == 'TnStaff':
        	anc_x = anc_x - anchorOffset
        anc_y = anchorHeight[ancLevelLeft]
        g.appendAnchor(anc_nm, (anc_x, anc_y))

    if ancLevelMidLeft > 0:
        anc_nm = "_TL"
        anc_x = adjItalX(marginPointLeft + radius,anchorHeight[ancLevelMidLeft])
        anc_y = anchorHeight[ancLevelMidLeft]
        g.appendAnchor(anc_nm, (anc_x, anc_y))

    if ancLevelMidRight > 0:
        anc_nm = "TL"
        anc_x = adjItalX(g.width - marginPointRight - radius,anchorHeight[ancLevelMidRight])
        anc_y = anchorHeight[ancLevelMidRight]
        g.appendAnchor(anc_nm, (anc_x, anc_y))

    if ancLevelRight > 0:
        anc_nm = "TL"
        anc_x = adjItalX(g.width,anchorHeight[ancLevelRight])
        if g.name[0:7] == 'TnStaff':
        	anc_x = anc_x + anchorOffset
        anc_y = anchorHeight[ancLevelRight]
        g.appendAnchor(anc_nm, (anc_x, anc_y))


# updating functions

def updateTLPieces(targetfont):

    f = targetfont

    # set spacer widths
    f["TnLtrSpcFlatLeft"].width = marginFlatLeft + radius
    f["TnLtrSpcPointLeft"].width = marginPointLeft + radius - 1    # -1 corrects final sidebearing
    f["TnLtrSpcFlatRight"].width = marginFlatRight + radius
    f["TnLtrSpcPointRight"].width = marginPointRight + radius - 1    # -1 corrects final sidebearing
    f["TnLtrSpcDotLeft"].width = marginDotLeft + dotRadius
    f["TnLtrSpcDotMiddle"].width = dotRadius + dotSpacing + radius
    f["TnLtrSpcDotRight"].width = dotRadius + marginDotRight

    # redraw bar
    g = f["TnLtrBar"]
    drawLine(g,adjItalX(0,strokeDepth),strokeDepth,adjItalX(0,strokeHeight),strokeHeight)
    g.width = 0

    # redraw contours
    namePre = 'TnLtrSeg'
    for i in range(1,6):
        for j in range(1,6):

            nameFull = namePre + str(i) + str(j)

            if i == 5:                            # this deals with round off errors
                startLevel = strokeHeight
            else:
                startLevel = fakeBottom + i * contourGap
            if j == 5:
                endLevel = strokeHeight
            else:
                endLevel = fakeBottom + j * contourGap

            g = f[nameFull]
            g.width = contourWidth
            drawLine(g,adjItalX(1,startLevel),startLevel,adjItalX(contourWidth-1,endLevel),endLevel)


    # redraw dots
    namePre = 'TnLtrDot'
    for i in range(1,6):

        nameFull = namePre + str(i)

        if i == 5:                            # this deals with round off errors
            dotLevel = glyphHeight - dotRadius
        else:
            dotLevel = fakeBottomDot + i * contourGapDot

        g = f[nameFull]
        drawDot(g,adjItalX(0,dotLevel),dotLevel)


def rebuildTLComps(targetfont):

    f = targetfont

    # staff right
    for i in range(1,6):
        nameFull = 'TnStaffRt' + str(i)
        buildComp(f,f[nameFull],['TnLtrBar','TnLtrSpcFlatRight'],i,0,0,0)

    # staff right no outline
    for i in range(1,6):
        nameFull = 'TnStaffRt' + str(i) + 'no'
        buildComp(f,f[nameFull],['TnLtrSpcFlatRight'],i,0,0,0)

    # staff left
    for i in range(1,6):
        nameFull = 'TnStaffLft' + str(i)
        buildComp(f,f[nameFull],['TnLtrSpcFlatLeft','TnLtrBar'],0,0,0,i)

    # staff left no outline
    for i in range(1,6):
        nameFull = 'TnStaffLft' + str(i) + 'no'
        buildComp(f,f[nameFull],['TnLtrSpcFlatLeft'],0,0,0,i)

    # contours right
    for i in range(1,6):
        for j in range(1,6):
            nameFull = 'TnContRt' + str(i) + str(j)
            segment = 'TnLtrSeg' + str(i) + str(j)
            buildComp(f,f[nameFull],['TnLtrSpcPointLeft',segment],0,i,0,j)

    # contours left
    for i in range(1,6):
        for j in range(1,6):
            nameFull = 'TnContLft' + str(i) + str(j)
            segment = 'TnLtrSeg' + str(i) + str(j)
            buildComp(f,f[nameFull],[segment,'TnLtrSpcPointRight'],i,0,j,0)

    # basic tone letters
    for i in range(1,6):
        nameFull = 'TnLtr' + str(i)
        segment = 'TnLtrSeg' + str(i) + str(i)
        buildComp(f,f[nameFull],['TnLtrSpcPointLeft',segment,'TnLtrBar','TnLtrSpcFlatRight'],0,0,0,0)

    # basic tone letters no outline
    for i in range(1,6):
        nameFull = 'TnLtr' + str(i) + 'no'
        segment = 'TnLtrSeg' + str(i) + str(i)
        buildComp(f,f[nameFull],['TnLtrSpcPointLeft',segment,'TnLtrSpcFlatRight'],0,i,0,0)

    # left stem tone letters
    for i in range(1,6):
        nameFull = 'LftStemTnLtr' + str(i)
        segment = 'TnLtrSeg' + str(i) + str(i)
        buildComp(f,f[nameFull],['TnLtrSpcFlatLeft','TnLtrBar',segment,'TnLtrSpcPointRight'],0,0,0,0)

    # left stem tone letters no outline
    for i in range(1,6):
        nameFull = 'LftStemTnLtr' + str(i) + 'no'
        segment = 'TnLtrSeg' + str(i) + str(i)
        buildComp(f,f[nameFull],['TnLtrSpcFlatLeft',segment,'TnLtrSpcPointRight'],0,0,i,0)

    # dotted tone letters
    for i in range(1,6):
        nameFull = 'DotTnLtr' + str(i)
        dot = 'TnLtrDot' + str(i)
        buildComp(f,f[nameFull],['TnLtrSpcDotLeft',dot,'TnLtrSpcDotMiddle','TnLtrBar','TnLtrSpcFlatRight'],0,0,0,0)

    # dotted left stem tone letters
    for i in range(1,6):
        nameFull = 'DotLftStemTnLtr' + str(i)
        dot = 'TnLtrDot' + str(i)
        buildComp(f,f[nameFull],['TnLtrSpcFlatLeft','TnLtrBar','TnLtrSpcDotMiddle',dot,'TnLtrSpcDotRight'],0,0,0,0)


def doit(args):

    psffont = UFO.Ufont(args.ifont, params = args.paramsobj)
    rffont = OpenFont(args.ifont)
    outfont = args.ofont

    getParameters(psffont)

    updateTLPieces(rffont)
    rebuildTLComps(rffont)


    rffont.save(outfont)

    return

def cmd() : execute(None,doit,argspec)
if __name__ == "__main__": cmd()
