• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from __future__ import print_function, division, absolute_import
2from fontTools.misc.py23 import *
3from fontTools.misc import sstruct
4from fontTools.misc.textTools import safeEval, num2binary, binary2num
5from fontTools.ttLib.tables import DefaultTable
6import logging
7
8
9log = logging.getLogger(__name__)
10
11# panose classification
12
13panoseFormat = """
14	bFamilyType:        B
15	bSerifStyle:        B
16	bWeight:            B
17	bProportion:        B
18	bContrast:          B
19	bStrokeVariation:   B
20	bArmStyle:          B
21	bLetterForm:        B
22	bMidline:           B
23	bXHeight:           B
24"""
25
26class Panose(object):
27
28	def toXML(self, writer, ttFont):
29		formatstring, names, fixes = sstruct.getformat(panoseFormat)
30		for name in names:
31			writer.simpletag(name, value=getattr(self, name))
32			writer.newline()
33
34	def fromXML(self, name, attrs, content, ttFont):
35		setattr(self, name, safeEval(attrs["value"]))
36
37
38# 'sfnt' OS/2 and Windows Metrics table - 'OS/2'
39
40OS2_format_0 = """
41	>   # big endian
42	version:                H       # version
43	xAvgCharWidth:          h       # average character width
44	usWeightClass:          H       # degree of thickness of strokes
45	usWidthClass:           H       # aspect ratio
46	fsType:                 h       # type flags
47	ySubscriptXSize:        h       # subscript horizontal font size
48	ySubscriptYSize:        h       # subscript vertical font size
49	ySubscriptXOffset:      h       # subscript x offset
50	ySubscriptYOffset:      h       # subscript y offset
51	ySuperscriptXSize:      h       # superscript horizontal font size
52	ySuperscriptYSize:      h       # superscript vertical font size
53	ySuperscriptXOffset:    h       # superscript x offset
54	ySuperscriptYOffset:    h       # superscript y offset
55	yStrikeoutSize:         h       # strikeout size
56	yStrikeoutPosition:     h       # strikeout position
57	sFamilyClass:           h       # font family class and subclass
58	panose:                 10s     # panose classification number
59	ulUnicodeRange1:        L       # character range
60	ulUnicodeRange2:        L       # character range
61	ulUnicodeRange3:        L       # character range
62	ulUnicodeRange4:        L       # character range
63	achVendID:              4s      # font vendor identification
64	fsSelection:            H       # font selection flags
65	usFirstCharIndex:       H       # first unicode character index
66	usLastCharIndex:        H       # last unicode character index
67	sTypoAscender:          h       # typographic ascender
68	sTypoDescender:         h       # typographic descender
69	sTypoLineGap:           h       # typographic line gap
70	usWinAscent:            H       # Windows ascender
71	usWinDescent:           H       # Windows descender
72"""
73
74OS2_format_1_addition =  """
75	ulCodePageRange1:   L
76	ulCodePageRange2:   L
77"""
78
79OS2_format_2_addition =  OS2_format_1_addition + """
80	sxHeight:           h
81	sCapHeight:         h
82	usDefaultChar:      H
83	usBreakChar:        H
84	usMaxContext:       H
85"""
86
87OS2_format_5_addition =  OS2_format_2_addition + """
88	usLowerOpticalPointSize:    H
89	usUpperOpticalPointSize:    H
90"""
91
92bigendian = "	>	# big endian\n"
93
94OS2_format_1 = OS2_format_0 + OS2_format_1_addition
95OS2_format_2 = OS2_format_0 + OS2_format_2_addition
96OS2_format_5 = OS2_format_0 + OS2_format_5_addition
97OS2_format_1_addition = bigendian + OS2_format_1_addition
98OS2_format_2_addition = bigendian + OS2_format_2_addition
99OS2_format_5_addition = bigendian + OS2_format_5_addition
100
101
102class table_O_S_2f_2(DefaultTable.DefaultTable):
103
104	"""the OS/2 table"""
105
106	dependencies = ["head"]
107
108	def decompile(self, data, ttFont):
109		dummy, data = sstruct.unpack2(OS2_format_0, data, self)
110
111		if self.version == 1:
112			dummy, data = sstruct.unpack2(OS2_format_1_addition, data, self)
113		elif self.version in (2, 3, 4):
114			dummy, data = sstruct.unpack2(OS2_format_2_addition, data, self)
115		elif self.version == 5:
116			dummy, data = sstruct.unpack2(OS2_format_5_addition, data, self)
117			self.usLowerOpticalPointSize /= 20
118			self.usUpperOpticalPointSize /= 20
119		elif self.version != 0:
120			from fontTools import ttLib
121			raise ttLib.TTLibError("unknown format for OS/2 table: version %s" % self.version)
122		if len(data):
123			log.warning("too much 'OS/2' table data")
124
125		self.panose = sstruct.unpack(panoseFormat, self.panose, Panose())
126
127	def compile(self, ttFont):
128		self.updateFirstAndLastCharIndex(ttFont)
129		panose = self.panose
130		head = ttFont["head"]
131		if (self.fsSelection & 1) and not (head.macStyle & 1<<1):
132			log.warning("fsSelection bit 0 (italic) and "
133				"head table macStyle bit 1 (italic) should match")
134		if (self.fsSelection & 1<<5) and not (head.macStyle & 1):
135			log.warning("fsSelection bit 5 (bold) and "
136				"head table macStyle bit 0 (bold) should match")
137		if (self.fsSelection & 1<<6) and (self.fsSelection & 1 + (1<<5)):
138			log.warning("fsSelection bit 6 (regular) is set, "
139				"bits 0 (italic) and 5 (bold) must be clear")
140		if self.version < 4 and self.fsSelection & 0b1110000000:
141			log.warning("fsSelection bits 7, 8 and 9 are only defined in "
142				"OS/2 table version 4 and up: version %s", self.version)
143		self.panose = sstruct.pack(panoseFormat, self.panose)
144		if self.version == 0:
145			data = sstruct.pack(OS2_format_0, self)
146		elif self.version == 1:
147			data = sstruct.pack(OS2_format_1, self)
148		elif self.version in (2, 3, 4):
149			data = sstruct.pack(OS2_format_2, self)
150		elif self.version == 5:
151			d = self.__dict__.copy()
152			d['usLowerOpticalPointSize'] = round(self.usLowerOpticalPointSize * 20)
153			d['usUpperOpticalPointSize'] = round(self.usUpperOpticalPointSize * 20)
154			data = sstruct.pack(OS2_format_5, d)
155		else:
156			from fontTools import ttLib
157			raise ttLib.TTLibError("unknown format for OS/2 table: version %s" % self.version)
158		self.panose = panose
159		return data
160
161	def toXML(self, writer, ttFont):
162		writer.comment(
163			"The fields 'usFirstCharIndex' and 'usLastCharIndex'\n"
164			"will be recalculated by the compiler")
165		writer.newline()
166		if self.version == 1:
167			format = OS2_format_1
168		elif self.version in (2, 3, 4):
169			format = OS2_format_2
170		elif self.version == 5:
171			format = OS2_format_5
172		else:
173			format = OS2_format_0
174		formatstring, names, fixes = sstruct.getformat(format)
175		for name in names:
176			value = getattr(self, name)
177			if name=="panose":
178				writer.begintag("panose")
179				writer.newline()
180				value.toXML(writer, ttFont)
181				writer.endtag("panose")
182			elif name in ("ulUnicodeRange1", "ulUnicodeRange2",
183					"ulUnicodeRange3", "ulUnicodeRange4",
184					"ulCodePageRange1", "ulCodePageRange2"):
185				writer.simpletag(name, value=num2binary(value))
186			elif name in ("fsType", "fsSelection"):
187				writer.simpletag(name, value=num2binary(value, 16))
188			elif name == "achVendID":
189				writer.simpletag(name, value=repr(value)[1:-1])
190			else:
191				writer.simpletag(name, value=value)
192			writer.newline()
193
194	def fromXML(self, name, attrs, content, ttFont):
195		if name == "panose":
196			self.panose = panose = Panose()
197			for element in content:
198				if isinstance(element, tuple):
199					name, attrs, content = element
200					panose.fromXML(name, attrs, content, ttFont)
201		elif name in ("ulUnicodeRange1", "ulUnicodeRange2",
202				"ulUnicodeRange3", "ulUnicodeRange4",
203				"ulCodePageRange1", "ulCodePageRange2",
204				"fsType", "fsSelection"):
205			setattr(self, name, binary2num(attrs["value"]))
206		elif name == "achVendID":
207			setattr(self, name, safeEval("'''" + attrs["value"] + "'''"))
208		else:
209			setattr(self, name, safeEval(attrs["value"]))
210
211	def updateFirstAndLastCharIndex(self, ttFont):
212		if 'cmap' not in ttFont:
213			return
214		codes = set()
215		for table in getattr(ttFont['cmap'], 'tables', []):
216			if table.isUnicode():
217				codes.update(table.cmap.keys())
218		if codes:
219			minCode = min(codes)
220			maxCode = max(codes)
221			# USHORT cannot hold codepoints greater than 0xFFFF
222			self.usFirstCharIndex = min(0xFFFF, minCode)
223			self.usLastCharIndex = min(0xFFFF, maxCode)
224
225	# misspelled attributes kept for legacy reasons
226
227	@property
228	def usMaxContex(self):
229		return self.usMaxContext
230
231	@usMaxContex.setter
232	def usMaxContex(self, value):
233		self.usMaxContext = value
234
235	@property
236	def fsFirstCharIndex(self):
237		return self.usFirstCharIndex
238
239	@fsFirstCharIndex.setter
240	def fsFirstCharIndex(self, value):
241		self.usFirstCharIndex = value
242
243	@property
244	def fsLastCharIndex(self):
245		return self.usLastCharIndex
246
247	@fsLastCharIndex.setter
248	def fsLastCharIndex(self, value):
249		self.usLastCharIndex = value
250
251	def getUnicodeRanges(self):
252		""" Return the set of 'ulUnicodeRange*' bits currently enabled. """
253		bits = set()
254		ul1, ul2 = self.ulUnicodeRange1, self.ulUnicodeRange2
255		ul3, ul4 = self.ulUnicodeRange3, self.ulUnicodeRange4
256		for i in range(32):
257			if ul1 & (1 << i):
258				bits.add(i)
259			if ul2 & (1 << i):
260				bits.add(i + 32)
261			if ul3 & (1 << i):
262				bits.add(i + 64)
263			if ul4 & (1 << i):
264				bits.add(i + 96)
265		return bits
266
267	def setUnicodeRanges(self, bits):
268		""" Set the 'ulUnicodeRange*' fields to the specified 'bits'. """
269		ul1, ul2, ul3, ul4 = 0, 0, 0, 0
270		for bit in bits:
271			if 0 <= bit < 32:
272				ul1 |= (1 << bit)
273			elif 32 <= bit < 64:
274				ul2 |= (1 << (bit - 32))
275			elif 64 <= bit < 96:
276				ul3 |= (1 << (bit - 64))
277			elif 96 <= bit < 123:
278				ul4 |= (1 << (bit - 96))
279			else:
280				raise ValueError('expected 0 <= int <= 122, found: %r' % bit)
281		self.ulUnicodeRange1, self.ulUnicodeRange2 = ul1, ul2
282		self.ulUnicodeRange3, self.ulUnicodeRange4 = ul3, ul4
283
284	def recalcUnicodeRanges(self, ttFont, pruneOnly=False):
285		""" Intersect the codepoints in the font's Unicode cmap subtables with
286		the Unicode block ranges defined in the OpenType specification (v1.7),
287		and set the respective 'ulUnicodeRange*' bits if there is at least ONE
288		intersection.
289		If 'pruneOnly' is True, only clear unused bits with NO intersection.
290		"""
291		unicodes = set()
292		for table in ttFont['cmap'].tables:
293			if table.isUnicode():
294				unicodes.update(table.cmap.keys())
295		if pruneOnly:
296			empty = intersectUnicodeRanges(unicodes, inverse=True)
297			bits = self.getUnicodeRanges() - empty
298		else:
299			bits = intersectUnicodeRanges(unicodes)
300		self.setUnicodeRanges(bits)
301		return bits
302
303
304# Unicode ranges data from the OpenType OS/2 table specification v1.7
305
306OS2_UNICODE_RANGES = (
307	(('Basic Latin',                              (0x0000, 0x007F)),),
308	(('Latin-1 Supplement',                       (0x0080, 0x00FF)),),
309	(('Latin Extended-A',                         (0x0100, 0x017F)),),
310	(('Latin Extended-B',                         (0x0180, 0x024F)),),
311	(('IPA Extensions',                           (0x0250, 0x02AF)),
312	 ('Phonetic Extensions',                      (0x1D00, 0x1D7F)),
313	 ('Phonetic Extensions Supplement',           (0x1D80, 0x1DBF))),
314	(('Spacing Modifier Letters',                 (0x02B0, 0x02FF)),
315	 ('Modifier Tone Letters',                    (0xA700, 0xA71F))),
316	(('Combining Diacritical Marks',              (0x0300, 0x036F)),
317	 ('Combining Diacritical Marks Supplement',   (0x1DC0, 0x1DFF))),
318	(('Greek and Coptic',                         (0x0370, 0x03FF)),),
319	(('Coptic',                                   (0x2C80, 0x2CFF)),),
320	(('Cyrillic',                                 (0x0400, 0x04FF)),
321	 ('Cyrillic Supplement',                      (0x0500, 0x052F)),
322	 ('Cyrillic Extended-A',                      (0x2DE0, 0x2DFF)),
323	 ('Cyrillic Extended-B',                      (0xA640, 0xA69F))),
324	(('Armenian',                                 (0x0530, 0x058F)),),
325	(('Hebrew',                                   (0x0590, 0x05FF)),),
326	(('Vai',                                      (0xA500, 0xA63F)),),
327	(('Arabic',                                   (0x0600, 0x06FF)),
328	 ('Arabic Supplement',                        (0x0750, 0x077F))),
329	(('NKo',                                      (0x07C0, 0x07FF)),),
330	(('Devanagari',                               (0x0900, 0x097F)),),
331	(('Bengali',                                  (0x0980, 0x09FF)),),
332	(('Gurmukhi',                                 (0x0A00, 0x0A7F)),),
333	(('Gujarati',                                 (0x0A80, 0x0AFF)),),
334	(('Oriya',                                    (0x0B00, 0x0B7F)),),
335	(('Tamil',                                    (0x0B80, 0x0BFF)),),
336	(('Telugu',                                   (0x0C00, 0x0C7F)),),
337	(('Kannada',                                  (0x0C80, 0x0CFF)),),
338	(('Malayalam',                                (0x0D00, 0x0D7F)),),
339	(('Thai',                                     (0x0E00, 0x0E7F)),),
340	(('Lao',                                      (0x0E80, 0x0EFF)),),
341	(('Georgian',                                 (0x10A0, 0x10FF)),
342	 ('Georgian Supplement',                      (0x2D00, 0x2D2F))),
343	(('Balinese',                                 (0x1B00, 0x1B7F)),),
344	(('Hangul Jamo',                              (0x1100, 0x11FF)),),
345	(('Latin Extended Additional',                (0x1E00, 0x1EFF)),
346	 ('Latin Extended-C',                         (0x2C60, 0x2C7F)),
347	 ('Latin Extended-D',                         (0xA720, 0xA7FF))),
348	(('Greek Extended',                           (0x1F00, 0x1FFF)),),
349	(('General Punctuation',                      (0x2000, 0x206F)),
350	 ('Supplemental Punctuation',                 (0x2E00, 0x2E7F))),
351	(('Superscripts And Subscripts',              (0x2070, 0x209F)),),
352	(('Currency Symbols',                         (0x20A0, 0x20CF)),),
353	(('Combining Diacritical Marks For Symbols',  (0x20D0, 0x20FF)),),
354	(('Letterlike Symbols',                       (0x2100, 0x214F)),),
355	(('Number Forms',                             (0x2150, 0x218F)),),
356	(('Arrows',                                   (0x2190, 0x21FF)),
357	 ('Supplemental Arrows-A',                    (0x27F0, 0x27FF)),
358	 ('Supplemental Arrows-B',                    (0x2900, 0x297F)),
359	 ('Miscellaneous Symbols and Arrows',         (0x2B00, 0x2BFF))),
360	(('Mathematical Operators',                   (0x2200, 0x22FF)),
361	 ('Supplemental Mathematical Operators',      (0x2A00, 0x2AFF)),
362	 ('Miscellaneous Mathematical Symbols-A',     (0x27C0, 0x27EF)),
363	 ('Miscellaneous Mathematical Symbols-B',     (0x2980, 0x29FF))),
364	(('Miscellaneous Technical',                  (0x2300, 0x23FF)),),
365	(('Control Pictures',                         (0x2400, 0x243F)),),
366	(('Optical Character Recognition',            (0x2440, 0x245F)),),
367	(('Enclosed Alphanumerics',                   (0x2460, 0x24FF)),),
368	(('Box Drawing',                              (0x2500, 0x257F)),),
369	(('Block Elements',                           (0x2580, 0x259F)),),
370	(('Geometric Shapes',                         (0x25A0, 0x25FF)),),
371	(('Miscellaneous Symbols',                    (0x2600, 0x26FF)),),
372	(('Dingbats',                                 (0x2700, 0x27BF)),),
373	(('CJK Symbols And Punctuation',              (0x3000, 0x303F)),),
374	(('Hiragana',                                 (0x3040, 0x309F)),),
375	(('Katakana',                                 (0x30A0, 0x30FF)),
376	 ('Katakana Phonetic Extensions',             (0x31F0, 0x31FF))),
377	(('Bopomofo',                                 (0x3100, 0x312F)),
378	 ('Bopomofo Extended',                        (0x31A0, 0x31BF))),
379	(('Hangul Compatibility Jamo',                (0x3130, 0x318F)),),
380	(('Phags-pa',                                 (0xA840, 0xA87F)),),
381	(('Enclosed CJK Letters And Months',          (0x3200, 0x32FF)),),
382	(('CJK Compatibility',                        (0x3300, 0x33FF)),),
383	(('Hangul Syllables',                         (0xAC00, 0xD7AF)),),
384	(('Non-Plane 0 *',                            (0xD800, 0xDFFF)),),
385	(('Phoenician',                               (0x10900, 0x1091F)),),
386	(('CJK Unified Ideographs',                   (0x4E00, 0x9FFF)),
387	 ('CJK Radicals Supplement',                  (0x2E80, 0x2EFF)),
388	 ('Kangxi Radicals',                          (0x2F00, 0x2FDF)),
389	 ('Ideographic Description Characters',       (0x2FF0, 0x2FFF)),
390	 ('CJK Unified Ideographs Extension A',       (0x3400, 0x4DBF)),
391	 ('CJK Unified Ideographs Extension B',       (0x20000, 0x2A6DF)),
392	 ('Kanbun',                                   (0x3190, 0x319F))),
393	(('Private Use Area (plane 0)',               (0xE000, 0xF8FF)),),
394	(('CJK Strokes',                              (0x31C0, 0x31EF)),
395	 ('CJK Compatibility Ideographs',             (0xF900, 0xFAFF)),
396	 ('CJK Compatibility Ideographs Supplement',  (0x2F800, 0x2FA1F))),
397	(('Alphabetic Presentation Forms',            (0xFB00, 0xFB4F)),),
398	(('Arabic Presentation Forms-A',              (0xFB50, 0xFDFF)),),
399	(('Combining Half Marks',                     (0xFE20, 0xFE2F)),),
400	(('Vertical Forms',                           (0xFE10, 0xFE1F)),
401	 ('CJK Compatibility Forms',                  (0xFE30, 0xFE4F))),
402	(('Small Form Variants',                      (0xFE50, 0xFE6F)),),
403	(('Arabic Presentation Forms-B',              (0xFE70, 0xFEFF)),),
404	(('Halfwidth And Fullwidth Forms',            (0xFF00, 0xFFEF)),),
405	(('Specials',                                 (0xFFF0, 0xFFFF)),),
406	(('Tibetan',                                  (0x0F00, 0x0FFF)),),
407	(('Syriac',                                   (0x0700, 0x074F)),),
408	(('Thaana',                                   (0x0780, 0x07BF)),),
409	(('Sinhala',                                  (0x0D80, 0x0DFF)),),
410	(('Myanmar',                                  (0x1000, 0x109F)),),
411	(('Ethiopic',                                 (0x1200, 0x137F)),
412	 ('Ethiopic Supplement',                      (0x1380, 0x139F)),
413	 ('Ethiopic Extended',                        (0x2D80, 0x2DDF))),
414	(('Cherokee',                                 (0x13A0, 0x13FF)),),
415	(('Unified Canadian Aboriginal Syllabics',    (0x1400, 0x167F)),),
416	(('Ogham',                                    (0x1680, 0x169F)),),
417	(('Runic',                                    (0x16A0, 0x16FF)),),
418	(('Khmer',                                    (0x1780, 0x17FF)),
419	 ('Khmer Symbols',                            (0x19E0, 0x19FF))),
420	(('Mongolian',                                (0x1800, 0x18AF)),),
421	(('Braille Patterns',                         (0x2800, 0x28FF)),),
422	(('Yi Syllables',                             (0xA000, 0xA48F)),
423	 ('Yi Radicals',                              (0xA490, 0xA4CF))),
424	(('Tagalog',                                  (0x1700, 0x171F)),
425	 ('Hanunoo',                                  (0x1720, 0x173F)),
426	 ('Buhid',                                    (0x1740, 0x175F)),
427	 ('Tagbanwa',                                 (0x1760, 0x177F))),
428	(('Old Italic',                               (0x10300, 0x1032F)),),
429	(('Gothic',                                   (0x10330, 0x1034F)),),
430	(('Deseret',                                  (0x10400, 0x1044F)),),
431	(('Byzantine Musical Symbols',                (0x1D000, 0x1D0FF)),
432	 ('Musical Symbols',                          (0x1D100, 0x1D1FF)),
433	 ('Ancient Greek Musical Notation',           (0x1D200, 0x1D24F))),
434	(('Mathematical Alphanumeric Symbols',        (0x1D400, 0x1D7FF)),),
435	(('Private Use (plane 15)',                   (0xF0000, 0xFFFFD)),
436	 ('Private Use (plane 16)',                   (0x100000, 0x10FFFD))),
437	(('Variation Selectors',                      (0xFE00, 0xFE0F)),
438	 ('Variation Selectors Supplement',           (0xE0100, 0xE01EF))),
439	(('Tags',                                     (0xE0000, 0xE007F)),),
440	(('Limbu',                                    (0x1900, 0x194F)),),
441	(('Tai Le',                                   (0x1950, 0x197F)),),
442	(('New Tai Lue',                              (0x1980, 0x19DF)),),
443	(('Buginese',                                 (0x1A00, 0x1A1F)),),
444	(('Glagolitic',                               (0x2C00, 0x2C5F)),),
445	(('Tifinagh',                                 (0x2D30, 0x2D7F)),),
446	(('Yijing Hexagram Symbols',                  (0x4DC0, 0x4DFF)),),
447	(('Syloti Nagri',                             (0xA800, 0xA82F)),),
448	(('Linear B Syllabary',                       (0x10000, 0x1007F)),
449	 ('Linear B Ideograms',                       (0x10080, 0x100FF)),
450	 ('Aegean Numbers',                           (0x10100, 0x1013F))),
451	(('Ancient Greek Numbers',                    (0x10140, 0x1018F)),),
452	(('Ugaritic',                                 (0x10380, 0x1039F)),),
453	(('Old Persian',                              (0x103A0, 0x103DF)),),
454	(('Shavian',                                  (0x10450, 0x1047F)),),
455	(('Osmanya',                                  (0x10480, 0x104AF)),),
456	(('Cypriot Syllabary',                        (0x10800, 0x1083F)),),
457	(('Kharoshthi',                               (0x10A00, 0x10A5F)),),
458	(('Tai Xuan Jing Symbols',                    (0x1D300, 0x1D35F)),),
459	(('Cuneiform',                                (0x12000, 0x123FF)),
460	 ('Cuneiform Numbers and Punctuation',        (0x12400, 0x1247F))),
461	(('Counting Rod Numerals',                    (0x1D360, 0x1D37F)),),
462	(('Sundanese',                                (0x1B80, 0x1BBF)),),
463	(('Lepcha',                                   (0x1C00, 0x1C4F)),),
464	(('Ol Chiki',                                 (0x1C50, 0x1C7F)),),
465	(('Saurashtra',                               (0xA880, 0xA8DF)),),
466	(('Kayah Li',                                 (0xA900, 0xA92F)),),
467	(('Rejang',                                   (0xA930, 0xA95F)),),
468	(('Cham',                                     (0xAA00, 0xAA5F)),),
469	(('Ancient Symbols',                          (0x10190, 0x101CF)),),
470	(('Phaistos Disc',                            (0x101D0, 0x101FF)),),
471	(('Carian',                                   (0x102A0, 0x102DF)),
472	 ('Lycian',                                   (0x10280, 0x1029F)),
473	 ('Lydian',                                   (0x10920, 0x1093F))),
474	(('Domino Tiles',                             (0x1F030, 0x1F09F)),
475	 ('Mahjong Tiles',                            (0x1F000, 0x1F02F))),
476)
477
478
479_unicodeRangeSets = []
480
481def _getUnicodeRangeSets():
482	# build the sets of codepoints for each unicode range bit, and cache result
483	if not _unicodeRangeSets:
484		for bit, blocks in enumerate(OS2_UNICODE_RANGES):
485			rangeset = set()
486			for _, (start, stop) in blocks:
487				rangeset.update(set(range(start, stop+1)))
488			if bit == 57:
489				# The spec says that bit 57 ("Non Plane 0") implies that there's
490				# at least one codepoint beyond the BMP; so I also include all
491				# the non-BMP codepoints here
492				rangeset.update(set(range(0x10000, 0x110000)))
493			_unicodeRangeSets.append(rangeset)
494	return _unicodeRangeSets
495
496
497def intersectUnicodeRanges(unicodes, inverse=False):
498	""" Intersect a sequence of (int) Unicode codepoints with the Unicode block
499	ranges defined in the OpenType specification v1.7, and return the set of
500	'ulUnicodeRanges' bits for which there is at least ONE intersection.
501	If 'inverse' is True, return the the bits for which there is NO intersection.
502
503	>>> intersectUnicodeRanges([0x0410]) == {9}
504	True
505	>>> intersectUnicodeRanges([0x0410, 0x1F000]) == {9, 57, 122}
506	True
507	>>> intersectUnicodeRanges([0x0410, 0x1F000], inverse=True) == (
508	...     set(range(123)) - {9, 57, 122})
509	True
510	"""
511	unicodes = set(unicodes)
512	uniranges = _getUnicodeRangeSets()
513	bits = set([
514		bit for bit, unirange in enumerate(uniranges)
515		if not unirange.isdisjoint(unicodes) ^ inverse])
516	return bits
517
518
519if __name__ == "__main__":
520	import doctest, sys
521	sys.exit(doctest.testmod().failed)
522