예제 #1
0
파일: models.py 프로젝트: Pomax/fonttools
def main(args):
	from fontTools import configLogger

	args = args[1:]

	# TODO: allow user to configure logging via command-line options
	configLogger(level="INFO")

	if len(args) < 1:
		print("usage: fonttools varLib.models source.designspace", file=sys.stderr)
		print("  or")
		print("usage: fonttools varLib.models location1 location2 ...", file=sys.stderr)
		sys.exit(1)

	from pprint import pprint

	if len(args) == 1 and args[0].endswith('.designspace'):
		from fontTools.designspaceLib import DesignSpaceDocument
		doc = DesignSpaceDocument()
		doc.read(args[0])
		locs = [s.location for s in doc.sources]
		print("Original locations:")
		pprint(locs)
		doc.normalize()
		print("Normalized locations:")
		pprint(locs)
	else:
		axes = [chr(c) for c in range(ord('A'), ord('Z')+1)]
		locs = [dict(zip(axes, (float(v) for v in s.split(',')))) for s in args]

	model = VariationModel(locs)
	print("Sorted locations:")
	pprint(model.locations)
	print("Supports:")
	pprint(model.supports)
예제 #2
0
def test_with_with_path_object(tmpdir):
    import pathlib
    tmpdir = str(tmpdir)
    dest = pathlib.Path(tmpdir) / "test.designspace"
    doc = DesignSpaceDocument()
    doc.write(dest)
    assert dest.exists()
예제 #3
0
파일: plot.py 프로젝트: MrBrezina/fonttools
def main(args=None):
	from fontTools import configLogger

	if args is None:
		args = sys.argv[1:]

	# configure the library logger (for >= WARNING)
	configLogger()
	# comment this out to enable debug messages from logger
	# log.setLevel(logging.DEBUG)

	if len(args) < 1:
		print("usage: fonttools varLib.plot source.designspace", file=sys.stderr)
		print("  or")
		print("usage: fonttools varLib.plot location1 location2 ...", file=sys.stderr)
		sys.exit(1)

	fig = pyplot.figure()

	if len(args) == 1 and args[0].endswith('.designspace'):
		doc = DesignSpaceDocument()
		doc.read(args[0])
		plotDocument(doc, fig)
	else:
		axes = [chr(c) for c in range(ord('A'), ord('Z')+1)]
		locs = [dict(zip(axes, (float(v) for v in s.split(',')))) for s in args]
		plotLocationsSurfaces(locs, fig)

	pyplot.show()
예제 #4
0
def test_load_masters_layerName_without_required_font():
    ds = DesignSpaceDocument()
    s = SourceDescriptor()
    s.font = None
    s.layerName = "Medium"
    ds.addSource(s)

    with pytest.raises(
        AttributeError,
        match="specified a layer name but lacks the required TTFont object",
    ):
        load_masters(ds)
예제 #5
0
def test_findDefault_axis_mapping():
    designspace_string = """\
<?xml version='1.0' encoding='UTF-8'?>
<designspace format="4.0">
  <axes>
    <axis tag="wght" name="Weight" minimum="100" maximum="800" default="400">
      <map input="100" output="20"/>
      <map input="300" output="40"/>
      <map input="400" output="80"/>
      <map input="700" output="126"/>
      <map input="800" output="170"/>
    </axis>
    <axis tag="ital" name="Italic" minimum="0" maximum="1" default="1"/>
  </axes>
  <sources>
    <source filename="Font-Light.ufo">
      <location>
        <dimension name="Weight" xvalue="20"/>
        <dimension name="Italic" xvalue="0"/>
      </location>
    </source>
    <source filename="Font-Regular.ufo">
      <location>
        <dimension name="Weight" xvalue="80"/>
        <dimension name="Italic" xvalue="0"/>
      </location>
    </source>
    <source filename="Font-Bold.ufo">
      <location>
        <dimension name="Weight" xvalue="170"/>
        <dimension name="Italic" xvalue="0"/>
      </location>
    </source>
    <source filename="Font-LightItalic.ufo">
      <location>
        <dimension name="Weight" xvalue="20"/>
        <dimension name="Italic" xvalue="1"/>
      </location>
    </source>
    <source filename="Font-Italic.ufo">
      <location>
        <dimension name="Weight" xvalue="80"/>
        <dimension name="Italic" xvalue="1"/>
      </location>
    </source>
    <source filename="Font-BoldItalic.ufo">
      <location>
        <dimension name="Weight" xvalue="170"/>
        <dimension name="Italic" xvalue="1"/>
      </location>
    </source>
  </sources>
</designspace>
    """
    designspace = DesignSpaceDocument.fromstring(designspace_string)
    assert designspace.findDefault().filename == "Font-Italic.ufo"

    designspace.axes[1].default = 0

    assert designspace.findDefault().filename == "Font-Regular.ufo"
예제 #6
0
def test_normalise4():
    # normalisation with a map
    doc = DesignSpaceDocument()
    # write some axes
    a4 = AxisDescriptor()
    a4.minimum = 0
    a4.maximum = 1000
    a4.default = 0
    a4.name = "ddd"
    a4.map = [(0,100), (300, 500), (600, 500), (1000,900)]
    doc.addAxis(a4)
    doc.normalize()
    r = []
    for axis in doc.axes:
        r.append((axis.name, axis.map))
    r.sort()
    assert r == [('ddd', [(0, 0.1), (300, 0.5), (600, 0.5), (1000, 0.9)])]
예제 #7
0
def test_axisMapping():
    # note: because designspance lib does not do any actual
    # processing of the mapping data, we can only check if there data is there.
    doc = DesignSpaceDocument()
    # write some axes
    a4 = AxisDescriptor()
    a4.minimum = 0
    a4.maximum = 1000
    a4.default = 0
    a4.name = "ddd"
    a4.map = [(0,100), (300, 500), (600, 500), (1000,900)]
    doc.addAxis(a4)
    doc.normalize()
    r = []
    for axis in doc.axes:
        r.append((axis.name, axis.map))
    r.sort()
    assert r == [('ddd', [(0, 0.1), (300, 0.5), (600, 0.5), (1000, 0.9)])]
예제 #8
0
def test_documentLib(tmpdir):
    # roundtrip test of the document lib with some nested data
    tmpdir = str(tmpdir)
    testDocPath1 = os.path.join(tmpdir, "testDocumentLibTest.designspace")
    doc = DesignSpaceDocument()
    a1 = AxisDescriptor()
    a1.tag = "TAGA"
    a1.name = "axisName_a"
    a1.minimum = 0
    a1.maximum = 1000
    a1.default = 0
    doc.addAxis(a1)
    dummyData = dict(a=123, b=u"äbc", c=[1,2,3], d={'a':123})
    dummyKey = "org.fontTools.designspaceLib"
    doc.lib = {dummyKey: dummyData}
    doc.write(testDocPath1)
    new = DesignSpaceDocument()
    new.read(testDocPath1)
    assert dummyKey in new.lib
    assert new.lib[dummyKey] == dummyData
예제 #9
0
    def test_varlib_build_from_ttx_paths(self):
        ds_path = self.get_test_input("Build.designspace")
        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
        expected_ttx_path = self.get_test_output("BuildMain.ttx")

        ds = DesignSpaceDocument.fromfile(ds_path)
        for source in ds.sources:
            source.path = os.path.join(
                ttx_dir, os.path.basename(source.filename).replace(".ufo", ".ttx")
            )
        ds.updatePaths()

        varfont, _, _ = build(ds)
        varfont = reload_font(varfont)
        tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"]
        self.expect_ttx(varfont, expected_ttx_path, tables)
예제 #10
0
def test_convert5to4(datadir, tmpdir, test_ds, expected_vfs):
    data_in = datadir / test_ds
    temp_in = tmpdir / test_ds
    shutil.copy(data_in, temp_in)
    doc = DesignSpaceDocument.fromfile(temp_in)

    variable_fonts = convert5to4(doc)

    assert variable_fonts.keys() == expected_vfs
    for vf_name, vf in variable_fonts.items():
        data_out = (datadir / "convert5to4_output" / vf_name).with_suffix(".designspace")
        temp_out = (Path(tmpdir) / "out" / vf_name).with_suffix(".designspace")
        temp_out.parent.mkdir(exist_ok=True)
        vf.write(temp_out)

        if UPDATE_REFERENCE_OUT_FILES_INSTEAD_OF_TESTING:
            data_out.write_text(temp_out.read_text(encoding="utf-8"), encoding="utf-8")
        else:
            assert data_out.read_text(encoding="utf-8") == temp_out.read_text(
                encoding="utf-8"
            )
예제 #11
0
    def test_varlib_build_sparse_CFF2(self):
        ds_path = self.get_test_input('TestSparseCFF2VF.designspace')
        ttx_dir = self.get_test_input("master_sparse_cff2")
        expected_ttx_path = self.get_test_output("TestSparseCFF2VF.ttx")

        self.temp_dir()
        for path in self.get_file_list(ttx_dir, '.ttx', 'MasterSet_Kanji-'):
            self.compile_font(path, ".otf", self.tempdir)

        ds = DesignSpaceDocument.fromfile(ds_path)
        for source in ds.sources:
            source.path = os.path.join(
                self.tempdir, os.path.basename(source.filename).replace(".ufo", ".otf")
            )
        ds.updatePaths()

        varfont, _, _ = build(ds)
        varfont = reload_font(varfont)

        tables = ["fvar", "CFF2"]
        self.expect_ttx(varfont, expected_ttx_path, tables)
예제 #12
0
    def test_varlib_build_CFF2(self):
        ds_path = self.get_test_input('TestCFF2.designspace')
        ttx_dir = self.get_test_input("master_cff2")
        expected_ttx_path = self.get_test_output("BuildTestCFF2.ttx")

        self.temp_dir()
        for path in self.get_file_list(ttx_dir, '.ttx', 'TestCFF2_'):
            self.compile_font(path, ".otf", self.tempdir)

        ds = DesignSpaceDocument.fromfile(ds_path)
        for source in ds.sources:
            source.path = os.path.join(
                self.tempdir,
                os.path.basename(source.filename).replace(".ufo", ".otf"))
        ds.updatePaths()

        varfont, _, _ = build(ds)
        varfont = reload_font(varfont)

        tables = ["fvar", "CFF2"]
        self.expect_ttx(varfont, expected_ttx_path, tables)
예제 #13
0
def getVFUserRegion(doc: DesignSpaceDocument,
                    vf: VariableFontDescriptor) -> Region:
    vfUserRegion: Region = {}
    # For each axis, 2 cases:
    #  - it has a range = it's an axis in the VF DS
    #  - it's a single location = use it to know which rules should apply in the VF
    for axisSubset in vf.axisSubsets:
        axis = doc.getAxis(axisSubset.name)
        if axis is None:
            raise DesignSpaceDocumentError(
                f"Cannot find axis named '{axisSubset.name}' for variable font '{vf.name}'."
            )
        if hasattr(axisSubset, "userMinimum"):
            # Mypy doesn't support narrowing union types via hasattr()
            # TODO(Python 3.10): use TypeGuard
            # https://mypy.readthedocs.io/en/stable/type_narrowing.html
            axisSubset = cast(RangeAxisSubsetDescriptor, axisSubset)
            if not hasattr(axis, "minimum"):
                raise DesignSpaceDocumentError(
                    f"Cannot select a range over '{axis.name}' for variable font '{vf.name}' "
                    "because it's a discrete axis, use only 'userValue' instead."
                )
            axis = cast(AxisDescriptor, axis)
            vfUserRegion[axis.name] = Range(
                max(axisSubset.userMinimum, axis.minimum),
                min(axisSubset.userMaximum, axis.maximum),
                axisSubset.userDefault or axis.default,
            )
        else:
            axisSubset = cast(ValueAxisSubsetDescriptor, axisSubset)
            vfUserRegion[axis.name] = axisSubset.userValue
    # Any axis not mentioned explicitly has a single location = default value
    for axis in doc.axes:
        if axis.name not in vfUserRegion:
            assert isinstance(
                axis.default,
                (int,
                 float)), f"Axis '{axis.name}' has no valid default value."
            vfUserRegion[axis.name] = axis.default
    return vfUserRegion
예제 #14
0
def buildFiles(sources=True,
               static=True,
               variable=True,
               ds="recursive-MONO_CASL_wght_slnt_ital--full_gsub.designspace",
               version="0.000"):

    print("🚚 Building files for mastering")

    paths = getFolders(ds)

    if sources:
        print("\n🚚 Generating sources")
        if os.path.exists(paths["root"]):
            shutil.rmtree(paths["root"])

        os.mkdir(paths["root"])
        os.mkdir(paths["static"])
        os.mkdir(paths["var"])

        makeSources(ds, paths["src"], version)

    ds = DesignSpaceDocument.fromfile(paths["designspace"])

    if static:
        print("\n🚚 Making files for static font mastering")

        name_map = buildNameMap()
        buildFolders(ds, paths["cff"], name_map)
        buildFontMenuDB(ds, paths["cff"], name_map)
        buildGlyphOrderAndAlias(ds.sources[0].path, paths["cff"])
        buildFamilyFeatures(paths["cff"],
                            os.path.join(paths["src"], 'features.fea'),
                            version)
        buildInstances(paths["designspace"], paths["cff"], name_map)

    if variable:
        print("\n🚚 Making files for varible font mastering")
        makeSTAT(paths["stylespace"], ds)

    return paths
예제 #15
0
    def test_varlib_build_vpal(self):
        self.temp_dir()

        ds_path = self.get_test_input('test_vpal.designspace', copy=True)
        ttx_dir = self.get_test_input("master_vpal_test")
        expected_ttx_path = self.get_test_output("test_vpal.ttx")

        for path in self.get_file_list(ttx_dir, '.ttx', 'master_vpal_test_'):
            self.compile_font(path, ".otf", self.tempdir)

        ds = DesignSpaceDocument.fromfile(ds_path)
        for source in ds.sources:
            source.path = os.path.join(
                self.tempdir, os.path.basename(source.filename).replace(".ufo", ".otf")
            )
        ds.updatePaths()

        varfont, _, _ = build(ds)
        varfont = reload_font(varfont)

        tables = ["GPOS"]
        self.expect_ttx(varfont, expected_ttx_path, tables)
예제 #16
0
    def __init__(self, name, path):
        """Answers a PreVarFamily instance from the defined design space path."""
        self.designSpace = ds = DesignSpaceDocument()
        ds.read(path)
        self.axes = {}

        for axis in self.designSpace.axes:
            self.axes[axis.tag] = axis

        fonts = {}

        for source in ds.sources:
            fonts[source.path] = Font(source.path)

        Family.__init__(self, name=name, fonts=fonts)
        self._parametricAxisFonts = {}  # Key is parametric axis name
        self._parametricAxisMetrics = {
        }  # Collection of font metrics and calculated parameters.
        self._metrics = None  # Initialized on property call
        self._defaultFont = None  # Initialized on property call
        self._glyphNames = None  # Set of all unique glyph names in all design space fonts
        self.baseGlyphName = self.BASE_GLYPH_NAME
예제 #17
0
def test_normalise3():
    # normalisation of negative values, with default == maximum
    doc = DesignSpaceDocument()
    # write some axes
    a3 = AxisDescriptor()
    a3.minimum = -1000
    a3.maximum = 0
    a3.default = 0
    a3.name = "ccc"
    doc.addAxis(a3)
    assert doc.normalizeLocation(dict(ccc=0)) == {'ccc': 0.0}
    assert doc.normalizeLocation(dict(ccc=1)) == {'ccc': 0.0}
    assert doc.normalizeLocation(dict(ccc=-1000)) == {'ccc': -1.0}
    assert doc.normalizeLocation(dict(ccc=-1001)) == {'ccc': -1.0}
    doc.normalize()
    r = []
    for axis in doc.axes:
        r.append((axis.name, axis.minimum, axis.default, axis.maximum))
    r.sort()
    assert r == [('ccc', -1.0, 0.0, 0.0)]
예제 #18
0
    def _set_path(self, path):
        self._path = path
        self._ds = None
        self.axes = {}
        self.axisList = []
        self.masters = {}
        self.masterList = []
        self.instances = {}
        self.instanceList = []

        if path is not None:
            self._ds = ds = DesignSpaceDocument.fromfile(
                path)  # Raw eTree from file.

            for a in ds['axes']:  # Maintain order by index
                axis = Axis(tag=a['tag'],
                            name=a['name'],
                            minimum=a['minimum'],
                            default=a['default'],
                            maximum=a['maximum'])
                self.appendAxes(axis)

            for m in ds['sources']:
                masterPath = self.getFontPath(m['filename'])
                master = FontInfo(name=m['name'],
                                  familyName=m['familyname'],
                                  styleName=m['stylename'],
                                  path=masterPath,
                                  location=self.asTagLocation(m['location']))
                self.appendMasters(master)

            for i in ds.get('instances', []):
                instancePath = self.getFontPath(i['filename'])
                instance = FontInfo(name=i['name'],
                                    familyName=i['familyname'],
                                    styleName=i['stylename'],
                                    path=masterPath,
                                    location=self.asTagLocation(i['location']))
                self.appendInstances(instance)
예제 #19
0
    def test_varlib_build_from_ttf_paths(self):
        ds_path = self.get_test_input("Build.designspace")
        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
        expected_ttx_path = self.get_test_output("BuildMain.ttx")

        self.temp_dir()
        for path in self.get_file_list(ttx_dir, '.ttx', 'TestFamily-'):
            self.compile_font(path, ".ttf", self.tempdir)

        ds = DesignSpaceDocument.fromfile(ds_path)
        for source in ds.sources:
            source.path = os.path.join(
                self.tempdir,
                os.path.basename(source.filename).replace(".ufo", ".ttf"))
        ds.updatePaths()

        varfont, _, _ = build(ds)
        varfont = reload_font(varfont)
        tables = [
            table_tag for table_tag in varfont.keys() if table_tag != "head"
        ]
        self.expect_ttx(varfont, expected_ttx_path, tables)
예제 #20
0
def test_split(datadir, tmpdir, test_ds, expected_interpolable_spaces):
    data_in = datadir / test_ds
    temp_in = Path(tmpdir) / test_ds
    shutil.copy(data_in, temp_in)
    doc = DesignSpaceDocument.fromfile(temp_in)

    for i, (location, sub_doc) in enumerate(splitInterpolable(doc)):
        expected_location, expected_vf_names = expected_interpolable_spaces[i]
        assert location == expected_location
        vfs = list(splitVariableFonts(sub_doc))
        assert expected_vf_names == set(vf[0] for vf in vfs)

        loc_str = "_".join(f"{name}_{value}"for name, value in sorted(location.items()))
        data_out = datadir / "split_output" / f"{temp_in.stem}_{loc_str}.designspace"
        temp_out = Path(tmpdir) / "out" / f"{temp_in.stem}_{loc_str}.designspace"
        temp_out.parent.mkdir(exist_ok=True)
        sub_doc.write(temp_out)

        if UPDATE_REFERENCE_OUT_FILES_INSTEAD_OF_TESTING:
            data_out.write_text(temp_out.read_text(encoding="utf-8"), encoding="utf-8")
        else:
            assert data_out.read_text(encoding="utf-8") == temp_out.read_text(
                encoding="utf-8"
            )

        for vf_name, vf_doc in vfs:
            data_out = (datadir / "split_output" / vf_name).with_suffix(".designspace")
            temp_out = (Path(tmpdir) / "out" / vf_name).with_suffix(".designspace")
            temp_out.parent.mkdir(exist_ok=True)
            vf_doc.write(temp_out)

            if UPDATE_REFERENCE_OUT_FILES_INSTEAD_OF_TESTING:
                data_out.write_text(
                    temp_out.read_text(encoding="utf-8"), encoding="utf-8"
                )
            else:
                assert data_out.read_text(encoding="utf-8") == temp_out.read_text(
                    encoding="utf-8"
                )
예제 #21
0
def test_normalise4():
    # normalisation with a map
    doc = DesignSpaceDocument()
    # write some axes
    a4 = AxisDescriptor()
    a4.minimum = 0
    a4.maximum = 1000
    a4.default = 0
    a4.name = "ddd"
    a4.map = [(0,100), (300, 500), (600, 500), (1000,900)]
    doc.addAxis(a4)
    doc.normalize()
    r = []
    for axis in doc.axes:
        r.append((axis.name, axis.map))
    r.sort()
    assert r == [('ddd', [(0, 0.0), (300, 0.5), (600, 0.5), (1000, 1.0)])]
예제 #22
0
def buildDesignSpace(sources, instances, axes):
    doc = DesignSpaceDocument()
    # build source descriptors from source list
    for source in sources:
        s = SourceDescriptor()
        s.path = source["path"]
        s.name = source["name"]
        s.copyInfo = source["copyInfo"]
        s.location = source["location"]
        s.familyName = source["familyName"]
        s.styleName = source["styleName"]
        doc.addSource(s)
    # build instance descriptors from instance list
    for instance in instances:
        i = InstanceDescriptor()
        i.location = instance["location"]
        i.familyName = instance["familyName"]
        i.styleName = instance["styleName"]
        i.path = instance["path"]
        i.postScriptFontName = instance["postScriptFontName"]
        i.styleMapFamilyName = instance["styleMapFamilyName"]
        i.styleMapStyleName = instance["styleMapStyleName"]
        doc.addInstance(i)
    # build axis descriptors from axis list
    for axis in axes:
        a = AxisDescriptor()
        a.minimum = axis["minimum"]
        a.maximum = axis["maximum"]
        a.default = axis["default"]
        a.name = axis["name"]
        a.tag = axis["tag"]
        for languageCode, labelName in axis["labelNames"].items():
            a.labelNames[languageCode] = labelName
        a.map = axis["map"]
        doc.addAxis(a)
    return doc
예제 #23
0
def test_getStatLocations(datadir):
    doc = DesignSpaceDocument.fromfile(datadir / "test_v5.designspace")

    assert getStatLocations(doc, {
        "Italic": 0,
        "Width": Range(50, 150),
        "Weight": Range(200, 900)
    }) == [
        {
            "flags": 0,
            "location": {
                "ital": 0.0,
                "wdth": 50.0,
                "wght": 300.0
            },
            "name": {
                "en": "Some Style",
                "fr": "Un Style"
            },
        },
    ]
    assert getStatLocations(doc, {
        "Italic": 1,
        "Width": Range(50, 150),
        "Weight": Range(200, 900)
    }) == [
        {
            "flags": 0,
            "location": {
                "ital": 1.0,
                "wdth": 100.0,
                "wght": 700.0
            },
            "name": {
                "en": "Other"
            },
        },
    ]
예제 #24
0
    def test_varlib_build_lazy_masters(self):
        # See https://github.com/fonttools/fonttools/issues/1808
        ds_path = self.get_test_input("SparseMasters.designspace")
        expected_ttx_path = self.get_test_output("SparseMasters.ttx")

        def _open_font(master_path, master_finder=lambda s: s):
            font = TTFont()
            font.importXML(master_path)
            buf = BytesIO()
            font.save(buf, reorderTables=False)
            buf.seek(0)
            font = TTFont(buf,
                          lazy=True)  # reopen in lazy mode, to reproduce #1808
            return font

        ds = DesignSpaceDocument.fromfile(ds_path)
        ds.loadSourceFonts(_open_font)
        varfont, _, _ = build(ds)
        varfont = reload_font(varfont)
        tables = [
            table_tag for table_tag in varfont.keys() if table_tag != "head"
        ]
        self.expect_ttx(varfont, expected_ttx_path, tables)
예제 #25
0
    def test_varlib_build_from_ds_object_in_memory_ttfonts(self):
        ds_path = self.get_test_input("Build.designspace")
        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
        expected_ttx_path = self.get_test_output("BuildMain.ttx")

        self.temp_dir()
        for path in self.get_file_list(ttx_dir, '.ttx', 'TestFamily-'):
            self.compile_font(path, ".ttf", self.tempdir)

        ds = DesignSpaceDocument.fromfile(ds_path)
        for source in ds.sources:
            filename = os.path.join(
                self.tempdir, os.path.basename(source.filename).replace(".ufo", ".ttf")
            )
            source.font = TTFont(
                filename, recalcBBoxes=False, recalcTimestamp=False, lazy=True
            )
            source.filename = None  # Make sure no file path gets into build()

        varfont, _, _ = build(ds)
        varfont = reload_font(varfont)
        tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"]
        self.expect_ttx(varfont, expected_ttx_path, tables)
예제 #26
0
def test_not_all_ordering_specified_and_translations(datadir):
    doc = DesignSpaceDocument.fromfile(datadir / "test_v5.designspace")

    assert getStatNames(doc, {"Weight": 200, "Width": 125, "Italic": 1}) == StatNames(
        familyNames={
            "en": "MasterFamilyName",
            "fr": "Montserrat",
            "ja": "モンセラート",
        },
        styleNames={
            "fr": "Wide Extra léger Italic",
            "de": "Wide Extraleicht Italic",
            "en": "Wide Extra Light Italic",
        },
        postScriptFontName="MasterFamilyName-WideExtraLightItalic",
        styleMapFamilyNames={
            "en": "MasterFamilyName Wide Extra Light",
            "fr": "Montserrat Wide Extra léger",
            "de": "MasterFamilyName Wide Extraleicht",
            "ja": "モンセラート Wide Extra Light",
        },
        styleMapStyleName="italic",
    )
예제 #27
0
    def test_varlib_build_from_ds_object_in_memory_ttfonts(self):
        ds_path = self.get_test_input("Build.designspace")
        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
        expected_ttx_path = self.get_test_output("BuildMain.ttx")

        self.temp_dir()
        for path in self.get_file_list(ttx_dir, '.ttx', 'TestFamily-'):
            self.compile_font(path, ".ttf", self.tempdir)

        ds = DesignSpaceDocument.fromfile(ds_path)
        for source in ds.sources:
            filename = os.path.join(
                self.tempdir, os.path.basename(source.filename).replace(".ufo", ".ttf")
            )
            source.font = TTFont(
                filename, recalcBBoxes=False, recalcTimestamp=False, lazy=True
            )
            source.filename = None  # Make sure no file path gets into build()

        varfont, _, _ = build(ds)
        varfont = reload_font(varfont)
        tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"]
        self.expect_ttx(varfont, expected_ttx_path, tables)
예제 #28
0
def test_axisMapping():
    # note: because designspance lib does not do any actual
    # processing of the mapping data, we can only check if there data is there.
    doc = DesignSpaceDocument()
    # write some axes
    a4 = AxisDescriptor()
    a4.minimum = 0
    a4.maximum = 1000
    a4.default = 0
    a4.name = "ddd"
    a4.map = [(0,100), (300, 500), (600, 500), (1000,900)]
    doc.addAxis(a4)
    doc.normalize()
    r = []
    for axis in doc.axes:
        r.append((axis.name, axis.map))
    r.sort()
    assert r == [('ddd', [(0, 0.0), (300, 0.5), (600, 0.5), (1000, 1.0)])]
예제 #29
0
    def test_varlib_build_VVAR_CFF2(self):
        ds_path = self.get_test_input('TestVVAR.designspace')
        ttx_dir = self.get_test_input("master_vvar_cff2")
        expected_ttx_name = 'TestVVAR'
        suffix = '.otf'

        self.temp_dir()
        for path in self.get_file_list(ttx_dir, '.ttx', 'TestVVAR'):
            font, savepath = self.compile_font(path, suffix, self.tempdir)

        ds = DesignSpaceDocument.fromfile(ds_path)
        for source in ds.sources:
            source.path = os.path.join(
                self.tempdir, os.path.basename(source.filename).replace(".ufo", suffix)
            )
        ds.updatePaths()

        varfont, _, _ = build(ds)
        varfont = reload_font(varfont)

        expected_ttx_path = self.get_test_output(expected_ttx_name + '.ttx')
        tables = ["VVAR"]
        self.expect_ttx(varfont, expected_ttx_path, tables)
        self.check_ttx_dump(varfont, expected_ttx_path, tables, suffix)
예제 #30
0
    def test_varlib_build_VVAR_CFF2(self):
        ds_path = self.get_test_input('TestVVAR.designspace')
        ttx_dir = self.get_test_input("master_vvar_cff2")
        expected_ttx_name = 'TestVVAR'
        suffix = '.otf'

        self.temp_dir()
        for path in self.get_file_list(ttx_dir, '.ttx', 'TestVVAR'):
            font, savepath = self.compile_font(path, suffix, self.tempdir)

        ds = DesignSpaceDocument.fromfile(ds_path)
        for source in ds.sources:
            source.path = os.path.join(
                self.tempdir,
                os.path.basename(source.filename).replace(".ufo", suffix))
        ds.updatePaths()

        varfont, _, _ = build(ds)
        varfont = reload_font(varfont)

        expected_ttx_path = self.get_test_output(expected_ttx_name + '.ttx')
        tables = ["VVAR"]
        self.expect_ttx(varfont, expected_ttx_path, tables)
        self.check_ttx_dump(varfont, expected_ttx_path, tables, suffix)
def build_masters(opts):
    """
    Build master OTFs using supplied options.
    """
    logger.info("Reading designspace file...")
    ds = DesignSpaceDocument.fromfile(opts.dsPath)
    validateDesignspaceDoc(ds)
    master_paths = [s.path for s in ds.sources]

    logger.info("Building local OTFs for master font paths...")
    dsDir = os.path.dirname(opts.dsPath)

    for master_path in master_paths:
        master_path = os.path.join(dsDir, master_path)
        otf_path = f"{os.path.splitext(master_path)[0]}.otf"

        result = makeotf(['-nshw', '-f', master_path, '-o', otf_path,
                          '-r', '-nS'] + opts.mkot)

        if result:
            raise Exception(f'makeotf return value: {result}')

        logger.info(f"Built OTF font for {master_path}")
        generalizeCFF(otf_path)
예제 #32
0
def test_loadSourceFonts():
    def opener(path):
        font = ttLib.TTFont()
        font.importXML(path)
        return font

    # this designspace file contains .TTX source paths
    path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "varLib",
                        "data", "SparseMasters.designspace")
    designspace = DesignSpaceDocument.fromfile(path)

    # force two source descriptors to have the same path
    designspace.sources[1].path = designspace.sources[0].path

    fonts = designspace.loadSourceFonts(opener)

    assert len(fonts) == 3
    assert all(isinstance(font, ttLib.TTFont) for font in fonts)
    assert fonts[0] is fonts[1]  # same path, identical font object

    fonts2 = designspace.loadSourceFonts(opener)

    for font1, font2 in zip(fonts, fonts2):
        assert font1 is font2
예제 #33
0
def main():
    parser = argparse.ArgumentParser(description="Build Reem Kufi fonts.")
    parser.add_argument("file", metavar="FILE", help="input font to process")
    parser.add_argument("--out-file",
                        metavar="FILE",
                        help="output font to write",
                        required=True)

    args = parser.parse_args()

    with TemporaryDirectory() as tempdir:
        ufos, designspace = build_masters(args.file, tempdir, tempdir)

        doc = DesignSpaceDocument()
        doc.read(designspace)
        doc.instances = [i for i in doc.instances if i.styleName == "Regular"]
        assert len(doc.instances) == 1
        instance = doc.instances[0]
        instance.location = dict(Weight=108)
        instance.path = args.out_file
        doc.write(designspace)

        build(designspace, outputUFOFormatVersion=3)
