source: packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/fontInfoData.py @ 1073

Revision 1073, 15.7 KB checked in by tal, 16 months ago (diff)
Import fix.
Line 
1"""
2This file provides fallback data for info attributes
3that are required for building OTFs. There are two main
4functions that are important:
5
6* :func:`~getAttrWithFallback`
7* :func:`~preflightInfo`
8
9There are a set of other functions that are used internally
10for synthesizing values for specific attributes. These can be
11used externally as well.
12"""
13
14import time
15import unicodedata
16from fontTools.misc.textTools import binary2num
17from fontTools.misc.arrayTools import unionRect
18import ufoLib
19try:
20    set
21except NameError:
22    from sets import Set as set
23
24# -----------------
25# Special Fallbacks
26# -----------------
27
28# generic
29
30def styleMapFamilyNameFallback(info):
31    """
32    Fallback to *openTypeNamePreferredFamilyName openTypeNamePreferredSubfamilyName*.
33    """
34    familyName = getAttrWithFallback(info, "openTypeNamePreferredFamilyName")
35    styleName = getAttrWithFallback(info, "openTypeNamePreferredSubfamilyName")
36    if styleName is None:
37        styleName = u""
38    return (familyName + u" " + styleName).strip()
39
40# head
41
42def dateStringForNow():
43    year, month, day, hour, minute, second, weekDay, yearDay, isDST = time.localtime()
44    year = str(year)
45    month = str(month).zfill(2)
46    day = str(day).zfill(2)
47    hour = str(hour).zfill(2)
48    minute = str(minute).zfill(2)
49    second = str(second).zfill(2)
50    return "%s/%s/%s %s:%s:%s" % (year, month, day, hour, minute, second)
51
52def openTypeHeadCreatedFallback(info):
53    """
54    Fallback to now.
55    """
56    return dateStringForNow()
57
58# hhea
59
60def openTypeHheaAscenderFallback(info):
61    """
62    Fallback to *unitsPerEm + descender*.
63    """
64    return info.unitsPerEm + info.descender
65
66def openTypeHheaDescenderFallback(info):
67    """
68    Fallback to *descender*.
69    """
70    return info.descender
71
72# name
73
74def openTypeNameVersionFallback(info):
75    """
76    Fallback to *versionMajor.versionMinor* in the form 0.000.
77    """
78    versionMajor = getAttrWithFallback(info, "versionMajor")
79    versionMinor = getAttrWithFallback(info, "versionMinor")
80    return "%d.%s" % (versionMajor, str(versionMinor).zfill(3))
81
82def openTypeNameUniqueIDFallback(info):
83    """
84    Fallback to *openTypeNameVersion;openTypeOS2VendorID;styleMapFamilyName styleMapStyleName*.
85    """
86    version = getAttrWithFallback(info, "openTypeNameVersion")
87    vendor = getAttrWithFallback(info, "openTypeOS2VendorID")
88    familyName = getAttrWithFallback(info, "styleMapFamilyName")
89    styleName = getAttrWithFallback(info, "styleMapStyleName").title()
90    return u"%s;%s;%s %s" % (version, vendor, familyName, styleName)
91
92def openTypeNamePreferredFamilyNameFallback(info):
93    """
94    Fallback to *familyName*.
95    """
96    return info.familyName
97
98def openTypeNamePreferredSubfamilyNameFallback(info):
99    """
100    Fallback to *styleName*.
101    """
102    return info.styleName
103
104def openTypeNameCompatibleFullNameFallback(info):
105    """
106    Fallback to *styleMapFamilyName styleMapStyleName*.
107    If *styleMapStyleName* is *regular* this will not add
108    the style name.
109    """
110    familyName = getAttrWithFallback(info, "styleMapFamilyName")
111    styleMapStyleName = getAttrWithFallback(info, "styleMapStyleName")
112    if styleMapStyleName != "regular":
113        familyName += " " + styleMapStyleName.title()
114    return familyName
115
116def openTypeNameWWSFamilyNameFallback(info):
117    # not yet supported
118    return None
119
120def openTypeNameWWSSubfamilyNameFallback(info):
121    # not yet supported
122    return None
123
124# OS/2
125
126def openTypeOS2TypoAscenderFallback(info):
127    """
128    Fallback to *unitsPerEm + descender*.
129    """
130    return info.unitsPerEm + info.descender
131
132def openTypeOS2TypoDescenderFallback(info):
133    """
134    Fallback to *descender*.
135    """
136    return info.descender
137
138def openTypeOS2WinAscentFallback(info):
139    """
140    Fallback to the maximum y value of the font's bounding box.
141    If that is not available, fallback to *ascender*.
142    """
143    font = info.getParent()
144    if font is None:
145        return getAttrWithFallback(info, "ascender")
146    bounds = getFontBounds(font)
147    xMin, yMin, xMax, yMax = bounds
148    return yMax
149
150def openTypeOS2WinDescentFallback(info):
151    """
152    Fallback to the minimum y value of the font's bounding box.
153    If that is not available, fallback to *descender*.
154    """
155    font = info.getParent()
156    if font is None:
157        return abs(getAttrWithFallback(info, "descender"))
158    bounds = getFontBounds(font)
159    if bounds is None:
160        return abs(getAttrWithFallback(info, "descender"))
161    xMin, yMin, xMax, yMax = bounds
162    return abs(yMin)
163
164# postscript
165
166_postscriptFontNameExceptions = set(" [](){}<>/%")
167_postscriptFontNameAllowed = set([unichr(i) for i in xrange(33, 137)])
168
169def normalizeNameForPostscript(name):
170    normalized = []
171    for c in name:
172        if c in _postscriptFontNameExceptions:
173            continue
174        if c not in _postscriptFontNameAllowed:
175            c = unicodedata.normalize("NFKD", c).encode("ascii", "ignore")
176        normalized.append(c)
177    return "".join(normalized)
178
179def postscriptFontNameFallback(info):
180    """
181    Fallback to a string containing only valid characters
182    as defined in the specification. This will draw from
183    *openTypeNamePreferredFamilyName* and *openTypeNamePreferredSubfamilyName*.
184    """
185    name = u"%s-%s" % (getAttrWithFallback(info, "openTypeNamePreferredFamilyName"), getAttrWithFallback(info, "openTypeNamePreferredSubfamilyName"))
186    return normalizeNameForPostscript(name)
187
188def postscriptFullNameFallback(info):
189    """
190    Fallback to *openTypeNamePreferredFamilyName openTypeNamePreferredSubfamilyName*.
191    """
192    return u"%s %s" % (getAttrWithFallback(info, "openTypeNamePreferredFamilyName"), getAttrWithFallback(info, "openTypeNamePreferredSubfamilyName"))
193
194def postscriptSlantAngleFallback(info):
195    """
196    Fallback to *italicAngle*.
197    """
198    return getAttrWithFallback(info, "italicAngle")
199
200_postscriptWeightNameOptions = {
201    100 : "Thin",
202    200 : "Extra-light",
203    300 : "Light",
204    400 : "Normal",
205    500 : "Medium",
206    600 : "Semi-bold",
207    700 : "Bold",
208    800 : "Extra-bold",
209    900 : "Black"
210}
211
212def postscriptWeightNameFallback(info):
213    """
214    Fallback to the closest match of the *openTypeOS2WeightClass*
215    in this table:
216
217    ===  ===========
218    100  Thin
219    200  Extra-light
220    300  Light
221    400  Normal
222    500  Medium
223    600  Semi-bold
224    700  Bold
225    800  Extra-bold
226    900  Black
227    ===  ===========
228    """
229    value = getAttrWithFallback(info, "openTypeOS2WeightClass")
230    value = int(round(value * .01) * 100)
231    if value < 100:
232        value = 100
233    elif value > 900:
234        value = 900
235    name = _postscriptWeightNameOptions[value]
236    return name
237
238# --------------
239# Attribute Maps
240# --------------
241
242staticFallbackData = dict(
243    styleMapStyleName="regular",
244    versionMajor=0,
245    versionMinor=0,
246    copyright=None,
247    trademark=None,
248    italicAngle=0,
249    # not needed
250    year=None,
251    note=None,
252
253    openTypeHeadLowestRecPPEM=6,
254    openTypeHeadFlags=[0, 1],
255
256    openTypeHheaLineGap=200,
257    openTypeHheaCaretSlopeRise=1,
258    openTypeHheaCaretSlopeRun=0,
259    openTypeHheaCaretOffset=0,
260
261    openTypeNameDesigner=None,
262    openTypeNameDesignerURL=None,
263    openTypeNameManufacturer=None,
264    openTypeNameManufacturerURL=None,
265    openTypeNameLicense=None,
266    openTypeNameLicenseURL=None,
267    openTypeNameDescription=None,
268    openTypeNameSampleText=None,
269
270    openTypeOS2WidthClass=5,
271    openTypeOS2WeightClass=400,
272    openTypeOS2Selection=[],
273    openTypeOS2VendorID="NONE",
274    openTypeOS2Panose=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
275    openTypeOS2FamilyClass=[0, 0],
276    openTypeOS2UnicodeRanges=[],
277    openTypeOS2CodePageRanges=[],
278    openTypeOS2TypoLineGap=200,
279    openTypeOS2Type=[2],
280    # let the FDK fallback on these
281    openTypeOS2SubscriptXSize=None,
282    openTypeOS2SubscriptYSize=None,
283    openTypeOS2SubscriptXOffset=None,
284    openTypeOS2SubscriptYOffset=None,
285    openTypeOS2SuperscriptXSize=None,
286    openTypeOS2SuperscriptYSize=None,
287    openTypeOS2SuperscriptXOffset=None,
288    openTypeOS2SuperscriptYOffset=None,
289    openTypeOS2StrikeoutSize=None,
290    openTypeOS2StrikeoutPosition=None,
291
292    # fallback to None on these
293    # as the user should be in
294    # complete control
295    openTypeVheaVertTypoAscender=None,
296    openTypeVheaVertTypoDescender=None,
297    openTypeVheaVertTypoLineGap=None,
298    openTypeVheaCaretSlopeRise=None,
299    openTypeVheaCaretSlopeRun=None,
300    openTypeVheaCaretOffset=None,
301
302    postscriptUniqueID=None,
303    postscriptUnderlineThickness=None,
304    postscriptUnderlinePosition=None,
305    postscriptIsFixedPitch=False,
306    postscriptBlueValues=[],
307    postscriptOtherBlues=[],
308    postscriptFamilyBlues=[],
309    postscriptFamilyOtherBlues=[],
310    postscriptStemSnapH=[],
311    postscriptStemSnapV=[],
312    postscriptBlueFuzz=1,
313    postscriptBlueShift=7,
314    postscriptBlueScale=.039625,
315    postscriptForceBold=False,
316    postscriptDefaultWidthX=200,
317    postscriptNominalWidthX=0,
318
319    # not used in OTF
320    postscriptDefaultCharacter=None,
321    postscriptWindowsCharacterSet=None,
322
323    # not used in OTF
324    macintoshFONDFamilyID=None,
325    macintoshFONDName=None
326)
327
328specialFallbacks = dict(
329    styleMapFamilyName=styleMapFamilyNameFallback,
330    openTypeHeadCreated=openTypeHeadCreatedFallback,
331    openTypeHheaAscender=openTypeHheaAscenderFallback,
332    openTypeHheaDescender=openTypeHheaDescenderFallback,
333    openTypeNameVersion=openTypeNameVersionFallback,
334    openTypeNameUniqueID=openTypeNameUniqueIDFallback,
335    openTypeNamePreferredFamilyName=openTypeNamePreferredFamilyNameFallback,
336    openTypeNamePreferredSubfamilyName=openTypeNamePreferredSubfamilyNameFallback,
337    openTypeNameCompatibleFullName=openTypeNameCompatibleFullNameFallback,
338    openTypeNameWWSFamilyName=openTypeNameWWSFamilyNameFallback,
339    openTypeNameWWSSubfamilyName=openTypeNameWWSSubfamilyNameFallback,
340    openTypeOS2TypoAscender=openTypeOS2TypoAscenderFallback,
341    openTypeOS2TypoDescender=openTypeOS2TypoDescenderFallback,
342    openTypeOS2WinAscent=openTypeOS2WinAscentFallback,
343    openTypeOS2WinDescent=openTypeOS2WinDescentFallback,
344    postscriptFontName=postscriptFontNameFallback,
345    postscriptFullName=postscriptFullNameFallback,
346    postscriptSlantAngle=postscriptSlantAngleFallback,
347    postscriptWeightName=postscriptWeightNameFallback
348)
349
350requiredAttributes = set(ufoLib.fontInfoAttributesVersion2) - (set(staticFallbackData.keys()) | set(specialFallbacks.keys()))
351
352recommendedAttributes = set([
353    "styleMapFamilyName",
354    "versionMajor",
355    "versionMinor",
356    "copyright",
357    "trademark",
358    "openTypeHeadCreated",
359    "openTypeNameDesigner",
360    "openTypeNameDesignerURL",
361    "openTypeNameManufacturer",
362    "openTypeNameManufacturerURL",
363    "openTypeNameLicense",
364    "openTypeNameLicenseURL",
365    "openTypeNameDescription",
366    "openTypeNameSampleText",
367    "openTypeOS2WidthClass",
368    "openTypeOS2WeightClass",
369    "openTypeOS2VendorID",
370    "openTypeOS2Panose",
371    "openTypeOS2FamilyClass",
372    "openTypeOS2UnicodeRanges",
373    "openTypeOS2CodePageRanges",
374    "openTypeOS2TypoLineGap",
375    "openTypeOS2Type",
376    "postscriptBlueValues",
377    "postscriptOtherBlues",
378    "postscriptFamilyBlues",
379    "postscriptFamilyOtherBlues",
380    "postscriptStemSnapH",
381    "postscriptStemSnapV"
382])
383
384# ------------
385# Main Methods
386# ------------
387
388def getAttrWithFallback(info, attr):
389    """
390    Get the value for *attr* from the *info* object.
391    If the object does not have the attribute or the value
392    for the atribute is None, this will either get a
393    value from a predefined set of attributes or it
394    will synthesize a value from the available data.
395    """
396    if hasattr(info, attr) and getattr(info, attr) is not None:
397        value = getattr(info, attr)
398    else:
399        if attr in specialFallbacks:
400            value = specialFallbacks[attr](info)
401        else:
402            value = staticFallbackData[attr]
403    return value
404
405def preflightInfo(info):
406    """
407    Returns a dict containing two items. The value for each
408    item will be a list of info attribute names.
409
410    ==================  ===
411    missingRequired     Required data that is missing.
412    missingRecommended  Recommended data that is missing.
413    ==================  ===
414    """
415    missingRequired = set()
416    missingRecommended = set()
417    for attr in requiredAttributes:
418        if not hasattr(info, attr) or getattr(info, attr) is None:
419            missingRequired.add(attr)
420    for attr in recommendedAttributes:
421        if not hasattr(info, attr) or getattr(info, attr) is None:
422            missingRecommended.add(attr)
423    return dict(missingRequired=missingRequired, missingRecommended=missingRecommended)
424
425def getFontBounds(font):
426    """
427    Get a tuple of (xMin, yMin, xMax, yMax) for all
428    glyphs in the given *font*.
429    """
430    rect = None
431    # defcon
432    if hasattr(font, "bounds"):
433        rect = font.bounds
434    # others
435    else:
436        for glyph in font:
437            # robofab
438            if hasattr(glyph,"box"):
439                bounds = glyph.box
440            # others
441            else:
442                bounds = glyph.bounds
443            if rect is None:
444                rect = bounds
445                continue
446            if rect is not None and bounds is not None:
447                rect = unionRect(rect, bounds)
448    if rect is None:
449        rect = (0, 0, 0, 0)
450    return rect
451
452# -----------------
453# Low Level Support
454# -----------------
455
456# these should not be used outside of this package
457
458def intListToNum(intList, start, length):
459    all = []
460    bin = ""
461    for i in range(start, start+length):
462        if i in intList:
463            b = "1"
464        else:
465            b = "0"
466        bin = b + bin
467        if not (i + 1) % 8:
468            all.append(bin)
469            bin = ""
470    if bin:
471        all.append(bin)
472    all.reverse()
473    all = " ".join(all)
474    return binary2num(all)
475
476def dateStringToTimeValue(date):
477    try:
478        t = time.strptime(date, "%Y/%m/%d %H:%M:%S")
479        return long(time.mktime(t))
480    except OverflowError:
481        return 0L
482
483# ----
484# Test
485# ----
486
487class _TestInfoObject(object):
488
489    def __init__(self):
490        self.familyName = "Family Name"
491        self.styleName = "Style Name"
492        self.unitsPerEm = 1000
493        self.descender = -250
494        self.xHeight = 450
495        self.capHeight = 600
496        self.ascender = 650
497        self.bounds = (0, -225, 100, 755)
498
499    def getParent(self):
500        return self
501
502
503def _test():
504    """
505    >>> info = _TestInfoObject()
506
507    >>> getAttrWithFallback(info, "familyName")
508    'Family Name'
509    >>> getAttrWithFallback(info, "styleName")
510    'Style Name'
511
512    >>> getAttrWithFallback(info, "styleMapFamilyName")
513    u'Family Name Style Name'
514    >>> info.styleMapFamilyName = "Style Map Family Name"
515    >>> getAttrWithFallback(info, "styleMapFamilyName")
516    'Style Map Family Name'
517
518    >>> getAttrWithFallback(info, "openTypeNamePreferredFamilyName")
519    'Family Name'
520    >>> getAttrWithFallback(info, "openTypeNamePreferredSubfamilyName")
521    'Style Name'
522    >>> getAttrWithFallback(info, "openTypeNameCompatibleFullName")
523    'Style Map Family Name'
524
525    >>> getAttrWithFallback(info, "openTypeHheaAscender")
526    750
527    >>> getAttrWithFallback(info, "openTypeHheaDescender")
528    -250
529
530    >>> getAttrWithFallback(info, "openTypeNameVersion")
531    '0.000'
532    >>> info.versionMinor = 1
533    >>> info.versionMajor = 1
534    >>> getAttrWithFallback(info, "openTypeNameVersion")
535    '1.001'
536
537    >>> getAttrWithFallback(info, "openTypeNameUniqueID")
538    u'1.001;NONE;Style Map Family Name Regular'
539
540    >>> getAttrWithFallback(info, "openTypeOS2TypoAscender")
541    750
542    >>> getAttrWithFallback(info, "openTypeOS2TypoDescender")
543    -250
544    >>> getAttrWithFallback(info, "openTypeOS2WinAscent")
545    755
546    >>> getAttrWithFallback(info, "openTypeOS2WinDescent")
547    225
548
549    >>> getAttrWithFallback(info, "postscriptSlantAngle")
550    0
551    >>> getAttrWithFallback(info, "postscriptWeightName")
552    'Normal'
553    """
554
555if __name__ == "__main__":
556    import doctest
557    doctest.testmod()
Note: See TracBrowser for help on using the repository browser.