emacs/src/sfntfont-android.c

822 lines
22 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* sfnt format font driver for Android.
Copyright (C) 2023-2024 Free Software Foundation, Inc.
This file is part of GNU Emacs.
GNU Emacs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.
GNU Emacs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include <config.h>
#include <dirent.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef __aarch64__
#include <arm_neon.h>
#endif
#include <android/api-level.h>
#include <android/log.h>
#include "androidterm.h"
#include "sfntfont.h"
#include "pdumper.h"
#include "blockinput.h"
#include "android.h"
/* Structure describing a temporary buffer. */
struct sfntfont_android_scanline_buffer
{
/* Size of this buffer. */
size_t buffer_size;
/* Pointer to the buffer data. */
void *buffer_data;
};
/* Array of directories to search for system fonts. */
static char *system_font_directories[] =
{
(char *) "/system/fonts",
(char *) "/product/fonts",
/* This should be filled in by init_sfntfont_android. */
(char[PATH_MAX]) { },
};
/* The font cache. */
static Lisp_Object font_cache;
/* The scanline buffer. */
static struct sfntfont_android_scanline_buffer scanline_buffer;
/* The largest size of the scanline buffer since the last window
update. */
static size_t max_scanline_buffer_size;
/* Return a temporary buffer for storing scan lines.
Set BUFFER to the buffer upon success. */
#ifndef __aarch64__
#define GET_SCANLINE_BUFFER(buffer, height, stride) \
do \
{ \
size_t _size; \
\
if (ckd_mul (&_size, height, stride)) \
memory_full (SIZE_MAX); \
\
if (_size < MAX_ALLOCA) \
(buffer) = alloca (_size); \
else \
{ \
if (_size > scanline_buffer.buffer_size) \
{ \
(buffer) \
= scanline_buffer.buffer_data \
= xrealloc (scanline_buffer.buffer_data, \
_size); \
scanline_buffer.buffer_size = _size; \
} \
else if (_size <= scanline_buffer.buffer_size) \
(buffer) = scanline_buffer.buffer_data; \
/* This is unreachable but clang says it is. */ \
else \
emacs_abort (); \
\
max_scanline_buffer_size \
= max (_size, max_scanline_buffer_size); \
} \
} while (false);
#else
#define GET_SCANLINE_BUFFER(buffer, height, stride) \
do \
{ \
size_t _size; \
void *_temp; \
\
if (ckd_mul (&_size, height, stride)) \
memory_full (SIZE_MAX); \
\
if (_size > scanline_buffer.buffer_size) \
{ \
if (posix_memalign (&_temp, 16, _size)) \
memory_full (_size); \
free (scanline_buffer.buffer_data); \
(buffer) \
= scanline_buffer.buffer_data \
= _temp; \
scanline_buffer.buffer_size = _size; \
} \
else if (_size <= scanline_buffer.buffer_size) \
(buffer) = scanline_buffer.buffer_data; \
/* This is unreachable but clang says it is. */ \
else \
emacs_abort (); \
\
max_scanline_buffer_size \
= max (_size, max_scanline_buffer_size); \
} while (false);
#endif
/* Scale each of the four packed bytes in P in the low 16 bits of P by
SCALE. Return the result.
SCALE is an integer between 0 and 256. */
static unsigned int
sfntfont_android_scale32 (unsigned int scale, unsigned int p)
{
uint32_t ag, rb;
uint32_t scaled_ag, scaled_rb;
ag = (p & 0xFF00FF00) >> 8;
rb = (p & 0x00FF00FF);
scaled_ag = (scale * ag) & 0xFF00FF00;
scaled_rb = (scale * rb) >> 8 & 0x00FF00FF;
return scaled_ag | scaled_rb;
}
static unsigned int
sfntfont_android_mul8x2 (unsigned int a8, unsigned int b32)
{
unsigned int i;
b32 &= 0xff00ff;
i = a8 * b32 + 0x800080;
return (i + ((i >> 8) & 0xff00ff)) >> 8 & 0xff00ff;
}
#define U255TO256(x) ((unsigned short) (x) + ((x) >> 7))
/* Blend two pixels SRC and DST without utilizing any control flow.
Both SRC and DST are expected to be in premultiplied ABGB8888
format. Value is returned in premultiplied ARGB8888 format. */
static unsigned int
sfntfont_android_blend (unsigned int src, unsigned int dst)
{
unsigned int a, br_part, ag_part, both;
a = (src >> 24);
br_part = sfntfont_android_mul8x2 (255 - a, dst);
ag_part = sfntfont_android_mul8x2 (255 - a, dst >> 8) << 8;
both = ag_part | br_part;
/* This addition need not be saturating because both has already
been multiplied by 255 - a. */
return both + src;
}
#ifdef __aarch64__
/* Like U255TO256, but operates on vectors. */
static uint16x8_t
sfntfont_android_u255to256 (uint8x8_t in)
{
return vaddl_u8 (vshr_n_u8 (in, 7), in);
}
/* Use processor features to efficiently composite four pixels at SRC
to DST. */
static void
sfntfont_android_over_8888_1 (unsigned int *src, unsigned int *dst)
{
uint8x8_t alpha;
uint16x8_t alpha_c16, v1, v3, v4;
uint8x8_t b, g, r, a, v2, v5;
uint8x8x4_t _src, _dst;
/* Pull in src and dst.
This loads bytes, not words, so little endian ABGR becomes
RGBA. */
_src = vld4_u8 ((const uint8_t *) src);
_dst = vld4_u8 ((const uint8_t *) dst);
/* Load constants. */
v4 = vdupq_n_u16 (256);
v5 = vdup_n_u8 (0);
/* Load src alpha. */
alpha = _src.val[3];
/* alpha_c16 = 256 - 255TO256 (alpha). */
alpha_c16 = sfntfont_android_u255to256 (alpha);
alpha_c16 = vsubq_u16 (v4, alpha_c16);
/* Cout = Csrc + Cdst * alpha_c. */
v1 = vaddl_u8 (_dst.val[2], v5);
v2 = _src.val[2];
v3 = vmulq_u16 (v1, alpha_c16);
b = vqadd_u8 (v2, vshrn_n_u16 (v3, 8));
v1 = vaddl_u8 (_dst.val[1], v5);
v2 = _src.val[1];
v3 = vmulq_u16 (v1, alpha_c16);
g = vqadd_u8 (v2, vshrn_n_u16 (v3, 8));
v1 = vaddl_u8 (_dst.val[0], v5);
v2 = _src.val[0];
v3 = vmulq_u16 (v1, alpha_c16);
r = vqadd_u8 (v2, vshrn_n_u16 (v3, 8));
#if 0
/* Aout = Asrc + Adst * alpha_c. */
v1 = vaddl_u8 (_dst.val[3], v5);
v2 = _src.val[3];
v3 = vmulq_u16 (v1, alpha_c16);
a = vqadd_u8 (v2, vshrn_n_u16 (v3, 8));
#else
/* We know that Adst is always 1, so Asrc + Adst * (1 - Asrc) is
always 1. */
a = vdup_n_u8 (255);
#endif
/* Store back in dst. */
_dst.val[0] = r;
_dst.val[1] = g;
_dst.val[2] = b;
_dst.val[3] = a;
vst4_u8 ((uint8_t *) dst, _dst);
}
/* Use processor features to efficiently composite the buffer at SRC
to DST. Composite at most MAX - SRC words.
If either SRC or DST are not yet properly aligned, value is 1.
Otherwise, value is 0, and *X is incremented to the start of any
trailing data which could not be composited due to data alignment
constraints. */
static int
sfntfont_android_over_8888 (unsigned int *src, unsigned int *dst,
unsigned int *max, unsigned int *x)
{
size_t i;
ptrdiff_t how_much;
void *s, *d;
/* Figure out how much can be composited by this loop. */
how_much = (max - src) & ~7;
/* Return if there is not enough to vectorize. */
if (!how_much)
return 1;
/* Now increment *X by that much so the containing loop can process
the remaining pixels one-by-one. */
*x += how_much;
for (i = 0; i < how_much; i += 8)
{
s = (src + i);
d = (dst + i);
sfntfont_android_over_8888_1 (s, d);
}
return 0;
}
#endif
/* Composite the bitmap described by BUFFER, STRIDE and TEXT_RECTANGLE
onto the native-endian ABGR8888 bitmap described by DEST and
BITMAP_INFO. RECT is the subset of the bitmap to composite. */
static void
sfntfont_android_composite_bitmap (unsigned char *restrict buffer,
size_t stride,
unsigned char *restrict dest,
AndroidBitmapInfo *bitmap_info,
struct android_rectangle *text_rectangle,
struct android_rectangle *rect)
{
unsigned int *src_row;
unsigned int *dst_row;
unsigned int i, src_y, x, src_x, max_x, dst_x;
#ifdef __aarch64__
unsigned int lim_x;
#endif
if ((intptr_t) dest & 3 || bitmap_info->stride & 3)
/* This shouldn't be possible as Android is supposed to align the
bitmap to at least a 4 byte boundary. */
emacs_abort ();
else
{
for (i = 0; i < rect->height; ++i)
{
if (i + rect->y >= bitmap_info->height)
/* Done. */
return;
src_y = i + (rect->y - text_rectangle->y);
if (src_y > text_rectangle->height)
/* Huh? */
return;
src_row = (unsigned int *) ((buffer + src_y * stride));
dst_row = (unsigned int *) (dest + ((i + rect->y)
* bitmap_info->stride));
/* Figure out where the loop below should end. */
max_x = min (rect->width, bitmap_info->width - rect->x);
/* Keep this loop simple! */
for (x = 0; x < max_x; ++x)
{
src_x = x + (rect->x - text_rectangle->x);
dst_x = x + rect->x;
#ifdef __aarch64__
/* This is the largest value of src_x. */
lim_x = max_x + (rect->x - text_rectangle->x);
if (!sfntfont_android_over_8888 (src_row + src_x,
dst_row + dst_x,
src_row + lim_x,
&x))
{
/* Decrement X by one so the for loop can increment
it again. */
x--;
continue;
}
#endif
dst_row[dst_x]
= sfntfont_android_blend (src_row[src_x],
dst_row[dst_x]);
}
}
}
}
/* Calculate the union containing both A and B, both boxes. Place the
result in RESULT. */
static void
sfntfont_android_union_boxes (struct gui_box a, struct gui_box b,
struct gui_box *result)
{
result->x1 = min (a.x1, b.x1);
result->y1 = min (a.y1, b.y1);
result->x2 = max (a.x2, b.x2);
result->y2 = max (a.y2, b.y2);
}
/* Draw the specified glyph rasters from FROM to TO on behalf of S,
using S->gc. Fill the background if WITH_BACKGROUND is true.
See init_sfntfont_vendor and sfntfont_draw for more details. */
static void
sfntfont_android_put_glyphs (struct glyph_string *s, int from,
int to, int x, int y, bool with_background,
struct sfnt_raster **rasters,
int *x_coords)
{
struct android_rectangle background, text_rectangle, rect;
struct gui_box text, character;
unsigned int *buffer, *row;
unsigned char *restrict raster_row;
size_t stride, i;
AndroidBitmapInfo bitmap_info;
unsigned char *bitmap_data;
jobject bitmap;
int left, top, temp_y;
unsigned int prod, raster_y;
unsigned long foreground, back_pixel, rb;
if (!s->gc->num_clip_rects)
/* Clip region is empty. */
return;
if (from == to)
/* Nothing to draw. */
return;
/* Swizzle the foreground and background in s->gc into BGR, then add
an alpha channel. */
foreground = s->gc->foreground;
back_pixel = s->gc->background;
rb = foreground & 0x00ff00ff;
foreground &= ~0x00ff00ff;
foreground |= rb >> 16 | rb << 16 | 0xff000000;
rb = back_pixel & 0x00ff00ff;
back_pixel &= ~0x00ff00ff;
back_pixel |= rb >> 16 | rb << 16 | 0xff000000;
prepare_face_for_display (s->f, s->face);
/* Build the scanline buffer. Figure out the bounds of the
background. */
memset (&background, 0, sizeof background);
if (with_background)
{
background.x = x;
background.y = y - FONT_BASE (s->font);
background.width = s->width;
background.height = FONT_HEIGHT (s->font);
}
/* Now figure out the bounds of the text. */
if (rasters[0])
{
text.x1 = x_coords[0] + rasters[0]->offx;
text.x2 = text.x1 + rasters[0]->width;
text.y1 = y - rasters[0]->height - rasters[0]->offy;
text.y2 = y - rasters[0]->offy;
}
else
memset (&text, 0, sizeof text);
for (i = 1; i < to - from; ++i)
{
/* See if text has to be extended. */
if (!rasters[i])
continue;
character.x1 = x_coords[i] + rasters[i]->offx;
character.x2 = character.x1 + rasters[i]->width;
character.y1 = y - rasters[i]->height - rasters[i]->offy;
character.y2 = y - rasters[i]->offy;
sfntfont_android_union_boxes (text, character, &text);
}
/* Union the background rect with the text rectangle. */
text_rectangle.x = text.x1;
text_rectangle.y = text.y1;
text_rectangle.width = text.x2 - text.x1;
text_rectangle.height = text.y2 - text.y1;
gui_union_rectangles (&background, &text_rectangle,
&text_rectangle);
/* Allocate enough to hold text_rectangle.height, aligned to 8 (or
16) bytes. Then fill it with the background. */
#ifndef __aarch64__
stride = ((text_rectangle.width * sizeof *buffer) + 7) & ~7;
#else
stride = ((text_rectangle.width * sizeof *buffer) + 15) & ~15;
#endif
GET_SCANLINE_BUFFER (buffer, text_rectangle.height, stride);
/* Try to optimize out this memset if the background rectangle
contains the whole text rectangle. */
if (!with_background || memcmp (&background, &text_rectangle,
sizeof text_rectangle))
memset (buffer, 0, text_rectangle.height * stride);
if (with_background)
{
/* The background should have been filled in advance if a stipple
is enabled. */
eassert (s->gc->fill_style != ANDROID_FILL_OPAQUE_STIPPLED);
/* Fill the background. First, offset the background rectangle
to become relative from text_rectangle.x,
text_rectangle.y. */
background.x = background.x - text_rectangle.x;
background.y = background.y - text_rectangle.y;
eassert (background.x >= 0 && background.y >= 0);
for (temp_y = background.y; (temp_y
< (background.y
+ background.height));
++temp_y)
{
row = (unsigned int *) ((unsigned char *) buffer
+ stride * temp_y);
for (x = background.x; x < background.x + background.width; ++x)
row[x] = back_pixel;
}
}
/* Draw all the rasters onto the buffer. */
for (i = 0; i < to - from; ++i)
{
if (!rasters[i])
continue;
/* Figure out the top and left of the raster relative to
text_rectangle. */
left = x_coords[i] + rasters[i]->offx - text_rectangle.x;
/* Note that negative offy represents the part of the text that
lies below the baseline. */
top = (y - (rasters[i]->height + rasters[i]->offy)
- text_rectangle.y);
eassert (left >= 0 && top >= 0);
/* Draw the raster onto the temporary bitmap using the
foreground color scaled by the alpha map. */
for (raster_y = 0; raster_y < rasters[i]->height; ++raster_y)
{
row = (unsigned int *) ((unsigned char *) buffer
+ stride * (raster_y + top));
raster_row = &rasters[i]->cells[raster_y * rasters[i]->stride];
for (x = 0; x < rasters[i]->width; ++x)
{
prod
= sfntfont_android_scale32 (U255TO256 (raster_row[x]),
foreground);
row[left + x]
= sfntfont_android_blend (prod, row[left + x]);
}
}
}
/* Lock the bitmap. It must be unlocked later. */
bitmap_data = android_lock_bitmap (FRAME_ANDROID_DRAWABLE (s->f),
&bitmap_info, &bitmap);
/* If locking the bitmap fails, just discard the data that was
allocated. */
if (!bitmap_data)
return;
/* Loop over each clip rect in the GC. */
eassert (bitmap_info.format == ANDROID_BITMAP_FORMAT_RGBA_8888);
if (s->gc->num_clip_rects > 0)
{
for (i = 0; i < s->gc->num_clip_rects; ++i)
{
if (!gui_intersect_rectangles (&s->gc->clip_rects[i],
&text_rectangle, &rect))
/* Outside the clip region. */
continue;
/* Composite the intersection onto the buffer. */
sfntfont_android_composite_bitmap ((unsigned char *) buffer,
stride, bitmap_data,
&bitmap_info,
&text_rectangle, &rect);
}
}
else /* gc->num_clip_rects < 0 */
sfntfont_android_composite_bitmap ((unsigned char *) buffer,
stride, bitmap_data,
&bitmap_info,
&text_rectangle,
&text_rectangle);
/* Release the bitmap. */
AndroidBitmap_unlockPixels (android_java_env, bitmap);
ANDROID_DELETE_LOCAL_REF (bitmap);
/* Damage the window by the text rectangle. */
android_damage_window (FRAME_ANDROID_DRAWABLE (s->f),
&text_rectangle);
#undef MAX_ALLOCA
}
/* Shrink the scanline buffer after a window update. If
max_scanline_buffer_size is not zero, and is less than
scanline_buffer.buffer_size / 2, then resize the scanline buffer to
max_scanline_buffer_size. */
void
sfntfont_android_shrink_scanline_buffer (void)
{
if (!max_scanline_buffer_size)
return;
if (max_scanline_buffer_size
< scanline_buffer.buffer_size / 2)
{
scanline_buffer.buffer_size
= max_scanline_buffer_size;
scanline_buffer.buffer_data
= xrealloc (scanline_buffer.buffer_data,
max_scanline_buffer_size);
}
max_scanline_buffer_size = 0;
}
/* Font driver definition. */
/* Return the font cache for this font driver. F is ignored. */
static Lisp_Object
sfntfont_android_get_cache (struct frame *f)
{
return font_cache;
}
/* The Android sfntfont driver. */
const struct font_driver android_sfntfont_driver =
{
.type = LISPSYM_INITIALLY (Qsfnt_android),
.case_sensitive = true,
.get_cache = sfntfont_android_get_cache,
.list = sfntfont_list,
.match = sfntfont_match,
.draw = sfntfont_draw,
.open_font = sfntfont_open,
.close_font = sfntfont_close,
.encode_char = sfntfont_encode_char,
.text_extents = sfntfont_text_extents,
.list_family = sfntfont_list_family,
.get_variation_glyphs = sfntfont_get_variation_glyphs,
#ifdef HAVE_HARFBUZZ
/* HarfBuzz support is enabled transparently on Android without
using a separate font driver. */
.begin_hb_font = sfntfont_begin_hb_font,
.combining_capability = hbfont_combining_capability,
.shape = hbfont_shape,
.otf_capability = hbfont_otf_capability,
#endif /* HAVE_HARFBUZZ */
};
/* This is an ugly hack that should go away, but I can't think of
how. */
DEFUN ("android-enumerate-fonts", Fandroid_enumerate_fonts,
Sandroid_enumerate_fonts, 0, 0, 0,
doc: /* Enumerate fonts present on the system.
Signal an error if fonts have already been enumerated. This would
normally have been done in C, but reading fonts require Lisp to be
loaded before character sets are made available. */)
(void)
{
DIR *dir;
int i;
struct dirent *dirent;
char name[PATH_MAX * 2];
static bool enumerated;
if (enumerated)
error ("Fonts have already been enumerated");
enumerated = true;
block_input ();
/* Scan through each of the system font directories. Enumerate each
font that looks like a TrueType font. */
for (i = 0; i < ARRAYELTS (system_font_directories); ++i)
{
dir = opendir (system_font_directories[i]);
__android_log_print (ANDROID_LOG_VERBOSE, __func__,
"Loading fonts from: %s",
system_font_directories[i]);
if (!dir)
continue;
while ((dirent = readdir (dir)))
{
/* If it contains (not ends with!) with .ttf or .ttc, then
enumerate it. */
if ((strstr (dirent->d_name, ".ttf")
|| strstr (dirent->d_name, ".ttc"))
/* Ignore the non-variable Roboto font. */
&& (i != 0 || strcmp (dirent->d_name,
"RobotoStatic-Regular.ttf")))
{
sprintf (name, "%s/%s", system_font_directories[i],
dirent->d_name);
sfnt_enum_font (name);
}
}
closedir (dir);
}
unblock_input ();
return Qnil;
}
static void
syms_of_sfntfont_android_for_pdumper (void)
{
init_sfntfont_vendor (Qsfnt_android, &android_sfntfont_driver,
sfntfont_android_put_glyphs);
register_font_driver (&android_sfntfont_driver, NULL);
}
void
init_sfntfont_android (void)
{
int api_level;
if (!android_init_gui)
return;
api_level = android_get_current_api_level ();
/* Make sure to pick the proper Sans Serif and Serif fonts for the
version of Android the device is running. */
if (api_level >= 21)
/* Android 5.0 and later distribute Noto Serif in lieu of Droid
Serif. */
Vsfnt_default_family_alist
= list4 (Fcons (build_string ("Monospace"),
build_string ("Droid Sans Mono")),
/* Android doesn't come with a Monospace Serif font, so
this will have to do. */
Fcons (build_string ("Monospace Serif"),
build_string ("Droid Sans Mono")),
Fcons (build_string ("Sans Serif"),
build_string ("Roboto")),
Fcons (build_string ("DejaVu Serif"),
build_string ("Noto Serif")));
else if (api_level >= 14)
/* Android 4.0 and later distribute Roboto in lieu of Droid
Sans. */
Vsfnt_default_family_alist
= list4 (Fcons (build_string ("Monospace"),
build_string ("Droid Sans Mono")),
/* Android doesn't come with a Monospace Serif font, so
this will have to do. */
Fcons (build_string ("Monospace Serif"),
build_string ("Droid Sans Mono")),
Fcons (build_string ("Sans Serif"),
build_string ("Roboto")),
Fcons (build_string ("DejaVu Serif"),
build_string ("Droid Serif")));
else
Vsfnt_default_family_alist
= list4 (Fcons (build_string ("Monospace"),
build_string ("Droid Sans Mono")),
Fcons (build_string ("Monospace Serif"),
build_string ("Droid Sans Mono")),
Fcons (build_string ("Sans Serif"),
build_string ("Droid Sans")),
Fcons (build_string ("DejaVu Serif"),
build_string ("Droid Serif")));
/* Set up the user fonts directory. This directory is ``fonts'' in
the Emacs files directory. */
snprintf (system_font_directories[2], PATH_MAX, "%s/fonts",
android_get_home_directory ());
}
void
syms_of_sfntfont_android (void)
{
DEFSYM (Qsfnt_android, "sfnt-android");
DEFSYM (Qandroid_enumerate_fonts, "android-enumerate-fonts");
Fput (Qandroid, Qfont_driver_superseded_by, Qsfnt_android);
font_cache = list (Qnil);
staticpro (&font_cache);
defsubr (&Sandroid_enumerate_fonts);
pdumper_do_now_and_after_load (syms_of_sfntfont_android_for_pdumper);
}