From 921c0a16ce20828043b3a9df3e0fb6c3a4ecbd74 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 14 Sep 2023 09:57:09 +0800 Subject: [PATCH] Improve the Android last resort font driver * java/org/gnu/emacs/EmacsSdk7FontDriver.java (Sdk7Typeface): Rename fileName to familyName and cease attempting to infer a style from it. (EmacsSdk7FontDriver): Employ preset typefaces rather than enumerating each typeface within the system fonts directory. (draw): Circumvent kerning difficulties by advancing past each character individually. --- java/org/gnu/emacs/EmacsSdk7FontDriver.java | 194 +++++++------------- 1 file changed, 71 insertions(+), 123 deletions(-) diff --git a/java/org/gnu/emacs/EmacsSdk7FontDriver.java b/java/org/gnu/emacs/EmacsSdk7FontDriver.java index 97969585d16..b8aecbe7c2d 100644 --- a/java/org/gnu/emacs/EmacsSdk7FontDriver.java +++ b/java/org/gnu/emacs/EmacsSdk7FontDriver.java @@ -31,6 +31,19 @@ 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"; @@ -46,106 +59,26 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver public int slant, width, weight, spacing; public - Sdk7Typeface (String fileName, Typeface typeface) + 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); - - /* For the calls to measureText below. */ - typefacePaint.setTextSize (10.0f); - - /* Parse the file name into some useful data. First, strip off - the extension. */ - fileName = fileName.split ("\\.", 2)[0]; - - /* Next, split the file name by dashes. Everything before the - last dash is part of the family name. */ - index = fileName.lastIndexOf ("-"); - - if (index > 0) - { - style = fileName.substring (index + 1, fileName.length ()); - familyName = fileName.substring (0, index); - - /* Look for something describing the weight. */ - if (style.contains ("Thin")) - weight = THIN; - else if (style.contains ("UltraLight")) - weight = ULTRA_LIGHT; - else if (style.contains ("SemiLight")) - weight = SEMI_LIGHT; - else if (style.contains ("Light")) - weight = LIGHT; - else if (style.contains ("Medium")) - weight = MEDIUM; - else if (style.contains ("SemiBold")) - weight = SEMI_BOLD; - else if (style.contains ("ExtraBold")) - weight = EXTRA_BOLD; - else if (style.contains ("Bold")) - weight = BOLD; - else if (style.contains ("Black")) - weight = BLACK; - else if (style.contains ("UltraHeavy")) - weight = ULTRA_HEAVY; - - /* And the slant. */ - if (style.contains ("ReverseOblique")) - slant = OBLIQUE; - else if (style.contains ("ReverseItalic")) - slant = REVERSE_ITALIC; - else if (style.contains ("Italic")) - slant = ITALIC; - else if (style.contains ("Oblique")) - slant = OBLIQUE; - - /* Finally, the width. */ - if (style.contains ("UltraCondensed")) - width = ULTRA_CONDENSED; - else if (style.contains ("ExtraCondensed")) - width = EXTRA_CONDENSED; - else if (style.contains ("SemiCondensed")) - width = SEMI_CONDENSED; - else if (style.contains ("Condensed")) - width = CONDENSED; - else if (style.contains ("SemiExpanded")) - width = SEMI_EXPANDED; - else if (style.contains ("ExtraExpanded")) - width = EXTRA_EXPANDED; - else if (style.contains ("UltraExpanded")) - width = ULTRA_EXPANDED; - else if (style.contains ("Expanded")) - width = EXPANDED; - - /* Guess the spacing information. */ - testString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - widths = new float[testString.length ()]; - - measured = typefacePaint.getTextWidths (testString, - 0, testString.length (), - widths); - spacing = MONO; - for (i = 0; i < measured; ++i) - { - if (i != 0 && widths[i - 1] != widths[i]) - /* This isn't a monospace font. */ - spacing = PROPORTIONAL; - } - } - else - familyName = fileName; } @Override @@ -244,43 +177,42 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver EmacsSdk7FontDriver () { int i; - File systemFontsDirectory, fontFile; Typeface typeface; - systemFontsDirectory = new File ("/system/fonts"); - - fontFamilyList = systemFontsDirectory.list (); - - /* If that returned null, replace it with an empty array. */ - fontFamilyList = new String[0]; + typefaceList = new Sdk7Typeface[5]; - typefaceList = new Sdk7Typeface[fontFamilyList.length + 3]; - - /* It would be nice to avoid opening each and every font upon - startup. But that doesn't seem to be possible on - Android. */ + /* 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; - for (i = 0; i < fontFamilyList.length; ++i) - { - fontFile = new File (systemFontsDirectory, - fontFamilyList[i]); - typeface = Typeface.createFromFile (fontFile); - typefaceList[i] = new Sdk7Typeface (fontFile.getName (), - typeface); - } + fallbackTypeface = new Sdk7Typeface ("Sans Serif", + Typeface.create (Typeface.DEFAULT, + Typeface.BOLD)); + fallbackTypeface.weight = BOLD; + typefaceList[2] = fallbackTypeface; - /* Initialize the default monospace and serif typefaces. */ - fallbackTypeface = new Sdk7Typeface ("monospace", - Typeface.MONOSPACE); - typefaceList[fontFamilyList.length] = 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); - typefaceList[fontFamilyList.length + 1] = fallbackTypeface; + fallbackTypeface.spacing = MONO; + typefaceList[0] = fallbackTypeface; - fallbackTypeface = new Sdk7Typeface ("Sans Serif", - Typeface.DEFAULT); - typefaceList[fontFamilyList.length + 2] = fallbackTypeface; + fontFamilyList = new String[] { "Monospace", "Sans Serif", }; } private boolean @@ -491,16 +423,12 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver { Rect backgroundRect, bounds; Sdk7FontObject sdk7FontObject; - char[] charsArray; int i; Canvas canvas; Paint paint; + char[] array; sdk7FontObject = (Sdk7FontObject) fontObject; - charsArray = new char[chars.length]; - - for (i = 0; i < chars.length; ++i) - charsArray[i] = (char) chars[i]; backgroundRect = new Rect (); backgroundRect.top = y - sdk7FontObject.ascent; @@ -526,13 +454,33 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver paint.setTextSize (sdk7FontObject.pixelSize); paint.setTypeface (sdk7FontObject.typeface.typeface); paint.setAntiAlias (true); - canvas.drawText (charsArray, 0, chars.length, x, y, paint); + + /* 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 (); - paint.getTextBounds (charsArray, 0, chars.length, bounds); - bounds.offset (x, y); - bounds.union (backgroundRect); - drawable.damageRect (bounds); + 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; } -- 2.39.5