
com.skynav.ttpe.layout.BasicLayoutState 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.layout;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import javax.xml.namespace.QName;
import org.w3c.dom.Element;
import com.skynav.ttpe.area.Area;
import com.skynav.ttpe.area.AreaNode;
import com.skynav.ttpe.area.BlockArea;
import com.skynav.ttpe.area.CanvasArea;
import com.skynav.ttpe.area.LineArea;
import com.skynav.ttpe.area.NonLeafAreaNode;
import com.skynav.ttpe.area.ReferenceArea;
import com.skynav.ttpe.area.ViewportArea;
import com.skynav.ttpe.fonts.Font;
import com.skynav.ttpe.fonts.FontCache;
import com.skynav.ttpe.geometry.Axis;
import com.skynav.ttpe.geometry.Dimension;
import com.skynav.ttpe.geometry.Extent;
import com.skynav.ttpe.geometry.Overflow;
import com.skynav.ttpe.geometry.Point;
import com.skynav.ttpe.geometry.TransformMatrix;
import com.skynav.ttpe.geometry.WritingMode;
import com.skynav.ttpe.style.BlockAlignment;
import com.skynav.ttpe.style.Defaults;
import com.skynav.ttpe.style.Helpers;
import com.skynav.ttpe.style.Whitespace;
import com.skynav.ttpe.text.LineBreakIterator;
import com.skynav.ttv.model.value.Length;
import com.skynav.ttv.util.StyleSet;
import com.skynav.ttv.util.StyleSpecification;
import com.skynav.ttv.verifier.util.Keywords;
import com.skynav.ttv.verifier.util.Lengths;
import com.skynav.ttv.verifier.util.MixedUnitsTreatment;
import com.skynav.ttv.verifier.util.NegativeTreatment;
import com.skynav.ttv.verifier.util.Positions;
import com.skynav.ttx.transformer.TransformerContext;
import com.skynav.xml.helpers.Documents;
import static com.skynav.ttpe.style.Constants.*;
import static com.skynav.ttpe.text.Constants.*;
public class BasicLayoutState implements LayoutState {
// initialized state
private TransformerContext context;
private FontCache fontCache;
private LineBreakIterator breakIterator;
private LineBreakIterator characterIterator;
private Defaults defaults;
private Stack areas;
private Map styles;
private int[] counters;
public BasicLayoutState(TransformerContext context) {
this.context = context;
this.counters = new int[Counter.values().length];
}
public LayoutState initialize(FontCache fontCache, LineBreakIterator breakIterator, LineBreakIterator characterIterator, Defaults defaults) {
this.fontCache = fontCache.maybeLoad();
this.breakIterator = breakIterator;
this.characterIterator = characterIterator;
this.defaults = defaults;
this.areas = new java.util.Stack();
this.styles = new java.util.HashMap();
return this;
}
public FontCache getFontCache() {
return fontCache;
}
public LineBreakIterator getBreakIterator() {
return breakIterator;
}
public LineBreakIterator getCharacterIterator() {
return characterIterator;
}
public Defaults getDefaults() {
return defaults;
}
public NonLeafAreaNode pushCanvas(Element e, double begin, double end) {
return push(new CanvasArea(e, begin, end));
}
public NonLeafAreaNode pushViewport(Element e, double width, double height, boolean clip) {
NonLeafAreaNode a = push(new ViewportArea(e, width, height, clip));
if (areas.size() > 2) {
incrementCounters(CounterEvent.ADD_REGION, a);
((ViewportArea) a).setId(generateRegionIdentifier());
}
return a;
}
public NonLeafAreaNode pushReference(Element e, double x, double y, double width, double height, WritingMode wm, TransformMatrix ctm) {
return push(new ReferenceArea(e, x, y, width, height, wm, ctm));
}
public NonLeafAreaNode pushBlock(Element e) {
ReferenceArea ra = getReferenceArea();
if (ra != null)
return push(new BlockArea(e, ra.getIPD(), ra.getBPD(), getBidiLevel()));
else
throw new IllegalStateException();
}
public NonLeafAreaNode push(NonLeafAreaNode a) {
NonLeafAreaNode p = !areas.empty() ? peek() : null;
if (p != null)
p.addChild(a);
return (NonLeafAreaNode) areas.push(a);
}
public NonLeafAreaNode addLine(LineArea l) {
NonLeafAreaNode p = !areas.empty() ? peek() : null;
if ((p == null) || !(p instanceof BlockArea))
throw new IllegalStateException();
else
p.addChild(l);
incrementCounters(CounterEvent.ADD_LINE, l);
return l;
}
public NonLeafAreaNode pop() {
return pop(true);
}
public NonLeafAreaNode pop(boolean collapse) {
NonLeafAreaNode a = areas.pop();
if (collapse)
collapse(a, Dimension.BPD);
return a;
}
public NonLeafAreaNode peek() {
return areas.peek();
}
public ReferenceArea getReferenceArea() {
if (!areas.empty()) {
for (int i = 0, n = areas.size(); i < n; ++i) {
int k = n - i - 1;
NonLeafAreaNode a = areas.get(k);
if (a instanceof ReferenceArea)
return (ReferenceArea) a;
}
}
return null;
}
public String getLanguage() {
String language = null;
if (!areas.empty())
language = areas.peek().getLanguage();
if (language == null)
language = defaults.getLanguage();
return language;
}
public Whitespace getWhitespace() {
Whitespace ws = null;
if (!areas.empty())
ws = areas.peek().getWhitespace();
if (ws == null)
ws = defaults.getWhitespace();
return ws;
}
public WritingMode getWritingMode() {
WritingMode wm = null;
if (!areas.empty())
wm = areas.peek().getWritingMode();
if (wm == null)
wm = defaults.getWritingMode();
return wm;
}
public int getBidiLevel() {
return !areas.empty() ? areas.peek().getBidiLevel() : -1;
}
public Font getFont() {
Font f = null;
if (!areas.empty())
f = areas.peek().getFont();
if (f == null)
f = fontCache.getDefaultFont(getWritingMode().getAxis(Dimension.BPD), defaults.getFontSize());
return f;
}
public double getAvailable(Dimension dimension) {
if (!areas.empty())
return areas.peek().getAvailable(dimension);
else
return 0;
}
public Extent getReferenceExtent() {
return getReferenceArea().getExtent();
}
public BlockAlignment getReferenceAlignment() {
return getDisplayAlign(getReferenceArea().getElement());
}
public Extent getExternalExtent() {
Object value = context.getExternalParameters().getParameter("externalExtent");
if (value instanceof double[]) {
double[] parsedExternalExtent = (double[]) value;
return new Extent(parsedExternalExtent[0], parsedExternalExtent[1]);
} else
return defaults.getExternalExtent();
}
public Point getExternalOrigin() {
return Point.ZERO; // [TBD] get from context
}
public Overflow getExternalOverflow() {
return Overflow.HIDDEN; // [TBD] get from context
}
public TransformMatrix getExternalTransform() {
return TransformMatrix.IDENTITY; // [TBD] get from context
}
public WritingMode getExternalWritingMode() {
return WritingMode.LRTB; // [TBD] get from context
}
public void saveStyles(Element e) {
assert Documents.isElement(e, isdComputedStyleSetElementName);
String id = Documents.getAttribute(e, xmlIdAttrName, null);
if (id != null) {
styles.put(id, parseStyle(e, id));
}
}
public Map getStyles() {
return styles;
}
public StyleSet getStyles(Element e) {
String style = Documents.getAttribute(e, isdCSSAttrName, null);
if (style != null) {
StyleSet styles = this.styles.get(style);
if (styles != null)
return styles;
}
return StyleSet.EMPTY;
}
public BlockAlignment getDisplayAlign(Element e) {
StyleSpecification s = getStyles(e).get(ttsDisplayAlignAttrName);
if (s != null) {
String v = s.getValue();
return BlockAlignment.valueOf(v.toUpperCase());
} else
return defaults.getDisplayAlign();
}
public Extent getExtent(Element e) {
StyleSpecification s = getStyles(e).get(ttsExtentAttrName);
if (s != null) {
String v = s.getValue();
if (Keywords.isAuto(v)) {
return getExternalExtent();
} else {
Integer[] minMax = new Integer[] { 2, 2 };
Object[] treatments = new Object[] { NegativeTreatment.Error, MixedUnitsTreatment.Allow };
List lengths = new java.util.ArrayList();
if (Lengths.isLengths(v, null, context, minMax, treatments, lengths)) {
assert lengths.size() == 2;
Extent externalExtent = getExternalExtent();
Extent referenceExtent = externalExtent;
Extent fontSize = Extent.EMPTY;
double w = Helpers.resolveLength(e, lengths.get(0), Axis.HORIZONTAL, externalExtent, referenceExtent, fontSize);
double h = Helpers.resolveLength(e, lengths.get(1), Axis.VERTICAL, externalExtent, referenceExtent, fontSize);
return new Extent(w, h);
}
}
}
return getExternalExtent();
}
public Point getOrigin(Element e) {
StyleSpecification s = getStyles(e).get(ttsOriginAttrName);
if (s != null) {
String v = s.getValue();
if (Keywords.isAuto(v)) {
return getExternalOrigin();
} else {
Integer[] minMax = new Integer[] { 2, 2 };
Object[] treatments = new Object[] { NegativeTreatment.Allow, MixedUnitsTreatment.Allow };
List lengths = new java.util.ArrayList();
if (Lengths.isLengths(v, null, context, minMax, treatments, lengths)) {
assert lengths.size() == 2;
Extent externalExtent = getExternalExtent();
Extent referenceExtent = externalExtent;
Extent fontSize = Extent.EMPTY;
double x = Helpers.resolveLength(e, lengths.get(0), Axis.HORIZONTAL, externalExtent, referenceExtent, fontSize);
double y = Helpers.resolveLength(e, lengths.get(1), Axis.VERTICAL, externalExtent, referenceExtent, fontSize);
return new Point(x, y);
}
}
}
return defaults.getOrigin();
}
public Point getPosition(Element e, Extent extent) {
StyleSpecification s = getStyles(e).get(ttsPositionAttrName);
if ((s == null) && (getStyles(e).get(ttsOriginAttrName) == null) && (context.getModel().getTTMLVersion() >= 2))
s = new StyleSpecification(ttsPositionAttrName, defaults.getPositionComponents());
if (s != null) {
String v = s.getValue();
String [] components = v.split("[ \t\r\n]+");
Length[] lengths = new Length[4];
if (Positions.isPosition(components, null, context, lengths))
return Helpers.resolvePosition(e, lengths, getExternalExtent(), extent);
}
return getOrigin(e);
}
public Overflow getOverflow(Element e) {
StyleSpecification s = getStyles(e).get(ttsOverflowAttrName);
if (s != null) {
String v = s.getValue();
return Overflow.valueOf(v.toUpperCase());
} else
return defaults.getOverflow();
}
public TransformMatrix getTransform(Element e) {
return defaults.getTransform();
}
public WritingMode getWritingMode(Element e) {
StyleSpecification s = getStyles(e).get(ttsWritingModeAttrName);
if (s != null) {
String v = s.getValue();
return WritingMode.valueOf(v.toUpperCase());
} else
return defaults.getWritingMode();
}
public void incrementCounters(CounterEvent event, Area a) {
if (event == CounterEvent.ADD_REGION) {
updateCharCounters(a);
updateLineCounters(a);
int nr = 1;
counters[Counter.REGIONS_IN_CANVAS.ordinal()] += nr;
} else if (event == CounterEvent.ADD_LINE) {
updateCharCounters(a);
int nc = countSpacingGlyphs(a);
counters[Counter.CHARS_IN_LINE.ordinal()] += nc;
counters[Counter.CHARS_IN_REGION.ordinal()] += nc;
counters[Counter.CHARS_IN_CANVAS.ordinal()] += nc;
int nl = 1;
counters[Counter.LINES_IN_REGION.ordinal()] += nl;
counters[Counter.LINES_IN_CANVAS.ordinal()] += nl;
} else if (event == CounterEvent.RESET) {
Arrays.fill(counters, 0);
} else {
throw new UnsupportedOperationException();
}
}
public void finalizeCounters() {
updateCharCounters(null);
updateLineCounters(null);
}
public int getCounter(Counter counter) {
return counters[counter.ordinal()];
}
private void updateLineCounters(Area a) {
int n, m;
if ((a == null) || (a instanceof ViewportArea)) {
n = counters[Counter.LINES_IN_REGION.ordinal()];
m = counters[Counter.MAX_LINES_IN_REGION.ordinal()];
if (n > m)
counters[Counter.MAX_LINES_IN_REGION.ordinal()] = n;
resetCounter(Counter.LINES_IN_REGION);
}
}
private void updateCharCounters(Area a) {
int n, m;
if ((a == null) || (a instanceof LineArea)) {
n = counters[Counter.CHARS_IN_LINE.ordinal()];
m = counters[Counter.MAX_CHARS_IN_LINE.ordinal()];
if (n > m)
counters[Counter.MAX_CHARS_IN_LINE.ordinal()] = n;
resetCounter(Counter.CHARS_IN_LINE);
}
if ((a == null) || (a instanceof ViewportArea)) {
n = counters[Counter.CHARS_IN_REGION.ordinal()];
m = counters[Counter.MAX_CHARS_IN_REGION.ordinal()];
if (n > m)
counters[Counter.MAX_CHARS_IN_REGION.ordinal()] = n;
resetCounter(Counter.CHARS_IN_REGION);
}
}
private int countSpacingGlyphs(Area a) {
if (a instanceof LineArea)
return ((LineArea) a).getSpacingGlyphsCount();
else
return 0;
}
private void resetCounter(Counter counter) {
counters[counter.ordinal()] = 0;
}
private String generateRegionIdentifier() {
StringBuffer sb = new StringBuffer();
sb.append('r');
sb.append(Integer.toString(getCounter(Counter.REGIONS_IN_CANVAS)));
return sb.toString();
}
private StyleSet parseStyle(Element e, String id) {
assert Documents.isElement(e, isdComputedStyleSetElementName);
StyleSet styles = new StyleSet(id);
for (Map.Entry a : Documents.getAttributes(e).entrySet()) {
QName qn = a.getKey();
String ns = qn.getNamespaceURI();
if (ns != null) {
if (ns.equals(ttsNamespace) || qn.equals(xmlLanguageAttrName))
styles.merge(new StyleSpecification(ns, qn.getLocalPart(), a.getValue()));
}
}
return styles;
}
private void collapse(NonLeafAreaNode a, Dimension dimension) {
if ((dimension == Dimension.BOTH) || (dimension == Dimension.BPD))
collapseBPD(a);
if ((dimension == Dimension.BOTH) || (dimension == Dimension.IPD))
collapseIPD(a);
}
private void collapseIPD(NonLeafAreaNode a) {
a.setIPD(computeConsumed(a, Dimension.IPD));
}
private void collapseBPD(NonLeafAreaNode a) {
a.setBPD(computeConsumed(a, Dimension.BPD));
}
private double computeConsumed(NonLeafAreaNode a, Dimension dimension) {
double consumed = 0;
for (AreaNode c : a.getChildren()) {
if (dimension == Dimension.IPD)
consumed += c.getIPD();
else if (dimension == Dimension.BPD)
consumed += c.getBPD();
}
return consumed;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy