From b9c191d690fd5d1480858469df23cc4509996fae Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 23 Apr 2024 14:30:38 +0800 Subject: [PATCH] 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) : New field. (markDirty): Synchronize the current stipple with tileObject. (prepareStipple, blitOpaqueStipple): New functions. * java/org/gnu/emacs/EmacsService.java (EmacsService) : 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. --- .gitignore | 2 +- java/Makefile.in | 9 ++- java/org/gnu/emacs/EmacsFillRectangle.java | 15 ++-- java/org/gnu/emacs/EmacsGC.java | 94 +++++++++++++++++++++- java/org/gnu/emacs/EmacsService.java | 7 +- src/android.c | 22 +---- src/dispextern.h | 19 +++-- src/epaths.in | 2 +- src/image.c | 2 +- src/sfntfont-android.c | 4 + src/xfaces.c | 24 ++---- 11 files changed, 139 insertions(+), 61 deletions(-) diff --git a/.gitignore b/.gitignore index 4098e2210b5..1557c085fad 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/java/Makefile.in b/java/Makefile.in index abddae6b5cf..35d2637837c 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -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 $@ diff --git a/java/org/gnu/emacs/EmacsFillRectangle.java b/java/org/gnu/emacs/EmacsFillRectangle.java index ca87c06c014..f338a54f97b 100644 --- a/java/org/gnu/emacs/EmacsFillRectangle.java +++ b/java/org/gnu/emacs/EmacsFillRectangle.java @@ -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); } -} +}; diff --git a/java/org/gnu/emacs/EmacsGC.java b/java/org/gnu/emacs/EmacsGC.java index e45f0666fe2..96df0c61ca6 100644 --- a/java/org/gnu/emacs/EmacsGC.java +++ b/java/org/gnu/emacs/EmacsGC.java @@ -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 (); } }; diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 2d4079c11b0..8e459ce4cdc 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -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) diff --git a/src/android.c b/src/android.c index 7a7eadc946a..e44b58c5973 100644 --- a/src/android.c +++ b/src/android.c @@ -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 (); } diff --git a/src/dispextern.h b/src/dispextern.h index f29377f3596..c3c2d61082b 100644 --- a/src/dispextern.h +++ b/src/dispextern.h @@ -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 diff --git a/src/epaths.in b/src/epaths.in index 275d13985aa..8415ce51586 100644 --- a/src/epaths.in +++ b/src/epaths.in @@ -95,7 +95,7 @@ along with GNU Emacs. If not, see . */ # 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; diff --git a/src/image.c b/src/image.c index 3968145728f..3028c2e707a 100644 --- a/src/image.c +++ b/src/image.c @@ -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) { diff --git a/src/sfntfont-android.c b/src/sfntfont-android.c index 1ed394b9458..b90ca857dd4 100644 --- a/src/sfntfont-android.c +++ b/src/sfntfont-android.c @@ -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. */ diff --git a/src/xfaces.c b/src/xfaces.c index d4583e1a78f..d307dbaa246 100644 --- a/src/xfaces.c +++ b/src/xfaces.c @@ -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);