diff --git a/batik-bridge/src/main/java/org/apache/batik/bridge/GlyphLayout.java b/batik-bridge/src/main/java/org/apache/batik/bridge/GlyphLayout.java index c4f0ed6365..41e4c896ce 100644 --- a/batik-bridge/src/main/java/org/apache/batik/bridge/GlyphLayout.java +++ b/batik-bridge/src/main/java/org/apache/batik/bridge/GlyphLayout.java @@ -132,6 +132,12 @@ public class GlyphLayout implements TextSpanLayout { private static final AttributedCharacterIterator.Attribute BASELINE_SHIFT = GVTAttributedCharacterIterator.TextAttribute.BASELINE_SHIFT; + private static final AttributedCharacterIterator.Attribute DOMINANT_BASELINE + = GVTAttributedCharacterIterator.TextAttribute.DOMINANT_BASELINE; + + private static final AttributedCharacterIterator.Attribute ALIGNMENT_BASELINE + = GVTAttributedCharacterIterator.TextAttribute.ALIGNMENT_BASELINE; + private static final AttributedCharacterIterator.Attribute WRITING_MODE = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE; @@ -153,6 +159,8 @@ public class GlyphLayout implements TextSpanLayout { runAtts.add(DY); runAtts.add(ROTATION); runAtts.add(BASELINE_SHIFT); + runAtts.add(DOMINANT_BASELINE); + runAtts.add(ALIGNMENT_BASELINE); } protected static Set szAtts = new HashSet(); @@ -1084,6 +1092,8 @@ protected void doExplicitGlyphLayout() { Float x=null, y=null, dx=null, dy=null, rotation=null; Object baseline=null; + String dominantBl=null; + String alignmentBl=null; float shift_x_pos = 0; float shift_y_pos = 0; @@ -1102,6 +1112,8 @@ protected void doExplicitGlyphLayout() { dy = (Float) aci.getAttribute(DY); rotation = (Float) aci.getAttribute(ROTATION); baseline = aci.getAttribute(BASELINE_SHIFT); + dominantBl = (String) aci.getAttribute(DOMINANT_BASELINE); + alignmentBl = (String) aci.getAttribute(ALIGNMENT_BASELINE); } GVTGlyphMetrics gm = gv.getGlyphMetrics(i); @@ -1227,6 +1239,22 @@ protected void doExplicitGlyphLayout() { } } + // Apply dominant-baseline and alignment-baseline offsets. + // dominant-baseline shifts the element so the specified + // baseline sits at the coordinate position. + // alignment-baseline shifts a child element so its specified + // baseline aligns with the parent's dominant baseline. + float dbOffset = getBaselineOffset(dominantBl, metrics); + float abOffset = getBaselineOffset(alignmentBl, metrics); + float blAdjust = dbOffset - abOffset; + if (blAdjust != 0f) { + if (vertical) { + ox += blAdjust; + } else { + oy -= blAdjust; + } + } + if (vertical) { // offset due to rotation of first character oy += verticalFirstOffset; @@ -2046,4 +2074,54 @@ public boolean isReversed(){ public void maybeReverse(boolean mirror){ gv.maybeReverse(mirror); } + + /** + * Returns the vertical offset (positive = above the alphabetic baseline) + * for the named SVG baseline value, using the given font metrics. + * Returns 0 for null, "auto", "baseline", or unrecognized values. + * + * @param baselineValue the SVG baseline identifier string + * @param metrics the line metrics for the current font + * @return the offset from the alphabetic baseline (positive = up) + */ + protected static float getBaselineOffset(String baselineValue, + GVTLineMetrics metrics) { + if (baselineValue == null) { + return 0f; + } + float ascent = metrics.getAscent(); + float descent = metrics.getDescent(); + switch (baselineValue) { + case "alphabetic": + case "auto": + case "baseline": + return 0f; + case "ideographic": + // Bottom of the ideographic em box ≈ descent below alphabetic + return -descent; + case "hanging": + // Hanging baseline — near the top of the em box + return ascent * 0.8f; + case "mathematical": + // Mathematical baseline — approximately at the math axis + return metrics.getStrikethroughOffset() + + ascent * 0.1f; + case "central": + // Midpoint between ascent and descent + return (ascent - descent) / 2.0f; + case "middle": + // SVG middle ≈ x-height / 2 ≈ strikethrough offset + return metrics.getStrikethroughOffset(); + case "text-before-edge": // SVG 1.1 name + case "text-top": // CSS name + case "before-edge": + return ascent; + case "text-after-edge": // SVG 1.1 name + case "text-bottom": // CSS name + case "after-edge": + return -descent; + default: + return 0f; + } + } } diff --git a/batik-bridge/src/main/java/org/apache/batik/bridge/SVGTextElementBridge.java b/batik-bridge/src/main/java/org/apache/batik/bridge/SVGTextElementBridge.java index e3bbe440a7..0486d9652e 100644 --- a/batik-bridge/src/main/java/org/apache/batik/bridge/SVGTextElementBridge.java +++ b/batik-bridge/src/main/java/org/apache/batik/bridge/SVGTextElementBridge.java @@ -129,6 +129,14 @@ public class SVGTextElementBridge extends AbstractGraphicsNodeBridge AttributedCharacterIterator.Attribute BASELINE_SHIFT = GVTAttributedCharacterIterator.TextAttribute.BASELINE_SHIFT; + public static final + AttributedCharacterIterator.Attribute DOMINANT_BASELINE + = GVTAttributedCharacterIterator.TextAttribute.DOMINANT_BASELINE; + + public static final + AttributedCharacterIterator.Attribute ALIGNMENT_BASELINE + = GVTAttributedCharacterIterator.TextAttribute.ALIGNMENT_BASELINE; + protected AttributedString laidoutText; // This is used to track the TextPainterInfo for each element @@ -709,8 +717,10 @@ public void handleCSSEngineEvent(CSSEngineEvent evt) { // first try to find CSS properties that change the layout for (int property : properties) { switch (property) { // fall-through is intended + case SVGCSSEngine.ALIGNMENT_BASELINE_INDEX: case SVGCSSEngine.BASELINE_SHIFT_INDEX: case SVGCSSEngine.DIRECTION_INDEX: + case SVGCSSEngine.DOMINANT_BASELINE_INDEX: case SVGCSSEngine.DISPLAY_INDEX: case SVGCSSEngine.FONT_FAMILY_INDEX: case SVGCSSEngine.FONT_SIZE_INDEX: @@ -1604,6 +1614,18 @@ protected Map getAttributeMap(BridgeContext ctx, result.put(BASELINE_SHIFT, bs); } + // Dominant baseline + String db = TextUtilities.convertDominantBaseline(element); + if (db != null) { + result.put(DOMINANT_BASELINE, db); + } + + // Alignment baseline + String ab = TextUtilities.convertAlignmentBaseline(element); + if (ab != null) { + result.put(ALIGNMENT_BASELINE, ab); + } + // Unicode-bidi mode Value val = CSSUtilities.getComputedStyle (element, SVGCSSEngine.UNICODE_BIDI_INDEX); diff --git a/batik-bridge/src/main/java/org/apache/batik/bridge/TextUtilities.java b/batik-bridge/src/main/java/org/apache/batik/bridge/TextUtilities.java index bb05ec35c6..0e96c05ede 100644 --- a/batik-bridge/src/main/java/org/apache/batik/bridge/TextUtilities.java +++ b/batik-bridge/src/main/java/org/apache/batik/bridge/TextUtilities.java @@ -286,6 +286,42 @@ public static Object convertBaselineShift(Element e) { } } + /** + * Converts the dominant-baseline CSS value to a string usable as a + * text attribute, or null if the value is "auto". + * @param e the element + */ + public static String convertDominantBaseline(Element e) { + Value v = CSSUtilities.getComputedStyle + (e, SVGCSSEngine.DOMINANT_BASELINE_INDEX); + if (v.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) { + String s = v.getStringValue(); + if (CSS_AUTO_VALUE.equals(s)) { + return null; + } + return s; + } + return null; + } + + /** + * Converts the alignment-baseline CSS value to a string usable as a + * text attribute, or null if the value is "auto" or "baseline". + * @param e the element + */ + public static String convertAlignmentBaseline(Element e) { + Value v = CSSUtilities.getComputedStyle + (e, SVGCSSEngine.ALIGNMENT_BASELINE_INDEX); + if (v.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) { + String s = v.getStringValue(); + if (CSS_AUTO_VALUE.equals(s) || CSS_BASELINE_VALUE.equals(s)) { + return null; + } + return s; + } + return null; + } + /** * Converts a kerning CSS value to a value usable as a text * attribute, or null. diff --git a/batik-gvt/src/main/java/org/apache/batik/gvt/text/GVTAttributedCharacterIterator.java b/batik-gvt/src/main/java/org/apache/batik/gvt/text/GVTAttributedCharacterIterator.java index d6792c63f8..3615faae07 100644 --- a/batik-gvt/src/main/java/org/apache/batik/gvt/text/GVTAttributedCharacterIterator.java +++ b/batik-gvt/src/main/java/org/apache/batik/gvt/text/GVTAttributedCharacterIterator.java @@ -310,6 +310,16 @@ public TextAttribute(String s) { public static final TextAttribute BASELINE_SHIFT = new TextAttribute("BASELINE_SHIFT"); + /** Dominant baseline for this character span (String value from CSS). + */ + public static final TextAttribute DOMINANT_BASELINE = + new TextAttribute("DOMINANT_BASELINE"); + + /** Alignment baseline for this character span (String value from CSS). + */ + public static final TextAttribute ALIGNMENT_BASELINE = + new TextAttribute("ALIGNMENT_BASELINE"); + /** Directional writing mode applied to this character span. */ public static final TextAttribute WRITING_MODE =