ps23 More bytes fixes.  All ''join()'s fixed
diff --git a/Lib/fontTools/cffLib.py b/Lib/fontTools/cffLib.py
index e5f9f73..cf5755e 100644
--- a/Lib/fontTools/cffLib.py
+++ b/Lib/fontTools/cffLib.py
@@ -328,7 +328,7 @@
 			print("    index count: %s offSize: %s" % (count, offSize))
 		assert offSize <= 4, "offSize too large: %s" % offSize
 		self.offsets = offsets = []
-		pad = '\0' * (4 - offSize)
+		pad = b'\0' * (4 - offSize)
 		for index in range(count+1):
 			chunk = file.read(offSize)
 			chunk = pad + chunk
@@ -853,7 +853,7 @@
 
 	for name in charset[1:]:
 		data.append(packCard16(getNameID(name,strings)))
-	return "".join(data)
+	return bytesjoin(data)
 
 
 def packCharset(charset, isCID, strings):
@@ -889,7 +889,7 @@
 		nLeftFunc = packCard16
 	for first, nLeft in ranges:
 		data.append(packCard16(first) + nLeftFunc(nLeft))
-	return "".join(data)
+	return bytesjoin(data)
 
 def parseCharset0(numGlyphs, file, strings, isCID):
 	charset = [".notdef"]
@@ -1049,7 +1049,7 @@
 		if code is None:
 			code = 0
 		data.append(packCard8(code))
-	return "".join(data)
+	return bytesjoin(data)
 
 def packEncoding1(charset, encoding, strings):
 	format = 1
@@ -1082,7 +1082,7 @@
 		if first == -1:  # unencoded
 			first = 0
 		data.append(packCard8(first) + packCard8(nLeft))
-	return "".join(data)
+	return bytesjoin(data)
 
 
 class FDArrayConverter(TableConverter):
@@ -1138,7 +1138,7 @@
 	data = [packCard8(format)]
 	for index in fdSelectArray:
 		data.append(packCard8(index))
-	return "".join(data)
+	return bytesjoin(data)
 
 
 def packFDSelect3(fdSelectArray):
@@ -1161,7 +1161,7 @@
 		data.append(packCard16(fdRange[0]))
 		data.append(packCard8(fdRange[1]))
 	data.append(packCard16(sentinelGID))
-	return "".join(data)
+	return bytesjoin(data)
 
 
 class FDSelectCompiler:
@@ -1350,7 +1350,7 @@
 				arghandler = getattr(self, "arg_" + argType)
 				data.append(arghandler(value))
 			data.append(op)
-		return "".join(data)
+		return bytesjoin(data)
 	
 	def toFile(self, file):
 		file.write(self.compile("toFile"))
@@ -1363,7 +1363,7 @@
 		data = []
 		for num in value:
 			data.append(encodeNumber(num))
-		return "".join(data)
+		return bytesjoin(data)
 	def arg_delta(self, value):
 		out = []
 		last = 0
@@ -1373,7 +1373,7 @@
 		data = []
 		for num in out:
 			data.append(encodeNumber(num))
-		return "".join(data)
+		return bytesjoin(data)
 
 
 def encodeNumber(num):
diff --git a/Lib/fontTools/fondLib.py b/Lib/fontTools/fondLib.py
index 4b7b04d..536c694 100644
--- a/Lib/fontTools/fondLib.py
+++ b/Lib/fontTools/fondLib.py
@@ -289,7 +289,7 @@
 			for firstchar, secondchar, kerndistance in table:
 				data.append(struct.pack(">cch", bytechr(firstchar), bytechr(secondchar), kerndistance))
 		
-		data = ''.join(data)
+		data = bytesjoin(data)
 		
 		if DEBUG:
 			print("kerning table is the same?", self._rawkerningtables == data and 'yes.' or 'no.')
diff --git a/Lib/fontTools/misc/eexec.py b/Lib/fontTools/misc/eexec.py
index c6c8f7b..1264355 100644
--- a/Lib/fontTools/misc/eexec.py
+++ b/Lib/fontTools/misc/eexec.py
@@ -32,7 +32,7 @@
 	for cipher in cipherstring:
 		plain, R = _decryptChar(cipher, R)
 		plainList.append(plain)
-	plainstring = ''.join(plainList)
+	plainstring = strjoin(plainList)
 	return plainstring, int(R)
 
 def encrypt(plainstring, R):
@@ -40,7 +40,7 @@
 	for plain in plainstring:
 		cipher, R = _encryptChar(plain, R)
 		cipherList.append(cipher)
-	cipherstring = ''.join(cipherList)
+	cipherstring = strjoin(cipherList)
 	return cipherstring, int(R)
 
 
@@ -50,7 +50,7 @@
 
 def deHexString(h):
 	import binascii
-	h = "".join(h.split())
+	h = strjoin(h.split())
 	return binascii.unhexlify(h)
 
 
diff --git a/Lib/fontTools/misc/macCreatorType.py b/Lib/fontTools/misc/macCreatorType.py
index ab5587a..bcbc4cb 100644
--- a/Lib/fontTools/misc/macCreatorType.py
+++ b/Lib/fontTools/misc/macCreatorType.py
@@ -10,7 +10,7 @@
 def _reverseString(s):
 	s = list(s)
 	s.reverse()
-	return "".join(s)
+	return strjoin(s)
 
 
 def getMacCreatorAndType(path):
diff --git a/Lib/fontTools/misc/psCharStrings.py b/Lib/fontTools/misc/psCharStrings.py
index 41e2d01..93685bd 100644
--- a/Lib/fontTools/misc/psCharStrings.py
+++ b/Lib/fontTools/misc/psCharStrings.py
@@ -49,18 +49,15 @@
 		return -(b0-251)*256 - b1 - 108, index+1
 	
 	def read_shortInt(self, b0, data, index):
-		bin = data[index] + data[index+1]
-		value, = struct.unpack(">h", bin)
+		value, = struct.unpack(">h", data[index:index+2])
 		return value, index+2
 	
 	def read_longInt(self, b0, data, index):
-		bin = data[index] + data[index+1] + data[index+2] + data[index+3]
-		value, = struct.unpack(">l", bin)
+		value, = struct.unpack(">l", data[index:index+4])
 		return value, index+4
 	
 	def read_fixed1616(self, b0, data, index):
-		bin = data[index] + data[index+1] + data[index+2] + data[index+3]
-		value, = struct.unpack(">l", bin)
+		value, = struct.unpack(">l", data[index:index+4])
 		return value / 65536, index+4
 	
 	def read_realNumber(self, b0, data, index):
@@ -202,7 +199,7 @@
 
 def encodeFixed(f, pack=struct.pack):
 	# For T2 only
-	return "\xff" + pack(">l", int(round(f * 65536)))
+	return b"\xff" + pack(">l", int(round(f * 65536)))
 
 def encodeFloat(f):
 	# For CFF only, used in cffLib
@@ -302,7 +299,7 @@
 			else:
 				assert 0, "unsupported type: %s" % tp
 		try:
-			bytecode = "".join(bytecode)
+			bytecode = bytesjoin(bytecode)
 		except TypeError:
 			print(bytecode)
 			raise
@@ -375,7 +372,7 @@
 						bits = []
 						for byte in hintMask:
 							bits.append(num2binary(byteord(byte), 8))
-						hintMask = ''.join(bits)
+						hintMask = strjoin(bits)
 						line = ' '.join(args + [token, hintMask])
 					else:
 						line = ' '.join(args + [token])
@@ -390,7 +387,7 @@
 		if attrs.get("raw"):
 			self.setBytecode(readHex(content))
 			return
-		content = "".join(content)
+		content = strjoin(content)
 		content = content.split()
 		program = []
 		end = len(content)
diff --git a/Lib/fontTools/misc/py23.py b/Lib/fontTools/misc/py23.py
index 8f1e6b4..99f06da 100644
--- a/Lib/fontTools/misc/py23.py
+++ b/Lib/fontTools/misc/py23.py
@@ -31,6 +31,8 @@
 	except ImportError:
 		from io import BytesIO as StringIO
 
+def strjoin(iterable):
+	return ''.join(iterable)
 if str == bytes:
 	class Tag(str):
 		def tobytes(self):
@@ -39,15 +41,14 @@
 			else:
 				return self.encode('latin-1')
 
-	def tostr(s):
+	def tostr(s, encoding='ascii'):
 		if not isinstance(s, str):
-			return s.encode('ascii')
+			return s.encode(encoding)
 		else:
 			return s
 	tobytes = tostr
 
-	def bytesjoin(iterable):
-		return ''.join(iterable)
+	bytesjoin = strjoin
 else:
 	class Tag(str):
 
@@ -68,14 +69,14 @@
 		def tobytes(self):
 			return self.encode('latin-1')
 
-	def tostr(s):
+	def tostr(s, encoding='ascii'):
 		if not isinstance(s, str):
-			return s.decode('ascii')
+			return s.decode(encoding)
 		else:
 			return s
-	def tobytes(s):
+	def tobytes(s, encoding='ascii'):
 		if not isinstance(s, bytes):
-			return s.encode('ascii')
+			return s.encode(encoding)
 		else:
 			return s
 
diff --git a/Lib/fontTools/misc/textTools.py b/Lib/fontTools/misc/textTools.py
index 1820acd..d657763 100644
--- a/Lib/fontTools/misc/textTools.py
+++ b/Lib/fontTools/misc/textTools.py
@@ -13,17 +13,17 @@
 
 def readHex(content):
 	"""Convert a list of hex strings to binary data."""
-	return deHexStr(''.join([ chunk for chunk in content if isinstance(chunk,str) ]))
+	return deHexStr(strjoin([ chunk for chunk in content if isinstance(chunk,str) ]))
 
 def deHexStr(hexdata):
 	"""Convert a hex string to binary data."""
-	hexdata = ''.join(hexdata.split())
+	hexdata = strjoin(hexdata.split())
 	if len(hexdata) % 2:
 		hexdata = hexdata + "0"
 	data = []
 	for i in range(0, len(hexdata), 2):
 		data.append(bytechr(int(hexdata[i:i+2], 16)))
-	return "".join(data)
+	return bytesjoin(data)
 
 
 def hexStr(data):
@@ -56,7 +56,7 @@
 
 
 def binary2num(bin):
-	bin = ''.join(bin.split())
+	bin = strjoin(bin.split())
 	l = 0
 	for digit in bin:
 		l = l << 1
diff --git a/Lib/fontTools/misc/xmlWriter.py b/Lib/fontTools/misc/xmlWriter.py
index a1ba65f..116467d 100644
--- a/Lib/fontTools/misc/xmlWriter.py
+++ b/Lib/fontTools/misc/xmlWriter.py
@@ -146,7 +146,7 @@
 			return c
 		else:
 			return "&#" + repr(n) + ";"
-	return "".join(map(escapechar, data))
+	return strjoin(map(escapechar, data))
 
 def escape16bit(data):
 	import array
@@ -163,7 +163,7 @@
 			return chr(n)
 		else:
 			return "&#" + repr(n) + ";"
-	return "".join(map(escapenum, a))
+	return strjoin(map(escapenum, a))
 
 
 def hexStr(s):
diff --git a/Lib/fontTools/nfntLib.py b/Lib/fontTools/nfntLib.py
index 0e54493..49cea20 100644
--- a/Lib/fontTools/nfntLib.py
+++ b/Lib/fontTools/nfntLib.py
@@ -72,8 +72,8 @@
 		for i in range(nEntries):
 			owTable[i] = bytechr(self.offsetTable[i]) + bytechr(self.widthTable[i])
 			locTable[i] = struct.pack("h", self.locTable[i])
-		owTable = ''.join(owTable)
-		locTable = ''.join(locTable)
+		owTable = bytesjoin(owTable)
+		locTable = bytesjoin(locTable)
 		assert len(locTable) == len(owTable) == 2 * (self.lastChar - self.firstChar + 3)
 		return header + self.bits + locTable + owTable
 	
@@ -159,7 +159,7 @@
 				for x in range(8):
 					byte = byte | ((bitImage[8 * xByte + x, y] & 0x01) << (7 - x))
 				bits.append(bytechr(byte))
-		bits = ''.join(bits)
+		bits = bytesjoin(bits)
 		
 		# assign values
 		self.fontType = 0x9000
diff --git a/Lib/fontTools/t1Lib.py b/Lib/fontTools/t1Lib.py
index 8021795..02b5257 100644
--- a/Lib/fontTools/t1Lib.py
+++ b/Lib/fontTools/t1Lib.py
@@ -170,7 +170,7 @@
 				raise T1Error('bad chunk code: ' + repr(code))
 	finally:
 		Res.CloseResFile(resRef)
-	data = ''.join(data)
+	data = bytesjoin(data)
 	assertType1(data)
 	return data
 
@@ -194,7 +194,7 @@
 		if onlyHeader:
 			break
 	f.close()
-	data = ''.join(data)
+	data = bytesjoin(data)
 	assertType1(data)
 	return data
 
@@ -212,7 +212,7 @@
 			data.append(deHexString(chunk))
 		else:
 			data.append(chunk)
-	return ''.join(data)
+	return bytesjoin(data)
 
 # file writing tools
 
@@ -308,7 +308,7 @@
 				data.append(chunk[:-len(EEXECBEGIN)-1])
 			else:
 				data.append(chunk)
-	return ''.join(data)
+	return bytesjoin(data)
 
 def findEncryptedChunks(data):
 	chunks = []
@@ -334,7 +334,7 @@
 	return chunks
 
 def deHexString(hexstring):
-	return eexec.deHexString(''.join(hexstring.split()))
+	return eexec.deHexString(strjoin(hexstring.split()))
 
 
 # Type 1 assertion
