All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.xfa.text.markup.MarkupXHTMLOut Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
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.TextTab;
import com.adobe.xfa.text.TextTabList;

import com.adobe.xfa.ut.UnitSpan;
import com.adobe.xfa.ut.Version;

/**
 * @exclude from published api.
 */

public class MarkupXHTMLOut extends MarkupEngineOut {
	private final StringBuilder mpTranslation = new StringBuilder();
	private final int meDefaultUnits;

	private boolean mbNewPara;
	private boolean mbStarted;
	private boolean mbParaHasContent;
	private final boolean mbIncludeAmbientAttrs;
	private boolean mbNeedSpanEnd;
	private boolean mbSubordinate;
	private final boolean mbBreakupOutput; // If TRUE, add carriage returns where possible
	private boolean mbExpandEmbed;
	private final boolean mbRoundTextSize;
	private boolean mbRequiresReset;
	private boolean mbTrailingBreak;

	private TextAttr moParaAttr;
	private final TextAttr moSpanAttr = new TextAttr();
	private final TextAttr moAmbientAttr;
	private final TextAttr moCSSDefault = new TextAttr (true);

	private final static UnitSpan ROUND_SIZE = new UnitSpan (UnitSpan.POINTS_1K, 50);

	private final static String gsDblSp = "  ";
	private final static String gsNBSP = " ";
	private final static String gsTab = "\t";
	private final static String gsNL = "\n";
	private final static String gsBR = "
"; private final static String gsNineSpaces = "         "; private final static String gsBodyStart1 = ""; private final static String gsBodyEnd = ""; private final static String gsParaOpenTag = ""; private final static String gsSpaceRunSingle = " "; private final static String gsTabCountStart = ""; private final static String gsStyleEndTag = "\"/>"; private final static String gsTagEnd = ">"; private final static String gsTextDirectionStart = "dir=\""; private final static String gsTextDirectionEnd = "\""; /** * Constructor. * @param pMarkupAttr - (optional) Markup attribute table to use instead * of the defalt. It is strongly recommended that you pass NULL * (default). * eDefaultUnits - (optional) Default units. All non-text unit values * are converted to this unit before writing. Default is inches. * @param poAmbientAttrs - (optional) Ambient attributes that can be * inherited from the environment and therefore don't need to be written * out. Default is NULL, no ambient attributes. * @param bIncludeAmbientAttrs - TRUE if ambient attributes are to be * used to make up for attributes missing in the source text; FALSE * (dafault) if ambient attributes can be suppressed altogether. * bBreakOutput - TRUE (default) to put in the occasional new-line when * allowed in order to prevent one very long line. FALSE to place all * the output on a single line. * @param bExpandEmbed - TRUE (default) if embedded field content is to * be expanded in-line; FALSE to generate field references. * @param bRoundTextSize - TRUE (default) if the text sizes are to be * rounded to the nearest 0.05pt. */ public MarkupXHTMLOut (MarkupAttr pMarkupAttr, int eDefaultUnits, TextAttr poAmbientAttrs, boolean bIncludeAmbientAttrs, boolean bBreakupOutput, boolean bExpandEmbed, boolean bRoundTextSize) { super ((pMarkupAttr == null) ? MarkupXHTMLAttr.getDefault() : pMarkupAttr); mbIncludeAmbientAttrs = bIncludeAmbientAttrs; mbBreakupOutput = bBreakupOutput; mbExpandEmbed = bExpandEmbed; mbRoundTextSize = bRoundTextSize; mbRequiresReset = true; mbTrailingBreak = false; // Check for various attributes which might not be specified, but which // we know are CSS and text services defaults. moCSSDefault.special (TextMeasurement.zero()); moCSSDefault.justifyV (TextAttr.JUST_V_TOP); moCSSDefault.justifyH (TextAttr.JUST_H_LEFT); moCSSDefault.tabs (new TextTabList()); // TODO: is it necessary to create these? moCSSDefault.baselineShift (new TextBaselineShift()); // TODO: is it necessary to create these? moCSSDefault.colour (GFXColour.black()); moCSSDefault.underline (GFXTextAttr.UNDER_NONE); moCSSDefault.weight (FontInfo.WEIGHT_NORMAL); moCSSDefault.italic (false); moCSSDefault.strikeout (GFXTextAttr.STRIKEOUT_NONE); moCSSDefault.marginL (TextMeasurement.zero()); moCSSDefault.marginR (TextMeasurement.zero()); moCSSDefault.hyphLevel (TextAttr.HYPHEN_OFF); moCSSDefault.leaderAlign (TextAttr.LEADER_ALIGN_NONE); moCSSDefault.leaderPattern (TextAttr.LEADER_PATTERN_SPACE); moCSSDefault.ruleStyle (TextAttr.RULE_STYLE_SOLID); moCSSDefault.ruleThickness (new TextMeasurement (new UnitSpan (UnitSpan.POINTS_1K, 1000))); moCSSDefault.horizontalScale (1.0); moCSSDefault.verticalScale (1.0); // no reasonable default for these moCSSDefault.spaceBeforeEnable (false); moCSSDefault.spaceAfterEnable (false); moCSSDefault.typefaceEnable (false); moCSSDefault.sizeEnable (false); moCSSDefault.spacingEnable (false); // Not enabled == single spacing moParaAttr = new TextAttr (moCSSDefault); if (poAmbientAttrs == null) { moAmbientAttr = new TextAttr(); } else { moAmbientAttr = new TextAttr (poAmbientAttrs); } moAmbientAttr.addDisabled (moCSSDefault); meDefaultUnits = eDefaultUnits; startDoc(); } public MarkupXHTMLOut (MarkupAttr pMarkupAttr, int eDefaultUnits, TextAttr poAmbientAttrs, boolean bIncludeAmbientAttrs, boolean bBreakupOutput, boolean bExpandEmbed) { this (pMarkupAttr, eDefaultUnits, poAmbientAttrs, bIncludeAmbientAttrs, bBreakupOutput, bExpandEmbed, true); } public MarkupXHTMLOut (MarkupAttr pMarkupAttr, int eDefaultUnits, TextAttr poAmbientAttrs, boolean bIncludeAmbientAttrs, boolean bBreakupOutput) { this (pMarkupAttr, eDefaultUnits, poAmbientAttrs, bIncludeAmbientAttrs, bBreakupOutput, true, true); } public MarkupXHTMLOut (MarkupAttr pMarkupAttr, int eDefaultUnits, TextAttr poAmbientAttrs, boolean bIncludeAmbientAttrs) { this (pMarkupAttr, eDefaultUnits, poAmbientAttrs, bIncludeAmbientAttrs, true, true, true); } public MarkupXHTMLOut (MarkupAttr pMarkupAttr, int eDefaultUnits, TextAttr poAmbientAttrs) { this (pMarkupAttr, eDefaultUnits, poAmbientAttrs, false, true, true, true); } public MarkupXHTMLOut (MarkupAttr pMarkupAttr, int eDefaultUnits) { this (pMarkupAttr, eDefaultUnits, null, false, true, true, true); } public MarkupXHTMLOut (MarkupAttr pMarkupAttr) { this (pMarkupAttr, UnitSpan.INCHES_72K, null, false, true, true, true); } public MarkupXHTMLOut () { this (null, UnitSpan.INCHES_72K, null, false, true, true, true); } public void reset () { if (mbRequiresReset) { super.reset(); startDoc(); } } /** * Obtain the resulting markup. * @return Generated markup, as a string. */ public String translation () { if (! mbSubordinate) { endPara(); mpTranslation.append (gsBodyEnd); } return mpTranslation.toString(); } public void text (String oStrText) { if (oStrText.length() == 0) { return; } mbTrailingBreak = false; // translate runs of spaces to a span that contains enough   chars // + one space at the end to equal the # of spaces in the text run StringBuilder sText = new StringBuilder (oStrText); // TODO: toXML() int nFoundAt; int nOffset = 0; nFoundAt = sText.indexOf (gsDblSp); boolean bNewRun = true; while (nFoundAt > 0) { if (bNewRun) { sText.insert (nFoundAt, gsSpaceRunStart); nFoundAt = sText.indexOf (gsDblSp); bNewRun = false; } sText.replace (nFoundAt, 1, gsNBSP); nOffset = nFoundAt + gsNBSP.length(); // Replaced comparison of two character substring because taking a // substring could generate invalid UTF-8 and cause an assert in // string.cpp. RD 08-Apr-2005. if ((sText.charAt (nOffset) != ' ') || (sText.charAt (nOffset + 1) != ' ')) { sText.insert (nOffset + 1, gsSpanEnd); bNewRun = true; } nFoundAt = sText.indexOf (gsDblSp, nOffset); } // Be sure and preserve single spaces at the beginning and end of our text // We only need to protect an starting space if it's the first one in a // paragraph or it's the only character in the span. if ((mbNewPara || sText.length() == 1) && (sText.length() > 0 && sText.charAt(0) == ' ')) { boolean bFoundNonWhiteSpace = false; for (int i = 1; i < sText.length(); i++) { if (sText.charAt (i) != ' ') { bFoundNonWhiteSpace = true; break; } } if (bFoundNonWhiteSpace) { sText.replace (0, 1, gsSpaceRunSingle); } } if (sText.charAt (sText.length()-1) == ' ') { sText.replace (sText.length() - 1, 1, gsSpaceRunSingle); } nOffset = 0; nFoundAt = sText.indexOf (gsNL); while (nFoundAt >= 0) { if (nFoundAt == sText.length() - 1) { mbTrailingBreak = true; } sText.replace (nFoundAt, 1, gsBR); nOffset = nFoundAt + gsBR.length(); nFoundAt = sText.indexOf (gsNL, nOffset); } // Replace tabs with spans that will preserve the tabs // The style attribute will be used to replace the tabs, the span content // will be discarded, but should be somewhat useful to browsers. nOffset = 0; nFoundAt = sText.indexOf (gsTab); while (nFoundAt >= 0) { // Find out how many consecutive tabs we're dealing with int nTabCount = 1; int nTabPos = nFoundAt; while (sText.charAt (++nTabPos) == '\t') { nTabCount++; } StringBuilder sTabReplace = new StringBuilder (gsTabCountStart); sTabReplace.append (Integer.toString (nTabCount)); sTabReplace.append (gsStyleEnd); if (moSpanAttr.leaderPatternEnable() && (moSpanAttr.leaderPattern() == TextAttr.LEADER_PATTERN_USE_CONTENT) && moSpanAttr.leaderContentEnable()) { MarkupXHTMLOut oMarkup = new MarkupXHTMLOut (markupAttr(), meDefaultUnits, moSpanAttr); oMarkup.mbSubordinate = true; moSpanAttr.leaderContent().markup (oMarkup, moSpanAttr, true); sTabReplace.append (oMarkup.translation()); } else { for (int i = 0; i < nTabCount; i++) { // For each tab, add 9 spaces (a pathetic replacement, but // better than nothing) sTabReplace.append (gsNineSpaces); } } sTabReplace.append (gsSpanEnd); sText.replace (nFoundAt, nTabCount, sTabReplace.toString()); nOffset = nTabPos + sTabReplace.length(); nFoundAt = sText.indexOf (gsTab, nOffset); } mbStarted = true; startPara(); // Create the necessary , and nodes. boolean bSpan = false; StringBuilder sMarkup = new StringBuilder(); String sTextDirection = getTextDirection (false); if (sTextDirection.length() > 0) { bSpan = true; sMarkup.append (gsSpanOpenTag); sMarkup.append (' '); sMarkup.append (gsTextDirectionStart); sMarkup.append (sTextDirection); sMarkup.append ('"'); } String sSpanStyle = getSpanStyle (false); if (sSpanStyle.length() > 0) { if (bSpan) { sMarkup.append (' '); sMarkup.append (gsStyleAttr); } else { sMarkup.append (gsStyleStart); } bSpan = true; sMarkup.append (sSpanStyle); sMarkup.append ('"'); } if (bSpan) { sMarkup.append (">"); } sMarkup.append (sText); //in case of conflict between adding a text decoration and turning another off if (mbNeedSpanEnd) { sMarkup.append (gsSpanEnd); } if (bSpan) { sMarkup.append (gsSpanEnd); } mpTranslation.append (sMarkup); mbNeedSpanEnd = false; } public void attr (TextAttr oAttr) { // If we haven't started our first paragraph yet, initialize our // attributes here. // // We might encounter several attribute changes before we get to any // text. So make sure we initialize the starting paragraph attributes // only once, and from then on merge attributes. if (! mbStarted) { mbStarted = true; moParaAttr = oAttr; pruneParaAttrs(); moSpanAttr.copyFrom (moParaAttr); } else { if (mbParaHasContent) { // Merge in the new attributes so they'll take effect on our next span moSpanAttr.override (oAttr); } else { // Or... if we're outside a paragraph, merge these attributes into // our paragraph attributes. moParaAttr.override (oAttr); pruneParaAttrs(); moSpanAttr.copyFrom (moParaAttr); } } } public void para () { endPara(); mbNewPara = true; mbParaHasContent = false; } public void field (TextField poField) { if (mbExpandEmbed) { // Keep the embed field from blowing away the current // translated contents. mbRequiresReset = false; poField.markup (this, null, false, true); mbRequiresReset = true; } else { startPara(); // put embeds inside paragraphs just like regular text // Watson #1043623: "Cannot apply rich text solely to runtime property text w/ floating field." // Need to add span node with the formatting options around the embedded field. // Create the necessary nodes. boolean bSpan = false; StringBuilder sMarkup = new StringBuilder(); String sSpanStyle = getSpanStyle (false); if (sSpanStyle.length() > 0) { if (bSpan) { sMarkup.append (' '); sMarkup.append (gsStyleAttr); } else { sMarkup.append (gsStyleStart); } bSpan = true; sMarkup.append (sSpanStyle); sMarkup.append (gsStyleEnd); } mpTranslation.append (sMarkup); mpTranslation.append (gsEmbedStartType); switch (poField.getEmbedType()) { case TextField.EMBEDTYPE_SOM: mpTranslation.append (gsEmbedSom); break; case TextField.EMBEDTYPE_URI: mpTranslation.append (gsEmbedUri); break; } mpTranslation.append (gsEmbedAddMode); switch (poField.getEmbedMode()) { case TextField.EMBED_RAW: mpTranslation.append (gsEmbedRaw); break; case TextField.EMBED_FORMATTED: mpTranslation.append (gsEmbedFormatted); break; } mpTranslation.append (gsEmbedAddEmbed); mpTranslation.append (poField.getExpression()); mpTranslation.append (gsStyleEndTag); if (bSpan) { mpTranslation.append (gsSpanEnd); } } } public boolean issueFirstPara () { return true; } public void expandEmbed (boolean bExpand) { mbExpandEmbed = bExpand; } private void startDoc () { mbNewPara = ! mbSubordinate; mbStarted = false; mbParaHasContent = false; mpTranslation.setLength(0); if (! mbSubordinate) { mpTranslation.append (gsBodyStart1); mpTranslation.append (Version.getImplementation()); mpTranslation.append (gsBodyStart2); } } private void addAttr (StringBuilder sStyle, int eCommand, int eParameter) { if ((sStyle.length() > 0) && (sStyle.charAt (sStyle.length()-1) != ';')) { sStyle.append (';'); } sStyle.append (getAttr (eCommand)); if (eParameter != MarkupAttr.MARKUP_UNKNOWN) { sStyle.append (getAttr (eParameter)); } } private void addAttr (StringBuilder sStyle, int eCommand) { addAttr (sStyle, eCommand, MarkupAttr.MARKUP_UNKNOWN); } private String getSpanStyle (boolean bIncludeAll) { StringBuilder sStyle = new StringBuilder(); // Get a text attribute which specifies only those attributes that are // different than the paragraph attributes. TextAttr oAttr = new TextAttr (moSpanAttr); // Check whether we want to skip those attributes already specified // by the paragraph attributes. if (bIncludeAll) { if (mbIncludeAmbientAttrs) { // Display all ambient attributes that don't match CSS defaults TextAttr oAmbient = new TextAttr (moAmbientAttr); oAmbient.dropSame (moCSSDefault); oAttr.addDisabled (oAmbient); } else { // Don't include ambient attributes. reconcileAttrs (moAmbientAttr, oAttr); } } else { // Set up the paragraph attributes so they represent the ambient settings // as well so that spans don't re-specify the ambient settings. TextAttr oParaAttr = new TextAttr (moParaAttr); if (!mbIncludeAmbientAttrs) { oParaAttr.addDisabled (moAmbientAttr); } reconcileAttrs (oParaAttr, oAttr); } if (oAttr.baselineShiftEnable()) { addAttr (sStyle, MarkupAttr.MARKUP_JUSTIFY_VERT); sStyle.append (oAttr.baselineShift().getString (false)); } if (oAttr.typefaceEnable()) { boolean bWhitespace = false; addAttr (sStyle, MarkupAttr.MARKUP_FONT_NAME); if (oAttr.typeface().indexOf (' ') > 0) { bWhitespace = true; } if (bWhitespace) { sStyle.append ('\''); } sStyle.append (oAttr.typeface()); if (bWhitespace) { sStyle.append ('\''); } } if (oAttr.sizeEnable()) { // Write out the size only if it's not zero (or very close to zero) // UnitSpan oSize = oAttr.size(); // Vantive 637378: Rounding to 0.5pt too coarse; changed to 0.05pt. Not // clear why rounding is here in the first place, but deemed safer to // leave it in than remove it altogether. RD 14-Apr-2004. if (mbRoundTextSize) { oSize = oSize.round (ROUND_SIZE); } UnitSpan oCompareSize = bIncludeAll ? moAmbientAttr.size() : moParaAttr.size(); if (mbRoundTextSize) { oCompareSize = oCompareSize.round (ROUND_SIZE); // TODO: C++ implementation rounds this differently } if ((oSize.value() != 0) && (bIncludeAll || (! oSize.equals (oCompareSize)))) { addAttr (sStyle, MarkupAttr.MARKUP_FONT_SIZE); addUnitSpan (oSize, sStyle, false, true); } } if (oAttr.horizontalScaleEnable()) { addAttr (sStyle, MarkupAttr.MARKUP_FONT_HORIZONTAL_SCALE); sStyle.append (TextAttr.formatPercent (oAttr.horizontalScale())); } if (oAttr.verticalScaleEnable()) { addAttr (sStyle, MarkupAttr.MARKUP_FONT_VERTICAL_SCALE); sStyle.append (TextAttr.formatPercent (oAttr.verticalScale())); } // Colour if (oAttr.colourEnable()) { // Can't assume that all clients use a scale of 255. GFXColour oColour = oAttr.colour(); oColour = oColour.scale (255); addAttr (sStyle, MarkupAttr.MARKUP_COLOUR); sStyle.append ('#'); addColour (oColour.r(), sStyle); addColour (oColour.g(), sStyle); addColour (oColour.b(), sStyle); } if (oAttr.weightEnable()) { addAttr (sStyle, MarkupAttr.MARKUP_FONT_WEIGHT); if (oAttr.weight() >= FontInfo.WEIGHT_BOLD) { sStyle.append (getAttr (MarkupAttr.MARKUP_FONT_BOLD)); } else { sStyle.append (getAttr (MarkupAttr.MARKUP_NORMAL)); } } // Italic if (oAttr.italicEnable()) { addAttr (sStyle, MarkupAttr.MARKUP_FONT_STYLE); if (oAttr.italic()) { sStyle.append (getAttr (MarkupAttr.MARKUP_FONT_ITALIC)); } else { sStyle.append (getAttr (MarkupAttr.MARKUP_NORMAL)); } } // Underline and strikeout must be written out together, since they // use the same text-decoration attribute. if (oAttr.underlineEnable() || oAttr.strikeoutEnable()) { addAttr (sStyle, MarkupAttr.MARKUP_TEXT_DECORATION); GFXDecorationInfo underline = GFXTextAttr.extractDecoration (oAttr.underline()); GFXDecorationInfo lineThrough = GFXTextAttr.extractDecoration (oAttr.strikeout()); if ((underline == null) && (lineThrough == null)) { sStyle.append (getAttr (MarkupAttr.MARKUP_NONE)); } else { if (underline != null) { if (underline.mCount == 1) { if (underline.mType == GFXDecorationInfo.DECORATE_WORD) { sStyle.append (getAttr (MarkupAttr.MARKUP_UNDERLINE_WORD)); } else { sStyle.append (getAttr (MarkupAttr.MARKUP_UNDERLINE)); } } else { if (underline.mType == GFXDecorationInfo.DECORATE_WORD) { sStyle.append (getAttr (MarkupAttr.MARKUP_UNDERLINE_DOUBLE)); sStyle.append (' '); sStyle.append (getAttr (MarkupAttr.MARKUP_UNDERLINE_WORD)); } else { sStyle.append (getAttr (MarkupAttr.MARKUP_UNDERLINE_DOUBLE)); } } } if (lineThrough != null) { if (underline != null) { sStyle.append (' '); } sStyle.append (getAttr (MarkupAttr.MARKUP_STRIKEOUT)); } } } if (oAttr.digits() != TextAttr.DIGITS_LOCALE) { int eDigits = (oAttr.digits() == TextAttr.DIGITS_INDIC) ? MarkupAttr.MARKUP_DIGITS_INDIC : MarkupAttr.MARKUP_DIGITS_ARABIC; addAttr (sStyle, MarkupAttr.MARKUP_DIGITS, eDigits); } if (oAttr.kerningEnable()) { addAttr (sStyle, MarkupAttr.MARKUP_KERNING_MODE, oAttr.kerning() ? MarkupAttr.MARKUP_NONE : MarkupAttr.MARKUP_KERN_PAIR); } // Spacing if (oAttr.charSpacingEnable() && (oAttr.charSpacing().getLengthValue() != 0)) { addAttr (sStyle, MarkupAttr.MARKUP_CHAR_SPACING); addMeasurement (oAttr, oAttr.charSpacing(), sStyle, false, true); } if (oAttr.wordSpacingEnable() && (oAttr.wordSpacing().getLengthValue() != 0)) { addAttr (sStyle, MarkupAttr.MARKUP_WORD_SPACING); addMeasurement (oAttr, oAttr.wordSpacing(), sStyle, false, true); } // Hyphenation if (oAttr.hyphLevel() == TextAttr.HYPHEN_OFF) { addAttr (sStyle, MarkupAttr.MARKUP_HYPHENATION, MarkupAttr.MARKUP_NONE); } else { addAttr (sStyle, MarkupAttr.MARKUP_HYPHENATION, MarkupAttr.MARKUP_AUTO); switch (oAttr.hyphLevel()) { case TextAttr.HYPHEN_PREFERRED: addAttr (sStyle, MarkupAttr.MARKUP_HYPHENATION_LEVEL, MarkupAttr.MARKUP_HYPHENATION_PREFERRED); break; case TextAttr.HYPHEN_ALL: addAttr (sStyle, MarkupAttr.MARKUP_HYPHENATION_LEVEL, MarkupAttr.MARKUP_ALL); break; } } if (oAttr.hyphMinWordEnable() || oAttr.hyphMinPrefixEnable() || oAttr.hyphMinSuffixEnable()) { StringBuilder values = new StringBuilder(); values.append ('\''); values.append (oAttr.hyphMinWord()); values.append (' '); values.append (oAttr.hyphMinPrefix()); values.append (' '); values.append (oAttr.hyphMinSuffix()); values.append ('\''); addAttr (sStyle, MarkupAttr.MARKUP_HYPHENATION_LENGTH); sStyle.append (values); } if (oAttr.hyphSuppressAcronymsEnable()) { addAttr (sStyle, MarkupAttr.MARKUP_HYPHENATION_ACRONYMS, oAttr.hyphSuppressAcronyms() ? MarkupAttr.MARKUP_NONE : MarkupAttr.MARKUP_AUTO); } if (oAttr.hyphSuppressNamesEnable()) { addAttr (sStyle, MarkupAttr.MARKUP_HYPHENATION_NAMES, oAttr.hyphSuppressNames() ? MarkupAttr.MARKUP_NONE : MarkupAttr.MARKUP_AUTO); } if (oAttr.leaderAlignEnable()) { addAttr (sStyle, MarkupAttr.MARKUP_LEADER_ALIGN, (oAttr.leaderAlign() == TextAttr.LEADER_ALIGN_PAGE) ? MarkupAttr.MARKUP_LEADER_ALIGN_PAGE : MarkupAttr.MARKUP_NONE); } if (oAttr.leaderPatternEnable()) { int ePattern = MarkupAttr.MARKUP_LEADER_PATTERN_SPACE; switch (oAttr.leaderPattern()) { case TextAttr.LEADER_PATTERN_RULE: ePattern = MarkupAttr.MARKUP_LEADER_PATTERN_RULE; break; case TextAttr.LEADER_PATTERN_DOTS: ePattern = MarkupAttr.MARKUP_LEADER_PATTERN_DOTS; break; case TextAttr.LEADER_PATTERN_USE_CONTENT: ePattern = MarkupAttr.MARKUP_LEADER_PATTERN_USE_CONTENT; break; } addAttr (sStyle, MarkupAttr.MARKUP_LEADER_PATTERN, ePattern); } if (oAttr.leaderPatternWidthEnable() && (! oAttr.leaderPatternWidth().isZero())) { addAttr (sStyle, MarkupAttr.MARKUP_LEADER_PATTERN_WIDTH); addMeasurement (oAttr, oAttr.leaderPatternWidth(), sStyle); } if (oAttr.ruleStyleEnable()) { int eStyle = MarkupAttr.MARKUP_SOLID; switch (oAttr.ruleStyle()) { case TextAttr.RULE_STYLE_NONE: eStyle = MarkupAttr.MARKUP_NONE; break; case TextAttr.RULE_STYLE_DOTTED: eStyle = MarkupAttr.MARKUP_RULE_STYLE_DOTTED; break; case TextAttr.RULE_STYLE_DASHED: eStyle = MarkupAttr.MARKUP_RULE_STYLE_DASHED; break; } addAttr (sStyle, MarkupAttr.MARKUP_RULE_STYLE, eStyle); } if (oAttr.ruleThicknessEnable() && (! oAttr.ruleThickness().isZero())) { addAttr (sStyle, MarkupAttr.MARKUP_RULE_STYLE); addMeasurement (oAttr, oAttr.ruleThickness(), sStyle); } return sStyle.toString(); } private String getParaStyle () { StringBuilder sMarkup = new StringBuilder(); TextAttr oAttr = new TextAttr (moParaAttr); TextAttr oAmbient = new TextAttr (); if (mbIncludeAmbientAttrs) { // Display all ambient attributes that don't match CSS defaults oAmbient.copyFrom (moAmbientAttr); oAmbient.dropSame (moCSSDefault); oAttr.addDisabled (oAmbient); } else { // Don't include ambient. reconcileAttrs (moAmbientAttr, oAttr); } boolean bWriteMarginL = oAttr.marginLEnable(); boolean bWriteMarginR = oAttr.marginREnable(); boolean bWriteMarginT = oAttr.spaceBeforeEnable(); boolean bWriteMarginB = oAttr.spaceAfterEnable(); // Check if we can write out our margins as one, rather than each // individually if (bWriteMarginL && bWriteMarginR && bWriteMarginT && bWriteMarginB && oAttr.spaceBefore().equals (oAttr.marginR()) && oAttr.spaceBefore().equals (oAttr.spaceAfter()) && oAttr.spaceBefore().equals (oAttr.marginL())) { addAttr (sMarkup, MarkupAttr.MARKUP_MARGIN); addMeasurement (oAttr, oAttr.spaceBefore(), sMarkup); } else { // Write the margins individually if (bWriteMarginT) { addAttr (sMarkup, MarkupAttr.MARKUP_SPACE_BEFORE); addMeasurement (oAttr, oAttr.spaceBefore(), sMarkup); } if (bWriteMarginR) { addAttr (sMarkup, MarkupAttr.MARKUP_INDENT_RIGHT); addMeasurement (oAttr, oAttr.marginR(), sMarkup); } if (bWriteMarginB) { addAttr (sMarkup, MarkupAttr.MARKUP_SPACE_AFTER); addMeasurement (oAttr, oAttr.spaceAfter(), sMarkup); } if (bWriteMarginL) { addAttr (sMarkup, MarkupAttr.MARKUP_INDENT_LEFT); addMeasurement (oAttr, oAttr.marginL(), sMarkup); } } if (oAttr.specialEnable()) { addAttr (sMarkup, MarkupAttr.MARKUP_INDENT_FIRST_LINE); addMeasurement (oAttr, oAttr.special(), sMarkup); } if (oAttr.spacingEnable()) { // Spacing often comes to us as zero (single spacing) // Never output zero, as it seldom makes sense to have // overlapping lines. // FF99 designer doesn't currently support changing lineheight // inside a control, so we don't have to worry about the scenario // where we might switch from e.g. double to single spacing. if (oAttr.spacing().getLengthValue() != 0) { addAttr (sMarkup, MarkupAttr.MARKUP_LINE_HEIGHT); addMeasurement (oAttr, oAttr.spacing(), sMarkup, false, true); } } //justification if (oAttr.justifyVEnable()) { switch(oAttr.justifyV()) { case TextAttr.JUST_V_TOP: addAttr (sMarkup, MarkupAttr.MARKUP_TEXT_VALIGN, MarkupAttr.MARKUP_TEXT_VALIGN_TOP); break; case TextAttr.JUST_V_MIDDLE: addAttr (sMarkup, MarkupAttr.MARKUP_TEXT_VALIGN, MarkupAttr.MARKUP_TEXT_VALIGN_MIDDLE); break; case TextAttr.JUST_V_BOTTOM: addAttr (sMarkup, MarkupAttr.MARKUP_TEXT_VALIGN, MarkupAttr.MARKUP_TEXT_VALIGN_BOTTOM); break; } } if (oAttr.justifyHEnable()) { switch(oAttr.justifyH()) { case TextAttr.JUST_H_LEFT: addAttr (sMarkup, MarkupAttr.MARKUP_JUSTIFY, MarkupAttr.MARKUP_XHTML_LEFT); break; case TextAttr.JUST_H_CENTRE: addAttr (sMarkup, MarkupAttr.MARKUP_JUSTIFY, MarkupAttr.MARKUP_XHTML_CENTER); break; case TextAttr.JUST_H_RIGHT: addAttr (sMarkup, MarkupAttr.MARKUP_JUSTIFY, MarkupAttr.MARKUP_XHTML_RIGHT); break; case TextAttr.JUST_H_SPREAD: addAttr (sMarkup, MarkupAttr.MARKUP_JUSTIFY, MarkupAttr.MARKUP_JUSTIFY_SPREAD); break; case TextAttr.JUST_H_SPREAD_ALL: addAttr (sMarkup, MarkupAttr.MARKUP_JUSTIFY, MarkupAttr.MARKUP_JUSTIFY_SPREAD_ALL); break; case TextAttr.JUST_H_COMB_LEFT: addAttr (sMarkup, MarkupAttr.MARKUP_JUSTIFY, MarkupAttr.MARKUP_JUSTIFY_COMB_LEFT); break; case TextAttr.JUST_H_COMB_CENTRE: addAttr (sMarkup, MarkupAttr.MARKUP_JUSTIFY, MarkupAttr.MARKUP_JUSTIFY_COMB_CENTER); break; case TextAttr.JUST_H_COMB_RIGHT: addAttr (sMarkup, MarkupAttr.MARKUP_JUSTIFY, MarkupAttr.MARKUP_JUSTIFY_COMB_RIGHT); break; } } //tabs if (oAttr.tabsEnable()) { addAttr (sMarkup, MarkupAttr.MARKUP_TAB_DEFAULT); addUnitSpan (oAttr.tabs().noUniform() ? UnitSpan.ZERO : oAttr.tabs().uniform().tabStop(), sMarkup, false); if (oAttr.tabs().size() > 0) { addAttr (sMarkup, MarkupAttr.MARKUP_TAB_POSITION); } for (int i = 1; i <= oAttr.tabs().size(); i++) { if (i != 1) { sMarkup.append (' '); } //get the stop TextTab oTab = oAttr.tabs().tabAt (i); UnitSpan oStop = oTab.tabStop(); String sAlign = ""; switch (oTab.tabType()) { case TextTab.TYPE_LEFT: case TextTab.TYPE_ALIGN_AFTER: sAlign = getAttr (MarkupAttr.MARKUP_XHTML_LEFT); break; case TextTab.TYPE_CENTRE: sAlign = getAttr (MarkupAttr.MARKUP_XHTML_CENTER); break; case TextTab.TYPE_RIGHT: case TextTab.TYPE_ALIGN_BEFORE: sAlign = getAttr (MarkupAttr.MARKUP_XHTML_RIGHT); break; case TextTab.TYPE_DECIMAL: sAlign = getAttr (MarkupAttr.MARKUP_TAB_ALIGN_DECIMAL); sMarkup.append (sAlign); sMarkup.append (' '); addUnitSpan (oStop, sMarkup, false); } } } if (oAttr.ligatureEnable()) { addAttr (sMarkup, MarkupAttr.MARKUP_LIGATURE, (oAttr.ligature() == TextAttr.LIGATURE_COMMON) ? MarkupAttr.MARKUP_LIGATURE_COMMON : MarkupAttr.MARKUP_LIGATURE_MINIMUM); } return sMarkup.toString(); } private String getTextDirection (boolean bIsPara) { int eAttrDirection = TextAttr.DIRECTION_NEUTRAL; if (bIsPara) { eAttrDirection = moSpanAttr.paraDirection(); } if (eAttrDirection == TextAttr.DIRECTION_NEUTRAL) { eAttrDirection = moSpanAttr.direction(); } if (eAttrDirection == TextAttr.DIRECTION_NEUTRAL) { return ""; } int eMkDirection = (eAttrDirection == TextAttr.DIRECTION_RTL) ? MarkupAttr.MARKUP_DIRECTION_RTL : MarkupAttr.MARKUP_DIRECTION_LTR; return getAttr (eMkDirection); } private void addColour (int lCol, StringBuilder sColour) { String s = Integer.toHexString (lCol + 0x100); sColour.append(s, 1, s.length()); } private String getAttr (int eTag) { return markupAttr().lookup (eTag); } private void addUnitSpan (UnitSpan oUnits, StringBuilder sAttrs, boolean bUseDefaultUnits) { addUnitSpan (oUnits, sAttrs, bUseDefaultUnits, false); } private void addUnitSpan (UnitSpan oUnits, StringBuilder sAttrs, boolean bUseDefaultUnits, boolean bUsePTs) { // Add the value of this unitspan to the attribute string. int eUnits = oUnits.units(); if (bUseDefaultUnits && (eUnits != meDefaultUnits)) { oUnits = new UnitSpan (meDefaultUnits, UnitSpan.convertUnit (meDefaultUnits, eUnits, oUnits.value())); } else if (bUseDefaultUnits && (eUnits != UnitSpan.POINTS_1K)) { oUnits = new UnitSpan (UnitSpan.POINTS_1K, UnitSpan.convertUnit (UnitSpan.POINTS_1K, eUnits, oUnits.value())); } sAttrs.append (oUnits.text (3, true, false)); } private void addMeasurement (TextAttr oAttr, TextMeasurement oMeasurement, StringBuilder sAttrs, boolean bUseDefaultUnits, boolean bUsePTs) { // Add the value of this unitspan to the attribute string. if (bUseDefaultUnits || bUsePTs) { addUnitSpan (oAttr.flattenMeasurement (oMeasurement), sAttrs, bUseDefaultUnits, bUsePTs); } else { sAttrs.append (oMeasurement.toString (3)); } } private void addMeasurement (TextAttr oAttr, TextMeasurement oMeasurement, StringBuilder sAttrs, boolean bUseDefaultUnits) { addMeasurement (oAttr, oMeasurement, sAttrs, bUseDefaultUnits, false); } private void addMeasurement (TextAttr oAttr, TextMeasurement oMeasurement, StringBuilder sAttrs) { addMeasurement (oAttr, oMeasurement, sAttrs, true); } private void pruneParaAttrs () { reconcileAttrs (moAmbientAttr, moParaAttr); } private void startPara () { if (! mbNewPara) { return; } StringBuilder sMarkup = new StringBuilder(); sMarkup.append (gsParaOpenTag); String sTextDirection = getTextDirection (true); if (sTextDirection.length() > 0) { sMarkup.append (' '); sMarkup.append (gsTextDirectionStart); sMarkup.append (sTextDirection); sMarkup.append (gsTextDirectionEnd); } StringBuilder sStyle = new StringBuilder(); sStyle.append (getParaStyle()); sStyle.append (getSpanStyle (true)); if (sStyle.length() == 0) { sMarkup.append (gsTagEnd); } else { // add a carriage return so that our xhtml output lines don't get too long if (mbBreakupOutput) { sMarkup.append ('\n'); } else { sMarkup.append (' '); } sMarkup.append (gsStyleAttr); sMarkup.append (sStyle); sMarkup.append (gsStyleEnd); } mpTranslation.append (sMarkup); mbNewPara = false; mbParaHasContent = true; // because the caller is about to add real content } private void endPara () { // if the previous paragraph didn't have content, start one and add a // single space run, to force a visual vertical space if (! mbParaHasContent) { startPara(); mpTranslation.append (gsSpaceRunSingle); } if (mbTrailingBreak) { mpTranslation.append (gsBR); } mbTrailingBreak = false; mpTranslation.append (gsParaEnd); // inherit the previous attributes for our next paragraph. moParaAttr.override (moSpanAttr); moSpanAttr.copyFrom (moParaAttr); } private static void reconcileAttrs (TextAttr oDrop, TextAttr oAttr) { boolean bRestoreDecoration = false; int eUnderline = GFXDecorationInfo.DECORATE_NONE; int eStrikeout = GFXDecorationInfo.DECORATE_NONE; if (oDrop.underlineEnable()) { if (oAttr.underlineEnable()) { eUnderline = oAttr.underline(); if (eUnderline != oDrop.underline()) { bRestoreDecoration = true; } } else { eUnderline = oDrop.underline(); } } if (oDrop.strikeoutEnable()) { if (oAttr.strikeoutEnable()) { eStrikeout = oAttr.strikeout(); if (eStrikeout != oDrop.strikeout()) { bRestoreDecoration = true; } } else { eStrikeout = oDrop.strikeout(); } } oAttr.dropSame (oDrop); if (bRestoreDecoration) { oAttr.underline (eUnderline); oAttr.strikeout (eStrikeout); } } }