1 package org.unicode.cldr.tool; 2 3 import java.awt.BasicStroke; 4 import java.awt.Color; 5 import java.awt.Dimension; 6 import java.awt.Font; 7 import java.awt.FontMetrics; 8 import java.awt.Frame; 9 import java.awt.Graphics; 10 import java.awt.Graphics2D; 11 import java.awt.RenderingHints; 12 import java.awt.event.WindowAdapter; 13 import java.awt.event.WindowEvent; 14 import java.awt.geom.Rectangle2D; 15 import java.awt.image.BufferedImage; 16 import java.io.IOException; 17 import java.io.PrintWriter; 18 import java.util.Set; 19 import java.util.TreeSet; 20 21 import javax.swing.JApplet; 22 23 import org.unicode.cldr.draft.FileUtilities; 24 import org.unicode.cldr.util.CLDRPaths; 25 26 import com.ibm.icu.dev.util.UnicodeMap; 27 import com.ibm.icu.impl.Utility; 28 import com.ibm.icu.lang.UCharacter; 29 import com.ibm.icu.lang.UScript; 30 import com.ibm.icu.text.UnicodeSet; 31 import com.ibm.icu.text.UnicodeSetIterator; 32 import com.ibm.icu.util.ICUUncheckedIOException; 33 34 public class GenerateApproximateWidths extends JApplet implements Runnable { 35 private static final long serialVersionUID = 1L; 36 37 private static final int IMAGE_HEIGHT = 360; 38 private static final int IMAGE_WIDTH = 640; 39 private static final BasicStroke BASIC_STROKE = new BasicStroke(0.5f); 40 private static final Font FONT = new Font("TimesNewRoman", 0, 100); 41 private static final Font FONT101 = new Font("TimesNewRoman", 0, 101); 42 43 private BufferedImage bimg; 44 private String string = ""; 45 paint(Graphics g)46 public void paint(Graphics g) { 47 Dimension d = getSize(); 48 if (bimg == null || bimg.getWidth() != d.width || bimg.getHeight() != d.height) { 49 bimg = (BufferedImage) createImage(d.width, d.height); 50 } 51 final int w = bimg.getWidth(); 52 final int h = bimg.getHeight(); 53 drawDemo(bimg, w, h); 54 g.drawImage(bimg, 0, 0, this); 55 } 56 drawDemo(BufferedImage bimg, int w, int h)57 public void drawDemo(BufferedImage bimg, int w, int h) { 58 Graphics2D g = bimg.createGraphics(); 59 g.setBackground(Color.white); 60 g.clearRect(0, 0, w, h); 61 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 62 g.setStroke(BASIC_STROKE); 63 64 drawString(g, FONT, string, 0.25f, w, 0.5f, h); // draw the 1st size character on the left 65 drawString(g, FONT101, string, 0.75, w, 0.5f, h); // draw the 2st size character on the right 66 showWidths(g); 67 g.dispose(); 68 } 69 showWidths(Graphics2D g)70 private void showWidths(Graphics2D g) { 71 try { 72 PrintWriter out = FileUtilities.openUTF8Writer(CLDRPaths.GEN_DIRECTORY + "widths/", "ApproximateWidth.txt"); 73 // TODO Auto-generated method stub 74 UnicodeMap<Integer> map = new UnicodeMap<Integer>(); 75 Widths widths = new Widths(g, new Font("Serif", 0, 100), new Font("SansSerif", 0, 100)); 76 77 UnicodeSet CHECK = new UnicodeSet("[[:^c:][:cc:][:cf:]]"); 78 int defaultWidth = widths.getMetrics(0xFFFD); 79 80 // corrections 81 CHECK.removeAll(addCorrections(map, "[:Cn:]", defaultWidth)); 82 CHECK.removeAll(addCorrections(map, "[\\u0000-\\u0008\\u000E-\\u001F\\u007F-\\u0084\\u0086-\\u009F]", 83 defaultWidth)); 84 85 int cjkWidth = widths.getMetrics(0x4E00); 86 CHECK.removeAll(addCorrections(map, "[:ideographic:]", cjkWidth)); 87 88 CHECK.removeAll(addCorrections(map, "[[:Cf:][:Mn:][:Me:]]", 0)); 89 90 int count = 0; 91 for (UnicodeSetIterator it = new UnicodeSetIterator(CHECK); it.next();) { 92 ++count; 93 if ((count % 1000) == 0) { 94 System.out.println(count + "\t" + Utility.hex(it.codepoint)); 95 } 96 int cpWidth = widths.getMetrics(it.codepoint); 97 if (cpWidth != defaultWidth) { 98 map.put(it.codepoint, cpWidth); 99 } 100 } 101 out.println("# ApproximateWidth\n" + 102 "# @missing: 0000..10FFFF; " + defaultWidth); 103 104 Set<Integer> values = new TreeSet<Integer>(map.values()); 105 for (Integer integer0 : values) { 106 if (integer0 == null) { 107 continue; 108 } 109 int integer = integer0; 110 if (integer == defaultWidth) { 111 continue; 112 } 113 UnicodeSet uset = map.getSet(integer); 114 out.println("\n# width: " + integer + "\n"); 115 for (UnicodeSetIterator foo = new UnicodeSetIterator(uset); foo.nextRange();) { 116 if (foo.codepoint != foo.codepointEnd) { 117 out.println(Utility.hex(foo.codepoint) + ".." + Utility.hex(foo.codepointEnd) + "; " 118 + integer + "; # " + UCharacter.getExtendedName(foo.codepoint) + ".." 119 + UCharacter.getExtendedName(foo.codepointEnd)); 120 } else { 121 out.println(Utility.hex(foo.codepoint) + "; " 122 + integer + "; # " + UCharacter.getExtendedName(foo.codepoint)); 123 } 124 } 125 out.println("\n# codepoints: " + uset.size() + "\n"); 126 } 127 out.close(); 128 System.out.println("Adjusted: " + widths.adjusted); 129 System.out.println("DONE"); 130 } catch (IOException e) { 131 throw new ICUUncheckedIOException(e); 132 } 133 } 134 addCorrections(UnicodeMap<Integer> map, String usetString, int width)135 private UnicodeSet addCorrections(UnicodeMap<Integer> map, String usetString, int width) { 136 UnicodeSet uset = new UnicodeSet(usetString); 137 map.putAll(uset, width); 138 return uset; 139 } 140 141 class Widths { 142 UnicodeSet adjusted = new UnicodeSet(); 143 144 Graphics2D g; 145 FontMetrics[] metrics; 146 char[] buffer = new char[20]; 147 int bufferLen = 0; 148 int baseChar = -1; 149 150 UnicodeSet SPACING_COMBINING = new UnicodeSet("[:Mc:]").freeze(); 151 int[] SCRIPT2BASE = new int[UScript.CODE_LIMIT]; 152 { 153 for (int i = 0; i < SCRIPT2BASE.length; ++i) { 154 SCRIPT2BASE[i] = -1; 155 } 156 for (String s : new UnicodeSet("[क ক ਕ ક କ க క ಕ ക ක ཀ ၵ ក]")) { 157 int cp = s.codePointAt(0); 158 if (cp < 0x10000) { 159 int script = UScript.getScript(cp); 160 SCRIPT2BASE[script] = cp; 161 } 162 } 163 } 164 Widths(Graphics2D g2, Font... fonts)165 public Widths(Graphics2D g2, Font... fonts) { 166 g = g2; 167 metrics = new FontMetrics[fonts.length]; 168 for (int i = 0; i < fonts.length; ++i) { 169 g.setFont(fonts[i]); 170 metrics[i] = g.getFontMetrics(); 171 } 172 } 173 getBase(int cp)174 private int getBase(int cp) { 175 if (SPACING_COMBINING.contains(cp)) { 176 return SCRIPT2BASE[UScript.getScript(cp)]; 177 } 178 return -1; 179 } 180 getMetrics(int cp)181 private int getMetrics(int cp) { 182 fillBuffer(cp); 183 double totalWidth = 0.0d; 184 for (FontMetrics m : metrics) { 185 double width = getTotalWidth(m); 186 if (baseChar >= 0) { 187 fillBuffer(baseChar); 188 width -= getTotalWidth(m) / bufferLen; 189 fillBuffer(cp); 190 } else { 191 width /= bufferLen; 192 } 193 totalWidth += width; 194 } 195 int result = (int) (totalWidth / metrics.length / 10.0d + 0.499999d); 196 // if (result == 0 && totalWidth != 0.0d) { 197 // result = 1; 198 // adjusted.add(cp); 199 // } 200 if (result > 31 || result < -2) { // just to catch odd results 201 throw new IllegalArgumentException("Value too large " + result); 202 } 203 return result; 204 } 205 getTotalWidth(FontMetrics fontMetrics)206 private double getTotalWidth(FontMetrics fontMetrics) { 207 Rectangle2D rect1 = fontMetrics.getStringBounds(buffer, 0, bufferLen, g); 208 return rect1.getWidth(); 209 // Rectangle2D rect2 = metrics2.getStringBounds(buffer, 0, bufferLen, g); 210 // double rwidth2 = rect2.getWidth(); 211 // if (DEBUG && rwidth1 != rwidth2) { 212 // System.out.println(Utility.hex(cp) + ", " + rwidth1 + ", " + rwidth2); 213 // } 214 // return (rwidth1 + rwidth2) / (2.0d * bufferLen); 215 } 216 fillBuffer(int cp)217 private void fillBuffer(int cp) { 218 baseChar = -1; 219 if (SPACING_COMBINING.contains(cp)) { 220 baseChar = getBase(cp); 221 if (baseChar != -1) { 222 buffer[0] = (char) baseChar; 223 buffer[1] = (char) cp; 224 bufferLen = 2; 225 return; 226 } 227 } 228 if (cp < 0x10000) { 229 buffer[0] = buffer[1] = buffer[2] = (char) cp; 230 bufferLen = 3; 231 } else { 232 char[] temp = UCharacter.toChars(cp); 233 buffer[0] = buffer[2] = buffer[4] = temp[0]; 234 buffer[1] = buffer[3] = buffer[5] = temp[1]; 235 bufferLen = 6; 236 } 237 } 238 } 239 drawString(Graphics2D g, Font font2, String mainString, double wPercent, double w, double hPercent, double h)240 public void drawString(Graphics2D g, Font font2, String mainString, 241 double wPercent, double w, double hPercent, double h) { 242 g.setFont(font2); 243 FontMetrics metrics = g.getFontMetrics(); 244 int ascent = metrics.getAscent(); 245 Rectangle2D bounds = metrics.getStringBounds(mainString, g); 246 double x = wPercent * (w - bounds.getWidth()); 247 double y = hPercent * (h - bounds.getHeight()); 248 bounds.setRect(x, y, bounds.getWidth(), bounds.getHeight()); 249 250 g.setColor(Color.blue); 251 g.draw(bounds); 252 g.drawLine((int) x, (int) y + ascent, (int) (x + bounds.getWidth()), (int) y + ascent); 253 254 g.setColor(Color.black); 255 g.drawString(mainString, (int) x, (int) y + ascent); 256 257 System.out.println(font2.getSize() + "\t" + string + " " + Integer.toHexString(string.codePointAt(0))); 258 } 259 main(String argv[])260 public static void main(String argv[]) throws IOException { 261 262 final GenerateApproximateWidths demo = new GenerateApproximateWidths(); 263 demo.init(); 264 Frame f = new Frame("Frame"); 265 f.addWindowListener(new WindowAdapter() { 266 public void windowClosing(WindowEvent e) { 267 System.exit(0); 268 } 269 270 public void windowDeiconified(WindowEvent e) { 271 demo.start(); 272 } 273 274 public void windowIconified(WindowEvent e) { 275 demo.stop(); 276 } 277 }); 278 f.add(demo); 279 f.pack(); 280 f.setSize(new Dimension(IMAGE_WIDTH, IMAGE_HEIGHT)); 281 f.show(); 282 demo.start(); 283 } 284 285 @Override run()286 public void run() { 287 repaint(); 288 } 289 }