diff --git a/Lib/fontTools/ttLib/tables/C_B_D_T_.py b/Lib/fontTools/ttLib/tables/C_B_D_T_.py
index 9aa645d..cd2a975 100644
--- a/Lib/fontTools/ttLib/tables/C_B_D_T_.py
+++ b/Lib/fontTools/ttLib/tables/C_B_D_T_.py
@@ -52,7 +52,7 @@
 		dataList.append(sstruct.pack(smallGlyphMetricsFormat, self.metrics))
 		dataList.append(struct.pack(">L", len(self.imageData)))
 		dataList.append(self.imageData)
-		return ''.join(dataList)
+		return bytesjoin(dataList)
 
 class cbdt_bitmap_format_18(BitmapPlusBigMetricsMixin, ColorBitmapGlyph):
 
@@ -71,7 +71,7 @@
 		dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics))
 		dataList.append(struct.pack(">L", len(self.imageData)))
 		dataList.append(self.imageData)
-		return ''.join(dataList)
+		return bytesjoin(dataList)
 
 class cbdt_bitmap_format_19(ColorBitmapGlyph):
 
diff --git a/Lib/fontTools/ttLib/tables/C_O_L_R_.py b/Lib/fontTools/ttLib/tables/C_O_L_R_.py
index 0c47519..6c98434 100644
--- a/Lib/fontTools/ttLib/tables/C_O_L_R_.py
+++ b/Lib/fontTools/ttLib/tables/C_O_L_R_.py
@@ -74,7 +74,7 @@
 		dataList = [struct.pack(">HHLLH", self.version, len(glyphMap), 14, 14+6*len(glyphMap), len(layerMap))]
 		dataList.extend(glyphMap)
 		dataList.extend(layerMap)
-		data = "".join(dataList)
+		data = bytesjoin(dataList)
 		return data
 
 	def toXML(self, writer, ttFont):
diff --git a/Lib/fontTools/ttLib/tables/C_P_A_L_.py b/Lib/fontTools/ttLib/tables/C_P_A_L_.py
index 02583b5..d6d6604 100644
--- a/Lib/fontTools/ttLib/tables/C_P_A_L_.py
+++ b/Lib/fontTools/ttLib/tables/C_P_A_L_.py
@@ -35,7 +35,7 @@
 			assert(len(palette) == self.numPaletteEntries)
 			for color in palette:
 				dataList.append(struct.pack(">BBBB", color.blue,color.green,color.red,color.alpha))
-		data = "".join(dataList)
+		data = bytesjoin(dataList)
 		return data
 
 	def toXML(self, writer, ttFont):
diff --git a/Lib/fontTools/ttLib/tables/D_S_I_G_.py b/Lib/fontTools/ttLib/tables/D_S_I_G_.py
index e82ac37..e3e7861 100644
--- a/Lib/fontTools/ttLib/tables/D_S_I_G_.py
+++ b/Lib/fontTools/ttLib/tables/D_S_I_G_.py
@@ -71,7 +71,7 @@
 			sigrec.ulOffset = offset
 			headers.append(sstruct.pack(DSIG_SignatureFormat, sigrec))
 			offset += sigrec.ulLength
-		return ''.join(headers+data)
+		return bytesjoin(headers+data)
 	
 	def toXML(self, xmlWriter, ttFont):
 		xmlWriter.comment("note that the Digital Signature will be invalid after recompilation!")
@@ -114,4 +114,4 @@
 		self.ulFormat = safeEval(attrs["format"])
 		self.usReserved1 = safeEval(attrs.get("reserved1", "0"))
 		self.usReserved2 = safeEval(attrs.get("reserved2", "0"))
-		self.pkcs7 = "".join(filter(pem_spam, content)).decode('base64')
+		self.pkcs7 = strjoin(filter(pem_spam, content)).decode('base64')
diff --git a/Lib/fontTools/ttLib/tables/E_B_D_T_.py b/Lib/fontTools/ttLib/tables/E_B_D_T_.py
index 365d6a1..ce1ad6b 100644
--- a/Lib/fontTools/ttLib/tables/E_B_D_T_.py
+++ b/Lib/fontTools/ttLib/tables/E_B_D_T_.py
@@ -113,7 +113,7 @@
 				# of any of the problems in the convertion that may arise.
 				curIndexSubTable.locations = dataLocations
 
-		return ''.join(dataList)
+		return bytesjoin(dataList)
 
 	def toXML(self, writer, ttFont):
 		# When exporting to XML if one of the data export formats
@@ -212,7 +212,7 @@
 				binaryList.append('0')
 			value = value >> 1
 		numBits -= numBitsCut
