mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-08-03 20:10:08 +03:00
font fallback for Raqm layout
This commit is contained in:
parent
3710f2d299
commit
910511606b
|
@ -725,18 +725,20 @@ class FreeTypeFontFamily:
|
|||
|
||||
# example:
|
||||
# from PIL import Image, ImageDraw, ImageFont
|
||||
# f1 = ImageFont.truetype("segoeui.ttf", 24)
|
||||
# f2 = ImageFont.truetype("seguisym.ttf", 24)
|
||||
# ff = ImageFont.FreeTypeFontFamily(f1, f2)
|
||||
# s = "a↦ľ"
|
||||
# im = Image.new("RGBA", (100, 100), "white")
|
||||
# d = ImageDraw.Draw(im)
|
||||
# d.text((10, 10), s, "black", f1)
|
||||
# d.text((10, 40), s, "black", f2)
|
||||
# d.text((10, 70), s, "black", ff)
|
||||
# im.show()
|
||||
# le = ImageFont.Layout.RAQM
|
||||
# f1 = ImageFont.truetype(r"C:\Users\Nulano\AppData\Local\Microsoft\Windows\Fonts\SCRIPTIN.ttf", 24)
|
||||
# f2 = ImageFont.truetype("segoeui.ttf", 24)
|
||||
# f3 = ImageFont.truetype("seguisym.ttf", 24)
|
||||
# ff = ImageFont.FreeTypeFontFamily(f1, f2, f3, layout_engine=le)
|
||||
# for s in ("testčingšsšccčcč", "ية↦α,abc", "a↦ľ"):
|
||||
# im = Image.new("RGBA", (300, 300), "white")
|
||||
# d = ImageDraw.Draw(im)
|
||||
# d.text((10, 60), s, "black", f1, direction="ltr", anchor="ls")
|
||||
# d.text((10, 160), s, "black", f2, direction="ltr", anchor="ls")
|
||||
# d.text((10, 260), s, "black", ff, direction="ltr", anchor="ls")
|
||||
# im.show()
|
||||
|
||||
def __init__(self, *fonts):
|
||||
def __init__(self, *fonts, layout_engine=None):
|
||||
fonts_list = []
|
||||
for font in fonts:
|
||||
try:
|
||||
|
@ -745,9 +747,21 @@ class FreeTypeFontFamily:
|
|||
)
|
||||
except AttributeError:
|
||||
fonts_list.append((font.path, font.size, font.index, font.encoding))
|
||||
|
||||
self.fonts = tuple(fonts_list)
|
||||
self.font = core.getfamily(self.fonts, layout_engine=Layout.BASIC)
|
||||
|
||||
if layout_engine not in (Layout.BASIC, Layout.RAQM):
|
||||
layout_engine = Layout.BASIC
|
||||
if core.HAVE_RAQM:
|
||||
layout_engine = Layout.RAQM
|
||||
elif layout_engine == Layout.RAQM and not core.HAVE_RAQM:
|
||||
warnings.warn(
|
||||
"Raqm layout was requested, but Raqm is not available. "
|
||||
"Falling back to basic layout."
|
||||
)
|
||||
layout_engine = Layout.BASIC
|
||||
self.layout_engine = layout_engine
|
||||
|
||||
self.font = core.getfamily(self.fonts, layout_engine=self.layout_engine)
|
||||
|
||||
def getlength(self, text, mode="", direction=None, features=None, language=None):
|
||||
"""
|
||||
|
|
203
src/_imagingft.c
203
src/_imagingft.c
|
@ -404,40 +404,35 @@ text_layout_raqm(
|
|||
raqm_glyph_t *glyphs = NULL;
|
||||
raqm_direction_t direction;
|
||||
|
||||
char *buffer = NULL;
|
||||
Py_UCS4 *text = NULL;
|
||||
Py_ssize_t size;
|
||||
int *fallback = NULL;
|
||||
|
||||
rq = raqm_create();
|
||||
if (rq == NULL) {
|
||||
PyErr_SetString(PyExc_ValueError, "raqm_create() failed.");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
Py_ssize_t size;
|
||||
int set_text;
|
||||
if (PyUnicode_Check(string)) {
|
||||
Py_UCS4 *text = PyUnicode_AsUCS4Copy(string);
|
||||
text = PyUnicode_AsUCS4Copy(string);
|
||||
size = PyUnicode_GET_LENGTH(string);
|
||||
if (!text || !size) {
|
||||
/* return 0 and clean up, no glyphs==no size,
|
||||
and raqm fails with empty strings */
|
||||
goto failed;
|
||||
}
|
||||
set_text = raqm_set_text(rq, text, size);
|
||||
PyMem_Free(text);
|
||||
} else {
|
||||
char *buffer;
|
||||
PyBytes_AsStringAndSize(string, &buffer, &size);
|
||||
if (!buffer || !size) {
|
||||
/* return 0 and clean up, no glyphs==no size,
|
||||
and raqm fails with empty strings */
|
||||
goto failed;
|
||||
}
|
||||
set_text = raqm_set_text_utf8(rq, buffer, size);
|
||||
}
|
||||
if (!set_text) {
|
||||
PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
|
||||
goto failed;
|
||||
}
|
||||
if (lang && !raqm_set_language(rq, lang, start, size)) {
|
||||
PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
|
||||
if (!buffer && !text) {
|
||||
PyErr_SetString(PyExc_ValueError, "expected string");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
|
@ -464,63 +459,142 @@ text_layout_raqm(
|
|||
}
|
||||
}
|
||||
|
||||
if (!raqm_set_par_direction(rq, direction)) {
|
||||
PyErr_SetString(PyExc_ValueError, "raqm_set_par_direction() failed");
|
||||
goto failed;
|
||||
if (family->font_count > 1) {
|
||||
fallback = PyMem_New(int, size);
|
||||
if (!fallback) {
|
||||
PyErr_SetString(PyExc_ValueError, "failed to allocate fallback buffer.");
|
||||
goto failed;
|
||||
}
|
||||
fallback[0] = -1;
|
||||
for (size_t j = 1; j < size; j++) {
|
||||
fallback[j] = -2;
|
||||
}
|
||||
}
|
||||
|
||||
if (features != Py_None) {
|
||||
int j, len;
|
||||
PyObject *seq = PySequence_Fast(features, "expected a sequence");
|
||||
if (!seq) {
|
||||
for (int face = 0; face < family->font_count; face++) {
|
||||
raqm_clear_contents(rq);
|
||||
|
||||
int set_text = text != NULL ? raqm_set_text(rq, text, size) : raqm_set_text_utf8(rq, buffer, size);
|
||||
if (!set_text) {
|
||||
PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
|
||||
goto failed;
|
||||
}
|
||||
if (lang) {
|
||||
if (!raqm_set_language(rq, lang, start, size)) {
|
||||
PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
if (!raqm_set_par_direction(rq, direction)) {
|
||||
PyErr_SetString(PyExc_ValueError, "raqm_set_par_direction() failed");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
len = PySequence_Fast_GET_SIZE(seq);
|
||||
for (j = 0; j < len; j++) {
|
||||
PyObject *item = PySequence_Fast_GET_ITEM(seq, j);
|
||||
char *feature = NULL;
|
||||
Py_ssize_t size = 0;
|
||||
PyObject *bytes;
|
||||
if (features != Py_None) {
|
||||
int j, len;
|
||||
PyObject *seq = PySequence_Fast(features, "expected a sequence");
|
||||
if (!seq) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (!PyUnicode_Check(item)) {
|
||||
Py_DECREF(seq);
|
||||
PyErr_SetString(PyExc_TypeError, "expected a string");
|
||||
goto failed;
|
||||
}
|
||||
bytes = PyUnicode_AsUTF8String(item);
|
||||
if (bytes == NULL) {
|
||||
Py_DECREF(seq);
|
||||
goto failed;
|
||||
}
|
||||
feature = PyBytes_AS_STRING(bytes);
|
||||
size = PyBytes_GET_SIZE(bytes);
|
||||
if (!raqm_add_font_feature(rq, feature, size)) {
|
||||
Py_DECREF(seq);
|
||||
len = PySequence_Fast_GET_SIZE(seq);
|
||||
for (j = 0; j < len; j++) {
|
||||
PyObject *item = PySequence_Fast_GET_ITEM(seq, j);
|
||||
char *feature = NULL;
|
||||
Py_ssize_t size = 0;
|
||||
PyObject *bytes;
|
||||
|
||||
if (!PyUnicode_Check(item)) {
|
||||
Py_DECREF(seq);
|
||||
PyErr_SetString(PyExc_TypeError, "expected a string");
|
||||
goto failed;
|
||||
}
|
||||
bytes = PyUnicode_AsUTF8String(item);
|
||||
if (bytes == NULL) {
|
||||
Py_DECREF(seq);
|
||||
goto failed;
|
||||
}
|
||||
feature = PyBytes_AS_STRING(bytes);
|
||||
size = PyBytes_GET_SIZE(bytes);
|
||||
if (!raqm_add_font_feature(rq, feature, size)) {
|
||||
Py_DECREF(seq);
|
||||
Py_DECREF(bytes);
|
||||
PyErr_SetString(PyExc_ValueError, "raqm_add_font_feature() failed");
|
||||
goto failed;
|
||||
}
|
||||
Py_DECREF(bytes);
|
||||
PyErr_SetString(PyExc_ValueError, "raqm_add_font_feature() failed");
|
||||
}
|
||||
Py_DECREF(seq);
|
||||
}
|
||||
|
||||
if (face == 0) {
|
||||
if (!raqm_set_freetype_face(rq, family->faces[0])) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "raqm_set_freetype_face() failed.");
|
||||
goto failed;
|
||||
}
|
||||
Py_DECREF(bytes);
|
||||
} else {
|
||||
start = 0;
|
||||
for (size_t j = 0; j <= size; j++) {
|
||||
if (j < size) {
|
||||
if (fallback[j] == -2) {
|
||||
/* not a cluster boundary */
|
||||
continue;
|
||||
}
|
||||
if (fallback[start] == fallback[j]) {
|
||||
/* use same font face for this cluster */
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (fallback[start] == -1) {
|
||||
raqm_set_freetype_face_range(
|
||||
rq, family->faces[face], start, j - start);
|
||||
} else {
|
||||
raqm_set_freetype_face_range(
|
||||
rq, family->faces[fallback[start]], start, j - start);
|
||||
}
|
||||
start = j;
|
||||
}
|
||||
}
|
||||
Py_DECREF(seq);
|
||||
}
|
||||
|
||||
if (!raqm_set_freetype_face(rq, family->faces[0])) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "raqm_set_freetype_face() failed.");
|
||||
goto failed;
|
||||
}
|
||||
if (!raqm_layout(rq)) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "raqm_layout() failed.");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (!raqm_layout(rq)) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "raqm_layout() failed.");
|
||||
goto failed;
|
||||
}
|
||||
glyphs = raqm_get_glyphs(rq, &count);
|
||||
if (glyphs == NULL) {
|
||||
PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed.");
|
||||
count = 0;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
glyphs = raqm_get_glyphs(rq, &count);
|
||||
if (glyphs == NULL) {
|
||||
PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed.");
|
||||
count = 0;
|
||||
goto failed;
|
||||
if (i + 1 == family->font_count) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (size_t j = 1; j < size; j++) {
|
||||
if (fallback[j] == -1) {
|
||||
fallback[j] = -2;
|
||||
}
|
||||
}
|
||||
|
||||
int missing = 0;
|
||||
for (size_t j = 0; j < count; j++) {
|
||||
int cluster = glyphs[j].cluster;
|
||||
if (glyphs[j].index == 0) {
|
||||
/* cluster contains missing glyph */
|
||||
fallback[cluster] = -1;
|
||||
missing = 1;
|
||||
} else if (fallback[cluster] < 0) {
|
||||
/* use current font face for this cluster */
|
||||
fallback[cluster] = face;
|
||||
}
|
||||
}
|
||||
|
||||
if (!missing) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
(*glyph_info) = PyMem_New(GlyphInfo, count);
|
||||
|
@ -531,16 +605,27 @@ text_layout_raqm(
|
|||
}
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
(*glyph_info)[i].face = family->faces[0];
|
||||
(*glyph_info)[i].index = glyphs[i].index;
|
||||
(*glyph_info)[i].x_offset = glyphs[i].x_offset;
|
||||
(*glyph_info)[i].x_advance = glyphs[i].x_advance;
|
||||
(*glyph_info)[i].y_offset = glyphs[i].y_offset;
|
||||
(*glyph_info)[i].y_advance = glyphs[i].y_advance;
|
||||
(*glyph_info)[i].cluster = glyphs[i].cluster;
|
||||
|
||||
uint32_t cluster = glyphs[i].cluster;
|
||||
(*glyph_info)[i].cluster = cluster;
|
||||
if (fallback && fallback[cluster] >= 0) {
|
||||
(*glyph_info)[i].face = family->faces[fallback[cluster]];
|
||||
} else {
|
||||
/* FIXME use first font's missing glyph, not last font's */
|
||||
(*glyph_info)[i].face = family->faces[family->font_count - 1];
|
||||
}
|
||||
}
|
||||
|
||||
failed:
|
||||
PyMem_Free(text);
|
||||
if (fallback) {
|
||||
PyMem_Free(fallback);
|
||||
}
|
||||
raqm_destroy(rq);
|
||||
return count;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user