Implement dots and dashes on Android

* java/org/gnu/emacs/EmacsDrawLine.java (EmacsDrawLine)
(measureLine, polyDashPattern): New function.
(perform): Delegate to polyDashPattern if the line style is not
LineSolid.  Also simplify now that anti-aliasing need no longer
be taken into account.

* java/org/gnu/emacs/EmacsDrawRectangle.java (perform): Mention
omission in commentary.

* java/org/gnu/emacs/EmacsGC.java (EmacsGC): Disable
anti-aliasing in default paint object.
<line_style, line>: New fields.
(markDirty): Apply stroke width.

* src/android.c (android_init_emacs_gc_class): Initialize new
fields.
(android_create_gc, android_free_gc, android_change_gc)
(android_set_dashes, android_get_gc_values):

* src/androidgui.h (enum android_line_style)
(enum android_gc_value_mask, struct android_gc): Introduce line
style, width, dash offset and dash GC attributes.

* src/androidterm.c (android_draw_dash, android_fill_underline)
(android_draw_glyph_string): Port from X.

* src/xterm.c (x_draw_dash): Delete redundant code.
This commit is contained in:
Po Lu 2024-04-28 16:58:58 +08:00
parent b329358334
commit e658a6938e
7 changed files with 382 additions and 31 deletions

View file

@ -25,6 +25,97 @@ import android.graphics.Rect;
public final class EmacsDrawLine
{
/* Return the normalized slope and magnitude of a line whose extrema
are DX and DY removed, on the X and Y axes respectively, from its
origin point. */
private static float[]
measureLine (float dx, float dy)
{
float hypot;
if (dx == 0f && dy == 0f)
return new float[] { 0f, 0f, 0f, };
if (dx == 0f)
return new float[] { 0f, dy > 0f ? 1f : -1f, Math.abs (dy), };
else if (dy == 0f)
return new float[] { dx > 0f ? 1f : -1f, 0f, Math.abs (dx), };
else
{
hypot = (float) Math.hypot (dx, dy);
return new float[] { dx / hypot, dy / hypot, hypot, };
}
}
private static void
polyDashPattern (EmacsGC gc, Canvas canvas, Paint paint, float x0,
float y0, float x1, float y1)
{
int patternTotal, i, offset;
float dx, dy, mag, dash_mag, rem, lx1, ly1;
float[] measured;
boolean which;
/* Compute the total length of this pattern. */
patternTotal = 0;
for (i = 0; i < gc.dashes.length; ++i)
patternTotal += gc.dashes[i];
if ((gc.dashes.length & 1) != 0)
patternTotal += patternTotal;
/* Subtract as much of the offset as does not contribute to the
phase at the first pixel of the line. */
offset = gc.dash_offset % patternTotal;
/* Set I to the first dash that ought to be drawn and WHICH to its
phase. */
i = 0;
which = true;
while (offset >= gc.dashes[i])
{
offset -= gc.dashes[i++];
if (i >= gc.dashes.length)
i = 0;
which = !which;
}
/* Compute the length of the first visible segment. */
dash_mag = gc.dashes[i] - offset;
/* Compute the slope of the line. */
dx = x1 - x0;
dy = y1 - y0;
measured = measureLine (dx, dy);
dx = measured[0];
dy = measured[1];
rem = mag = measured[2];
lx1 = x0;
ly1 = y0;
while (rem > 0f)
{
dash_mag = Math.min (dash_mag, rem);
rem -= dash_mag;
/* End of this segment. */
x1 = (mag - rem) * dx + x0;
y1 = (mag - rem) * dy + y0;
if (which)
canvas.drawLine (lx1, ly1, x1, y1, paint);
which = !which;
/* Start of the next segment. */
lx1 = x1;
ly1 = y1;
i++;
if (i >= gc.dashes.length)
i = 0;
dash_mag = gc.dashes[i];
}
}
public static void
perform (EmacsDrawable drawable, EmacsGC gc,
int x, int y, int x2, int y2)
@ -52,22 +143,20 @@ public final class EmacsDrawLine
if (canvas == null)
return;
paint.setStyle (Paint.Style.FILL);
/* Since drawLine has PostScript style behavior, adjust the
coordinates appropriately.
The left most pixel of a straight line is always partially
filled. Patch it in manually. */
The leftmost pixel of a straight line is always partially filled.
Patch it in manually. */
if (gc.clip_mask == null)
{
canvas.drawLine ((float) x + 0.5f, (float) y + 0.5f,
(float) x2 + 0.5f, (float) y2 + 0.5f,
paint);
if (x2 > x)
canvas.drawRect (new Rect (x, y, x + 1, y + 1), paint);
if (gc.line_style != EmacsGC.GC_LINE_ON_OFF_DASH)
canvas.drawLine ((float) x, (float) y, (float) x2, (float) y2,
paint);
else
polyDashPattern (gc, canvas, paint, (float) x, (float) y,
(float) x2, (float) y2);
}
/* DrawLine with clip mask not implemented; it is not used by

View file

@ -52,6 +52,9 @@ public final class EmacsDrawRectangle
paint = gc.gcPaint;
paint.setStyle (Paint.Style.STROKE);
/* This graphics request, in contrast to X, does not presently
respect the GC's line style. */
if (gc.clip_mask == null)
/* Use canvas.drawRect with a RectF. That seems to reliably
get PostScript behavior. */

View file

@ -46,12 +46,17 @@ public final class EmacsGC extends EmacsHandleObject
public static final int GC_FILL_SOLID = 0;
public static final int GC_FILL_OPAQUE_STIPPLED = 1;
public static final int GC_LINE_SOLID = 0;
public static final int GC_LINE_ON_OFF_DASH = 1;
public static final Xfermode xorAlu, srcInAlu;
public int function, fill_style;
public int foreground, background;
public int clip_x_origin, clip_y_origin;
public int ts_origin_x, ts_origin_y;
public int line_style, line_width;
public int dashes[], dash_offset;
public Rect clip_rects[], real_clip_rects[];
public EmacsPixmap clip_mask, stipple;
public Paint gcPaint;
@ -89,6 +94,10 @@ public final class EmacsGC extends EmacsHandleObject
foreground = 0;
background = 0xffffff;
gcPaint = new Paint ();
/* Android S and above enable anti-aliasing unless explicitly told
otherwise. */
gcPaint.setAntiAlias (false);
}
/* Mark this GC as dirty. Apply parameters to the paint and
@ -119,7 +128,8 @@ public final class EmacsGC extends EmacsHandleObject
clipRectID = ++clip_serial;
}
gcPaint.setStrokeWidth (1f);
/* A line_width of 0 is equivalent to that of 1. */
gcPaint.setStrokeWidth (line_width < 1 ? 1 : line_width);
gcPaint.setColor (foreground | 0xff000000);
gcPaint.setXfermode (function == GC_XOR
? xorAlu : srcInAlu);

View file

@ -177,7 +177,9 @@ static jfieldID emacs_gc_function, emacs_gc_clip_rects;
static jfieldID emacs_gc_clip_x_origin, emacs_gc_clip_y_origin;
static jfieldID emacs_gc_stipple, emacs_gc_clip_mask;
static jfieldID emacs_gc_fill_style, emacs_gc_ts_origin_x;
static jfieldID emacs_gc_ts_origin_y;
static jfieldID emacs_gc_ts_origin_y, emacs_gc_line_style;
static jfieldID emacs_gc_line_width, emacs_gc_dash_offset;
static jfieldID emacs_gc_dashes;
/* The constructor and one function. */
static jmethodID emacs_gc_constructor, emacs_gc_mark_dirty;
@ -3254,6 +3256,22 @@ android_init_emacs_gc_class (void)
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"ts_origin_y", "I");
emacs_gc_line_style
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"line_style", "I");
emacs_gc_line_width
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"line_width", "I");
emacs_gc_dash_offset
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"dash_offset", "I");
emacs_gc_dashes
= (*android_java_env)->GetFieldID (android_java_env,
emacs_gc_class,
"dashes", "[I");
}
struct android_gc *
@ -3285,6 +3303,11 @@ android_create_gc (enum android_gc_value_mask mask,
gc->stipple = ANDROID_NONE;
gc->ts_x_origin = 0;
gc->ts_y_origin = 0;
gc->line_style = ANDROID_LINE_SOLID;
gc->line_width = 0;
gc->dash_offset = 0;
gc->dashes = NULL;
gc->n_segments = 0;
if (!gc->gcontext)
{
@ -3323,6 +3346,7 @@ android_free_gc (struct android_gc *gc)
{
android_destroy_handle (gc->gcontext);
xfree (gc->dashes);
xfree (gc->clip_rects);
xfree (gc);
}
@ -3332,7 +3356,7 @@ android_change_gc (struct android_gc *gc,
enum android_gc_value_mask mask,
struct android_gc_values *values)
{
jobject what, gcontext;
jobject what, gcontext, array;
jboolean clip_changed;
clip_changed = false;
@ -3448,6 +3472,59 @@ android_change_gc (struct android_gc *gc,
gc->ts_y_origin = values->ts_y_origin;
}
if (mask & ANDROID_GC_LINE_STYLE)
{
(*android_java_env)->SetIntField (android_java_env,
gcontext,
emacs_gc_line_style,
values->line_style);
gc->line_style = values->line_style;
}
if (mask & ANDROID_GC_LINE_WIDTH)
{
(*android_java_env)->SetIntField (android_java_env,
gcontext,
emacs_gc_line_width,
values->line_width);
gc->line_width = values->line_width;
}
if (mask & ANDROID_GC_DASH_OFFSET)
{
(*android_java_env)->SetIntField (android_java_env,
gcontext,
emacs_gc_dash_offset,
values->dash_offset);
gc->dash_offset = values->dash_offset;
}
if (mask & ANDROID_GC_DASH_LIST)
{
/* Compare the new dash pattern with the old. */
if (gc->dashes && gc->n_segments == 1
&& gc->dashes[0] == values->dash)
/* If they be identical, nothing needs to change. */
mask &= ~ANDROID_GC_DASH_LIST;
else
{
if (gc->n_segments != 1)
gc->dashes = xrealloc (gc->dashes, sizeof *gc->dashes);
gc->n_segments = 1;
gc->dashes[0] = values->dash;
array = (*android_java_env)->NewIntArray (android_java_env, 1);
android_exception_check ();
(*android_java_env)->SetIntArrayRegion (android_java_env,
array, 0, 1,
(jint *) &values->dash);
(*android_java_env)->SetObjectField (android_java_env,
gcontext,
emacs_gc_dashes,
array);
ANDROID_DELETE_LOCAL_REF (array);
}
}
if (mask)
{
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
@ -3539,6 +3616,75 @@ android_set_clip_rectangles (struct android_gc *gc, int clip_x_origin,
n_clip_rects * sizeof *gc->clip_rects);
}
void
android_set_dashes (struct android_gc *gc, int dash_offset,
int *dash_list, int n)
{
int i;
jobject array, gcontext;
gcontext = android_resolve_handle (gc->gcontext,
ANDROID_HANDLE_GCONTEXT);
if (n == gc->n_segments
&& (!gc->dashes || !memcmp (gc->dashes, dash_list,
sizeof *dash_list * n)))
/* No change in the dash list. */
goto set_offset;
if (!n)
{
/* Reset the dash list to its initial empty state. */
xfree (gc->dashes);
gc->dashes = NULL;
array = NULL;
}
else
{
/* If the size of the array has not changed, it can be reused. */
if (n != gc->n_segments)
{
gc->dashes = xrealloc (gc->dashes, sizeof *gc->dashes * n);
array = (*android_java_env)->NewIntArray (android_java_env, n);
android_exception_check ();
}
else
array = (*android_java_env)->GetObjectField (android_java_env,
gcontext,
emacs_gc_dashes);
/* Copy the list of segments into both arrays. */
for (i = 0; i < n; ++i)
gc->dashes[i] = dash_list[i];
verify (sizeof (int) == sizeof (jint));
(*android_java_env)->SetIntArrayRegion (android_java_env,
array, 0, n,
(jint *) dash_list);
}
/* Replace the dash array in the GContext object if required. */
if (n != gc->n_segments)
{
(*android_java_env)->SetObjectField (android_java_env,
gcontext,
emacs_gc_dashes,
array);
ANDROID_DELETE_LOCAL_REF (array);
}
gc->n_segments = n;
set_offset:
/* And the offset. */
if (dash_offset != gc->dash_offset)
(*android_java_env)->SetIntField (android_java_env,
gcontext,
emacs_gc_dash_offset,
dash_offset);
gc->dash_offset = dash_offset;
}
void
android_reparent_window (android_window w, android_window parent_handle,
int x, int y)
@ -3690,7 +3836,8 @@ android_get_gc_values (struct android_gc *gc,
values->ts_y_origin = gc->ts_y_origin;
/* Fields involving handles are not used by Emacs, and thus not
implemented */
implemented. In addition, the size of GCClipMask and GCDashList is
not static, precluding their retrieval. */
}
void

View file

@ -71,6 +71,10 @@ enum android_gc_value_mask
ANDROID_GC_FILL_STYLE = (1 << 7),
ANDROID_GC_TILE_STIP_X_ORIGIN = (1 << 8),
ANDROID_GC_TILE_STIP_Y_ORIGIN = (1 << 9),
ANDROID_GC_LINE_STYLE = (1 << 10),
ANDROID_GC_LINE_WIDTH = (1 << 11),
ANDROID_GC_DASH_LIST = (1 << 12),
ANDROID_GC_DASH_OFFSET = (1 << 13),
};
enum android_fill_style
@ -79,6 +83,12 @@ enum android_fill_style
ANDROID_FILL_OPAQUE_STIPPLED = 1,
};
enum android_line_style
{
ANDROID_LINE_SOLID = 0,
ANDROID_LINE_ON_OFF_DASH = 1,
};
enum android_window_value_mask
{
ANDROID_CW_BACK_PIXEL = (1 << 1),
@ -114,6 +124,18 @@ struct android_gc_values
/* The tile-stipple X and Y origins. */
int ts_x_origin, ts_y_origin;
/* The line style. */
enum android_line_style line_style;
/* The line width. */
int line_width;
/* Offset in pixels into the dash pattern specified below. */
int dash_offset;
/* One integer providing both segments of a even-odd dash pattern. */
int dash;
};
/* X-like graphics context structure. This is implemented in
@ -152,6 +174,18 @@ struct android_gc
/* The tile-stipple X and Y origins. */
int ts_x_origin, ts_y_origin;
/* The line style. */
enum android_line_style line_style;
/* The line width. */
int line_width;
/* Offset in pixels into the dash pattern specified below. */
int dash_offset;
/* The segments of an even/odd dash pattern. */
int *dashes, n_segments;
};
enum android_swap_action
@ -675,6 +709,7 @@ extern void android_set_clip_rectangles (struct android_gc *,
int, int,
struct android_rectangle *,
int);
extern void android_set_dashes (struct android_gc *, int, int *, int);
extern void android_change_gc (struct android_gc *,
enum android_gc_value_mask,
struct android_gc_values *);

