Implement face stipples on Android

* .gitignore:

* java/Makefile.in: Fix typos.

* java/org/gnu/emacs/EmacsFillRectangle.java (perform): Call
blitOpaqueStipple if filling an unobscured rectangle with an
opaque stipple.

* java/org/gnu/emacs/EmacsGC.java (EmacsGC) <tileObject>: New
field.
(markDirty): Synchronize the current stipple with tileObject.
(prepareStipple, blitOpaqueStipple): New functions.

* java/org/gnu/emacs/EmacsService.java (EmacsService)
<resources>: New static field.
(onCreate): Set it.

* src/android.c (android_create_bitmap_from_data): Correct order
of arguments to android_create_pixmap_from_bitmap_data.
(HAS_BUILTIN_TRAP): Delete macro.
(emacs_abort): Always induce backtraces by means of a NULL
pointer deference.

* src/dispextern.h (Emacs_GC, Emacs_Rectangle, GCForeground)
(GCBackground, GCFillStyle, GCStipple, FillOpaqueStipple)
[HAVE_ANDROID]: Define to their Android counterparts rather
than simulating their existence.

* src/epaths.in: Set bitmap path to /assets/bitmaps on Android.

* src/image.c (image_bitmap_pixmap): Also enable when
HAVE_ANDROID.

* src/sfntfont-android.c (sfntfont_android_put_glyphs): Assert
that this is never called to draw a stippled background.
* src/xfaces.c (x_create_gc) [HAVE_ANDROID]: Redefine as
wrapper around android_create_gc.
(prepare_face_for_display) [HAVE_ANDROID]: Enable stipples.
scratch/lisp-func-type-decls
Po Lu 2024-04-23 14:30:38 +08:00
parent 63765a74f1
commit b9c191d690
11 changed files with 139 additions and 61 deletions

2
.gitignore vendored
View File

@ -68,7 +68,7 @@ java/org/gnu/emacs/R.java
# Built by `make'.
java/org/gnu/emacs/EmacsConfig.java
java/org/gnu/emacs/cf-stamp
java/cf-stamp
# Built by `config.status'.
java/AndroidManifest.xml

View File

@ -197,10 +197,13 @@ install_temp: $(CROSS_BINS) $(CROSS_LIBS) $(RESOURCE_FILES)
$(AM_V_SILENT) mkdir -p install_temp/assets/etc
$(AM_V_SILENT) mkdir -p install_temp/assets/lisp
$(AM_V_SILENT) mkdir -p install_temp/assets/info
# Install architecture independents to assets/etc and assets/lisp
$(AM_V_SILENT) mkdir -p install_temp/assets/bitmaps
# Install architecture independents to assets/etc, assets/lisp and
# assets/bitmaps
$(AM_V_SILENT) cp -r $(top_srcdir)/lisp install_temp/assets
$(AM_V_SILENT) cp -r $(top_srcdir)/etc install_temp/assets
$(AM_V_SILENT) cp -r $(top_srcdir)/info install_temp/assets
$(AM_V_SILENT) cp -r $(top_srcdir)/src/bitmaps install_temp/assets
# Replace etc/DOC generated by compiling Emacs for the build machine
# with etc/DOC from the cross-compiled Emacs.
$(AM_V_SILENT) test -f $(top_builddir)/cross/etc/DOC \
@ -354,8 +357,8 @@ public static final String[] EMACS_SHARED_LIBRARIES\
# cf-stamp-1 is a phony target invoked in a second `make' instance after
# all shared libraries are compiled, because the computation of
# ALL_DEPENDENCIES cannot be postponed until that stage in this instance
# of Make.
# ALL_DEPENDENCIES in this instance of Make cannot be postponed until
# that stage.
cf-stamp: $(NDK_BUILD_SHARED) $(CROSS_LIBS)
$(AM_V_EMACSCONFIG) $(MAKE) cf-stamp-1
$(AM_V_at) touch $@

View File

@ -40,22 +40,23 @@ public final class EmacsFillRectangle
Canvas canvas;
Bitmap clipBitmap;
/* TODO implement stippling. */
if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED)
return;
canvas = drawable.lockCanvas (gc);
if (canvas == null)
return;
paint = gc.gcPaint;
rect = new Rect (x, y, x + width, y + height);
paint = gc.gcPaint;
paint.setStyle (Paint.Style.FILL);
if (gc.clip_mask == null)
canvas.drawRect (rect, paint);
{
if (gc.fill_style != EmacsGC.GC_FILL_OPAQUE_STIPPLED)
canvas.drawRect (rect, paint);
else
gc.blitOpaqueStipple (canvas, rect);
}
else
{
/* Drawing with a clip mask involves calculating the
@ -113,4 +114,4 @@ public final class EmacsFillRectangle
drawable.damageRect (rect);
}
}
};

View File

@ -22,10 +22,19 @@ package org.gnu.emacs;
import android.graphics.Rect;
import android.graphics.Paint;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter;
import android.graphics.PorterDuffXfermode;
import android.graphics.Shader.TileMode;
import android.graphics.Xfermode;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
/* X like graphics context structures. Keep the enums in synch with
androidgui.h! */
@ -47,6 +56,9 @@ public final class EmacsGC extends EmacsHandleObject
public EmacsPixmap clip_mask, stipple;
public Paint gcPaint;
/* Drawable object for rendering the stiple bitmap. */
public BitmapDrawable tileObject;
/* ID incremented every time the clipping rectangles of any GC
changes. */
private static long clip_serial;
@ -86,6 +98,7 @@ public final class EmacsGC extends EmacsHandleObject
markDirty (boolean clipRectsChanged)
{
int i;
Bitmap stippleBitmap;
if (clipRectsChanged)
{
@ -110,12 +123,85 @@ public final class EmacsGC extends EmacsHandleObject
gcPaint.setColor (foreground | 0xff000000);
gcPaint.setXfermode (function == GC_XOR
? xorAlu : srcInAlu);
/* Update the stipple object with the new stipple bitmap, or delete
it if the stipple has been cleared on systems too old to support
modifying such objects. */
if (stipple != null)
{
stippleBitmap = stipple.getBitmap ();
/* Allocate a new tile object if none is already present or it
cannot be reconfigured. */
if ((tileObject == null)
|| (Build.VERSION.SDK_INT < Build.VERSION_CODES.S))
{
tileObject = new BitmapDrawable (EmacsService.resources,
stippleBitmap);
tileObject.setTileModeXY (TileMode.MIRROR, TileMode.MIRROR);
}
else
/* Otherwise, update the existing tile object with the new
bitmap. */
tileObject.setBitmap (stippleBitmap);
}
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
&& tileObject != null)
tileObject.setBitmap (null);
else if (tileObject != null)
tileObject = null;
}
public void
resetXfermode ()
/* Prepare the tile object to draw a stippled image onto a section of
a drawable defined by RECT. It is an error to call this function
unless the `stipple' field of the GContext is set. */
private void
prepareStipple (Rect rect)
{
gcPaint.setXfermode (function == GC_XOR
? xorAlu : srcInAlu);
int sx, sy; /* Stipple origin. */
int bw, bh; /* Stipple size. */
Bitmap bitmap;
Rect boundsRect;
/* Retrieve the dimensions of the stipple bitmap, which doubles as
the unit of advance for this stipple. */
bitmap = tileObject.getBitmap ();
bw = bitmap.getWidth ();
bh = bitmap.getHeight ();
/* Align the lower left corner of the bounds rectangle to the
initial position of the stipple. */
sx = (rect.left % bw) * -1 + (-ts_origin_x % bw) * -1;
sy = (rect.top % bh) * -1 + (-ts_origin_y % bh) * -1;
boundsRect = new Rect (rect.left + sx, rect.top + sy,
rect.right, rect.bottom);
tileObject.setBounds (boundsRect);
}
/* Fill the rectangle BOUNDS in the provided CANVAS with the stipple
pattern defined for this GContext, in the foreground color where
the pattern is on, and in the background color where off. */
protected void
blitOpaqueStipple (Canvas canvas, Rect rect)
{
ColorFilter filter;
prepareStipple (rect);
filter = new PorterDuffColorFilter (foreground | 0xff000000,
Mode.SRC_IN);
tileObject.setColorFilter (filter);
canvas.save ();
canvas.clipRect (rect);
tileObject.draw (canvas);
filter = new PorterDuffColorFilter (background | 0xff000000,
Mode.SRC_OUT);
tileObject.setColorFilter (filter);
tileObject.draw (canvas);
canvas.restore ();
}
};

View File

@ -64,6 +64,7 @@ import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.hardware.input.InputManager;
@ -146,6 +147,9 @@ public final class EmacsService extends Service
thread. */
private Thread mainThread;
/* "Resources" object required by GContext bookkeeping. */
public static Resources resources;
static
{
servicingQuery = new AtomicInteger ();
@ -238,10 +242,11 @@ public final class EmacsService extends Service
super.onCreate ();
SERVICE = this;
resources = getResources ();
handler = new Handler (Looper.getMainLooper ());
manager = getAssets ();
app_context = getApplicationContext ();
metrics = getResources ().getDisplayMetrics ();
metrics = resources.getDisplayMetrics ();
pixelDensityX = metrics.xdpi;
pixelDensityY = metrics.ydpi;
tempScaledDensity = ((getScaledDensity (metrics)

View File

@ -4882,8 +4882,8 @@ android_pixmap
android_create_bitmap_from_data (char *bits, unsigned int width,
unsigned int height)
{
return android_create_pixmap_from_bitmap_data (bits, 1, 0,
width, height, 1);
return android_create_pixmap_from_bitmap_data (bits, width, height,
1, 0, 1);
}
struct android_image *
@ -5994,40 +5994,22 @@ android_toggle_on_screen_keyboard (android_window window, bool show)
#if defined __clang_major__ && __clang_major__ < 5
# define HAS_BUILTIN_TRAP 0
#elif 3 < __GNUC__ + (3 < __GNUC_MINOR__ + (4 <= __GNUC_PATCHLEVEL__))
# define HAS_BUILTIN_TRAP 1
#elif defined __has_builtin
# define HAS_BUILTIN_TRAP __has_builtin (__builtin_trap)
#else /* !__has_builtin */
# define HAS_BUILTIN_TRAP 0
#endif /* defined __clang_major__ && __clang_major__ < 5 */
/* emacs_abort implementation for Android. This logs a stack
trace. */
void
emacs_abort (void)
{
#ifndef HAS_BUILTIN_TRAP
volatile char *foo;
#endif /* !HAS_BUILTIN_TRAP */
__android_log_print (ANDROID_LOG_FATAL, __func__,
"emacs_abort called, please review the following"
" stack trace");
#ifndef HAS_BUILTIN_TRAP
/* Induce a NULL pointer dereference to make debuggerd generate a
tombstone. */
foo = NULL;
*foo = '\0';
#else /* HAS_BUILTIN_TRAP */
/* Crash through __builtin_trap instead. This appears to more
uniformly elicit crash reports from debuggerd. */
__builtin_trap ();
#endif /* !HAS_BUILTIN_TRAP */
abort ();
}

View File

@ -69,12 +69,6 @@ typedef struct
unsigned width, height;
} Emacs_Rectangle;
#else
typedef struct android_rectangle Emacs_Rectangle;
#endif
/* XGCValues-like struct used by non-X GUI code. */
typedef struct
{
@ -88,6 +82,19 @@ typedef struct
#define GCForeground 0x01
#define GCBackground 0x02
#else
typedef struct android_rectangle Emacs_Rectangle;
typedef struct android_gc_values Emacs_GC;
#define GCForeground ANDROID_GC_FOREGROUND
#define GCBackground ANDROID_GC_BACKGROUND
#define GCFillStyle ANDROID_GC_FILL_STYLE
#define GCStipple ANDROID_GC_STIPPLE
#define FillOpaqueStippled ANDROID_FILL_OPAQUE_STIPPLED
#endif
#endif /* HAVE_X_WINDOWS */
#ifdef MSDOS

View File

@ -95,7 +95,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
# define PATH_DOC "/assets/etc/"
# define PATH_INFO "/assets/info/"
# define PATH_GAME ""
# define PATH_BITMAPS ""
# define PATH_BITMAPS "/assets/bitmaps/"
extern char *android_site_load_path;
extern char *android_lib_dir;

View File

@ -419,7 +419,7 @@ x_bitmap_stipple (struct frame *f, Pixmap pixmap)
#endif /* USE_CAIRO */
#endif
#if defined (HAVE_X_WINDOWS) || defined (HAVE_NTGUI)
#if defined (HAVE_X_WINDOWS) || defined (HAVE_NTGUI) || defined (HAVE_ANDROID)
ptrdiff_t
image_bitmap_pixmap (struct frame *f, ptrdiff_t id)
{

View File

@ -503,6 +503,10 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from,
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. */

View File

@ -619,21 +619,7 @@ static struct android_gc *
x_create_gc (struct frame *f, unsigned long value_mask,
Emacs_GC *xgcv)
{
struct android_gc_values gcv;
unsigned long mask;
gcv.foreground = xgcv->foreground;
gcv.background = xgcv->background;
mask = 0;
if (value_mask & GCForeground)
mask |= ANDROID_GC_FOREGROUND;
if (value_mask & GCBackground)
mask |= ANDROID_GC_BACKGROUND;
return android_create_gc (mask, &gcv);
return android_create_gc (value_mask, xgcv);
}
static void
@ -4630,14 +4616,18 @@ prepare_face_for_display (struct frame *f, struct face *face)
#endif
block_input ();
#ifdef HAVE_X_WINDOWS
#if defined HAVE_X_WINDOWS || defined HAVE_ANDROID
if (face->stipple)
{
egc.fill_style = FillOpaqueStippled;
#ifndef ANDROID_STUBIFY
egc.stipple = image_bitmap_pixmap (f, face->stipple);
#else /* !ANDROID_STUBIFY */
emacs_abort ();
#endif /* !ANDROID_STUBIFY */
mask |= GCFillStyle | GCStipple;
}
#endif
#endif /* HAVE_X_WINDOWS || HAVE_ANDROID */
face->gc = x_create_gc (f, mask, &egc);
if (face->font)
font_prepare_for_face (f, face);