1
0
mirror of https://github.com/octoleo/plantuml.git synced 2024-11-22 13:05:09 +00:00

Add Support for Font Selection in Ditaa

This adds support for selecting the font in ditaa diagrams by specifying
--font-family, --font-size and/or --font-variant in the ditaa() line.

The change is actually taken over from @pepijnve who implemented in
https://github.com/pepijnve/ditaa/issues/1. I extended it for supporting
the necessary flags from plantuml.
This commit is contained in:
Christoph Rüthing 2022-02-24 13:12:53 +01:00
parent 87cbe82cd0
commit 8066a3f730
6 changed files with 141 additions and 118 deletions

View File

@ -34,6 +34,7 @@
*/ */
package net.sourceforge.plantuml.ditaa; package net.sourceforge.plantuml.ditaa;
import java.awt.Font;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@ -61,11 +62,12 @@ public class PSystemDitaa extends AbstractPSystem {
private final boolean dropShadows; private final boolean dropShadows;
private final String data; private final String data;
private final float scale; private final float scale;
private final Font font;
private final boolean performSeparationOfCommonEdges; private final boolean performSeparationOfCommonEdges;
private final boolean allCornersAreRound; private final boolean allCornersAreRound;
public PSystemDitaa(UmlSource source, String data, boolean performSeparationOfCommonEdges, boolean dropShadows, public PSystemDitaa(UmlSource source, String data, boolean performSeparationOfCommonEdges, boolean dropShadows,
boolean allCornersAreRound, float scale) { boolean allCornersAreRound, float scale, Font font) {
super(source); super(source);
this.data = data; this.data = data;
this.dropShadows = dropShadows; this.dropShadows = dropShadows;
@ -84,11 +86,12 @@ public class PSystemDitaa extends AbstractPSystem {
this.processingOptions = null; this.processingOptions = null;
} }
this.scale = scale; this.scale = scale;
this.font = font;
} }
PSystemDitaa add(String line) { PSystemDitaa add(String line) {
return new PSystemDitaa(getSource(), data + line + BackSlash.NEWLINE, performSeparationOfCommonEdges, return new PSystemDitaa(getSource(), data + line + BackSlash.NEWLINE, performSeparationOfCommonEdges,
dropShadows, allCornersAreRound, scale); dropShadows, allCornersAreRound, scale, font);
} }
public DiagramDescription getDescription() { public DiagramDescription getDescription() {
@ -113,6 +116,10 @@ public class PSystemDitaa extends AbstractPSystem {
final Field f_renderingOptions = options.getClass().getField("renderingOptions"); final Field f_renderingOptions = options.getClass().getField("renderingOptions");
final Object renderingOptions = f_renderingOptions.get(options); final Object renderingOptions = f_renderingOptions.get(options);
// renderingOptions.setFont(font);
final Method setFont = renderingOptions.getClass().getMethod("setFont", Font.class);
setFont.invoke(renderingOptions, font);
// renderingOptions.setScale(scale); // renderingOptions.setScale(scale);
final Method setScale = renderingOptions.getClass().getMethod("setScale", float.class); final Method setScale = renderingOptions.getClass().getMethod("setScale", float.class);
setScale.invoke(renderingOptions, scale); setScale.invoke(renderingOptions, scale);

View File

@ -34,6 +34,7 @@
*/ */
package net.sourceforge.plantuml.ditaa; package net.sourceforge.plantuml.ditaa;
import java.awt.Font;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -70,10 +71,11 @@ public class PSystemDitaaFactory extends PSystemBasicFactory<PSystemDitaa> {
allCornersAreRound = true; allCornersAreRound = true;
final float scale = extractScale(startLine); final float scale = extractScale(startLine);
final Font font = extractFont(startLine);
if (getDiagramType() == DiagramType.UML) if (getDiagramType() == DiagramType.UML)
return null; return null;
else if (getDiagramType() == DiagramType.DITAA) else if (getDiagramType() == DiagramType.DITAA)
return new PSystemDitaa(source, "", performSeparationOfCommonEdges, dropShadows, allCornersAreRound, scale); return new PSystemDitaa(source, "", performSeparationOfCommonEdges, dropShadows, allCornersAreRound, scale, font);
else else
throw new IllegalStateException(getDiagramType().name()); throw new IllegalStateException(getDiagramType().name());
@ -95,7 +97,8 @@ public class PSystemDitaaFactory extends PSystemBasicFactory<PSystemDitaa> {
allCornersAreRound = true; allCornersAreRound = true;
final float scale = extractScale(line); final float scale = extractScale(line);
return new PSystemDitaa(source, "", performSeparationOfCommonEdges, dropShadows, allCornersAreRound, scale); final Font font = extractFont(line);
return new PSystemDitaa(source, "", performSeparationOfCommonEdges, dropShadows, allCornersAreRound, scale, font);
} }
if (system == null) if (system == null)
return null; return null;
@ -115,4 +118,46 @@ public class PSystemDitaaFactory extends PSystemBasicFactory<PSystemDitaa> {
} }
return 1; return 1;
} }
private Font extractFont(String line) {
if (line == null)
return new Font("Dialog", Font.BOLD, 12);
final Pattern pName = Pattern.compile("font-family=([a-zA-Z0-0 ]+)");
final Matcher mName = pName.matcher(line);
String fontName = "Dialog";
if (mName.find())
{
fontName = mName.group(1);
}
final Pattern pVariant = Pattern.compile("font-variant=(BOLD|ITALIC|PLAIN)");
final Matcher mVariant = pVariant.matcher(line);
int fontVariant = Font.BOLD;
if (mVariant.find())
{
switch (mVariant.group(1))
{
case "BOLD":
fontVariant = Font.BOLD;
break;
case "ITALIC":
fontVariant = Font.ITALIC;
break;
case "PLAIN":
fontVariant = Font.PLAIN;
break;
}
}
final Pattern pSize = Pattern.compile("font-size=([\\d]+)");
final Matcher mSize = pSize.matcher(line);
int fontSize = 12;
if (mSize.find())
{
fontSize = Integer.parseInt(mSize.group(1));
}
return new Font(fontName, fontVariant, fontSize);
}
} }

