Subject: [PATCH v3 09/12] gnu: Add python-manimpango.

From: Daniel Meißner<[email-masked]>
Date: Sun, 2 Jan 2022 00:51:52 +0100

* gnu/packages/python-science.scm (python-manimpango): New variable.
---
 gnu/local.mk                                  |   2 +
 .../python-manimpango-remove-manim-dep.patch  | 172 ++++++++++++++++++
 gnu/packages/python-science.scm               |  26 +++
 3 files changed, 200 insertions(+)
 create mode 100644 gnu/packages/patches/python-manimpango-remove-manim-dep.patch

diff --git a/gnu/local.mk b/gnu/local.mk
index ff3e80ea68..c5fe7d87b8 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -47,6 +47,7 @@
 # Copyright © 2021 Dmitry Polyakov <[email-masked]>
 # Copyright © 2021 Andrew Tropin <[email-masked]>
 # Copyright © 2021 Simon Tournier <[email-masked]>
+# Copyright © 2021 Daniel Meißner <[email-masked]>
 #
 # This file is part of GNU Guix.
 #
@@ -1683,6 +1684,7 @@ dist_patch_DATA =						\
   %D%/packages/patches/python-flint-includes.patch		\
   %D%/packages/patches/python-libxml2-utf8.patch		\
   %D%/packages/patches/python-magic-python-bytecode.patch	\
+  %D%/packages/patches/python-manimpango-remove-manim-dep.patch \
   %D%/packages/patches/python-matplotlib-run-under-wayland-gtk3.patch	\
   %D%/packages/patches/python-memcached-syntax-warnings.patch	\
   %D%/packages/patches/python-moderngl-window-skip-tests.patch  \
