Is Fontlab's Paste Special > Left / Right Kerning reliable?

Hi there,

Does anyone know if the command Paste Special > Left / Right Kerning in Fontlab is reliable?

Thanks in advance.

Comments

  • Chris Lozos
    Chris Lozos Posts: 1,458
    Not always ;-)
  • IIRC FontLab 5 does not paste kerning reliably. I think someone—John Hudson and Karsten Luecke?—wrote scripts to copy/paste kerning correctly. There were great Typophile threads about this…
  • I didn't know that feature would be used! Ramiro what is the situation, maybe there would be another solution?
  • Ramiro Espinoza
    Ramiro Espinoza Posts: 839
    edited January 2016
    I need to export / import only some blocks of a kerning table. That's it. Too complicated to explain why.
  • Kent Lew
    Kent Lew Posts: 944
    I think the main issue is when you copy a selection of glyphs and an early glyph has a pair with a glyph that is later in the selection. When the first glyph is pasted, if a second glyph is not yet present as a key in the font, the second glyph/value is not assigned to the first. Something like that.

    There are probably better ways around this using a Python script and RoboFab f.kerning objects.

    Depending upon the scope and structure of the kerning to be transferred, I’ve also gone at this before via AFMs with some intermediate GREP work before appending/replacing.
  • John Hudson
    John Hudson Posts: 3,227
    I need to export / import only some blocks of a kerning table.

    I wouldn't try to use the paste kerning option for that. What you want is a script that allows export of kerning for selected glyphs t
    o a text file; the corresponding import script should have options to add to or replace existing kerning for the same glyphs in the target font source. I have scripts like this, but they're not mine to share. I suggest contacting Karsten.
  • Perhaps this would do - I have reworked one of my scripts, so it could suit your needs. It works by either glyph names or glyph references that form a kerning pair. You could use a dictionary or multiple lists, and it will copy kerning information from one pair to another for all MMM layers.
    #FLM: Copy Kerning Pair to Pair v.1.0
    # Vassil Kateliev (2016)
    
    # - Dependancies ----------------
    from FL import *
    
    # - Functions ----------------
    def copyKerningPair2Pair(leftSRC, rightSRC, leftDST, rightDST):
    	# - Source Glyph
    	if isinstance(leftSRC, basestring) and isinstance(rightSRC, basestring):
    		leftSRC = fl.font[fl.font.FindGlyph(leftSRC)]
    		rightSRC = fl.font[fl.font.FindGlyph(rightSRC)] #fl.font.FindGlyph(rightSRC)
    
    	# - Destination Glyph
    	if isinstance(leftDST, basestring) and isinstance(rightDST, basestring):
    		leftDST = fl.font[fl.font.FindGlyph(leftDST)]
    		rightDST = fl.font[fl.font.FindGlyph(rightDST)] #fl.font.FindGlyph(rightDST)
    
    	if len(leftSRC.kerning):
    		keySRC = [kpair for kpair in range(len(leftSRC.kerning)) if leftSRC.kerning[kpair].key == rightSRC.index]
    
    		if keySRC:
    			if len(leftDST.kerning):
    				keyDST = [kpair for kpair in range(len(leftDST.kerning)) if leftDST.kerning[kpair].key == rightDST.index]
    
    				if keyDST:
    					for layer in range(leftDST.layers_number):
    						leftDST.kerning[keyDST[0]].values[layer] = leftSRC.kerning[keySRC[0]].values[layer]
    
    					print 'DONE:\tCopied kerning data from %s to %s.' %(leftSRC.name + ':' + rightSRC.name, leftDST.name + ':' + rightDST.name)
    
    				else:
    					newKernPair = KerningPair(leftSRC.kerning[keySRC[0]])
    					leftDST.kerning.append(newKernPair)
    					print 'WARN:\tDestination pair %s not found! Created NEW!' %(leftDST.name + ':' + rightDST.name)
    		else:
    			print 'ERROR:\tSource pair %s not found!' %(leftSRC.name + ':' + rightSRC.name)
    
    	else:
    		print 'ERROR:\tSource pair %s not found!' %(leftSRC.name + ':' + rightSRC.name)
    
    # - Run ---
    # Example using Dictinary:
    copyKerning = {('a','b'):('c','d')}
    for key,value in copyKerning.iteritems():
    	copyKerningPair2Pair(key[0],key[1],value[0],value[1])
    
    fl.UpdateFont()
  • Thanks a lot but I need something that works differently (and I don't work with MMs). I need something that point to specific pairs like in this piece of code:
    
    left = ['exclamdown', 'questiondown', 'quotesingle', 'quoteleft','quoteright','quotesinglbase','hyphen', 'period', 'parenleft', 'bracketleft', 'braceleft', 'slash']
    
    middle= ['A', 'AE', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Thorn', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
    
    right = ['exclam', 'question', 'quotesingle', 'quoteleft', 'quoteright','hyphen', 'period', 'colon','parenright', 'bracketright', 'braceright', 'slash']
    
    for A in left:
    	for B in middle:
    		print A,B
    		
    for A in middle:
    	for B in right:
    		print A,B
    
    Then the kerning values should be stored in a dictionary (I think) and written in a text file. Other script should be use to import these values in the receiving font. I think I can write it but at the moment I am too busy and my coding skills are more than modest and takes me quite a lot of time.
  • So just to be clear, and I hope, that I understand you correctly: (1) You wish to copy kerning info from [left]:[middle] to [middle]:[right], or (2) you wish to store information for [left]:middle] and [middle]:[right] to a file and import it to another typeface... i presume you need the second option?
  • Ramiro Espinoza
    Ramiro Espinoza Posts: 839
    edited January 2016
    No, iterations between 'left' and 'middle' and 'middle' and 'right' is my system to distinguish left and right kerning. Basically I want to copy / store the kerning values of the pairs generated by:
    
    for A in left:
    	for B in middle:
    		print A,B
    		
    for A in middle:
    	for B in right:
    		print A,B
    
    ...and then to be able to import these values to the same pairs of another font. It shouldn't be difficult.
  • This should do the job (probably..., needs more testing). Uses pickle and dictionary.
    #FLM: Export Kerning Block v.1.0
    # Vassil Kateliev (2016)
    
    # - Dependancies ----------------
    from FL import *
    import itertools, pickle
    
    # - Functions ----------------
    def getKerningValue(fontSRC, leftSRC, rightSRC):
    	# - Source Glyph
    	leftSRC = fontSRC[fontSRC.FindGlyph(leftSRC)]
    	rightSRC = fontSRC[fontSRC.FindGlyph(rightSRC)]
    
    	if len(leftSRC.kerning):
    		keySRC = [kpair for kpair in range(len(leftSRC.kerning)) if leftSRC.kerning[kpair].key == rightSRC.index]
    
    		if len(keySRC):
    			return int(leftSRC.kerning[keySRC[0]].value)
    		else:
    			return None
    	else:
    		return None
    
    def putKerningValue(fontDST, leftDST, rightDST, value):
    	# - Source Glyph
    	leftDST = fontDST[fontDST.FindGlyph(leftDST)]
    	rightDST = fontDST[fontDST.FindGlyph(rightDST)]
    
    	if len(leftDST.kerning):
    		keyDST = [kpair for kpair in range(len(leftDST.kerning)) if leftDST.kerning[kpair].key == rightDST.index]
    
    		if len(keyDST):
    			leftDST.kerning[keyDST[0]].value = value
    			print 'DONE:\tCopied kerning data to %s.' %(leftDST.name + ':' + leftDST.name)
    		else:
    			# TODO: Create new kern pair and assign value! Not implemented, beacuse of iconsistent results!
    			print 'ERR:\tDestination Kerning pair %s not found!' %(leftDST.name + ':' + leftDST.name)
    	else:
    		# TODO: Create new kern pair and assign value! Not implemented, beacuse of iconsistent results!
    		print 'ERR:\tDestination Kerning pair %s not found!' %(leftDST.name + ':' + leftDST.name)
    
    # - Run ---
    # -- Init
    fontSRC = fl.font 	# Source font = CurrentFont! Change to desired font!
    fontDST = fl.font	# Destination font = CurrentFont! Change to desired font!
    fileName = str(fl.font.file_name.replace('.vfb', '')) + '-KerningBlock.p' # Database file
    
    left = ['exclamdown', 'questiondown', 'quotesingle', 'quoteleft','quoteright','quotesinglbase','hyphen', 'period', 'parenleft', 'bracketleft', 'braceleft', 'slash']
    middle= ['A', 'AE', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Thorn', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
    right = ['exclam', 'question', 'quotesingle', 'quoteleft', 'quoteright','hyphen', 'period', 'colon','parenright', 'bracketright', 'braceright', 'slash']
    
    # -- Build
    left2middle = {pair : getKerningValue(fontSRC, pair[0],pair[1]) for pair in itertools.product(left, middle) if getKerningValue(fontSRC, pair[0],pair[1]) != None}
    middle2right = {pair : getKerningValue(fontSRC, pair[0],pair[1]) for pair in itertools.product(middle, right) if getKerningValue(fontSRC, pair[0],pair[1]) != None}
    
    # -- Export
    exportKerning = left2middle.copy()
    exportKerning.update(middle2right)
    pickle.dump(exportKerning, open(fileName, "wb"))
    
    # -- Import
    importKerning = pickle.load(open(fileName, "rb"))
    print importKerning
    
    for key,value in importKerning.iteritems():
    	putKerningValue(fontDST, key[0],key[1],value)
    
    fl.UpdateFont()
    
  • Thanks a lot! I will try to test it during the weekend.
  • One more remark: The script is now not MMM capable and will not create new pairs, just update the existing ones. I have left place for this "future functionality", but in my experience, so far I had little (and inconsistent) success making Fontlab create new pairs and assigning values to them - most of the time it ends in a mess with duplicate pairs and etc. Perhaps Adam can clarify us the process of adding new pairs (and hopefully in MMM space)
  • Ramiro Espinoza
    Ramiro Espinoza Posts: 839
    edited January 2016
    I need something able to create new pairs in the receiving fonts if necessary.
    What about creating the same pairs first by assigning them any value (for example: kerning[A,B]=1) and then importing the values stored from the inputing font?
    Why don't you use Robofab's objects? 
  • OK, I figured out a (very basic) way to write a txt with my targeted kerning stored. 
    Now I have to see how I import this information into another font. :)
    
    import sys
    sys.dont_write_bytecode = True
    from robofab.world import CurrentFont
    
    font = CurrentFont()
    kerning = font.kerning
    
    target = []
    		
    left = ['exclamdown', 'questiondown', 'quotesingle', 'quoteleft','quoteright','quotesinglbase','hyphen', 'period', 'parenleft', 'bracketleft', 'braceleft', 'slash']
    
    middle= ['A', 'AE', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Thorn', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
    
    right = ['exclam', 'question', 'quotesingle', 'quoteleft', 'quoteright','hyphen', 'period', 'colon','parenright', 'bracketright', 'braceright', 'slash']
    
    for x in left:
    	for y in middle:
    		target.append(  x+' ' + y +' '+ str(kerning[x,y]) )
    		
    for x in middle:
    	for y in right:
    		target.append(  x+' ' + y +' '+ str(kerning[x,y]) )
    
    import os.path
    path = os.path.join( os.path.split(fl.font.file_name)[0] , "Kerning.txt")
    f = open(path, 'w')
    f.write("\r".join(target))
    f.close()
    
    
  • I need something able to create new pairs in the receiving fonts if necessary.
    What about creating the same pairs first by assigning them any value (for example: kerning[A,B]=1) and then importing the values stored from the inputing font?
    Why don't you use Robofab's objects? 
    In reverse order :smile: I am not particularly found of Robofab once I established its limited-to-nonexistent MM capability, so I usually work with Fontlab directly.

    I have revisited my work and the following code will create new pairs, if necessary - it was more simple than I imagined :smile: 

    Note, that this is in Python 2.7 syntax, so it needs one of the last version(s) of Fontlab (5.2). For Python 2.4 the dictionary comprehension must be converted to nested loops the way you do it.

    Why do you need the kerning information saved in .txt file? Do you mean to edit it between import/export sessions? Otherwise i will suggest using pickle.dump() - it is reliable, faster and easier to use.
    #FLM: Export Kerning Block v.1.1
    # Vassil Kateliev (2016)
    
    # - Dependancies ----------------
    from FL import *
    import itertools, pickle
    
    # - Functions ----------------
    def getKerningValue(fontSRC, leftSRC, rightSRC):
    	# - Source Glyph
    	leftSRC = fontSRC[fontSRC.FindGlyph(leftSRC)]
    	rightSRC = fontSRC[fontSRC.FindGlyph(rightSRC)]
    
    	if len(leftSRC.kerning):
    		keySRC = [kpair for kpair in range(len(leftSRC.kerning)) if leftSRC.kerning[kpair].key == rightSRC.index]
    
    		if len(keySRC):
    			return int(leftSRC.kerning[keySRC[0]].value)
    		else:
    			return None
    	else:
    		return None
    
    def putKerningValue(fontDST, leftDST, rightDST, value):
    	# - Source Glyph
    	leftDST = fontDST[fontDST.FindGlyph(leftDST)]
    	rightDST = fontDST[fontDST.FindGlyph(rightDST)]
    
    	if len(leftDST.kerning):
    		keyDST = [kpair for kpair in range(len(leftDST.kerning)) if leftDST.kerning[kpair].key == rightDST.index]
    
    		if len(keyDST):
    			leftDST.kerning[keyDST[0]].value = value
    			print 'DONE:\tCopied kerning data to %s.' %(leftDST.name + ':' + leftDST.name)
    		else:
    			newKernPair = KerningPair(rightDST.index, value)
    			leftDST.kerning.append(newKernPair)
    			print 'WARN:\tDestination Kerning pair %s not found! Created NEW!' %(leftDST.name + ':' + leftDST.name)
    	else:
    		newKernPair = KerningPair(rightDST.index, value)
    		leftDST.kerning.append(newKernPair)
    		print 'WARN:\tDestination Kerning pair %s not found! Created NEW!' %(leftDST.name + ':' + leftDST.name)
    
    # - Run ---
    # -- Init
    fontSRC = fl.font 	# Source font = CurrentFont! Change to desired font!
    fontDST = fl.font	# Destination font = CurrentFont! Change to desired font!
    fileName = str(fl.font.file_name.replace('.vfb', '')) + '-KerningBlock.p' # Database file
    
    left = ['exclamdown', 'questiondown', 'quotesingle', 'quoteleft','quoteright','quotesinglbase','hyphen', 'period', 'parenleft', 'bracketleft', 'braceleft', 'slash']
    middle= ['A', 'AE', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Thorn', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
    right = ['exclam', 'question', 'quotesingle', 'quoteleft', 'quoteright','hyphen', 'period', 'colon','parenright', 'bracketright', 'braceright', 'slash']
    
    # -- Build
    left2middle = {pair : getKerningValue(fontSRC, pair[0],pair[1]) for pair in itertools.product(left, middle) if getKerningValue(fontSRC, pair[0],pair[1]) != None}
    middle2right = {pair : getKerningValue(fontSRC, pair[0],pair[1]) for pair in itertools.product(middle, right) if getKerningValue(fontSRC, pair[0],pair[1]) != None}
    
    # -- Export
    exportKerning = left2middle.copy()
    exportKerning.update(middle2right)
    pickle.dump(exportKerning, open(fileName, "wb"))
    
    # -- Import
    importKerning = pickle.load(open(fileName, "rb"))
    print importKerning
    
    for key,value in importKerning.iteritems():
    	putKerningValue(fontDST, key[0],key[1],value)
    
    fl.UpdateFont()