
com.skynav.ttpe.render.svg.SVGRenderProcessor Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2014-15 Skynav, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY SKYNAV, INC. AND ITS CONTRIBUTORS “AS IS” AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL SKYNAV, INC. OR ITS CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.skynav.ttpe.render.svg;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import com.skynav.ttpe.area.AnnotationArea;
import com.skynav.ttpe.area.Area;
import com.skynav.ttpe.area.AreaNode;
import com.skynav.ttpe.area.BlockArea;
import com.skynav.ttpe.area.BlockFillerArea;
import com.skynav.ttpe.area.CanvasArea;
import com.skynav.ttpe.area.GlyphArea;
import com.skynav.ttpe.area.Inline;
import com.skynav.ttpe.area.InlineFillerArea;
import com.skynav.ttpe.area.LineArea;
import com.skynav.ttpe.area.NonLeafAreaNode;
import com.skynav.ttpe.area.ReferenceArea;
import com.skynav.ttpe.area.SpaceArea;
import com.skynav.ttpe.area.ViewportArea;
import com.skynav.ttpe.fonts.Font;
import com.skynav.ttpe.fonts.FontStyle;
import com.skynav.ttpe.fonts.FontWeight;
import com.skynav.ttpe.fonts.GlyphMapping;
import com.skynav.ttpe.geometry.Axis;
import com.skynav.ttpe.geometry.Dimension;
import com.skynav.ttpe.geometry.Direction;
import com.skynav.ttpe.geometry.Extent;
import com.skynav.ttpe.geometry.Point;
import com.skynav.ttpe.geometry.Rectangle;
import com.skynav.ttpe.geometry.TransformMatrix;
import com.skynav.ttpe.geometry.WritingMode;
import com.skynav.ttpe.render.Frame;
import com.skynav.ttpe.render.RenderProcessor;
import com.skynav.ttpe.style.AnnotationPosition;
import com.skynav.ttpe.style.Color;
import com.skynav.ttpe.style.Decoration;
import com.skynav.ttpe.style.InlineAlignment;
import com.skynav.ttpe.style.Outline;
import com.skynav.ttv.app.InvalidOptionUsageException;
import com.skynav.ttv.app.MissingOptionArgumentException;
import com.skynav.ttv.app.OptionSpecification;
import com.skynav.ttv.util.Namespaces;
import com.skynav.ttv.util.Reporter;
import com.skynav.ttv.verifier.util.Colors;
import com.skynav.ttx.transformer.TransformerContext;
import com.skynav.xml.helpers.Documents;
import static com.skynav.ttpe.geometry.Direction.*;
import static com.skynav.ttpe.text.Constants.*;
public class SVGRenderProcessor extends RenderProcessor {
public static final String NAME = "svg";
// static defaults
private static final String defaultOutputFileNamePattern = "ttps{0,number,000000}.svg";
// option and usage info
private static final String[][] longOptionSpecifications = new String[][] {
{ "svg-background", "COLOR", "paint background of specified color into root region (default: transparent)" },
{ "svg-decorate-all", "", "decorate regions, lines, glyphs, etc., for debugging purposes" },
{ "svg-decorate-glyphs", "", "decorate glyphs with bounding box, etc., for debugging purposes" },
{ "svg-decorate-lines", "", "decorate lines with bounding box, etc., for debugging purposes" },
{ "svg-decorate-regions", "", "decorate regions with bounding box, etc., for debugging purposes" },
{ "svg-decoration", "COLOR", "paint decorations using specified color (default: color contrasting with specified background or black)" },
};
private static final Map longOptions;
static {
longOptions = new java.util.TreeMap();
for (String[] spec : longOptionSpecifications) {
longOptions.put(spec[0], new OptionSpecification(spec[0], spec[1], spec[2]));
}
}
// miscellaneous statics
public static final MessageFormat doubleFormatter = new MessageFormat("{0,number,#.####}");
public static final MessageFormat matrixFormatter = new MessageFormat("matrix({0})");
public static final MessageFormat translateFormatter = new MessageFormat("translate({0,number,#.####},{1,number,#.####})");
// options state
private String backgroundOption;
@SuppressWarnings("unused")
private boolean decorateGlyphs;
private boolean decorateLines;
private boolean decorateRegions;
private String decorationOption;
private String outputPattern;
// derived options state
private Color backgroundColor;
private Color decorationColor;
// render state
private double xCurrent;
private double yCurrent;
private List regions;
private int paragraphGenerationIndex;
private int lineGenerationIndex;
public SVGRenderProcessor(TransformerContext context) {
super(context);
}
@Override
public String getName() {
return NAME;
}
@Override
public String getOutputPattern() {
return outputPattern;
}
@Override
public Collection getLongOptionSpecs() {
return longOptions.values();
}
@Override
public int parseLongOption(List args, int index) {
String arg = args.get(index);
int numArgs = args.size();
String option = arg;
assert option.length() > 2;
option = option.substring(2);
if (option.equals("output-pattern")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
outputPattern = args.get(++index);
} else if (option.equals("svg-background")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
backgroundOption = args.get(++index);
} else if (option.equals("svg-decorate-all")) {
// decorateGlyphs = true;
decorateLines = true;
decorateRegions = true;
} else if (option.equals("svg-decorate-glyphs")) {
decorateGlyphs = true;
} else if (option.equals("svg-decorate-lines")) {
decorateLines = true;
} else if (option.equals("svg-decorate-regions")) {
decorateRegions = true;
} else if (option.equals("svg-decoration")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
decorationOption = args.get(++index);
} else {
return super.parseLongOption(args, index);
}
return index + 1;
}
@Override
public void processDerivedOptions() {
super.processDerivedOptions();
// backgroundColor
Color backgroundColor;
if (backgroundOption != null) {
com.skynav.ttv.model.value.Color[] retColor = new com.skynav.ttv.model.value.Color[1];
if (Colors.isColor(backgroundOption, null, context, retColor)) {
backgroundColor = new Color(retColor[0].getRed(), retColor[0].getGreen(), retColor[0].getBlue(), retColor[0].getAlpha());
} else
throw new InvalidOptionUsageException("svg-background", "invalid color: " + backgroundOption);
} else
backgroundColor = null;
this.backgroundColor = backgroundColor;
// decoration
Color decorationColor;
if (decorationOption != null) {
com.skynav.ttv.model.value.Color[] retColor = new com.skynav.ttv.model.value.Color[1];
if (Colors.isColor(decorationOption, null, context, retColor)) {
decorationColor = new Color(retColor[0].getRed(), retColor[0].getGreen(), retColor[0].getBlue(), retColor[0].getAlpha());
} else
throw new InvalidOptionUsageException("svg-decoration", "invalid color: " + decorationOption);
} else
decorationColor = (backgroundColor != null) ? backgroundColor.contrast() : Color.BLACK;
this.decorationColor = decorationColor;
// output pattern
String outputPattern = this.outputPattern;
if (outputPattern == null)
outputPattern = defaultOutputFileNamePattern;
this.outputPattern = outputPattern;
}
@Override
public List render(List areas) {
List frames = new java.util.ArrayList();
for (Area a : areas) {
if (a instanceof CanvasArea) {
Frame f = renderCanvas((CanvasArea) a);
if (f != null)
frames.add(f);
}
}
return frames;
}
@Override
public void clear(boolean all) {
xCurrent = 0;
yCurrent = 0;
regions = null;
}
protected Frame renderCanvas(CanvasArea a) {
Reporter reporter = context.getReporter();
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document d = db.newDocument();
d.appendChild(renderCanvas(null, a, d));
Namespaces.normalize(d, SVGDocumentFrame.prefixes);
return new SVGDocumentFrame(a.getBegin(), a.getEnd(), a.getExtent(), d, regions);
} catch (ParserConfigurationException e) {
reporter.logError(e);
}
return null;
}
private Element renderCanvas(Element parent, CanvasArea a, Document d) {
Element e = Documents.createElement(d, SVGDocumentFrame.svgSVGEltName);
return renderChildren(e, a, d);
}
private Element renderViewport(Element parent, ViewportArea a, Document d) {
Element e = parent;
return renderChildren(e, a, d);
}
private Element renderReference(Element parent, ReferenceArea a, Document d) {
Element eSVG;
boolean root = isRootReference(a);
if (root)
eSVG = parent;
else
eSVG = Documents.createElement(d, SVGDocumentFrame.svgSVGEltName);
Extent extent = a.getExtent();
if (extent == null)
extent = Extent.EMPTY;
if (extent != null) {
Documents.setAttribute(eSVG, SVGDocumentFrame.widthAttrName, doubleFormatter.format(new Object[] {extent.getWidth()}));
Documents.setAttribute(eSVG, SVGDocumentFrame.heightAttrName, doubleFormatter.format(new Object[] {extent.getHeight()}));
}
if (root) {
if (backgroundColor != null) {
Element eBackground = Documents.createElement(d, SVGDocumentFrame.svgRectEltName);
Documents.setAttribute(eBackground, SVGDocumentFrame.widthAttrName, doubleFormatter.format(new Object[] {extent.getWidth()}));
Documents.setAttribute(eBackground, SVGDocumentFrame.heightAttrName, doubleFormatter.format(new Object[] {extent.getHeight()}));
Documents.setAttribute(eBackground, SVGDocumentFrame.fillAttrName, backgroundColor.toRGBString());
if (backgroundColor.getAlpha() < 1)
Documents.setAttribute(eBackground, SVGDocumentFrame.opacityAttrName, doubleFormatter.format(new Object[] {backgroundColor.getAlpha()}));
eSVG.appendChild(eBackground);
}
return renderChildren(eSVG, a, d);
} else {
Element eGroup = Documents.createElement(d, SVGDocumentFrame.svgGroupEltName);
Point origin = a.getOrigin();
if (origin != null) {
Documents.setAttribute(eGroup, SVGDocumentFrame.transformAttrName, translateFormatter.format(new Object[] {origin.getX(),origin.getY()}));
if (extent != null) {
String id = a.getParent().getId();
Documents.setAttribute(eSVG, SVGDocumentFrame.idAttrName, id);
Documents.setAttribute(eSVG, SVGDocumentFrame.classAttrName, "region");
addRegion(id, origin, extent);
}
}
xCurrent = yCurrent = 0;
WritingMode wm = a.getWritingMode();
if (wm.isVertical()) {
if (wm.getDirection(Dimension.BPD) == RL)
xCurrent += extent.getWidth();
}
if (decorateRegions) {
Element eDecoration = Documents.createElement(d, SVGDocumentFrame.svgRectEltName);
Documents.setAttribute(eDecoration, SVGDocumentFrame.widthAttrName, doubleFormatter.format(new Object[] {extent.getWidth()}));
Documents.setAttribute(eDecoration, SVGDocumentFrame.heightAttrName, doubleFormatter.format(new Object[] {extent.getHeight()}));
Documents.setAttribute(eDecoration, SVGDocumentFrame.fillAttrName, "none");
Documents.setAttribute(eDecoration, SVGDocumentFrame.strokeAttrName, decorationColor.toRGBString());
eSVG.appendChild(eDecoration);
}
eGroup.appendChild(renderChildren(eSVG, a, d));
paragraphGenerationIndex = 0;
return eGroup;
}
}
private boolean isRootReference(AreaNode a) {
for (AreaNode p = a.getParent(); p != null; p = p.getParent()) {
if (p instanceof ReferenceArea)
return false;
}
return true;
}
private void addRegion(String id, Point origin, Extent extent) {
if (regions == null)
regions = new java.util.ArrayList();
regions.add(new SVGFrameRegion(id, new Rectangle(origin, extent)));
}
private Element renderBlock(Element parent, BlockArea a, Document d) {
Element e = parent;
double xSaved = xCurrent;
double ySaved = yCurrent;
// render children
Element eBlockGroup = renderChildren(e, a, d);
// update current position
WritingMode wm = a.getWritingMode();
if (a instanceof Inline) {
double ipd = a.getIPD();
if (a.isVertical())
yCurrent = ySaved + ipd;
else
xCurrent = xSaved + ipd;
}
double bpd = a.getBPD();
if (a.isVertical()) {
Direction bpdDirection = wm.getDirection(Dimension.BPD);
xCurrent = xSaved + bpd * ((bpdDirection == RL) ? -1 : 1);
} else
yCurrent = ySaved + bpd;
// update decoration indices
if (Documents.isElement(a.getElement(), ttParagraphElementName)) {
++paragraphGenerationIndex;
lineGenerationIndex = 0;
}
return eBlockGroup;
}
private Element renderFiller(Element parent, BlockFillerArea a, Document d) {
double bpd = a.getBPD();
if (a.isVertical()) {
if (a.getWritingMode().getDirection(Dimension.BPD) == RL)
bpd = -bpd;
xCurrent += bpd;
} else
yCurrent += bpd;
return null;
}
private Element renderAnnotation(Element parent, AnnotationArea a, Document d) {
Element e = Documents.createElement(d, SVGDocumentFrame.svgGroupEltName);
double xSaved = xCurrent;
double ySaved = yCurrent;
LineArea l = a.getLine();
WritingMode wm = l.getWritingMode();
boolean vertical = wm.isVertical();
Direction bpdDirection = wm.getDirection(Dimension.BPD);
Direction ipdDirection = wm.getDirection(Dimension.IPD);
AnnotationPosition position = a.getPosition();
double ipdOffset = (a.getAlignment() == InlineAlignment.CENTER) ? (a.getOverflow() / 2) : 0;
if (ipdOffset > 0) {
double lMeasure = l.getIPD();
if (vertical) {
yCurrent -= ipdOffset;
if (yCurrent < 0)
yCurrent = 0;
else if (yCurrent > lMeasure)
yCurrent = lMeasure - a.getIPD();
} else {
xCurrent -= ipdOffset;
if (xCurrent < 0)
xCurrent = 0;
else if (xCurrent > lMeasure)
xCurrent = lMeasure - a.getIPD();
}
}
if (position == AnnotationPosition.AFTER) {
double bpdOffset = l.getBPD() - a.getBPD();
if (vertical) {
if (bpdDirection == LR)
xCurrent += bpdOffset;
else
xCurrent -= bpdOffset;
} else {
yCurrent += bpdOffset;
}
}
if ((xCurrent != 0) || (yCurrent != 0))
Documents.setAttribute(e, SVGDocumentFrame.transformAttrName, translateFormatter.format(new Double[] {xCurrent, yCurrent}));
xCurrent = 0;
yCurrent = 0;
if (decorateLines)
decorateLine(e, a, d, vertical, bpdDirection, ipdDirection, true);
maybeStyleLineGroup(e, a);
e = renderChildren(e, a, d);
xCurrent = xSaved;
yCurrent = ySaved;
return e;
}
private Element renderLine(Element parent, LineArea a, Document d) {
Element e = Documents.createElement(d, SVGDocumentFrame.svgGroupEltName);
double xSaved = xCurrent;
double ySaved = yCurrent;
WritingMode wm = a.getWritingMode();
boolean vertical = wm.isVertical();
Direction bpdDirection = wm.getDirection(Dimension.BPD);
Direction ipdDirection = wm.getDirection(Dimension.IPD);
if ((xCurrent != 0) || (yCurrent != 0))
Documents.setAttribute(e, SVGDocumentFrame.transformAttrName, translateFormatter.format(new Double[] {xCurrent, yCurrent}));
xCurrent = 0;
yCurrent = 0;
if (decorateLines)
decorateLine(e, a, d, vertical, bpdDirection, ipdDirection, false);
maybeStyleLineGroup(e, a);
e = renderChildren(e, a, d);
xCurrent = xSaved;
yCurrent = ySaved;
if (vertical) {
if (bpdDirection == LR)
xCurrent += a.getBPD();
else
xCurrent -= a.getBPD();
} else {
yCurrent += a.getBPD();
}
++lineGenerationIndex;
return e;
}
private void decorateLine(Element e, LineArea a, Document d, boolean vertical, Direction bpdDirection, Direction ipdDirection, boolean annotation) {
boolean showBoundingBox = true;
boolean showLabel = !annotation;
double w, h;
if (vertical) {
h = a.getIPD();
w = a.getBPD();
} else {
h = a.getBPD();
w = a.getIPD();
}
double x, y;
if (bpdDirection == RL) {
x = xCurrent - w;
y = yCurrent;
} else {
x = xCurrent;
y = yCurrent;
}
// bounding box
if (showBoundingBox) {
Element eDecoration = Documents.createElement(d, SVGDocumentFrame.svgRectEltName);
Documents.setAttribute(eDecoration, SVGDocumentFrame.fillAttrName, "none");
Documents.setAttribute(eDecoration, SVGDocumentFrame.strokeAttrName, decorationColor.toRGBString());
Documents.setAttribute(eDecoration, SVGDocumentFrame.widthAttrName, doubleFormatter.format(new Double[] {w}));
Documents.setAttribute(eDecoration, SVGDocumentFrame.heightAttrName, doubleFormatter.format(new Double[] {h}));
if (x != 0)
Documents.setAttribute(eDecoration, SVGDocumentFrame.xAttrName, doubleFormatter.format(new Double[] {x}));
if (y != 0)
Documents.setAttribute(eDecoration, SVGDocumentFrame.yAttrName, doubleFormatter.format(new Double[] {y}));
e.appendChild(eDecoration);
}
// crop marks
// label
if (showLabel) {
Element eDecorationLabel = Documents.createElement(d, SVGDocumentFrame.svgTextEltName);
String label = "P" + (paragraphGenerationIndex + 1) + "L" + (lineGenerationIndex + 1);
Documents.setAttribute(eDecorationLabel, SVGDocumentFrame.fontFamilyAttrName, "sans-serif");
Documents.setAttribute(eDecorationLabel, SVGDocumentFrame.fontSizeAttrName, "6");
if (vertical) {
if (bpdDirection == LR) {
Documents.setAttribute(eDecorationLabel, SVGDocumentFrame.xAttrName, doubleFormatter.format(new Double[] {x + 6}));
Documents.setAttribute(eDecorationLabel, SVGDocumentFrame.yAttrName, doubleFormatter.format(new Double[] {y + 3}));
} else {
Documents.setAttribute(eDecorationLabel, SVGDocumentFrame.xAttrName, doubleFormatter.format(new Double[] {x + w - 6}));
Documents.setAttribute(eDecorationLabel, SVGDocumentFrame.yAttrName, doubleFormatter.format(new Double[] {y + 3}));
}
} else {
if (ipdDirection == LR) {
Documents.setAttribute(eDecorationLabel, SVGDocumentFrame.xAttrName, doubleFormatter.format(new Double[] {x + 2}));
Documents.setAttribute(eDecorationLabel, SVGDocumentFrame.yAttrName, doubleFormatter.format(new Double[] {y + 8}));
} else {
Documents.setAttribute(eDecorationLabel, SVGDocumentFrame.xAttrName, doubleFormatter.format(new Double[] {x + w - (double) 4*label.length()}));
Documents.setAttribute(eDecorationLabel, SVGDocumentFrame.yAttrName, doubleFormatter.format(new Double[] {y + 8}));
}
}
Documents.setAttribute(eDecorationLabel, SVGDocumentFrame.fillAttrName, decorationColor.toRGBString());
if (vertical)
Documents.setAttribute(eDecorationLabel, SVGDocumentFrame.writingModeAttrName, "tb");
eDecorationLabel.appendChild(d.createTextNode(label));
e.appendChild(eDecorationLabel);
}
}
private void maybeStyleLineGroup(Element e, LineArea a) {
if (hasGlyphChild(a)) {
Color color = a.getColor();
Documents.setAttribute(e, SVGDocumentFrame.fillAttrName, color.toRGBString());
Font font = a.getFont();
String fontFamily = font.getPreferredFamilyName();
Documents.setAttribute(e, SVGDocumentFrame.fontFamilyAttrName, fontFamily);
Extent fontSize = font.getSize();
Documents.setAttribute(e, SVGDocumentFrame.fontSizeAttrName, doubleFormatter.format(new Object[] {fontSize.getHeight()}));
FontStyle fontStyle = font.getStyle();
if (fontStyle != FontStyle.NORMAL)
Documents.setAttribute(e, SVGDocumentFrame.fontStyleAttrName, fontStyle.name().toLowerCase());
FontWeight fontWeight = font.getWeight();
if (fontWeight != FontWeight.NORMAL)
Documents.setAttribute(e, SVGDocumentFrame.fontWeightAttrName, fontWeight.name().toLowerCase());
}
}
private boolean hasGlyphChild(LineArea l) {
for (Area a : ((NonLeafAreaNode) l).getChildren()) {
if (a instanceof GlyphArea)
return true;
}
return false;
}
private Element renderGlyphs(Element parent, GlyphArea a, Document d) {
Element g = Documents.createElement(d, SVGDocumentFrame.svgGroupEltName);
LineArea l = a.getLine();
double bpdLine = l.getBPD();
double bpdLineAnnotationBefore = l.getAnnotationBPD(AnnotationPosition.BEFORE);
double bpdLineSansAnnotationBefore = bpdLine - bpdLineAnnotationBefore;
double bpdLineAnnotationAfter = l.getAnnotationBPD(AnnotationPosition.AFTER);
double bpdLineSansAnnotation = bpdLine - (bpdLineAnnotationBefore + bpdLineAnnotationAfter);
double bpdGlyphs = a.getBPD();
double ipdGlyphs = a.getIPD();
Font font = a.getFont();
boolean combined = a.isCombined();
if (a.isVertical()) {
double baselineOffset = bpdLineAnnotationBefore;
double yOffset = 0;
boolean rotate = a.isRotatedOrientation();
if (rotate && !combined) {
if (a.getWritingMode().getDirection(Dimension.BPD) == RL)
baselineOffset += (bpdLineSansAnnotation - font.getHeight())/2 + font.getAscent();
else
baselineOffset += (bpdLineSansAnnotation - bpdGlyphs)/2 + font.getLeading()/2;
} else if (combined) {
if (a.getWritingMode().getDirection(Dimension.BPD) == RL)
baselineOffset += (bpdLineSansAnnotationBefore + ipdGlyphs)/2;
else
baselineOffset += (bpdLineSansAnnotationBefore - ipdGlyphs)/2;
yOffset = font.getHeight();
} else {
baselineOffset += bpdLineSansAnnotation/2;
}
if (a.getWritingMode().getDirection(Dimension.BPD) == RL)
baselineOffset *= -1;
StringBuffer sb = new StringBuffer(translateFormatter.format(new Object[] {baselineOffset, yCurrent + yOffset}));
if (rotate && !combined)
sb.append(",rotate(90)");
Documents.setAttribute(g, SVGDocumentFrame.transformAttrName, sb.toString());
} else {
double baselineOffset = font.getHeight() + bpdLineAnnotationBefore;
Documents.setAttribute(g, SVGDocumentFrame.transformAttrName, translateFormatter.format(new Object[] {xCurrent, baselineOffset}));
}
List decorations = a.getDecorations();
Element e;
e = renderGlyphText(g, a, d, decorations);
assert e != null;
g.appendChild(e);
if (a.isVertical()) {
yCurrent += combined ? bpdGlyphs : ipdGlyphs;
} else {
xCurrent += ipdGlyphs;
}
return g;
}
private Element renderGlyphText(Element parent, GlyphArea a, Document d, List decorations) {
if (a.isVertical() && !a.isRotatedOrientation() && !a.isCombined())
return renderGlyphTextVertical(parent, a, d, decorations);
else
return renderGlyphTextHorizontal(parent, a, d, decorations);
}
private Element renderGlyphTextVertical(Element parent, GlyphArea a, Document d, List decorations) {
Font font = a.getFont();
Element gOuter = Documents.createElement(d, SVGDocumentFrame.svgGroupEltName);
Documents.setAttribute(gOuter, SVGDocumentFrame.transformAttrName, translateFormatter.format(new Object[] {-font.getWidth()/2,font.getAscent()}));
boolean rotate = a.isRotatedOrientation() && !a.isCombined();
TransformMatrix fontMatrix = font.getTransform(Axis.VERTICAL, rotate);
GlyphMapping gm = a.getGlyphMapping();
String text = gm.getGlyphsAsText();
double[] advances = font.getScaledAdvances(gm);
boolean btt = false;
double ySaved = yCurrent;
yCurrent = 0;
double shearAdvance = font.getShearAdvance(rotate, a.isCombined());
if (shearAdvance < 0)
yCurrent += -shearAdvance;
for (int i = 0, n = advances.length; i < n; ++i) {
double ga = advances[i];
double y = btt ? yCurrent - ga : yCurrent;
int j = i + 1;
String tGlyphs = text.substring(i, j);
String tGlyphsPath;
if (font.containsPUAMapping(tGlyphs))
tGlyphsPath = font.getGlyphsPath(tGlyphs, Axis.VERTICAL, Arrays.copyOfRange(advances, i, j));
else
tGlyphsPath = null;
// outline if required
Element tOutline;
Decoration decorationOutline = findDecoration(decorations, Decoration.Type.OUTLINE, i, j);
if (decorationOutline != null) {
Outline outline = decorationOutline.getOutline();
if (tGlyphsPath != null)
tOutline = Documents.createElement(d, SVGDocumentFrame.svgPathEltName);
else
tOutline = Documents.createElement(d, SVGDocumentFrame.svgTextEltName);
Documents.setAttribute(tOutline, SVGDocumentFrame.strokeAttrName, outline.getColor().toRGBString());
Documents.setAttribute(tOutline, SVGDocumentFrame.strokeWidthAttrName, doubleFormatter.format(new Object[] {outline.getThickness()}));
Documents.setAttribute(tOutline, SVGDocumentFrame.fillAttrName, "none");
if (tGlyphsPath != null) {
Documents.setAttribute(tOutline, SVGDocumentFrame.dAttrName, tGlyphsPath);
} else
tOutline.appendChild(d.createTextNode(tGlyphs));
} else
tOutline = null;
// text
Element t;
Color tColor;
Color lColor = a.getLine().getColor();
Decoration decorationColor = findDecoration(decorations, Decoration.Type.COLOR, i, j);
if (decorationColor != null)
tColor = decorationColor.getColor();
else
tColor = lColor;
if (tGlyphsPath == null) {
t = Documents.createElement(d, SVGDocumentFrame.svgTextEltName);
t.appendChild(d.createTextNode(tGlyphs));
} else {
t = Documents.createElement(d, SVGDocumentFrame.svgPathEltName);
Documents.setAttribute(t, SVGDocumentFrame.dAttrName, tGlyphsPath);
Documents.setAttribute(t, SVGDocumentFrame.strokeAttrName, tColor.toRGBString());
Documents.setAttribute(t, SVGDocumentFrame.strokeWidthAttrName, "0.5");
}
if (!tColor.equals(lColor))
Documents.setAttribute(t, SVGDocumentFrame.fillAttrName, tColor.toRGBString());
// group wrapper (gInner) if font transform required or using glyphs path
if ((fontMatrix != null) || ((tGlyphsPath != null) && (y != 0))) {
Element gInner = Documents.createElement(d, SVGDocumentFrame.svgGroupEltName);
if (y != 0)
Documents.setAttribute(gInner, SVGDocumentFrame.transformAttrName, translateFormatter.format(new Object[] {0,y}));
if (tOutline != null) {
if (fontMatrix != null)
Documents.setAttribute(tOutline, SVGDocumentFrame.transformAttrName, matrixFormatter.format(new Object[] {fontMatrix.toString()}));
gInner.appendChild(tOutline);
}
if (fontMatrix != null)
Documents.setAttribute(t, SVGDocumentFrame.transformAttrName, matrixFormatter.format(new Object[] {fontMatrix.toString()}));
gInner.appendChild(t);
gOuter.appendChild(gInner);
} else {
if (tOutline != null) {
if (y != 0)
Documents.setAttribute(tOutline, SVGDocumentFrame.yAttrName, doubleFormatter.format(new Object[] {y}));
gOuter.appendChild(tOutline);
}
if (y != 0)
Documents.setAttribute(t, SVGDocumentFrame.yAttrName, doubleFormatter.format(new Object[] {y}));
gOuter.appendChild(t);
}
yCurrent += btt ? -ga : ga;
}
yCurrent = ySaved;
return gOuter;
}
private Element renderGlyphTextHorizontal(Element parent, GlyphArea a, Document d, List decorations) {
Font font = a.getFont();
Element gOuter = Documents.createElement(d, SVGDocumentFrame.svgGroupEltName);
boolean rotate = a.isRotatedOrientation() && !a.isCombined();
TransformMatrix fontMatrix = font.getTransform(Axis.HORIZONTAL, rotate);
GlyphMapping gm = a.getGlyphMapping();
String text = gm.getGlyphsAsText();
double[] advances = font.getScaledAdvances(gm);
double[][] adjustments = font.getScaledAdjustments(gm);
boolean rtl = false;
double xSaved = xCurrent;
xCurrent = 0;
double shearAdvance = font.getShearAdvance(rotate, a.isCombined());
if ((shearAdvance < 0) && rotate)
xCurrent += -shearAdvance;
for (int i = 0, n = advances.length; i < n; ++i) {
double ga = advances[i];
double x = rtl ? xCurrent - ga : xCurrent;
double y = getCrossShearAdjustment(a);
if (adjustments != null) {
double[] aa = adjustments[i];
if (aa != null) {
x += aa[0];
y -= aa[1];
ga += aa[2];
}
}
int j = i + 1;
String tGlyphs = text.substring(i, j);
String tGlyphsPath;
if (font.containsPUAMapping(tGlyphs))
tGlyphsPath = font.getGlyphsPath(tGlyphs, Axis.HORIZONTAL, Arrays.copyOfRange(advances, i, j));
else
tGlyphsPath = null;
// outline if required
Element tOutline;
Decoration decorationOutline = findDecoration(decorations, Decoration.Type.OUTLINE, i, j);
if (decorationOutline != null) {
Outline outline = decorationOutline.getOutline();
if (tGlyphsPath != null)
tOutline = Documents.createElement(d, SVGDocumentFrame.svgPathEltName);
else
tOutline = Documents.createElement(d, SVGDocumentFrame.svgTextEltName);
Documents.setAttribute(tOutline, SVGDocumentFrame.strokeAttrName, outline.getColor().toRGBString());
Documents.setAttribute(tOutline, SVGDocumentFrame.strokeWidthAttrName, doubleFormatter.format(new Object[] {outline.getThickness()}));
Documents.setAttribute(tOutline, SVGDocumentFrame.fillAttrName, "none");
if (tGlyphsPath != null)
Documents.setAttribute(tOutline, SVGDocumentFrame.dAttrName, tGlyphsPath);
else
tOutline.appendChild(d.createTextNode(tGlyphs));
} else
tOutline = null;
// text
Element t;
Color tColor;
Color lColor = a.getLine().getColor();
Decoration decorationColor = findDecoration(decorations, Decoration.Type.COLOR, i, j);
if (decorationColor != null)
tColor = decorationColor.getColor();
else
tColor = lColor;
if (tGlyphsPath == null) {
t = Documents.createElement(d, SVGDocumentFrame.svgTextEltName);
t.appendChild(d.createTextNode(tGlyphs));
} else {
t = Documents.createElement(d, SVGDocumentFrame.svgPathEltName);
Documents.setAttribute(t, SVGDocumentFrame.dAttrName, tGlyphsPath);
Documents.setAttribute(t, SVGDocumentFrame.strokeAttrName, tColor.toRGBString());
Documents.setAttribute(t, SVGDocumentFrame.strokeWidthAttrName, "0.5");
}
if (!tColor.equals(lColor))
Documents.setAttribute(t, SVGDocumentFrame.fillAttrName, tColor.toRGBString());
// group wrapper (gInner) if font transform required or using glyphs path
if ((fontMatrix != null) || (tGlyphsPath != null) && ((x != 0) || (y != 0))) {
Element gInner = Documents.createElement(d, SVGDocumentFrame.svgGroupEltName);
if ((x != 0) || (y != 0))
Documents.setAttribute(gInner, SVGDocumentFrame.transformAttrName, translateFormatter.format(new Object[] {x,y}));
if (tOutline != null) {
if (fontMatrix != null)
Documents.setAttribute(tOutline, SVGDocumentFrame.transformAttrName, matrixFormatter.format(new Object[] {fontMatrix.toString()}));
gInner.appendChild(tOutline);
}
if (fontMatrix != null)
Documents.setAttribute(t, SVGDocumentFrame.transformAttrName, matrixFormatter.format(new Object[] {fontMatrix.toString()}));
gInner.appendChild(t);
gOuter.appendChild(gInner);
} else {
if (tOutline != null) {
if (x != 0)
Documents.setAttribute(tOutline, SVGDocumentFrame.xAttrName, doubleFormatter.format(new Object[] {x}));
if (y != 0)
Documents.setAttribute(tOutline, SVGDocumentFrame.yAttrName, doubleFormatter.format(new Object[] {y}));
gOuter.appendChild(tOutline);
}
if (x != 0)
Documents.setAttribute(t, SVGDocumentFrame.xAttrName, doubleFormatter.format(new Object[] {x}));
if (y != 0)
Documents.setAttribute(t, SVGDocumentFrame.yAttrName, doubleFormatter.format(new Object[] {y}));
gOuter.appendChild(t);
}
xCurrent += rtl ? -ga : ga;
}
xCurrent = xSaved;
return gOuter;
}
private double getCrossShearAdjustment(GlyphArea a) {
if (a.isCombined()) {
AreaNode aPrev = a.getPreviousSibling();
if ((aPrev != null) && (aPrev instanceof GlyphArea)) {
GlyphArea aPrevGlyphs = (GlyphArea) aPrev;
if (!aPrevGlyphs.isCombined())
return - aPrevGlyphs.getFont().getShearAdvance(false, true)/2;
}
}
return 0;
}
private Decoration findDecoration(List decorations, Decoration.Type type, int from, int to) {
if (decorations != null) {
for (Decoration d : decorations) {
if (d.isType(type) && d.intersects(from, to))
return d;
}
}
return null;
}
private Element renderSpace(Element parent, SpaceArea a, Document d) {
double ipd = a.getIPD();
if (a.isVertical())
yCurrent += ipd;
else
xCurrent += ipd;
return null;
}
private Element renderFiller(Element parent, InlineFillerArea a, Document d) {
double ipd = a.getIPD();
if (a.isVertical())
yCurrent += ipd;
else
xCurrent += ipd;
return null;
}
private Element renderChildren(Element parent, Area a, Document d) {
if (a instanceof NonLeafAreaNode) {
for (Area c : ((NonLeafAreaNode) a).getChildren()) {
Element e = renderArea(parent, c, d);
if (e != null) {
if (e != parent) {
parent.appendChild(e);
}
}
}
}
return parent;
}
private Element renderArea(Element parent, Area a, Document d) {
if (a instanceof GlyphArea)
return renderGlyphs(parent, (GlyphArea) a, d);
else if (a instanceof SpaceArea)
return renderSpace(parent, (SpaceArea) a, d);
else if (a instanceof InlineFillerArea)
return renderFiller(parent, (InlineFillerArea) a, d);
else if (a instanceof AnnotationArea)
return renderAnnotation(parent, (AnnotationArea) a, d);
else if (a instanceof LineArea)
return renderLine(parent, (LineArea) a, d);
else if (a instanceof ReferenceArea)
return renderReference(parent, (ReferenceArea) a, d);
else if (a instanceof ViewportArea)
return renderViewport(parent, (ViewportArea) a, d);
else if (a instanceof BlockFillerArea)
return renderFiller(parent, (BlockFillerArea) a, d);
else if (a instanceof BlockArea)
return renderBlock(parent, (BlockArea) a, d);
else
throw new IllegalArgumentException();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy