diff --git a/pyproject.toml b/pyproject.toml index b68ab56c4..49bb13318 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ Source = "https://github.com/googlefonts/fontra" [project.scripts] fontra = "fontra.__main__:main" +fontra-copy = "fontra.backends.copy:main" [project.entry-points."fontra.views"] diff --git a/src/fontra/backends/copy.py b/src/fontra/backends/copy.py new file mode 100644 index 000000000..95c06076a --- /dev/null +++ b/src/fontra/backends/copy.py @@ -0,0 +1,74 @@ +import argparse +import asyncio +import logging +import pathlib +import shutil + +from . import getFileSystemBackend, newFileSystemBackend + +logger = logging.getLogger(__name__) + + +async def copyFont(sourceBackend, destBackend, *, numTasks=8): + await destBackend.putGlobalAxes(await sourceBackend.getGlobalAxes()) + glyphMap = await sourceBackend.getGlyphMap() + glyphNamesToCopy = sorted(glyphMap) + + # Needed for rcjk backend, but is a bug there + # _ = await destBackend.getGlyphMap() + + tasks = [ + asyncio.create_task( + copyGlyphs(sourceBackend, destBackend, glyphMap, glyphNamesToCopy) + ) + for i in range(numTasks) + ] + done, pending = await asyncio.wait(tasks) + # await asyncio.sleep(4) + assert not pending + + +async def copyGlyphs(sourceBackend, destBackend, glyphMap, glyphNamesToCopy): + while glyphNamesToCopy: + glyphName = glyphNamesToCopy.pop(0) + logger.info(f"reading {glyphName}") + glyph = await sourceBackend.getGlyph(glyphName) + logger.info(f"writing {glyphName}") + error = await destBackend.putGlyph(glyphName, glyph, glyphMap[glyphName]) + if error: + # FIXME: putGlyph should always raise, and not return some error string + # This may be unique to the rcjk backend, though. + raise ValueError(error) + + +async def mainAsync(): + parser = argparse.ArgumentParser() + parser.add_argument("source") + parser.add_argument("destination") + parser.add_argument("--overwrite", type=bool, default=False) + + args = parser.parse_args() + + sourcePath = pathlib.Path(args.source) + assert sourcePath.exists() + destPath = pathlib.Path(args.destination) + if args: + if destPath.is_dir(): + shutil.rmtree(destPath) + elif destPath.exists(): + destPath.unlink() + elif destPath.exists(): + raise argparse.ArgumentError("the destination file already exists") + + sourceBackend = getFileSystemBackend(sourcePath) + destBackend = newFileSystemBackend(destPath) + + await copyFont(sourceBackend, destBackend) + + +def main(): + asyncio.run(mainAsync()) + + +if __name__ == "__main__": + main() diff --git a/test-py/test_backend_copy.py b/test-py/test_backend_copy.py new file mode 100644 index 000000000..e8c45e0f7 --- /dev/null +++ b/test-py/test_backend_copy.py @@ -0,0 +1,42 @@ +import pathlib +import subprocess + +from test_backend_designspace import fileNamesFromDir + +from fontra.backends import getFileSystemBackend, newFileSystemBackend +from fontra.backends.copy import copyFont + +mutatorDSPath = ( + pathlib.Path(__file__).resolve().parent + / "data" + / "mutatorsans" + / "MutatorSans.designspace" +) + + +async def test_copyFont(tmpdir): + tmpdir = pathlib.Path(tmpdir) + destPath = tmpdir / "MutatorCopy.designspace" + sourceFont = getFileSystemBackend(mutatorDSPath) + destFont = newFileSystemBackend(destPath) + await copyFont(sourceFont, destFont) + assert [ + "MutatorCopy.designspace", + "MutatorCopy_BoldCondensed.ufo", + "MutatorCopy_BoldWide.ufo", + "MutatorCopy_LightWide.ufo", + "MutatorCopy_Regular.ufo", + ] == fileNamesFromDir(tmpdir) + + +def test_fontra_copy(tmpdir): + tmpdir = pathlib.Path(tmpdir) + destPath = tmpdir / "MutatorCopy.designspace" + subprocess.run(["fontra-copy", mutatorDSPath, destPath]) + assert [ + "MutatorCopy.designspace", + "MutatorCopy_BoldCondensed.ufo", + "MutatorCopy_BoldWide.ufo", + "MutatorCopy_LightWide.ufo", + "MutatorCopy_Regular.ufo", + ] == fileNamesFromDir(tmpdir)