aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/merge/cmap.py
blob: 7d98b588e73bc891d8fb2e913065a5a9df7eac37 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# Copyright 2013 Google, Inc. All Rights Reserved.
#
# Google Author(s): Behdad Esfahbod, Roozbeh Pournader

from fontTools.merge.unicode import is_Default_Ignorable
from fontTools.pens.recordingPen import DecomposingRecordingPen
import logging


log = logging.getLogger("fontTools.merge")


def computeMegaGlyphOrder(merger, glyphOrders):
	"""Modifies passed-in glyphOrders to reflect new glyph names.
    Stores merger.glyphOrder."""
	megaOrder = {}
	for glyphOrder in glyphOrders:
		for i,glyphName in enumerate(glyphOrder):
			if glyphName in megaOrder:
				n = megaOrder[glyphName]
				while (glyphName + "." + repr(n)) in megaOrder:
					n += 1
				megaOrder[glyphName] = n
				glyphName += "." + repr(n)
				glyphOrder[i] = glyphName
			megaOrder[glyphName] = 1
	merger.glyphOrder = megaOrder = list(megaOrder.keys())


def _glyphsAreSame(glyphSet1, glyphSet2, glyph1, glyph2,
				   advanceTolerance=.05,
				   advanceToleranceEmpty=.20):
	pen1 = DecomposingRecordingPen(glyphSet1)
	pen2 = DecomposingRecordingPen(glyphSet2)
	g1 = glyphSet1[glyph1]
	g2 = glyphSet2[glyph2]
	g1.draw(pen1)
	g2.draw(pen2)
	if pen1.value != pen2.value:
		return False
	# Allow more width tolerance for glyphs with no ink
	tolerance = advanceTolerance if pen1.value else advanceToleranceEmpty
    # TODO Warn if advances not the same but within tolerance.
	if abs(g1.width - g2.width) > g1.width * tolerance:
		return False
	if hasattr(g1, 'height') and g1.height is not None:
		if abs(g1.height - g2.height) > g1.height * tolerance:
			return False
	return True

# Valid (format, platformID, platEncID) triplets for cmap subtables containing
# Unicode BMP-only and Unicode Full Repertoire semantics.
# Cf. OpenType spec for "Platform specific encodings":
# https://docs.microsoft.com/en-us/typography/opentype/spec/name
class _CmapUnicodePlatEncodings:
	BMP = {(4, 3, 1), (4, 0, 3), (4, 0, 4), (4, 0, 6)}
	FullRepertoire = {(12, 3, 10), (12, 0, 4), (12, 0, 6)}

def computeMegaCmap(merger, cmapTables):
	"""Sets merger.cmap and merger.glyphOrder."""

	# TODO Handle format=14.
	# Only merge format 4 and 12 Unicode subtables, ignores all other subtables
	# If there is a format 12 table for a font, ignore the format 4 table of it
	chosenCmapTables = []
	for fontIdx,table in enumerate(cmapTables):
		format4 = None
		format12 = None
		for subtable in table.tables:
			properties = (subtable.format, subtable.platformID, subtable.platEncID)
			if properties in _CmapUnicodePlatEncodings.BMP:
				format4 = subtable
			elif properties in _CmapUnicodePlatEncodings.FullRepertoire:
				format12 = subtable
			else:
				log.warning(
					"Dropped cmap subtable from font '%s':\t"
					"format %2s, platformID %2s, platEncID %2s",
					fontIdx, subtable.format, subtable.platformID, subtable.platEncID
				)
		if format12 is not None:
			chosenCmapTables.append((format12, fontIdx))
		elif format4 is not None:
			chosenCmapTables.append((format4, fontIdx))

	# Build the unicode mapping
	merger.cmap = cmap = {}
	fontIndexForGlyph = {}
	glyphSets = [None for f in merger.fonts] if hasattr(merger, 'fonts') else None

	for table,fontIdx in chosenCmapTables:
		# handle duplicates
		for uni,gid in table.cmap.items():
			oldgid = cmap.get(uni, None)
			if oldgid is None:
				cmap[uni] = gid
				fontIndexForGlyph[gid] = fontIdx
			elif is_Default_Ignorable(uni) or uni in (0x25CC,): # U+25CC DOTTED CIRCLE
				continue
			elif oldgid != gid:
				# Char previously mapped to oldgid, now to gid.
				# Record, to fix up in GSUB 'locl' later.
				if merger.duplicateGlyphsPerFont[fontIdx].get(oldgid) is None:
					if glyphSets is not None:
						oldFontIdx = fontIndexForGlyph[oldgid]
						for idx in (fontIdx, oldFontIdx):
							if glyphSets[idx] is None:
								glyphSets[idx] = merger.fonts[idx].getGlyphSet()
						#if _glyphsAreSame(glyphSets[oldFontIdx], glyphSets[fontIdx], oldgid, gid):
						#	continue
					merger.duplicateGlyphsPerFont[fontIdx][oldgid] = gid
				elif merger.duplicateGlyphsPerFont[fontIdx][oldgid] != gid:
					# Char previously mapped to oldgid but oldgid is already remapped to a different
					# gid, because of another Unicode character.
					# TODO: Try harder to do something about these.
					log.warning("Dropped mapping from codepoint %#06X to glyphId '%s'", uni, gid)


def renameCFFCharStrings(merger, glyphOrder, cffTable):
	"""Rename topDictIndex charStrings based on glyphOrder."""
	td = cffTable.cff.topDictIndex[0]

	charStrings = {}
	for i, v in enumerate(td.CharStrings.charStrings.values()):
		glyphName = glyphOrder[i]
		charStrings[glyphName] = v
	td.CharStrings.charStrings = charStrings

	td.charset = list(glyphOrder)