jamisonjiang / graph-support

Java re-implementation of tiny graphviz
Apache License 2.0
24 stars 7 forks source link

Table alignment problems, depending on font #2

Closed atextor closed 1 month ago

atextor commented 11 months ago

I'm trying to create a Node based on a table, with left-aligned lines. However, even with .align(Labeljust.LEFT), I can't seem to create consistently aligned lines. The results vary depending on the used font.

Creation of the node:

String fontName = "...";
Html.Table table = table()
   .border( 1 )
   .cellBorder( 0 )
   .cellSpacing( 0 )
   .cellPadding( 4 )
   .tr( td().text( "Test test test" ).fontName( fontName ).align( Labeljust.CENTER ) )
   .tr( td().text( "Another test test" ).fontName( fontName ).align( Labeljust.CENTER ) )
   .tr( td().cellPadding( 1 ).bgColor( Color.BLACK ).height( 1 ) )
   .tr( td().text( "someProperty: Test test123" ).fontName( fontName ).align( Labeljust.LEFT ) )
   .tr( td().text( "description: This is a test description" ).fontName( fontName ).align( Labeljust.LEFT ) )
   .tr( td().text( "anotherProperty: Another12" ).fontName( fontName ).align( Labeljust.LEFT ) )
   .tr( td().text( "see: http://example.com/" ).fontName( fontName ).align( Labeljust.LEFT ) )
   .tr( td().text( "yetAnother: yetAnotherTest" ).fontName( fontName ).align( Labeljust.LEFT ) );
Node node = Node.builder().color( Color.BLACK ).table( table ).build();

The results, for various fonts tried: Roboto Condensed: image

Arial: image

Noto Sans: image

DejaVu Sans: image

Is there something I can do to force multiple lines to be properly left aligned?

jamisonjiang commented 11 months ago

This is a known issue in the schedule task. The font from svg needs to be mapped with the font from java.awt, otherwise there will be some errors when measuring the text length.

atextor commented 11 months ago

I tried the following:

// java.io.File
// java.awt.Font
// java.awt.GraphicsEnvironment
File fontFile = new File("RobotoCondensed.ttf");
Font font = Font.createFont(Font.TRUETYPE_FONT, fontFile);
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
ge.registerFont(font);

to make the font known to AWT before generating the SVG, but unfortunately it made no difference.

jamisonjiang commented 11 months ago
  1. Please take it from the font name below:
    String[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
    for (String f : fonts) {
    System.out.println(f);
    }

    2.Please confirm whether the machine that browses the svg is the same as the machine that generated the svg. The fonts of different machines may not be the same, and the results of the svg generated on the server to the svg renderer of different machines may be different.

atextor commented 11 months ago

I have tried that as well (before explictly registering the font in the AWT GraphicsEnvironment). getAvailableFontFamilyNames() contains all the font names I've tried in the generation. I'm doing all tests locally, i.e, the machine generating the SVG is the same machine that is viewing the SVG.

jamisonjiang commented 11 months ago

I can not reproduce on my local env, would you mind provide env info and font output?

atextor commented 11 months ago

I'm running Ubuntu Linux 20.04.5, with Xfce 4.14. Adoptium/Temurin JDK 17.0.8. The output of GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames() has over 2000 entries on my machine, but it contains the fonts:

image

Please let me know if there is other specific info that would help you.

jamisonjiang commented 11 months ago

This problem is due to the inaccurate measurement of FontMetrics in some fonts. At present, I have not found a better way to measure the length of the string. If you have any local more accurate test methods, you can register the implementation of MeasureText locally to override someone's. method to measure the length of the string, or you can try to use the font with the most accurate measurement.

atextor commented 11 months ago

Hi @jamisonjiang, thank you for the hint that a custom MeasureText can be provided.

I've achieved good results in determining the string width via AWT by using Font.getStringBounds() like so:

import java.awt.Font;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;

import org.apache_gs.commons.lang3.StringUtils;
import org.graphper.def.FlatPoint;
import org.graphper.layout.MeasureText;

public class FontMeasurement implements MeasureText {
   @Override
   public int order() {
      return -1;
   }

   @Override
   public boolean envSupport() {
      return true;
   }

   @Override
   public FlatPoint measure(String text, String fontName, double fontSize) {
      if (StringUtils.isEmpty(text) || fontSize <= 0) {
         return new FlatPoint(0, 0);
      }

      Font font = new Font(fontName, Font.PLAIN, (int) fontSize);
      AffineTransform affineTransform = font.getTransform();
      FontRenderContext fontRenderContext = new FontRenderContext(affineTransform, true, true);
      Rectangle2D stringBounds = font.getStringBounds(text, fontRenderContext);
      int width = (int) (stringBounds.getWidth()) + 10;
      int height = (int) (stringBounds.getHeight());
      return new FlatPoint(height, width);
   }
}

This solves all the alignment problems, it has the following results:

Roboto Condensed: image

Noto Sans: image

DejaVu Sans: image

I can live with that. The only question is, should this be made the default measurement algorithm? You decide. If not, the issue can be closed.

jamisonjiang commented 11 months ago

Awesome! I just simply tested a few commonly used fonts before, and found that there is no significant difference in value between the two, so I always thought that the two were equivalent. It seems that I can consider this as the default measurement algorithm, thank you.

jamisonjiang commented 6 months ago

We supported export image directly in latest version and the more accuracy font name detect because render and measure at the same way.