diff --git a/gnu/packages/patches/python-manimpango-remove-manim-dep.patch b/gnu/packages/patches/python-manimpango-remove-manim-dep.patch
new file mode 100644
index 0000000000..579c2302d8
--- /dev/null
+++ b/gnu/packages/patches/python-manimpango-remove-manim-dep.patch
@@ -0,0 +1,172 @@
+Fix dependency on manim for tests
+
+This fixes a circular dependency between manim and manimpango.
+
+Extracted from upstream:
+https://github.com/ManimCommunity/ManimPango/commit/7e2b17aa14b10bd58af0598cc2de51a406682797
+
+diff --git a/tests/_manim.py b/tests/_manim.py
+index 3ea4676..b11d3e9 100644
+--- a/tests/_manim.py
++++ b/tests/_manim.py
+@@ -2,11 +2,12 @@
+ """This file contains helpers for the tests copied and modified
+ from Manim.
+ """
+-
++import copy
+ import os
++import re
+ from pathlib import Path
+
+-from manimpango import Alignment, MarkupUtils
++from manimpango import Alignment, MarkupUtils, TextSetting, text2svg
+
+
+ class MarkupText:
+@@ -104,3 +105,115 @@ class MarkupText:
+
+     def __repr__(self):
+         return f"MarkupText({repr(self.original_text)})"
++
++
++class Text:
++    def __init__(
++        self,
++        text: str,
++        fill_opacity: float = 1.0,
++        stroke_width: int = 0,
++        size: int = 1,
++        line_spacing: int = -1,
++        font: str = "",
++        slant: str = "NORMAL",
++        weight: str = "NORMAL",
++        gradient: tuple = None,
++        tab_width: int = 4,
++        disable_ligatures: bool = False,
++        filename: str = "text.svg",
++        **kwargs,
++    ) -> None:
++        self.size = size
++        self.filename = filename
++        self.line_spacing = line_spacing
++        self.font = font
++        self.slant = slant
++        self.weight = weight
++        self.gradient = gradient
++        self.tab_width = tab_width
++        self.original_text = text
++        self.disable_ligatures = disable_ligatures
++        text_without_tabs = text
++        self.t2f = self.t2s = self.t2w = {}
++        if text.find("\t") != -1:
++            text_without_tabs = text.replace("\t", " " * self.tab_width)
++        self.text = text_without_tabs
++        if self.line_spacing == -1:
++            self.line_spacing = self.size + self.size * 0.3
++        else:
++            self.line_spacing = self.size + self.size * self.line_spacing
++        self.text2svg()
++
++    def text2settings(self):
++        """Internally used function. Converts the texts and styles
++        to a setting for parsing."""
++        settings = []
++        t2x = [self.t2f, self.t2s, self.t2w]
++        for i in range(len(t2x)):
++            fsw = [self.font, self.slant, self.weight]
++            if t2x[i]:
++                for word, x in list(t2x[i].items()):
++                    for start, end in self.find_indexes(word, self.text):
++                        fsw[i] = x
++                        settings.append(TextSetting(start, end, *fsw))
++        # Set all text settings (default font, slant, weight)
++        fsw = [self.font, self.slant, self.weight]
++        settings.sort(key=lambda setting: setting.start)
++        temp_settings = settings.copy()
++        start = 0
++        for setting in settings:
++            if setting.start != start:
++                temp_settings.append(TextSetting(start, setting.start, *fsw))
++            start = setting.end
++        if start != len(self.text):
++            temp_settings.append(TextSetting(start, len(self.text), *fsw))
++        settings = sorted(temp_settings, key=lambda setting: setting.start)
++
++        if re.search(r"\n", self.text):
++            line_num = 0
++            for start, end in self.find_indexes("\n", self.text):
++                for setting in settings:
++                    if setting.line_num == -1:
++                        setting.line_num = line_num
++                    if start < setting.end:
++                        line_num += 1
++                        new_setting = copy.copy(setting)
++                        setting.end = end
++                        new_setting.start = end
++                        new_setting.line_num = line_num
++                        settings.append(new_setting)
++                        settings.sort(key=lambda setting: setting.start)
++                        break
++        for setting in settings:
++            if setting.line_num == -1:
++                setting.line_num = 0
++        return settings
++
++    def text2svg(self):
++        """Internally used function.
++        Convert the text to SVG using Pango
++        """
++        size = self.size * 10
++        line_spacing = self.line_spacing * 10
++        dir_name = Path(self.filename).parent
++        disable_liga = self.disable_ligatures
++        if not os.path.exists(dir_name):
++            os.makedirs(dir_name)
++        file_name = self.filename
++        settings = self.text2settings()
++        width = 600
++        height = 400
++
++        return text2svg(
++            settings,
++            size,
++            line_spacing,
++            disable_liga,
++            file_name,
++            30,
++            30,
++            width,
++            height,
++            self.text,
++        )
+diff --git a/tests/test_fonts.py b/tests/test_fonts.py
+index 51e7eb4..da42895 100644
+--- a/tests/test_fonts.py
++++ b/tests/test_fonts.py
+@@ -3,13 +3,12 @@ import sys
+ from pathlib import Path
+ from shutil import copyfile
+
+-import manim
+ import pytest
+
+ import manimpango
+
+ from . import FONT_DIR
+-from ._manim import MarkupText
++from ._manim import MarkupText, Text
+
+ font_lists = {
+     (FONT_DIR / "AdobeVFPrototype.ttf").absolute(): "Adobe Variable Font Prototype",
+@@ -38,7 +37,7 @@ def test_register_font(font_name):
+ @pytest.mark.parametrize("font_name", font_lists.values())
+ def test_warning(capfd, font_name):
+     print(font_name)
+-    manim.Text("Testing", font=font_name)
++    Text("Testing", font=font_name)
+     captured = capfd.readouterr()
+     assert "Pango-WARNING **" not in captured.err, "Looks like pango raised a warning?"
+
+--
+2.32.0
diff --git a/gnu/packages/python-science.scm b/gnu/packages/python-science.scm
index 65f1dbf014..61628ec956 100644
--- a/gnu/packages/python-science.scm
+++ b/gnu/packages/python-science.scm
@@ -39,6 +39,7 @@ (define-module (gnu packages python-science)
   #:use-module (gnu packages check)
   #:use-module (gnu packages databases)
   #:use-module (gnu packages gcc)
+  #:use-module (gnu packages gtk)
   #:use-module (gnu packages image-processing)
   #:use-module (gnu packages machine-learning)
   #:use-module (gnu packages maths)
@@ -1037,3 +1038,28 @@ (define-public python-modin
 libraries, Modin provides seamless integration and compatibility with existing
 pandas code.")
     (license license:asl2.0)))
+
+(define-public python-manimpango
+  (package
+    (name "python-manimpango")
+    (version "0.3.0")
+    (source
+     (origin
+       (method url-fetch)
+       (uri (pypi-uri "ManimPango" version))
+       (sha256
+        (base32
+         "1j2mbhf7d82718nkc0r8x7cf35hlh13b67qkczjbbys3w24nyfsw"))
+       (patches (search-patches "python-manimpango-remove-manim-dep.patch"))))
+    (build-system python-build-system)
+    (native-inputs
+     (list pkg-config python-cython python-pytest))
+    (inputs
+     (list pango))
+    (home-page "https://manimpango.manim.community/")
+    (synopsis
+     "Bindings for pango for use with Manim")
+    (description
+     "These are Python bindings for Pango to be used with the mathematical
+animation software Manim.")
+    (license license:gpl3+)))
-- 
2.34.0