summaryrefslogtreecommitdiff
path: root/java/org/gnu/emacs/EmacsSdk7FontDriver.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/org/gnu/emacs/EmacsSdk7FontDriver.java')
-rw-r--r--java/org/gnu/emacs/EmacsSdk7FontDriver.java497
1 files changed, 497 insertions, 0 deletions
diff --git a/java/org/gnu/emacs/EmacsSdk7FontDriver.java b/java/org/gnu/emacs/EmacsSdk7FontDriver.java
new file mode 100644
index 00000000000..49d9514c104
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsSdk7FontDriver.java
@@ -0,0 +1,497 @@
+/* Font backend for Android terminals. -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023-2024 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.io.File;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.Canvas;
+
+import android.util.Log;
+
+
+
+/* EmacsSdk7FontDriver implements a fallback font driver under
+ Android. This font driver is enabled when the SFNT font driver (in
+ sfntfont-android.c) proves incapable of locating any fonts, which
+ has hitherto not been observed in practice.
+
+ This font driver does not supply each font installed on the system,
+ in lieu of which it provides a list of fonts for each conceivable
+ style and sub-type of the system's own Typefaces, which arises from
+ Android's absence of suitable APIs for loading individual font
+ files. */
+
+public class EmacsSdk7FontDriver extends EmacsFontDriver
+{
+ private static final String TOFU_STRING = "\uDB3F\uDFFD";
+ private static final String EM_STRING = "m";
+ private static final String TAG = "EmacsSdk7FontDriver";
+
+ protected static final class Sdk7Typeface
+ {
+ /* The typeface and paint. */
+ public Typeface typeface;
+ public Paint typefacePaint;
+ public String familyName;
+ public int slant, width, weight, spacing;
+
+ public
+ Sdk7Typeface (String familyName, Typeface typeface)
+ {
+ String style, testString;
+ int index, measured, i;
+ float[] widths;
+
+ /* Initialize the font style fields and create a paint object
+ linked with that typeface. */
+
+ slant = NORMAL;
+ weight = REGULAR;
+ width = UNSPECIFIED;
+ spacing = PROPORTIONAL;
+
+ this.typeface = typeface;
+ this.familyName = familyName;
+
+ typefacePaint = new Paint ();
+ typefacePaint.setAntiAlias (true);
+ typefacePaint.setTypeface (typeface);
+ }
+
+ @Override
+ public String
+ toString ()
+ {
+ return ("Sdk7Typeface ("
+ + String.valueOf (familyName) + ", "
+ + String.valueOf (slant) + ", "
+ + String.valueOf (width) + ", "
+ + String.valueOf (weight) + ", "
+ + String.valueOf (spacing) + ")");
+ }
+ };
+
+ protected static final class Sdk7FontEntity extends FontEntity
+ {
+ /* The typeface. */
+ public Sdk7Typeface typeface;
+
+ @SuppressWarnings ("deprecation")
+ public
+ Sdk7FontEntity (Sdk7Typeface typeface)
+ {
+ foundry = "Google";
+ family = typeface.familyName;
+ adstyle = null;
+ weight = typeface.weight;
+ slant = typeface.slant;
+ spacing = typeface.spacing;
+ width = typeface.width;
+ dpi = Math.round (EmacsService.SERVICE.metrics.scaledDensity * 160f);
+
+ this.typeface = typeface;
+ }
+ };
+
+ protected final class Sdk7FontObject extends FontObject
+ {
+ /* The typeface. */
+ public Sdk7Typeface typeface;
+
+ @SuppressWarnings ("deprecation")
+ public
+ Sdk7FontObject (Sdk7Typeface typeface, int pixelSize)
+ {
+ float totalWidth;
+ String testWidth, testString;
+
+ this.typeface = typeface;
+ this.pixelSize = pixelSize;
+
+ family = typeface.familyName;
+ adstyle = null;
+ weight = typeface.weight;
+ slant = typeface.slant;
+ spacing = typeface.spacing;
+ width = typeface.width;
+ dpi = Math.round (EmacsService.SERVICE.metrics.scaledDensity * 160f);
+
+ /* Compute the ascent and descent. */
+ typeface.typefacePaint.setTextSize (pixelSize);
+ ascent
+ = Math.round (-typeface.typefacePaint.ascent ());
+ descent
+ = Math.round (typeface.typefacePaint.descent ());
+
+ /* Compute the average width. */
+ testString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ totalWidth = typeface.typefacePaint.measureText (testString);
+
+ if (totalWidth > 0)
+ avgwidth = Math.round (totalWidth
+ / testString.length ());
+
+ /* Android doesn't expose the font average width and height
+ information, so this will have to do. */
+ minWidth = maxWidth = avgwidth;
+
+ /* This is different from avgwidth in the font spec! */
+ averageWidth = avgwidth;
+
+ /* Set the space width. */
+ totalWidth = typeface.typefacePaint.measureText (" ");
+ spaceWidth = Math.round (totalWidth);
+
+ /* Set the height and default ascent. */
+ height = ascent + descent;
+ defaultAscent = ascent;
+ }
+ };
+
+ private String[] fontFamilyList;
+ private Sdk7Typeface[] typefaceList;
+ private Sdk7Typeface fallbackTypeface;
+
+ public
+ EmacsSdk7FontDriver ()
+ {
+ int i;
+ Typeface typeface;
+
+ typefaceList = new Sdk7Typeface[5];
+
+ /* Initialize the default monospace and Sans Serif typefaces.
+ Initialize the same typeface with various distinct styles. */
+ fallbackTypeface = new Sdk7Typeface ("Sans Serif",
+ Typeface.DEFAULT);
+ typefaceList[1] = fallbackTypeface;
+
+ fallbackTypeface = new Sdk7Typeface ("Sans Serif",
+ Typeface.create (Typeface.DEFAULT,
+ Typeface.BOLD));
+ fallbackTypeface.weight = BOLD;
+ typefaceList[2] = fallbackTypeface;
+
+ fallbackTypeface = new Sdk7Typeface ("Sans Serif",
+ Typeface.create (Typeface.DEFAULT,
+ Typeface.ITALIC));
+ fallbackTypeface.slant = ITALIC;
+ typefaceList[3] = fallbackTypeface;
+
+ fallbackTypeface
+ = new Sdk7Typeface ("Sans Serif",
+ Typeface.create (Typeface.DEFAULT,
+ Typeface.BOLD_ITALIC));
+ fallbackTypeface.weight = BOLD;
+ fallbackTypeface.slant = ITALIC;
+ typefaceList[4] = fallbackTypeface;
+
+ fallbackTypeface = new Sdk7Typeface ("Monospace",
+ Typeface.MONOSPACE);
+ fallbackTypeface.spacing = MONO;
+ typefaceList[0] = fallbackTypeface;
+
+ fontFamilyList = new String[] { "Monospace", "Sans Serif", };
+ }
+
+ private boolean
+ checkMatch (Sdk7Typeface typeface, FontSpec fontSpec)
+ {
+ if (fontSpec.family != null
+ && !fontSpec.family.equals (typeface.familyName))
+ return false;
+
+ if (fontSpec.slant != null
+ && !fontSpec.weight.equals (typeface.weight))
+ return false;
+
+ if (fontSpec.spacing != null
+ && !fontSpec.spacing.equals (typeface.spacing))
+ return false;
+
+ if (fontSpec.weight != null
+ && !fontSpec.weight.equals (typeface.weight))
+ return false;
+
+ if (fontSpec.width != null
+ && !fontSpec.width.equals (typeface.width))
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public FontEntity[]
+ list (FontSpec fontSpec)
+ {
+ LinkedList<FontEntity> list;
+ int i;
+
+ list = new LinkedList<FontEntity> ();
+
+ for (i = 0; i < typefaceList.length; ++i)
+ {
+ if (checkMatch (typefaceList[i], fontSpec))
+ list.add (new Sdk7FontEntity (typefaceList[i]));
+ }
+
+ return list.toArray (new FontEntity[0]);
+ }
+
+ @Override
+ public FontEntity
+ match (FontSpec fontSpec)
+ {
+ FontEntity[] entities;
+ int i;
+
+ entities = this.list (fontSpec);
+
+ if (entities.length == 0)
+ return new Sdk7FontEntity (fallbackTypeface);
+
+ return entities[0];
+ }
+
+ @Override
+ public String[]
+ listFamilies ()
+ {
+ return fontFamilyList;
+ }
+
+ @Override
+ public FontObject
+ openFont (FontEntity fontEntity, int pixelSize)
+ {
+ return new Sdk7FontObject (((Sdk7FontEntity) fontEntity).typeface,
+ pixelSize);
+ }
+
+ @Override
+ public int
+ hasChar (FontSpec font, int charCode)
+ {
+ float missingGlyphWidth, width;
+ Rect rect1, rect2;
+ Paint paint;
+ Sdk7FontObject fontObject;
+
+ /* Ignore characters outside the BMP. */
+
+ if (charCode > 65535)
+ return 0;
+
+ if (font instanceof Sdk7FontObject)
+ {
+ fontObject = (Sdk7FontObject) font;
+ paint = fontObject.typeface.typefacePaint;
+ }
+ else
+ paint = ((Sdk7FontEntity) font).typeface.typefacePaint;
+
+ paint.setTextSize (10);
+
+ if (Character.isWhitespace ((char) charCode))
+ return 1;
+
+ missingGlyphWidth = paint.measureText (TOFU_STRING);
+ width = paint.measureText ("" + charCode);
+
+ if (width == 0f)
+ return 0;
+
+ if (width != missingGlyphWidth)
+ return 1;
+
+ rect1 = new Rect ();
+ rect2 = new Rect ();
+
+ paint.getTextBounds (TOFU_STRING, 0, TOFU_STRING.length (),
+ rect1);
+ paint.getTextBounds ("" + (char) charCode, 0, 1, rect2);
+ return rect1.equals (rect2) ? 0 : 1;
+ }
+
+ private void
+ textExtents1 (Sdk7FontObject font, int code, FontMetrics metrics,
+ Paint paint, Rect bounds)
+ {
+ char[] text;
+
+ text = new char[1];
+ text[0] = (char) code;
+
+ paint.getTextBounds (text, 0, 1, bounds);
+
+ /* bounds is the bounding box of the glyph corresponding to CODE.
+ Translate these into XCharStruct values.
+
+ The origin is at 0, 0, and lbearing is the distance counting
+ rightwards from the origin to the left most pixel in the glyph
+ raster. rbearing is the distance between the origin and the
+ rightmost pixel in the glyph raster. ascent is the distance
+ counting upwards between the the topmost pixel in the glyph
+ raster. descent is the distance (once again counting
+ downwards) between the origin and the bottommost pixel in the
+ glyph raster.
+
+ width is the distance between the origin and the origin of any
+ character to the right. */
+
+ metrics.lbearing = (short) bounds.left;
+ metrics.rbearing = (short) bounds.right;
+ metrics.ascent = (short) -bounds.top;
+ metrics.descent = (short) bounds.bottom;
+ metrics.width = (short) paint.measureText ("" + text[0]);
+ }
+
+ @Override
+ public void
+ textExtents (FontObject font, int code[], FontMetrics fontMetrics)
+ {
+ int i;
+ Paint paintCache;
+ Rect boundsCache;
+ Sdk7FontObject fontObject;
+ char[] text;
+ float width;
+
+ fontObject = (Sdk7FontObject) font;
+ paintCache = fontObject.typeface.typefacePaint;
+ paintCache.setTextSize (fontObject.pixelSize);
+ boundsCache = new Rect ();
+
+ if (code.length == 0)
+ {
+ fontMetrics.lbearing = 0;
+ fontMetrics.rbearing = 0;
+ fontMetrics.ascent = 0;
+ fontMetrics.descent = 0;
+ fontMetrics.width = 0;
+ }
+ else if (code.length == 1)
+ textExtents1 ((Sdk7FontObject) font, code[0], fontMetrics,
+ paintCache, boundsCache);
+ else
+ {
+ text = new char[code.length];
+
+ for (i = 0; i < code.length; ++i)
+ text[i] = (char) code[i];
+
+ paintCache.getTextBounds (text, 0, code.length,
+ boundsCache);
+ width = paintCache.measureText (text, 0, code.length);
+
+ fontMetrics.lbearing = (short) boundsCache.left;
+ fontMetrics.rbearing = (short) boundsCache.right;
+ fontMetrics.ascent = (short) -boundsCache.top;
+ fontMetrics.descent = (short) boundsCache.bottom;
+ fontMetrics.width = (short) Math.round (width);
+ }
+ }
+
+ @Override
+ public int
+ encodeChar (FontObject fontObject, int charCode)
+ {
+ if (charCode > 65535)
+ return FONT_INVALID_CODE;
+
+ return charCode;
+ }
+
+ @Override
+ public int
+ draw (FontObject fontObject, EmacsGC gc, EmacsDrawable drawable,
+ int[] chars, int x, int y, int backgroundWidth,
+ boolean withBackground)
+ {
+ Rect backgroundRect, bounds;
+ Sdk7FontObject sdk7FontObject;
+ int i;
+ Canvas canvas;
+ Paint paint;
+ char[] array;
+
+ sdk7FontObject = (Sdk7FontObject) fontObject;
+
+ backgroundRect = new Rect ();
+ backgroundRect.top = y - sdk7FontObject.ascent;
+ backgroundRect.left = x;
+ backgroundRect.right = x + backgroundWidth;
+ backgroundRect.bottom = y + sdk7FontObject.descent;
+
+ canvas = drawable.lockCanvas (gc);
+
+ if (canvas == null)
+ return 0;
+
+ paint = gc.gcPaint;
+ paint.setStyle (Paint.Style.FILL);
+
+ if (withBackground)
+ {
+ paint.setColor (gc.background | 0xff000000);
+ canvas.drawRect (backgroundRect, paint);
+ paint.setColor (gc.foreground | 0xff000000);
+ }
+
+ paint.setTextSize (sdk7FontObject.pixelSize);
+ paint.setTypeface (sdk7FontObject.typeface.typeface);
+ paint.setAntiAlias (true);
+
+ /* Android applies kerning to non-monospaced fonts by default,
+ which brings the dimensions of strings drawn via `drawText' out
+ of agreement with measurements previously provided to redisplay
+ by textExtents. To avert such disaster, draw each character
+ individually, advancing the origin point by hand. */
+
+ bounds = new Rect ();
+ array = new char[1];
+
+ for (i = 0; i < chars.length; ++i)
+ {
+ /* Retrieve the text bounds for this character so as to
+ compute the damage rectangle. */
+ array[0] = (char) chars[i];
+ paint.getTextBounds (array, 0, 1, bounds);
+ bounds.offset (x, y);
+ backgroundRect.union (bounds);
+
+ /* Draw this character. */
+ canvas.drawText (array, 0, 1, x, y, paint);
+
+ /* Advance the origin point by that much. */
+ x += paint.measureText ("" + array[0]);
+ }
+
+ drawable.damageRect (backgroundRect);
+ paint.setAntiAlias (false);
+ return 1;
+ }
+};