• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2013 Google, Inc. All Rights Reserved.
2#
3# Google Author(s): Behdad Esfahbod, Roozbeh Pournader
4
5from fontTools.merge.unicode import is_Default_Ignorable
6from fontTools.pens.recordingPen import DecomposingRecordingPen
7import logging
8
9
10log = logging.getLogger("fontTools.merge")
11
12
13def computeMegaGlyphOrder(merger, glyphOrders):
14	"""Modifies passed-in glyphOrders to reflect new glyph names.
15    Stores merger.glyphOrder."""
16	megaOrder = {}
17	for glyphOrder in glyphOrders:
18		for i,glyphName in enumerate(glyphOrder):
19			if glyphName in megaOrder:
20				n = megaOrder[glyphName]
21				while (glyphName + "#" + repr(n)) in megaOrder:
22					n += 1
23				megaOrder[glyphName] = n
24				glyphName += "#" + repr(n)
25				glyphOrder[i] = glyphName
26			megaOrder[glyphName] = 1
27	merger.glyphOrder = megaOrder = list(megaOrder.keys())
28
29
30def _glyphsAreSame(glyphSet1, glyphSet2, glyph1, glyph2,
31				   advanceTolerance=.05,
32				   advanceToleranceEmpty=.20):
33	pen1 = DecomposingRecordingPen(glyphSet1)
34	pen2 = DecomposingRecordingPen(glyphSet2)
35	g1 = glyphSet1[glyph1]
36	g2 = glyphSet2[glyph2]
37	g1.draw(pen1)
38	g2.draw(pen2)
39	if pen1.value != pen2.value:
40		return False
41	# Allow more width tolerance for glyphs with no ink
42	tolerance = advanceTolerance if pen1.value else advanceToleranceEmpty
43    # TODO Warn if advances not the same but within tolerance.
44	if abs(g1.width - g2.width) > g1.width * tolerance:
45		return False
46	if hasattr(g1, 'height') and g1.height is not None:
47		if abs(g1.height - g2.height) > g1.height * tolerance:
48			return False
49	return True
50
51# Valid (format, platformID, platEncID) triplets for cmap subtables containing
52# Unicode BMP-only and Unicode Full Repertoire semantics.
53# Cf. OpenType spec for "Platform specific encodings":
54# https://docs.microsoft.com/en-us/typography/opentype/spec/name
55class _CmapUnicodePlatEncodings:
56	BMP = {(4, 3, 1), (4, 0, 3), (4, 0, 4), (4, 0, 6)}
57	FullRepertoire = {(12, 3, 10), (12, 0, 4), (12, 0, 6)}
58
59def computeMegaCmap(merger, cmapTables):
60	"""Sets merger.cmap and merger.glyphOrder."""
61
62	# TODO Handle format=14.
63	# Only merge format 4 and 12 Unicode subtables, ignores all other subtables
64	# If there is a format 12 table for a font, ignore the format 4 table of it
65	chosenCmapTables = []
66	for fontIdx,table in enumerate(cmapTables):
67		format4 = None
68		format12 = None
69		for subtable in table.tables:
70			properties = (subtable.format, subtable.platformID, subtable.platEncID)
71			if properties in _CmapUnicodePlatEncodings.BMP:
72				format4 = subtable
73			elif properties in _CmapUnicodePlatEncodings.FullRepertoire:
74				format12 = subtable
75			else:
76				log.warning(
77					"Dropped cmap subtable from font '%s':\t"
78					"format %2s, platformID %2s, platEncID %2s",
79					fontIdx, subtable.format, subtable.platformID, subtable.platEncID
80				)
81		if format12 is not None:
82			chosenCmapTables.append((format12, fontIdx))
83		elif format4 is not None:
84			chosenCmapTables.append((format4, fontIdx))
85
86	# Build the unicode mapping
87	merger.cmap = cmap = {}
88	fontIndexForGlyph = {}
89	glyphSets = [None for f in merger.fonts] if hasattr(merger, 'fonts') else None
90
91	for table,fontIdx in chosenCmapTables:
92		# handle duplicates
93		for uni,gid in table.cmap.items():
94			oldgid = cmap.get(uni, None)
95			if oldgid is None:
96				cmap[uni] = gid
97				fontIndexForGlyph[gid] = fontIdx
98			elif is_Default_Ignorable(uni) or uni in (0x25CC,): # U+25CC DOTTED CIRCLE
99				continue
100			elif oldgid != gid:
101				# Char previously mapped to oldgid, now to gid.
102				# Record, to fix up in GSUB 'locl' later.
103				if merger.duplicateGlyphsPerFont[fontIdx].get(oldgid) is None:
104					if glyphSets is not None:
105						oldFontIdx = fontIndexForGlyph[oldgid]
106						for idx in (fontIdx, oldFontIdx):
107							if glyphSets[idx] is None:
108								glyphSets[idx] = merger.fonts[idx].getGlyphSet()
109						#if _glyphsAreSame(glyphSets[oldFontIdx], glyphSets[fontIdx], oldgid, gid):
110						#	continue
111					merger.duplicateGlyphsPerFont[fontIdx][oldgid] = gid
112				elif merger.duplicateGlyphsPerFont[fontIdx][oldgid] != gid:
113					# Char previously mapped to oldgid but oldgid is already remapped to a different
114					# gid, because of another Unicode character.
115					# TODO: Try harder to do something about these.
116					log.warning("Dropped mapping from codepoint %#06X to glyphId '%s'", uni, gid)
117
118
119def renameCFFCharStrings(merger, glyphOrder, cffTable):
120	"""Rename topDictIndex charStrings based on glyphOrder."""
121	td = cffTable.cff.topDictIndex[0]
122
123	charStrings = {}
124	for i, v in enumerate(td.CharStrings.charStrings.values()):
125		glyphName = glyphOrder[i]
126		charStrings[glyphName] = v
127	td.CharStrings.charStrings = charStrings
128
129	td.charset = list(glyphOrder)
130