Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.adobe.xfa.text.markup.MarkupXHTMLIn Maven / Gradle / Ivy
package com.adobe.xfa.text.markup;
import com.adobe.xfa.font.FontInfo;
import com.adobe.xfa.gfx.GFXColour;
import com.adobe.xfa.gfx.GFXDecorationInfo;
import com.adobe.xfa.gfx.GFXTextAttr;
import com.adobe.xfa.text.TextAttr;
import com.adobe.xfa.text.TextBaselineShift;
import com.adobe.xfa.text.TextField;
import com.adobe.xfa.text.TextMeasurement;
import com.adobe.xfa.text.TextPosn;
import com.adobe.xfa.text.TextResolver;
import com.adobe.xfa.text.TextStream;
import com.adobe.xfa.text.TextTab;
import com.adobe.xfa.text.TextTabList;
import com.adobe.xfa.ut.Storage;
import com.adobe.xfa.ut.StringUtils;
import com.adobe.xfa.ut.UnitSpan;
import com.adobe.xfa.ut.UniCharIterator;
import org.xml.sax.Attributes;
/**
* This class is the XHTML input markup engine. To use it, one creates
* an instance, passing the string to be translated in a constructor
* parameter. Then the engine is passed to a Markup() method on either
* a text stream or range.
*
* For more information, please see the extenral documentation.
*
* @exclude from published api.
*/
public class MarkupXHTMLIn extends MarkupEngineIn {
/**
* @exclude from published api.
*/
private static class Frame {
int meTag; // current tag, MARKUP_UNKNOWN if unrecognized
int meSpace; // current space state (see above)
Frame () {
meTag = MarkupAttr.MARKUP_UNKNOWN;
meSpace = SPACE_NORMAL;
}
Frame (int eTag, int eSpace) {
meTag = eTag;
meSpace = eSpace;
}
}
/**
* @exclude from published api.
*/
private static class Stack extends Storage {
static final long serialVersionUID = 1010338834063169704L;
Stack () {
}
final Frame frameAt (int index) {
return get (index);
}
final Frame top () {
return frameAt (size() - 1);
}
final void push (Frame frame) {
add (frame);
}
final void pop () {
removeLast();
}
}
/**
* @exclude from published api.
*/
private static class LeaderInfo {
final MarkupXHTMLIn mpoIn;
final TextStream moContent;
final TextPosn moPosn;
public LeaderInfo (MarkupXHTMLIn poIn) {
mpoIn = poIn;
moContent = new TextStream();
moPosn = new TextPosn (moContent);
}
}
/**
* @exclude from published api.
*/
private static class StyleInfo {
final String attrValue;
int offset;
int commandEnum;
final StringBuilder command = new StringBuilder();
final StringBuilder parameter = new StringBuilder();
StyleInfo (String attrValue) {
this.attrValue = attrValue;
}
}
/**
* @exclude from published api.
*/
private static class XHTMLParser extends XMLParserBase {
private static class StackData {
final MarkupXHTMLIn mpoProcessor;
final int mnPopDepth;
StackData (MarkupXHTMLIn processor, int popDepth) {
mpoProcessor = processor;
mnPopDepth = popDepth;
}
}
private MarkupXHTMLIn mpoCurrent;
private final Storage moStack = new Storage();
private int mnDepth;
private int mnPopDepth;
XHTMLParser (MarkupXHTMLIn poXHTMLIn) {
mpoCurrent = poXHTMLIn;
push (poXHTMLIn);
}
void cleanup () {
while (moStack.size() > 0) {
pop();
}
}
public void onStartTag (String name, Attributes attributes) {
mnDepth++;
MarkupXHTMLIn poProcessor = mpoCurrent.onStartTag (name, attributes);
if (poProcessor != null) {
push (poProcessor);
}
}
public void onEndTag (String name) {
if (mnDepth == mnPopDepth) {
pop();
}
mpoCurrent.onEndTag (name);
assert (mnDepth > 0);
mnDepth--;
}
public void onContent (String content) {
mpoCurrent.onHandleText (content);
}
private void push (MarkupXHTMLIn poProcessor) {
StackData oStackData = new StackData (poProcessor, mnDepth);
moStack.add (oStackData);
mpoCurrent = poProcessor;
mnPopDepth = mnDepth;
}
private void pop () {
assert (moStack.size() > 0);
if (mnPopDepth > 0) {
mpoCurrent.commit();
}
moStack.removeLast();
if (moStack.size() > 0) {
StackData oStackData = moStack.last();
mpoCurrent = oStackData.mpoProcessor;
mnPopDepth = oStackData.mnPopDepth;
}
}
}
static final int SPACE_SUPPRESS_BREAK = 0; // suppress whitespace and breaks around block elements
static final int SPACE_SUPPRESS = 1; // suppress whitespace around breaks
static final int SPACE_NORMAL = 2; // normal collapsing
static final int SPACE_XML = 3; // future: for xml:space attribute
static final int SPACE_RUN = 4; // corresponds to xfa-spacerun:yes
static final int SPACE_FORCED = 5; // preserve all space, FF99 legacy
private static final int PENDING_DEFAULT = 0; // use default, depending on legacy blank line mode
private static final int PENDING_NONE = 1; // no space/break pending
private static final int PENDING_DIV = 2; // div start/end line break pending
private static final int PENDING_SPACE = 3; // space pending
private static final int PENDING_BREAK = 4; // break pending (supersedes pending space)
//private static final char NAMESPACE_SEPARATOR = '`';
private final static String gsXHTMLNS = "http://www.w3.org/1999/xhtml`";
private final static String gsDefaultTypefaceDefault = "Arial";
private final static String gsNewLine = "\n";
private final static String gsAPIVersion = "xfa:APIVersion";
private final static String gsRGBStart = "rgb(";
private final static String gsRGBEnd = ")";
private final static String gsNumeric = "-0123456789.";
private final static int gnVersion_1_0_0_0[] = {1, 0, 0, 0};
private final static int gnVersion_2_5_6129_0[] = {2, 5, 6129, 0};
private XHTMLParser mpoParser;
private final Stack moStack = new Stack();
private boolean mbVersionDetermined; // TRUE once version heuristically determined
private boolean mbTextAccumulated; // TRUE once text has been accumulated after block element
private boolean mbParaStarted; // TRUE if started paragraph with no text yet
private boolean mbAmbientSupplied;
private int mePending;
private final TextAttr moPendingAttr = new TextAttr();
private final TextAttr moPrevParaAttr = new TextAttr();
private final TextResolver mpoResolver;
private UniCharIterator mIterator;
private LeaderInfo mpoLeaderInfo;
private int mnTabCount; // if >0, this span starts with mnTabCount tabs
/**
* Constructor.
* @param oMarkupSource - XHTML markup to be processed.
* @param pMarkupAttr - (optional) Markup attribute table to use for the
* translation. It is strongly recommended that you pass a NULL pointer
* (default).
* @param poAmbientAttr - (optional) Overall text attribute settings,
* for those not specified in the markup. NULL (default) establishes no
* overall attributes.
* @param poResolver - Callback interface for embedded field references.
*/
public MarkupXHTMLIn (String oMarkupSource, MarkupAttr pMarkupAttr, TextAttr poAmbientAttr, TextResolver poResolver) {
super (oMarkupSource, (pMarkupAttr == null) ? MarkupXHTMLAttr.getDefault() : pMarkupAttr);
mpoResolver = poResolver;
initialize (poAmbientAttr);
}
void commit () {
if (mpoLeaderInfo != null) {
mpoLeaderInfo.mpoIn.commitTabs (mpoLeaderInfo);
}
}
/**
* Translate the XHTML markup.
*
* This is not normally called by the creator of the XHTML markup
* engine. Instead, the engine is passed to a text stream or range in a
* Markup() method that makes the call.
*/
public void translate () {
mbVersionDetermined = false;
// Establish our default attribute settings.
moCurrentAttr.setDefault (true);
// Special handling for Type and size. We don't want
// specified from the start, since if they're not, the environment should
// take over and provide the default. e.g. The default font for a field.
// The old FF99 edit control will emit xhtml with no font specified if the
// font is the same as the field.
moCurrentAttr.typefaceEnable (false);
moCurrentAttr.sizeEnable (false);
// However, if they supplied an ambient attribute in our constructor, we
// can deal with our font a little more intelligently
if (mbAmbientSupplied) {
moCurrentAttr.override (moAmbientAttr);
}
// Special handling for background colour. This is not set in xhtml, so
// we don't want this attr's default (white) clobbering the control values.
moCurrentAttr.colourBgEnable (false);
moCurrentAttr.transparentEnable (false);
// Make the current attribute stick before disabling all the paragraph
// attributes. We want to set the ambient paragraph attributes, but we
// don't want them reset when we pop the stack back.
// Set all the paragraph attributes to the default values that they share
// with xhtml.
if (! moCurrentAttr.specialEnable()) {
moCurrentAttr.special (TextMeasurement.zero());
}
if (! moCurrentAttr.justifyVEnable()) {
moCurrentAttr.justifyV (TextAttr.JUST_V_TOP);
}
if (! moCurrentAttr.justifyHEnable()) {
moCurrentAttr.justifyH (TextAttr.JUST_H_LEFT);
}
if (! moCurrentAttr.tabsEnable()) {
moCurrentAttr.tabs (new TextTabList());
}
if (! moCurrentAttr.spacingEnable()) {
moCurrentAttr.spacing (TextMeasurement.zero());
}
attr (moCurrentAttr);
moAttrList.add (moCurrentAttr);
moStack.add (new Frame (MarkupAttr.MARKUP_HTML, SPACE_NORMAL));
if (mpoParser == null) {
XHTMLParser oParser = new XHTMLParser (this);
mpoParser = oParser;
oParser.processText (sourceText());
mpoParser = null;
commit();
}
}
/**
* Set the default tab increment.
*
* NOTE: As a static method, there may be multithreading issues.
* @param sAttrValue - a unit span value for a left tab.
* @param oAttr - the text attribute to be modified
* @param pMarkupAttr - the table of markup strings to be used for unit
* conversion.
*/
public static void tabDefault (String sAttrValue, TextAttr oAttr, MarkupAttr pMarkupAttr) {
TextTab oTab = extractTab (pMarkupAttr, sAttrValue, TextTab.TYPE_ALIGN_AFTER);
TextTabList oTabList = getTabList (oAttr);
if (oTab.value() <= 0) {
oTabList.noUniform (true);
} else {
oTabList.uniform (oTab);
}
oAttr.tabs (oTabList);
}
/**
* Set the tab stops.
*
* NOTE: As a static method, there may be multithreading issues. Tab
* types can be decimal, left, right and center. Example:
* "decimal 2cm left 5cm" specifies two tabs.
* @param sAttrValue - a collection of tab types and unitspans.
* @param oAttr - the text attribute to be modified
* @param pMarkupAttr - the table of markup strings to be used for unit
* conversion and looking up tab types.
*/
public static void tabSet (String sAttrValue, TextAttr oAttr, MarkupAttr pMarkupAttr) {
TextTabList oTabList = getTabList (oAttr);
int nOffset = 0;
int nFoundAt = sAttrValue.indexOf (' ');
while (nFoundAt >= nOffset) {
String sAlign = sAttrValue.substring (nOffset, nFoundAt);
nOffset = nFoundAt + 1;
nFoundAt = sAttrValue.indexOf (' ', nOffset);
if (nFoundAt < 0) {
nFoundAt = sAttrValue.length();
}
String sValue = sAttrValue.substring (nOffset, nFoundAt);
UnitSpan oValue = new UnitSpan (sValue);
TextTab oTab = new TextTab (oValue, stringToAlign (pMarkupAttr, sAlign));
oTabList.set (oTab);
nOffset = nFoundAt + 1;
nFoundAt = sAttrValue.indexOf (' ', nOffset);
}
oAttr.tabs (oTabList);
}
// Not intended for general public consumption.
public String defaultTypeface () {
return gsDefaultTypefaceDefault;
}
public boolean skipThisCommand (int eTag) {
return false;
}
MarkupXHTMLIn onStartTag (String pcName, Attributes attributes) {
String sTagName = pcName;
int nPos = sTagName.indexOf (gsXHTMLNS);
if (nPos > 0) {
StringBuilder noNS = new StringBuilder();
noNS.append(pcName, 0, nPos);
noNS.append(pcName, nPos + gsXHTMLNS.length(), pcName.length());
sTagName = noNS.toString();
}
mnTabCount = 0;
String sAttr = null;
if (inUnknownElement()) {
moStack.push (new Frame (MarkupAttr.MARKUP_UNKNOWN, moStack.top().meSpace));
}
else {
int eTag = markupAttr().lookup (sTagName);
moStack.push (new Frame (eTag, moStack.top().meSpace));
updateSpaceStatus (eTag);
switch (eTag) {
case MarkupAttr.MARKUP_BREAK:
onCommand (MarkupAttr.MARKUP_BREAK, "");
break;
case MarkupAttr.MARKUP_BOLD:
onCommand (MarkupAttr.MARKUP_BOLD, "");
break;
case MarkupAttr.MARKUP_ITALIC:
onCommand (MarkupAttr.MARKUP_ITALIC, "");
break;
case MarkupAttr.MARKUP_BODY:
if (! mbVersionDetermined) {
String sVersion = getAttr (attributes, gsAPIVersion);
if (sVersion != null) {
mbVersionDetermined = true;
int[] nVersion = new int [4];
if (extractHTMLVersion (sVersion, nVersion)) {
if (compareHTMLVersions (nVersion, gnVersion_1_0_0_0) == 0) {
switchToFF99Mode(); // version 1.0.0.0
} else if (compareHTMLVersions (nVersion, gnVersion_2_5_6129_0) >= 0) {
setLegacyBlankLineMode (false);
for (int i = 0; i < moStack.size(); i++) {
if (moStack.frameAt(i).meSpace == SPACE_SUPPRESS_BREAK) {
moStack.frameAt(i).meSpace = SPACE_SUPPRESS;
}
}
}
}
}
}
pushAttr();
handleStylingAttributes (attributes, true);
onCommand (MarkupAttr.MARKUP_BODY, "");
break;
case MarkupAttr.MARKUP_HTML:
pushAttr();
handleStylingAttributes (attributes, true);
onCommand (MarkupAttr.MARKUP_HTML, "");
break;
case MarkupAttr.MARKUP_PARAGRAPH_START:
case MarkupAttr.MARKUP_DIV:
if ((legacyBlankLineMode() && (eTag == MarkupAttr.MARKUP_PARAGRAPH_START)) || (! mbParaStarted)) {
if (legacyPositioning()) {
para();
} else {
pushAttr();
attr (moPrevParaAttr);
para();
popAttr();
}
}
pushAttr();
if (! handleStylingAttributes (attributes, eTag == MarkupAttr.MARKUP_PARAGRAPH_START)) {
// this needs to be done, otherwise the underlying
// paragraph style settings are lost
TextAttr oParaAttrs = new TextAttr (textAttr());
oParaAttrs.isolatePara (true, posn().legacyPositioning());
attr (oParaAttrs);
}
onCommand (eTag, "");
break;
case MarkupAttr.MARKUP_SUPER:
onCommand (MarkupAttr.MARKUP_SUPER, "");
break;
case MarkupAttr.MARKUP_SUB:
onCommand (MarkupAttr.MARKUP_SUB, "");
break;
// For now, treat Anchor tags the same as spans so that we don't
// discard their contents. In some future release we'll do a better job
// supporting proper hyperlinks
case MarkupAttr.MARKUP_SPAN:
case MarkupAttr.MARKUP_ANCHOR:
flushPendingText (PENDING_SPACE);
openScopedBlock();
pushAttr();
if (handleStylingAttributes (attributes)) {
if (mnTabCount != 0) {
if (moCurrentAttr.leaderPatternEnable() && (moCurrentAttr.leaderPattern() == TextAttr.LEADER_PATTERN_USE_CONTENT)) {
LeaderInfo poLeaderInfo = new LeaderInfo (this);
MarkupXHTMLIn poReturn = new MarkupXHTMLIn (poLeaderInfo);
return poReturn;
} else {
commitTabs (null); // pops attr stack and scoped block
}
}
}
else {
String sExpression = getAttr (attributes, MarkupAttr.MARKUP_EMBED);
if (sExpression != null) {
flushPendingText (PENDING_SPACE);
// set up defaults
int eEmbedType = TextField.EMBEDTYPE_SOM;
int eEmbedMode = TextField.EMBED_FORMATTED;
sAttr = getAttr (attributes, MarkupAttr.MARKUP_EMBEDTYPE);
if (sAttr != null) {
if (sAttr.equals ("uri")) {
eEmbedType = TextField.EMBEDTYPE_URI;
} else if (sAttr.equals ("som")) {
eEmbedType = TextField.EMBEDTYPE_SOM;
}
}
sAttr = getAttr (attributes, MarkupAttr.MARKUP_EMBEDMODE);
if (sAttr != null) {
if (sAttr.equals ("raw")) {
eEmbedMode = TextField.EMBED_RAW;
} else if (sAttr.equals ("formatted")) {
eEmbedMode = TextField.EMBED_FORMATTED;
}
}
embed (sExpression, eEmbedType, eEmbedMode);
if (! legacyBlankLineMode()) {
mbParaStarted = false; // paragraph has no content
}
}
}
onCommand (MarkupAttr.MARKUP_SPAN, "");
break;
case MarkupAttr.MARKUP_UNDERLINE_TAG:
onCommand (MarkupAttr.MARKUP_UNDERLINE_TAG, "");
break;
}
}
return null;
}
void onEndTag (String pcTag) {
int eTag = moStack.top().meTag;
moStack.removeLast();
if (moStack.top().meSpace < SPACE_NORMAL) {
moStack.top().meSpace = SPACE_NORMAL;
}
switch (eTag) {
case MarkupAttr.MARKUP_PARAGRAPH_START:
onCommand (MarkupAttr.MARKUP_PARAGRAPH_END, "");
break;
case MarkupAttr.MARKUP_DIV:
onCommand (MarkupAttr.MARKUP_DIV_END, "");
break;
case MarkupAttr.MARKUP_BOLD:
onCommand (MarkupAttr.MARKUP_BOLD_END, "");
break;
case MarkupAttr.MARKUP_UNDERLINE_TAG:
onCommand (MarkupAttr.MARKUP_UNDERLINE_END, "");
break;
case MarkupAttr.MARKUP_ITALIC:
onCommand (MarkupAttr.MARKUP_ITALIC_END, "");
break;
case MarkupAttr.MARKUP_SUB:
onCommand (MarkupAttr.MARKUP_SUB_END, "");
break;
case MarkupAttr.MARKUP_SUPER:
onCommand (MarkupAttr.MARKUP_SUPER_END, "");
break;
case MarkupAttr.MARKUP_BODY:
onCommand (MarkupAttr.MARKUP_BODY_END, "");
break;
case MarkupAttr.MARKUP_HTML:
onCommand (MarkupAttr.MARKUP_HTML_END, "");
break;
case MarkupAttr.MARKUP_SPAN:
case MarkupAttr.MARKUP_ANCHOR:
onCommand (MarkupAttr.MARKUP_SPAN_END, "");
break;
}
updateSpaceStatus (eTag, true);
}
void onHandleText (String sText) {
if (inUnknownElement()) { // ignore the content of any unknown element
return;
}
StringBuilder sAccumulate = new StringBuilder();
int eSpace = moStack.top().meSpace;
// Process the text character-by-character. The handling of the
// character varies with each space handling state. Note that the space
// handling state may change from one character to the next.
if (mIterator == null) {
mIterator = new UniCharIterator();
}
mIterator.attach (sText);
for (int c = mIterator.next(); c != '\0'; c = mIterator.next()) {
switch (eSpace) {
// Suppressing whitespace (e.g., after a or
): As long as we're
// processing whitespace, just ignore it. Once we hit a "real"
// character, accumulate it and switch to normal mode to get normal
// whitespace collapsing for subsequent whitespace.
case SPACE_SUPPRESS:
if (legacyBlankLineMode()) {
flushPendingText (PENDING_BREAK); // any pending break
}
if (! isXHTMLSpace (c)) {
if (! legacyBlankLineMode()) {
flushPendingText (PENDING_BREAK);
}
UniCharIterator.append (sAccumulate, c);
eSpace = SPACE_NORMAL;
moStack.top().meSpace = SPACE_NORMAL;
}
break;
case SPACE_SUPPRESS_BREAK:
if (legacyBlankLineMode()) {
flushPendingText (PENDING_BREAK); // any pending break
if (! isXHTMLSpace (c)) {
UniCharIterator.append (sAccumulate, c);
eSpace = SPACE_NORMAL;
moStack.top().meSpace = SPACE_NORMAL;
}
}
break;
// Normal space collapsing: Don't accumulate spaces as we hit them.
// Instead, mark a pending space, and accumulate the space only when we
// hit the next "real" character.
case SPACE_NORMAL:
if (isXHTMLSpace (c)) {
if (mePending == PENDING_NONE) {
mePending = PENDING_SPACE;
}
} else {
switch (mePending) {
case PENDING_SPACE:
sAccumulate.append (' ');
break;
case PENDING_DIV:
if (legacyBlankLineMode()) {
if (mbTextAccumulated) {
sAccumulate.append ('\n');
}
}
break;
case PENDING_BREAK:
sAccumulate.append ('\n');
break;
}
mePending = PENDING_NONE;
UniCharIterator.append (sAccumulate, c);
}
if (mePending == PENDING_SPACE) {
moPendingAttr.copyFrom (textAttr());
}
break;
// XFA Space runs: Accumulate all text as is, including spaces, except for non-breaking
// spaces which are translated to regular spaces. Do not accumulate other whitespace.
case SPACE_RUN:
if (legacyBlankLineMode()) {
flushPendingText (PENDING_NONE); // any pending break or div
}
if (c == 160) {
c = ' ';
}
if ((! isXHTMLSpace (c)) || (c == ' ')) {
if (! legacyBlankLineMode()) {
flushPendingText (PENDING_NONE); // any pending break or div
}
UniCharIterator.append (sAccumulate, c);
}
break;
// XML:Space (future): Accumulate all text as is, including spaces.
case SPACE_XML:
flushPendingText (PENDING_NONE); // any pending break or div
UniCharIterator.append (sAccumulate, c);
break;
// Forced spaces (old FF99 and test suite compatibility): Treat new-lines
// as pending breaks, only to be accumulated when followed by "real"
// text. Real text includes all other whitespace characters. The point
// of this is to suppress the new-line following the last line of text
case SPACE_FORCED:
if ((legacyBlankLineMode() && (mePending == PENDING_DIV)) || (mePending == PENDING_BREAK)) {
if (mbTextAccumulated || (mePending == PENDING_BREAK)) {
sAccumulate.append ('\n');
}
mePending = PENDING_NONE;
}
if (c == '\n') {
mePending = PENDING_BREAK;
} else {
UniCharIterator.append (sAccumulate, c);
}
break;
}
}
if (sAccumulate.length() > 0) {
text (sAccumulate.toString());
}
}
private MarkupXHTMLIn (LeaderInfo poLeaderInfo) {
super ("", poLeaderInfo.mpoIn.markupAttr());
mpoParser = poLeaderInfo.mpoIn.mpoParser;
mpoResolver = poLeaderInfo.mpoIn.mpoResolver;
mpoLeaderInfo = poLeaderInfo;
// TBD: may need special initialization of space handling and may need to
// be flushed on pop.
initialize ((poLeaderInfo.mpoIn.moCurrentAttr));
setup (poLeaderInfo.moPosn, null);
moStack.add (new Frame (MarkupAttr.MARKUP_SPAN, SPACE_NORMAL));
updateSpaceStatus (MarkupAttr.MARKUP_SPAN);
}
//if the particular markup attribute tag is found, puts the value in sAttr and returns TRUE
//if not found, returns FALSE and an empty string parameter
public String getAttr (Attributes attrlist, int eTag) {
return getAttr (attrlist, markupAttr().lookup (eTag));
}
public String getAttr (Attributes attrlist, String sName) {
if (attrlist == null) {
return null;
}
String result = attrlist.getValue (sName);
if ((result != null) && (result.length() == 0)) {
result = null;
}
return result;
}
// inherited from class TextMkBase
public void text (String sText) {
super.text (sText);
mbTextAccumulated = true;
mbParaStarted = false;
moPrevParaAttr.setDefault (false); // in case text between paragraphs
}
// Inherited from TextMarkupEngineIn
protected boolean onCommand (int eTag, String sParameter) {
TextMeasurement oMeasure = null;
int nOffset;
int eParm;
double dScale;
boolean bCommandHandled = false;
switch (eTag) {
case MarkupAttr.MARKUP_PARAGRAPH_START: // +++
case MarkupAttr.MARKUP_DIV:
mbTextAccumulated = false;
mbParaStarted = true;
break;
case MarkupAttr.MARKUP_BREAK:
if (legacyBlankLineMode()) {
if (moStack.top().meSpace != SPACE_SUPPRESS_BREAK) {
flushPendingText (PENDING_BREAK);
mePending = PENDING_BREAK;
moPendingAttr.setDefault (false);
mbTextAccumulated = false;
}
} else {
flushPendingText (PENDING_BREAK); // previous break gets written
mePending = PENDING_BREAK; // this break is pending
moPendingAttr.setDefault (false);
mbTextAccumulated = false;
mbParaStarted = false; // break-only para is valid para
}
break;
// Special character
case MarkupAttr.MARKUP_HTML:
case MarkupAttr.MARKUP_BODY:
case MarkupAttr.MARKUP_SPAN:
mbTextAccumulated = false;
break;
case MarkupAttr.MARKUP_HTML_END:
case MarkupAttr.MARKUP_BODY_END:
popAttr();
mbTextAccumulated = false;
break;
case MarkupAttr.MARKUP_SPAN_END:
if (legacyBlankLineMode()) {
flushPendingText (PENDING_BREAK);
}
moPrevParaAttr.copyFrom (moCurrentAttr);
closeScopedBlock();
popAttr();
break;
case MarkupAttr.MARKUP_SPACE_RUN:
switch (markupAttr().lookup (sParameter)) {
case MarkupAttr.MARKUP_SPACE_RUN_YES:
moStack.top().meSpace = SPACE_RUN;
break;
case MarkupAttr.MARKUP_SPACE_RUN_NO:
moStack.top().meSpace = moStack.frameAt(moStack.size()-2).meSpace; // TBD: is this correct and safe?
break;
}
break;
// indentation
case MarkupAttr.MARKUP_INDENT_FIRST_LINE:
oMeasure = TextMeasurement.fromString (sParameter, stringToUnit (markupAttr(), sParameter));
// -ve Special means indent all but the 1st line; +ve Special
// means indent 1st line only. So, if we get a +ve indent 1st line
// from XHTML, we're OK. If it's negative, subtract it from our
// current Left Margin.
moCurrentAttr.special (oMeasure);
break;
case MarkupAttr.MARKUP_INDENT_LEFT:
if (sParameter.length() == 0) {
flushAttr();
} else {
// See comments under INDENT_FIRST_LINE
oMeasure = TextMeasurement.fromString (sParameter, stringToUnit (markupAttr(), sParameter));
moCurrentAttr.marginL (oMeasure);
}
break;
case MarkupAttr.MARKUP_INDENT_RIGHT:
oMeasure = TextMeasurement.fromString (sParameter, stringToUnit (markupAttr(), sParameter));
moCurrentAttr.marginR (oMeasure);
break;
case MarkupAttr.MARKUP_SPACE_BEFORE:
oMeasure = TextMeasurement.fromString (sParameter, stringToUnit (markupAttr(), sParameter));
moCurrentAttr.spaceBefore (oMeasure);
break;
case MarkupAttr.MARKUP_SPACE_AFTER:
oMeasure = TextMeasurement.fromString (sParameter, stringToUnit (markupAttr(), sParameter));
moCurrentAttr.spaceAfter (oMeasure);
break;
case MarkupAttr.MARKUP_MARGIN:
StringBuilder sCmd = new StringBuilder (sParameter);
StringUtils.trimStart (sCmd);
int nCount = 0;
int nFoundAt = sCmd.indexOf (" ");
nOffset = 0;
while ((nFoundAt >= 0) && (nCount < 4)) {
String sSize = sCmd.substring (nOffset, nFoundAt);
nCount++;
oMeasure = TextMeasurement.fromString(sSize, stringToUnit (markupAttr(), sSize));
if (nCount == 1) { //if only one value: all margins = value
moCurrentAttr.marginL (oMeasure);
moCurrentAttr.marginR (oMeasure);
moCurrentAttr.spaceBefore (oMeasure);
moCurrentAttr.spaceAfter (oMeasure);
} else if (nCount == 2) { // if two values: top, bottom = 1; left, right = 2
moCurrentAttr.marginL (oMeasure);
moCurrentAttr.marginR (oMeasure);
} else if (nCount == 3) { // if three values: top = 1, left, right = 2, bottom = 3
moCurrentAttr.spaceAfter (oMeasure);
} else if (nCount == 4) { // if four values: top=1, right=2, bottom=3, left=4
moCurrentAttr.marginL (oMeasure);
}
nOffset = nFoundAt + 1;
nFoundAt = sCmd.indexOf (" ", nOffset);
}
break;
case MarkupAttr.MARKUP_LINE_HEIGHT:
oMeasure = TextMeasurement.fromString (sParameter, stringToUnit (markupAttr(), sParameter));
moCurrentAttr.spacing (oMeasure);
break;
// font
case MarkupAttr.MARKUP_FONT_NAME:
moCurrentAttr.typeface (sParameter);
break;
case MarkupAttr.MARKUP_FONT_SIZE: {
UnitSpan size = new UnitSpan (sParameter, stringToUnit (markupAttr(), sParameter), false);
moCurrentAttr.size (size);
break;
}
case MarkupAttr.MARKUP_FONT_WEIGHT:
updateWeight(sParameter);
break;
case MarkupAttr.MARKUP_FONT_STYLE:
updateItalic(sParameter);
break;
case MarkupAttr.MARKUP_FONT:
updateFont(sParameter);
break;
case MarkupAttr.MARKUP_FONT_HORIZONTAL_SCALE:
dScale = TextAttr.parsePercent (sParameter, true);
if (! Double.isNaN(dScale)) {
moCurrentAttr.horizontalScale (dScale);
}
break;
case MarkupAttr.MARKUP_FONT_VERTICAL_SCALE:
dScale = TextAttr.parsePercent (sParameter, true);
if (! Double.isNaN(dScale)) {
moCurrentAttr.verticalScale (dScale);
}
break;
case MarkupAttr.MARKUP_COLOUR:
int lR = 0;
int lG = 0;
int lB = 0;
// #FFFFFF style
if (sParameter.charAt (0) == '#') {
lR = Integer.parseInt (sParameter.substring (1, 3), 16);
lG = Integer.parseInt (sParameter.substring (3, 5), 16);
lB = Integer.parseInt (sParameter.substring (5, 7), 16);
}
// rgb(999,999,999) style
else if ((sParameter.substring(0,4).equalsIgnoreCase (gsRGBStart))
&& (sParameter.substring(sParameter.length()-1).equals (gsRGBEnd))) {
nOffset = 4;
int nFound = sParameter.indexOf (',', nOffset);
lR = Integer.parseInt (sParameter.substring (nOffset, nFound));
nOffset = nFound + 1;
nFound = sParameter.indexOf (',', nOffset);
lG = Integer.parseInt (sParameter.substring (nOffset, nFound));
nOffset = nFound + 1;
nFound = sParameter.indexOf (',', nOffset);
lB = Integer.parseInt (sParameter.substring (nOffset, nFound));
}
GFXColour oColour = new GFXColour (lR, lG, lB, 255);
moCurrentAttr.colour (oColour);
break;
// effects
case MarkupAttr.MARKUP_TEXT_DECORATION:
int eStrikeCode = GFXTextAttr.STRIKEOUT_NONE;
if (findAttr (sParameter, MarkupAttr.MARKUP_STRIKEOUT)) {
eStrikeCode = GFXTextAttr.STRIKEOUT_SINGLE;
}
moCurrentAttr.strikeout (eStrikeCode);
int nUnderType = GFXTextAttr.UNDER_NONE;
int nUnderCount = 0;
if (findAttr (sParameter, MarkupAttr.MARKUP_UNDERLINE)) {
nUnderType = GFXDecorationInfo.DECORATE_ALL;
nUnderCount = GFXDecorationInfo.DECORATE_SINGLE;
}
if (findAttr (sParameter, MarkupAttr.MARKUP_UNDERLINE_DOUBLE)) {
nUnderType = GFXDecorationInfo.DECORATE_ALL;
nUnderCount = GFXDecorationInfo.DECORATE_DOUBLE;
}
if (findAttr (sParameter, MarkupAttr.MARKUP_UNDERLINE_WORD)) {
nUnderType = GFXDecorationInfo.DECORATE_WORD;
if (nUnderCount == 0) {
nUnderCount = GFXDecorationInfo.DECORATE_SINGLE;
}
}
moCurrentAttr.underline (nUnderType | nUnderCount);
break;
case MarkupAttr.MARKUP_KERNING_MODE:
switch (markupAttr().lookup (sParameter)) {
case MarkupAttr.MARKUP_NONE:
moCurrentAttr.kerning (false);
break;
case MarkupAttr.MARKUP_KERN_PAIR:
moCurrentAttr.kerning (true);
break;
}
break;
case MarkupAttr.MARKUP_BOLD:
pushAttr();
moCurrentAttr.weight (FontInfo.WEIGHT_BOLD);
flushPendingText (PENDING_DEFAULT);
attr (moCurrentAttr);
break;
case MarkupAttr.MARKUP_ITALIC:
pushAttr();
moCurrentAttr.italic (true);
flushPendingText (PENDING_DEFAULT);
attr (moCurrentAttr);
break;
// underlining
case MarkupAttr.MARKUP_UNDERLINE_TAG:
pushAttr();
moCurrentAttr.underline (GFXTextAttr.UNDER_ALL | GFXTextAttr.UNDER_SINGLE);
flushPendingText (PENDING_DEFAULT);
attr (moCurrentAttr);
break;
case MarkupAttr.MARKUP_UNDERLINE_END:
moPrevParaAttr.copyFrom (moCurrentAttr);
popAttr();
break;
// justification
// Horizontal
case MarkupAttr.MARKUP_JUSTIFY:
switch (markupAttr().lookup (sParameter)) {
case MarkupAttr.MARKUP_JUSTIFY_SPREAD:
moCurrentAttr.justifyH (TextAttr.JUST_H_SPREAD);
break;
case MarkupAttr.MARKUP_JUSTIFY_SPREAD_ALL:
moCurrentAttr.justifyH (TextAttr.JUST_H_SPREAD_ALL);
break;
case MarkupAttr.MARKUP_XHTML_LEFT:
moCurrentAttr.justifyH (TextAttr.JUST_H_LEFT);
break;
case MarkupAttr.MARKUP_XHTML_CENTER:
moCurrentAttr.justifyH (TextAttr.JUST_H_CENTRE);
break;
case MarkupAttr.MARKUP_XHTML_RIGHT:
moCurrentAttr.justifyH (TextAttr.JUST_H_RIGHT);
break;
case MarkupAttr.MARKUP_JUSTIFY_COMB_LEFT:
moCurrentAttr.justifyH (TextAttr.JUST_H_COMB_LEFT);
break;
case MarkupAttr.MARKUP_JUSTIFY_COMB_CENTER:
moCurrentAttr.justifyH (TextAttr.JUST_H_COMB_CENTRE);
break;
case MarkupAttr.MARKUP_JUSTIFY_COMB_RIGHT:
moCurrentAttr.justifyH (TextAttr.JUST_H_COMB_RIGHT);
break;
}
break;
// Vertical
case MarkupAttr.MARKUP_JUSTIFY_VERT:
case MarkupAttr.MARKUP_TEXT_VALIGN:
switch (markupAttr().lookup (sParameter)) {
case MarkupAttr.MARKUP_TEXT_VALIGN_TOP:
moCurrentAttr.justifyV (TextAttr.JUST_V_TOP);
break;
case MarkupAttr.MARKUP_TEXT_VALIGN_MIDDLE:
moCurrentAttr.justifyV (TextAttr.JUST_V_MIDDLE);
break;
case MarkupAttr.MARKUP_TEXT_VALIGN_BOTTOM:
moCurrentAttr.justifyV (TextAttr.JUST_V_BOTTOM);
break;
default: // also use vertical-align for baseline shift
boolean bSuppressInversion = (moStack.size() > 0) // check old FF99
&& (moStack.frameAt(0).meSpace == SPACE_FORCED);
TextBaselineShift oShift = new TextBaselineShift (sParameter, bSuppressInversion);
moCurrentAttr.baselineShift (oShift);
}
break;
case MarkupAttr.MARKUP_SUPER:
pushAttr();
UnitSpan oShift = textAttr().size();
oShift = oShift.multiply (-0.31);
if (moCurrentAttr.baselineShiftEnable()) {
UnitSpan oBase = new UnitSpan (moCurrentAttr.baselineShift().getString (false));
oShift = oShift.add (oBase);
}
moCurrentAttr.baselineShift (new TextBaselineShift (oShift));
moCurrentAttr.size (moCurrentAttr.size().multiply (0.66));
flushPendingText (PENDING_DEFAULT);
attr (moCurrentAttr);
break;
case MarkupAttr.MARKUP_SUB_END:
case MarkupAttr.MARKUP_SUPER_END:
case MarkupAttr.MARKUP_BOLD_END:
case MarkupAttr.MARKUP_ITALIC_END:
flushPendingText (PENDING_BREAK);
moPrevParaAttr.copyFrom (moCurrentAttr);
popAttr();
break;
case MarkupAttr.MARKUP_PARAGRAPH_END:
case MarkupAttr.MARKUP_DIV_END:
moPrevParaAttr.copyFrom (moCurrentAttr);
popAttr();
mbTextAccumulated = false;
break;
case MarkupAttr.MARKUP_SUB:
pushAttr();
oShift = moCurrentAttr.size();
oShift = oShift.multiply (0.15);
if (moCurrentAttr.baselineShiftEnable()) {
UnitSpan oBase = new UnitSpan (moCurrentAttr.baselineShift().getString (false));
oShift = oShift.add (oBase);
}
moCurrentAttr.baselineShift (new TextBaselineShift (oShift));
moCurrentAttr.size (moCurrentAttr.size().multiply (0.66));
flushPendingText (PENDING_DEFAULT);
attr (moCurrentAttr);
break;
// tabs
case MarkupAttr.MARKUP_TAB_DEFAULT:
tabDefault(sParameter, moCurrentAttr, markupAttr());
break;
case MarkupAttr.MARKUP_TAB:
break;
case MarkupAttr.MARKUP_TAB_POSITION:
tabSet (sParameter, moCurrentAttr, markupAttr());
break;
case MarkupAttr.MARKUP_XHTML_LEFT:
pendingTab(TextTab.TYPE_LEFT);
break;
case MarkupAttr.MARKUP_XHTML_CENTER:
pendingTab(TextTab.TYPE_CENTRE);
break;
case MarkupAttr.MARKUP_XHTML_RIGHT:
pendingTab(TextTab.TYPE_RIGHT);
break;
case MarkupAttr.MARKUP_TAB_ALIGN_DECIMAL:
pendingTab(TextTab.TYPE_DECIMAL);
break;
case MarkupAttr.MARKUP_TAB_COUNT:
Integer count = StringUtils.number (sParameter);
if (count != null) {
int tabs = count.intValue();
if (tabs > 0) {
mnTabCount += tabs;
}
}
break;
case MarkupAttr.MARKUP_DIGITS:
eParm = markupAttr().lookup (sParameter);
int eDigits = TextAttr.DIGITS_LOCALE;
switch (eParm) {
case MarkupAttr.MARKUP_DIGITS_ARABIC:
eDigits = TextAttr.DIGITS_ARABIC;
break;
case MarkupAttr.MARKUP_DIGITS_INDIC:
eDigits = TextAttr.DIGITS_INDIC;
break;
}
moCurrentAttr.digits (eDigits);
break;
case MarkupAttr.MARKUP_LIGATURE:
eParm = markupAttr().lookup (sParameter);
int eLigature;
switch (eParm)
{
case MarkupAttr.MARKUP_LIGATURE_COMMON:
eLigature = TextAttr.LIGATURE_COMMON;
break;
default:
eLigature = TextAttr.LIGATURE_MINIMUM;
break;
}
moCurrentAttr.ligature (eLigature);
break;
case MarkupAttr.MARKUP_CHAR_SPACING:
oMeasure = TextMeasurement.fromString (sParameter, stringToUnit (markupAttr(), sParameter));
moCurrentAttr.charSpacing (oMeasure);
break;
case MarkupAttr.MARKUP_WORD_SPACING:
oMeasure = TextMeasurement.fromString (sParameter, stringToUnit (markupAttr(), sParameter));
moCurrentAttr.wordSpacing (oMeasure);
break;
case MarkupAttr.MARKUP_HYPHENATION:
eParm = markupAttr().lookup (sParameter);
if (eParm == MarkupAttr.MARKUP_AUTO) {
if (moCurrentAttr.hyphLevelEnable()
&& (moCurrentAttr.hyphLevel() == TextAttr.HYPHEN_OFF))
moCurrentAttr.hyphLevel (TextAttr.HYPHEN_NORMAL);
} else {
moCurrentAttr.hyphLevel (TextAttr.HYPHEN_OFF);
}
break;
case MarkupAttr.MARKUP_HYPHENATION_LEVEL:
eParm = markupAttr().lookup (sParameter);
int eLevel = TextAttr.HYPHEN_OFF;
switch (eParm) {
case MarkupAttr.MARKUP_HYPHENATION_PREFERRED:
eLevel = TextAttr.HYPHEN_PREFERRED;
break;
case MarkupAttr.MARKUP_NORMAL:
eLevel = TextAttr.HYPHEN_NORMAL;
break;
case MarkupAttr.MARKUP_ALL:
eLevel = TextAttr.HYPHEN_ALL;
break;
}
if (eLevel != TextAttr.HYPHEN_OFF) {
moCurrentAttr.hyphLevel (eLevel);
}
break;
case MarkupAttr.MARKUP_HYPHENATION_LENGTH:
StringBuilder sTokens = new StringBuilder (sParameter);
int nMin[] = new int [3];
int nValues = 0;
while (nValues < 3) {
String sToken = StringUtils.parseToken (sTokens);
if (sToken == null) {
break;
}
Integer convert = StringUtils.number (sToken);
if (convert == null) {
break;
}
int result = convert.intValue();
if (result < 0) {
break;
}
nMin[nValues] = result;
nValues++;
}
if (nValues > 0) {
moCurrentAttr.hyphMinWord (nMin[0]);
if (nValues > 1) {
moCurrentAttr.hyphMinPrefix (nMin[1]);
if (nValues > 2) {
moCurrentAttr.hyphMinSuffix (nMin[2]);
}
}
}
break;
case MarkupAttr.MARKUP_HYPHENATION_ACRONYMS:
boolean bAcronyms = markupAttr().lookup (sParameter) == MarkupAttr.MARKUP_AUTO;
moCurrentAttr.hyphSuppressAcronyms (! bAcronyms);
break;
case MarkupAttr.MARKUP_HYPHENATION_NAMES:
boolean bNames = markupAttr().lookup (sParameter) == MarkupAttr.MARKUP_AUTO;
moCurrentAttr.hyphSuppressNames (! bNames);
break;
case MarkupAttr.MARKUP_LEADER_ALIGN: {
int eAlign = TextAttr.LEADER_ALIGN_NONE;
if (markupAttr().lookup (sParameter) == MarkupAttr.MARKUP_LEADER_ALIGN_PAGE) {
eAlign = TextAttr.LEADER_ALIGN_PAGE;
}
moCurrentAttr.leaderAlign (eAlign);
break;
}
case MarkupAttr.MARKUP_LEADER_PATTERN: {
int ePattern = TextAttr.LEADER_PATTERN_SPACE;
switch (markupAttr().lookup (sParameter)) {
case MarkupAttr.MARKUP_LEADER_PATTERN_RULE:
ePattern = TextAttr.LEADER_PATTERN_RULE;
break;
case MarkupAttr.MARKUP_LEADER_PATTERN_DOTS:
ePattern = TextAttr.LEADER_PATTERN_DOTS;
break;
case MarkupAttr.MARKUP_LEADER_PATTERN_USE_CONTENT:
case MarkupAttr.MARKUP_LEADER_PATTERN_USE_CONTENT2:
ePattern = TextAttr.LEADER_PATTERN_USE_CONTENT;
break;
}
moCurrentAttr.leaderPattern (ePattern);
break;
}
case MarkupAttr.MARKUP_LEADER_PATTERN_WIDTH:
oMeasure = TextMeasurement.fromString (sParameter);
moCurrentAttr.leaderPatternWidth (oMeasure);
break;
case MarkupAttr.MARKUP_RULE_STYLE: {
int eStyle = TextAttr.RULE_STYLE_SOLID;
switch (markupAttr().lookup (sParameter)) {
case MarkupAttr.MARKUP_NONE:
eStyle = TextAttr.RULE_STYLE_NONE;
break;
case MarkupAttr.MARKUP_RULE_STYLE_DOTTED:
eStyle = TextAttr.RULE_STYLE_DOTTED;
break;
case MarkupAttr.MARKUP_RULE_STYLE_DASHED:
eStyle = TextAttr.RULE_STYLE_DASHED;
break;
}
moCurrentAttr.ruleStyle (eStyle);
break;
}
case MarkupAttr.MARKUP_RULE_THICKNESS:
oMeasure = TextMeasurement.fromString (sParameter);
moCurrentAttr.ruleThickness (oMeasure);
break;
case MarkupAttr.MARKUP_UNKNOWN:
default:
bCommandHandled = false; // not handled here
break;
}
return bCommandHandled;
}
private boolean handleStylingAttributes (Attributes attributes, boolean bIsPara) {
String sAttr;
boolean bHasStyle = false;
sAttr = getAttr (attributes, MarkupAttr.MARKUP_STYLE);
if (sAttr != null) {
parseStyleAttr (sAttr, false);
bHasStyle = true;
}
sAttr = getAttr (attributes, MarkupAttr.MARKUP_DIRECTION);
if (sAttr != null) {
int eParm = markupAttr().lookup (sAttr);
int eDirection = TextAttr.DIRECTION_NEUTRAL;
switch (eParm) {
case MarkupAttr.MARKUP_DIRECTION_LTR:
eDirection = TextAttr.DIRECTION_LTR;
break;
case MarkupAttr.MARKUP_DIRECTION_RTL:
eDirection = TextAttr.DIRECTION_RTL;
break;
}
if (bIsPara) {
moCurrentAttr.paraDirection (eDirection);
} else {
moCurrentAttr.direction (eDirection);
}
attr (moCurrentAttr);
bHasStyle = true;
}
return bHasStyle;
}
private void initialize (TextAttr poAmbientAttr) {
mbVersionDetermined = false;
mbTextAccumulated = false;
mbParaStarted = true;
mePending = PENDING_NONE;
mnTabCount = 0;
mbAmbientSupplied = false;
if (poAmbientAttr != null) {
moAmbientAttr = poAmbientAttr;
mbAmbientSupplied = true;
}
}
private boolean handleStylingAttributes (Attributes attributes) {
return handleStylingAttributes (attributes, false);
}
private void parseStyleAttr (String sAttr, boolean bIsInlineTag) {
final int BIT_INDENT_LEFT = 0x001;
final int BIT_SPACE_BEFORE = 0x002;
final int BIT_INDENT_RIGHT = 0x004;
final int BIT_SPACE_AFTER = 0x008;
final int BIT_INDENT_FIRST_LINE = 0x010;
final int BIT_LINE_HEIGHT = 0x020;
final int BIT_JUSTIFY_VERT = 0x040;
final int BIT_JUSTIFY = 0x080;
final int BIT_TAB_DEFAULT = 0x100;
final int BIT_ALL_PARA = 0x1FF;
int nBits = 0;
String sName = null;
int nItalic = FontInfo.STYLE_UNKNOWN;
int nWeight = FontInfo.WEIGHT_UNKNOWN;
UnitSpan oSize = null;
StyleInfo info = new StyleInfo (sAttr);
// Watson# 1311258. Collect all of the font parameters before requesting a font for a style.
while (extractStyleElement (info)) {
String value = info.parameter.toString();
switch (info.commandEnum) {
case MarkupAttr.MARKUP_FONT_NAME:
sName = value;
break;
case MarkupAttr.MARKUP_FONT_WEIGHT:
if (findAttr (value, MarkupAttr.MARKUP_FONT_BOLD))
nWeight = FontInfo.WEIGHT_BOLD;
else if (findAttr (value, MarkupAttr.MARKUP_NORMAL))
nWeight = FontInfo.WEIGHT_NORMAL;
break;
case MarkupAttr.MARKUP_FONT_STYLE:
if (findAttr (value, MarkupAttr.MARKUP_FONT_ITALIC))
nItalic = FontInfo.STYLE_ITALIC;
else if (findAttr (value, MarkupAttr.MARKUP_NORMAL))
nItalic = FontInfo.STYLE_UPRIGHT;
break;
case MarkupAttr.MARKUP_FONT_SIZE:
oSize = new UnitSpan (value, stringToUnit (markupAttr(), value), false);
break;
}
}
// Send the font request with all of the font-related properties from the style attribute in one call.
if ((sName != null)
|| (nWeight != FontInfo.WEIGHT_UNKNOWN)
|| (nItalic != FontInfo.STYLE_UNKNOWN)
|| (oSize != null)) {
moCurrentAttr.typeface (sName, oSize, nWeight, nItalic);
}
// Now process complete style attribute, ignoring the above font-related properties that have already been processed.
info.offset = 0;
while (extractStyleElement (info)) {
if ((info.commandEnum != MarkupAttr.MARKUP_FONT_NAME)
&& (info.commandEnum != MarkupAttr.MARKUP_FONT_WEIGHT)
&& (info.commandEnum != MarkupAttr.MARKUP_FONT_STYLE)
&& (info.commandEnum != MarkupAttr.MARKUP_FONT_SIZE)) {
onCommand (info.commandEnum, info.parameter.toString());
}
switch (info.commandEnum) {
case MarkupAttr.MARKUP_INDENT_LEFT: nBits |= BIT_INDENT_LEFT; break;
case MarkupAttr.MARKUP_SPACE_BEFORE: nBits |= BIT_SPACE_BEFORE; break;
case MarkupAttr.MARKUP_INDENT_RIGHT: nBits |= BIT_INDENT_RIGHT; break;
case MarkupAttr.MARKUP_SPACE_AFTER: nBits |= BIT_SPACE_AFTER; break;
case MarkupAttr.MARKUP_INDENT_FIRST_LINE: nBits |= BIT_INDENT_FIRST_LINE; break;
case MarkupAttr.MARKUP_LINE_HEIGHT: nBits |= BIT_LINE_HEIGHT; break;
case MarkupAttr.MARKUP_JUSTIFY_VERT: nBits |= BIT_JUSTIFY_VERT; break;
case MarkupAttr.MARKUP_JUSTIFY: nBits |= BIT_JUSTIFY; break;
case MarkupAttr.MARKUP_TAB_DEFAULT: nBits |= BIT_TAB_DEFAULT; break;
}
}
// At one time, FF99 put out HTML with no version number. The following
// heuristic attempts to identify FF99 by checking for all paragraph
// attributes on the first style attribute encountered.
if (! mbVersionDetermined) {
mbVersionDetermined = true;
if (nBits == BIT_ALL_PARA) {
switchToFF99Mode();
}
}
flushPendingText (bIsInlineTag ? PENDING_DEFAULT : PENDING_BREAK);
attr (moCurrentAttr);
}
private static int stringToUnit (MarkupAttr pMarkupAttr, String sString) {
int lStart = StringUtils.skipOver (sString, gsNumeric, 0);
String sUnit = sString.substring (lStart);
if (sUnit.equals (pMarkupAttr.lookup (MarkupAttr.MARKUP_CM))) {
return UnitSpan.CM_250K;
} else if (sUnit.equals (pMarkupAttr.lookup (MarkupAttr.MARKUP_PT))) {
return UnitSpan.POINTS_1K;
} else if (sUnit.equals (pMarkupAttr.lookup (MarkupAttr.MARKUP_INCHES))) {
return UnitSpan.INCHES_72K;
} else if (sUnit.equals (pMarkupAttr.lookup (MarkupAttr.MARKUP_MM))) {
return UnitSpan.MM_25K;
} else {
return UnitSpan.UNIT_UNKNOWN;
}
}
private static int stringToAlign (MarkupAttr pMarkupAttr, String sString) {
if (sString.equals (pMarkupAttr.lookup (MarkupAttr.MARKUP_XHTML_LEFT))) {
return TextTab.TYPE_LEFT;
} else if (sString.equals (pMarkupAttr.lookup (MarkupAttr.MARKUP_XHTML_RIGHT))) {
return TextTab.TYPE_RIGHT;
} else if (sString.equals (pMarkupAttr.lookup (MarkupAttr.MARKUP_XHTML_CENTER))) {
return TextTab.TYPE_CENTRE;
} else if (sString.equals (pMarkupAttr.lookup (MarkupAttr.MARKUP_TAB_ALIGN_DECIMAL))) {
return TextTab.TYPE_DECIMAL;
} else {
return TextTab.TYPE_LEFT;
}
}
private void updateWeight (String sString) {
if (findAttr (sString, MarkupAttr.MARKUP_FONT_BOLD)) {
moCurrentAttr.weight (FontInfo.WEIGHT_BOLD);
} else if (findAttr (sString, MarkupAttr.MARKUP_NORMAL)) {
moCurrentAttr.weight (FontInfo.WEIGHT_NORMAL);
}
}
private void updateItalic (String sString) {
if (findAttr (sString, MarkupAttr.MARKUP_FONT_ITALIC)) {
moCurrentAttr.italic (true);
} else if (findAttr (sString, MarkupAttr.MARKUP_NORMAL)) {
moCurrentAttr.italic (false);
}
}
private void updateFont (String sString) {
updateWeight (sString);
updateItalic (sString);
int nFoundAt = sString.indexOf (' ');
int nOffset = 0;
while (nFoundAt >= nOffset) {
String sCmd = sString.substring (nOffset, nFoundAt);
//we've already dealt with style and weight
int eCmd = markupAttr().lookup (sCmd);
if ((eCmd != MarkupAttr.MARKUP_FONT_BOLD)
&& (eCmd != MarkupAttr.MARKUP_NORMAL)
&& (eCmd != MarkupAttr.MARKUP_FONT_ITALIC)) {
char c = (sCmd.length() == 0) ? '\0' : sCmd.charAt (0);
if ((c >= '0') && (c <= '9')) { //assume this is the size
//check for line-height
int nFoundLH = sCmd.indexOf ('/');
if (nFoundLH >= 0) {
String sLineHeight = sCmd.substring (nFoundLH + 1);
onCommand (MarkupAttr.MARKUP_LINE_HEIGHT, sLineHeight);
sCmd = sCmd.substring (nFoundLH);
}
onCommand (MarkupAttr.MARKUP_FONT_SIZE, sCmd);
} else { //assume this is the typeface
if ((c == '\'') || (c == '"')) {
nFoundAt = sString.indexOf (c, nFoundAt + 1);
sCmd = sString.substring (nOffset, nFoundAt);
}
onCommand (MarkupAttr.MARKUP_FONT_NAME, sCmd);
}
}
nOffset = nFoundAt + 1;
nFoundAt = sString.indexOf (' ', nOffset);
}
}
private boolean findAttr (String sCommand, int eTag) {
return sCommand.contains(markupAttr().lookup (eTag)); // TODO: is/should this be case sensitive?
}
// private boolean compareAttr (String sAttr, int eTag) {
// return sAttr.compareToIgnoreCase (markupAttr().lookup (eTag)) == 0; // TODO: case sensitivity?
// }
private void embed (String sExpression, int eEmbedType, int eEmbedMode) {
// embedded content shouldn't always be resolved
TextField oTextField = new TextField (eEmbedType, eEmbedMode, sExpression);
// oTextField.attrPool (AttrPool());
oTextField.fontService (fontService());
if (mpoResolver != null) {
mpoResolver.embedContent (oTextField);
}
field (oTextField);
}
//----------------------------------------------------------------------
//
// SwitchToFF99Mode - Legacy: Apparently FF99 expects all
// spaces to be rendered, so if during processing of the
// HTML, we determine that this is a FF99 form, we need to
// update our stack to force spaces.
//
//----------------------------------------------------------------------
private void switchToFF99Mode () {
for (int i = 0; i < moStack.size(); i++) {
if (moStack.frameAt(i).meSpace <= SPACE_NORMAL) {
moStack.frameAt(i).meSpace = SPACE_FORCED;
}
}
}
private boolean extractStyleElement (StyleInfo info) {
final int nCommandInitialAlloc = 32;
final int nParameterInitialAlloc = 128;
if (mIterator == null) {
mIterator = new UniCharIterator();
}
mIterator.attach (info.attrValue, info.offset);
info.commandEnum = MarkupAttr.MARKUP_UNKNOWN;
while ((info.commandEnum == MarkupAttr.MARKUP_UNKNOWN) && (mIterator.getIndex() < info.attrValue.length())) {
info.command.delete (0, info.command.length());
info.parameter.delete (0, info.parameter.length());
int nAlloc = info.attrValue.length() - info.offset;
if (nAlloc < nCommandInitialAlloc) {
nAlloc = nCommandInitialAlloc;
}
info.command.ensureCapacity (nAlloc);
int cQuote = '\0';
int nQuoteStart = 0;
final int STATE_PRE_COMMAND = 0;
final int STATE_COMMAND = 1;
final int STATE_POST_COMMAND = 2;
final int STATE_PRE_PARAMETER = 3;
final int STATE_PARAMETER = 4;
final int STATE_DONE = 5;
int eState = STATE_PRE_COMMAND;
while ((mIterator.getIndex() < info.attrValue.length()) && (eState != STATE_DONE)) {
int nPrevOffset = mIterator.getIndex();
int c = mIterator.next();
if (cQuote != '\0') {
if (c == cQuote) {
cQuote = '\0';
switch (eState) {
case STATE_PRE_PARAMETER:
case STATE_PARAMETER:
info.parameter.append(info.attrValue, nQuoteStart, nPrevOffset);
break;
}
}
}
else if ((c == '"') || (c == '\'')) {
nQuoteStart = nPrevOffset + 1;
cQuote = c;
}
else {
switch (eState) {
case STATE_PRE_COMMAND:
switch (c) {
case ' ':
case ':':
case ';':
break;
default:
UniCharIterator.append (info.command, c);
eState = STATE_COMMAND;
}
break;
case STATE_COMMAND:
switch (c) {
case ' ': eState = STATE_POST_COMMAND; break;
case ';': eState = STATE_DONE; break;
case ':': eState = STATE_PRE_PARAMETER; break;
default: UniCharIterator.append (info.command, c);
}
break;
case STATE_POST_COMMAND:
switch (c) {
case ';': eState = STATE_DONE; break;
case ':': eState = STATE_PRE_PARAMETER; break;
}
break;
case STATE_PRE_PARAMETER:
switch (c) {
case ';': eState = STATE_DONE; break;
case ' ': break;
default:
nAlloc = info.attrValue.length() - mIterator.getIndex();
if (nAlloc < nParameterInitialAlloc) {
nAlloc = nParameterInitialAlloc;
}
info.parameter.ensureCapacity (nAlloc);
UniCharIterator.append (info.parameter, c);
eState = STATE_PARAMETER;
}
break;
case STATE_PARAMETER:
switch (c) {
case ';': eState = STATE_DONE; break;
default: UniCharIterator.append (info.parameter, c);
}
break;
}
}
}
if (eState != STATE_PRE_COMMAND) {
info.command.append (':'); // so we don't confuse styles with tags and attributes
String cmd = info.command.toString();
info.commandEnum = markupAttr().lookup (cmd);
int nLength;
for (nLength = info.parameter.length(); nLength > 0; ) {
nLength--; // length to index
if (! isXHTMLSpace (info.parameter.charAt (nLength))) {
nLength++; // back to a length again
break;
}
}
info.parameter.delete (nLength, info.parameter.length());
}
}
info.offset = mIterator.getIndex();
return info.commandEnum != MarkupAttr.MARKUP_UNKNOWN;
}
private boolean inUnknownElement () {
return moStack.top().meTag == MarkupAttr.MARKUP_UNKNOWN;
}
private static boolean isXHTMLSpace (int c) {
return (c == ' ') || (c == '\n') || (c == '\r') || (c == '\t') || (c == '\u000C'); // return: should never happen // formfeed: should never happen
}
private void updateSpaceStatus (int eTag, boolean bEnd) {
int eSpace = moStack.top().meSpace;
if ((eTag == MarkupAttr.MARKUP_PARAGRAPH_START)
|| (eTag == MarkupAttr.MARKUP_BODY)
|| (eTag == MarkupAttr.MARKUP_DIV)
|| (eTag == MarkupAttr.MARKUP_HTML)) {
if ((! bEnd) || (eTag == MarkupAttr.MARKUP_BODY)) {
if ((eSpace == SPACE_NORMAL) || (eSpace == SPACE_SUPPRESS)) {
moStack.top().meSpace = legacyBlankLineMode() // suppress at start of block
? SPACE_SUPPRESS_BREAK
: SPACE_SUPPRESS;
}
mePending = PENDING_NONE;
} else {
if (mePending == PENDING_SPACE) {
flushPendingText (PENDING_DEFAULT);
}
}
} else if (eTag == MarkupAttr.MARKUP_BREAK) {
if (eSpace == SPACE_NORMAL) {
moStack.top().meSpace = SPACE_SUPPRESS;
}
} else {
if (eSpace < SPACE_NORMAL) {
moStack.top().meSpace = SPACE_NORMAL;
}
}
}
private void updateSpaceStatus (int eTag) {
updateSpaceStatus (eTag, false);
}
private void flushPendingText (int ePending) { // special case: break and div only
if (ePending == PENDING_DEFAULT) {
ePending = legacyBlankLineMode() ? PENDING_DIV : PENDING_NONE;
}
if ((mePending >= ePending)
|| (legacyBlankLineMode() && (ePending == PENDING_NONE))) { // special case: break and div only
String psText = null;
switch (mePending) {
case PENDING_DIV:
if (legacyBlankLineMode()) {
if (mbTextAccumulated) {
psText = gsNewLine;
}
}
break;
case PENDING_BREAK:
psText = gsNewLine;
break;
case PENDING_SPACE:
if ((! legacyBlankLineMode()) || (ePending != PENDING_NONE)) {
psText = " "; // special case: break and div only
}
break;
}
if (psText != null) {
pushAttr();
attr (moPendingAttr);
text (psText);
if (moStack.top().meSpace == SPACE_NORMAL) {
moStack.top().meSpace = SPACE_SUPPRESS;
}
popAttr();
mePending = PENDING_NONE;
}
}
}
private static boolean extractHTMLVersion (String sVersion, int[] nVersion) {
if (sVersion.length() == 0) {
return false;
}
int nVersionIndex;
for (nVersionIndex = 0; nVersionIndex < 4; nVersionIndex++) {
nVersion[nVersionIndex] = 0;
}
int nChar = 0;
int nStartChar = 0;
nVersionIndex = 0;
while (nChar < sVersion.length()) {
int nEndChar = nChar;
char c = sVersion.charAt (nChar);
nChar++;
if (c == '.') {
if (nEndChar >= nStartChar) {
String sComponent = sVersion.substring (nStartChar, nEndChar);
Integer value = StringUtils.number (sComponent);
if (value == null) {
return false;
}
nVersion[nVersionIndex] = value.intValue();
}
nVersionIndex++;
if (nVersionIndex >= 4) {
return true;
}
nStartChar = nChar;
}
}
return true;
}
private static int compareHTMLVersions (int[] nVersion1, int[] nVersion2) {
for (int i = 0; i < 4; i++) {
if (nVersion1[i] < nVersion2[i]) {
return -1;
}
if (nVersion1[i] > nVersion2[i]) {
return 1;
}
}
return 0;
}
private static TextTab extractTab (MarkupAttr markupAttr, String measurement, int tabType) {
UnitSpan oValue = new UnitSpan (measurement, stringToUnit (markupAttr, measurement), false);
return new TextTab (oValue, tabType);
}
private static TextTabList getTabList (TextAttr attr) {
return ((attr == null) || (! attr.tabsEnable())) ? new TextTabList ()
: new TextTabList (attr.tabs());
}
private void commitTabs (LeaderInfo poLeaderInfo) {
if (poLeaderInfo != null) {
moCurrentAttr.leaderContent (poLeaderInfo.moContent);
attr (moCurrentAttr);
}
StringBuilder sTabs = new StringBuilder();
sTabs.ensureCapacity (mnTabCount);
for (int i = 0; i < mnTabCount; i++) {
sTabs.append ('\t');
}
text (sTabs.toString());
mnTabCount = 0;
closeScopedBlock();
popAttr();
moStack.top().meTag = MarkupAttr.MARKUP_UNKNOWN;
}
}