Reviewer needed - new regression test for checking font rendering & addition of copyright text to existing test
Dr Andrew John Hughes
ahughes at redhat.com
Mon Jan 17 07:43:58 PST 2011
On 16:27 Mon 17 Jan , Pavel Tisnovsky wrote:
> Hi all,
>
> I'd like to add new regression test which check proper rendering of
> international fonts using various logical font names and font styles.
> This new test is stored in attachment as [InternationalFontsStyles.java]
> (for review) and it is also part of icedtea-jtreg-international-fonts.patch.
>
> I also added copyright text into existing test
> InternationalFontRendering..java. Both changes are included as hg diff
> done against recent IcedTea6 [hg_diff].
>
> Can anyone please review these changes?
>
> Thank you in advance
> Pavel
Couple of points:
* I think the characters used should use the Unicode escape notation (\uXXXX).
Here in the mail, at least, they are just displaying as ?????.
* You don't include any change to Makefile.am to actually apply the patch.
Also for future reference, the ChangeLog should be in the body of the
e-mail not the patch, when posting to the list, so others can more
easily apply and test the patch.
> import java.awt.Color;
> import java.awt.Font;
> import java.awt.Graphics2D;
> import java.awt.RenderingHints;
> import java.awt.image.BufferedImage;
> import java.io.File;
> import java.io.IOException;
> import java.util.Arrays;
> import java.util.List;
> import java.util.ArrayList;
>
> import javax.imageio.ImageIO;
>
> /**
> * @test
> * @run main InternationalFontsStyles
> * @author Pavel Tisnovsky
> *
> * @summary This test check if selected international characters could be
> * properly rendered with various font styles and logical names.
> *
> * This regression test check if selected international characters
> * could be properly rendered using various styles and logical font
> * names - the test check rendering using all combinations of logical
> * font names (Font.SERIF, Font.SANS_SERIF, Font.MONOSPACED,
> * Font.DIALOG, Font.DIALOG_INPUT) and font styles (Font.PLAIN,
> * Font.BOLD, Font.ITALIC, Font.BOLD+Font.ITALIC).
> *
> * The test is based on rendering certain characters from selected code
> * pages into BufferedImage.
> *
> * When the shape of selected character does not exists or font
> * configuration is broken, only empty rectangle is rendered instead of
> * the selected character shape. This rectangle is filtered and then
> * the destination image is tested whether it is empty (=white).
> *
> * If test images with rendered characters needs to be created use
> * following flag: -create-images
> *
> */
> public class InternationalFontsStyles
> {
> // all logical font names which are tested
> private static final String[] fontNames = {Font.SERIF, Font.SANS_SERIF, Font.MONOSPACED, Font.DIALOG, Font.DIALOG_INPUT};
>
> // all font styles which are tested
> private static final int[] fontStyles = {Font.PLAIN, Font.BOLD, Font.ITALIC, Font.BOLD+Font.ITALIC};
>
> // width and height of image where is each character rendered
> private static int WIDTH = 200;
> private static int HEIGHT = 200;
>
> // font size used for rendering
> private static final int FONT_SIZE = 160;
>
> private static final int MINIMUM_HORIZONTAL_LINE_LENGTH = 70;
> private static final int MINIMUM_VERTICAL_LINE_LENGTH = 80;
> private static final int BLACK_WHITE_THRESHOLD = 128;
> private static final int BLACK_PIXEL_COUNT_THRESHOLD = WIDTH * HEIGHT / 1000;
>
> private static final String[][] testedStrings = {
> {"Latin-1", "abcdefABCDEF"},
> {"Latin-2", "??????????????????????????"},
> {"Cyrilic", "??????????????????"},
> {"Greek", "????????????????????????????????"},
> {"Asia-Test1", "???????????????"},
> {"Asia-Test2", "??????????????????"},
> {"Asia-Test3", "???????????????"},
> {"Asia-Test4", "???????????????"},
> };
>
> /**
> * Returns string containing given font style. This style could
> * be PLAIN, BOLD, ITALIC or combination of these styles.
> *
> * @param fontStyle selected font style, eg. Font.BOLD
> * @return textual representation of font style
> */
> private static String getFontStyle(int fontStyle)
> {
> return new String[]
> // 0 1 2 1+2
> { "PLAIN", "BOLD", "ITALIC", "BOLD+ITALIC" }[fontStyle];
> }
>
> /**
> * Creates test image a renders one big character to it.
> * @param str string to be rendered
> * @param fontStyle selected font style
> * @param fontName selected font name
> * @return
> */
> private BufferedImage createTestImage(String str, String fontName, int fontStyle) {
> BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_BYTE_GRAY);
> Graphics2D gc = image.createGraphics();
> gc.setBackground(Color.WHITE);
> gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
> gc.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
> gc.clearRect(0, 0, WIDTH, HEIGHT);
> gc.setFont(new Font(fontName, fontStyle, FONT_SIZE));
> gc.setColor(Color.BLACK);
> gc.drawString(str, 0, HEIGHT - 40);
> gc.dispose();
> return image;
> }
>
> /**
> * Creates destination image and then copies data from source image to it
> * @param src source image
> * @return destination image as copy of source image
> */
> private BufferedImage createDestinationImage(BufferedImage src)
> {
> BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_BYTE_GRAY);
> src.copyData(image.getRaster());
> return image;
> }
>
> /**
> * Removes long horizontal lines from image
> */
> private void removeHorizontalLines(BufferedImage image)
> {
> for (int y = 0; y < image.getHeight(); y++)
> {
> int startX = -1, endX = -1;
> for (int x = 0; x < image.getWidth(); x++)
> {
> int color = image.getRaster().getSample(x, y, 0);
> if (startX < 0 && color <= BLACK_WHITE_THRESHOLD) {
> startX = x;
> //System.out.println("> " + y + "\t" + x);
> }
> if (startX > 0 && endX < 0 && color > BLACK_WHITE_THRESHOLD)
> {
> endX = x;
> //System.out.println("< " + y + "\t" + x);
> }
> }
> // remove long horizontal line, but only if this line found detected in image
> if (startX > 0 && endX > 0 && (endX - startX) > MINIMUM_HORIZONTAL_LINE_LENGTH)
> {
> for (int x = startX; x < endX; x++) {
> image.getRaster().setSample(x, y, 0, 255);
> }
> }
> }
> }
>
> /**
> * Removes long vertical lines from image
> */
> private void removeVerticalLines(BufferedImage image)
> {
> for (int x = 0; x < image.getWidth(); x++)
> {
> int startY = -1, endY = -1;
> for (int y = 0; y < image.getHeight(); y++)
> {
> int color = image.getRaster().getSample(x, y, 0);
> if (startY < 0 && color <= BLACK_WHITE_THRESHOLD)
> {
> startY = y;
> }
> if (startY > 0 && endY < 0 && color > BLACK_WHITE_THRESHOLD)
> {
> endY = y;
> //System.out.println("< " + y + "\t" + x);
> }
> }
> // remove long vertical line, but only if this line found detected in image
> if (startY > 0 && endY > 0 && (endY - startY) > MINIMUM_VERTICAL_LINE_LENGTH)
> {
> for (int y = startY; y < endY; y++) {
> image.getRaster().setSample(x, y, 0, 255);
> }
> }
> }
> }
>
> /**
> * Test if image is almost empty (one large white area)
> * @param image
> * @return
> */
> private boolean isImageAlmostEmpty(BufferedImage image)
> {
> int blackPixelCount = 0;
> for (int y = 0; y < image.getHeight(); y++)
> {
> for (int x = 0; x < image.getWidth(); x++)
> {
> if (image.getRaster().getSample(x, y, 0) < BLACK_WHITE_THRESHOLD)
> {
> blackPixelCount++;
> }
> }
> }
> return blackPixelCount < BLACK_PIXEL_COUNT_THRESHOLD;
> }
>
> private String constructImageFileName(String fontName, int fontStyle, int code, String suffix)
> {
> return fontName + "_" + getFontStyle(fontStyle) + "_" + code + "_" + suffix + ".png";
> }
>
> public void runTest(boolean createImages) throws IOException
> {
> List<Integer> badCharacters = new ArrayList<Integer>();
> for (String[] testedString : testedStrings)
> {
> for (String fontName : fontNames)
> {
> for (int fontStyle : fontStyles)
> {
> System.out.format("check string %s rendered by font %s with style %s\n", testedString[1], fontName,
> getFontStyle(fontStyle));
> for (int i = 0; i < testedString[1].length(); i++)
> {
> String str = testedString[1].substring(i, 1 + i);
> int code = str.charAt(0);
> System.out.print(code + "\t");
> BufferedImage src = createTestImage(str, fontName, fontStyle);
> BufferedImage dst = createDestinationImage(src);
> removeHorizontalLines(dst);
> removeVerticalLines(dst);
>
> if (createImages /*|| true*/)
> {
> ImageIO.write(src, "png",
> new File(constructImageFileName(fontName, fontStyle, code, "scr")));
> ImageIO.write(dst, "png",
> new File(constructImageFileName(fontName, fontStyle, code, "dst")));
> }
>
> if (isImageAlmostEmpty(dst))
> {
> System.out.println("*** Error in rendering of character with code: " + code);
> badCharacters.add(Integer.valueOf(code));
> }
> }
> System.out.println();
> }
> }
> }
> // if at least one character is not rendered properly throw an exception
> if (!badCharacters.isEmpty())
> {
> throw new RuntimeException("There are some broken characters: " + badCharacters.toString());
> }
> System.out.println("done!");
> }
>
> public static void main(String[] args) throws IOException
> {
> new InternationalFontsStyles().runTest(Arrays.asList(args).contains("-create-images"));
> }
> }
> diff -r fd09f4a3b767 ChangeLog
> --- a/ChangeLog Sun Jan 09 00:43:01 2011 +0000
> +++ b/ChangeLog Mon Jan 17 16:09:19 2011 +0100
> @@ -1,3 +1,9 @@
> +2011-01-17 Pavel Tisnovsky <ptisnovs at redhat.com>
> +
> + * patches/icedtea-jtreg-international-fonts.patch:
> + Added new font test - InternationalFontsStyles
> + Added copyright text into InternationalFontRendering
> +
> 2011-01-07 Andrew John Hughes <ahughes at redhat.com>
>
> * patches/jtreg-TestXEmbedServer-fix.patch:
> diff -r fd09f4a3b767 patches/icedtea-jtreg-international-fonts.patch
> --- a/patches/icedtea-jtreg-international-fonts.patch Sun Jan 09 00:43:01 2011 +0000
> +++ b/patches/icedtea-jtreg-international-fonts.patch Mon Jan 17 16:09:19 2011 +0100
> @@ -1,6 +1,25 @@
> ---- /dev/null 2010-06-29 14:56:30.329576932 +0200
> -+++ openjdk/jdk/test/java/awt/font/InternationalFonts/InternationalFontsRendering.java 2010-11-01 11:27:46.000000000 +0100
> -@@ -0,0 +1,192 @@
> +--- /dev/null 2010-06-29 11:10:08.737208357 +0200
> ++++ openjdk/jdk/test/java/awt/font/InternationalFonts/InternationalFontsRendering.java 2011-01-17 16:01:02.000000000 +0100
> +@@ -0,0 +1,211 @@
> ++/*
> ++ * Copyright 2010 Red Hat, Inc. All Rights Reserved.
> ++ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
> ++ *
> ++ * This code is free software; you can redistribute it and/or modify it
> ++ * under the terms of the GNU General Public License version 2 only, as
> ++ * published by the Free Software Foundation.
> ++ *
> ++ * This code is distributed in the hope that it will be useful, but WITHOUT
> ++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> ++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> ++ * version 2 for more details (a copy is included in the LICENSE file that
> ++ * accompanied this code).
> ++ *
> ++ * You should have received a copy of the GNU General Public License version
> ++ * 2 along with this work; if not, write to the Free Software Foundation,
> ++ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
> ++ */
> ++
> +import java.awt.Color;
> +import java.awt.Font;
> +import java.awt.Graphics2D;
> @@ -193,3 +212,283 @@
> + }
> +}
> +
> +--- /dev/null 2010-06-29 11:10:08.737208357 +0200
> ++++ openjdk/jdk/test/java/awt/font/InternationalFonts/InternationalFontsStyles.java 2011-01-17 16:01:18.000000000 +0100
> +@@ -0,0 +1,277 @@
> ++/*
> ++ * Copyright 2011 Red Hat, Inc. All Rights Reserved.
> ++ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
> ++ *
> ++ * This code is free software; you can redistribute it and/or modify it
> ++ * under the terms of the GNU General Public License version 2 only, as
> ++ * published by the Free Software Foundation.
> ++ *
> ++ * This code is distributed in the hope that it will be useful, but WITHOUT
> ++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> ++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> ++ * version 2 for more details (a copy is included in the LICENSE file that
> ++ * accompanied this code).
> ++ *
> ++ * You should have received a copy of the GNU General Public License version
> ++ * 2 along with this work; if not, write to the Free Software Foundation,
> ++ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
> ++ */
> ++
> ++import java.awt.Color;
> ++import java.awt.Font;
> ++import java.awt.Graphics2D;
> ++import java.awt.RenderingHints;
> ++import java.awt.image.BufferedImage;
> ++import java.io.File;
> ++import java.io.IOException;
> ++import java.util.Arrays;
> ++import java.util.List;
> ++import java.util.ArrayList;
> ++
> ++import javax.imageio.ImageIO;
> ++
> ++/**
> ++ * @test
> ++ * @run main InternationalFontsStyles
> ++ * @author Pavel Tisnovsky
> ++ *
> ++ * @summary This test check if selected international characters could be
> ++ * properly rendered with various font styles and logical names.
> ++ *
> ++ * This regression test check if selected international characters
> ++ * could be properly rendered using various styles and logical font
> ++ * names - the test check rendering using all combinations of logical
> ++ * font names (Font.SERIF, Font.SANS_SERIF, Font.MONOSPACED,
> ++ * Font.DIALOG, Font.DIALOG_INPUT) and font styles (Font.PLAIN,
> ++ * Font.BOLD, Font.ITALIC, Font.BOLD+Font.ITALIC).
> ++ *
> ++ * The test is based on rendering certain characters from selected code
> ++ * pages into BufferedImage.
> ++ *
> ++ * When the shape of selected character does not exists or font
> ++ * configuration is broken, only empty rectangle is rendered instead of
> ++ * the selected character shape. This rectangle is filtered and then
> ++ * the destination image is tested whether it is empty (=white).
> ++ *
> ++ * If test images with rendered characters needs to be created use
> ++ * following flag: -create-images
> ++ *
> ++ */
> ++public class InternationalFontsStyles
> ++{
> ++ // all logical font names which are tested
> ++ private static final String[] fontNames = {Font.SERIF, Font.SANS_SERIF, Font.MONOSPACED, Font.DIALOG, Font.DIALOG_INPUT};
> ++
> ++ // all font styles which are tested
> ++ private static final int[] fontStyles = {Font.PLAIN, Font.BOLD, Font.ITALIC, Font.BOLD+Font.ITALIC};
> ++
> ++ // width and height of image where is each character rendered
> ++ private static int WIDTH = 200;
> ++ private static int HEIGHT = 200;
> ++
> ++ // font size used for rendering
> ++ private static final int FONT_SIZE = 160;
> ++
> ++ private static final int MINIMUM_HORIZONTAL_LINE_LENGTH = 70;
> ++ private static final int MINIMUM_VERTICAL_LINE_LENGTH = 80;
> ++ private static final int BLACK_WHITE_THRESHOLD = 128;
> ++ private static final int BLACK_PIXEL_COUNT_THRESHOLD = WIDTH * HEIGHT / 1000;
> ++
> ++ private static final String[][] testedStrings = {
> ++ {"Latin-1", "abcdefABCDEF"},
> ++ {"Latin-2", "??????????????????????????"},
> ++ {"Cyrilic", "??????????????????"},
> ++ {"Greek", "????????????????????????????????"},
> ++ {"Asia-Test1", "???????????????"},
> ++ {"Asia-Test2", "??????????????????"},
> ++ {"Asia-Test3", "???????????????"},
> ++ {"Asia-Test4", "???????????????"},
> ++ };
> ++
> ++ /**
> ++ * Returns string containing given font style. This style could
> ++ * be PLAIN, BOLD, ITALIC or combination of these styles.
> ++ *
> ++ * @param fontStyle selected font style, eg. Font.BOLD
> ++ * @return textual representation of font style
> ++ */
> ++ private static String getFontStyle(int fontStyle)
> ++ {
> ++ return new String[]
> ++ // 0 1 2 1+2
> ++ { "PLAIN", "BOLD", "ITALIC", "BOLD+ITALIC" }[fontStyle];
> ++ }
> ++
> ++ /**
> ++ * Creates test image a renders one big character to it.
> ++ * @param str string to be rendered
> ++ * @param fontStyle selected font style
> ++ * @param fontName selected font name
> ++ * @return
> ++ */
> ++ private BufferedImage createTestImage(String str, String fontName, int fontStyle) {
> ++ BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_BYTE_GRAY);
> ++ Graphics2D gc = image.createGraphics();
> ++ gc.setBackground(Color.WHITE);
> ++ gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
> ++ gc.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
> ++ gc.clearRect(0, 0, WIDTH, HEIGHT);
> ++ gc.setFont(new Font(fontName, fontStyle, FONT_SIZE));
> ++ gc.setColor(Color.BLACK);
> ++ gc.drawString(str, 0, HEIGHT - 40);
> ++ gc.dispose();
> ++ return image;
> ++ }
> ++
> ++ /**
> ++ * Creates destination image and then copies data from source image to it
> ++ * @param src source image
> ++ * @return destination image as copy of source image
> ++ */
> ++ private BufferedImage createDestinationImage(BufferedImage src)
> ++ {
> ++ BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_BYTE_GRAY);
> ++ src.copyData(image.getRaster());
> ++ return image;
> ++ }
> ++
> ++ /**
> ++ * Removes long horizontal lines from image
> ++ */
> ++ private void removeHorizontalLines(BufferedImage image)
> ++ {
> ++ for (int y = 0; y < image.getHeight(); y++)
> ++ {
> ++ int startX = -1, endX = -1;
> ++ for (int x = 0; x < image.getWidth(); x++)
> ++ {
> ++ int color = image.getRaster().getSample(x, y, 0);
> ++ if (startX < 0 && color <= BLACK_WHITE_THRESHOLD) {
> ++ startX = x;
> ++ //System.out.println("> " + y + "\t" + x);
> ++ }
> ++ if (startX > 0 && endX < 0 && color > BLACK_WHITE_THRESHOLD)
> ++ {
> ++ endX = x;
> ++ //System.out.println("< " + y + "\t" + x);
> ++ }
> ++ }
> ++ // remove long horizontal line, but only if this line found detected in image
> ++ if (startX > 0 && endX > 0 && (endX - startX) > MINIMUM_HORIZONTAL_LINE_LENGTH)
> ++ {
> ++ for (int x = startX; x < endX; x++) {
> ++ image.getRaster().setSample(x, y, 0, 255);
> ++ }
> ++ }
> ++ }
> ++ }
> ++
> ++ /**
> ++ * Removes long vertical lines from image
> ++ */
> ++ private void removeVerticalLines(BufferedImage image)
> ++ {
> ++ for (int x = 0; x < image.getWidth(); x++)
> ++ {
> ++ int startY = -1, endY = -1;
> ++ for (int y = 0; y < image.getHeight(); y++)
> ++ {
> ++ int color = image.getRaster().getSample(x, y, 0);
> ++ if (startY < 0 && color <= BLACK_WHITE_THRESHOLD)
> ++ {
> ++ startY = y;
> ++ }
> ++ if (startY > 0 && endY < 0 && color > BLACK_WHITE_THRESHOLD)
> ++ {
> ++ endY = y;
> ++ //System.out.println("< " + y + "\t" + x);
> ++ }
> ++ }
> ++ // remove long vertical line, but only if this line found detected in image
> ++ if (startY > 0 && endY > 0 && (endY - startY) > MINIMUM_VERTICAL_LINE_LENGTH)
> ++ {
> ++ for (int y = startY; y < endY; y++) {
> ++ image.getRaster().setSample(x, y, 0, 255);
> ++ }
> ++ }
> ++ }
> ++ }
> ++
> ++ /**
> ++ * Test if image is almost empty (one large white area)
> ++ * @param image
> ++ * @return
> ++ */
> ++ private boolean isImageAlmostEmpty(BufferedImage image)
> ++ {
> ++ int blackPixelCount = 0;
> ++ for (int y = 0; y < image.getHeight(); y++)
> ++ {
> ++ for (int x = 0; x < image.getWidth(); x++)
> ++ {
> ++ if (image.getRaster().getSample(x, y, 0) < BLACK_WHITE_THRESHOLD)
> ++ {
> ++ blackPixelCount++;
> ++ }
> ++ }
> ++ }
> ++ return blackPixelCount < BLACK_PIXEL_COUNT_THRESHOLD;
> ++ }
> ++
> ++ private String constructImageFileName(String fontName, int fontStyle, int code, String suffix)
> ++ {
> ++ return fontName + "_" + getFontStyle(fontStyle) + "_" + code + "_" + suffix + ".png";
> ++ }
> ++
> ++ public void runTest(boolean createImages) throws IOException
> ++ {
> ++ List<Integer> badCharacters = new ArrayList<Integer>();
> ++ for (String[] testedString : testedStrings)
> ++ {
> ++ for (String fontName : fontNames)
> ++ {
> ++ for (int fontStyle : fontStyles)
> ++ {
> ++ System.out.format("check string %s rendered by font %s with style %s\n", testedString[1], fontName,
> ++ getFontStyle(fontStyle));
> ++ for (int i = 0; i < testedString[1].length(); i++)
> ++ {
> ++ String str = testedString[1].substring(i, 1 + i);
> ++ int code = str.charAt(0);
> ++ System.out.print(code + "\t");
> ++ BufferedImage src = createTestImage(str, fontName, fontStyle);
> ++ BufferedImage dst = createDestinationImage(src);
> ++ removeHorizontalLines(dst);
> ++ removeVerticalLines(dst);
> ++
> ++ if (createImages /*|| true*/)
> ++ {
> ++ ImageIO.write(src, "png",
> ++ new File(constructImageFileName(fontName, fontStyle, code, "scr")));
> ++ ImageIO.write(dst, "png",
> ++ new File(constructImageFileName(fontName, fontStyle, code, "dst")));
> ++ }
> ++
> ++ if (isImageAlmostEmpty(dst))
> ++ {
> ++ System.out.println("*** Error in rendering of character with code: " + code);
> ++ badCharacters.add(Integer.valueOf(code));
> ++ }
> ++ }
> ++ System.out.println();
> ++ }
> ++ }
> ++ }
> ++ // if at least one character is not rendered properly throw an exception
> ++ if (!badCharacters.isEmpty())
> ++ {
> ++ throw new RuntimeException("There are some broken characters: " + badCharacters.toString());
> ++ }
> ++ System.out.println("done!");
> ++ }
> ++
> ++ public static void main(String[] args) throws IOException
> ++ {
> ++ new InternationalFontsStyles().runTest(Arrays.asList(args).contains("-create-images"));
> ++ }
> ++}
--
Andrew :)
Free Java Software Engineer
Red Hat, Inc. (http://www.redhat.com)
Support Free Java!
Contribute to GNU Classpath and IcedTea
http://www.gnu.org/software/classpath
http://icedtea.classpath.org
PGP Key: 94EFD9D8 (http://subkeys.pgp.net)
Fingerprint = F8EF F1EA 401E 2E60 15FA 7927 142C 2591 94EF D9D8
More information about the distro-pkg-dev
mailing list