Skip to content

Commit

Permalink
Improve preview, svg/pdf export of node's label's box (#2813)
Browse files Browse the repository at this point in the history
- Remove text outline's fill (avoids doubling text fill) (svg)
- Boxes no longer have solid fill, but are stroked (svg, pdf)
- Boxes, labels use transparency (svg)
- Non-zero outline size no longer affects box stroke (preview)
- Fix first box size by setting an explicit stroke (preview)
- Box has consistent sharp corners (preview)
- Box size accounts for outline size (all)
- Box size includes descenders (y,g,p, etc) (pdf)
- Box matches label position and looks roughly centered (svg)
  dominant-baseline is unsupported by batik at the moment:
  https://xmlgraphics.apache.org/batik/status.html
  • Loading branch information
nathanal committed Sep 2, 2023
1 parent a42603f commit 6a466be
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ public void renderSVG(SVGTarget target, Edge edge, String label, float x, float
outlineElem.setAttribute("x", String.valueOf(x));
outlineElem.setAttribute("y", String.valueOf(y));
outlineElem.setAttribute("style", "text-anchor: middle; dominant-baseline: central;");
outlineElem.setAttribute("fill", target.toHexString(color));
outlineElem.setAttribute("fill", "none");
outlineElem.setAttribute("font-family", font.getFamily());
outlineElem.setAttribute("font-size", font.getSize() + "");
outlineElem.setAttribute("stroke", target.toHexString(outlineColor));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ public class NodeLabelRenderer implements Renderer {
protected final int defaultOutlineOpacity = 40;
protected final boolean defaultShowBox = false;
protected final DependantColor defaultBoxColor = new DependantColor(DependantColor.Mode.PARENT);
protected final float defaultBoxStrokeSize = 0.16f;
protected final int defaultBoxOpacity = 100;
//Font cache
protected Map<Integer, Font> fontCache;
Expand Down Expand Up @@ -202,16 +203,18 @@ public void render(Item item, RenderTarget target, PreviewProperties properties)
if (boxAlpha > 255) {
boxAlpha = 255;
}
float boxStrokeSize = showBox ? getBoxStrokeSize(properties, item) : 0f;
boxColor = new Color(boxColor.getRed(), boxColor.getGreen(), boxColor.getBlue(), boxAlpha);

if (target instanceof G2DTarget) {
renderG2D((G2DTarget) target, label, x, y, fontSize, color, outlineSize, outlineColor, showBox, boxColor);
renderG2D((G2DTarget) target, label, x, y, fontSize, color, outlineSize, outlineColor, showBox,
boxColor, boxStrokeSize);
} else if (target instanceof SVGTarget) {
renderSVG((SVGTarget) target, node, label, x, y, fontSize, color, outlineSize, outlineColor, showBox,
boxColor);
boxColor, boxStrokeSize);
} else if (target instanceof PDFTarget) {
renderPDF((PDFTarget) target, node, label, x, y, fontSize, color, outlineSize, outlineColor, showBox,
boxColor);
boxColor, boxStrokeSize);
}
}

Expand All @@ -237,8 +240,17 @@ public CanvasSize getCanvasSize(
return new CanvasSize(x - textWidth / 2f, y - textHeight / 2f, textWidth, textHeight);
}

private float getBoxStrokeSize(PreviewProperties properties, Item item) {
if (properties.getBooleanValue(PreviewProperty.NODE_BORDER_FIXED) ||
!properties.getBooleanValue(PreviewProperty.NODE_LABEL_PROPORTIONAL_SIZE)) {
return properties.getFloatValue(PreviewProperty.NODE_BORDER_WIDTH);
} else {
return (float) item.getData(NODE_SIZE) * defaultBoxStrokeSize / 2f;
}
}

