Saltar al contenido

¿Por qué una fuente Java de solo caracteres latinos afirma admitir caracteres asiáticos, aunque no es así?

Hola usuario de nuestro sitio, descubrimos la solución a tu interrogante, continúa leyendo y la encontrarás un poco más abajo.

Solución:

Finalmente lo resolví. Hubo una serie de causas subyacentes, que se vieron obstaculizadas aún más por una dosis adicional de variabilidad multiplataforma.

JFreeChart representa el texto en la ubicación incorrecta porque usa un objeto de fuente diferente

El problema de diseño se produjo porque JFreeChart estaba calculando inadvertidamente las métricas para el diseño utilizando un objeto de fuente diferente que el que AWT realmente usa para renderizar la fuente. (Como referencia, el cálculo de JFreeChart ocurre en org.jfree.text#getTextBounds.)

El motivo del objeto Font diferente es el resultado de la “manipulación mágica” implícita mencionada en la pregunta, que se realiza dentro de java.awt.font.TextLayout#singleFont.

Esas tres líneas de manipulación mágica se pueden condensar en esto:

font = Font.getFont(font.getAttributes())

En inglés, esto le pide al administrador de fuentes que nos dé un nuevo objeto Font basado en el “attributes”(nombre, familia, tamaño en puntos, etc.) de la fuente proporcionada. En determinadas circunstancias, Font te devuelve será diferente de la Font con el que originalmente empezaste.

Para corregir las métricas (y así arreglar el diseño), la solución es ejecutar el one-liner de arriba por su cuenta Font objeto antes de establecer la fuente en los objetos JFreeChart.

Después de hacer esto, el diseño funcionó bien para mí, al igual que los caracteres japoneses. También debería arreglar el diseño para usted, aunque es posible que no muestre los caracteres japoneses correctamente para usted. Lea a continuación sobre las fuentes nativas para comprender por qué.

El administrador de fuentes de Mac OS X prefiere devolver fuentes nativas incluso si lo alimenta con un archivo TTF físico

El diseño del texto fue arreglado por el cambio anterior … pero ¿por qué sucede esto? ¿En qué circunstancias FontManager nos devolvería realmente un tipo diferente de Font objeto que el que proporcionamos?

Hay muchas razones, pero al menos en Mac OS X, la razón relacionada con el problema es que el administrador de fuentes parece prefiero devolver fuentes nativas siempre que sea posible.

En otras palabras, si crea una nueva fuente a partir de una fuente TTF física llamada “Foobar” usando Font.createFonty luego llame a Font.getFont () con attributes derivado de su fuente física “Foobar” … siempre que OS X ya tenga una fuente Foobar instalada, el administrador de fuentes le devolverá una CFont objeto en lugar del TrueTypeFont objeto que estabas esperando. Esto parece aguantar true incluso si registra la fuente mediante GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont.

En mi caso, esto arrojó una pista falsa a la investigación: ya tenía instalada la fuente “Source Sans” en mi Mac, lo que significaba que estaba obteniendo resultados diferentes de las personas que no la tenían.

Las fuentes nativas de Mac OS X siempre admiten caracteres asiáticos

El quid del asunto es que Mac OS X CFont objetos siempre admite juegos de caracteres asiáticos. No tengo claro el mecanismo exacto que permite esto, pero sospecho que es una especie de característica de fuente alternativa del propio OS X y no de Java. En cualquier caso, un CFont siempre pretende (y realmente puede) representar caracteres asiáticos con los glifos correctos.

Esto deja en claro el mecanismo que permitió que ocurriera el problema original:

  • creamos un físico Font desde un archivo TTF físico, que en sí mismo no es compatible con el japonés.
  • La misma fuente física que la anterior también se instaló en mi Libro de fuentes de Mac OS X
  • al calcular el diseño del gráfico, JFreeChart preguntó al físico Font objeto de las métricas del texto japonés. El fisico Font no pudo hacer esto correctamente porque no admite juegos de caracteres asiáticos.
  • cuando realmente se dibuja el gráfico, la manipulación mágica en TextLayout#singleFont hizo que obtuviera un CFont objeto y dibuje el glifo utilizando la fuente nativa del mismo nombre, en lugar de la física TrueTypeFont. Por lo tanto, los glifos eran correctos pero no estaban colocados correctamente.

Obtendrá resultados diferentes dependiendo de si ha registrado la fuente y si tiene la fuente instalada en su sistema operativo

Si llamas Font.getFont() con el attributes a partir de una fuente TTF creada, obtendrá uno de tres resultados diferentes, dependiendo de si la fuente está registrada y si tiene la misma fuente instalada de forma nativa:

  • Si tu hacer tiene una fuente de plataforma nativa instalada con el mismo nombre que su fuente TTF (independientemente de si registró la fuente o no), obtendrá una fuente de soporte asiático CFont para la fuente que deseaba.
  • Si registró el TTF Font en GraphicsEnvironment pero no tiene una fuente nativa con el mismo nombre, llamar a Font.getFont () devolverá una fuente física TrueTypeFont Objeto de vuelta. Esto le da la fuente que desea, pero no obtiene caracteres asiáticos.
  • Si no registró el TTF Font y tampoco tiene una fuente nativa con el mismo nombre, llamar a Font.getFont () devuelve un CFont compatible con Asia, pero no será la fuente que solicitó.

En retrospectiva, nada de esto es del todo sorprendente. Llevando a:

Inadvertidamente estaba usando la fuente incorrecta

En la aplicación de producción, estaba creando una fuente, pero olvidé registrarla inicialmente con GraphicsEnvironment. Si no ha registrado una fuente cuando realiza la manipulación mágica anterior, Font.getFont() no sabe cómo recuperarlo y, en su lugar, obtiene una fuente de respaldo. UPS.

En Windows, Mac y Linux, esta fuente de respaldo generalmente parece ser Dialog, que es una fuente lógica (compuesta) que admite caracteres asiáticos. Al menos en Java 7u72, la fuente Dialog tiene por defecto las siguientes fuentes para alfabetos occidentales:

  • Mac: Lucida Grande
  • Linux (CentOS): Lucida Sans
  • Windows: Arial

Este error fue realmente bueno para nuestros usuarios asiáticos., porque significaba que sus juegos de caracteres se representaban como se esperaba con la fuente lógica … aunque los usuarios occidentales no obtenían los juegos de caracteres que queríamos.

Dado que se había renderizado en las fuentes incorrectas y necesitábamos arreglar el diseño japonés de todos modos, decidí que sería mejor intentar estandarizar una sola fuente común para futuras versiones (y así acercarme a las sugerencias de trashgod).

Además, la aplicación tiene requisitos de calidad de representación de fuentes que pueden no siempre permitir el uso de ciertas fuentes, por lo que una decisión razonable parece ser intentar configurar la aplicación para usar Lucida Sans, que es la única fuente física incluida por Oracle en todas las copias de Java. Pero…

Lucida Sans no juega bien con personajes asiáticos en todas las plataformas

La decisión de intentar usar Lucida Sans parecía razonable … pero rápidamente descubrí que existen diferencias de plataforma en cómo se maneja Lucida Sans. En Linux y Windows, si solicita una copia de la fuente “Lucida Sans”, obtendrá una TrueTypeFont objeto. Pero esa fuente no admite caracteres asiáticos.

El mismo problema tiene true en Mac OS X si solicita “Lucida Sans” … pero si solicita un nombre ligeramente diferente “LucidaSans” (tenga en cuenta la falta de espacio), obtendrá un CFont objeto que admite Lucida Sans y personajes asiáticos, para que puedas tener tu pastel y comértelo también.

En otras plataformas, al solicitar “LucidaSans” se obtiene una copia de la fuente estándar de Dialog porque no existe dicha fuente y Java está devolviendo su valor predeterminado. En Linux, tiene algo de suerte aquí porque Dialog en realidad usa Lucida Sans para texto occidental (y también usa una fuente alternativa decente para caracteres asiáticos).

Esto nos da una ruta para obtener (casi) la misma fuente física en todas las plataformas, y que también admite caracteres asiáticos, solicitando fuentes con estos nombres:

  • Mac OS X: “LucidaSans” (que genera Lucida Sans + fuentes de respaldo asiáticas)
  • Linux: “Diálogo” (que genera Lucida Sans + fuentes de copia de seguridad asiáticas)
  • Windows: “Diálogo” (cede Arial + Fuentes de respaldo asiáticas)

He estudiado detenidamente las propiedades de fonts.properties en Windows y no pude encontrar una secuencia de fuentes que por defecto sea Lucida Sans, por lo que parece que nuestros usuarios de Windows necesitarán quedarse atascados con Arial … pero al menos no es tan diferente visualmente de Lucida Sans, y la calidad de representación de fuentes de Windows es razonable.

¿Dónde terminó todo?

En resumen, ahora solo estamos usando fuentes de plataforma. (¡Estoy seguro de que @trashgod se está riendo mucho en este momento!) ¡Tanto los servidores Mac como Linux obtienen Lucida Sans, Windows obtiene Arial, la calidad de renderizado es buena y todos están felices!

Aunque no aborda su pregunta directamente, pensé que podría proporcionar un punto de referencia útil para mostrar el resultado utilizando la fuente predeterminada de la plataforma en un gráfico sin adornos. Una versión simplificada de BarChartDemo1, fuente, se muestra a continuación.

Debido a los caprichos de las métricas de fuentes de terceros, trato de evitar desviarme de las fuentes lógicas estándar de la plataforma, que se eligen en función de la configuración regional admitida por la plataforma. Las fuentes lógicas se asignan a fuentes físicas en los archivos de configuración de la plataforma. En Mac OS, el archivo relevante está en $JAVA_HOME/jre/lib/, dónde $JAVA_HOME es el resultado de evaluar /usr/libexec/java_home -v 1.n y norte es tu versión. Veo resultados similares con la versión 7 u 8. En particular, fontconfig.properties.src define la fuente utilizada para proporcionar variaciones de la familia de fuentes japonesas. Todas las asignaciones parecen utilizar MS Mincho o MS Gothic.

imagen

import java.awt.Dimension;
import java.awt.EventQueue;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;

/**
 * @see http://stackoverflow.com/a/26090878/230513
 * @see http://www.jfree.org/jfreechart/api/javadoc/src-html/org/jfree/chart/demo/BarChartDemo1.html
 */
public class BarChartDemo1 extends ApplicationFrame 

    /**
     * Creates a new demo instance.
     *
     * @param title the frame title.
     */
    public BarChartDemo1(String title) 
        super(title);
        CategoryDataset dataset = createDataset();
        JFreeChart chart = createChart(dataset);
        ChartPanel chartPanel = new ChartPanel(chart)

            @Override
            public Dimension getPreferredSize() 
                return new Dimension(600, 400);
            
        ;
        chartPanel.setFillZoomRectangle(true);
        chartPanel.setMouseWheelEnabled(true);
        setContentPane(chartPanel);
    

    /**
     * Returns a sample dataset.
     *
     * @return The dataset.
     */
    private static CategoryDataset createDataset() 

        // row keys...
        String series1 = "First";
        String series2 = "Second";
        String series3 = "Third";

        // column keys...
        String category1 = "クローズ";
        String category2 = "クローズ";
        String category3 = "クローズクローズクローズ";
        String category4 = "Category 4 クローズ";
        String category5 = "Category 5";

        // create the dataset...
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();

        dataset.addValue(1.0, series1, category1);
        dataset.addValue(4.0, series1, category2);
        dataset.addValue(3.0, series1, category3);
        dataset.addValue(5.0, series1, category4);
        dataset.addValue(5.0, series1, category5);

        dataset.addValue(5.0, series2, category1);
        dataset.addValue(7.0, series2, category2);
        dataset.addValue(6.0, series2, category3);
        dataset.addValue(8.0, series2, category4);
        dataset.addValue(4.0, series2, category5);

        dataset.addValue(4.0, series3, category1);
        dataset.addValue(3.0, series3, category2);
        dataset.addValue(2.0, series3, category3);
        dataset.addValue(3.0, series3, category4);
        dataset.addValue(6.0, series3, category5);

        return dataset;

    

    /**
     * Creates a sample chart.
     *
     * @param dataset the dataset.
     *
     * @return The chart.
     */
    private static JFreeChart createChart(CategoryDataset dataset) 

        // create the chart...
        JFreeChart chart = ChartFactory.createBarChart(
                "Bar Chart Demo 1", // chart title
                "Category", // domain axis label
                "Value", // range axis label
                dataset, // data
                PlotOrientation.HORIZONTAL, // orientation
                true, // include legend
                true, // tooltips?
                false // URLs?
        );
        return chart;
    

    /**
     * Starting point for the demonstration application.
     *
     * @param args ignored.
     */
    public static void main(String[] args) 
        EventQueue.invokeLater(() -> 
            BarChartDemo1 demo = new BarChartDemo1("Bar Chart Demo 1");
            demo.pack();
            RefineryUtilities.centerFrameOnScreen(demo);
            demo.setVisible(true);
        );
    

Si posees algún recelo y forma de ascender nuestro tutorial puedes escribir una crítica y con mucho gusto lo analizaremos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *