From 1b8258a1f2b6a080a4f0e819aa4a86c1ec2da89f Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 17 Jan 2023 22:10:43 +0800 Subject: [PATCH] Update Android port * doc/emacs/android.texi (Android Fonts): Document that TTC format fonts are now supported. * doc/emacs/emacs.texi (Top): Fix menus. * doc/lispref/commands.texi (Touchscreen Events) (Key Sequence Input): Document changes to touchscreen events. * etc/DEBUG: Describe how to debug 64 bit binaries on Android. * java/org/gnu/emacs/EmacsCopyArea.java (perform): Explicitly recycle copy bitmap. * java/org/gnu/emacs/EmacsDialog.java (EmacsDialog): New class. * java/org/gnu/emacs/EmacsDrawRectangle.java (perform): Use 5 point PolyLine like X, because Android behaves like Postscript on some devices and X elsewhere. * java/org/gnu/emacs/EmacsFillRectangle.java (perform): Explicitly recycle copy bitmap. * java/org/gnu/emacs/EmacsPixmap.java (destroyHandle): Explicitly recycle bitmap and GC if it is big. * java/org/gnu/emacs/EmacsView.java (EmacsView): Make `bitmapDirty' a boolean. (handleDirtyBitmap): Reimplement in terms of that boolean. Explicitly recycle old bitmap and GC. (onLayout): Fix lock up. (onDetachedFromWindow): Recycle bitmap and GC. * java/org/gnu/emacs/EmacsWindow.java (requestViewLayout): Update call to explicitlyDirtyBitmap. * src/android.c (android_run_select_thread, android_select): Really fix android_select. (android_build_jstring): New function. * src/android.h: Update prototypes. * src/androidmenu.c (android_process_events_for_menu): Totally unblock input before process_pending_signals. (android_menu_show): Remove redundant unblock_input and debugging code. (struct android_emacs_dialog, android_init_emacs_dialog) (android_dialog_show, android_popup_dialog, init_androidmenu): Implement popup dialogs on Android. * src/androidterm.c (android_update_tools) (handle_one_android_event, android_frame_up_to_date): Allow tapping tool bar items. (android_create_terminal): Add dialog hook. (android_wait_for_event): Adjust call to android_select. * src/androidterm.h (struct android_touch_point): New field `tool_bar_p'. * src/keyboard.c (read_key_sequence, head_table) (syms_of_keyboard): Prefix touchscreen events with posn. * src/keyboard.h (EVENT_HEAD): Handle touchscreen events. * src/process.c (wait_reading_process_output): Adjust call to android_select. * src/sfnt.c (sfnt_read_table_directory): If the first long turns out to be ttcf, return -1. (sfnt_read_ttc_header): New function. (main): Test TTC support. * src/sfnt.h (struct sfnt_ttc_header): New structure. (enum sfnt_ttc_tag): New enum. * src/sfntfont-android.c (struct sfntfont_android_scanline_buffer): New structure. (GET_SCANLINE_BUFFER): New macro. Try to avoid so much malloc upon accessing the scanline buffer. (sfntfont_android_put_glyphs): Do not use SAFE_ALLOCA to allocate the scaline buffer. (Fandroid_enumerate_fonts): Enumerate ttc fonts too. * src/sfntfont.c (struct sfnt_font_desc): New field `offset'. (sfnt_enum_font_1): Split out enumeration code from sfnt_enum_font. (sfnt_enum_font): Read TTC tables and enumerate each font therein. (sfntfont_open): Seek to the offset specified. * xcompile/Makefile.in (maintainer-clean): Fix depends here. --- doc/emacs/android.texi | 9 +- doc/emacs/emacs.texi | 2 + doc/lispref/commands.texi | 31 +- etc/DEBUG | 9 + java/org/gnu/emacs/EmacsCopyArea.java | 4 + java/org/gnu/emacs/EmacsDialog.java | 333 +++++++++++++++++++++ java/org/gnu/emacs/EmacsDrawRectangle.java | 32 +- java/org/gnu/emacs/EmacsFillRectangle.java | 3 + java/org/gnu/emacs/EmacsPixmap.java | 23 ++ java/org/gnu/emacs/EmacsView.java | 84 ++++-- java/org/gnu/emacs/EmacsWindow.java | 2 +- src/android.c | 62 ++-- src/android.h | 3 +- src/androidmenu.c | 240 ++++++++++++++- src/androidterm.c | 100 ++++++- src/androidterm.h | 7 + src/keyboard.c | 7 +- src/keyboard.h | 13 +- src/process.c | 2 +- src/sfnt.c | 167 ++++++++++- src/sfnt.h | 40 +++ src/sfntfont-android.c | 99 +++++- src/sfntfont.c | 113 +++++-- xcompile/Makefile.in | 2 +- 24 files changed, 1259 insertions(+), 128 deletions(-) create mode 100644 java/org/gnu/emacs/EmacsDialog.java diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index b5a91b0f98f..8806a2a2bf6 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -335,11 +335,10 @@ places fonts. Emacs assumes there will always be a font named ``Droid Sans Mono'', and then defaults to using this font. These fonts are then rendered by the @code{sfnt-android} font driver. -When running on Android, Emacs currently lacks support for TrueType -Container and OpenType fonts. This means that only a subset of the -fonts installed on the system are currently available to Emacs. If -you are interested in raising this limitation, please contact -@email{emacs-devel@@gnu.org}. +When running on Android, Emacs currently lacks support for OpenType +fonts. This means that only a subset of the fonts installed on the +system are currently available to Emacs. If you are interested in +lifting this limitation, please contact @email{emacs-devel@@gnu.org}. If the @code{sfnt-android} font driver fails to find any fonts at all, Emacs falls back to the @code{android} font driver. This is a very diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi index 19a99d1deb2..d21d09bf920 100644 --- a/doc/emacs/emacs.texi +++ b/doc/emacs/emacs.texi @@ -223,6 +223,7 @@ Appendices * Antinews:: Information about Emacs version 28. * Mac OS / GNUstep:: Using Emacs under macOS and GNUstep. * Haiku:: Using Emacs on Haiku. +* Android:: Using Emacs on Android. * Microsoft Windows:: Using Emacs on Microsoft Windows and MS-DOS. * Manifesto:: What's GNU? Gnu's Not Unix! @@ -1262,6 +1263,7 @@ Emacs and Android * Android Startup:: Starting up Emacs on Android. * Android File System:: The Android file system. * Android Environment:: Running Emacs under Android. +* Android Fonts:: Font selection under Android. Emacs and Microsoft Windows/MS-DOS diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index 14702ce6efa..6d1ce145fbf 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -1999,6 +1999,13 @@ each point is represented by a cons of an arbitrary number identifying the point and a mouse position list (@pxref{Click Events}) specifying the position of the finger when the event occurred. +In addition, @code{touchscreen-begin} events also have imaginary +prefixes keys added by @code{read-key-sequence} when they originate on +top of a special part of a frame or window. @xref{Key Sequence +Input}. The reason the other touch screen events do not undergo this +treatment is that they are rarely useful without being used in tandem +from their corresponding @code{touchscreen-begin} events. + @table @code @cindex @code{touchscreen-begin} event @item (touchscreen-begin @var{point}) @@ -3024,19 +3031,21 @@ with any other events. @cindex @code{right-divider}, prefix key @cindex @code{bottom-divider}, prefix key @cindex mouse events, in special parts of window or frame -When mouse events occur in special parts of a window or frame, such as a mode -line or a scroll bar, the event type shows nothing special---it is the -same symbol that would normally represent that combination of mouse -button and modifier keys. The information about the window part is kept -elsewhere in the event---in the coordinates. But -@code{read-key-sequence} translates this information into imaginary -prefix keys, all of which are symbols: @code{tab-line}, @code{header-line}, -@code{horizontal-scroll-bar}, @code{menu-bar}, @code{tab-bar}, @code{mode-line}, +@cindex touch screen events, in special parts of window or frame +When mouse or @code{touch-screen-begin} events occur in special parts +of a window or frame, such as a mode line or a scroll bar, the event +type shows nothing special---it is the same symbol that would normally +represent that combination of mouse button and modifier keys. The +information about the window part is kept elsewhere in the event---in +the coordinates. But @code{read-key-sequence} translates this +information into imaginary prefix keys, all of which are symbols: +@code{tab-line}, @code{header-line}, @code{horizontal-scroll-bar}, +@code{menu-bar}, @code{tab-bar}, @code{mode-line}, @code{vertical-line}, @code{vertical-scroll-bar}, @code{left-margin}, @code{right-margin}, @code{left-fringe}, @code{right-fringe}, -@code{right-divider}, and @code{bottom-divider}. You can define meanings for -mouse clicks in special window parts by defining key sequences using these -imaginary prefix keys. +@code{right-divider}, and @code{bottom-divider}. You can define +meanings for mouse clicks in special window parts by defining key +sequences using these imaginary prefix keys. For example, if you call @code{read-key-sequence} and then click the mouse on the window's mode line, you get two events, like this: diff --git a/etc/DEBUG b/etc/DEBUG index 124453939b1..1cc575598bf 100644 --- a/etc/DEBUG +++ b/etc/DEBUG @@ -1123,6 +1123,15 @@ then placing a breakpoint on: will let you find the source of the crash. +If there is no `gdbserver' binary present on the device, then you can +specify one to upload, like so: + + ../java/debug.sh --gdbserver /path/to/gdbserver + +In addition, when Emacs runs as a 64-bit process on a system +supporting both 64 and 32-bit binaries, you must specify the path to a +64-bit gdbserver binary. + This file is part of GNU Emacs. diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java index 00e817bb97d..5d72a7860c8 100644 --- a/java/org/gnu/emacs/EmacsCopyArea.java +++ b/java/org/gnu/emacs/EmacsCopyArea.java @@ -116,6 +116,7 @@ public class EmacsCopyArea src_x, src_y, width, height); canvas.drawBitmap (bitmap, null, rect, paint); + bitmap.recycle (); } else { @@ -183,6 +184,9 @@ public class EmacsCopyArea paint.setXfermode (overAlu); canvas.drawBitmap (maskBitmap, null, maskRect, paint); gc.resetXfermode (); + + /* Recycle this unused bitmap. */ + maskBitmap.recycle (); } canvas.restore (); diff --git a/java/org/gnu/emacs/EmacsDialog.java b/java/org/gnu/emacs/EmacsDialog.java new file mode 100644 index 00000000000..5bc8efa5978 --- /dev/null +++ b/java/org/gnu/emacs/EmacsDialog.java @@ -0,0 +1,333 @@ +/* Communication module for Android terminals. -*- c-file-style: "GNU" -*- + +Copyright (C) 2023 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 . */ + +package org.gnu.emacs; + +import java.util.List; +import java.util.ArrayList; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Context; +import android.util.Log; + +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.FrameLayout; + +import android.view.View; +import android.view.ViewGroup; + +/* Toolkit dialog implementation. This object is built from JNI and + describes a single alert dialog. Then, `inflate' turns it into + AlertDialog. */ + +public class EmacsDialog implements DialogInterface.OnDismissListener +{ + private static final String TAG = "EmacsDialog"; + + /* List of buttons in this dialog. */ + private List buttons; + + /* Dialog title. */ + private String title; + + /* Dialog text. */ + private String text; + + /* Whether or not a selection has already been made. */ + private boolean wasButtonClicked; + + /* Dialog to dismiss after click. */ + private AlertDialog dismissDialog; + + private class EmacsButton implements View.OnClickListener, + DialogInterface.OnClickListener + { + /* Name of this button. */ + public String name; + + /* ID of this button. */ + public int id; + + /* Whether or not the button is enabled. */ + public boolean enabled; + + @Override + public void + onClick (View view) + { + Log.d (TAG, "onClicked " + this); + + wasButtonClicked = true; + EmacsNative.sendContextMenu ((short) 0, id); + dismissDialog.dismiss (); + } + + @Override + public void + onClick (DialogInterface dialog, int which) + { + Log.d (TAG, "onClicked " + this); + + wasButtonClicked = true; + EmacsNative.sendContextMenu ((short) 0, id); + } + }; + + /* Create a popup dialog with the title TITLE and the text TEXT. + TITLE may be NULL. */ + + public static EmacsDialog + createDialog (String title, String text) + { + EmacsDialog dialog; + + dialog = new EmacsDialog (); + dialog.buttons = new ArrayList (); + dialog.title = title; + dialog.text = text; + + return dialog; + } + + /* Add a button named NAME, with the identifier ID. If DISABLE, + disable the button. */ + + public void + addButton (String name, int id, boolean disable) + { + EmacsButton button; + + button = new EmacsButton (); + button.name = name; + button.id = id; + button.enabled = !disable; + buttons.add (button); + } + + /* Turn this dialog into an AlertDialog for the specified + CONTEXT. + + Upon a button being selected, the dialog will send an + ANDROID_CONTEXT_MENU event with the id of that button. + + Upon the dialog being dismissed, an ANDROID_CONTEXT_MENU event + will be sent with an id of 0. */ + + public AlertDialog + toAlertDialog (Context context) + { + AlertDialog dialog; + int size; + EmacsButton button; + LinearLayout layout; + Button buttonView; + ViewGroup.LayoutParams layoutParams; + + size = buttons.size (); + + if (size <= 3) + { + dialog = new AlertDialog.Builder (context).create (); + dialog.setMessage (text); + dialog.setCancelable (true); + dialog.setOnDismissListener (this); + + if (title != null) + dialog.setTitle (title); + + /* There are less than 4 buttons. Add the buttons the way + Android intends them to be added. */ + + if (size >= 1) + { + button = buttons.get (0); + dialog.setButton (DialogInterface.BUTTON_POSITIVE, + button.name, button); + } + + if (size >= 2) + { + button = buttons.get (1); + dialog.setButton (DialogInterface.BUTTON_NEUTRAL, + button.name, button); + buttonView + = dialog.getButton (DialogInterface.BUTTON_NEUTRAL); + buttonView.setEnabled (button.enabled); + } + + if (size >= 3) + { + button = buttons.get (2); + dialog.setButton (DialogInterface.BUTTON_NEGATIVE, + button.name, button); + } + } + else + { + /* There are more than 4 buttons. Add them all to a + LinearLayout. */ + layout = new LinearLayout (context); + layoutParams + = new LinearLayout.LayoutParams (ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + + for (EmacsButton emacsButton : buttons) + { + buttonView = new Button (context); + buttonView.setText (emacsButton.name); + buttonView.setOnClickListener (emacsButton); + buttonView.setLayoutParams (layoutParams); + buttonView.setEnabled (emacsButton.enabled); + layout.addView (buttonView); + } + + layoutParams + = new FrameLayout.LayoutParams (ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + layout.setLayoutParams (layoutParams); + + /* Add that layout to the dialog's custom view. + + android.R.id.custom is documented to work. But looking it + up returns NULL, so setView must be used instead. */ + + dialog = new AlertDialog.Builder (context).setView (layout).create (); + dialog.setMessage (text); + dialog.setCancelable (true); + dialog.setOnDismissListener (this); + + if (title != null) + dialog.setTitle (title); + } + + return dialog; + } + + /* Internal helper for display run on the main thread. */ + + private boolean + display1 () + { + EmacsActivity activity; + int size; + Button buttonView; + EmacsButton button; + AlertDialog dialog; + + if (EmacsActivity.focusedActivities.isEmpty ()) + return false; + + activity = EmacsActivity.focusedActivities.get (0); + dialog = dismissDialog = toAlertDialog (activity); + dismissDialog.show (); + + /* If there are less than four buttons, then they must be + individually enabled or disabled after the dialog is + displayed. */ + size = buttons.size (); + + if (size <= 3) + { + if (size >= 1) + { + button = buttons.get (0); + buttonView + = dialog.getButton (DialogInterface.BUTTON_POSITIVE); + buttonView.setEnabled (button.enabled); + } + + if (size >= 2) + { + button = buttons.get (1); + dialog.setButton (DialogInterface.BUTTON_NEUTRAL, + button.name, button); + buttonView + = dialog.getButton (DialogInterface.BUTTON_NEUTRAL); + buttonView.setEnabled (button.enabled); + } + + if (size >= 3) + { + button = buttons.get (2); + buttonView + = dialog.getButton (DialogInterface.BUTTON_NEGATIVE); + buttonView.setEnabled (button.enabled); + } + } + + return true; + } + + /* Display this dialog for a suitable activity. + Value is false if the dialog could not be displayed, + and true otherwise. */ + + public boolean + display () + { + Runnable runnable; + final Holder rc; + + rc = new Holder (); + runnable = new Runnable () { + @Override + public void + run () + { + synchronized (this) + { + rc.thing = display1 (); + notify (); + } + } + }; + + synchronized (runnable) + { + EmacsService.SERVICE.runOnUiThread (runnable); + + try + { + runnable.wait (); + } + catch (InterruptedException e) + { + EmacsNative.emacsAbort (); + } + } + + return rc.thing; + } + + + + @Override + public void + onDismiss (DialogInterface dialog) + { + Log.d (TAG, "onDismiss: " + this); + + if (wasButtonClicked) + return; + + EmacsNative.sendContextMenu ((short) 0, 0); + } +}; diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java index b42e9556e8c..c29d413f66e 100644 --- a/java/org/gnu/emacs/EmacsDrawRectangle.java +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -36,10 +36,10 @@ public class EmacsDrawRectangle Paint maskPaint, paint; Canvas maskCanvas; Bitmap maskBitmap; - Rect rect; Rect maskRect, dstRect; Canvas canvas; Bitmap clipBitmap; + Rect clipRect; /* TODO implement stippling. */ if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) @@ -58,13 +58,29 @@ public class EmacsDrawRectangle canvas.clipRect (gc.real_clip_rects[i]); } - paint = gc.gcPaint; - rect = new Rect (x, y, x + width, y + height); + /* Clip to the clipRect because some versions of Android draw an + overly wide line. */ + clipRect = new Rect (x, y, x + width + 1, + y + height + 1); + canvas.clipRect (clipRect); - paint.setStyle (Paint.Style.STROKE); + paint = gc.gcPaint; if (gc.clip_mask == null) - canvas.drawRect (rect, paint); + { + /* canvas.drawRect just doesn't work on Android, producing + different results on various devices. Do a 5 point + PolyLine instead. */ + canvas.drawLine ((float) x, (float) y, (float) x + width, + (float) y, paint); + canvas.drawLine ((float) x + width, (float) y, + (float) x + width, (float) y + height, + paint); + canvas.drawLine ((float) x + width, (float) y + height, + (float) x, (float) y + height, paint); + canvas.drawLine ((float) x, (float) y + height, + (float) x, (float) y, paint); + } else { /* Drawing with a clip mask involves calculating the @@ -116,10 +132,12 @@ public class EmacsDrawRectangle /* Finally, draw the mask bitmap to the destination. */ paint.setXfermode (null); canvas.drawBitmap (maskBitmap, null, maskRect, paint); + + /* Recycle this unused bitmap. */ + maskBitmap.recycle (); } canvas.restore (); - drawable.damageRect (new Rect (x, y, x + width + 1, - y + height + 1)); + drawable.damageRect (clipRect); } } diff --git a/java/org/gnu/emacs/EmacsFillRectangle.java b/java/org/gnu/emacs/EmacsFillRectangle.java index b733b417d6b..7cc55d3db96 100644 --- a/java/org/gnu/emacs/EmacsFillRectangle.java +++ b/java/org/gnu/emacs/EmacsFillRectangle.java @@ -115,6 +115,9 @@ public class EmacsFillRectangle /* Finally, draw the mask bitmap to the destination. */ paint.setXfermode (null); canvas.drawBitmap (maskBitmap, null, maskRect, paint); + + /* Recycle this unused bitmap. */ + maskBitmap.recycle (); } canvas.restore (); diff --git a/java/org/gnu/emacs/EmacsPixmap.java b/java/org/gnu/emacs/EmacsPixmap.java index 15452f007c4..85931c2abd4 100644 --- a/java/org/gnu/emacs/EmacsPixmap.java +++ b/java/org/gnu/emacs/EmacsPixmap.java @@ -25,6 +25,8 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; +import android.os.Build; + /* Drawable backed by bitmap. */ public class EmacsPixmap extends EmacsHandleObject @@ -123,4 +125,25 @@ public class EmacsPixmap extends EmacsHandleObject { return bitmap; } + + @Override + public void + destroyHandle () + { + boolean needCollect; + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) + needCollect = (bitmap.getByteCount () + >= 1024 * 512); + else + needCollect = (bitmap.getAllocationByteCount () + >= 1024 * 512); + + bitmap.recycle (); + bitmap = null; + + /* Collect the bitmap storage if the bitmap is big. */ + if (needCollect) + Runtime.getRuntime ().gc (); + } }; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 445d8ffa023..6137fd74a7f 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -70,9 +70,9 @@ public class EmacsView extends ViewGroup event regardless of what changed. */ public boolean mustReportLayout; - /* If non-null, whether or not bitmaps must be recreated upon the - next call to getBitmap. */ - private Rect bitmapDirty; + /* Whether or not bitmaps must be recreated upon the next call to + getBitmap. */ + private boolean bitmapDirty; /* Whether or not a popup is active. */ private boolean popupActive; @@ -80,6 +80,9 @@ public class EmacsView extends ViewGroup /* The current context menu. */ private EmacsContextMenu contextMenu; + /* The last measured width and height. */ + private int measuredWidth, measuredHeight; + public EmacsView (EmacsWindow window) { @@ -116,13 +119,27 @@ public class EmacsView extends ViewGroup { Bitmap oldBitmap; + if (measuredWidth == 0 || measuredHeight == 0) + return; + + /* If bitmap is the same width and height as the measured width + and height, there is no need to do anything. Avoid allocating + the extra bitmap. */ + if (bitmap != null + && (bitmap.getWidth () == measuredWidth + && bitmap.getHeight () == measuredHeight)) + { + bitmapDirty = false; + return; + } + /* Save the old bitmap. */ oldBitmap = bitmap; /* Recreate the front and back buffer bitmaps. */ bitmap - = Bitmap.createBitmap (bitmapDirty.width (), - bitmapDirty.height (), + = Bitmap.createBitmap (measuredWidth, + measuredHeight, Bitmap.Config.ARGB_8888); bitmap.eraseColor (0xffffffff); @@ -133,23 +150,27 @@ public class EmacsView extends ViewGroup if (oldBitmap != null) canvas.drawBitmap (oldBitmap, 0f, 0f, new Paint ()); - bitmapDirty = null; + bitmapDirty = false; + + /* Explicitly free the old bitmap's memory. */ + if (oldBitmap != null) + oldBitmap.recycle (); + + /* Some Android versions still don't free the bitmap until the + next GC. */ + Runtime.getRuntime ().gc (); } public synchronized void - explicitlyDirtyBitmap (Rect rect) + explicitlyDirtyBitmap () { - if (bitmapDirty == null - && (bitmap == null - || rect.width () != bitmap.getWidth () - || rect.height () != bitmap.getHeight ())) - bitmapDirty = rect; + bitmapDirty = true; } public synchronized Bitmap getBitmap () { - if (bitmapDirty != null) + if (bitmapDirty || bitmap == null) handleDirtyBitmap (); return bitmap; @@ -158,7 +179,7 @@ public class EmacsView extends ViewGroup public synchronized Canvas getCanvas () { - if (bitmapDirty != null) + if (bitmapDirty || bitmap == null) handleDirtyBitmap (); return canvas; @@ -196,8 +217,12 @@ public class EmacsView extends ViewGroup super.setMeasuredDimension (width, height); } + /* Note that the monitor lock for the window must never be held from + within the lock for the view, because the window also locks the + other way around. */ + @Override - protected synchronized void + protected void onLayout (boolean changed, int left, int top, int right, int bottom) { @@ -213,12 +238,13 @@ public class EmacsView extends ViewGroup window.viewLayout (left, top, right, bottom); } - if (changed - /* Check that a change has really happened. */ - && (bitmapDirty == null - || bitmapDirty.width () != right - left - || bitmapDirty.height () != bottom - top)) - bitmapDirty = new Rect (left, top, right, bottom); + measuredWidth = right - left; + measuredHeight = bottom - top; + + /* Dirty the back buffer. */ + + if (changed) + explicitlyDirtyBitmap (); for (i = 0; i < count; ++i) { @@ -472,4 +498,20 @@ public class EmacsView extends ViewGroup contextMenu = null; popupActive = false; } + + @Override + public synchronized void + onDetachedFromWindow () + { + synchronized (this) + { + /* Recycle the bitmap and call GC. */ + bitmap.recycle (); + bitmap = null; + canvas = null; + + /* Collect the bitmap storage; it could be large. */ + Runtime.getRuntime ().gc (); + } + } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 7181bc89fea..f5b50f11f14 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -260,7 +260,7 @@ public class EmacsWindow extends EmacsHandleObject { /* This is necessary because otherwise subsequent drawing on the Emacs thread may be lost. */ - view.explicitlyDirtyBitmap (rect); + view.explicitlyDirtyBitmap (); EmacsService.SERVICE.runOnUiThread (new Runnable () { @Override diff --git a/src/android.c b/src/android.c index 9b15ea9f15a..cfb79045c0b 100644 --- a/src/android.c +++ b/src/android.c @@ -26,6 +26,7 @@ along with GNU Emacs. If not, see . */ #include #include #include +#include #include #include @@ -225,7 +226,6 @@ static fd_set *volatile android_pselect_readfds; static fd_set *volatile android_pselect_writefds; static fd_set *volatile android_pselect_exceptfds; static struct timespec *volatile android_pselect_timeout; -static const sigset_t *volatile android_pselect_sigset; /* Value of pselect. */ static int android_pselect_rc; @@ -242,8 +242,8 @@ static sem_t android_pselect_sem, android_pselect_start_sem; static void * android_run_select_thread (void *data) { - sigset_t signals, sigset; - int rc; + sigset_t signals, waitset; + int rc, sig; sigfillset (&signals); @@ -253,6 +253,8 @@ android_run_select_thread (void *data) strerror (errno)); sigdelset (&signals, SIGUSR1); + sigemptyset (&waitset); + sigaddset (&waitset, SIGUSR1); while (true) { @@ -262,35 +264,33 @@ android_run_select_thread (void *data) /* Get the select lock and call pselect. */ pthread_mutex_lock (&event_queue.select_mutex); - - /* Make sure SIGUSR1 can always wake pselect up. */ - if (android_pselect_sigset) - { - sigset = *android_pselect_sigset; - sigdelset (&sigset, SIGUSR1); - android_pselect_sigset = &sigset; - } - else - android_pselect_sigset = &signals; - rc = pselect (android_pselect_nfds, android_pselect_readfds, android_pselect_writefds, android_pselect_exceptfds, android_pselect_timeout, - android_pselect_sigset); + &signals); android_pselect_rc = rc; pthread_mutex_unlock (&event_queue.select_mutex); + /* Signal the main thread that there is now data to read. + It is ok to signal this condition variable without holding + the event queue lock, because android_select will always + wait for this to complete before returning. */ + android_pselect_completed = true; + pthread_cond_signal (&event_queue.read_var); + + if (rc != -1 || errno != EINTR) + /* Now, wait for SIGUSR1, unless pselect was interrupted and + the signal was already delivered. The Emacs thread will + always send this signal after read_var is triggered or the + UI thread has sent an event. */ + sigwait (&waitset, &sig); + /* Signal the Emacs thread that pselect is done. If read_var was signaled by android_write_event, event_queue.mutex could still be locked, so this must come before. */ sem_post (&android_pselect_sem); - - pthread_mutex_lock (&event_queue.mutex); - android_pselect_completed = true; - pthread_cond_signal (&event_queue.read_var); - pthread_mutex_unlock (&event_queue.mutex); } } @@ -445,8 +445,7 @@ android_write_event (union android_event *event) int android_select (int nfds, fd_set *readfds, fd_set *writefds, - fd_set *exceptfds, struct timespec *timeout, - const sigset_t *sigset) + fd_set *exceptfds, struct timespec *timeout) { int nfds_return; @@ -467,7 +466,6 @@ android_select (int nfds, fd_set *readfds, fd_set *writefds, android_pselect_writefds = writefds; android_pselect_exceptfds = exceptfds; android_pselect_timeout = timeout; - android_pselect_sigset = sigset; pthread_mutex_unlock (&event_queue.select_mutex); /* Release the select thread. */ @@ -3725,6 +3723,24 @@ android_build_string (Lisp_Object text) return string; } +/* Do the same, except TEXT is constant string data. */ + +jstring +android_build_jstring (const char *text) +{ + jstring string; + + string = (*android_java_env)->NewStringUTF (android_java_env, + text); + if (!string) + { + (*android_java_env)->ExceptionClear (android_java_env); + memory_full (0); + } + + return string; +} + /* Check for JNI exceptions and call memory_full in that situation. */ diff --git a/src/android.h b/src/android.h index e68e0a51fbf..036e6d266fd 100644 --- a/src/android.h +++ b/src/android.h @@ -48,7 +48,7 @@ extern int ANDROID_EXPORT android_emacs_init (int, char **); #ifndef ANDROID_STUBIFY extern int android_select (int, fd_set *, fd_set *, fd_set *, - struct timespec *, const sigset_t *); + struct timespec *); extern bool android_file_access_p (const char *, int); extern int android_open (const char *, int, int); @@ -86,6 +86,7 @@ extern void android_set_dont_focus_on_map (android_window, bool); extern void android_set_dont_accept_focus (android_window, bool); extern jstring android_build_string (Lisp_Object); +extern jstring android_build_jstring (const char *); extern void android_exception_check (void); extern void android_get_keysym_name (int, char *, size_t); diff --git a/src/androidmenu.c b/src/androidmenu.c index 6fb4963174b..f65b5d3ffd1 100644 --- a/src/androidmenu.c +++ b/src/androidmenu.c @@ -168,11 +168,17 @@ android_dismiss_menu (void *pointer) static void android_process_events_for_menu (int *id) { + int blocked; + /* Set menu_event_id to -1; handle_one_android_event will set it to the event ID upon receiving a context menu event. This can cause a non-local exit. */ x_display_list->menu_event_id = -1; + /* Unblock input completely. */ + blocked = interrupt_input_blocked; + totally_unblock_input (); + /* Now wait for the menu event ID to change. */ while (x_display_list->menu_event_id == -1) { @@ -181,11 +187,11 @@ android_process_events_for_menu (int *id) /* Process pending signals. */ process_pending_signals (); - - /* Maybe quit. */ - maybe_quit (); } + /* Restore the input block. */ + interrupt_input_blocked = blocked; + /* Return the ID. */ *id = x_display_list->menu_event_id; } @@ -420,9 +426,7 @@ android_menu_show (struct frame *f, int x, int y, int menuflags, /* Next, process events waiting for something to be selected. */ popup_activated_flag = 1; - unblock_input (); android_process_events_for_menu (&id); - block_input (); if (!id) /* This means no menu item was selected. */ @@ -498,8 +502,6 @@ android_menu_show (struct frame *f, int x, int y, int menuflags, } } - Fprint (tem, Qexternal_debugging_output); - unblock_input (); return unbind_to (count, tem); @@ -508,6 +510,229 @@ android_menu_show (struct frame *f, int x, int y, int menuflags, return unbind_to (count, Qnil); } + + +/* Toolkit dialog implementation. */ + +/* Structure describing the EmacsDialog class. */ + +struct android_emacs_dialog +{ + jclass class; + jmethodID create_dialog; + jmethodID add_button; + jmethodID display; +}; + +/* Identifiers associated with the EmacsDialog class. */ +static struct android_emacs_dialog dialog_class; + +static void +android_init_emacs_dialog (void) +{ + jclass old; + + dialog_class.class + = (*android_java_env)->FindClass (android_java_env, + "org/gnu/emacs/EmacsDialog"); + eassert (dialog_class.class); + + old = dialog_class.class; + dialog_class.class + = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, + (jobject) old); + ANDROID_DELETE_LOCAL_REF (old); + + if (!dialog_class.class) + emacs_abort (); + +#define FIND_METHOD(c_name, name, signature) \ + dialog_class.c_name \ + = (*android_java_env)->GetMethodID (android_java_env, \ + dialog_class.class, \ + name, signature); \ + eassert (dialog_class.c_name); + +#define FIND_METHOD_STATIC(c_name, name, signature) \ + dialog_class.c_name \ + = (*android_java_env)->GetStaticMethodID (android_java_env, \ + dialog_class.class, \ + name, signature); \ + + FIND_METHOD_STATIC (create_dialog, "createDialog", "(Ljava/lang/String;" + "Ljava/lang/String;)Lorg/gnu/emacs/EmacsDialog;"); + FIND_METHOD (add_button, "addButton", "(Ljava/lang/String;IZ)V"); + FIND_METHOD (display, "display", "()Z"); + +#undef FIND_METHOD +#undef FIND_METHOD_STATIC +} + +static Lisp_Object +android_dialog_show (struct frame *f, Lisp_Object title, + Lisp_Object header, const char **error_name) +{ + specpdl_ref count; + jobject dialog, java_header, java_title, temp; + size_t i; + Lisp_Object item_name, enable, entry; + bool rc; + int id; + jmethodID method; + + if (menu_items_n_panes > 1) + { + *error_name = "Multiple panes in dialog box"; + return Qnil; + } + + /* Do the initial setup. */ + count = SPECPDL_INDEX (); + *error_name = NULL; + + android_push_local_frame (); + + /* Figure out what header to use. */ + java_header = (!NILP (header) + ? android_build_jstring ("Information") + : android_build_jstring ("Question")); + + /* And the title. */ + java_title = android_build_string (title); + + /* Now create the dialog. */ + method = dialog_class.create_dialog; + dialog = (*android_java_env)->CallStaticObjectMethod (android_java_env, + dialog_class.class, + method, java_header, + java_title); + android_exception_check (); + + /* Delete now unused local references. */ + if (java_header) + ANDROID_DELETE_LOCAL_REF (java_header); + ANDROID_DELETE_LOCAL_REF (java_title); + + /* Create the buttons. */ + i = MENU_ITEMS_PANE_LENGTH; + while (i < menu_items_used) + { + item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME); + enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE); + + /* Verify that there is no submenu here. */ + + if (NILP (item_name)) + { + *error_name = "Submenu in dialog items"; + return unbind_to (count, Qnil); + } + + /* Skip past boundaries between buttons on different sides. The + Android toolkit is too silly to understand this + distinction. */ + + if (EQ (item_name, Qquote)) + ++i; + else + { + /* Make sure i is within bounds. */ + if (i > TYPE_MAXIMUM (jint)) + { + *error_name = "Dialog box too big"; + return unbind_to (count, Qnil); + } + + /* Add the button. */ + temp = android_build_string (item_name); + (*android_java_env)->CallVoidMethod (android_java_env, + dialog, + dialog_class.add_button, + temp, (jint) i, + (jboolean) NILP (enable)); + android_exception_check (); + ANDROID_DELETE_LOCAL_REF (temp); + i += MENU_ITEMS_ITEM_LENGTH; + } + } + + /* The dialog is now built. Run it. */ + rc = (*android_java_env)->CallBooleanMethod (android_java_env, + dialog, + dialog_class.display); + android_exception_check (); + + if (!rc) + quit (); + + /* Wait for the menu ID to arrive. */ + android_process_events_for_menu (&id); + + if (!id) + quit (); + + /* Find the selected item, and its pane, to return + the proper value. */ + i = 0; + while (i < menu_items_used) + { + if (EQ (AREF (menu_items, i), Qt)) + i += MENU_ITEMS_PANE_LENGTH; + else if (EQ (AREF (menu_items, i), Qquote)) + /* This is the boundary between left-side elts and right-side + elts. */ + ++i; + else + { + entry = AREF (menu_items, i + MENU_ITEMS_ITEM_VALUE); + + if (id == i) + return entry; + + i += MENU_ITEMS_ITEM_LENGTH; + } + } + + return Qnil; +} + +Lisp_Object +android_popup_dialog (struct frame *f, Lisp_Object header, + Lisp_Object contents) +{ + Lisp_Object title; + const char *error_name; + Lisp_Object selection; + specpdl_ref specpdl_count = SPECPDL_INDEX (); + + check_window_system (f); + + /* Decode the dialog items from what was specified. */ + title = Fcar (contents); + CHECK_STRING (title); + record_unwind_protect_void (unuse_menu_items); + + if (NILP (Fcar (Fcdr (contents)))) + /* No buttons specified, add an "Ok" button so users can pop down + the dialog. */ + contents = list2 (title, Fcons (build_string ("Ok"), Qt)); + + list_of_panes (list1 (contents)); + + /* Display them in a dialog box. */ + block_input (); + selection = android_dialog_show (f, title, header, &error_name); + unblock_input (); + + unbind_to (specpdl_count, Qnil); + discard_menu_items (); + + if (error_name) + error ("%s", error_name); + + return selection; +} + #else int @@ -531,6 +756,7 @@ init_androidmenu (void) { #ifndef ANDROID_STUBIFY android_init_emacs_context_menu (); + android_init_emacs_dialog (); #endif } diff --git a/src/androidterm.c b/src/androidterm.c index cc2da279bb3..f19cee5b11b 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -496,10 +496,17 @@ android_update_tools (struct frame *f, struct input_event *ie) /* Build the list of active touches. */ for (touchpoint = FRAME_OUTPUT_DATA (f)->touch_points; touchpoint; touchpoint = touchpoint->next) - ie->arg = Fcons (list3i (touchpoint->x, - touchpoint->y, - touchpoint->tool_id), - ie->arg); + { + /* Skip touch points which originated on the tool bar. */ + + if (touchpoint->tool_bar_p) + continue; + + ie->arg = Fcons (list3i (touchpoint->x, + touchpoint->y, + touchpoint->tool_id), + ie->arg); + } } /* Find and return an existing tool pressed against FRAME, identified @@ -951,6 +958,59 @@ handle_one_android_event (struct android_display_info *dpyinfo, touchpoint->next = FRAME_OUTPUT_DATA (any)->touch_points; FRAME_OUTPUT_DATA (any)->touch_points = touchpoint; + /* Figure out whether or not the tool was pressed on the tool + bar. Note that the code which runs when it was is more or + less an abuse of the mouse highlight machinery, but it works + well enough in practice. */ + + if (WINDOWP (any->tool_bar_window) + && WINDOW_TOTAL_LINES (XWINDOW (any->tool_bar_window))) + { + Lisp_Object window; + int x = event->touch.x; + int y = event->touch.y; + + window = window_from_coordinates (any, x, y, 0, true, + true); + + /* If this touch has started in the tool bar, do not + send it to Lisp. Instead, simulate a tool bar + click, releasing it once it goes away. */ + + if (EQ (window, any->tool_bar_window)) + { + /* Call note_mouse_highlight on the tool bar + item. Otherwise, get_tool_bar_item will + return 1. + + This is not necessary when mouse-highlight is + nil. */ + + if (!NILP (Vmouse_highlight)) + { + note_mouse_highlight (any, x, y); + + /* Always allow future mouse motion to + update the mouse highlight, no matter + where it is. */ + memset (&dpyinfo->last_mouse_glyph, 0, + sizeof dpyinfo->last_mouse_glyph); + dpyinfo->last_mouse_glyph_frame = any; + } + + handle_tool_bar_click (any, x, y, true, 0); + + /* Flush any changes made by that to the front + buffer. */ + android_flush_dirty_back_buffer_on (any); + + /* Mark the touch point as being grabbed by the tool + bar. */ + touchpoint->tool_bar_p = true; + goto OTHER; + } + } + /* Now generate the Emacs event. */ inev.ie.kind = TOUCHSCREEN_BEGIN_EVENT; inev.ie.timestamp = event->touch.time; @@ -970,9 +1030,10 @@ handle_one_android_event (struct android_display_info *dpyinfo, touchpoint = android_find_tool (any, event->touch.pointer_id); - /* If it doesn't exist, skip processing this event. */ + /* If it doesn't exist or has been grabbed by the tool bar, skip + processing this event. */ - if (!touchpoint) + if (!touchpoint || touchpoint->tool_bar_p) goto OTHER; /* Otherwise, update the position and send the update event. */ @@ -999,8 +1060,27 @@ handle_one_android_event (struct android_display_info *dpyinfo, *last = touchpoint->next; /* The tool was unlinked. Free it and generate the - appropriate Emacs event. */ + appropriate Emacs event (assuming that it was not + grabbed by the tool bar). */ xfree (touchpoint); + + if (touchpoint->tool_bar_p) + { + /* Do what is necessary to release the tool bar and + possibly trigger a click. */ + + if (any->last_tool_bar_item != -1) + handle_tool_bar_click (any, event->touch.x, + event->touch.y, false, + 0); + + /* Cancel any outstanding mouse highlight. */ + note_mouse_highlight (any, -1, -1); + android_flush_dirty_back_buffer_on (any); + + goto OTHER; + } + inev.ie.kind = TOUCHSCREEN_END_EVENT; inev.ie.timestamp = event->touch.time; @@ -1227,6 +1307,9 @@ android_frame_up_to_date (struct frame *f) /* The frame is now complete, as its contents have been drawn. */ FRAME_ANDROID_COMPLETE_P (f) = true; + + /* Shrink the scanline buffer used by the font backend. */ + sfntfont_android_shrink_scanline_buffer (); unblock_input (); } @@ -1513,7 +1596,7 @@ android_wait_for_event (struct frame *f, int eventtype) break; tmo = timespec_sub (tmo_at, time_now); - if (android_select (0, NULL, NULL, NULL, &tmo, NULL) == 0) + if (android_select (0, NULL, NULL, NULL, &tmo) == 0) break; /* Timeout */ } @@ -4061,6 +4144,7 @@ android_create_terminal (struct android_display_info *dpyinfo) terminal->set_bitmap_icon_hook = android_bitmap_icon; terminal->implicit_set_name_hook = android_implicitly_set_name; terminal->menu_show_hook = android_menu_show; + terminal->popup_dialog_hook = android_popup_dialog; terminal->change_tab_bar_height_hook = android_change_tab_bar_height; terminal->change_tool_bar_height_hook = android_change_tool_bar_height; terminal->set_scroll_bar_default_width_hook diff --git a/src/androidterm.h b/src/androidterm.h index 9aa09877196..c0f862e35fb 100644 --- a/src/androidterm.h +++ b/src/androidterm.h @@ -148,6 +148,9 @@ struct android_touch_point /* The tool ID and the last known X and Y positions. */ int tool_id, x, y; + + /* Whether or not the tool is pressed on the tool bar. */ + bool tool_bar_p; }; struct android_output @@ -410,6 +413,9 @@ extern void android_finalize_font_entity (struct font_entity *); extern Lisp_Object android_menu_show (struct frame *, int, int, int, Lisp_Object, const char **); +extern Lisp_Object android_popup_dialog (struct frame *, Lisp_Object, + Lisp_Object); + extern void init_androidmenu (void); extern void syms_of_androidmenu (void); @@ -417,6 +423,7 @@ extern void syms_of_androidmenu (void); extern const struct font_driver android_sfntfont_driver; +extern void sfntfont_android_shrink_scanline_buffer (void); extern void init_sfntfont_android (void); extern void syms_of_sfntfont_android (void); diff --git a/src/keyboard.c b/src/keyboard.c index 990b5307f14..834049b496a 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -10380,7 +10380,7 @@ read_key_sequence (Lisp_Object *keybuf, Lisp_Object prompt, if (EVENT_HAS_PARAMETERS (key)) { Lisp_Object kind = EVENT_HEAD_KIND (EVENT_HEAD (key)); - if (EQ (kind, Qmouse_click)) + if (EQ (kind, Qmouse_click) || EQ (kind, Qtouchscreen)) { Lisp_Object window = POSN_WINDOW (EVENT_START (key)); Lisp_Object posn = POSN_POSN (EVENT_START (key)); @@ -12185,7 +12185,9 @@ static const struct event_head head_table[] = { {SYMBOL_INDEX (Qmake_frame_visible), SYMBOL_INDEX (Qmake_frame_visible)}, /* `select-window' should be handled just like `switch-frame' in read_key_sequence. */ - {SYMBOL_INDEX (Qselect_window), SYMBOL_INDEX (Qswitch_frame)} + {SYMBOL_INDEX (Qselect_window), SYMBOL_INDEX (Qswitch_frame)}, + /* Touchscreen events should be prefixed by the posn. */ + {SYMBOL_INDEX (Qtouchscreen_begin), SYMBOL_INDEX (Qtouchscreen)}, }; static Lisp_Object @@ -12895,6 +12897,7 @@ See also `pre-command-hook'. */); "display-monitors-changed-functions"); DEFSYM (Qcoding, "coding"); + DEFSYM (Qtouchscreen, "touchscreen"); Fset (Qecho_area_clear_hook, Qnil); diff --git a/src/keyboard.h b/src/keyboard.h index 3f86a8e03ad..26eecd48b00 100644 --- a/src/keyboard.h +++ b/src/keyboard.h @@ -395,8 +395,17 @@ extern void unuse_menu_items (void); #define EVENT_HEAD(event) \ (EVENT_HAS_PARAMETERS (event) ? XCAR (event) : (event)) -/* Extract the starting and ending positions from a composite event. */ -#define EVENT_START(event) (CAR_SAFE (CDR_SAFE (event))) +/* Extract the starting and ending positions from a composite event. */ + +/* Unlike Lisp `event-start', this also handles touch screen events, + which are not actually mouse events in the general sense. */ +#define EVENT_START(event) \ + ((EQ (EVENT_HEAD (event), Qtouchscreen_begin) \ + || EQ (EVENT_HEAD (event), Qtouchscreen_end)) \ + ? CDR_SAFE (CAR_SAFE (CDR_SAFE (event))) \ + : CAR_SAFE (CDR_SAFE (event))) + +/* This does not handle touchscreen events. */ #define EVENT_END(event) (CAR_SAFE (CDR_SAFE (CDR_SAFE (event)))) /* Extract the click count from a multi-click event. */ diff --git a/src/process.c b/src/process.c index 111e0c80e43..651b5fa035b 100644 --- a/src/process.c +++ b/src/process.c @@ -5689,7 +5689,7 @@ wait_reading_process_output (intmax_t time_limit, int nsecs, int read_kbd, #if defined HAVE_ANDROID && !defined ANDROID_STUBIFY nfds = android_select (max_desc + 1, &Available, (check_write ? &Writeok : 0), - NULL, &timeout, NULL); + NULL, &timeout); #else /* Non-macOS HAVE_GLIB builds call thread_select in diff --git a/src/sfnt.c b/src/sfnt.c index ee74ba0fefe..6d58798c599 100644 --- a/src/sfnt.c +++ b/src/sfnt.c @@ -129,8 +129,12 @@ _sfnt_swap32 (uint32_t *value) #define sfnt_swap32(what) (_sfnt_swap32 ((uint32_t *) (what))) /* Read the table directory from the file FD. FD must currently be at - the start of the file, and must be seekable. Return the table - directory upon success, else NULL. */ + the start of the file (or an offset defined in the TTC header, if + applicable), and must be seekable. Return the table directory upon + success, else NULL. + + Value is NULL upon failure, and the offset subtable upon success. + If FD is actually a TrueType collection file, value is -1. */ TEST_STATIC struct sfnt_offset_subtable * sfnt_read_table_directory (int fd) @@ -147,11 +151,34 @@ sfnt_read_table_directory (int fd) if (rc < offset) { + if (rc >= sizeof (uint32_t)) + { + /* Detect a TTC file. In that case, the first long will be + ``ttcf''. */ + sfnt_swap32 (&subtable->scaler_type); + + if (subtable->scaler_type == SFNT_TTC_TTCF) + { + xfree (subtable); + return (struct sfnt_offset_subtable *) -1; + } + } + xfree (subtable); return NULL; } sfnt_swap32 (&subtable->scaler_type); + + /* Bail out early if this font is actually a TrueType collection + file. */ + + if (subtable->scaler_type == SFNT_TTC_TTCF) + { + xfree (subtable); + return (struct sfnt_offset_subtable *) -1; + } + sfnt_swap16 (&subtable->num_tables); sfnt_swap16 (&subtable->search_range); sfnt_swap16 (&subtable->entry_selector); @@ -4183,6 +4210,101 @@ sfnt_find_metadata (struct sfnt_meta_table *meta, +/* TrueType collection format support. */ + +/* Read a TrueType collection header from the font file FD. + FD must currently at the start of the file. + + Value is the header upon success, else NULL. */ + +TEST_STATIC struct sfnt_ttc_header * +sfnt_read_ttc_header (int fd) +{ + struct sfnt_ttc_header *ttc; + size_t size, i; + ssize_t rc; + + /* First, allocate only as much as required. */ + + ttc = xmalloc (sizeof *ttc); + + /* Read the version 1.0 data. */ + + size = SFNT_ENDOF (struct sfnt_ttc_header, num_fonts, + uint32_t); + rc = read (fd, ttc, size); + if (rc < size) + { + xfree (ttc); + return NULL; + } + + /* Now swap what was read. */ + sfnt_swap32 (&ttc->ttctag); + sfnt_swap32 (&ttc->version); + sfnt_swap32 (&ttc->num_fonts); + + /* Verify that the tag is as expected. */ + if (ttc->ttctag != SFNT_TTC_TTCF) + { + xfree (ttc); + return NULL; + } + + /* Now, read the variable length data. Make sure to check for + overflow. */ + + if (INT_MULTIPLY_WRAPV (ttc->num_fonts, + sizeof *ttc->offset_table, + &size)) + { + xfree (ttc); + return NULL; + } + + ttc = xrealloc (ttc, sizeof *ttc + size); + ttc->offset_table = (uint32_t *) (ttc + 1); + rc = read (fd, ttc->offset_table, size); + if (rc < size) + { + xfree (ttc); + return NULL; + } + + /* Swap each of the offsets read. */ + for (i = 0; i < ttc->num_fonts; ++i) + sfnt_swap32 (&ttc->offset_table[i]); + + /* Now, look at the version. If it is earlier than 2.0, then + reading is finished. */ + + if (ttc->version < 0x00020000) + return ttc; + + /* If it is 2.0 or later, then continue to read ul_dsig_tag to + ul_dsig_offset. */ + + size = (SFNT_ENDOF (struct sfnt_ttc_header, ul_dsig_offset, + uint32_t) + - offsetof (struct sfnt_ttc_header, ul_dsig_tag)); + rc = read (fd, &ttc->ul_dsig_offset, size); + if (rc < size) + { + xfree (ttc); + return NULL; + } + + /* Swap what was read. */ + sfnt_swap32 (&ttc->ul_dsig_tag); + sfnt_swap32 (&ttc->ul_dsig_length); + sfnt_swap32 (&ttc->ul_dsig_offset); + + /* All done. */ + return ttc; +} + + + #ifdef TEST struct sfnt_test_dcontext @@ -4397,6 +4519,7 @@ main (int argc, char **argv) unsigned char *string; struct sfnt_name_record record; struct sfnt_meta_table *meta; + struct sfnt_ttc_header *ttc; if (argc != 2) return 1; @@ -4406,8 +4529,41 @@ main (int argc, char **argv) if (fd < 1) return 1; + ttc = NULL; + font = sfnt_read_table_directory (fd); + if (font == (struct sfnt_offset_subtable *) -1) + { + if (lseek (fd, 0, SEEK_SET) != 0) + return 1; + + ttc = sfnt_read_ttc_header (fd); + + if (!ttc) + return 1; + + fprintf (stderr, "TrueType collection: %"PRIu32" fonts installed\n", + ttc->num_fonts); + fflush (stderr); + + printf ("Which font? "); + if (scanf ("%d", &i) == EOF) + return 1; + + if (i >= ttc->num_fonts || i < 0) + { + printf ("out of range\n"); + return 1; + } + + if (lseek (fd, ttc->offset_table[i], SEEK_SET) + != ttc->offset_table[i]) + return 1; + + font = sfnt_read_table_directory (fd); + } + if (!font) { close (fd); @@ -4432,9 +4588,9 @@ main (int argc, char **argv) for (i = 0; i < table->num_subtables; ++i) { fprintf (stderr, "Found cmap table %"PRIu32": %p\n", - subtables[i].offset, data); + subtables[i].offset, data[i]); - if (data) + if (data[i]) fprintf (stderr, " format: %"PRIu16"\n", data[i]->format); } @@ -4552,7 +4708,7 @@ main (int argc, char **argv) if (scanf ("%d %"SCNu32"", &i, &character) == EOF) break; - if (i >= table->num_subtables) + if (i < 0 || i >= table->num_subtables) { printf ("table out of range\n"); continue; @@ -4699,6 +4855,7 @@ main (int argc, char **argv) xfree (hmtx); xfree (name); xfree (meta); + xfree (ttc); return 0; } diff --git a/src/sfnt.h b/src/sfnt.h index 91d7b261cb0..fe6b6ec3dd7 100644 --- a/src/sfnt.h +++ b/src/sfnt.h @@ -875,6 +875,44 @@ enum sfnt_meta_data_tag +/* TrueType collection format support. */ + +struct sfnt_ttc_header +{ + /* TrueType collection ID tag. */ + uint32_t ttctag; + + /* Version of the TTC header. */ + uint32_t version; + + /* Number of fonts in the TTC header. */ + uint32_t num_fonts; + + /* Array of offsets to the offset table for each font in the + file. */ + uint32_t *offset_table; + + /* Tag indicating that a DSIG table exists, or 0. Fields from here + on are only set on version 2.0 headers or later. */ + uint32_t ul_dsig_tag; + + /* Length in bytes of the signature table, or 0 if there is no + signature. */ + uint32_t ul_dsig_length; + + /* Offset in bytes of the dsig table from the beginning of the TTC + file. */ + uint32_t ul_dsig_offset; +}; + +enum sfnt_ttc_tag + { + SFNT_TTC_TTCF = 0x74746366, + SFNT_TTC_DSIG = 0x44534947, + }; + + + #define SFNT_CEIL_FIXED(fixed) \ (!((fixed) & 0177777) ? (fixed) \ : ((fixed) + 0200000) & 037777600000) @@ -960,5 +998,7 @@ extern char *sfnt_find_metadata (struct sfnt_meta_table *, enum sfnt_meta_data_tag, struct sfnt_meta_data_map *); +extern struct sfnt_ttc_header *sfnt_read_ttc_header (int); + #endif /* TEST */ #endif /* _SFNT_H_ */ diff --git a/src/sfntfont-android.c b/src/sfntfont-android.c index cddb3fd40f3..1b01a4d9be4 100644 --- a/src/sfntfont-android.c +++ b/src/sfntfont-android.c @@ -31,6 +31,17 @@ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #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. */ const char *system_font_directories[] = { @@ -40,6 +51,49 @@ const char *system_font_directories[] = /* 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. */ + +#define GET_SCANLINE_BUFFER(buffer, height, stride) \ + do \ + { \ + size_t _size; \ + \ + if (INT_MULTIPLY_WRAPV (height, stride, &_size)) \ + 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); + /* Scale each of the four packed bytes in P in the low 16 bits of P by @@ -205,8 +259,6 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from, back_pixel &= ~0x00ff00ff; back_pixel |= rb >> 16 | rb << 16 | 0xff000000; - USE_SAFE_ALLOCA; - prepare_face_for_display (s->f, s->face); /* Build the scanline buffer. Figure out the bounds of the @@ -259,7 +311,7 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from, /* Allocate enough to hold text_rectangle.height, aligned to 8 bytes. Then fill it with the background. */ stride = (text_rectangle.width * sizeof *buffer) + 7 & ~7; - SAFE_NALLOCA (buffer, text_rectangle.height, stride); + GET_SCANLINE_BUFFER (buffer, text_rectangle.height, stride); memset (buffer, 0, text_rectangle.height * stride); if (with_background) @@ -327,10 +379,7 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from, /* If locking the bitmap fails, just discard the data that was allocated. */ if (!bitmap_data) - { - SAFE_FREE (); - return; - } + return; /* Loop over each clip rect in the GC. */ eassert (bitmap_info.format == ANDROID_BITMAP_FORMAT_RGBA_8888); @@ -366,8 +415,33 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from, android_damage_window (FRAME_ANDROID_DRAWABLE (s->f), &text_rectangle); - /* Release the temporary scanline buffer. */ - SAFE_FREE (); +#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; } @@ -437,10 +511,11 @@ loaded before character sets are made available. */) while ((dirent = readdir (dir))) { - /* If it contains (not ends with!) with .ttf, then enumerate - it. */ + /* If it contains (not ends with!) with .ttf or .ttc, then + enumerate it. */ - if (strstr (dirent->d_name, ".ttf")) + if (strstr (dirent->d_name, ".ttf") + || strstr (dirent->d_name, ".ttc")) { sprintf (name, "%s/%s", system_font_directories[i], dirent->d_name); diff --git a/src/sfntfont.c b/src/sfntfont.c index 56977622211..e2d18517fcb 100644 --- a/src/sfntfont.c +++ b/src/sfntfont.c @@ -77,6 +77,9 @@ struct sfnt_font_desc /* The header of the cmap being used. May be invalid, in which case platform_id will be 500. */ struct sfnt_cmap_encoding_subtable subtable; + + /* The offset of the table directory within PATH. */ + off_t offset; }; /* List of fonts. */ @@ -426,15 +429,17 @@ sfnt_parse_style (Lisp_Object style_name, struct sfnt_font_desc *desc) } } -/* Enumerate the font FILE into the list of system fonts. Return 1 if - it could not be enumerated, 0 otherwise. */ +/* Enumerate the offset subtable SUBTABLES in the file FD, whose file + name is FILE. OFFSET should be the offset of the subtable within + the font file, and is recorded for future use. Value is 1 upon + failure, else 0. */ -int -sfnt_enum_font (const char *file) +static int +sfnt_enum_font_1 (int fd, const char *file, + struct sfnt_offset_subtable *subtables, + off_t offset) { struct sfnt_font_desc *desc; - int fd; - struct sfnt_offset_subtable *subtables; struct sfnt_head_table *head; struct sfnt_name_table *name; struct sfnt_meta_table *meta; @@ -444,18 +449,7 @@ sfnt_enum_font (const char *file) desc = xzalloc (sizeof *desc + strlen (file) + 1); desc->path = (char *) (desc + 1); memcpy (desc->path, file, strlen (file) + 1); - - /* Now open the font for reading. */ - fd = emacs_open (file, O_RDONLY, 0); - - if (fd == -1) - goto bail; - - /* Read the table directory. */ - subtables = sfnt_read_table_directory (fd); - - if (!subtables) - goto bail0; + desc->offset = offset; /* Check that this is a TrueType font. */ if (subtables->scaler_type != SFNT_SCALER_TRUE @@ -511,8 +505,6 @@ sfnt_enum_font (const char *file) xfree (meta); xfree (name); xfree (head); - xfree (subtables); - emacs_close (fd); return 0; bail3: @@ -521,11 +513,84 @@ sfnt_enum_font (const char *file) bail2: xfree (head); bail1: + xfree (desc); + return 1; +} + +/* Enumerate the font FILE into the list of system fonts. Return 1 if + it could not be enumerated, 0 otherwise. + + FILE can either be a TrueType collection file containing TrueType + fonts, or a TrueType font itself. */ + +int +sfnt_enum_font (const char *file) +{ + int fd, rc; + struct sfnt_offset_subtable *subtables; + struct sfnt_ttc_header *ttc; + size_t i; + + /* Now open the font for reading. */ + fd = emacs_open (file, O_RDONLY, 0); + + if (fd == -1) + goto bail; + + /* Read the table directory. */ + subtables = sfnt_read_table_directory (fd); + + if (subtables == (struct sfnt_offset_subtable *) -1) + { + /* This is actually a TrueType container file. Go back to the + beginning and read the TTC header. */ + + if (lseek (fd, 0, SEEK_SET)) + goto bail0; + + ttc = sfnt_read_ttc_header (fd); + + if (!ttc) + goto bail0; + + /* Enumerate each of the fonts in the collection. */ + + for (i = 0; i < ttc->num_fonts; ++i) + { + if (lseek (fd, ttc->offset_table[i], SEEK_SET) + != ttc->offset_table[i]) + continue; + + subtables = sfnt_read_table_directory (fd); + + if (!subtables) + continue; + + sfnt_enum_font_1 (fd, file, subtables, + ttc->offset_table[i]); + xfree (subtables); + } + + /* Always treat reading containers as having been + successful. */ + + emacs_close (fd); + xfree (ttc); + return 0; + } + + if (!subtables) + goto bail0; + + /* Now actually enumerate this font. */ + rc = sfnt_enum_font_1 (fd, file, subtables, 0); xfree (subtables); + emacs_close (fd); + return rc; + bail0: emacs_close (fd); bail: - xfree (desc); return 1; } @@ -1730,6 +1795,12 @@ sfntfont_open (struct frame *f, Lisp_Object font_entity, if (fd == -1) goto bail; + /* Seek to the offset specified. */ + + if (desc->offset + && lseek (fd, desc->offset, SEEK_SET) != desc->offset) + goto bail; + /* Read the offset subtable. */ subtable = sfnt_read_table_directory (fd); diff --git a/xcompile/Makefile.in b/xcompile/Makefile.in index 9f817bb4c53..ca3af4a9586 100644 --- a/xcompile/Makefile.in +++ b/xcompile/Makefile.in @@ -170,7 +170,7 @@ distclean bootstrap-clean: clean # Just in case. rm -rf lib/Makefile lib/gnulib.mk -maintainer-clean: clean +maintainer-clean: distclean bootstrap-clean if [ -e lib/Makefile ]; then \ make -C lib maintainer-clean; \ fi -- 2.39.5