Skip to content

Commit

Permalink
register_font: support for new API
Browse files Browse the repository at this point in the history
- Pango 1.52.0 supports a new API for registering fonts on
Windows and this commit adds support for it.
- Using the new API requires adding the font to every
PangoFontMap that is used in the application.
- Due to this, the font files is stored in memory and
registered with every PangoFontMap that is created
when rendering text.
- `list_fonts` is improved by caching the list of fonts
based on the registered font files.

Signed-off-by: Naveen M K <naveen521kk@gmail.com>
  • Loading branch information
naveen521kk committed Sep 10, 2024
1 parent 83faa9b commit 85893e0
Show file tree
Hide file tree
Showing 10 changed files with 377 additions and 248 deletions.
2 changes: 1 addition & 1 deletion manimpango/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
f"{os.environ['PATH']}"
)
try:
from .register_font import * # noqa: F403,F401
from .cmanimpango import * # noqa: F403,F401
from .enums import * # noqa: F403,F401
from .register_font import * # noqa: F403,F401
except ImportError as ie: # pragma: no cover
py_ver = ".".join(map(str, sys.version_info[:3]))
msg = f"""
Expand Down
10 changes: 8 additions & 2 deletions manimpango/register_font.pxd → manimpango/_register_font.pxd
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@

from pango cimport *
from libc.stddef cimport wchar_t


cdef extern from "Python.h":
wchar_t* PyUnicode_AsWideCharString(
object unicode,
Expand Down Expand Up @@ -35,6 +34,13 @@ IF UNAME_SYSNAME == "Windows":
DWORD fl,
unsigned int pdv
)

ctypedef void* HANDLE
HANDLE CreateMutexA(void* lpMutexAttributes, int bInitialOwner, const char* lpName)
int ReleaseMutex(HANDLE hMutex)
int WaitForSingleObject(HANDLE hHandle, unsigned long dwMilliseconds)
int CloseHandle(HANDLE hObject)

ELIF UNAME_SYSNAME == "Darwin":
cdef extern from "Carbon/Carbon.h":
ctypedef struct CFURLRef:
Expand Down
144 changes: 144 additions & 0 deletions manimpango/_register_font.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
from pathlib import Path
from pango cimport *

import os
import warnings

include "utils.pxi"

cpdef bint _fc_register_font(set registered_fonts, str font_path):
a = Path(font_path)
assert a.exists(), f"font doesn't exist at {a.absolute()}"
font_path = os.fspath(a.absolute())
font_path_bytes = font_path.encode('utf-8')
cdef const unsigned char* fontPath = font_path_bytes
fontAddStatus = FcConfigAppFontAddFile(FcConfigGetCurrent(), fontPath)
if fontAddStatus:
registered_fonts.add(font_path)
return True
else:
return False


cpdef bint _fc_unregister_font(set registered_fonts, str font_path):
FcConfigAppFontClear(NULL)
registered_fonts.clear()
return True


IF UNAME_SYSNAME == "Linux":
_register_font = _fc_register_font
_unregister_font = _fc_unregister_font


ELIF UNAME_SYSNAME == "Windows":
cpdef bint _register_font(set registered_fonts, str font_path):
a = Path(font_path)
assert a.exists(), f"font doesn't exist at {a.absolute()}"
font_path = os.fspath(a.absolute())
cdef LPCWSTR wchar_path = PyUnicode_AsWideCharString(font_path, NULL)
fontAddStatus = AddFontResourceExW(
wchar_path,
FR_PRIVATE,
0
)

# add to registered_fonts even if it fails
# since there's another new API where it's registered again
registered_fonts.add(font_path)


if fontAddStatus > 0:
return True
else:
return False


cpdef bint _unregister_font(set registered_fonts, str font_path):
a = Path(font_path)
assert a.exists(), f"font doesn't exist at {a.absolute()}"
font_path = os.fspath(a.absolute())

if font_path in registered_fonts:
registered_fonts.remove(font_path)

cdef LPCWSTR wchar_path = PyUnicode_AsWideCharString(font_path, NULL)
return RemoveFontResourceExW(
wchar_path,
FR_PRIVATE,
0
)


ELIF UNAME_SYSNAME == "Darwin":
cpdef bint _register_font(set registered_fonts, str font_path):
a = Path(font_path)
assert a.exists(), f"font doesn't exist at {a.absolute()}"
font_path_bytes_py = str(a.absolute().as_uri()).encode('utf-8')
cdef unsigned char* font_path_bytes = <bytes>font_path_bytes_py
b = len(a.absolute().as_uri())
cdef CFURLRef cf_url = CFURLCreateWithBytes(NULL, font_path_bytes, b, 0x08000100, NULL)
res = CTFontManagerRegisterFontsForURL(
cf_url,
kCTFontManagerScopeProcess,
NULL
)
if res:
registered_fonts.add(os.fspath(a.absolute()))
return True
else:
return False


cpdef bint _unregister_font(set registered_fonts, str font_path):
a = Path(font_path)
assert a.exists(), f"font doesn't exist at {a.absolute()}"
font_path_bytes_py = str(a.absolute().as_uri()).encode('utf-8')
cdef unsigned char* font_path_bytes = <bytes>font_path_bytes_py
b = len(a.absolute().as_uri())
cdef CFURLRef cf_url = CFURLCreateWithBytes(NULL, font_path_bytes, b, 0x08000100, NULL)
res = CTFontManagerUnregisterFontsForURL(
cf_url,
kCTFontManagerScopeProcess,
NULL
)
if res:
if font_path in registered_fonts:
registered_fonts.remove(os.fspath(a.absolute()))
return True
else:
return False


cpdef list _list_fonts(tuple registered_fonts):
cdef PangoFontMap* fontmap = pango_cairo_font_map_new()
if fontmap == NULL:
raise MemoryError("Pango.FontMap can't be created.")

for font in registered_fonts:
add_to_fontmap(fontmap, font)

cdef int n_families=0
cdef PangoFontFamily** families=NULL
pango_font_map_list_families(
fontmap,
&families,
&n_families
)
if families is NULL or n_families == 0:
raise MemoryError("Pango returned unexpected length on families.")

family_list = []
for i in range(n_families):
name = pango_font_family_get_name(families[i])
# according to pango's docs, the `char *` returned from
# `pango_font_family_get_name`is owned by pango, and python
# shouldn't interfere with it. I hope Cython handles it.
# https://cython.readthedocs.io/en/stable/src/tutorial/strings.html#dealing-with-const
family_list.append(name.decode())

g_free(families)
g_object_unref(fontmap)
family_list.sort()
return family_list

13 changes: 13 additions & 0 deletions manimpango/cmanimpango.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ from xml.sax.saxutils import escape

from .enums import Alignment
from .utils import *
from . import registered_fonts

include "utils.pxi"

class TextSetting:
"""Formatting for slices of a :class:`manim.mobject.svg.text_mobject.Text` object."""
Expand Down Expand Up @@ -48,6 +50,7 @@ def text2svg(
cdef double font_size_c = size
cdef cairo_status_t status
cdef int temp_width
cdef PangoFontMap* fontmap

file_name_bytes = file_name.encode("utf-8")
surface = cairo_svg_surface_create(file_name_bytes,width,height)
Expand All @@ -72,6 +75,10 @@ def text2svg(
last_line_num = 0

layout = pango_cairo_create_layout(cr)
fontmap = pango_context_get_font_map (pango_layout_get_context (layout));

for font_path in registered_fonts:
add_to_fontmap(fontmap, font_path)

if layout == NULL:
cairo_destroy(cr)
Expand Down Expand Up @@ -206,6 +213,7 @@ class MarkupUtils:
cdef cairo_status_t status
cdef double font_size = size
cdef int temp_int # a temporary C integer for conversion
cdef PangoFontMap* fontmap

file_name_bytes = file_name.encode("utf-8")

Expand Down Expand Up @@ -235,6 +243,11 @@ class MarkupUtils:
cairo_surface_destroy(surface)
raise MemoryError("Pango.Layout can't be created from Cairo Context.")

fontmap = pango_context_get_font_map (pango_layout_get_context (layout));

for font_path in registered_fonts:
add_to_fontmap(fontmap, font_path)

if pango_width is None:
pango_layout_set_width(layout, pango_units_from_double(width))
else:
Expand Down
33 changes: 33 additions & 0 deletions manimpango/pango.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,20 @@ cdef extern from "pango/pangocairo.h":
PangoLayout *layout,
PangoAlignment alignment
)
PangoFontMap* pango_context_get_font_map(
PangoContext* context
)
PangoContext* pango_layout_get_context(
PangoLayout* layout
)



cdef extern from *:
"""
#if _WIN32
#include <pango/pangowin32.h>
#endif
#if PANGO_VERSION_CHECK(1,44,0)
int set_line_width(PangoLayout *layout,float spacing)
{
Expand All @@ -151,10 +162,32 @@ cdef extern from *:
#else
int set_line_width(PangoLayout *layout,float spacing){return 0;}
#endif
#if _WIN32 && PANGO_VERSION_CHECK(1,52,0)
gboolean font_map_add_font_file(PangoFontMap *font_map,
const char *font_file_path,
GError **error)
{
return pango_win32_font_map_add_font_file(font_map, font_file_path, error);
}
#else
gboolean font_map_add_font_file(PangoFontMap *font_map,
const char *font_file_path,
GError **error)
{
return 1;
}
#endif
"""
# The above docs string is C which is used to
# check for the Pango Version there at run time.
# pango_layout_set_line_spacing is only avaiable only for
# pango>=1.44.0 but we support pango>=1.30.0 that why this
# conditionals.
bint set_line_width(PangoLayout *layout,float spacing)

# only for windows and 1.52.0+
gboolean font_map_add_font_file(PangoFontMap *font_map,
const char *font_file_path,
GError **error)
Loading

0 comments on commit 85893e0

Please sign in to comment.