diff options
Diffstat (limited to 'java/org/gnu/emacs/EmacsSdk7FontDriver.java')
-rw-r--r-- | java/org/gnu/emacs/EmacsSdk7FontDriver.java | 497 |
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; + } +}; |