View File

@ -20,6 +20,7 @@
*/ */
package org.stathissideris.ascii2image.core; package org.stathissideris.ascii2image.core;
import java.awt.*;
import java.util.HashMap; import java.util.HashMap;
import org.stathissideris.ascii2image.graphics.CustomShapeDefinition; import org.stathissideris.ascii2image.graphics.CustomShapeDefinition;
@ -41,6 +42,8 @@ public class RenderingOptions {
private float scale = 1; private float scale = 1;
private Font font = new Font("Dialog", Font.BOLD, 12);
/** /**
* @return * @return
*/ */
@ -113,4 +116,14 @@ public class RenderingOptions {
antialias = b; antialias = b;
} }
public Font getFont()
{
return font;
}
public void setFont(Font font)
{
this.font = font;
}
} }

View File

@ -111,6 +111,8 @@ public class Diagram {
this.cellWidth = options.renderingOptions.getCellWidth(); this.cellWidth = options.renderingOptions.getCellWidth();
this.cellHeight = options.renderingOptions.getCellHeight(); this.cellHeight = options.renderingOptions.getCellHeight();
FontMeasurer fontMeasurer = new FontMeasurer(options.renderingOptions.getFont());
width = grid.getWidth() * cellWidth; width = grid.getWidth() * cellWidth;
height = grid.getHeight() * cellHeight; height = grid.getHeight() * cellHeight;
@ -526,7 +528,7 @@ public class Diagram {
ArrayList textGroups = nonBlank.breakIntoDistinctBoundaries(); ArrayList textGroups = nonBlank.breakIntoDistinctBoundaries();
if(DEBUG) System.out.println(textGroups.size()+" text groups found"); if(DEBUG) System.out.println(textGroups.size()+" text groups found");
Font font = FontMeasurer.instance().getFontFor(cellHeight); Font font = fontMeasurer.getFontFor(cellHeight);
Iterator textGroupIt = textGroups.iterator(); Iterator textGroupIt = textGroups.iterator();
while(textGroupIt.hasNext()){ while(textGroupIt.hasNext()){
@ -550,10 +552,10 @@ public class Diagram {
int maxX = getCellMaxX(lastCell); int maxX = getCellMaxX(lastCell);
DiagramText textObject; DiagramText textObject;
if(FontMeasurer.instance().getWidthFor(string, font) > maxX - minX){ //does not fit horizontally if(fontMeasurer.getWidthFor(string, font) > maxX - minX){ //does not fit horizontally
Font lessWideFont = FontMeasurer.instance().getFontFor(maxX - minX, string); Font lessWideFont = fontMeasurer.getFontFor(maxX - minX, string);
textObject = new DiagramText(minX, y, string, lessWideFont); textObject = new DiagramText(minX, y, string, lessWideFont, fontMeasurer);
} else textObject = new DiagramText(minX, y, string, font); } else textObject = new DiagramText(minX, y, string, font, fontMeasurer);
textObject.centerVerticallyBetween(getCellMinY(cell), getCellMaxY(cell)); textObject.centerVerticallyBetween(getCellMinY(cell), getCellMaxY(cell));

View File

@ -39,8 +39,9 @@ public class DiagramText extends DiagramComponent {
private boolean isTextOnLine = false; private boolean isTextOnLine = false;
private boolean hasOutline = false; private boolean hasOutline = false;
private Color outlineColor = Color.white; private Color outlineColor = Color.white;
private final FontMeasurer fontMeasurer;
public DiagramText(int x, int y, String text, Font font){ public DiagramText(int x, int y, String text, Font font, FontMeasurer fontMeasurer){
if(text == null) throw new IllegalArgumentException("DiagramText cannot be initialised with a null string"); if(text == null) throw new IllegalArgumentException("DiagramText cannot be initialised with a null string");
if(font == null) throw new IllegalArgumentException("DiagramText cannot be initialised with a null font"); if(font == null) throw new IllegalArgumentException("DiagramText cannot be initialised with a null font");
@ -48,6 +49,7 @@ public class DiagramText extends DiagramComponent {
this.yPos = y; this.yPos = y;
this.text = text; this.text = text;
this.font = font; this.font = font;
this.fontMeasurer = fontMeasurer;
} }
public void centerInBounds(Rectangle2D bounds){ public void centerInBounds(Rectangle2D bounds){
@ -56,20 +58,20 @@ public class DiagramText extends DiagramComponent {
} }
public void centerHorizontallyBetween(int minX, int maxX){ public void centerHorizontallyBetween(int minX, int maxX){
int width = FontMeasurer.instance().getWidthFor(text, font); int width = fontMeasurer.getWidthFor(text, font);
int center = Math.abs(maxX - minX) / 2; int center = Math.abs(maxX - minX) / 2;
xPos += Math.abs(center - width / 2); xPos += Math.abs(center - width / 2);
} }
public void centerVerticallyBetween(int minY, int maxY){ public void centerVerticallyBetween(int minY, int maxY){
int zHeight = FontMeasurer.instance().getZHeight(font); int zHeight = fontMeasurer.getZHeight(font);
int center = Math.abs(maxY - minY) / 2; int center = Math.abs(maxY - minY) / 2;
yPos -= Math.abs(center - zHeight / 2); yPos -= Math.abs(center - zHeight / 2);
} }
public void alignRightEdgeTo(int x){ public void alignRightEdgeTo(int x){
int width = FontMeasurer.instance().getWidthFor(text, font); int width = fontMeasurer.getWidthFor(text, font);
xPos = x - width; xPos = x - width;
} }
@ -145,7 +147,7 @@ public class DiagramText extends DiagramComponent {
} }
public Rectangle2D getBounds(){ public Rectangle2D getBounds(){
Rectangle2D bounds = FontMeasurer.instance().getBoundsFor(text, font); Rectangle2D bounds = fontMeasurer.getBoundsFor(text, font);
bounds.setRect( bounds.setRect(
bounds.getMinX() + xPos, bounds.getMinX() + xPos,
bounds.getMinY() + yPos, bounds.getMinY() + yPos,

View File

@ -26,32 +26,25 @@ import java.awt.Graphics2D;
import java.awt.font.FontRenderContext; import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.Locale;
/** /**
* *
* @author Efstathios Sideris * @author Efstathios Sideris
*/ */
public class FontMeasurer { public class FontMeasurer {
private static final String fontFamilyName = "Dialog"; private final Font baseFont;
//private static final String fontFamilyName = "Helvetica"; private FontRenderContext fakeRenderContext;
private Graphics2D fakeGraphics;
private static final boolean DEBUG = false;
public FontMeasurer(Font font){
private static final FontMeasurer instance = new FontMeasurer(); baseFont = font;
FontRenderContext fakeRenderContext;
Graphics2D fakeGraphics;
{
BufferedImage image = new BufferedImage(1,1, BufferedImage.TYPE_INT_RGB); BufferedImage image = new BufferedImage(1,1, BufferedImage.TYPE_INT_RGB);
fakeGraphics = image.createGraphics(); fakeGraphics = image.createGraphics();
if (DEBUG) System.out.println("Locale: "+Locale.getDefault());
fakeRenderContext = fakeGraphics.getFontRenderContext(); fakeRenderContext = fakeGraphics.getFontRenderContext();
} }
public int getWidthFor(String str, int pixelHeight){ public int getWidthFor(String str, int pixelHeight){
Font font = getFontFor(pixelHeight); Font font = getFontFor(pixelHeight);
@ -74,125 +67,86 @@ public class FontMeasurer {
Rectangle2D rectangle = font.getStringBounds(str, fakeRenderContext); Rectangle2D rectangle = font.getStringBounds(str, fakeRenderContext);
return (int) rectangle.getHeight(); return (int) rectangle.getHeight();
} }
public Rectangle2D getBoundsFor(String str, Font font){ public Rectangle2D getBoundsFor(String str, Font font){
return font.getStringBounds(str, fakeRenderContext); return font.getStringBounds(str, fakeRenderContext);
} }
public Font getFontFor(int pixelHeight){
BufferedImage image = new BufferedImage(1,1, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = image.createGraphics();
return getFontFor(pixelHeight, fakeRenderContext);
}
public int getAscent(Font font){ public int getAscent(Font font){
fakeGraphics.setFont(font); fakeGraphics.setFont(font);
FontMetrics metrics = fakeGraphics.getFontMetrics(); FontMetrics metrics = fakeGraphics.getFontMetrics();
if(DEBUG) System.out.println("Ascent: "+metrics.getAscent());
return metrics.getAscent(); return metrics.getAscent();
} }
public int getZHeight(Font font){ public int getZHeight(Font font){
int height = (int) font.createGlyphVector(fakeRenderContext, "Z").getOutline().getBounds().getHeight(); return (int) font.createGlyphVector(fakeRenderContext, "Z").getOutline().getBounds().getHeight();
if(DEBUG) System.out.println("Z height: "+height);
return height;
} }
public Font getFontFor(int maxWidth, String string){ public Font getFontFor(final int maxWidth, final String string){
float size = 12; FontPredicate predicate = new FontPredicate() {
Font currentFont = new Font(fontFamilyName, Font.BOLD, (int) size); @Override
//ascent is the distance between the baseline and the tallest character public boolean test(Font font)
int width = getWidthFor(string, currentFont); {
int width = getWidthFor(string, font);
return width > maxWidth;
}
};
return deriveFont(predicate, 1.0f);
}
public Font getFontFor(final int pixelHeight){
FontPredicate predicate = new FontPredicate() {
@Override
public boolean test(Font font)
{
//ascent is the distance between the baseline and the tallest character
int ascent = getAscent(font);
return ascent > pixelHeight;
}
};
return deriveFont(predicate, 0.5f);
}
private Font deriveFont(FontPredicate predicate, float sizeDelta)
{
Font currentFont = baseFont;
float size = baseFont.getSize2D();
int direction; //direction of size change (towards smaller or bigger) int direction; //direction of size change (towards smaller or bigger)
if(width > maxWidth){ if(predicate.test(currentFont)){
currentFont = currentFont.deriveFont(size - 1); currentFont = currentFont.deriveFont(size - 1f);
size--; size--;
direction = -1; direction = -1;
} else { } else {
currentFont = currentFont.deriveFont(size + 1); currentFont = currentFont.deriveFont(size + 1f);
size++; size++;
direction = 1; direction = 1;
} }
while(size > 0){ while(size > 0){
currentFont = currentFont.deriveFont(size); currentFont = currentFont.deriveFont(size);
//rectangle = currentFont.getStringBounds(testString, frc); //rectangle = currentFont.getStringBounds(testString, frc);
width = getWidthFor(string, currentFont); if (direction == 1) {
if(direction == 1){ if (predicate.test(currentFont)) {
if(width > maxWidth){ size = size - sizeDelta;
size = size - 1;
return currentFont.deriveFont(size); return currentFont.deriveFont(size);
} else {
size = size + sizeDelta;
} }
else size = size + 1;
} else { } else {
if(width < maxWidth) if (!predicate.test(currentFont)) {
return currentFont; return currentFont;
else size = size - 1; } else {
size = size - sizeDelta;
}
} }
} }
return null; return null;
} }
private interface FontPredicate {
boolean test(Font font);
public Font getFontFor(int pixelHeight, FontRenderContext frc){
float size = 12;
Font currentFont = new Font(fontFamilyName, Font.BOLD, (int) size);
// Font currentFont = new Font("Times", Font.BOLD, (int) size);
if (DEBUG) System.out.println(currentFont.getFontName());
//ascent is the distance between the baseline and the tallest character
int ascent = getAscent(currentFont);
int direction; //direction of size change (towards smaller or bigger)
if(ascent > pixelHeight){
currentFont = currentFont.deriveFont(size - 1);
size--;
direction = -1;
} else {
currentFont = currentFont.deriveFont(size + 1);
size++;
direction = 1;
}
while(size > 0){
currentFont = currentFont.deriveFont(size);
//rectangle = currentFont.getStringBounds(testString, frc);
ascent = getAscent(currentFont);
if(direction == 1){
if(ascent > pixelHeight){
size = size - 0.5f;
return currentFont.deriveFont(size);
}
else size = size + 0.5f;
} else {
if(ascent < pixelHeight)
return currentFont;
else size = size - 0.5f;
}
}
return null;
}
public static FontMeasurer instance(){
return instance;
}
public FontMeasurer(){
}
public static void main(String[] args) {
//FontMeasurer.instance().getFontFor(7);
float size = 12;
Font currentFont = new Font("Sans", Font.BOLD, (int) size);
System.out.println(currentFont.getSize());
currentFont = currentFont.deriveFont(--size);
System.out.println(currentFont.getSize());
currentFont = currentFont.deriveFont(--size);
System.out.println(currentFont.getSize());
currentFont = currentFont.deriveFont(--size);
System.out.println(currentFont.getSize());
currentFont = currentFont.deriveFont(--size);
System.out.println(currentFont.getSize());
currentFont = currentFont.deriveFont(--size);
System.out.println(currentFont.getSize());
} }
} }