Пример #1
0
def to_ufos(data, include_instances=False, family_name=None, debug=False):
    """Take .glyphs file data and load it into UFOs.

    Takes in data as a dictionary structured according to
    https://github.com/schriftgestalt/GlyphsSDK/blob/master/GlyphsFileFormat.md
    and returns a list of UFOs, one per master.

    If debug is True, returns unused input data instead of the resulting UFOs.
    """

    # check that source was generated with at least stable version 2.3
    # https://github.com/googlei18n/glyphsLib/pull/65#issuecomment-237158140
    if data.pop('.appVersion', 0) < 895:
        warn('This Glyphs source was generated with an outdated version of '
             'Glyphs. The resulting UFOs may be incorrect.')

    source_family_name = data.pop('familyName')
    if family_name is None:
        family_name = source_family_name

    feature_prefixes, classes, features = [], [], []
    for f in data.get('featurePrefixes', []):
        feature_prefixes.append(
            (f.pop('name'), f.pop('code'), f.pop('automatic', None)))
    for c in data.get('classes', []):
        classes.append((c.pop('name'), c.pop('code'), c.pop('automatic',
                                                            None)))
    for f in data.get('features', []):
        features.append(
            (f.pop('name'), f.pop('code'), f.pop('automatic', None),
             f.pop('disabled', None), f.pop('notes', None)))
    kerning_groups = {}

    # stores background data from "associated layers"
    supplementary_bg_data = []

    #TODO(jamesgk) maybe create one font at a time to reduce memory usage
    ufos, master_id_order = generate_base_fonts(data, family_name)

    glyph_order = []

    for glyph in data['glyphs']:
        add_glyph_to_groups(kerning_groups, glyph)

        glyph_name = glyph.pop('glyphname')
        glyph_order.append(glyph_name)
        if not re.match(r'^([A-Za-z_][\w.]*|\.notdef)$', glyph_name):
            warn('Illegal glyph name "%s". If this is used in the font\'s '
                 'feature syntax, it could cause errors.' % glyph_name)

        # pop glyph metadata only once, i.e. not when looping through layers
        metadata_keys = [
            'unicode', 'color', 'export', 'lastChange', 'leftMetricsKey',
            'note', 'production', 'rightMetricsKey', 'widthMetricsKey'
        ]
        glyph_data = {k: glyph.pop(k) for k in metadata_keys if k in glyph}

        for layer in glyph['layers']:
            layer_id = layer.pop('layerId')
            layer_name = layer.pop('name', None)

            assoc_id = layer.pop('associatedMasterId', None)
            if assoc_id is not None:
                if layer_name is not None:
                    supplementary_bg_data.append(
                        (assoc_id, glyph_name, layer_name, layer))
                continue

            ufo = ufos[layer_id]
            glyph = ufo.newGlyph(glyph_name)
            load_glyph(glyph, layer, glyph_data)

    for layer_id, glyph_name, bg_name, bg_data in supplementary_bg_data:
        glyph = ufos[layer_id][glyph_name]
        set_robofont_glyph_background(glyph, bg_name, bg_data)

    for ufo in ufos.values():
        propagate_font_anchors(ufo)
        add_features_to_ufo(ufo, feature_prefixes, classes, features)
        add_groups_to_ufo(ufo, kerning_groups)
        ufo.lib[PUBLIC_PREFIX + 'glyphOrder'] = glyph_order

    for master_id, kerning in data.pop('kerning', {}).items():
        load_kerning(ufos[master_id], kerning)

    result = [ufos[master_id] for master_id in master_id_order]
    instances = {
        'defaultFamilyName': source_family_name,
        'data': data.pop('instances', [])
    }
    if debug:
        return clear_data(data)
    elif include_instances:
        return result, instances
    return result
Пример #2
0
def to_ufos(data, include_instances=False, family_name=None, debug=False):
    """Take .glyphs file data and load it into UFOs.

    Takes in data as a dictionary structured according to
    https://github.com/schriftgestalt/GlyphsSDK/blob/master/GlyphsFileFormat.md
    and returns a list of UFOs, one per master.

    If debug is True, returns unused input data instead of the resulting UFOs.
    """

    # check that source was generated with at least stable version 2.3
    # https://github.com/googlei18n/glyphsLib/pull/65#issuecomment-237158140
    if data.pop('.appVersion', 0) < 895:
        warn('This Glyphs source was generated with an outdated version of '
             'Glyphs. The resulting UFOs may be incorrect.')

    source_family_name = data.pop('familyName')
    if family_name is None:
        family_name = source_family_name

    feature_prefixes, classes, features = [], [], []
    for f in data.get('featurePrefixes', []):
        feature_prefixes.append((f.pop('name'), f.pop('code'),
                                 f.pop('automatic', None)))
    for c in data.get('classes', []):
        classes.append((c.pop('name'), c.pop('code'), c.pop('automatic', None)))
    for f in data.get('features', []):
        features.append((f.pop('name'), f.pop('code'), f.pop('automatic', None),
                         f.pop('disabled', None), f.pop('notes', None)))
    kerning_groups = {}

    # stores background data from "associated layers"
    supplementary_bg_data = []

    #TODO(jamesgk) maybe create one font at a time to reduce memory usage
    ufos, master_id_order = generate_base_fonts(data, family_name)

    glyph_order = []

    for glyph in data['glyphs']:
        add_glyph_to_groups(kerning_groups, glyph)

        glyph_name = glyph.pop('glyphname')
        glyph_order.append(glyph_name)
        if not re.match(r'^([A-Za-z_][\w.]*|\.notdef)$', glyph_name):
            warn('Illegal glyph name "%s". If this is used in the font\'s '
                 'feature syntax, it could cause errors.' % glyph_name)

        # pop glyph metadata only once, i.e. not when looping through layers
        metadata_keys = ['unicode', 'color', 'export', 'lastChange',
                         'leftMetricsKey', 'note', 'production',
                         'rightMetricsKey', 'widthMetricsKey']
        glyph_data = {k: glyph.pop(k) for k in metadata_keys if k in glyph}

        for layer in glyph['layers']:
            layer_id = layer.pop('layerId')
            layer_name = layer.pop('name', None)

            assoc_id = layer.pop('associatedMasterId', None)
            if assoc_id is not None:
                if layer_name is not None:
                    supplementary_bg_data.append(
                        (assoc_id, glyph_name, layer_name, layer))
                continue

            ufo = ufos[layer_id]
            glyph = ufo.newGlyph(glyph_name)
            load_glyph(glyph, layer, glyph_data)

    for layer_id, glyph_name, bg_name, bg_data in supplementary_bg_data:
        glyph = ufos[layer_id][glyph_name]
        set_glyph_background(glyph, bg_name, bg_data)

    for ufo in ufos.values():
        propagate_font_anchors(ufo)
        add_features_to_ufo(ufo, feature_prefixes, classes, features)
        add_groups_to_ufo(ufo, kerning_groups)
        ufo.lib[PUBLIC_PREFIX + 'glyphOrder'] = glyph_order

    for master_id, kerning in data.pop('kerning', {}).items():
        load_kerning(ufos[master_id], kerning)

    result = [ufos[master_id] for master_id in master_id_order]
    instances = {'defaultFamilyName': source_family_name,
                 'data': data.pop('instances', [])}
    if debug:
        return clear_data(data)
    elif include_instances:
        return result, instances
    return result