public void renderG2D(G2DTarget target, String label, float x, float y, int fontSize, Color color,
float outlineSize, Color outlineColor, boolean showBox, Color boxColor) {
float outlineSize, Color outlineColor, boolean showBox, Color boxColor, float boxStrokeSize) {
Graphics2D graphics = target.getGraphics();

Font font = fontCache.get(fontSize);
Expand All @@ -252,12 +264,13 @@ public void renderG2D(G2DTarget target, String label, float x, float y, int font

//Box
if (showBox) {
graphics.setStroke(new BasicStroke(boxStrokeSize, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
graphics.setColor(boxColor);
Rectangle2D.Float rect = new Rectangle2D.Float();
rect.setFrame(posX - outlineSize / 2f,
y - (fm.getAscent() + fm.getDescent()) / 2f - outlineSize / 2f,
fm.stringWidth(label) + outlineSize,
fm.getAscent() + fm.getDescent() + outlineSize);
rect.setFrame(posX - (outlineSize + boxStrokeSize) / 2f,
y - (fm.getAscent() + fm.getDescent()) / 2f - (outlineSize + boxStrokeSize) / 2f,
fm.stringWidth(label) + outlineSize + boxStrokeSize,
fm.getAscent() + fm.getDescent() + outlineSize + boxStrokeSize);
graphics.draw(rect);
}

Expand All @@ -280,7 +293,7 @@ public void renderG2D(G2DTarget target, String label, float x, float y, int font
}

public void renderSVG(SVGTarget target, Node node, String label, float x, float y, int fontSize, Color color,
float outlineSize, Color outlineColor, boolean showBox, Color boxColor) {
float outlineSize, Color outlineColor, boolean showBox, Color boxColor, float boxStrokeSize) {
Text labelText = target.createTextNode(label);
Font font = fontCache.get(fontSize);

Expand All @@ -290,19 +303,20 @@ public void renderSVG(SVGTarget target, Node node, String label, float x, float
outlineElem.setAttribute("class", SVGUtils.idAsClassAttribute(node.getId()));
outlineElem.setAttribute("x", String.valueOf(x));
outlineElem.setAttribute("y", String.valueOf(y));
outlineElem.setAttribute("style", "text-anchor: middle; dominant-baseline: central;");
outlineElem.setAttribute("fill", target.toHexString(color));
outlineElem.setAttribute("style", "text-anchor: middle;");
outlineElem.setAttribute("font-family", font.getFamily());
outlineElem.setAttribute("font-size", String.valueOf(fontSize));
outlineElem.setAttribute("fill", "none");
outlineElem.setAttribute("stroke", target.toHexString(outlineColor));
outlineElem.setAttribute("stroke-width", (outlineSize * target.getScaleRatio()) + "px");
outlineElem.setAttribute("stroke-width", Float.toString(outlineSize * target.getScaleRatio()));
outlineElem.setAttribute("stroke-linecap", "round");
outlineElem.setAttribute("stroke-linejoin", "round");
outlineElem.setAttribute("stroke-opacity", String.valueOf(outlineColor.getAlpha() / 255f));
outlineElem.appendChild(labelTextOutline);
target.getTopElement(SVGTarget.TOP_NODE_LABELS_OUTLINE).appendChild(outlineElem);

//Trick to center text vertically on node:
//Better results with dominant-baseline: central, but the bbox is wrong.
SVGRect rect = ((SVGLocatable) outlineElem).getBBox();
outlineElem.setAttribute("y", String.valueOf(y + (rect != null ? rect.getHeight() / 4f : 0)));
}
Expand All @@ -311,54 +325,66 @@ public void renderSVG(SVGTarget target, Node node, String label, float x, float
labelElem.setAttribute("class", SVGUtils.idAsClassAttribute(node.getId()));
labelElem.setAttribute("x", String.valueOf(x));
labelElem.setAttribute("y", String.valueOf(y));
labelElem.setAttribute("style", "text-anchor: middle; dominant-baseline: central;");
labelElem.setAttribute("style", "text-anchor: middle;");
labelElem.setAttribute("fill", target.toHexString(color));
labelElem.setAttribute("fill-opacity", String.valueOf(color.getAlpha() / 255f));
labelElem.setAttribute("font-family", font.getFamily());
labelElem.setAttribute("font-size", String.valueOf(fontSize));
labelElem.appendChild(labelText);
target.getTopElement(SVGTarget.TOP_NODE_LABELS).appendChild(labelElem);

//Trick to center text vertically on node:
//Better results with dominant-baseline: central, but the bbox is wrong.
SVGRect rect = ((SVGLocatable) labelElem).getBBox();
labelElem.setAttribute("y", String.valueOf(y + (rect != null ? rect.getHeight() / 4f : 0)));

//Box
if (showBox) {
rect = ((SVGLocatable) labelElem).getBBox();
Element boxElem = target.createElement("rect");
boxElem.setAttribute("x", Float.toString(rect.getX() - outlineSize / 2f));
boxElem.setAttribute("y", Float.toString(rect.getY() - outlineSize / 2f));
boxElem.setAttribute("width", Float.toString(rect.getWidth() + outlineSize));
boxElem.setAttribute("height", Float.toString(rect.getHeight() + outlineSize));
boxElem.setAttribute("fill", target.toHexString(boxColor));
float strokeWidth = boxStrokeSize * target.getScaleRatio();
float padding = strokeWidth + outlineSize * target.getScaleRatio();
boxElem.setAttribute("x", Float.toString(rect.getX() - padding / 2f));
boxElem.setAttribute("y", Float.toString(rect.getY() - padding / 2f));
boxElem.setAttribute("width", Float.toString(rect.getWidth() + padding));
boxElem.setAttribute("height", Float.toString(rect.getHeight() + padding));
boxElem.setAttribute("fill", "none");
boxElem.setAttribute("stroke", target.toHexString(boxColor));
boxElem.setAttribute("stroke-opacity", String.valueOf(boxColor.getAlpha() / 255f));
boxElem.setAttribute("stroke-width", Float.toString(strokeWidth));
boxElem.setAttribute("opacity", String.valueOf(boxColor.getAlpha() / 255f));
target.getTopElement(SVGTarget.TOP_NODE_LABELS).insertBefore(boxElem, labelElem);
}
}

public void renderPDF(PDFTarget target, Node node, String label, float x, float y, int fontSize, Color color,
float outlineSize, Color outlineColor, boolean showBox, Color boxColor) {
float outlineSize, Color outlineColor, boolean showBox, Color boxColor, float boxStrokeSize) {
PDPageContentStream contentStream = target.getContentStream();
Font font = fontCache.get(fontSize);
PDFont pdFont = target.getPDFont(font);

try {
float textHeight = PDFUtils.getTextHeight(pdFont, fontSize);
float textMaxHeight = PDFUtils.getMaxTextHeight(pdFont, fontSize);
float textWidth = PDFUtils.getTextWidth(pdFont, fontSize, label);

if (showBox) {
contentStream.setNonStrokingColor(boxColor);
contentStream.setStrokingColor(boxColor);
contentStream.setRenderingMode(RenderingMode.STROKE);
contentStream.setLineJoinStyle(0); //miter
contentStream.setLineWidth(boxStrokeSize);
if (boxColor.getAlpha() < 255) {
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
graphicsState.setNonStrokingAlphaConstant(boxColor.getAlpha() / 255f);
graphicsState.setStrokingAlphaConstant(boxColor.getAlpha() / 255f);
contentStream.saveGraphicsState();
contentStream.setGraphicsStateParameters(graphicsState);
}

contentStream.addRect(x - textWidth / 2f - outlineSize / 2f, -y - textHeight / 2f - outlineSize / 2f,
textWidth + outlineSize, textHeight + outlineSize);
contentStream.addRect(x - (textWidth + outlineSize + boxStrokeSize) / 2f,
-y - (textMaxHeight + outlineSize + boxStrokeSize) / 2f,
textWidth + outlineSize + boxStrokeSize,
textMaxHeight + outlineSize + boxStrokeSize);

contentStream.fill();
contentStream.stroke();
if (boxColor.getAlpha() < 255) {
contentStream.restoreGraphicsState();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ public static float getTextHeight(PDFont pdFont, float fontSize) throws IOExcept
return pdFont.getFontDescriptor().getCapHeight() / 1000 * fontSize;
}

public static float getMaxTextHeight(PDFont pdFont, float fontSize) throws IOException {
return pdFont.getBoundingBox().getHeight() / 1000 * fontSize;
}

public static float getTextWidth(PDFont pdFont, float fontSize, String text) throws IOException {
return pdFont.getStringWidth(text) / 1000 * fontSize;
}
Expand Down

0 comments on commit 6a466be

Please sign in to comment.