Bug 562442: [RGraphic] Fix calculation of font metrics on Linux

Change-Id: Ief8d22e4aa534bbd3eca773e05f6aea2959393ff
diff --git a/eclient/org.eclipse.statet.rj.eclient.graphics/src/org/eclipse/statet/internal/rj/eclient/graphics/FontManager.java b/eclient/org.eclipse.statet.rj.eclient.graphics/src/org/eclipse/statet/internal/rj/eclient/graphics/FontManager.java
index 2725bd6..4d56ccb 100644
--- a/eclient/org.eclipse.statet.rj.eclient.graphics/src/org/eclipse/statet/internal/rj/eclient/graphics/FontManager.java
+++ b/eclient/org.eclipse.statet.rj.eclient.graphics/src/org/eclipse/statet/internal/rj/eclient/graphics/FontManager.java
@@ -14,6 +14,7 @@
 
 package org.eclipse.statet.internal.rj.eclient.graphics;
 
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -27,7 +28,11 @@
 import org.eclipse.swt.graphics.ImageData;
 import org.eclipse.swt.widgets.Display;
 
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
 
+
+@NonNullByDefault
 public class FontManager {
 	
 	private static final int[] R2SWT_STYLE= new int[] {
@@ -43,6 +48,8 @@
 	private static final double HR_FACTOR= HR_FONTSIZE;
 	private static final int HR_MIN_FONTSIZE= HR_FONTSIZE / 2;
 	
+	private static final int[] EMPTY_INT_ARRY= new int[0];
+	
 	
 	private static final class FontInstance {
 		
@@ -58,7 +65,7 @@
 		private double ascentUp= Integer.MIN_VALUE;
 		private double ascentLow= Integer.MIN_VALUE;
 		
-		private int[] charAbove255;
+		private int[] charAbove255= EMPTY_INT_ARRY;
 		private double[] charMetrics= UNINITIALIZED;
 		private double[] strWidth= UNINITIALIZED;
 		
@@ -86,27 +93,24 @@
 			else if (ch <= 255) {
 				return ch;
 			}
-			else if (this.charAbove255 == null){
-				this.charAbove255= new int[256];
-				this.charAbove255[0]= ch;
-				return 256 + 0;
-			}
 			else {
-				for (int i= 0; i < this.charAbove255.length; i++) {
-					final int c= this.charAbove255[i];
-					if (c == 0) {
-						this.charAbove255[i]= c;
+				int[] charAbove255= this.charAbove255;
+				int i= 0;
+				for (; i < charAbove255.length; i++) {
+					final int c= charAbove255[i];
+					if (c == ch) {
 						return 256 + i;
 					}
-					else if (c == ch) {
+					if (c == 0) {
+						charAbove255[i]= c;
 						return 256 + i;
 					}
 				}
-				final int[] newChar= new int[this.charAbove255.length + 256];
-				System.arraycopy(this.charAbove255, 0, newChar, 0, this.charAbove255.length);
-				newChar[this.charAbove255.length]= ch;
-				this.charAbove255= newChar;
-				return this.charAbove255.length - 256;
+				// i == charAbove255.length
+				charAbove255= Arrays.copyOf(charAbove255, i + 256);
+				this.charAbove255= charAbove255;
+				charAbove255[i]= ch;
+				return 256 + i;
 			}
 		}
 		
@@ -123,11 +127,12 @@
 		private final Image image;
 		private final int imageWidth;
 		private final int imageHeigth;
-		private final int imagePixelBytes;
+		private final int imagePixelBWidth;
+		private final int imagePixelBIndex;
 		private final int imageLineBytes;
 		private final byte imageBlankData;
 		
-		private ImageData imageData;
+		private @Nullable ImageData imageData;
 		
 		
 		public TestGC(final Device device) {
@@ -153,10 +158,13 @@
 			this.font= null;
 			
 			clearImage();
-			this.imageData= this.image.getImageData();
-			this.imageLineBytes= this.imageData.bytesPerLine;
-			this.imagePixelBytes= Math.max(this.imageData.bytesPerLine / this.imageData.width, 1);
-			this.imageBlankData= this.imageData.data[0];
+			final var imageData= this.image.getImageData();
+			this.imageLineBytes= imageData.bytesPerLine;
+			this.imagePixelBWidth= Math.max(imageData.bytesPerLine / imageData.width, 1);
+			this.imagePixelBIndex= (imageData.palette.isDirect) ?
+					this.imagePixelBWidth - 1 + imageData.palette.redShift / 8 :
+					0;
+			this.imageBlankData= imageData.data[this.imagePixelBIndex];
 		}
 		
 		
@@ -165,8 +173,8 @@
 				this.font= font;
 				this.testGC.setFont(font.swtFont);
 //			}
-			if (this.font.baseLine == 0) {
-				this.font.init(this.testGC);
+			if (font.baseLine == 0) {
+				font.init(this.testGC);
 				this.testGC.setFont(font.swtFont); // E-Bug #319125
 			}
 		}
@@ -184,31 +192,38 @@
 			this.imageData= null;
 		}
 		
+		private ImageData getImageData() {
+			var imageData= this.imageData;
+			if (imageData == null) {
+				imageData= this.image.getImageData();
+				this.imageData= imageData;
+			}
+			return imageData;
+		}
+		
 		private void drawText(final String txt) {
 			this.testGC.drawString(txt, 0, 0, true);
 		}
 		
 		public int findFirstLine() {
-			if (this.imageData == null) {
-				this.imageData= this.image.getImageData();
-			}
-			final byte[] data= this.imageData.data;
-			for (int i= 0; i < data.length; i+= this.imagePixelBytes) {
-				if (data[i] != this.imageBlankData) {
-					return (i / this.imageLineBytes);
+			final var imageData= getImageData();
+			final byte[] data= imageData.data;
+			for (int index= this.imagePixelBIndex;
+					index < data.length; index+= this.imagePixelBWidth) {
+				if (data[index] != this.imageBlankData) {
+					return (index / this.imageLineBytes);
 				}
 			}
 			return -1;
 		}
 		
 		public int findLastLine() {
-			if (this.imageData == null) {
-				this.imageData= this.image.getImageData();
-			}
-			final byte[] data= this.imageData.data;
-			for (int i= data.length - this.imagePixelBytes; i >= 0; i-= this.imagePixelBytes) {
-				if (data[i] != this.imageBlankData) {
-					return (i / this.imageLineBytes);
+			final var imageData= getImageData();
+			final byte[] data= imageData.data;
+			for (int index= data.length - this.imagePixelBWidth + this.imagePixelBIndex;
+					index >= 0; index-= this.imagePixelBWidth) {
+				if (data[index] != this.imageBlankData) {
+					return (index / this.imageLineBytes);
 				}
 			}
 			return -1;
@@ -253,7 +268,8 @@
 		private FontInstance get(final int style, final int size) {
 			FontInstance[] styleFonts= this.fonts[style];
 			if (styleFonts == null) {
-				this.fonts[style]= styleFonts= new FontInstance[4];
+				styleFonts= new FontInstance[8];
+				this.fonts[style]= styleFonts;
 			}
 			int idx;
 			if (size == HR_FONTSIZE) {
@@ -274,17 +290,19 @@
 					}
 				}
 				if (idx >= styleFonts.length) {
-					this.fonts[style]= new FontInstance[styleFonts.length+4];
-					System.arraycopy(styleFonts, 0, this.fonts[style], 0, styleFonts.length);
-					styleFonts= this.fonts[style];
+					styleFonts= Arrays.copyOf(styleFonts, styleFonts.length + 8);
+					this.fonts[style]= styleFonts;
 				}
 			}
-			if (styleFonts[idx] == null) {
+			
+			FontInstance font= styleFonts[idx];
+			if (font == null) {
 				final FontData fontData= new FontData(this.name, size, R2SWT_STYLE[style]);
-				styleFonts[idx]= new FontInstance(size,
-						new Font(FontManager.this.display, fontData));
+				font= new FontInstance(size,
+						new Font(FontManager.this.display, fontData) );
+				styleFonts[idx]= font;
 			}
-			return styleFonts[idx];
+			return font;
 		}
 		
 		public synchronized Font getSWTFont(final int style, final int size) {
@@ -514,7 +532,7 @@
 	private final Display display;
 	
 	private final Object testGCLock= new Object();
-	private TestGC testGC;
+	private @Nullable TestGC testGC;
 	private boolean disposed;
 	
 	private final Map<String, FontFamily> fontFamilies= new HashMap<>();
@@ -540,17 +558,19 @@
 	}
 	
 	protected final TestGC getTestGC() {
-		if (this.testGC == null) {
-			this.display.syncExec(new Runnable() {
-				@Override
-				public void run() {
-					if (!FontManager.this.disposed) {
-						FontManager.this.testGC= new TestGC(FontManager.this.display);
-					}
+		TestGC testGC= this.testGC;
+		if (testGC == null) {
+			this.display.syncExec(() -> {
+				if (!this.disposed) {
+					this.testGC= new TestGC(this.display);
 				}
 			});
+			testGC= this.testGC;
+			if (testGC == null) {
+				throw new RuntimeException("disposed");
+			}
 		}
-		return this.testGC;
+		return testGC;
 	}
 	
 	
@@ -561,9 +581,10 @@
 		}
 		this.fontFamilies.clear();
 		
-		if (this.testGC != null) {
-			this.testGC.dispose();
+		final var testGC= this.testGC;
+		if (testGC != null) {
 			this.testGC= null;
+			testGC.dispose();
 		}
 	}