• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }