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
* 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!
* 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
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})
@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:
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.
+
\f
This file is part of GNU Emacs.
src_x, src_y, width,
height);
canvas.drawBitmap (bitmap, null, rect, paint);
+ bitmap.recycle ();
}
else
{
paint.setXfermode (overAlu);
canvas.drawBitmap (maskBitmap, null, maskRect, paint);
gc.resetXfermode ();
+
+ /* Recycle this unused bitmap. */
+ maskBitmap.recycle ();
}
canvas.restore ();
--- /dev/null
+/* 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 <https://www.gnu.org/licenses/>. */
+
+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<EmacsButton> 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<EmacsButton> ();
+ 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<Boolean> rc;
+
+ rc = new Holder<Boolean> ();
+ 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;
+ }
+
+\f
+
+ @Override
+ public void
+ onDismiss (DialogInterface dialog)
+ {
+ Log.d (TAG, "onDismiss: " + this);
+
+ if (wasButtonClicked)
+ return;
+
+ EmacsNative.sendContextMenu ((short) 0, 0);
+ }
+};
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)
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
/* 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);
}
}
/* 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 ();
import android.graphics.Canvas;
import android.graphics.Rect;
+import android.os.Build;
+
/* Drawable backed by bitmap. */
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 ();
+ }
};
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;
/* The current context menu. */
private EmacsContextMenu contextMenu;
+ /* The last measured width and height. */
+ private int measuredWidth, measuredHeight;
+
public
EmacsView (EmacsWindow window)
{
{
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);
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;
public synchronized Canvas
getCanvas ()
{
- if (bitmapDirty != null)
+ if (bitmapDirty || bitmap == null)
handleDirtyBitmap ();
return canvas;
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)
{
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)
{
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 ();
+ }
+ }
};
{
/* 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
#include <semaphore.h>
#include <dlfcn.h>
#include <errno.h>
+#include <math.h>
#include <sys/stat.h>
#include <sys/mman.h>
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;
static void *
android_run_select_thread (void *data)
{
- sigset_t signals, sigset;
- int rc;
+ sigset_t signals, waitset;
+ int rc, sig;
sigfillset (&signals);
strerror (errno));
sigdelset (&signals, SIGUSR1);
+ sigemptyset (&waitset);
+ sigaddset (&waitset, SIGUSR1);
while (true)
{
/* 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);
}
}
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;
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. */
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. */
#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);
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);
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)
{
/* 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;
}
/* 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. */
}
}
- Fprint (tem, Qexternal_debugging_output);
-
unblock_input ();
return unbind_to (count, tem);
return unbind_to (count, Qnil);
}
+\f
+
+/* 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
{
#ifndef ANDROID_STUBIFY
android_init_emacs_context_menu ();
+ android_init_emacs_dialog ();
#endif
}
/* 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
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;
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. */
*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;
/* 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 ();
}
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 */
}
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
/* 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
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);
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);
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));
{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
"display-monitors-changed-functions");
DEFSYM (Qcoding, "coding");
+ DEFSYM (Qtouchscreen, "touchscreen");
Fset (Qecho_area_clear_hook, Qnil);
#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. */
#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
#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)
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);
\f
+/* 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;
+}
+
+\f
+
#ifdef TEST
struct sfnt_test_dcontext
unsigned char *string;
struct sfnt_name_record record;
struct sfnt_meta_table *meta;
+ struct sfnt_ttc_header *ttc;
if (argc != 2)
return 1;
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);
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);
}
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;
xfree (hmtx);
xfree (name);
xfree (meta);
+ xfree (ttc);
return 0;
}
\f
+/* 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,
+ };
+
+\f
+
#define SFNT_CEIL_FIXED(fixed) \
(!((fixed) & 0177777) ? (fixed) \
: ((fixed) + 0200000) & 037777600000)
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_ */
#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[] =
{
/* 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;
+
+\f
+
+/* 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);
+
\f
/* Scale each of the four packed bytes in P in the low 16 bits of P by
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
/* 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)
/* 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);
android_damage_window (FRAME_ANDROID_DRAWABLE (s->f),
&text_rectangle);
- /* Release the temporary scanline buffer. */
- SAFE_FREE ();
+#undef MAX_ALLOCA
+}
+
+\f
+
+/* 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;
}
\f
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);
/* 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. */
}
}
-/* 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;
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
xfree (meta);
xfree (name);
xfree (head);
- xfree (subtables);
- emacs_close (fd);
return 0;
bail3:
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;
}
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);
# 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