From 8066a3f730f52920233831d637cc7a241cc60303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20R=C3=BCthing?= Date: Thu, 24 Feb 2022 13:12:53 +0100 Subject: [PATCH] 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. --- .../plantuml/ditaa/PSystemDitaa.java | 11 +- .../plantuml/ditaa/PSystemDitaaFactory.java | 49 +++++- .../ascii2image/core/RenderingOptions.java | 13 ++ .../ascii2image/graphics/Diagram.java | 12 +- .../ascii2image/graphics/DiagramText.java | 12 +- .../ascii2image/graphics/FontMeasurer.java | 162 +++++++----------- 6 files changed, 141 insertions(+), 118 deletions(-) diff --git a/src/net/sourceforge/plantuml/ditaa/PSystemDitaa.java b/src/net/sourceforge/plantuml/ditaa/PSystemDitaa.java index eb3dcd11d..786a43d41 100644 --- a/src/net/sourceforge/plantuml/ditaa/PSystemDitaa.java +++ b/src/net/sourceforge/plantuml/ditaa/PSystemDitaa.java @@ -34,6 +34,7 @@ */ package net.sourceforge.plantuml.ditaa; +import java.awt.Font; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.OutputStream; @@ -61,11 +62,12 @@ public class PSystemDitaa extends AbstractPSystem { private final boolean dropShadows; private final String data; private final float scale; + private final Font font; private final boolean performSeparationOfCommonEdges; private final boolean allCornersAreRound; public PSystemDitaa(UmlSource source, String data, boolean performSeparationOfCommonEdges, boolean dropShadows, - boolean allCornersAreRound, float scale) { + boolean allCornersAreRound, float scale, Font font) { super(source); this.data = data; this.dropShadows = dropShadows; @@ -84,11 +86,12 @@ public class PSystemDitaa extends AbstractPSystem { this.processingOptions = null; } this.scale = scale; + this.font = font; } PSystemDitaa add(String line) { return new PSystemDitaa(getSource(), data + line + BackSlash.NEWLINE, performSeparationOfCommonEdges, - dropShadows, allCornersAreRound, scale); + dropShadows, allCornersAreRound, scale, font); } public DiagramDescription getDescription() { @@ -113,6 +116,10 @@ public class PSystemDitaa extends AbstractPSystem { final Field f_renderingOptions = options.getClass().getField("renderingOptions"); 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); final Method setScale = renderingOptions.getClass().getMethod("setScale", float.class); setScale.invoke(renderingOptions, scale); diff --git a/src/net/sourceforge/plantuml/ditaa/PSystemDitaaFactory.java b/src/net/sourceforge/plantuml/ditaa/PSystemDitaaFactory.java index 12fa22a23..7eb5395c7 100644 --- a/src/net/sourceforge/plantuml/ditaa/PSystemDitaaFactory.java +++ b/src/net/sourceforge/plantuml/ditaa/PSystemDitaaFactory.java @@ -34,6 +34,7 @@ */ package net.sourceforge.plantuml.ditaa; +import java.awt.Font; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -70,10 +71,11 @@ public class PSystemDitaaFactory extends PSystemBasicFactory { allCornersAreRound = true; final float scale = extractScale(startLine); + final Font font = extractFont(startLine); if (getDiagramType() == DiagramType.UML) return null; else if (getDiagramType() == DiagramType.DITAA) - return new PSystemDitaa(source, "", performSeparationOfCommonEdges, dropShadows, allCornersAreRound, scale); + return new PSystemDitaa(source, "", performSeparationOfCommonEdges, dropShadows, allCornersAreRound, scale, font); else throw new IllegalStateException(getDiagramType().name()); @@ -95,7 +97,8 @@ public class PSystemDitaaFactory extends PSystemBasicFactory { allCornersAreRound = true; 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) return null; @@ -115,4 +118,46 @@ public class PSystemDitaaFactory extends PSystemBasicFactory { } 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); + } } diff --git a/src/org/stathissideris/ascii2image/core/RenderingOptions.java b/src/org/stathissideris/ascii2image/core/RenderingOptions.java index 851514334..baecad629 100644 --- a/src/org/stathissideris/ascii2image/core/RenderingOptions.java +++ b/src/org/stathissideris/ascii2image/core/RenderingOptions.java @@ -20,6 +20,7 @@ */ package org.stathissideris.ascii2image.core; +import java.awt.*; import java.util.HashMap; import org.stathissideris.ascii2image.graphics.CustomShapeDefinition; @@ -41,6 +42,8 @@ public class RenderingOptions { private float scale = 1; + private Font font = new Font("Dialog", Font.BOLD, 12); + /** * @return */ @@ -113,4 +116,14 @@ public class RenderingOptions { antialias = b; } + public Font getFont() + { + return font; + } + + public void setFont(Font font) + { + this.font = font; + } + } diff --git a/src/org/stathissideris/ascii2image/graphics/Diagram.java b/src/org/stathissideris/ascii2image/graphics/Diagram.java index ec5cd9a40..d60cff898 100644 --- a/src/org/stathissideris/ascii2image/graphics/Diagram.java +++ b/src/org/stathissideris/ascii2image/graphics/Diagram.java @@ -111,6 +111,8 @@ public class Diagram { this.cellWidth = options.renderingOptions.getCellWidth(); this.cellHeight = options.renderingOptions.getCellHeight(); + FontMeasurer fontMeasurer = new FontMeasurer(options.renderingOptions.getFont()); + width = grid.getWidth() * cellWidth; height = grid.getHeight() * cellHeight; @@ -526,7 +528,7 @@ public class Diagram { ArrayList textGroups = nonBlank.breakIntoDistinctBoundaries(); 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(); while(textGroupIt.hasNext()){ @@ -550,10 +552,10 @@ public class Diagram { int maxX = getCellMaxX(lastCell); DiagramText textObject; - if(FontMeasurer.instance().getWidthFor(string, font) > maxX - minX){ //does not fit horizontally - Font lessWideFont = FontMeasurer.instance().getFontFor(maxX - minX, string); - textObject = new DiagramText(minX, y, string, lessWideFont); - } else textObject = new DiagramText(minX, y, string, font); + if(fontMeasurer.getWidthFor(string, font) > maxX - minX){ //does not fit horizontally + Font lessWideFont = fontMeasurer.getFontFor(maxX - minX, string); + textObject = new DiagramText(minX, y, string, lessWideFont, fontMeasurer); + } else textObject = new DiagramText(minX, y, string, font, fontMeasurer); textObject.centerVerticallyBetween(getCellMinY(cell), getCellMaxY(cell)); diff --git a/src/org/stathissideris/ascii2image/graphics/DiagramText.java b/src/org/stathissideris/ascii2image/graphics/DiagramText.java index 58c984d50..3cb9c31c1 100644 --- a/src/org/stathissideris/ascii2image/graphics/DiagramText.java +++ b/src/org/stathissideris/ascii2image/graphics/DiagramText.java @@ -39,8 +39,9 @@ public class DiagramText extends DiagramComponent { private boolean isTextOnLine = false; private boolean hasOutline = false; 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(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.text = text; this.font = font; + this.fontMeasurer = fontMeasurer; } public void centerInBounds(Rectangle2D bounds){ @@ -56,20 +58,20 @@ public class DiagramText extends DiagramComponent { } 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; xPos += Math.abs(center - width / 2); } 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; yPos -= Math.abs(center - zHeight / 2); } public void alignRightEdgeTo(int x){ - int width = FontMeasurer.instance().getWidthFor(text, font); + int width = fontMeasurer.getWidthFor(text, font); xPos = x - width; } @@ -145,7 +147,7 @@ public class DiagramText extends DiagramComponent { } public Rectangle2D getBounds(){ - Rectangle2D bounds = FontMeasurer.instance().getBoundsFor(text, font); + Rectangle2D bounds = fontMeasurer.getBoundsFor(text, font); bounds.setRect( bounds.getMinX() + xPos, bounds.getMinY() + yPos, diff --git a/src/org/stathissideris/ascii2image/graphics/FontMeasurer.java b/src/org/stathissideris/ascii2image/graphics/FontMeasurer.java index 665c0f314..89caeeb82 100644 --- a/src/org/stathissideris/ascii2image/graphics/FontMeasurer.java +++ b/src/org/stathissideris/ascii2image/graphics/FontMeasurer.java @@ -26,32 +26,25 @@ import java.awt.Graphics2D; import java.awt.font.FontRenderContext; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; -import java.util.Locale; /** * * @author Efstathios Sideris */ public class FontMeasurer { - - private static final String fontFamilyName = "Dialog"; - //private static final String fontFamilyName = "Helvetica"; - - private static final boolean DEBUG = false; - - private static final FontMeasurer instance = new FontMeasurer(); - FontRenderContext fakeRenderContext; - Graphics2D fakeGraphics; - - { + + private final Font baseFont; + private FontRenderContext fakeRenderContext; + private Graphics2D fakeGraphics; + + public FontMeasurer(Font font){ + baseFont = font; + BufferedImage image = new BufferedImage(1,1, BufferedImage.TYPE_INT_RGB); fakeGraphics = image.createGraphics(); - if (DEBUG) System.out.println("Locale: "+Locale.getDefault()); - fakeRenderContext = fakeGraphics.getFontRenderContext(); - } - + } public int getWidthFor(String str, int pixelHeight){ Font font = getFontFor(pixelHeight); @@ -74,125 +67,86 @@ public class FontMeasurer { Rectangle2D rectangle = font.getStringBounds(str, fakeRenderContext); return (int) rectangle.getHeight(); } - + public Rectangle2D getBoundsFor(String str, Font font){ 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){ fakeGraphics.setFont(font); FontMetrics metrics = fakeGraphics.getFontMetrics(); - if(DEBUG) System.out.println("Ascent: "+metrics.getAscent()); return metrics.getAscent(); } public int getZHeight(Font font){ - int height = (int) font.createGlyphVector(fakeRenderContext, "Z").getOutline().getBounds().getHeight(); - if(DEBUG) System.out.println("Z height: "+height); - return height; + return (int) font.createGlyphVector(fakeRenderContext, "Z").getOutline().getBounds().getHeight(); } - public Font getFontFor(int maxWidth, String string){ - float size = 12; - Font currentFont = new Font(fontFamilyName, Font.BOLD, (int) size); - //ascent is the distance between the baseline and the tallest character - int width = getWidthFor(string, currentFont); + public Font getFontFor(final int maxWidth, final String string){ + FontPredicate predicate = new FontPredicate() { + @Override + public boolean test(Font font) + { + 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) - if(width > maxWidth){ - currentFont = currentFont.deriveFont(size - 1); + if(predicate.test(currentFont)){ + currentFont = currentFont.deriveFont(size - 1f); size--; - direction = -1; + direction = -1; } else { - currentFont = currentFont.deriveFont(size + 1); + currentFont = currentFont.deriveFont(size + 1f); size++; direction = 1; } + while(size > 0){ currentFont = currentFont.deriveFont(size); //rectangle = currentFont.getStringBounds(testString, frc); - width = getWidthFor(string, currentFont); - if(direction == 1){ - if(width > maxWidth){ - size = size - 1; + if (direction == 1) { + if (predicate.test(currentFont)) { + size = size - sizeDelta; return currentFont.deriveFont(size); + } else { + size = size + sizeDelta; } - else size = size + 1; } else { - if(width < maxWidth) + if (!predicate.test(currentFont)) { return currentFont; - else size = size - 1; + } else { + size = size - sizeDelta; + } } } return null; } - - - 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()); + private interface FontPredicate { + boolean test(Font font); } }