-	return ''.join(binaryList)
+	return strjoin(binaryList)
 
 def _binary2data(binary):
 	byteList = []
@@ -224,7 +224,7 @@
 			if curBit == '1':
 				curByte |= 1
 		byteList.append(bytechr(curByte))
-	return ''.join(byteList)
+	return bytesjoin(byteList)
 
 def _memoize(f):
 	class memodict(dict):
@@ -242,7 +242,7 @@
 @_memoize
 def _reverseBytes(data):
 	if len(data) != 1:
-		return "".join(map(_reverseBytes, data))
+		return bytesjoin(map(_reverseBytes, data))
 	byte = byteord(data)
 	result = 0
 	for i in range(8):
@@ -309,7 +309,7 @@
 		rowData = bitmapObject.getRow(curRow, bitDepth=1, metrics=metrics, reverseBytes=True)
 		rowData = _data2binary(rowData, metrics.width)
 		# Make the output a readable ASCII art form.
-		rowData = "".join(map(binaryConv.get, rowData))
+		rowData = strjoin(map(binaryConv.get, rowData))
 		writer.simpletag('row', value=rowData)
 		writer.newline()
 	writer.endtag('bitwiseimagedata')
@@ -332,7 +332,7 @@
 		name, attr, content = element
 		if name == 'row':
 			mapParams = zip(attr['value'], itertools.repeat('1'))
-			rowData = ''.join(itertools.starmap(binaryConv.get, mapParams))
+			rowData = strjoin(itertools.starmap(binaryConv.get, mapParams))
 			dataRows.append(_binary2data(rowData))
 
 	bitmapObject.setRows(dataRows, bitDepth=bitDepth, metrics=metrics, reverseBytes=True)
@@ -532,7 +532,7 @@
 			dataList.append(bytechr(newByte))
 
 		# The way the data is kept is opposite the algorithm used.
-		data = ''.join(dataList)
+		data = bytesjoin(dataList)
 		if not reverseBytes:
 			data = _reverseBytes(data)
 		return data
@@ -567,7 +567,7 @@
 					ordDataList[secondByteLoc] |= secondByte
 
 		# Save the image data with the bits going the correct way.
-		self.imageData = _reverseBytes("".join(map(bytechr, ordDataList)))
+		self.imageData = _reverseBytes(bytesjoin(map(bytechr, ordDataList)))
 
 class ByteAlignedBitmapMixin:
 
@@ -591,7 +591,7 @@
 			metrics = self.metrics
 		if reverseBytes:
 			dataRows = map(_reverseBytes, dataRows)
-		self.imageData = "".join(dataRows)
+		self.imageData = bytesjoin(dataRows)
 
 class ebdt_bitmap_format_1(ByteAlignedBitmapMixin, BitmapPlusSmallMetricsMixin, BitmapGlyph):
 
@@ -706,12 +706,12 @@
 	def compile(self, ttFont):
 		dataList = []
 		dataList.append(sstruct.pack(smallGlyphMetricsFormat, self.metrics))
-		dataList.append('\0')
+		dataList.append(b'\0')
 		dataList.append(struct.pack(">H", len(self.componentArray)))
 		for curComponent in self.componentArray:
 			curComponent.glyphCode = ttFont.getGlyphID(curComponent.name)
 			dataList.append(sstruct.pack(ebdtComponentFormat, curComponent))
-		return ''.join(dataList)
+		return bytesjoin(dataList)
 
 
 class ebdt_bitmap_format_9(BitmapPlusBigMetricsMixin, ComponentBitmapGlyph):
@@ -735,7 +735,7 @@
 		for curComponent in self.componentArray:
 			curComponent.glyphCode = ttFont.getGlyphID(curComponent.name)
 			dataList.append(sstruct.pack(ebdtComponentFormat, curComponent))
-		return ''.join(dataList)
+		return bytesjoin(dataList)
 
 
 # Dictionary of bitmap formats to the class representing that format
diff --git a/Lib/fontTools/ttLib/tables/E_B_L_C_.py b/Lib/fontTools/ttLib/tables/E_B_L_C_.py
index c8c6507..a9b6a46 100644
--- a/Lib/fontTools/ttLib/tables/E_B_L_C_.py
+++ b/Lib/fontTools/ttLib/tables/E_B_L_C_.py
@@ -197,7 +197,7 @@
 			dataList.append(data)
 		dataList.extend(indexSubTablePairDataList)
 
-		return ''.join(dataList)
+		return bytesjoin(dataList)
 
 	def toXML(self, writer, ttFont):
 		writer.simpletag('header', [('version', self.version)])
@@ -477,7 +477,7 @@
 			# Take care of any padding issues. Only occurs in format 3.
 			if offsetDataSize * len(dataList) % 4 != 0:
 				dataList.append(struct.pack(dataFormat, 0))
-			return ''.join(dataList)
+			return bytesjoin(dataList)
 
 	return OffsetArrayIndexSubTableMixin
 
@@ -508,7 +508,7 @@
 		# Make sure that the data isn't bigger than the fixed size.
 		assert len(data) <= self.imageSize, "Data in indexSubTable format %d must be less than the fixed size." % self.indexFormat
 		# Pad the data so that it matches the fixed size.
-		pad = (self.imageSize - len(data)) * '\0'
+		pad = (self.imageSize - len(data)) * b'\0'
 		return data + pad
 
 class eblc_index_sub_table_1(_createOffsetArrayIndexSubTableMixin('L'), EblcIndexSubTable):
@@ -534,7 +534,7 @@
 		dataList = [EblcIndexSubTable.compile(self, ttFont)]
 		dataList.append(struct.pack(">L", self.imageSize))
 		dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics))
-		return ''.join(dataList)
+		return bytesjoin(dataList)
 
 class eblc_index_sub_table_3(_createOffsetArrayIndexSubTableMixin('H'), EblcIndexSubTable):
 	pass
@@ -576,7 +576,7 @@
 		dataList.append(struct.pack(">L", len(glyphIds)))
 		tmp = [struct.pack(codeOffsetPairFormat, *cop) for cop in zip(idsPlusPad, offsets)]
 		dataList += tmp
-		data = ''.join(dataList)
+		data = bytesjoin(dataList)
 		return data
 
 class eblc_index_sub_table_5(FixedSizeIndexSubTableMixin, EblcIndexSubTable):
@@ -604,7 +604,7 @@
 		dataList += [struct.pack(">H", curId) for curId in glyphIds]
 		if len(glyphIds) % 2 == 1:
 			dataList.append(struct.pack(">H", 0))