Пример #3
0
def to_ufos(data, include_instances=False, family_name=None, debug=False):
    """Take .glyphs file data and load it into UFOs.

    Takes in data as a dictionary structured according to
    https://github.com/schriftgestalt/GlyphsSDK/blob/master/GlyphsFileFormat.md
    and returns a list of UFOs, one per master.

    If debug is True, returns unused input data instead of the resulting UFOs.
    """

    # check that source was generated with at least stable version 2.3
    # https://github.com/googlei18n/glyphsLib/pull/65#issuecomment-237158140
    if data.pop('.appVersion', 0) < 895:
        logger.warn(
            'This Glyphs source was generated with an outdated version '
            'of Glyphs. The resulting UFOs may be incorrect.')

    source_family_name = data.pop('familyName')
    if family_name is None:
        family_name = source_family_name

    feature_prefixes, classes, features = [], [], []
    for f in data.get('featurePrefixes', []):
        feature_prefixes.append(
            (f.pop('name'), f.pop('code'), f.pop('automatic', None)))
    for c in data.get('classes', []):
        classes.append((c.pop('name'), c.pop('code'), c.pop('automatic',
                                                            None)))
    for f in data.get('features', []):
        features.append(
            (f.pop('name'), f.pop('code'), f.pop('automatic', None),
             f.pop('disabled', None), f.pop('notes', None)))
    kerning_groups = {}

    # stores background data from "associated layers"
    supplementary_bg_data = []

    #TODO(jamesgk) maybe create one font at a time to reduce memory usage
    ufos, master_id_order = generate_base_fonts(data, family_name)

    # get the 'glyphOrder' custom parameter as stored in the lib.plist.
    # We assume it's the same for all ufos.
    first_ufo = ufos[master_id_order[0]]
    glyphOrder_key = PUBLIC_PREFIX + 'glyphOrder'
    if glyphOrder_key in first_ufo.lib:
        glyph_order = first_ufo.lib[glyphOrder_key]
    else:
        glyph_order = []
    sorted_glyphset = set(glyph_order)

    for glyph in data['glyphs']:
        add_glyph_to_groups(kerning_groups, glyph)

        glyph_name = glyph.pop('glyphname')
        if glyph_name not in sorted_glyphset:
            # glyphs not listed in the 'glyphOrder' custom parameter but still
            # in the font are appended after the listed glyphs, in the order
            # in which they appear in the source file
            glyph_order.append(glyph_name)

        # pop glyph metadata only once, i.e. not when looping through layers
        metadata_keys = [
            'unicode', 'color', 'export', 'lastChange', 'leftMetricsKey',
            'note', 'production', 'rightMetricsKey', 'widthMetricsKey',
            'category', 'subCategory'
        ]
        glyph_data = {k: glyph.pop(k) for k in metadata_keys if k in glyph}

        for layer in glyph['layers']:
            layer_id = layer.pop('layerId')
            layer_name = layer.pop('name', None)

            assoc_id = layer.pop('associatedMasterId', None)
            if assoc_id is not None:
                if layer_name is not None:
                    supplementary_bg_data.append(
                        (assoc_id, glyph_name, layer_name, layer))
                continue

            ufo = ufos[layer_id]
            glyph = ufo.newGlyph(glyph_name)
            load_glyph(glyph, layer, glyph_data)

    for layer_id, glyph_name, bg_name, bg_data in supplementary_bg_data:
        glyph = ufos[layer_id][glyph_name]
        set_robofont_glyph_background(glyph, bg_name, bg_data)

    for ufo in ufos.values():
        ufo.lib[glyphOrder_key] = glyph_order
        propagate_font_anchors(ufo)
        add_features_to_ufo(ufo, feature_prefixes, classes, features)
        add_groups_to_ufo(ufo, kerning_groups)

    for master_id, kerning in data.pop('kerning', {}).items():
        load_kerning(ufos[master_id], kerning)

    result = [ufos[master_id] for master_id in master_id_order]
    instances = {
        'defaultFamilyName': source_family_name,
        'data': data.pop('instances', [])
    }

    # the 'Variation Font Origin' is a font-wide custom parameter, thus it is
    # shared by all the master ufos; here we just get it from the first one
    varfont_origin_key = "Variation Font Origin"
    varfont_origin = first_ufo.lib.get(GLYPHS_PREFIX + varfont_origin_key)
    if varfont_origin:
        instances[varfont_origin_key] = varfont_origin
    if debug:
        return clear_data(data)
    elif include_instances:
        return result, instances
    return result
