root/java/org/gnu/emacs/EmacsSdk7FontDriver.java

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. toString
  2. checkMatch
  3. list
  4. match
  5. listFamilies
  6. openFont
  7. hasChar
  8. textExtents1
  9. textExtents
  10. encodeChar
  11. draw

     1 /* Font backend for Android terminals.  -*- c-file-style: "GNU" -*-
     2 
     3 Copyright (C) 2023 Free Software Foundation, Inc.
     4 
     5 This file is part of GNU Emacs.
     6 
     7 GNU Emacs is free software: you can redistribute it and/or modify
     8 it under the terms of the GNU General Public License as published by
     9 the Free Software Foundation, either version 3 of the License, or (at
    10 your option) any later version.
    11 
    12 GNU Emacs is distributed in the hope that it will be useful,
    13 but WITHOUT ANY WARRANTY; without even the implied warranty of
    14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    15 GNU General Public License for more details.
    16 
    17 You should have received a copy of the GNU General Public License
    18 along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
    19 
    20 package org.gnu.emacs;
    21 
    22 import java.io.File;
    23 
    24 import java.util.LinkedList;
    25 import java.util.List;
    26 
    27 import android.graphics.Paint;
    28 import android.graphics.Rect;
    29 import android.graphics.Typeface;
    30 import android.graphics.Canvas;
    31 
    32 import android.util.Log;
    33 
    34 public class EmacsSdk7FontDriver extends EmacsFontDriver
    35 {
    36   private static final String TOFU_STRING = "\uDB3F\uDFFD";
    37   private static final String EM_STRING   = "m";
    38   private static final String TAG         = "EmacsSdk7FontDriver";
    39 
    40   protected static final class Sdk7Typeface
    41   {
    42     /* The typeface and paint.  */
    43     public Typeface typeface;
    44     public Paint typefacePaint;
    45     public String familyName;
    46     public int slant, width, weight, spacing;
    47 
    48     public
    49     Sdk7Typeface (String fileName, Typeface typeface)
    50     {
    51       String style, testString;
    52       int index, measured, i;
    53       float[] widths;
    54 
    55       slant = NORMAL;
    56       weight = REGULAR;
    57       width = UNSPECIFIED;
    58       spacing = PROPORTIONAL;
    59 
    60       this.typeface = typeface;
    61 
    62       typefacePaint = new Paint ();
    63       typefacePaint.setAntiAlias (true);
    64       typefacePaint.setTypeface (typeface);
    65 
    66       /* For the calls to measureText below.  */
    67       typefacePaint.setTextSize (10.0f);
    68 
    69       /* Parse the file name into some useful data.  First, strip off
    70          the extension.  */
    71       fileName = fileName.split ("\\.", 2)[0];
    72 
    73       /* Next, split the file name by dashes.  Everything before the
    74          last dash is part of the family name.  */
    75       index = fileName.lastIndexOf ("-");
    76 
    77       if (index > 0)
    78         {
    79           style = fileName.substring (index + 1, fileName.length ());
    80           familyName = fileName.substring (0, index);
    81 
    82           /* Look for something describing the weight.  */
    83           if (style.contains ("Thin"))
    84             weight = THIN;
    85           else if (style.contains ("UltraLight"))
    86             weight = ULTRA_LIGHT;
    87           else if (style.contains ("SemiLight"))
    88             weight = SEMI_LIGHT;
    89           else if (style.contains ("Light"))
    90             weight = LIGHT;
    91           else if (style.contains ("Medium"))
    92             weight = MEDIUM;
    93           else if (style.contains ("SemiBold"))
    94             weight = SEMI_BOLD;
    95           else if (style.contains ("ExtraBold"))
    96             weight = EXTRA_BOLD;
    97           else if (style.contains ("Bold"))
    98             weight = BOLD;
    99           else if (style.contains ("Black"))
   100             weight = BLACK;
   101           else if (style.contains ("UltraHeavy"))
   102             weight = ULTRA_HEAVY;
   103 
   104           /* And the slant.  */
   105           if (style.contains ("ReverseOblique"))
   106             slant = OBLIQUE;
   107           else if (style.contains ("ReverseItalic"))
   108             slant = REVERSE_ITALIC;
   109           else if (style.contains ("Italic"))
   110             slant = ITALIC;
   111           else if (style.contains ("Oblique"))
   112             slant = OBLIQUE;
   113 
   114           /* Finally, the width.  */
   115           if (style.contains ("UltraCondensed"))
   116             width = ULTRA_CONDENSED;
   117           else if (style.contains ("ExtraCondensed"))
   118             width = EXTRA_CONDENSED;
   119           else if (style.contains ("SemiCondensed"))
   120             width = SEMI_CONDENSED;
   121           else if (style.contains ("Condensed"))
   122             width = CONDENSED;
   123           else if (style.contains ("SemiExpanded"))
   124             width = SEMI_EXPANDED;
   125           else if (style.contains ("ExtraExpanded"))
   126             width = EXTRA_EXPANDED;
   127           else if (style.contains ("UltraExpanded"))
   128             width = ULTRA_EXPANDED;
   129           else if (style.contains ("Expanded"))
   130             width = EXPANDED;
   131 
   132           /* Guess the spacing information.  */
   133           testString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
   134           widths = new float[testString.length ()];
   135 
   136           measured = typefacePaint.getTextWidths (testString,
   137                                                   0, testString.length (),
   138                                                   widths);
   139           spacing = MONO;
   140           for (i = 0; i < measured; ++i)
   141             {
   142               if (i != 0 && widths[i - 1] != widths[i])
   143                 /* This isn't a monospace font.  */
   144                 spacing = PROPORTIONAL;
   145             }
   146         }
   147       else
   148         familyName = fileName;
   149     }
   150 
   151     @Override
   152     public String
   153     toString ()
   154     {
   155       return ("Sdk7Typeface ("
   156               + String.valueOf (familyName) + ", "
   157               + String.valueOf (slant) + ", "
   158               + String.valueOf (width) + ", "
   159               + String.valueOf (weight) + ", "
   160               + String.valueOf (spacing) + ")");
   161     }
   162   };
   163 
   164   protected static final class Sdk7FontEntity extends FontEntity
   165   {
   166     /* The typeface.  */
   167     public Sdk7Typeface typeface;
   168 
   169     public
   170     Sdk7FontEntity (Sdk7Typeface typeface)
   171     {
   172       foundry = "Google";
   173       family = typeface.familyName;
   174       adstyle = null;
   175       weight = typeface.weight;
   176       slant = typeface.slant;
   177       spacing = typeface.spacing;
   178       width = typeface.width;
   179       dpi = Math.round (EmacsService.SERVICE.metrics.scaledDensity * 160f);
   180 
   181       this.typeface = typeface;
   182     }
   183   };
   184 
   185   protected final class Sdk7FontObject extends FontObject
   186   {
   187     /* The typeface.  */
   188     public Sdk7Typeface typeface;
   189 
   190     public
   191     Sdk7FontObject (Sdk7Typeface typeface, int pixelSize)
   192     {
   193       float totalWidth;
   194       String testWidth, testString;
   195 
   196       this.typeface = typeface;
   197       this.pixelSize = pixelSize;
   198 
   199       family = typeface.familyName;
   200       adstyle = null;
   201       weight = typeface.weight;
   202       slant = typeface.slant;
   203       spacing = typeface.spacing;
   204       width = typeface.width;
   205       dpi = Math.round (EmacsService.SERVICE.metrics.scaledDensity * 160f);
   206 
   207       /* Compute the ascent and descent.  */
   208       typeface.typefacePaint.setTextSize (pixelSize);
   209       ascent
   210         = Math.round (-typeface.typefacePaint.ascent ());
   211       descent
   212         = Math.round (typeface.typefacePaint.descent ());
   213 
   214       /* Compute the average width.  */
   215       testString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
   216       totalWidth = typeface.typefacePaint.measureText (testString);
   217 
   218       if (totalWidth > 0)
   219         avgwidth = Math.round (totalWidth
   220                                / testString.length ());
   221 
   222       /* Android doesn't expose the font average width and height
   223          information, so this will have to do.  */
   224       minWidth = maxWidth = avgwidth;
   225 
   226       /* This is different from avgwidth in the font spec! */
   227       averageWidth = avgwidth;
   228 
   229       /* Set the space width.  */
   230       totalWidth = typeface.typefacePaint.measureText (" ");
   231       spaceWidth = Math.round (totalWidth);
   232 
   233       /* Set the height and default ascent.  */
   234       height = ascent + descent;
   235       defaultAscent = ascent;
   236     }
   237   };
   238 
   239   private String[] fontFamilyList;
   240   private Sdk7Typeface[] typefaceList;
   241   private Sdk7Typeface fallbackTypeface;
   242 
   243   public
   244   EmacsSdk7FontDriver ()
   245   {
   246     int i;
   247     File systemFontsDirectory, fontFile;
   248     Typeface typeface;
   249 
   250     systemFontsDirectory = new File ("/system/fonts");
   251 
   252     fontFamilyList = systemFontsDirectory.list ();
   253 
   254     /* If that returned null, replace it with an empty array.  */
   255     fontFamilyList = new String[0];
   256 
   257     typefaceList = new Sdk7Typeface[fontFamilyList.length + 3];
   258 
   259     /* It would be nice to avoid opening each and every font upon
   260        startup.  But that doesn't seem to be possible on
   261        Android.  */
   262 
   263     for (i = 0; i < fontFamilyList.length; ++i)
   264       {
   265         fontFile = new File (systemFontsDirectory,
   266                              fontFamilyList[i]);
   267         typeface = Typeface.createFromFile (fontFile);
   268         typefaceList[i] = new Sdk7Typeface (fontFile.getName (),
   269                                             typeface);
   270       }
   271 
   272     /* Initialize the default monospace and serif typefaces.  */
   273     fallbackTypeface = new Sdk7Typeface ("monospace",
   274                                          Typeface.MONOSPACE);
   275     typefaceList[fontFamilyList.length] = fallbackTypeface;
   276 
   277     fallbackTypeface = new Sdk7Typeface ("Monospace",
   278                                          Typeface.MONOSPACE);
   279     typefaceList[fontFamilyList.length + 1] = fallbackTypeface;
   280 
   281     fallbackTypeface = new Sdk7Typeface ("Sans Serif",
   282                                          Typeface.DEFAULT);
   283     typefaceList[fontFamilyList.length + 2] = fallbackTypeface;
   284   }
   285 
   286   private boolean
   287   checkMatch (Sdk7Typeface typeface, FontSpec fontSpec)
   288   {
   289     if (fontSpec.family != null
   290         && !fontSpec.family.equals (typeface.familyName))
   291       return false;
   292 
   293     if (fontSpec.slant != null
   294         && !fontSpec.weight.equals (typeface.weight))
   295       return false;
   296 
   297     if (fontSpec.spacing != null
   298         && !fontSpec.spacing.equals (typeface.spacing))
   299       return false;
   300 
   301     if (fontSpec.weight != null
   302         && !fontSpec.weight.equals (typeface.weight))
   303       return false;
   304 
   305     if (fontSpec.width != null
   306         && !fontSpec.width.equals (typeface.width))
   307       return false;
   308 
   309     return true;
   310   }
   311 
   312   @Override
   313   public FontEntity[]
   314   list (FontSpec fontSpec)
   315   {
   316     LinkedList<FontEntity> list;
   317     int i;
   318 
   319     list = new LinkedList<FontEntity> ();
   320 
   321     for (i = 0; i < typefaceList.length; ++i)
   322       {
   323         if (checkMatch (typefaceList[i], fontSpec))
   324           list.add (new Sdk7FontEntity (typefaceList[i]));
   325       }
   326 
   327     return list.toArray (new FontEntity[0]);
   328   }
   329 
   330   @Override
   331   public FontEntity
   332   match (FontSpec fontSpec)
   333   {
   334     FontEntity[] entities;
   335     int i;
   336 
   337     entities = this.list (fontSpec);
   338 
   339     if (entities.length == 0)
   340       return new Sdk7FontEntity (fallbackTypeface);
   341 
   342     return entities[0];
   343   }
   344 
   345   @Override
   346   public String[]
   347   listFamilies ()
   348   {
   349     return fontFamilyList;
   350   }
   351 
   352   @Override
   353   public FontObject
   354   openFont (FontEntity fontEntity, int pixelSize)
   355   {
   356     return new Sdk7FontObject (((Sdk7FontEntity) fontEntity).typeface,
   357                                pixelSize);
   358   }
   359 
   360   @Override
   361   public int
   362   hasChar (FontSpec font, char charCode)
   363   {
   364     float missingGlyphWidth, width;
   365     Rect rect1, rect2;
   366     Paint paint;
   367     Sdk7FontObject fontObject;
   368 
   369     if (font instanceof Sdk7FontObject)
   370       {
   371         fontObject = (Sdk7FontObject) font;
   372         paint = fontObject.typeface.typefacePaint;
   373       }
   374     else
   375       paint = ((Sdk7FontEntity) font).typeface.typefacePaint;
   376 
   377     paint.setTextSize (10);
   378 
   379     if (Character.isWhitespace (charCode))
   380       return 1;
   381 
   382     missingGlyphWidth = paint.measureText (TOFU_STRING);
   383     width = paint.measureText ("" + charCode);
   384 
   385     if (width == 0f)
   386       return 0;
   387 
   388     if (width != missingGlyphWidth)
   389       return 1;
   390 
   391     rect1 = new Rect ();
   392     rect2 = new Rect ();
   393 
   394     paint.getTextBounds (TOFU_STRING, 0, TOFU_STRING.length (),
   395                          rect1);
   396     paint.getTextBounds ("" + charCode, 0, 1, rect2);
   397     return rect1.equals (rect2) ? 0 : 1;
   398   }
   399 
   400   private void
   401   textExtents1 (Sdk7FontObject font, int code, FontMetrics metrics,
   402                 Paint paint, Rect bounds)
   403   {
   404     char[] text;
   405 
   406     text = new char[1];
   407     text[0] = (char) code;
   408 
   409     paint.getTextBounds (text, 0, 1, bounds);
   410 
   411     /* bounds is the bounding box of the glyph corresponding to CODE.
   412        Translate these into XCharStruct values.
   413 
   414        The origin is at 0, 0, and lbearing is the distance counting
   415        rightwards from the origin to the left most pixel in the glyph
   416        raster.  rbearing is the distance between the origin and the
   417        rightmost pixel in the glyph raster.  ascent is the distance
   418        counting upwards between the the topmost pixel in the glyph
   419        raster.  descent is the distance (once again counting
   420        downwards) between the origin and the bottommost pixel in the
   421        glyph raster.
   422 
   423        width is the distance between the origin and the origin of any
   424        character to the right.  */
   425 
   426     metrics.lbearing = (short) bounds.left;
   427     metrics.rbearing = (short) bounds.right;
   428     metrics.ascent = (short) -bounds.top;
   429     metrics.descent = (short) bounds.bottom;
   430     metrics.width = (short) paint.measureText ("" + text[0]);
   431   }
   432 
   433   @Override
   434   public void
   435   textExtents (FontObject font, int code[], FontMetrics fontMetrics)
   436   {
   437     int i;
   438     Paint paintCache;
   439     Rect boundsCache;
   440     Sdk7FontObject fontObject;
   441     char[] text;
   442     float width;
   443 
   444     fontObject = (Sdk7FontObject) font;
   445     paintCache = fontObject.typeface.typefacePaint;
   446     paintCache.setTextSize (fontObject.pixelSize);
   447     boundsCache = new Rect ();
   448 
   449     if (code.length == 0)
   450       {
   451         fontMetrics.lbearing = 0;
   452         fontMetrics.rbearing = 0;
   453         fontMetrics.ascent = 0;
   454         fontMetrics.descent = 0;
   455         fontMetrics.width = 0;
   456       }
   457     else if (code.length == 1)
   458       textExtents1 ((Sdk7FontObject) font, code[0], fontMetrics,
   459                     paintCache, boundsCache);
   460     else
   461       {
   462         text = new char[code.length];
   463 
   464         for (i = 0; i < code.length; ++i)
   465           text[i] = (char) code[i];
   466 
   467         paintCache.getTextBounds (text, 0, code.length,
   468                                   boundsCache);
   469         width = paintCache.measureText (text, 0, code.length);
   470 
   471         fontMetrics.lbearing = (short) boundsCache.left;
   472         fontMetrics.rbearing = (short) boundsCache.right;
   473         fontMetrics.ascent = (short) -boundsCache.top;
   474         fontMetrics.descent = (short) boundsCache.bottom;
   475         fontMetrics.width = (short) Math.round (width);
   476       }
   477   }
   478 
   479   @Override
   480   public int
   481   encodeChar (FontObject fontObject, char charCode)
   482   {
   483     return charCode;
   484   }
   485 
   486   @Override
   487   public int
   488   draw (FontObject fontObject, EmacsGC gc, EmacsDrawable drawable,
   489         int[] chars, int x, int y, int backgroundWidth,
   490         boolean withBackground)
   491   {
   492     Rect backgroundRect, bounds;
   493     Sdk7FontObject sdk7FontObject;
   494     char[] charsArray;
   495     int i;
   496     Canvas canvas;
   497     Paint paint;
   498 
   499     sdk7FontObject = (Sdk7FontObject) fontObject;
   500     charsArray = new char[chars.length];
   501 
   502     for (i = 0; i < chars.length; ++i)
   503       charsArray[i] = (char) chars[i];
   504 
   505     backgroundRect = new Rect ();
   506     backgroundRect.top = y - sdk7FontObject.ascent;
   507     backgroundRect.left = x;
   508     backgroundRect.right = x + backgroundWidth;
   509     backgroundRect.bottom = y + sdk7FontObject.descent;
   510 
   511     canvas = drawable.lockCanvas (gc);
   512 
   513     if (canvas == null)
   514       return 0;
   515 
   516     paint = gc.gcPaint;
   517     paint.setStyle (Paint.Style.FILL);
   518 
   519     if (withBackground)
   520       {
   521         paint.setColor (gc.background | 0xff000000);
   522         canvas.drawRect (backgroundRect, paint);
   523         paint.setColor (gc.foreground | 0xff000000);
   524       }
   525 
   526     paint.setTextSize (sdk7FontObject.pixelSize);
   527     paint.setTypeface (sdk7FontObject.typeface.typeface);
   528     paint.setAntiAlias (true);
   529     canvas.drawText (charsArray, 0, chars.length, x, y, paint);
   530 
   531     bounds = new Rect ();
   532     paint.getTextBounds (charsArray, 0, chars.length, bounds);
   533     bounds.offset (x, y);
   534     bounds.union (backgroundRect);
   535     drawable.damageRect (bounds);
   536     paint.setAntiAlias (false);
   537     return 1;
   538   }
   539 };

/* [<][>][^][v][top][bottom][index][help] */