-		return ''.join(dataList)
+		return bytesjoin(dataList)
 
 # Dictionary of indexFormat to the class representing that format.
 eblc_sub_table_classes = {
diff --git a/Lib/fontTools/ttLib/tables/G_P_K_G_.py b/Lib/fontTools/ttLib/tables/G_P_K_G_.py
index a9f27e5..f9d604c 100644
--- a/Lib/fontTools/ttLib/tables/G_P_K_G_.py
+++ b/Lib/fontTools/ttLib/tables/G_P_K_G_.py
@@ -73,7 +73,7 @@
 		dataList.append(glyphletArray.tostring())
 		dataList += self.GMAPs
 		dataList += self.glyphlets
-		data = "".join(dataList)
+		data = bytesjoin(dataList)
 		return data
 	
 	def toXML(self, writer, ttFont):
diff --git a/Lib/fontTools/ttLib/tables/S_V_G_.py b/Lib/fontTools/ttLib/tables/S_V_G_.py
index 5e367fe..ed3f4e4 100644
--- a/Lib/fontTools/ttLib/tables/S_V_G_.py
+++ b/Lib/fontTools/ttLib/tables/S_V_G_.py
@@ -171,7 +171,7 @@
 			for entry in entries:
 				start = entry.svgDocOffset + subTableStart
 				end = start + entry.svgDocLength
-				doc = data[start:end]
+				doc = tostr(data[start:end], "utf-8")
 				self.docList.append( [doc, entry.startGlyphID, entry.endGlyphID] )
 
 	def compile(self, ttFont):
@@ -195,11 +195,11 @@
 			docOffset = curOffset
 			docLength = len(doc)
 			curOffset += docLength
-		 	entry = struct.pack(">HHLL", startGlyphID, endGlyphID, docOffset, docLength)
-		 	entryList.append(entry)
-		 	docList.append(doc)
+			entry = struct.pack(">HHLL", startGlyphID, endGlyphID, docOffset, docLength)
+			entryList.append(entry)
+			docList.append(tobytes(doc, encoding="utf-8"))
 		entryList.extend(docList)
-		svgDocData = "".join(entryList)
+		svgDocData = bytesjoin(entryList)
 
 		# get colorpalette info.
 		if self.colorPalettes == None:
@@ -223,11 +223,11 @@
 				for colorRecord in colorPalette.paletteColors:
 					data = struct.pack(">BBBB", colorRecord.red, colorRecord.green, colorRecord.blue, colorRecord.alpha)
 					dataList.append(data)
-			palettesData = "".join(dataList)
+			palettesData = bytesjoin(dataList)
 
 		header = struct.pack(">HLL", version, offsetToSVGDocIndex, offsetToColorPalettes)
 		data = [header, svgDocData, palettesData]
-		data = "".join(data)
+		data = bytesjoin(data)
 		return data
 
 	def compileFormat1(self, ttFont):
@@ -241,11 +241,11 @@
 			docOffset = curOffset
 			docLength = len(doc)
 			curOffset += docLength
-		 	entry = struct.pack(">HHLL", startGlyphID, endGlyphID, docOffset, docLength)
-		 	dataList.append(entry)
-		 	docList.append(doc)
+			entry = struct.pack(">HHLL", startGlyphID, endGlyphID, docOffset, docLength)
+			dataList.append(entry)
+			docList.append(tobytes(doc, encoding="utf-8"))
 		dataList.extend(docList)
-		data = "".join(dataList)
+		data = bytesjoin(dataList)
 		return data
 
 	def toXML(self, writer, ttFont):
@@ -294,7 +294,7 @@
 		if name == "svgDoc":
 			if not hasattr(self, "docList"):
 				self.docList = []
-			doc = "".join(content)
+			doc = strjoin(content)
 			doc = doc.strip()
 			startGID = int(attrs["startGlyphID"])
 			endGID = int(attrs["endGlyphID"])
diff --git a/Lib/fontTools/ttLib/tables/T_S_I__1.py b/Lib/fontTools/ttLib/tables/T_S_I__1.py
index 04b41e3..356b90f 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I__1.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I__1.py
@@ -108,7 +108,7 @@
 		if not hasattr(self, "glyphPrograms"):
 			self.glyphPrograms = {}
 			self.extraPrograms = {}
-		lines = ''.join(content).replace("\r", "\n").split("\n")
+		lines = strjoin(content).replace("\r", "\n").split("\n")
 		text = '\r'.join(lines[1:-1])
 		if name == "glyphProgram":
 			self.glyphPrograms[attrs["name"]] = text
diff --git a/Lib/fontTools/ttLib/tables/V_O_R_G_.py b/Lib/fontTools/ttLib/tables/V_O_R_G_.py
index 754b768..e4ec360 100644
--- a/Lib/fontTools/ttLib/tables/V_O_R_G_.py
+++ b/Lib/fontTools/ttLib/tables/V_O_R_G_.py
@@ -54,7 +54,7 @@
 		dataList = [ struct.pack(">Hh", rec[0], rec[1]) for rec in vOriginTable]
 		header = struct.pack(">HHhH", self.majorVersion, self.minorVersion, self.defaultVertOriginY, self.numVertOriginYMetrics)
 		dataList.insert(0, header)
-		data = "".join(dataList)
+		data = bytesjoin(dataList)
 		return data
 
 	def toXML(self, writer, ttFont):
diff --git a/Lib/fontTools/ttLib/tables/_c_m_a_p.py b/Lib/fontTools/ttLib/tables/_c_m_a_p.py
index 44473be..1d49c35 100644
--- a/Lib/fontTools/ttLib/tables/_c_m_a_p.py
+++ b/Lib/fontTools/ttLib/tables/_c_m_a_p.py
@@ -515,7 +515,7 @@
 		for subhead in 	subHeaderList[:-1]:
 			for gi in subhead.glyphIndexArray:
 				dataList.append(struct.pack(">H", gi))
-		data = "".join(dataList)
+		data = bytesjoin(dataList)
 		assert (len(data) == length), "Error: cmap format 2 is not same length as calculated! actual: " + str(len(data))+ " calc : " + str(length)
 		return data
 
@@ -998,7 +998,7 @@
 			lastCharCode = charCode
 		dataList.append(struct.pack(">LLL", startCharCode, lastCharCode, startGlyphID))
 		nGroups = nGroups + 1
-		data = "".join(dataList)
+		data = bytesjoin(dataList)
 		lengthSubtable = len(data) +16
 		assert len(data) == (nGroups*12) == (lengthSubtable-16) 
 		return struct.pack(">HHLLL", self.format, self.reserved , lengthSubtable, self.language, nGroups) + data
@@ -1246,7 +1246,7 @@
 			vrec = struct.pack(">3sLL", cvtFromUVS(uvs), defOVSOffset, nonDefUVSOffset)
 			varSelectorRecords.append(vrec)
 				
-		data = "".join(varSelectorRecords) + "".join(data)
+		data = bytesjoin(varSelectorRecords) + bytesjoin(data)
 		self.length = 10 + len(data)
 		headerdata = struct.pack(">HLL", self.format, self.length , self.numVarSelectorRecords)
 		self.data = headerdata + data
diff --git a/Lib/fontTools/ttLib/tables/_h_d_m_x.py b/Lib/fontTools/ttLib/tables/_h_d_m_x.py
index 802227e..f9fd557 100644
--- a/Lib/fontTools/ttLib/tables/_h_d_m_x.py
+++ b/Lib/fontTools/ttLib/tables/_h_d_m_x.py
@@ -32,7 +32,7 @@
 		numGlyphs = ttFont['maxp'].numGlyphs
 		glyphOrder = ttFont.getGlyphOrder()
 		self.recordSize = 4 * ((2 + numGlyphs + 3) // 4)
-		pad = (self.recordSize - 2 - numGlyphs) * "\0"
+		pad = (self.recordSize - 2 - numGlyphs) * b"\0"
 		self.numRecords = len(self.hdmx)
 		data = sstruct.pack(hdmxHeaderFormat, self)
 		items = sorted(self.hdmx.items())
@@ -76,7 +76,7 @@
 	def fromXML(self, name, attrs, content, ttFont):
 		if name != "hdmxData":
 			return
-		content = ''.join(content)
+		content = strjoin(content)
 		lines = content.split(";")
 		topRow = lines[0].split()
 		assert topRow[0] == "ppem:", "illegal hdmx format"
diff --git a/Lib/fontTools/ttLib/tables/_n_a_m_e.py b/Lib/fontTools/ttLib/tables/_n_a_m_e.py
index 36f1f4a..c2fa0e9 100644
--- a/Lib/fontTools/ttLib/tables/_n_a_m_e.py
+++ b/Lib/fontTools/ttLib/tables/_n_a_m_e.py
@@ -116,7 +116,7 @@
 		self.platformID = safeEval(attrs["platformID"])
 		self.platEncID = safeEval(attrs["platEncID"])
 		self.langID =  safeEval(attrs["langID"])
-		s = ''.join(content).decode("utf8").strip()
+		s = strjoin(content).decode("utf8").strip()
 		if self.platformID == 0 or (self.platformID == 3 and self.platEncID in (0, 1)):
 			self.string = s.encode("utf_16_be")
 		else:
diff --git a/Lib/fontTools/ttLib/tables/asciiTable.py b/Lib/fontTools/ttLib/tables/asciiTable.py
index 47b37d0..87ef62a 100644
--- a/Lib/fontTools/ttLib/tables/asciiTable.py
+++ b/Lib/fontTools/ttLib/tables/asciiTable.py
@@ -6,10 +6,10 @@
 class asciiTable(DefaultTable.DefaultTable):
 	
 	def toXML(self, writer, ttFont):
-		data = self.data
+		data = tostr(self.data)
 		# removing null bytes. XXX needed??
 		data = data.split('\0')
-		data = ''.join(data)
+		data = strjoin(data)
 		writer.begintag("source")
 		writer.newline()
 		writer.write_noindent(data.replace("\r", "\n"))
@@ -18,6 +18,6 @@
 		writer.newline()
 	
 	def fromXML(self, name, attrs, content, ttFont):
-		lines = ''.join(content).replace("\r", "\n").split("\n")
-		self.data = "\r".join(lines[1:-1])
+		lines = strjoin(content).replace("\r", "\n").split("\n")
+		self.data = tobytes("\r".join(lines[1:-1]))
 
diff --git a/Lib/fontTools/ttLib/tables/otBase.py b/Lib/fontTools/ttLib/tables/otBase.py
index 72cbb3b..843953a 100644
--- a/Lib/fontTools/ttLib/tables/otBase.py
+++ b/Lib/fontTools/ttLib/tables/otBase.py
@@ -406,7 +406,7 @@
 
 	def writeUInt24(self, value):
 		assert 0 <= value < 0x1000000
-		self.items.append(''.join(bytechr(v) for v in (value>>16, (value>>8)&0xFF, value&0xff)))
+		self.items.append(bytesjoin(bytechr(v) for v in (value>>16, (value>>8)&0xFF, value&0xff)))
 	
 	def writeLong(self, value):
 		self.items.append(struct.pack(">l", value))
diff --git a/Lib/fontTools/ttLib/tables/ttProgram.py b/Lib/fontTools/ttLib/tables/ttProgram.py
index a3401a6..cfc9d71 100644
--- a/Lib/fontTools/ttLib/tables/ttProgram.py
+++ b/Lib/fontTools/ttLib/tables/ttProgram.py
@@ -260,7 +260,7 @@
 	
 	def fromXML(self, name, attrs, content, ttFont):
 		if name == "assembly":
-			self.fromAssembly(''.join(content))
+			self.fromAssembly(strjoin(content))
 			self._assemble()
 			del self.assembly
 		else: