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)
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
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. */
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;
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
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);
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;
= (*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 *
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)
{
{
android_destroy_handle (gc->gcontext);
+ xfree (gc->dashes);
xfree (gc->clip_rects);
xfree (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;
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,
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)
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
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
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),
/* 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
/* 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
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 *);
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)
{
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
{
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. */
{
/* 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);
}
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);