/*
* Copyright 2014-16 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.net.URI;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
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.BlockImageArea;
import com.skynav.ttpe.area.BoundedBlockArea;
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.InlinePaddingArea;
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.FrameResource;
import com.skynav.ttpe.render.RenderProcessor;
import com.skynav.ttpe.style.AnnotationPosition;
import com.skynav.ttpe.style.BackgroundColor;
import com.skynav.ttpe.style.Color;
import com.skynav.ttpe.style.Decoration;
import com.skynav.ttpe.style.Image;
import com.skynav.ttpe.style.InlineAlignment;
import com.skynav.ttpe.style.Outline;
import com.skynav.ttpe.style.Visibility;
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";
private static final String defaultOutputFileNamePatternResource = "ttpr{0,number,000000}.dat";
// option and usage info
private static final String[][] longOptionSpecifications = new String[][] {
{ "output-pattern-resource", "PATTERN", "specify output resource file name pattern" },
{ "svg-background", "COLOR", "paint background of specified color into root region (default: transparent)" },
{ "svg-decorate-all", "", "decorate regions, lines, glyphs" },
{ "svg-decorate-glyphs", "", "decorate glyphs with bounding box" },
{ "svg-decorate-line-baselines","", "decorate line baselines" },
{ "svg-decorate-line-bounds", "", "decorate line bounding boxes" },
{ "svg-decorate-line-labels", "", "decorate line labels" },
{ "svg-decorate-lines", "", "decorate line features (bounding baselines, boxes, labels)" },
{ "svg-decorate-none", "", "disble decorations on regions, lines, glyphs" },
{ "svg-decorate-regions", "", "decorate regions with bounding box" },
{ "svg-decoration", "COLOR", "paint decorations using specified color (default: color contrasting with specified background or black)" },
{ "svg-mark-classes", "", "mark area classes" },
};
private static final Map<String,OptionSpecification> longOptions;
static {
longOptions = new java.util.TreeMap<String,OptionSpecification>();
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,#.####}", Locale.US);
public static final MessageFormat matrixFormatter = new MessageFormat("matrix({0})", Locale.US);
public static final MessageFormat translateFormatter = new MessageFormat("translate({0,number,#.####},{1,number,#.####})", Locale.US);
public static final double bgFuzz = 0.5;
// options state
private String backgroundOption;
@SuppressWarnings("unused")
private boolean decorateGlyphs;
private boolean decorateLineBaselines;
private boolean decorateLineBounds;
private boolean decorateLineLabels;
private boolean decorateNone;
private boolean decorateRegions;
private String decorationOption;
private boolean markClasses;
private String outputPatternResource;
private String outputPattern;
// derived options state
private Color backgroundColor;
private Color decorationColor;
private MessageFormat outputPatternResourceFormatter;
// render state
private double xCurrent;
private double yCurrent;
private List<SVGFrameRegion> regions;
private int paragraphGenerationIndex;
private int lineGenerationIndex;
private List<FrameResource> resources;
private int resourceGenerationIndex;
public SVGRenderProcessor(TransformerContext context) {
super(context);
}
@Override
public void resetAllState(boolean restart) {
resetRenderState(restart);
resetDerivedOptionsState(restart);
resetOptionsState(restart);
}
private void resetRenderState(boolean restart) {
xCurrent = 0;
yCurrent = 0;
regions = null;
paragraphGenerationIndex = 0;
lineGenerationIndex = 0;
resources = null;
resourceGenerationIndex = 0;
}
private void resetDerivedOptionsState(boolean restart) {
backgroundColor = null;
decorationColor = null;
outputPatternResourceFormatter = null;
}
private void resetOptionsState(boolean restart) {
backgroundOption = null;
decorateGlyphs = false;
decorateLineBaselines = false;
decorateLineBounds = false;
decorateLineLabels = false;
decorateNone = false;
decorateRegions = false;
decorationOption = null;
markClasses = false;
outputPatternResource = null;
outputPattern = null;
}
@Override
public String getName() {
return NAME;
}
@Override
public String getOutputPattern() {
return outputPattern;
}
@Override
public Collection<OptionSpecification> getLongOptionSpecs() {
return longOptions.values();
}
@Override
public int parseLongOption(List<String> 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("output-pattern-resource")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
outputPatternResource = 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;
decorateLineBaselines = true;
decorateLineBounds = true;
decorateLineLabels = true;
decorateRegions = true;
} else if (option.equals("svg-decorate-glyphs")) {
decorateGlyphs = true;
} else if (option.equals("svg-decorate-line-baselines")) {
decorateLineBaselines = true;
} else if (option.equals("svg-decorate-line-bounds")) {
decorateLineBounds = true;
} else if (option.equals("svg-decorate-line-labels")) {
decorateLineLabels = true;
} else if (option.equals("svg-decorate-lines")) {
decorateLineBaselines = true;
decorateLineBounds = true;
decorateLineLabels = true;
} else if (option.equals("svg-decorate-none")) {
decorateNone = 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 if (option.equals("svg-mark-classes")) {
markClasses = true;
} 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;
if (decorateNone) {
decorateGlyphs = false;
decorateLineBaselines = false;
decorateLineBounds = false;
decorateLineLabels = false;
decorateRegions = false;
}
// output pattern
String outputPattern = this.outputPattern;
if (outputPattern == null)
outputPattern = defaultOutputFileNamePattern;
this.outputPattern = outputPattern;
// output pattern for resources
String outputPatternResource = this.outputPatternResource;
if (outputPatternResource == null)
outputPatternResource = defaultOutputFileNamePatternResource;
this.outputPatternResource = outputPatternResource;
this.outputPatternResourceFormatter = new MessageFormat(outputPatternResource, Locale.US);
}
@Override
public List<Frame> render(List<Area> areas) {
List<Frame> frames = new java.util.ArrayList<Frame>();
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;
resources = 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, resources, 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 void maybeMarkClasses(Element eTarget, Area a, String renderClass) {
if (markClasses) {
StringBuffer sb = new StringBuffer();
sb.append(renderClass);
Element eGenerator = a.getElement();
if (eGenerator != null) {
sb.append(' ');
sb.append(eGenerator.getLocalName());
}
Documents.setAttribute(eTarget, SVGDocumentFrame.classAttrName, sb.toString());
}
}
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);
maybeMarkClasses(eSVG, a, "viewport");
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);
maybeMarkClasses(eGroup, a, "reference");
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);
}
}
// render region presentation traits
Color bColor = a.getBackgroundColor();
if ((bColor != null) && !bColor.isTransparent()) {
Element eBackgroundColor = Documents.createElement(d, SVGDocumentFrame.svgRectEltName);
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.widthAttrName, doubleFormatter.format(new Object[] {extent.getWidth()}));
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.heightAttrName, doubleFormatter.format(new Object[] {extent.getHeight()}));
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.fillAttrName, bColor.toRGBString());
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.strokeAttrName, "none");
eSVG.appendChild(eBackgroundColor);
}
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);
}
Point contentOrigin = a.getContentOrigin();
xCurrent = contentOrigin.getX();
yCurrent = contentOrigin.getY();
WritingMode wm = a.getWritingMode();
if (wm.isVertical()) {
if (wm.getDirection(Dimension.BPD) == RL)
xCurrent += a.getContentExtent().getWidth();
}
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<SVGFrameRegion>();
regions.add(new SVGFrameRegion(id, new Rectangle(origin, extent)));
}
private FrameResource addResource(Image image) {
return addResource(FrameResource.Type.IMAGE, generateResourceName(), image.getSource());
}
private String generateResourceName() {
return outputPatternResourceFormatter.format(new Object[]{Integer.valueOf(++resourceGenerationIndex)});
}
private FrameResource addResource(FrameResource.Type type, String name, URI source) {
if (resources == null)
resources = new java.util.ArrayList<FrameResource>();
FrameResource resource = new FrameResource(type, name, source);
resources.add(resource);
return resource;
}
private Element renderBlock(Element parent, BlockArea a, Document d) {
// Element e = hasBlockPresentationTraits(a) ? Documents.createElement(d, SVGDocumentFrame.svgGroupEltName) : parent;
Element e = Documents.createElement(d, SVGDocumentFrame.svgGroupEltName);
double xSaved = xCurrent;
double ySaved = yCurrent;
// transform to current position, then reset current to zero
if ((xCurrent != 0) || (yCurrent != 0))
Documents.setAttribute(e, SVGDocumentFrame.transformAttrName, translateFormatter.format(new Double[] {xCurrent, yCurrent}));
xCurrent = 0;
yCurrent = 0;
// render block presentation traits
Extent extent = (a instanceof BoundedBlockArea) ? ((BoundedBlockArea) a).getBorderExtent() : null;
if (extent == null)
extent = new Extent(a.getIPD(), a.getBPD());
Color bColor = a.getBackgroundColor();
if ((bColor != null) && !bColor.isTransparent()) {
Element eBackgroundColor = Documents.createElement(d, SVGDocumentFrame.svgRectEltName);
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.widthAttrName, doubleFormatter.format(new Object[] {extent.getWidth()}));
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.heightAttrName, doubleFormatter.format(new Object[] {extent.getHeight()}));
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.fillAttrName, bColor.toRGBString());
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.strokeAttrName, "none");
e.appendChild(eBackgroundColor);
}
Image bImage = a.getBackgroundImage();
if ((bImage != null) && !bImage.isNone()) {
FrameResource resource = addResource(bImage);
Element eBackgroundImage = Documents.createElement(d, SVGDocumentFrame.svgImageEltName);
Documents.setAttribute(eBackgroundImage, SVGDocumentFrame.widthAttrName, doubleFormatter.format(new Object[] {extent.getWidth()}));
Documents.setAttribute(eBackgroundImage, SVGDocumentFrame.heightAttrName, doubleFormatter.format(new Object[] {extent.getHeight()}));
Documents.setAttribute(eBackgroundImage, SVGDocumentFrame.preserveAspectRatioAttrName, "xMinYMin slice");
Documents.setAttribute(eBackgroundImage, SVGDocumentFrame.xlinkHrefAttrName, resource.getName());
e.appendChild(eBackgroundImage);
}
// 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;
}
if (Documents.getAttribute(eBlockGroup, SVGDocumentFrame.classAttrName, null) == null)
maybeMarkClasses(eBlockGroup, a, "block");
return eBlockGroup;
}
private Element renderImage(Element parent, BlockImageArea a, Document d) {
Element e = parent;
Image image = a.getImage();
if ((image != null) && !image.isNone()) {
FrameResource resource = addResource(image);
Element eImage = Documents.createElement(d, SVGDocumentFrame.svgImageEltName);
Documents.setAttribute(eImage, SVGDocumentFrame.widthAttrName, doubleFormatter.format(new Object[] {image.getWidth()}));
Documents.setAttribute(eImage, SVGDocumentFrame.heightAttrName, doubleFormatter.format(new Object[] {image.getHeight()}));
Documents.setAttribute(eImage, SVGDocumentFrame.preserveAspectRatioAttrName, "xMinYMin slice");
Documents.setAttribute(eImage, SVGDocumentFrame.xlinkHrefAttrName, resource.getName());
e.appendChild(eImage);
}
double bpd = a.getBPD();
if (a.isVertical()) {
if (a.getWritingMode().getDirection(Dimension.BPD) == RL)
bpd = -bpd;
xCurrent += bpd;
} else
yCurrent += bpd;
return e;
}
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);
maybeMarkClasses(e, a, "annotation");
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 (hasLineDecoration())
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);
maybeMarkClasses(e, a, "line");
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 (hasLineDecoration())
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 boolean hasLineDecoration() {
return decorateLineBaselines || decorateLineBounds || decorateLineLabels;
}
private void decorateLine(Element e, LineArea a, Document d, boolean vertical, Direction bpdDirection, Direction ipdDirection, boolean annotation) {
boolean showBoundingBox = decorateLineBounds;
boolean showLabel = decorateLineLabels && !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;
}
// baseline [TBD]
// 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 [TBD]
// 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);
maybeMarkClasses(g, a, "glyphs");
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 baselineOffset;
double ipdGlyphs = a.getIPD();
Font font = a.getFont();
boolean combined = a.isCombined();
if (a.isVertical()) {
double yOffset = 0;
boolean rotate = a.isRotatedOrientation();
baselineOffset = bpdLineAnnotationBefore;
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 {
baselineOffset = font.getHeight() + bpdLineAnnotationBefore;
Documents.setAttribute(g, SVGDocumentFrame.transformAttrName, translateFormatter.format(new Object[] {xCurrent, baselineOffset}));
}
List<Decoration> decorations = a.getDecorations();
Element e;
e = renderGlyphText(g, a, d, decorations, bpdGlyphs, baselineOffset);
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<Decoration> decorations, double bpdGlyphs, double baselineOffset) {
if (a.isVertical() && !a.isRotatedOrientation() && !a.isCombined())
return renderGlyphTextVertical(parent, a, d, decorations, bpdGlyphs, baselineOffset);
else
return renderGlyphTextHorizontal(parent, a, d, decorations, bpdGlyphs, baselineOffset);
}
private Element renderGlyphTextVertical(Element parent, GlyphArea a, Document d, List<Decoration> decorations, double bpdGlyphs, double baselineOffset) {
Font font = a.getFont();
Element gOuter = Documents.createElement(d, SVGDocumentFrame.svgGroupEltName);
maybeMarkClasses(gOuter, a, "text-v");
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;
boolean areaVisible = a.isVisible();
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;
// inline visibility
boolean glyphVisible;
Decoration decorationVisibility = findDecoration(decorations, Decoration.Type.VISIBILITY, i, j);
if (decorationVisibility != null)
glyphVisible = (decorationVisibility.getVisibility() == Visibility.VISIBLE);
else
glyphVisible = areaVisible;
// outline if required
Element tOutline;
Decoration decorationOutline = findDecoration(decorations, Decoration.Type.OUTLINE, i, j);
if ((decorationOutline != null) && glyphVisible) {
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;
if (glyphVisible) {
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());
} else
t = null;
// 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);
maybeMarkClasses(gInner, a, "text-v-inner");
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 (t != null) {
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 (t != null) {
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<Decoration> decorations, double bpdGlyphs, double baselineOffset) {
Font font = a.getFont();
Element gOuter = Documents.createElement(d, SVGDocumentFrame.svgGroupEltName);
maybeMarkClasses(gOuter, a, "text-h");
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;
boolean areaVisible = a.isVisible();
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;
// inline visibility
boolean glyphVisible;
Decoration decorationVisibility = findDecoration(decorations, Decoration.Type.VISIBILITY, i, j);
if (decorationVisibility != null)
glyphVisible = (decorationVisibility.getVisibility() == Visibility.VISIBLE);
else
glyphVisible = areaVisible;
// background color if required
if (a.isVisible()) {
Decoration decorationBackgroundColor = findDecoration(decorations, Decoration.Type.BACKGROUND_COLOR, i, j);
if ((decorationBackgroundColor != null) && glyphVisible) {
BackgroundColor backgroundColor = decorationBackgroundColor.getBackgroundColor();
Element eBackgroundColor = Documents.createElement(d, SVGDocumentFrame.svgRectEltName);
if (x != 0)
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.xAttrName, doubleFormatter.format(new Object[] {x}));
if (baselineOffset > 0)
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.yAttrName, doubleFormatter.format(new Object[] {-baselineOffset}));
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.widthAttrName, doubleFormatter.format(new Object[] {ga + bgFuzz}));
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.heightAttrName, doubleFormatter.format(new Object[] {bpdGlyphs}));
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.fillAttrName, backgroundColor.toRGBString());
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.strokeAttrName, "none");
gOuter.appendChild(eBackgroundColor);
}
}
// outline if required
Element tOutline;
Decoration decorationOutline = findDecoration(decorations, Decoration.Type.OUTLINE, i, j);
if ((decorationOutline != null) && glyphVisible) {
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;
if (glyphVisible) {
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());
} else
t = null;
// 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);
maybeMarkClasses(gInner, a, "text-h-inner");
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 (t != null) {
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 (t != null) {
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<Decoration> 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) {
Element g = Documents.createElement(d, SVGDocumentFrame.svgGroupEltName);
maybeMarkClasses(g, a, "space");
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 bpdSpace = a.getBPD();
double baselineOffset;
double ipdSpace = a.getIPD();
Font font = a.getFont();
boolean combined = /*a.isCombined()*/ false;
if (a.isVertical()) {
double yOffset = 0;
boolean rotate = /*a.isRotatedOrientation()*/ false;
baselineOffset = bpdLineAnnotationBefore;
if (rotate && !combined) {
if (a.getWritingMode().getDirection(Dimension.BPD) == RL)
baselineOffset += (bpdLineSansAnnotation - font.getHeight())/2 + font.getAscent();
else
baselineOffset += (bpdLineSansAnnotation - bpdSpace)/2 + font.getLeading()/2;
} else if (combined) {
if (a.getWritingMode().getDirection(Dimension.BPD) == RL)
baselineOffset += (bpdLineSansAnnotationBefore + ipdSpace)/2;
else
baselineOffset += (bpdLineSansAnnotationBefore - ipdSpace)/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 {
baselineOffset = font.getHeight() + bpdLineAnnotationBefore;
Documents.setAttribute(g, SVGDocumentFrame.transformAttrName, translateFormatter.format(new Object[] {xCurrent, baselineOffset}));
}
// inline visibility
boolean areaVisible = a.isVisible();
boolean spaceVisible;
List<Decoration> decorations = a.getDecorations();
Decoration decorationVisibility = findDecoration(decorations, Decoration.Type.VISIBILITY, 0, 1);
if (decorationVisibility != null)
spaceVisible = (decorationVisibility.getVisibility() == Visibility.VISIBLE);
else
spaceVisible = areaVisible;
// background color if required
Decoration decorationBackgroundColor = findDecoration(decorations, Decoration.Type.BACKGROUND_COLOR, 0, 1);
if ((decorationBackgroundColor != null) && spaceVisible) {
BackgroundColor backgroundColor = decorationBackgroundColor.getBackgroundColor();
Element eBackgroundColor = Documents.createElement(d, SVGDocumentFrame.svgRectEltName);
if (baselineOffset > 0)
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.yAttrName, doubleFormatter.format(new Object[] {-baselineOffset}));
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.widthAttrName, doubleFormatter.format(new Object[] {ipdSpace + bgFuzz}));
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.heightAttrName, doubleFormatter.format(new Object[] {bpdSpace}));
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.fillAttrName, backgroundColor.toRGBString());
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.strokeAttrName, "none");
g.appendChild(eBackgroundColor);
}
// update point
if (a.isVertical())
yCurrent += ipdSpace;
else
xCurrent += ipdSpace;
return g.hasChildNodes() ? g : null;
}
private Element renderFiller(Element parent, InlineFillerArea a, Document d) {
double ipd = a.getIPD();
Element g;
if (a instanceof InlinePaddingArea) {
double bpd = a.getBPD();
g = Documents.createElement(d, SVGDocumentFrame.svgGroupEltName);
maybeMarkClasses(g, a, "padding");
Documents.setAttribute(g, SVGDocumentFrame.transformAttrName, translateFormatter.format(new Object[] {xCurrent, 0}));
// inline visibility
boolean areaVisible = a.isVisible();
boolean paddingVisible;
List<Decoration> decorations = ((InlinePaddingArea) a).getDecorations();
Decoration decorationVisibility = findDecoration(decorations, Decoration.Type.VISIBILITY, 0, 1);
if (decorationVisibility != null)
paddingVisible = (decorationVisibility.getVisibility() == Visibility.VISIBLE);
else
paddingVisible = areaVisible;
// background color if required
Decoration decorationBackgroundColor = findDecoration(decorations, Decoration.Type.BACKGROUND_COLOR, 0, 1);
if ((decorationBackgroundColor != null) && paddingVisible) {
BackgroundColor backgroundColor = decorationBackgroundColor.getBackgroundColor();
Element eBackgroundColor = Documents.createElement(d, SVGDocumentFrame.svgRectEltName);
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.widthAttrName, doubleFormatter.format(new Object[] {ipd + bgFuzz}));
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.heightAttrName, doubleFormatter.format(new Object[] {bpd}));
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.fillAttrName, backgroundColor.toRGBString());
Documents.setAttribute(eBackgroundColor, SVGDocumentFrame.strokeAttrName, "none");
g.appendChild(eBackgroundColor);
}
} else
g = null;
// udpate point
if (a.isVertical())
yCurrent += ipd;
else
xCurrent += ipd;
return ((g != null) && g.hasChildNodes()) ? g : 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 BlockImageArea)
return renderImage(parent, (BlockImageArea) a, d);
else if (a instanceof BlockArea)
return renderBlock(parent, (BlockArea) a, d);
else
throw new IllegalArgumentException();
}
}