View file

@ -4031,6 +4031,80 @@ android_draw_glyphless_glyph_string_foreground (struct glyph_string *s)
s->char2b = NULL;
}
/* Draw a dashed underline of thickness THICKNESS and width WIDTH onto F
at a vertical offset of OFFSET from the position of the glyph string
S, with each segment SEGMENT pixels in length. */
static void
android_draw_dash (struct frame *f, struct glyph_string *s, int width,
int segment, int offset, int thickness)
{
struct android_gc *gc;
struct android_gc_values gcv;
int y_center;
/* Configure the GC, the dash pattern and a suitable offset. */
gc = s->gc;
gcv.line_style = ANDROID_LINE_ON_OFF_DASH;
gcv.line_width = thickness;
android_change_gc (s->gc, (ANDROID_GC_LINE_STYLE
| ANDROID_GC_LINE_WIDTH), &gcv);
android_set_dashes (s->gc, s->x, &segment, 1);
/* Offset the origin of the line by half the line width. */
y_center = s->ybase + offset + thickness / 2;
android_draw_line (FRAME_ANDROID_WINDOW (f), gc,
s->x, y_center, s->x + width, y_center);
/* Restore the initial line style. */
gcv.line_style = ANDROID_LINE_SOLID;
gcv.line_width = 1;
android_change_gc (s->gc, (ANDROID_GC_LINE_STYLE
| ANDROID_GC_LINE_WIDTH), &gcv);
}
/* Draw an underline of STYLE onto F at an offset of POSITION from the
baseline of the glyph string S, DECORATION_WIDTH in length, and
THICKNESS in height. */
static void
android_fill_underline (struct frame *f, struct glyph_string *s,
enum face_underline_type style, int position,
int decoration_width, int thickness)
{
int segment;
segment = thickness * 3;
switch (style)
{
/* FACE_UNDERLINE_DOUBLE_LINE is treated identically to SINGLE, as
the second line will be filled by another invocation of this
function. */
case FACE_UNDERLINE_SINGLE:
case FACE_UNDERLINE_DOUBLE_LINE:
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f),
s->gc, s->x, s->ybase + position,
decoration_width, thickness);
break;
case FACE_UNDERLINE_DOTS:
segment = thickness;
FALLTHROUGH;
case FACE_UNDERLINE_DASHES:
android_draw_dash (f, s, decoration_width, segment, position,
thickness);
break;
case FACE_NO_UNDERLINE:
case FACE_UNDERLINE_WAVE:
default:
emacs_abort ();
}
}
static void
android_draw_glyph_string (struct glyph_string *s)
{
@ -4167,16 +4241,13 @@ android_draw_glyph_string (struct glyph_string *s)
android_set_foreground (s->gc, xgcv.foreground);
}
}
else if (s->face->underline == FACE_UNDERLINE_SINGLE
|| s->face->underline == FACE_UNDERLINE_DOUBLE_LINE)
else if (s->face->underline >= FACE_UNDERLINE_SINGLE)
{
unsigned long thickness, position;
int y;
if (s->prev
&& ((s->prev->face->underline == FACE_UNDERLINE_SINGLE)
|| (s->prev->face->underline
== FACE_UNDERLINE_DOUBLE_LINE))
&& (s->prev->face->underline != FACE_UNDERLINE_WAVE
&& s->prev->face->underline >= FACE_UNDERLINE_SINGLE)
&& (s->prev->face->underline_at_descent_line_p
== s->face->underline_at_descent_line_p)
&& (s->prev->face->underline_pixels_above_descent_line
@ -4257,18 +4328,16 @@ android_draw_glyph_string (struct glyph_string *s)
{
struct android_gc_values xgcv;
y = s->ybase + position;
if (s->face->underline_defaulted_p)
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc,
s->x, y, decoration_width, thickness);
else
if (!s->face->underline_defaulted_p)
{
android_get_gc_values (s->gc, ANDROID_GC_FOREGROUND, &xgcv);
android_set_foreground (s->gc, s->face->underline_color);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc,
s->x, y, decoration_width, thickness);
}
android_fill_underline (s->f, s, s->face->underline,
position, decoration_width,
thickness);
/* Place a second underline above the first if this was
requested in the face specification. */
@ -4276,9 +4345,8 @@ android_draw_glyph_string (struct glyph_string *s)
{
/* Compute the position of the second underline. */
position = position - thickness - 1;
y = s->ybase + position;
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f),
s->gc, s->x, y, decoration_width,
android_fill_underline (s->f, s, s->face->underline,
position, decoration_width,
thickness);
}

View file

@ -10851,7 +10851,6 @@ x_draw_dash (struct frame *f, struct glyph_string *s, int width,
gc = s->gc;
display = FRAME_X_DISPLAY (f);
XGetGCValues (display, s->gc, GCLineStyle, &gcv);
gcv.line_style = LineOnOffDash;
gcv.line_width = thickness;
XChangeGC (display, s->gc, GCLineStyle | GCLineWidth, &gcv);