JAL-4375 Clipped label text in annotations to annotation rectangle. Added property...
authorBen Soares <b.soares@dundee.ac.uk>
Wed, 7 Feb 2024 01:00:31 +0000 (01:00 +0000)
committerBen Soares <b.soares@dundee.ac.uk>
Wed, 7 Feb 2024 01:00:31 +0000 (01:00 +0000)
src/jalview/renderer/AnnotationRenderer.java

index 0fbfb02..734e33c 100644 (file)
@@ -142,6 +142,51 @@ public class AnnotationRenderer
    */
   private boolean canClip = false;
 
+  /**
+   * Property to set text antialiasing method
+   */
+  private static final String TEXT_ANTIALIAS_METHOD = "TEXT_ANTIALIAS_METHOD";
+
+  private static final Object textAntialiasMethod;
+
+  static
+  {
+    final String textAntialiasMethodPref = Cache
+            .getDefault(TEXT_ANTIALIAS_METHOD, "GASP");
+    switch (textAntialiasMethodPref)
+    {
+    case "ON":
+      textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_ON;
+      break;
+    case "GASP":
+      textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_GASP;
+      break;
+    case "LCD_HBGR":
+      textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HBGR;
+      break;
+    case "LCD_HRGB":
+      textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB;
+      break;
+    case "LCD_VBGR":
+      textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_VBGR;
+      break;
+    case "LCD_VRGB":
+      textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_VRGB;
+      break;
+    case "OFF":
+      textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_OFF;
+      break;
+    case "DEFAULT":
+      textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT;
+      break;
+    default:
+      jalview.bin.Console.warn(TEXT_ANTIALIAS_METHOD + " value '"
+              + textAntialiasMethodPref
+              + "' not recognised, defaulting to 'GASP'. See https://docs.oracle.com/javase/8/docs/api/java/awt/RenderingHints.html#KEY_TEXT_ANTIALIASING");
+      textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_GASP;
+    }
+  }
+
   public AnnotationRenderer()
   {
     this(false);
@@ -518,7 +563,6 @@ public class AnnotationRenderer
             .getComplementConsensusAnnotation();
 
     BitSet graphGroupDrawn = new BitSet();
-    int charOffset = 0; // offset for a label
     // \u03B2 \u03B1
     // debug ints
     int yfrom = 0, f_i = 0, yto = 0, f_to = 0;
@@ -695,9 +739,7 @@ public class AnnotationRenderer
             if (validCharWidth && validRes && displayChar != null
                     && (displayChar.length() > 0))
             {
-              // Graphics2D gg = (g);
-              float fmWidth = fm.charsWidth(displayChar.toCharArray(), 0,
-                      displayChar.length());
+              float fmWidth = fm.stringWidth(displayChar);
 
               /*
                * shrink label width to fit in column, if that is
@@ -708,13 +750,12 @@ public class AnnotationRenderer
               if (scaleColLabel && fmWidth > charWidth)
               {
                 scaledToFit = true;
-                fmScaling = charWidth;
-                fmScaling /= fmWidth;
+                fmScaling = (float) charWidth / fmWidth;
                 // and update the label's width to reflect the scaling.
                 fmWidth = charWidth;
               }
 
-              charOffset = (int) ((charWidth - fmWidth) / 2f);
+              float charOffset = (charWidth - fmWidth) / 2f;
 
               if (row_annotations[column].colour == null)
               {
@@ -729,26 +770,39 @@ public class AnnotationRenderer
                * draw the label, unless it is the same secondary structure
                * symbol (excluding RNA Helix) as the previous column
                */
-              final int xPos = (x * charWidth) + charOffset;
-              final int yPos = y + iconOffset;
-
+              final float xPos = (x * charWidth) + charOffset;
+              final float yPos = y + iconOffset;
+
+              // Act on a copy of the Graphics2d object
+              Graphics2D g2dCopy = (Graphics2D) g2d.create();
+              // Clip to this annotation line (particularly width).
+              // This removes artifacts from text when side scrolling
+              // (particularly in wrap format), but can result in clipped
+              // characters until a full paint is drawn.
+              g2dCopy.setClip(0, (int) yPos - charHeight, imgWidth - 1,
+                      charHeight);
               /*
                * translate to drawing position _before_ applying any scaling
                */
-              g2d.translate(xPos, yPos);
+              g2dCopy.translate(xPos, yPos);
+              // narrow the clipping rectangle to the size of a character square
+              // or the character width
+              g2dCopy.clipRect(0, 0 - charHeight,
+                      (int) Math.ceil(Math.max(charWidth, fmWidth)),
+                      charHeight);
               if (scaledToFit)
               {
                 /*
                  * use a scaling transform to make the label narrower
                  * (JalviewJS doesn't have Font.deriveFont(AffineTransform))
                  */
-                g2d.transform(
+                g2dCopy.transform(
                         AffineTransform.getScaleInstance(fmScaling, 1.0));
               }
-              setAntialias(g);
+              setAntialias(g2dCopy, true);
               if (column == 0 || row.graph > 0)
               {
-                g2d.drawString(displayChar, 0, 0);
+                g2dCopy.drawString(displayChar, 0, 0);
               }
               else if (row_annotations[column - 1] == null || (labelAllCols
                       || !displayChar.equals(
@@ -756,18 +810,9 @@ public class AnnotationRenderer
                       || (displayChar.length() < 2
                               && row_annotations[column].secondaryStructure == ' ')))
               {
-                g2d.drawString(displayChar, 0, 0);
-              }
-              if (scaledToFit)
-              {
-                /*
-                 * undo scaling before translating back 
-                 * (restoring saved transform does NOT work in JS PDFGraphics!)
-                 */
-                g2d.transform(AffineTransform
-                        .getScaleInstance(1D / fmScaling, 1.0));
+                g2dCopy.drawString(displayChar, 0, 0);
               }
-              g2d.translate(-xPos, -yPos);
+              g2dCopy.dispose();
             }
           }
           if (row.hasIcons)
@@ -1939,6 +1984,11 @@ public class AnnotationRenderer
 
   private void setAntialias(Graphics g)
   {
+    setAntialias(g, false);
+  }
+
+  private void setAntialias(Graphics g, boolean text)
+  {
     if (isVectorRendering())
     {
       // no need to antialias vector drawings
@@ -1947,8 +1997,16 @@ public class AnnotationRenderer
     if (Cache.getDefault("ANTI_ALIAS", true))
     {
       Graphics2D g2d = (Graphics2D) g;
-      g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
-              RenderingHints.VALUE_ANTIALIAS_ON);
+      if (text)
+      {
+        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+                this.textAntialiasMethod);
+      }
+      else
+      {
+        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                RenderingHints.VALUE_ANTIALIAS_ON);
+      }
     }
   }