mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-24 17:06:16 +03:00
add support for fonts with COLR data
This commit is contained in:
parent
877831be13
commit
82a28d12e2
|
@ -291,7 +291,7 @@ Methods
|
|||
|
||||
Draw a shape.
|
||||
|
||||
.. py:method:: ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None)
|
||||
.. py:method:: ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None, embedded_color=False)
|
||||
|
||||
Draws the string at the given position.
|
||||
|
||||
|
@ -352,7 +352,12 @@ Methods
|
|||
|
||||
.. versionadded:: 6.2.0
|
||||
|
||||
.. py:method:: ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None)
|
||||
:param embedded_color: Whether to use embedded color info in COLR and CPAL tables.
|
||||
|
||||
.. versionadded:: 8.0.0
|
||||
|
||||
|
||||
.. py:method:: ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None, embedded_color=False)
|
||||
|
||||
Draws the string at the given position.
|
||||
|
||||
|
@ -399,6 +404,19 @@ Methods
|
|||
|
||||
.. versionadded:: 6.0.0
|
||||
|
||||
:param stroke_width: The width of the text stroke.
|
||||
|
||||
.. versionadded:: 6.2.0
|
||||
|
||||
:param stroke_fill: Color to use for the text stroke. If not given, will default to
|
||||
the ``fill`` parameter.
|
||||
|
||||
.. versionadded:: 6.2.0
|
||||
|
||||
:param embedded_color: Whether to use embedded color info in COLR and CPAL tables.
|
||||
|
||||
.. versionadded:: 8.0.0
|
||||
|
||||
.. py:method:: ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0)
|
||||
|
||||
Return the size of the given string, in pixels.
|
||||
|
|
|
@ -282,6 +282,7 @@ class ImageDraw:
|
|||
language=None,
|
||||
stroke_width=0,
|
||||
stroke_fill=None,
|
||||
embedded_color=False,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
|
@ -299,8 +300,12 @@ class ImageDraw:
|
|||
language,
|
||||
stroke_width,
|
||||
stroke_fill,
|
||||
embedded_color,
|
||||
)
|
||||
|
||||
if embedded_color and self.mode not in ("RGB", "RGBA"):
|
||||
raise ValueError("Embedded color supported only in RGB and RGBA modes")
|
||||
|
||||
if font is None:
|
||||
font = self.getfont()
|
||||
|
||||
|
@ -311,16 +316,20 @@ class ImageDraw:
|
|||
return ink
|
||||
|
||||
def draw_text(ink, stroke_width=0, stroke_offset=None):
|
||||
mode = self.fontmode
|
||||
if stroke_width == 0 and embedded_color:
|
||||
mode = "RGBA"
|
||||
coord = xy
|
||||
try:
|
||||
mask, offset = font.getmask2(
|
||||
text,
|
||||
self.fontmode,
|
||||
mode,
|
||||
direction=direction,
|
||||
features=features,
|
||||
language=language,
|
||||
stroke_width=stroke_width,
|
||||
anchor=anchor,
|
||||
ink=ink,
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
@ -329,12 +338,13 @@ class ImageDraw:
|
|||
try:
|
||||
mask = font.getmask(
|
||||
text,
|
||||
self.fontmode,
|
||||
mode,
|
||||
direction,
|
||||
features,
|
||||
language,
|
||||
stroke_width,
|
||||
anchor,
|
||||
ink,
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
@ -342,7 +352,15 @@ class ImageDraw:
|
|||
mask = font.getmask(text)
|
||||
if stroke_offset:
|
||||
coord = coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]
|
||||
self.draw.draw_bitmap(coord, mask, ink)
|
||||
if mode == "RGBA":
|
||||
# font.getmask2(mode="RGBA") returns color in RGB bands and mask in A
|
||||
# extract mask and set text alpha
|
||||
color, mask = mask, mask.getband(3)
|
||||
color.fillband(3, (ink >> 24) & 0xFF)
|
||||
coord2 = coord[0] + mask.size[0], coord[1] + mask.size[1]
|
||||
self.im.paste(color, coord + coord2, mask)
|
||||
else:
|
||||
self.draw.draw_bitmap(coord, mask, ink)
|
||||
|
||||
ink = getink(fill)
|
||||
if ink is not None:
|
||||
|
@ -374,6 +392,7 @@ class ImageDraw:
|
|||
language=None,
|
||||
stroke_width=0,
|
||||
stroke_fill=None,
|
||||
embedded_color=False,
|
||||
):
|
||||
if direction == "ttb":
|
||||
raise ValueError("ttb direction is unsupported for multiline text")
|
||||
|
@ -440,6 +459,7 @@ class ImageDraw:
|
|||
language=language,
|
||||
stroke_width=stroke_width,
|
||||
stroke_fill=stroke_fill,
|
||||
embedded_color=embedded_color,
|
||||
)
|
||||
top += line_spacing
|
||||
|
||||
|
|
|
@ -261,7 +261,7 @@ class FreeTypeFont:
|
|||
"""
|
||||
# vertical offset is added for historical reasons
|
||||
# see https://github.com/python-pillow/Pillow/pull/4910#discussion_r486682929
|
||||
size, offset = self.font.getsize(text, False, direction, features, language)
|
||||
size, offset = self.font.getsize(text, "L", direction, features, language)
|
||||
return (
|
||||
size[0] + stroke_width * 2,
|
||||
size[1] + stroke_width * 2 + offset[1],
|
||||
|
@ -348,12 +348,14 @@ class FreeTypeFont:
|
|||
language=None,
|
||||
stroke_width=0,
|
||||
anchor=None,
|
||||
ink=0,
|
||||
):
|
||||
"""
|
||||
Create a bitmap for the text.
|
||||
|
||||
If the font uses antialiasing, the bitmap should have mode ``L`` and use a
|
||||
maximum value of 255. Otherwise, it should have mode ``1``.
|
||||
maximum value of 255. If the font has embedded color data, the bitmap
|
||||
should have mode ``RGBA``. Otherwise, it should have mode ``1``.
|
||||
|
||||
:param text: Text to render.
|
||||
:param mode: Used by some graphics drivers to indicate what mode the
|
||||
|
@ -402,6 +404,10 @@ class FreeTypeFont:
|
|||
|
||||
.. versionadded:: 8.0.0
|
||||
|
||||
:param ink: Foreground ink for rendering in RGBA mode.
|
||||
|
||||
.. versionadded:: 8.0.0
|
||||
|
||||
:return: An internal PIL storage memory instance as defined by the
|
||||
:py:mod:`PIL.Image.core` interface module.
|
||||
"""
|
||||
|
@ -413,6 +419,7 @@ class FreeTypeFont:
|
|||
language=language,
|
||||
stroke_width=stroke_width,
|
||||
anchor=anchor,
|
||||
ink=ink,
|
||||
)[0]
|
||||
|
||||
def getmask2(
|
||||
|
@ -425,6 +432,7 @@ class FreeTypeFont:
|
|||
language=None,
|
||||
stroke_width=0,
|
||||
anchor=None,
|
||||
ink=0,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
|
@ -432,7 +440,8 @@ class FreeTypeFont:
|
|||
Create a bitmap for the text.
|
||||
|
||||
If the font uses antialiasing, the bitmap should have mode ``L`` and use a
|
||||
maximum value of 255. Otherwise, it should have mode ``1``.
|
||||
maximum value of 255. If the font has embedded color data, the bitmap
|
||||
should have mode ``RGBA``. Otherwise, it should have mode ``1``.
|
||||
|
||||
:param text: Text to render.
|
||||
:param mode: Used by some graphics drivers to indicate what mode the
|
||||
|
@ -481,18 +490,22 @@ class FreeTypeFont:
|
|||
|
||||
.. versionadded:: 8.0.0
|
||||
|
||||
:param ink: Foreground ink for rendering in RGBA mode.
|
||||
|
||||
.. versionadded:: 8.0.0
|
||||
|
||||
:return: A tuple of an internal PIL storage memory instance as defined by the
|
||||
:py:mod:`PIL.Image.core` interface module, and the text offset, the
|
||||
gap between the starting coordinate and the first marking
|
||||
"""
|
||||
size, offset = self.font.getsize(
|
||||
text, mode == "1", direction, features, language, anchor
|
||||
text, mode, direction, features, language, anchor
|
||||
)
|
||||
size = size[0] + stroke_width * 2, size[1] + stroke_width * 2
|
||||
offset = offset[0] - stroke_width, offset[1] - stroke_width
|
||||
im = fill("L", size, 0)
|
||||
im = fill("RGBA" if mode == "RGBA" else "L", size, 0)
|
||||
self.font.render(
|
||||
text, im.id, mode == "1", direction, features, language, stroke_width
|
||||
text, im.id, mode, direction, features, language, stroke_width, ink
|
||||
)
|
||||
return im, offset
|
||||
|
||||
|
|
123
src/_imagingft.c
123
src/_imagingft.c
|
@ -28,6 +28,9 @@
|
|||
#include FT_STROKER_H
|
||||
#include FT_MULTIPLE_MASTERS_H
|
||||
#include FT_SFNT_NAMES_H
|
||||
#ifdef FT_COLOR_H
|
||||
#include FT_COLOR_H
|
||||
#endif
|
||||
|
||||
#define KEEP_PY_UNICODE
|
||||
|
||||
|
@ -350,7 +353,7 @@ font_getchar(PyObject* string, int index, FT_ULong* char_out)
|
|||
|
||||
static size_t
|
||||
text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject *features,
|
||||
const char* lang, GlyphInfo **glyph_info, int mask)
|
||||
const char* lang, GlyphInfo **glyph_info, int mask, int color)
|
||||
{
|
||||
size_t i = 0, count = 0, start = 0;
|
||||
raqm_t *rq;
|
||||
|
@ -529,7 +532,7 @@ failed:
|
|||
|
||||
static size_t
|
||||
text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObject *features,
|
||||
const char* lang, GlyphInfo **glyph_info, int mask)
|
||||
const char* lang, GlyphInfo **glyph_info, int mask, int color)
|
||||
{
|
||||
int error, load_flags;
|
||||
FT_ULong ch;
|
||||
|
@ -565,6 +568,11 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObje
|
|||
if (mask) {
|
||||
load_flags |= FT_LOAD_TARGET_MONO;
|
||||
}
|
||||
#ifdef FT_LOAD_COLOR
|
||||
if (color) {
|
||||
load_flags |= FT_LOAD_COLOR;
|
||||
}
|
||||
#endif
|
||||
for (i = 0; font_getchar(string, i, &ch); i++) {
|
||||
(*glyph_info)[i].index = FT_Get_Char_Index(self->face, ch);
|
||||
error = FT_Load_Glyph(self->face, (*glyph_info)[i].index, load_flags);
|
||||
|
@ -595,14 +603,14 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObje
|
|||
|
||||
static size_t
|
||||
text_layout(PyObject* string, FontObject* self, const char* dir, PyObject *features,
|
||||
const char* lang, GlyphInfo **glyph_info, int mask)
|
||||
const char* lang, GlyphInfo **glyph_info, int mask, int color)
|
||||
{
|
||||
size_t count;
|
||||
|
||||
if (p_raqm.raqm && self->layout_engine == LAYOUT_RAQM) {
|
||||
count = text_layout_raqm(string, self, dir, features, lang, glyph_info, mask);
|
||||
count = text_layout_raqm(string, self, dir, features, lang, glyph_info, mask, color);
|
||||
} else {
|
||||
count = text_layout_fallback(string, self, dir, features, lang, glyph_info, mask);
|
||||
count = text_layout_fallback(string, self, dir, features, lang, glyph_info, mask, color);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
@ -624,6 +632,8 @@ font_getsize(FontObject* self, PyObject* args)
|
|||
size_t i, count; /* glyph_info index and length */
|
||||
int horizontal_dir; /* is primary axis horizontal? */
|
||||
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
||||
int color = 0; /* is FT_LOAD_COLOR enabled? */
|
||||
const char *mode = NULL;
|
||||
const char *dir = NULL;
|
||||
const char *lang = NULL;
|
||||
const char *anchor = NULL;
|
||||
|
@ -632,12 +642,15 @@ font_getsize(FontObject* self, PyObject* args)
|
|||
|
||||
/* calculate size and bearing for a given string */
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O|izOzz:getsize", &string, &mask, &dir, &features, &lang, &anchor)) {
|
||||
if (!PyArg_ParseTuple(args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
||||
|
||||
mask = mode && strcmp(mode, "1") == 0;
|
||||
color = mode && strcmp(mode, "RGBA") == 0;
|
||||
|
||||
if (anchor == NULL) {
|
||||
anchor = horizontal_dir ? "la" : "lt";
|
||||
}
|
||||
|
@ -645,7 +658,7 @@ font_getsize(FontObject* self, PyObject* args)
|
|||
goto bad_anchor;
|
||||
}
|
||||
|
||||
count = text_layout(string, self, dir, features, lang, &glyph_info, mask);
|
||||
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
|
||||
if (PyErr_Occurred()) {
|
||||
return NULL;
|
||||
}
|
||||
|
@ -657,6 +670,11 @@ font_getsize(FontObject* self, PyObject* args)
|
|||
if (mask) {
|
||||
load_flags |= FT_LOAD_TARGET_MONO;
|
||||
}
|
||||
#ifdef FT_LOAD_COLOR
|
||||
if (color) {
|
||||
load_flags |= FT_LOAD_COLOR;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* text bounds are given by:
|
||||
|
@ -834,7 +852,10 @@ font_render(FontObject* self, PyObject* args)
|
|||
Py_ssize_t id;
|
||||
int horizontal_dir; /* is primary axis horizontal? */
|
||||
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
||||
int color = 0; /* is FT_LOAD_COLOR enabled? */
|
||||
int stroke_width = 0;
|
||||
PY_LONG_LONG foreground_ink = 0;
|
||||
const char *mode = NULL;
|
||||
const char *dir = NULL;
|
||||
const char *lang = NULL;
|
||||
PyObject *features = Py_None;
|
||||
|
@ -843,14 +864,28 @@ font_render(FontObject* self, PyObject* args)
|
|||
/* render string into given buffer (the buffer *must* have
|
||||
the right size, or this will crash) */
|
||||
|
||||
if (!PyArg_ParseTuple(args, "On|izOzi:render", &string, &id, &mask, &dir, &features, &lang,
|
||||
&stroke_width)) {
|
||||
if (!PyArg_ParseTuple(args, "On|zzOziL:render", &string, &id, &mode, &dir, &features, &lang,
|
||||
&stroke_width, &foreground_ink)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
||||
|
||||
count = text_layout(string, self, dir, features, lang, &glyph_info, mask);
|
||||
mask = mode && strcmp(mode, "1") == 0;
|
||||
color = mode && strcmp(mode, "RGBA") == 0;
|
||||
|
||||
#ifdef FT_COLOR_H
|
||||
if (color) {
|
||||
FT_Color foreground_color;
|
||||
foreground_color.red = (FT_Byte) (foreground_ink);
|
||||
foreground_color.green = (FT_Byte) (foreground_ink >> 8);
|
||||
foreground_color.blue = (FT_Byte) (foreground_ink >> 16);
|
||||
foreground_color.alpha = (FT_Byte) 255; /* ink alpha is handled in ImageDraw.text */
|
||||
FT_Palette_Set_Foreground_Color(self->face, foreground_color);
|
||||
}
|
||||
#endif
|
||||
|
||||
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
|
||||
if (PyErr_Occurred()) {
|
||||
return NULL;
|
||||
}
|
||||
|
@ -874,6 +909,11 @@ font_render(FontObject* self, PyObject* args)
|
|||
if (mask) {
|
||||
load_flags |= FT_LOAD_TARGET_MONO;
|
||||
}
|
||||
#ifdef FT_LOAD_COLOR
|
||||
if (color) {
|
||||
load_flags |= FT_LOAD_COLOR;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* calculate x_min and y_max
|
||||
|
@ -960,25 +1000,58 @@ font_render(FontObject* self, PyObject* args)
|
|||
/* clip glyph bitmap height to target image bounds */
|
||||
if (yy >= 0 && yy < im->ysize) {
|
||||
// blend this glyph into the buffer
|
||||
unsigned char *target = im->image8[yy] + xx;
|
||||
if (mask) {
|
||||
// use monochrome mask (on palette images, etc)
|
||||
int j, k, m = 128;
|
||||
for (j = k = 0; j < x1; j++) {
|
||||
if (j >= x0 && (source[k] & m)) {
|
||||
target[j] = 255;
|
||||
if (color) {
|
||||
/* target[RGB] returns the color, target[A] returns the mask */
|
||||
/* target bands get split again in ImageDraw.text */
|
||||
unsigned char *target = im->image[yy] + xx * 4;
|
||||
#ifdef FT_LOAD_COLOR
|
||||
if (bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
|
||||
// paste color glyph
|
||||
int k;
|
||||
for (k = x0; k < x1; k++) {
|
||||
if (target[k * 4 + 3] < source[k * 4 + 3]) {
|
||||
/* unpremultiply BGRa to RGBA */
|
||||
target[k * 4 + 0] = CLIP8((255 * (int)source[k * 4 + 2]) / source[k * 4 + 3]);
|
||||
target[k * 4 + 1] = CLIP8((255 * (int)source[k * 4 + 1]) / source[k * 4 + 3]);
|
||||
target[k * 4 + 2] = CLIP8((255 * (int)source[k * 4 + 0]) / source[k * 4 + 3]);
|
||||
target[k * 4 + 3] = source[k * 4 + 3];
|
||||
}
|
||||
}
|
||||
if (!(m >>= 1)) {
|
||||
m = 128;
|
||||
k++;
|
||||
} else
|
||||
#endif
|
||||
{ // pixel_mode should be FT_PIXEL_MODE_GRAY
|
||||
// fill with ink
|
||||
int k;
|
||||
for (k = x0; k < x1; k++) {
|
||||
if (target[k * 4 + 3] < source[k]) {
|
||||
target[k * 4 + 0] = (unsigned char) (foreground_ink);
|
||||
target[k * 4 + 1] = (unsigned char) (foreground_ink >> 8);
|
||||
target[k * 4 + 2] = (unsigned char) (foreground_ink >> 16);
|
||||
target[k * 4 + 3] = source[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// use antialiased rendering
|
||||
int k;
|
||||
for (k = x0; k < x1; k++) {
|
||||
if (target[k] < source[k]) {
|
||||
target[k] = source[k];
|
||||
unsigned char *target = im->image8[yy] + xx;
|
||||
if (mask) {
|
||||
// use monochrome mask (on palette images, etc)
|
||||
int j, k, m = 128;
|
||||
for (j = k = 0; j < x1; j++) {
|
||||
if (j >= x0 && (source[k] & m)) {
|
||||
target[j] = 255;
|
||||
}
|
||||
if (!(m >>= 1)) {
|
||||
m = 128;
|
||||
k++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// use antialiased rendering
|
||||
int k;
|
||||
for (k = x0; k < x1; k++) {
|
||||
if (target[k] < source[k]) {
|
||||
target[k] = source[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user