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