예제 #34
0
def main(args=None):
	"""Normalize locations on a given designspace"""
	from fontTools import configLogger
	import argparse

	parser = argparse.ArgumentParser(
		"fonttools varLib.models",
		description=main.__doc__,
	)
	parser.add_argument('--loglevel', metavar='LEVEL', default="INFO",
		help="Logging level (defaults to INFO)")

	group = parser.add_mutually_exclusive_group(required=True)
	group.add_argument('-d', '--designspace',metavar="DESIGNSPACE",type=str)
	group.add_argument('-l', '--locations', metavar='LOCATION', nargs='+',
		help="Master locations as comma-separate coordinates. One must be all zeros.")

	args = parser.parse_args(args)

	configLogger(level=args.loglevel)
	from pprint import pprint

	if args.designspace:
		from fontTools.designspaceLib import DesignSpaceDocument
		doc = DesignSpaceDocument()
		doc.read(args.designspace)
		locs = [s.location for s in doc.sources]
		print("Original locations:")
		pprint(locs)
		doc.normalize()
		print("Normalized locations:")
		locs = [s.location for s in doc.sources]
		pprint(locs)
	else:
		axes = [chr(c) for c in range(ord('A'), ord('Z')+1)]
		locs = [dict(zip(axes, (float(v) for v in s.split(',')))) for s in args.locations]

	model = VariationModel(locs)
	print("Sorted locations:")
	pprint(model.locations)
	print("Supports:")
	pprint(model.supports)
예제 #35
0
def main(args):
    from fontTools import configLogger

    args = args[1:]

    # TODO: allow user to configure logging via command-line options
    configLogger(level="INFO")

    if len(args) < 1:
        print("usage: fonttools varLib.models source.designspace",
              file=sys.stderr)
        print("  or")
        print("usage: fonttools varLib.models location1 location2 ...",
              file=sys.stderr)
        sys.exit(1)

    from pprint import pprint

    if len(args) == 1 and args[0].endswith('.designspace'):
        from fontTools.designspaceLib import DesignSpaceDocument
        doc = DesignSpaceDocument()
        doc.read(args[0])
        locs = [s.location for s in doc.sources]
        print("Original locations:")
        pprint(locs)
        doc.normalize()
        print("Normalized locations:")
        locs = [s.location for s in doc.sources]
        pprint(locs)
    else:
        axes = [chr(c) for c in range(ord('A'), ord('Z') + 1)]
        locs = [
            dict(zip(axes, (float(v) for v in s.split(',')))) for s in args
        ]

    model = VariationModel(locs)
    print("Sorted locations:")
    pprint(model.locations)
    print("Supports:")
    pprint(model.supports)
예제 #36
0
    def test_varlib_build_sparse_masters_MVAR(self):
        import fontTools.varLib.mvar

        ds_path = self.get_test_input("SparseMasters.designspace")
        ds = DesignSpaceDocument.fromfile(ds_path)
        load_masters(ds)

        # Trigger MVAR generation so varLib is forced to create deltas with a
        # sparse master inbetween.
        font_0_os2 = ds.sources[0].font["OS/2"]
        font_0_os2.sTypoAscender = 1
        font_0_os2.sTypoDescender = 1
        font_0_os2.sTypoLineGap = 1
        font_0_os2.usWinAscent = 1
        font_0_os2.usWinDescent = 1
        font_0_os2.sxHeight = 1
        font_0_os2.sCapHeight = 1
        font_0_os2.ySubscriptXSize = 1
        font_0_os2.ySubscriptYSize = 1
        font_0_os2.ySubscriptXOffset = 1
        font_0_os2.ySubscriptYOffset = 1
        font_0_os2.ySuperscriptXSize = 1
        font_0_os2.ySuperscriptYSize = 1
        font_0_os2.ySuperscriptXOffset = 1
        font_0_os2.ySuperscriptYOffset = 1
        font_0_os2.yStrikeoutSize = 1
        font_0_os2.yStrikeoutPosition = 1
        font_0_vhea = newTable("vhea")
        font_0_vhea.ascent = 1
        font_0_vhea.descent = 1
        font_0_vhea.lineGap = 1
        font_0_vhea.caretSlopeRise = 1
        font_0_vhea.caretSlopeRun = 1
        font_0_vhea.caretOffset = 1
        ds.sources[0].font["vhea"] = font_0_vhea
        font_0_hhea = ds.sources[0].font["hhea"]
        font_0_hhea.caretSlopeRise = 1
        font_0_hhea.caretSlopeRun = 1
        font_0_hhea.caretOffset = 1
        font_0_post = ds.sources[0].font["post"]
        font_0_post.underlineThickness = 1
        font_0_post.underlinePosition = 1

        font_2_os2 = ds.sources[2].font["OS/2"]
        font_2_os2.sTypoAscender = 800
        font_2_os2.sTypoDescender = 800
        font_2_os2.sTypoLineGap = 800
        font_2_os2.usWinAscent = 800
        font_2_os2.usWinDescent = 800
        font_2_os2.sxHeight = 800
        font_2_os2.sCapHeight = 800
        font_2_os2.ySubscriptXSize = 800
        font_2_os2.ySubscriptYSize = 800
        font_2_os2.ySubscriptXOffset = 800
        font_2_os2.ySubscriptYOffset = 800
        font_2_os2.ySuperscriptXSize = 800
        font_2_os2.ySuperscriptYSize = 800
        font_2_os2.ySuperscriptXOffset = 800
        font_2_os2.ySuperscriptYOffset = 800
        font_2_os2.yStrikeoutSize = 800
        font_2_os2.yStrikeoutPosition = 800
        font_2_vhea = newTable("vhea")
        font_2_vhea.ascent = 800
        font_2_vhea.descent = 800
        font_2_vhea.lineGap = 800
        font_2_vhea.caretSlopeRise = 800
        font_2_vhea.caretSlopeRun = 800
        font_2_vhea.caretOffset = 800
        ds.sources[2].font["vhea"] = font_2_vhea
        font_2_hhea = ds.sources[2].font["hhea"]
        font_2_hhea.caretSlopeRise = 800
        font_2_hhea.caretSlopeRun = 800
        font_2_hhea.caretOffset = 800
        font_2_post = ds.sources[2].font["post"]
        font_2_post.underlineThickness = 800
        font_2_post.underlinePosition = 800

        varfont, _, _ = build(ds)
        mvar_tags = [vr.ValueTag for vr in varfont["MVAR"].table.ValueRecord]
        assert all(tag in mvar_tags for tag in fontTools.varLib.mvar.MVAR_ENTRIES)
예제 #37
0
def build(designspace, master_finder=lambda s:s, exclude=[], optimize=True):
	"""
	Build variation font from a designspace file.

	If master_finder is set, it should be a callable that takes master
	filename as found in designspace file and map it to master font
	binary as to be opened (eg. .ttf or .otf).
	"""
	if hasattr(designspace, "sources"):  # Assume a DesignspaceDocument
		pass
	else:  # Assume a file path
		designspace = DesignSpaceDocument.fromfile(designspace)

	ds = load_designspace(designspace)
	log.info("Building variable font")

	log.info("Loading master fonts")
	master_fonts = load_masters(designspace, master_finder)

	# TODO: 'master_ttfs' is unused except for return value, remove later
	master_ttfs = []
	for master in master_fonts:
		try:
			master_ttfs.append(master.reader.file.name)
		except AttributeError:
			master_ttfs.append(None)  # in-memory fonts have no path

	# Copy the base master to work from it
	vf = deepcopy(master_fonts[ds.base_idx])

	# TODO append masters as named-instances as well; needs .designspace change.
	fvar = _add_fvar(vf, ds.axes, ds.instances)
	if 'STAT' not in exclude:
		_add_stat(vf, ds.axes)
	if 'avar' not in exclude:
		_add_avar(vf, ds.axes)

	# Map from axis names to axis tags...
	normalized_master_locs = [
		{ds.axes[k].tag: v for k,v in loc.items()} for loc in ds.normalized_master_locs
	]
	# From here on, we use fvar axes only
	axisTags = [axis.axisTag for axis in fvar.axes]

	# Assume single-model for now.
	model = models.VariationModel(normalized_master_locs, axisOrder=axisTags)
	assert 0 == model.mapping[ds.base_idx]

	log.info("Building variations tables")
	if 'MVAR' not in exclude:
		_add_MVAR(vf, model, master_fonts, axisTags)
	if 'HVAR' not in exclude:
		_add_HVAR(vf, model, master_fonts, axisTags)
	if 'GDEF' not in exclude or 'GPOS' not in exclude:
		_merge_OTL(vf, model, master_fonts, axisTags)
	if 'gvar' not in exclude and 'glyf' in vf:
		_add_gvar(vf, model, master_fonts, optimize=optimize)
	if 'cvar' not in exclude and 'glyf' in vf:
		_merge_TTHinting(vf, model, master_fonts)
	if 'GSUB' not in exclude and ds.rules:
		_add_GSUB_feature_variations(vf, ds.axes, ds.internal_axis_supports, ds.rules)
	if 'CFF2' not in exclude and 'CFF ' in vf:
		_add_CFF2(vf, model, master_fonts)

	for tag in exclude:
		if tag in vf:
			del vf[tag]

	# TODO: Only return vf for 4.0+, the rest is unused.
	return vf, model, master_ttfs
예제 #38
0
def load_designspace(designspace_filename):

	ds = DesignSpaceDocument.fromfile(designspace_filename)
	masters = ds.sources
	if not masters:
		raise VarLibError("no sources found in .designspace")
	instances = ds.instances

	standard_axis_map = OrderedDict([
		('weight',  ('wght', {'en':'Weight'})),
		('width',   ('wdth', {'en':'Width'})),
		('slant',   ('slnt', {'en':'Slant'})),
		('optical', ('opsz', {'en':'Optical Size'})),
		])

	# Setup axes
	axes = OrderedDict()
	for axis in ds.axes:
		axis_name = axis.name
		if not axis_name:
			assert axis.tag is not None
			axis_name = axis.name = axis.tag

		if axis_name in standard_axis_map:
			if axis.tag is None:
				axis.tag = standard_axis_map[axis_name][0]
			if not axis.labelNames:
				axis.labelNames.update(standard_axis_map[axis_name][1])
		else:
			assert axis.tag is not None
			if not axis.labelNames:
				axis.labelNames["en"] = axis_name

		axes[axis_name] = axis
	log.info("Axes:\n%s", pformat([axis.asdict() for axis in axes.values()]))

	# Check all master and instance locations are valid and fill in defaults
	for obj in masters+instances:
		obj_name = obj.name or obj.styleName or ''
		loc = obj.location
		for axis_name in loc.keys():
			assert axis_name in axes, "Location axis '%s' unknown for '%s'." % (axis_name, obj_name)
		for axis_name,axis in axes.items():
			if axis_name not in loc:
				loc[axis_name] = axis.default
			else:
				v = axis.map_backward(loc[axis_name])
				assert axis.minimum <= v <= axis.maximum, "Location for axis '%s' (mapped to %s) out of range for '%s' [%s..%s]" % (axis_name, v, obj_name, axis.minimum, axis.maximum)

	# Normalize master locations

	internal_master_locs = [o.location for o in masters]
	log.info("Internal master locations:\n%s", pformat(internal_master_locs))

	# TODO This mapping should ideally be moved closer to logic in _add_fvar/avar
	internal_axis_supports = {}
	for axis in axes.values():
		triple = (axis.minimum, axis.default, axis.maximum)
		internal_axis_supports[axis.name] = [axis.map_forward(v) for v in triple]
	log.info("Internal axis supports:\n%s", pformat(internal_axis_supports))

	normalized_master_locs = [models.normalizeLocation(m, internal_axis_supports) for m in internal_master_locs]
	log.info("Normalized master locations:\n%s", pformat(normalized_master_locs))

	# Find base master
	base_idx = None
	for i,m in enumerate(normalized_master_locs):
		if all(v == 0 for v in m.values()):
			assert base_idx is None
			base_idx = i
	assert base_idx is not None, "Base master not found; no master at default location?"
	log.info("Index of base master: %s", base_idx)

	return _DesignSpaceData(
		axes,
		internal_axis_supports,
		base_idx,
		normalized_master_locs,
		masters,
		instances,
		ds.rules,
	)
예제 #39
0
def test_fill_document(tmpdir):
    tmpdir = str(tmpdir)
    testDocPath = os.path.join(tmpdir, "test.designspace")
    masterPath1 = os.path.join(tmpdir, "masters", "masterTest1.ufo")
    masterPath2 = os.path.join(tmpdir, "masters", "masterTest2.ufo")
    instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo")
    instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")
    doc = DesignSpaceDocument()

    # write some axes
    a1 = AxisDescriptor()
    a1.minimum = 0
    a1.maximum = 1000
    a1.default = 0
    a1.name = "weight"
    a1.tag = "wght"
    # note: just to test the element language, not an actual label name recommendations.
    a1.labelNames[u'fa-IR'] = u"قطر"
    a1.labelNames[u'en'] = u"Wéíght"
    doc.addAxis(a1)
    a2 = AxisDescriptor()
    a2.minimum = 0
    a2.maximum = 1000
    a2.default = 20
    a2.name = "width"
    a2.tag = "wdth"
    a2.map = [(0.0, 10.0), (401.0, 66.0), (1000.0, 990.0)]
    a2.hidden = True
    a2.labelNames[u'fr'] = u"Chasse"
    doc.addAxis(a2)

    # add master 1
    s1 = SourceDescriptor()
    s1.filename = os.path.relpath(masterPath1, os.path.dirname(testDocPath))
    assert s1.font is None
    s1.name = "master.ufo1"
    s1.copyLib = True
    s1.copyInfo = True
    s1.copyFeatures = True
    s1.location = dict(weight=0)
    s1.familyName = "MasterFamilyName"
    s1.styleName = "MasterStyleNameOne"
    s1.mutedGlyphNames.append("A")
    s1.mutedGlyphNames.append("Z")
    doc.addSource(s1)
    # add master 2
    s2 = SourceDescriptor()
    s2.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath))
    s2.name = "master.ufo2"
    s2.copyLib = False
    s2.copyInfo = False
    s2.copyFeatures = False
    s2.muteKerning = True
    s2.location = dict(weight=1000)
    s2.familyName = "MasterFamilyName"
    s2.styleName = "MasterStyleNameTwo"
    doc.addSource(s2)
    # add master 3 from a different layer
    s3 = SourceDescriptor()
    s3.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath))
    s3.name = "master.ufo2"
    s3.copyLib = False
    s3.copyInfo = False
    s3.copyFeatures = False
    s3.muteKerning = False
    s3.layerName = "supports"
    s3.location = dict(weight=1000)
    s3.familyName = "MasterFamilyName"
    s3.styleName = "Supports"
    doc.addSource(s3)
    # add instance 1
    i1 = InstanceDescriptor()
    i1.filename = os.path.relpath(instancePath1, os.path.dirname(testDocPath))
    i1.familyName = "InstanceFamilyName"
    i1.styleName = "InstanceStyleName"
    i1.name = "instance.ufo1"
    i1.location = dict(weight=500, spooky=666)  # this adds a dimension that is not defined.
    i1.postScriptFontName = "InstancePostscriptName"
    i1.styleMapFamilyName = "InstanceStyleMapFamilyName"
    i1.styleMapStyleName = "InstanceStyleMapStyleName"
    glyphData = dict(name="arrow", mute=True, unicodes=[0x123, 0x124, 0x125])
    i1.glyphs['arrow'] = glyphData
    i1.lib['com.coolDesignspaceApp.binaryData'] = plistlib.Data(b'<binary gunk>')
    i1.lib['com.coolDesignspaceApp.specimenText'] = "Hamburgerwhatever"
    doc.addInstance(i1)
    # add instance 2
    i2 = InstanceDescriptor()
    i2.filename = os.path.relpath(instancePath2, os.path.dirname(testDocPath))
    i2.familyName = "InstanceFamilyName"
    i2.styleName = "InstanceStyleName"
    i2.name = "instance.ufo2"
    # anisotropic location
    i2.location = dict(weight=500, width=(400,300))
    i2.postScriptFontName = "InstancePostscriptName"
    i2.styleMapFamilyName = "InstanceStyleMapFamilyName"
    i2.styleMapStyleName = "InstanceStyleMapStyleName"
    glyphMasters = [dict(font="master.ufo1", glyphName="BB", location=dict(width=20,weight=20)), dict(font="master.ufo2", glyphName="CC", location=dict(width=900,weight=900))]
    glyphData = dict(name="arrow", unicodes=[101, 201, 301])
    glyphData['masters'] = glyphMasters
    glyphData['note'] = "A note about this glyph"
    glyphData['instanceLocation'] = dict(width=100, weight=120)
    i2.glyphs['arrow'] = glyphData
    i2.glyphs['arrow2'] = dict(mute=False)
    doc.addInstance(i2)

    doc.filename = "suggestedFileName.designspace"
    doc.lib['com.coolDesignspaceApp.previewSize'] = 30

    # write some rules
    r1 = RuleDescriptor()
    r1.name = "named.rule.1"
    r1.conditionSets.append([
        dict(name='axisName_a', minimum=0, maximum=1),
        dict(name='axisName_b', minimum=2, maximum=3)
    ])
    r1.subs.append(("a", "a.alt"))
    doc.addRule(r1)
    # write the document
    doc.write(testDocPath)
    assert os.path.exists(testDocPath)
    assert_equals_test_file(testDocPath, 'data/test.designspace')
    # import it again
    new = DesignSpaceDocument()
    new.read(testDocPath)

    assert new.default.location == {'width': 20.0, 'weight': 0.0}
    assert new.filename == 'test.designspace'
    assert new.lib == doc.lib
    assert new.instances[0].lib == doc.instances[0].lib

    # test roundtrip for the axis attributes and data
    axes = {}
    for axis in doc.axes:
        if axis.tag not in axes:
            axes[axis.tag] = []
        axes[axis.tag].append(axis.serialize())
    for axis in new.axes:
        if axis.tag[0] == "_":
            continue
        if axis.tag not in axes:
            axes[axis.tag] = []
        axes[axis.tag].append(axis.serialize())
    for v in axes.values():
        a, b = v
        assert a == b
예제 #40
0
def main(argv):
    designspace_file = argv[1]
    designspace = DesignSpaceDocument.fromfile(designspace_file)
    designspace = fix_opsz_maximum(designspace)
    designspace = update_sources(designspace)
    designspace.write(designspace_file)
예제 #41
0
def test_read_with_path_object():
    import pathlib
    source = (pathlib.Path(__file__) / "../data/test.designspace").resolve()
    assert source.exists()
    doc = DesignSpaceDocument()
    doc.read(source)
예제 #42
0
def test_documentLib(tmpdir):
    # roundtrip test of the document lib with some nested data
    tmpdir = str(tmpdir)
    testDocPath1 = os.path.join(tmpdir, "testDocumentLibTest.designspace")
    doc = DesignSpaceDocument()
    a1 = AxisDescriptor()
    a1.tag = "TAGA"
    a1.name = "axisName_a"
    a1.minimum = 0
    a1.maximum = 1000
    a1.default = 0
    doc.addAxis(a1)
    dummyData = dict(a=123, b=u"äbc", c=[1,2,3], d={'a':123})
    dummyKey = "org.fontTools.designspaceLib"
    doc.lib = {dummyKey: dummyData}
    doc.write(testDocPath1)
    new = DesignSpaceDocument()
    new.read(testDocPath1)
    assert dummyKey in new.lib
    assert new.lib[dummyKey] == dummyData
예제 #43
0
from fontTools.designspaceLib import DesignSpaceDocument
from vanilla import *
import inspect
import defcon

f = CurrentFont()
thisGlyph = CurrentGlyph()
doc = DesignSpaceDocument()
doc.read("Crispy[SRIF,wdth,wght].designspace")
referenceFileName = doc.sources[
    0].familyName  #this assumed a well-made designspace file with correctly named font masters.
coordinateslist = []

fontsList = doc.loadSourceFonts(defcon.Font)

for i in range(len(fontsList)):
    nameTag = str(fontsList[i].info.styleName)
    for glyph in fontsList[i]:
        compatibilityCounter = 0
        if glyph.name == thisGlyph.name:
            pointCount = 0
            for contour in glyph:
                for segment in contour.segments:
                    for points in segment:
                        pointCount += 1
        for refFonts in fontslist:
            compatible = glyph.isCompatible(refFont[glyph.name])
            if compatible:
                compatibilityCounter += 1
        print(compatibilityCounter)
예제 #44
0
def test_rulesDocument(tmpdir):
    # tests of rules in a document, roundtripping.
    tmpdir = str(tmpdir)
    testDocPath = os.path.join(tmpdir, "testRules.designspace")
    testDocPath2 = os.path.join(tmpdir, "testRules_roundtrip.designspace")
    doc = DesignSpaceDocument()
    a1 = AxisDescriptor()
    a1.minimum = 0
    a1.maximum = 1000
    a1.default = 0
    a1.name = "axisName_a"
    a1.tag = "TAGA"
    b1 = AxisDescriptor()
    b1.minimum = 2000
    b1.maximum = 3000
    b1.default = 2000
    b1.name = "axisName_b"
    b1.tag = "TAGB"
    doc.addAxis(a1)
    doc.addAxis(b1)
    r1 = RuleDescriptor()
    r1.name = "named.rule.1"
    r1.conditionSets.append([
        dict(name='axisName_a', minimum=0, maximum=1000),
        dict(name='axisName_b', minimum=0, maximum=3000)
    ])
    r1.subs.append(("a", "a.alt"))
    # rule with minium and maximum
    doc.addRule(r1)
    assert len(doc.rules) == 1
    assert len(doc.rules[0].conditionSets) == 1
    assert len(doc.rules[0].conditionSets[0]) == 2
    assert _axesAsDict(doc.axes) == {'axisName_a': {'map': [], 'name': 'axisName_a', 'default': 0, 'minimum': 0, 'maximum': 1000, 'tag': 'TAGA'}, 'axisName_b': {'map': [], 'name': 'axisName_b', 'default': 2000, 'minimum': 2000, 'maximum': 3000, 'tag': 'TAGB'}}
    assert doc.rules[0].conditionSets == [[
        {'minimum': 0, 'maximum': 1000, 'name': 'axisName_a'},
        {'minimum': 0, 'maximum': 3000, 'name': 'axisName_b'}]]
    assert doc.rules[0].subs == [('a', 'a.alt')]
    doc.normalize()
    assert doc.rules[0].name == 'named.rule.1'
    assert doc.rules[0].conditionSets == [[
        {'minimum': 0.0, 'maximum': 1.0, 'name': 'axisName_a'},
        {'minimum': 0.0, 'maximum': 1.0, 'name': 'axisName_b'}]]
    # still one conditionset
    assert len(doc.rules[0].conditionSets) == 1
    doc.write(testDocPath)
    # add a stray conditionset
    _addUnwrappedCondition(testDocPath)
    doc2 = DesignSpaceDocument()
    doc2.read(testDocPath)
    assert len(doc2.axes) == 2
    assert len(doc2.rules) == 1
    assert len(doc2.rules[0].conditionSets) == 2
    doc2.write(testDocPath2)
    # verify these results
    # make sure the stray condition is now neatly wrapped in a conditionset.
    doc3 = DesignSpaceDocument()
    doc3.read(testDocPath2)
    assert len(doc3.rules) == 1
    assert len(doc3.rules[0].conditionSets) == 2
예제 #45
0
def test_localisedNames(tmpdir):
    tmpdir = str(tmpdir)
    testDocPath = os.path.join(tmpdir, "testLocalisedNames.designspace")
    testDocPath2 = os.path.join(tmpdir, "testLocalisedNames_roundtrip.designspace")
    masterPath1 = os.path.join(tmpdir, "masters", "masterTest1.ufo")
    masterPath2 = os.path.join(tmpdir, "masters", "masterTest2.ufo")
    instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo")
    instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")
    doc = DesignSpaceDocument()
    # add master 1
    s1 = SourceDescriptor()
    s1.filename = os.path.relpath(masterPath1, os.path.dirname(testDocPath))
    s1.name = "master.ufo1"
    s1.copyInfo = True
    s1.location = dict(weight=0)
    doc.addSource(s1)
    # add master 2
    s2 = SourceDescriptor()
    s2.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath))
    s2.name = "master.ufo2"
    s2.location = dict(weight=1000)
    doc.addSource(s2)
    # add instance 1
    i1 = InstanceDescriptor()
    i1.filename = os.path.relpath(instancePath1, os.path.dirname(testDocPath))
    i1.familyName = "Montserrat"
    i1.styleName = "SemiBold"
    i1.styleMapFamilyName = "Montserrat SemiBold"
    i1.styleMapStyleName = "Regular"
    i1.setFamilyName("Montserrat", "fr")
    i1.setFamilyName(u"モンセラート", "ja")
    i1.setStyleName("Demigras", "fr")
    i1.setStyleName(u"半ば", "ja")
    i1.setStyleMapStyleName(u"Standard", "de")
    i1.setStyleMapFamilyName("Montserrat Halbfett", "de")
    i1.setStyleMapFamilyName(u"モンセラート SemiBold", "ja")
    i1.name = "instance.ufo1"
    i1.location = dict(weight=500, spooky=666)  # this adds a dimension that is not defined.
    i1.postScriptFontName = "InstancePostscriptName"
    glyphData = dict(name="arrow", mute=True, unicodes=[0x123])
    i1.glyphs['arrow'] = glyphData
    doc.addInstance(i1)
    # now we have sources and instances, but no axes yet.
    doc.axes = []   # clear the axes
    # write some axes
    a1 = AxisDescriptor()
    a1.minimum = 0
    a1.maximum = 1000
    a1.default = 0
    a1.name = "weight"
    a1.tag = "wght"
    # note: just to test the element language, not an actual label name recommendations.
    a1.labelNames[u'fa-IR'] = u"قطر"
    a1.labelNames[u'en'] = u"Wéíght"
    doc.addAxis(a1)
    a2 = AxisDescriptor()
    a2.minimum = 0
    a2.maximum = 1000
    a2.default = 0
    a2.name = "width"
    a2.tag = "wdth"
    a2.map = [(0.0, 10.0), (401.0, 66.0), (1000.0, 990.0)]
    a2.labelNames[u'fr'] = u"Poids"
    doc.addAxis(a2)
    # add an axis that is not part of any location to see if that works
    a3 = AxisDescriptor()
    a3.minimum = 333
    a3.maximum = 666
    a3.default = 444
    a3.name = "spooky"
    a3.tag = "spok"
    a3.map = [(0.0, 10.0), (401.0, 66.0), (1000.0, 990.0)]
    #doc.addAxis(a3)    # uncomment this line to test the effects of default axes values
    # write some rules
    r1 = RuleDescriptor()
    r1.name = "named.rule.1"
    r1.conditionSets.append([
        dict(name='weight', minimum=200, maximum=500),
        dict(name='width', minimum=0, maximum=150)
    ])
    r1.subs.append(("a", "a.alt"))
    doc.addRule(r1)
    # write the document
    doc.write(testDocPath)
    assert os.path.exists(testDocPath)
    # import it again
    new = DesignSpaceDocument()
    new.read(testDocPath)
    new.write(testDocPath2)
    with open(testDocPath, 'r', encoding='utf-8') as f1:
        t1 = f1.read()
    with open(testDocPath2, 'r', encoding='utf-8') as f2:
        t2 = f2.read()
    assert t1 == t2
예제 #46
0
def test_handleNoAxes(tmpdir):
    tmpdir = str(tmpdir)
    # test what happens if the designspacedocument has no axes element.
    testDocPath = os.path.join(tmpdir, "testNoAxes_source.designspace")
    testDocPath2 = os.path.join(tmpdir, "testNoAxes_recontructed.designspace")
    masterPath1 = os.path.join(tmpdir, "masters", "masterTest1.ufo")
    masterPath2 = os.path.join(tmpdir, "masters", "masterTest2.ufo")
    instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo")
    instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")

    # Case 1: No axes element in the document, but there are sources and instances
    doc = DesignSpaceDocument()

    for name, value in [('One', 1),('Two', 2),('Three', 3)]:
        a = AxisDescriptor()
        a.minimum = 0
        a.maximum = 1000
        a.default = 0
        a.name = "axisName%s" % (name)
        a.tag = "ax_%d" % (value)
        doc.addAxis(a)

    # add master 1
    s1 = SourceDescriptor()
    s1.filename = os.path.relpath(masterPath1, os.path.dirname(testDocPath))
    s1.name = "master.ufo1"
    s1.copyLib = True
    s1.copyInfo = True
    s1.copyFeatures = True
    s1.location = dict(axisNameOne=-1000, axisNameTwo=0, axisNameThree=1000)
    s1.familyName = "MasterFamilyName"
    s1.styleName = "MasterStyleNameOne"
    doc.addSource(s1)

    # add master 2
    s2 = SourceDescriptor()
    s2.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath))
    s2.name = "master.ufo1"
    s2.copyLib = False
    s2.copyInfo = False
    s2.copyFeatures = False
    s2.location = dict(axisNameOne=1000, axisNameTwo=1000, axisNameThree=0)
    s2.familyName = "MasterFamilyName"
    s2.styleName = "MasterStyleNameTwo"
    doc.addSource(s2)

    # add instance 1
    i1 = InstanceDescriptor()
    i1.filename = os.path.relpath(instancePath1, os.path.dirname(testDocPath))
    i1.familyName = "InstanceFamilyName"
    i1.styleName = "InstanceStyleName"
    i1.name = "instance.ufo1"
    i1.location = dict(axisNameOne=(-1000,500), axisNameTwo=100)
    i1.postScriptFontName = "InstancePostscriptName"
    i1.styleMapFamilyName = "InstanceStyleMapFamilyName"
    i1.styleMapStyleName = "InstanceStyleMapStyleName"
    doc.addInstance(i1)

    doc.write(testDocPath)
    verify = DesignSpaceDocument()
    verify.read(testDocPath)
    verify.write(testDocPath2)
예제 #47
0
def test_normalise2():
    # normalisation with minimum > 0
    doc = DesignSpaceDocument()
    # write some axes
    a2 = AxisDescriptor()
    a2.minimum = 100
    a2.maximum = 1000
    a2.default = 100
    a2.name = "axisName_b"
    doc.addAxis(a2)
    assert doc.normalizeLocation(dict(axisName_b=0)) == {'axisName_b': 0.0}
    assert doc.normalizeLocation(dict(axisName_b=1000)) == {'axisName_b': 1.0}
    # clipping beyond max values:
    assert doc.normalizeLocation(dict(axisName_b=1001)) == {'axisName_b': 1.0}
    assert doc.normalizeLocation(dict(axisName_b=500)) == {'axisName_b': 0.4444444444444444}
    assert doc.normalizeLocation(dict(axisName_b=-1000)) == {'axisName_b': 0.0}
    assert doc.normalizeLocation(dict(axisName_b=-1001)) == {'axisName_b': 0.0}
    # anisotropic coordinates normalise to isotropic
    assert doc.normalizeLocation(dict(axisName_b=(1000,-1000))) == {'axisName_b': 1.0}
    assert doc.normalizeLocation(dict(axisName_b=1001)) == {'axisName_b': 1.0}
    doc.normalize()
    r = []
    for axis in doc.axes:
        r.append((axis.name, axis.minimum, axis.default, axis.maximum))
    r.sort()
    assert r == [('axisName_b', 0.0, 0.0, 1.0)]
예제 #48
0
def test_normalise1():
    # normalisation of anisotropic locations, clipping
    doc = DesignSpaceDocument()
    # write some axes
    a1 = AxisDescriptor()
    a1.minimum = -1000
    a1.maximum = 1000
    a1.default = 0
    a1.name = "axisName_a"
    a1.tag = "TAGA"
    doc.addAxis(a1)
    assert doc.normalizeLocation(dict(axisName_a=0)) == {'axisName_a': 0.0}
    assert doc.normalizeLocation(dict(axisName_a=1000)) == {'axisName_a': 1.0}
    # clipping beyond max values:
    assert doc.normalizeLocation(dict(axisName_a=1001)) == {'axisName_a': 1.0}
    assert doc.normalizeLocation(dict(axisName_a=500)) == {'axisName_a': 0.5}
    assert doc.normalizeLocation(dict(axisName_a=-1000)) == {'axisName_a': -1.0}
    assert doc.normalizeLocation(dict(axisName_a=-1001)) == {'axisName_a': -1.0}
    # anisotropic coordinates normalise to isotropic
    assert doc.normalizeLocation(dict(axisName_a=(1000, -1000))) == {'axisName_a': 1.0}
    doc.normalize()
    r = []
    for axis in doc.axes:
        r.append((axis.name, axis.minimum, axis.default, axis.maximum))
    r.sort()
    assert r == [('axisName_a', -1.0, 0.0, 1.0)]
예제 #49
0
    def from_designspace(
        cls,
        designspace: designspaceLib.DesignSpaceDocument,
        round_geometry: bool = True,
    ):
        """Instantiates a new data class from a Designspace object."""
        if designspace.default is None:
            raise ValueError(
                "Can't generate UFOs from this designspace: no default font."
            )

        glyph_names: Set[str] = set()
        for source in designspace.sources:
            if source.font is None:
                if not Path(source.path).exists():
                    raise ValueError(f"Source at path '{source.path}' not found.")
                source.font = ufoLib2.Font.open(source.path, lazy=False)
            glyph_names.update(source.font.keys())

        # Construct Variators
        axis_bounds: Dict[str, Tuple[float, float, float]] = {}
        axis_by_name: Dict[str, designspaceLib.AxisDescriptor] = {}
        weight_width_axes = {}
        for axis in designspace.axes:
            axis_by_name[axis.name] = axis
            axis_bounds[axis.name] = (axis.minimum, axis.default, axis.maximum)
            if axis.tag in ("wght", "wdth"):
                weight_width_axes[axis.tag] = axis

        masters_info = collect_info_masters(designspace)
        info_mutator = Variator.from_masters(masters_info, axis_by_name, axis_bounds)

        masters_kerning = collect_kerning_masters(designspace)
        kerning_mutator = Variator.from_masters(
            masters_kerning, axis_by_name, axis_bounds
        )

        glyph_mutators: Dict[str, Variator] = {}
        for glyph_name in glyph_names:
            items = collect_glyph_masters(designspace, glyph_name)
            mutator = Variator.from_masters(items, axis_by_name, axis_bounds)
            glyph_mutators[glyph_name] = mutator

        # Construct defaults to copy over
        default_source = designspace.findDefault()
        copy_feature_text: str = next(
            (s.font.features.text for s in designspace.sources if s.copyFeatures),
            default_source.font.features.text,
        )
        copy_groups: Mapping[str, List[str]] = next(
            (s.font.groups for s in designspace.sources if s.copyGroups),
            default_source.font.groups,
        )
        copy_info: ufoLib2.objects.Info = next(
            (s.font.info for s in designspace.sources if s.copyInfo),
            default_source.font.info,
        )
        copy_lib: Mapping[str, Any] = next(
            (s.font.lib for s in designspace.sources if s.copyLib),
            default_source.font.lib,
        )

        # The list of glyphs not to export and decompose where used as a component is
        # supposed to be taken from the Designspace when a Designspace is used as the
        # starting point of the compilation process. It should be exported to all
        # instance libs, where the ufo2ft compilation functions will pick it up.
        skip_export_glyphs = designspace.lib.get("public.skipExportGlyphs", [])

        return cls(
            copy_feature_text,
            copy_groups,
            copy_info,
            copy_lib,
            designspace.rules,
            glyph_mutators,
            info_mutator,
            kerning_mutator,
            round_geometry,
            skip_export_glyphs,
            weight_width_axes,
        )
예제 #50
0
def test_updatePaths(tmpdir):
    doc = DesignSpaceDocument()
    doc.path = str(tmpdir / "foo" / "bar" / "MyDesignspace.designspace")

    s1 = SourceDescriptor()
    doc.addSource(s1)

    doc.updatePaths()

    # expect no changes
    assert s1.path is None
    assert s1.filename is None

    name1 = "../masters/Source1.ufo"
    path1 = posix(str(tmpdir / "foo" / "masters" / "Source1.ufo"))

    s1.path = path1
    s1.filename = None

    doc.updatePaths()

    assert s1.path == path1
    assert s1.filename == name1  # empty filename updated

    name2 = "../masters/Source2.ufo"
    s1.filename = name2

    doc.updatePaths()

    # conflicting filename discarded, path always gets precedence
    assert s1.path == path1
    assert s1.filename == "../masters/Source1.ufo"

    s1.path = None
    s1.filename = name2

    doc.updatePaths()

    # expect no changes
    assert s1.path is None
    assert s1.filename == name2
예제 #51
0
def test_updatePaths(tmpdir):
    doc = DesignSpaceDocument()
    doc.path = str(tmpdir / "foo" / "bar" / "MyDesignspace.designspace")

    s1 = SourceDescriptor()
    doc.addSource(s1)

    doc.updatePaths()

    # expect no changes
    assert s1.path is None
    assert s1.filename is None

    name1 = "../masters/Source1.ufo"
    path1 = posix(str(tmpdir / "foo" / "masters" / "Source1.ufo"))

    s1.path = path1
    s1.filename = None

    doc.updatePaths()

    assert s1.path == path1
    assert s1.filename == name1  # empty filename updated

    name2 = "../masters/Source2.ufo"
    s1.filename = name2

    doc.updatePaths()

    # conflicting filename discarded, path always gets precedence
    assert s1.path == path1
    assert s1.filename == "../masters/Source1.ufo"

    s1.path = None
    s1.filename = name2

    doc.updatePaths()

    # expect no changes
    assert s1.path is None
    assert s1.filename == name2
예제 #52
0
def test_read_with_path_object():
    import pathlib
    source = (pathlib.Path(__file__) / "../data/test.designspace").resolve()
    assert source.exists()
    doc = DesignSpaceDocument()
    doc.read(source)
예제 #53
0
def test_loadSourceFonts_no_required_path():
    designspace = DesignSpaceDocument()
    designspace.sources.append(SourceDescriptor())

    with pytest.raises(DesignSpaceDocumentError, match="no 'path' attribute"):
        designspace.loadSourceFonts(lambda p: p)
예제 #54
0
    def from_designspace(
        cls,
        designspace: designspaceLib.DesignSpaceDocument,
        round_geometry: bool = True,
    ):
        """Instantiates a new data class from a Designspace object."""
        if designspace.default is None:
            raise ValueError(
                "Can't generate UFOs from this designspace: no default font."
            )

        designspace.loadSourceFonts(ufoLib2.Font.open)

        glyph_names: Set[str] = set()
        for source in designspace.sources:
            glyph_names.update(source.font.keys())

        # Construct Variators
        axis_bounds: Dict[str, Tuple[float, float, float]] = {}  # Design space!
        axis_order: List[str] = []
        weight_width_axes = {}
        for axis in designspace.axes:
            axis_order.append(axis.name)
            axis_bounds[axis.name] = (
                axis.map_forward(axis.minimum),
                axis.map_forward(axis.default),
                axis.map_forward(axis.maximum),
            )
            if axis.tag in ("wght", "wdth"):
                weight_width_axes[axis.tag] = axis

        masters_info = collect_info_masters(designspace, axis_bounds)
        info_mutator = Variator.from_masters(masters_info, axis_order)

        masters_kerning = collect_kerning_masters(designspace, axis_bounds)
        kerning_mutator = Variator.from_masters(masters_kerning, axis_order)

        default_font = designspace.findDefault().font
        glyph_mutators: Dict[str, Variator] = {}
        glyph_name_to_unicodes: Dict[str, List[int]] = {}
        for glyph_name in glyph_names:
            items = collect_glyph_masters(designspace, glyph_name, axis_bounds)
            glyph_mutators[glyph_name] = Variator.from_masters(items, axis_order)
            glyph_name_to_unicodes[glyph_name] = default_font[glyph_name].unicodes

        # Construct defaults to copy over
        copy_feature_text: str = default_font.features.text
        copy_groups: Mapping[str, List[str]] = default_font.groups
        copy_info: ufoLib2.objects.Info = default_font.info
        copy_lib: Mapping[str, Any] = default_font.lib

        # The list of glyphs not to export and decompose where used as a component is
        # supposed to be taken from the Designspace when a Designspace is used as the
        # starting point of the compilation process. It should be exported to all
        # instance libs, where the ufo2ft compilation functions will pick it up.
        skip_export_glyphs = designspace.lib.get("public.skipExportGlyphs", [])

        return cls(
            axis_bounds,
            copy_feature_text,
            copy_groups,
            copy_info,
            copy_lib,
            designspace.rules,
            glyph_mutators,
            glyph_name_to_unicodes,
            info_mutator,
            kerning_mutator,
            round_geometry,
            skip_export_glyphs,
            weight_width_axes,
        )
예제 #55
0
    async def load(self, outputWriter):
        if self.doc is None:
            self.doc = DesignSpaceDocument.fromfile(self.fontPath)
            self.doc.findDefault()

        with tempfile.TemporaryDirectory(
                prefix="fontgoggles_temp") as ttFolder:
            sourcePathToTTPath = getTTPaths(self.doc, ttFolder)
            ufosToCompile = []
            ttPaths = []
            outputs = []
            coros = []
            self._sourceFiles = defaultdict(list)
            self._includedFeatureFiles = defaultdict(list)
            previousUFOs = self._ufos
            self._ufos = {}
            previousSourceData = self._sourceFontData
            self._sourceFontData = {}

            for source in self.doc.sources:
                sourceKey = (source.path, source.layerName)
                self._sourceFiles[pathlib.Path(source.path)].append(sourceKey)
                ufoState = previousUFOs.get(sourceKey)
                if ufoState is None:
                    reader = UFOReader(source.path, validate=False)
                    glyphSet = reader.getGlyphSet(layerName=source.layerName)
                    glyphSet.glyphClass = Glyph
                    if source.layerName is None:
                        includedFeatureFiles = extractIncludedFeatureFiles(
                            source.path, reader)
                        getUnicodesAndAnchors = functools.partial(
                            self._getUnicodesAndAnchors, source.path)
                    else:
                        includedFeatureFiles = []

                        # We're not compiling features nor do we need cmaps for these sparse layers,
                        # so we don't need need proper anchor or unicode data
                        def getUnicodesAndAnchors():
                            return ({}, {})

                    ufoState = UFOState(
                        reader,
                        glyphSet,
                        getUnicodesAndAnchors=getUnicodesAndAnchors,
                        includedFeatureFiles=includedFeatureFiles)
                for includedFeaFile in ufoState.includedFeatureFiles:
                    self._includedFeatureFiles[includedFeaFile].append(
                        sourceKey)
                self._ufos[sourceKey] = ufoState

                if source.layerName is not None:
                    continue

                if source.path in ufosToCompile:
                    continue
                ttPath = sourcePathToTTPath[source.path]
                if source.path in previousSourceData:
                    with open(ttPath, "wb") as f:
                        f.write(previousSourceData[source.path])
                    self._sourceFontData[source.path] = previousSourceData[
                        source.path]
                else:
                    ufosToCompile.append(source.path)
                    ttPaths.append(ttPath)
                    output = io.StringIO()
                    outputs.append(output)
                    coros.append(
                        compileUFOToPath(source.path, ttPath, output.write))

            # print(f"compiling {len(coros)} fonts")
            errors = await asyncio.gather(*coros, return_exceptions=True)

            for sourcePath, exc, output in zip(ufosToCompile, errors, outputs):
                output = output.getvalue()
                if output or exc is not None:
                    outputWriter(f"compile output for {sourcePath}:\n")
                    if output:
                        outputWriter(output)
                    if exc is not None:
                        outputWriter(f"{exc!r}\n")

            if any(errors):
                raise DesignSpaceSourceError(
                    f"Could not build '{os.path.basename(self.fontPath)}': "
                    "some sources did not successfully compile")
            for sourcePath, ttPath in zip(ufosToCompile, ttPaths):
                # Store compiled tt data so we can reuse it to rebuild ourselves
                # without recompiling the source.
                with open(ttPath, "rb") as f:
                    self._sourceFontData[sourcePath] = f.read()

            if not ufosToCompile and not self._needsVFRebuild:
                # self.ttFont and self.shaper are still up-to-date
                return

            vfFontData = await compileDSToBytes(self.fontPath, ttFolder,
                                                outputWriter)

        f = io.BytesIO(vfFontData)
        self.ttFont = TTFont(f, lazy=True)
        # Nice cookie for us from the worker
        self.masterModel = pickle.loads(self.ttFont["MPcl"].data)
        assert len(self.masterModel.deltaWeights) == len(self.doc.sources)

        self.shaper = HBShape(vfFontData,
                              getHorizontalAdvance=self._getHorizontalAdvance,
                              getVerticalAdvance=self._getVerticalAdvance,
                              getVerticalOrigin=self._getVerticalOrigin,
                              ttFont=self.ttFont)
        self._needsVFRebuild = False
예제 #56
0
def test_pathNameResolve(tmpdir):
    tmpdir = str(tmpdir)
    # test how descriptor.path and descriptor.filename are resolved
    testDocPath1 = os.path.join(tmpdir, "testPathName_case1.designspace")
    testDocPath2 = os.path.join(tmpdir, "testPathName_case2.designspace")
    testDocPath3 = os.path.join(tmpdir, "testPathName_case3.designspace")
    testDocPath4 = os.path.join(tmpdir, "testPathName_case4.designspace")
    testDocPath5 = os.path.join(tmpdir, "testPathName_case5.designspace")
    testDocPath6 = os.path.join(tmpdir, "testPathName_case6.designspace")
    masterPath1 = os.path.join(tmpdir, "masters", "masterTest1.ufo")
    masterPath2 = os.path.join(tmpdir, "masters", "masterTest2.ufo")
    instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo")
    instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")

    a1 = AxisDescriptor()
    a1.tag = "TAGA"
    a1.name = "axisName_a"
    a1.minimum = 0
    a1.maximum = 1000
    a1.default = 0

    # Case 1: filename and path are both empty. Nothing to calculate, nothing to put in the file.
    doc = DesignSpaceDocument()
    doc.addAxis(a1)
    s = SourceDescriptor()
    s.filename = None
    s.path = None
    s.copyInfo = True
    s.location = dict(weight=0)
    s.familyName = "MasterFamilyName"
    s.styleName = "MasterStyleNameOne"
    doc.addSource(s)
    doc.write(testDocPath1)
    verify = DesignSpaceDocument()
    verify.read(testDocPath1)
    assert verify.sources[0].filename == None
    assert verify.sources[0].path == None

    # Case 2: filename is empty, path points somewhere: calculate a new filename.
    doc = DesignSpaceDocument()
    doc.addAxis(a1)
    s = SourceDescriptor()
    s.filename = None
    s.path = masterPath1
    s.copyInfo = True
    s.location = dict(weight=0)
    s.familyName = "MasterFamilyName"
    s.styleName = "MasterStyleNameOne"
    doc.addSource(s)
    doc.write(testDocPath2)
    verify = DesignSpaceDocument()
    verify.read(testDocPath2)
    assert verify.sources[0].filename == "masters/masterTest1.ufo"
    assert verify.sources[0].path == posix(masterPath1)

    # Case 3: the filename is set, the path is None.
    doc = DesignSpaceDocument()
    doc.addAxis(a1)
    s = SourceDescriptor()
    s.filename = "../somewhere/over/the/rainbow.ufo"
    s.path = None
    s.copyInfo = True
    s.location = dict(weight=0)
    s.familyName = "MasterFamilyName"
    s.styleName = "MasterStyleNameOne"
    doc.addSource(s)
    doc.write(testDocPath3)
    verify = DesignSpaceDocument()
    verify.read(testDocPath3)
    assert verify.sources[0].filename == "../somewhere/over/the/rainbow.ufo"
    # make the absolute path for filename so we can see if it matches the path
    p = os.path.abspath(os.path.join(os.path.dirname(testDocPath3), verify.sources[0].filename))
    assert verify.sources[0].path == posix(p)

    # Case 4: the filename points to one file, the path points to another. The path takes precedence.
    doc = DesignSpaceDocument()
    doc.addAxis(a1)
    s = SourceDescriptor()
    s.filename = "../somewhere/over/the/rainbow.ufo"
    s.path = masterPath1
    s.copyInfo = True
    s.location = dict(weight=0)
    s.familyName = "MasterFamilyName"
    s.styleName = "MasterStyleNameOne"
    doc.addSource(s)
    doc.write(testDocPath4)
    verify = DesignSpaceDocument()
    verify.read(testDocPath4)
    assert verify.sources[0].filename == "masters/masterTest1.ufo"

    # Case 5: the filename is None, path has a value, update the filename
    doc = DesignSpaceDocument()
    doc.addAxis(a1)
    s = SourceDescriptor()
    s.filename = None
    s.path = masterPath1
    s.copyInfo = True
    s.location = dict(weight=0)
    s.familyName = "MasterFamilyName"
    s.styleName = "MasterStyleNameOne"
    doc.addSource(s)
    doc.write(testDocPath5) # so that the document has a path
    doc.updateFilenameFromPath()
    assert doc.sources[0].filename == "masters/masterTest1.ufo"

    # Case 6: the filename has a value, path has a value, update the filenames with force
    doc = DesignSpaceDocument()
    doc.addAxis(a1)
    s = SourceDescriptor()
    s.filename = "../somewhere/over/the/rainbow.ufo"
    s.path = masterPath1
    s.copyInfo = True
    s.location = dict(weight=0)
    s.familyName = "MasterFamilyName"
    s.styleName = "MasterStyleNameOne"
    doc.write(testDocPath5) # so that the document has a path
    doc.addSource(s)
    assert doc.sources[0].filename == "../somewhere/over/the/rainbow.ufo"
    doc.updateFilenameFromPath(force=True)
    assert doc.sources[0].filename == "masters/masterTest1.ufo"
예제 #57
0
def test_designspace_source_locations(tmpdir, ufo_module):
    """Check that opening UFOs from their source descriptor works with both
    the filename and the path attributes.
    """
    designspace_path = os.path.join(str(tmpdir), "test.designspace")
    light_ufo_path = os.path.join(str(tmpdir), "light.ufo")
    bold_ufo_path = os.path.join(str(tmpdir), "bold.ufo")

    designspace = DesignSpaceDocument()
    wght = AxisDescriptor()
    wght.minimum = 100
    wght.maximum = 700
    wght.default = 100
    wght.name = "Weight"
    wght.tag = "wght"
    designspace.addAxis(wght)
    light_source = designspace.newSourceDescriptor()
    light_source.filename = "light.ufo"
    light_source.location = {"Weight": 100}
    designspace.addSource(light_source)
    bold_source = designspace.newSourceDescriptor()
    bold_source.path = bold_ufo_path
    bold_source.location = {"Weight": 700}
    designspace.addSource(bold_source)
    designspace.write(designspace_path)

    light = ufo_module.Font()
    light.info.ascender = 30
    light.save(light_ufo_path)

    bold = ufo_module.Font()
    bold.info.ascender = 40
    bold.save(bold_ufo_path)

    designspace = DesignSpaceDocument()
    designspace.read(designspace_path)

    font = to_glyphs(designspace, ufo_module=ufo_module)

    assert len(font.masters) == 2
    assert font.masters[0].ascender == 30
    assert font.masters[1].ascender == 40
예제 #58
0
def test_unicodes(tmpdir):
    tmpdir = str(tmpdir)
    testDocPath = os.path.join(tmpdir, "testUnicodes.designspace")
    testDocPath2 = os.path.join(tmpdir, "testUnicodes_roundtrip.designspace")
    masterPath1 = os.path.join(tmpdir, "masters", "masterTest1.ufo")
    masterPath2 = os.path.join(tmpdir, "masters", "masterTest2.ufo")
    instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo")
    instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")
    doc = DesignSpaceDocument()
    # add master 1
    s1 = SourceDescriptor()
    s1.filename = os.path.relpath(masterPath1, os.path.dirname(testDocPath))
    s1.name = "master.ufo1"
    s1.copyInfo = True
    s1.location = dict(weight=0)
    doc.addSource(s1)
    # add master 2
    s2 = SourceDescriptor()
    s2.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath))
    s2.name = "master.ufo2"
    s2.location = dict(weight=1000)
    doc.addSource(s2)
    # add instance 1
    i1 = InstanceDescriptor()
    i1.filename = os.path.relpath(instancePath1, os.path.dirname(testDocPath))
    i1.name = "instance.ufo1"
    i1.location = dict(weight=500)
    glyphData = dict(name="arrow", mute=True, unicodes=[100, 200, 300])
    i1.glyphs['arrow'] = glyphData
    doc.addInstance(i1)
    # now we have sources and instances, but no axes yet.
    doc.axes = []   # clear the axes
    # write some axes
    a1 = AxisDescriptor()
    a1.minimum = 0
    a1.maximum = 1000
    a1.default = 0
    a1.name = "weight"
    a1.tag = "wght"
    doc.addAxis(a1)
    # write the document
    doc.write(testDocPath)
    assert os.path.exists(testDocPath)
    # import it again
    new = DesignSpaceDocument()
    new.read(testDocPath)
    new.write(testDocPath2)
    # compare the file contents
    with open(testDocPath, 'r', encoding='utf-8') as f1:
        t1 = f1.read()
    with open(testDocPath2, 'r', encoding='utf-8') as f2:
        t2 = f2.read()
    assert t1 == t2
    # check the unicode values read from the document
    assert new.instances[0].glyphs['arrow']['unicodes'] == [100,200,300]