From 886d506a6418ea39fc8b704b91671ff8edcced5a Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 28 Apr 2024 16:58:58 +0800 Subject: [PATCH] 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. : 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. (cherry picked from commit e658a6938e3b7a8a7c0be8b74fbd885787c26df6) --- java/org/gnu/emacs/EmacsDrawLine.java | 109 +++++++++++++-- java/org/gnu/emacs/EmacsDrawRectangle.java | 3 + java/org/gnu/emacs/EmacsGC.java | 12 +- src/android.c | 153 ++++++++++++++++++++- src/androidgui.h | 35 +++++ src/androidterm.c | 100 +++++++++++--- src/xterm.c | 1 - 7 files changed, 382 insertions(+), 31 deletions(-) diff --git a/java/org/gnu/emacs/EmacsDrawLine.java b/java/org/gnu/emacs/EmacsDrawLine.java index 61b7d54d63c..a49fe96c26e 100644 --- a/java/org/gnu/emacs/EmacsDrawLine.java +++ b/java/org/gnu/emacs/EmacsDrawLine.java @@ -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 diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java index a8f68c6530a..e40a7c16068 100644 --- a/java/org/gnu/emacs/EmacsDrawRectangle.java +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -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. */ diff --git a/java/org/gnu/emacs/EmacsGC.java b/java/org/gnu/emacs/EmacsGC.java index b2474c5bd76..ec2b9c9e475 100644 --- a/java/org/gnu/emacs/EmacsGC.java +++ b/java/org/gnu/emacs/EmacsGC.java @@ -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); diff --git a/src/android.c b/src/android.c index 00a77fc398d..2777add5059 100644 --- a/src/android.c +++ b/src/android.c @@ -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 diff --git a/src/androidgui.h b/src/androidgui.h index f941c7cc577..5e4f6ec3989 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -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 *); diff --git a/src/androidterm.c b/src/androidterm.c index 25b7fa97ebc..4549941ee2e 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -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); } diff --git a/src/xterm.c b/src/xterm.c index 505a3d9360a..93d347a77ef 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -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); -- 2.39.5