docs/feature-requests/svg-font-weight-textattribute.md
Is your feature request related to a problem? Please describe.
Numeric CSS font-weight values (100-900) are currently reduced to bold/normal in the SAX SVG sprite parser and ignored in the Nano parser. PlantUML's style system also treats FontStyle as a three-value flag (plain, bold, italic), so it cannot represent intermediate weight levels. This prevents SVG sprites and PlantUML styles from rendering weights beyond bold/normal, even when the font provides those faces.
Describe the solution you'd like
Use Java2D TextAttribute.WEIGHT and TextLayout so numeric weights can be honored when the font provides matching faces, and extend PlantUML styling with an optional FontWeight attribute. This keeps FontStyle responsible for italic/oblique while weight is handled independently.
High-level changes:
UFont/UFontFactory to accept an optional weight.TextAttribute.WEIGHT constants.FontWeight across style-driven rendering paths, including SVG sprites.Describe alternatives you've considered
FontWeight.Additional context Current behavior:
font-weight is parsed and mapped to bold/normal only.font-weight is not parsed.FontStyle maps only to plain, bold, or italic.Visual comparison:
Current PlantUML style behavior for FontStyle:
bold -> Font.BOLDitalic -> Font.ITALICFont.PLAINPossible extension: FontWeight style attribute
Add a new FontWeight style property that accepts CSS values (normal, bold, bolder, lighter, or 100-900). When supplied, it maps to TextAttribute.WEIGHT and falls back to bold/normal if a weighted face is not available.
Potential interaction rules:
FontStyle=italic with FontWeight=900 should produce italic + heavy weight.FontStyle=normal with FontWeight=900 should still produce a heavy weight.FontStyle=bold with FontWeight=100 should honor the explicit weight and keep style as normal.If both FontStyle and FontWeight are provided, a clear precedence rule should be defined (e.g., FontWeight controls weight only; FontStyle controls italic/oblique only). This mirrors CSS, where font-style and font-weight are independent axes.
Examples of fonts with and without weight families (typical availability):
Example code sketch (Java2D weights):
import java.awt.Font;
import java.awt.font.TextAttribute;
import java.text.AttributedString;
import java.util.HashMap;
import java.util.Map;
// Example utility: map CSS numeric weights to Java2D weights
// TextAttribute weight constants and their Float values:
// EXTRA_LIGHT=0.5f, LIGHT=0.75f, DEMILIGHT=0.875f, REGULAR=1.0f,
// SEMIBOLD=1.25f, MEDIUM=1.5f, DEMIBOLD=1.75f, BOLD=2.0f,
// HEAVY=2.25f, EXTRABOLD=2.5f, ULTRABOLD=2.75f
private static Float toTextAttributeWeight(int cssWeight) {
if (cssWeight <= 200)
return TextAttribute.WEIGHT_EXTRA_LIGHT;
if (cssWeight <= 300)
return TextAttribute.WEIGHT_LIGHT;
if (cssWeight <= 400)
return TextAttribute.WEIGHT_REGULAR;
if (cssWeight <= 500)
return TextAttribute.WEIGHT_MEDIUM;
if (cssWeight <= 600)
return TextAttribute.WEIGHT_SEMIBOLD;
if (cssWeight <= 700)
return TextAttribute.WEIGHT_BOLD;
if (cssWeight <= 800)
return TextAttribute.WEIGHT_HEAVY;
return TextAttribute.WEIGHT_ULTRABOLD;
}
// Example: derive a weighted font
private static Font deriveWeightedFont(Font baseFont, int cssWeight) {
Map<TextAttribute, Object> attrs = new HashMap<TextAttribute, Object>();
attrs.put(TextAttribute.WEIGHT, toTextAttributeWeight(cssWeight));
return baseFont.deriveFont(attrs);
}
// Example integration point in UFontFactory
public static UFont buildWeighted(String family, int style, int size, Integer cssWeight) {
Font base = new Font(family, style, size);
if (cssWeight == null)
return new UFont(base);
Font weighted = deriveWeightedFont(base, cssWeight.intValue());
return new UFont(weighted);
}
// Example: pass weighted font into UText (or FontConfiguration)
Font weightedFont = deriveWeightedFont(new Font(family, style, size), 700);
AttributedString as = new AttributedString(text);
as.addAttribute(TextAttribute.FONT, weightedFont);
// TextLayout can be created from the AttributedString for measurement
Implementation sketch:
UFont/UFontFactory to accept an optional weight (e.g., float or TextAttribute.WEIGHT mapping). Keep existing constructors for backward compatibility.FontConfiguration and UText to carry an optional Map<TextAttribute, ?> or a dedicated weight field.TextAttribute.WEIGHT constants (e.g., 100-900 to WEIGHT_EXTRA_LIGHT .. WEIGHT_ULTRABOLD).Font.deriveFont(Map<TextAttribute, ?>) to create the weighted font.TextLayout with a FontRenderContext so width calculations (text-anchor) reflect the weighted font.FontWeight style attribute is added, parse it alongside FontStyle and feed the resolved weight into the font creation pipeline (style controls italic/oblique only; weight controls the weight axis only).PName.FontWeight style property and a Value accessor (e.g., asFontWeight()) to normalize keywords and numeric values (100-900) into a single weight value.FontWeight in Style.getUFont() so all style-driven text paths (not just SVG sprites) share the same weight mapping.Compatibility notes:
TextLayout is introduced, validate every place that calls getStringBounder()/measures text width (text-anchor, alignment, wrapping, label sizing) so the weighted font metrics are used consistently; mismatches can shift centering or truncate wrapped labels.Embedding fonts in generated SVG:
It is possible to embed fonts in SVG using @font-face with a base64 data URL, for example:
<style>
@font-face {
font-family: "MyFont";
src: url("data:font/ttf;base64,BASE64_DATA") format("truetype");
}
</style>
Notes:
Open questions:
normal, bold, bolder)?