Пример #4
0
def merge(args):
    """Merges Arabic and Latin fonts together, and messages the combined font a
    bit. Returns the combined font."""

    ufo = Font(args.arabicfile)

    propagate_font_anchors(ufo)

    latin = Font(args.latinfile)
    # Parse the GlyphOrderAndAliasDB file for Unicode values and production
    # glyph names of the Latin glyphs.
    goadb = GOADBParser(
        os.path.dirname(args.latinfile) + "/../GlyphOrderAndAliasDB")

    ufo.lib[POSTSCRIPT_NAMES] = {}

    # Save original glyph order, used below.
    glyphOrder = ufo.glyphOrder + latin.glyphOrder

    # Generate production glyph names for Arabic glyphs, in case it differs
    # from working names. This will be used by ufo2ft to set the final glyph
    # names in the font file.
    for glyph in ufo:
        if glyph.unicode is not None:
            if glyph.unicode < 0xffff:
                postName = "uni%04X" % glyph.unicode
            else:
                postName = "u%06X" % glyph.unicode
            if postName != glyph.name:
                ufo.lib[POSTSCRIPT_NAMES][glyph.name] = postName

    # Populate the font’s feature text, we keep our main feature file out of
    # the UFO to share it between the fonts.
    features = ufo.features
    with open(args.feature_file) as feafile:
        fea = feafile.read()
        # Set Latin language system, ufo2ft will use it when generating kern
        # feature.
        features.text += fea.replace("#{languagesystems}",
                                     "languagesystem latn dflt;")
    features.text += generateStyleSets(ufo)

    for glyph in latin:
        if glyph.name in goadb.encodings:
            glyph.unicode = goadb.encodings[glyph.name]

    # Source Sans Pro has different advance widths for space and NBSP
    # glyphs, fix it.
    latin["nbspace"].width = latin["space"].width

    glyphs, components = collectGlyphs(latin, args.latin_subset)

    counter = Counter(components)
    uniqueComponents = set()
    for name in counter:
        if name not in glyphs:
            latin[name].unicode = None
        if counter[name] == 1:
            uniqueComponents.add(name)
        else:
            glyphs.add(name)

    # Set Latin production names
    ufo.lib[POSTSCRIPT_NAMES].update(goadb.names)

    # Copy Latin glyphs.
    for name in glyphs:
        glyph = latin[name]
        for component in glyph.components:
            if component.baseGlyph in uniqueComponents:
                glyph.decomposeComponent(component)
        # Remove anchors from spacing marks, otherwise ufo2ft will give them
        # mark glyph class which will cause HarfBuzz to zero their width.
        if glyph.unicode and unicodedata.category(unichr(
                glyph.unicode)) in ("Sk", "Lm"):
            for anchor in glyph.anchors:
                glyph.removeAnchor(anchor)
        # Add Arabic anchors to the dotted circle, we use an offset of 100
        # units because the Latin anchors are too close to the glyph.
        offset = 100
        if glyph.unicode == 0x25CC:
            for anchor in glyph.anchors:
                if anchor.name == "aboveLC":
                    glyph.appendAnchor(
                        dict(name="markAbove", x=anchor.x,
                             y=anchor.y + offset))
                    glyph.appendAnchor(
                        dict(name="hamzaAbove",
                             x=anchor.x,
                             y=anchor.y + offset))
                if anchor.name == "belowLC":
                    glyph.appendAnchor(
                        dict(name="markBelow", x=anchor.x,
                             y=anchor.y - offset))
                    glyph.appendAnchor(
                        dict(name="hamzaBelow",
                             x=anchor.x,
                             y=anchor.y - offset))
        # Break loudly if we have duplicated glyph in Latin and Arabic.
        # TODO should check duplicated Unicode values as well
        assert glyph.name not in ufo, glyph.name
        ufo.insertGlyph(glyph)

    # Copy kerning and groups.
    ufo.groups.update(latin.groups)
    ufo.kerning.update(latin.kerning)

    # We don’t set these in the Arabic font, so we just copy the Latin’s.
    for attr in ("xHeight", "capHeight"):
        value = getattr(latin.info, attr)
        if value is not None:
            setattr(ufo.info, attr, getattr(latin.info, attr))

    # MutatorMath does not like multiple unicodes and will drop it entirely,
    # turning the glyph unencoded:
    # https://github.com/LettError/MutatorMath/issues/85
    for glyph in ufo:
        assert not " " in glyph.unicodes

    # Make sure we don’t have glyphs with the same unicode value
    unicodes = []
    for glyph in ufo:
        unicodes.extend(glyph.unicodes)
    duplicates = set([u for u in unicodes if unicodes.count(u) > 1])
    assert len(duplicates) == 0, "Duplicate unicodes: %s " % (
        ["%04X" % d for d in duplicates])

    # Make sure we have a fixed glyph order by using the original Arabic and
    # Latin glyph order, not whatever we end up with after adding glyphs.
    ufo.glyphOrder = sorted(ufo.glyphOrder, key=glyphOrder.index)

    return ufo