diff options
Diffstat (limited to '')
-rw-r--r-- | examples/psftoneletters.py | 327 |
1 files changed, 327 insertions, 0 deletions
diff --git a/examples/psftoneletters.py b/examples/psftoneletters.py new file mode 100644 index 0000000..5fd871f --- /dev/null +++ b/examples/psftoneletters.py @@ -0,0 +1,327 @@ +#!/usr/bin/env python +from __future__ import unicode_literals +'''Creates Latin script tone letters (pitch contours)''' +__url__ = 'http://github.com/silnrsi/pysilfont' +__copyright__ = 'Copyright (c) 2017 SIL International (http://www.sil.org)' +__license__ = 'Released under the MIT License (http://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() |