JDK14/Java14源码在线阅读

/*
 * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package javax.swing.text.rtf;

import java.lang.*;
import java.util.*;
import java.awt.Color;
import java.awt.Font;
import java.io.OutputStream;
import java.io.IOException;

import javax.swing.text.*;

/**
 * Generates an RTF output stream (java.io.OutputStream) from rich text
 * (handed off through a series of LTTextAcceptor calls).  Can be used to
 * generate RTF from any object which knows how to write to a text acceptor
 * (e.g., LTAttributedText and LTRTFFilter).
 *
 * <p>Note that this is a lossy conversion since RTF's model of
 * text does not exactly correspond with LightText's.
 *
 * @see java.io.OutputStream
 */
class RTFGenerator extends Object
{
    /* These dictionaries map Colors, font names, or Style objects
       to Integers */
    Dictionary<Object, Integer> colorTable;
    int colorCount;
    Dictionary<String, Integer> fontTable;
    int fontCount;
    Dictionary<AttributeSet, Integer> styleTable;
    int styleCount;

    /* where all the text is going */
    OutputStream outputStream;

    boolean afterKeyword;

    MutableAttributeSet outputAttributes;

    /* the value of the last \\ucN keyword emitted */
    int unicodeCount;

    /* for efficiency's sake (ha) */
    private Segment workingSegment;

    int[] outputConversion;

    /** The default color, used for text without an explicit color
     *  attribute. */
    public static final Color defaultRTFColor = Color.black;

    public static final float defaultFontSize = 12f;

    public static final String defaultFontFamily = "Helvetica";

    /* constants so we can avoid allocating objects in inner loops */
    private static final Object MagicToken;

    /* An array of character-keyword pairs. This could be done
       as a dictionary (and lookup would be quicker), but that
       would require allocating an object for every character
       written (slow!). */
    static class CharacterKeywordPair
      { public char character; public String keyword; }
    protected static CharacterKeywordPair[] textKeywords;

    static {
        MagicToken = new Object();

        Dictionary<String, String> textKeywordDictionary = RTFReader.textKeywords;
        Enumeration<String> keys = textKeywordDictionary.keys();
        Vector<CharacterKeywordPair> tempPairs = new Vector<CharacterKeywordPair>();
        while(keys.hasMoreElements()) {
            CharacterKeywordPair pair = new CharacterKeywordPair();
            pair.keyword = keys.nextElement();
            pair.character = textKeywordDictionary.get(pair.keyword).charAt(0);
            tempPairs.addElement(pair);
        }
        textKeywords = new CharacterKeywordPair[tempPairs.size()];
        tempPairs.copyInto(textKeywords);
    }

    static final char[] hexdigits = { '0', '1', '2', '3', '4', '5', '6', '7',
                                      '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

public static void writeDocument(Document d, OutputStream to)
    throws IOException
{
    RTFGenerator gen = new RTFGenerator(to);
    Element root = d.getDefaultRootElement();

    gen.examineElement(root);
    gen.writeRTFHeader();
    gen.writeDocumentProperties(d);

    /* TODO this assumes a particular element structure; is there
       a way to iterate more generically ? */
    int max = root.getElementCount();
    for(int idx = 0; idx < max; idx++)
        gen.writeParagraphElement(root.getElement(idx));

    gen.writeRTFTrailer();
}

public RTFGenerator(OutputStream to)
{
    colorTable = new Hashtable<Object, Integer>();
    colorTable.put(defaultRTFColor, Integer.valueOf(0));
    colorCount = 1;

    fontTable = new Hashtable<String, Integer>();
    fontCount = 0;

    styleTable = new Hashtable<AttributeSet, Integer>();
    /* TODO: put default style in style table */
    styleCount = 0;

    workingSegment = new Segment();

    outputStream = to;

    unicodeCount = 1;
}

public void examineElement(Element el)
{
    AttributeSet a = el.getAttributes();
    String fontName;
    Object foregroundColor, backgroundColor;

    tallyStyles(a);

    if (a != null) {
        /* TODO: default color must be color 0! */

        foregroundColor = StyleConstants.getForeground(a);
        if (foregroundColor != null &&
            colorTable.get(foregroundColor) == null) {
            colorTable.put(foregroundColor, colorCount);
            colorCount ++;
        }

        backgroundColor = a.getAttribute(StyleConstants.Background);
        if (backgroundColor != null &&
            colorTable.get(backgroundColor) == null) {
            colorTable.put(backgroundColor, colorCount);
            colorCount ++;
        }

        fontName = StyleConstants.getFontFamily(a);

        if (fontName == null)
            fontName = defaultFontFamily;

        if (fontName != null &&
            fontTable.get(fontName) == null) {
            fontTable.put(fontName, fontCount);
            fontCount ++;
        }
    }

    int el_count = el.getElementCount();
    for(int el_idx = 0; el_idx < el_count; el_idx ++) {
        examineElement(el.getElement(el_idx));
    }
}

private void tallyStyles(AttributeSet a) {
    while (a != null) {
        if (a instanceof Style) {
            Integer aNum = styleTable.get(a);
            if (aNum == null) {
                styleCount = styleCount + 1;
                aNum = styleCount;
                styleTable.put(a, aNum);
            }
        }
        a = a.getResolveParent();
    }
}

private Style findStyle(AttributeSet a)
{
    while(a != null) {
        if (a instanceof Style) {
            Object aNum = styleTable.get(a);
            if (aNum != null)
                return (Style)a;
        }
        a = a.getResolveParent();
    }
    return null;
}

private Integer findStyleNumber(AttributeSet a, String domain)
{
    while(a != null) {
        if (a instanceof Style) {
            Integer aNum = styleTable.get(a);
            if (aNum != null) {
                if (domain == null ||
                    domain.equals(a.getAttribute(Constants.StyleType)))
                    return aNum;
            }

        }
        a = a.getResolveParent();
    }
    return null;
}

private static Object attrDiff(MutableAttributeSet oldAttrs,
                               AttributeSet newAttrs,
                               Object key,
                               Object dfl)
{
    Object oldValue, newValue;

    oldValue = oldAttrs.getAttribute(key);
    newValue = newAttrs.getAttribute(key);

    if (newValue == oldValue)
        return null;
    if (newValue == null) {
        oldAttrs.removeAttribute(key);
        if (dfl != null && !dfl.equals(oldValue))
            return dfl;
        else
            return null;
    }
    if (oldValue == null ||
        !equalArraysOK(oldValue, newValue)) {
        oldAttrs.addAttribute(key, newValue);
        return newValue;
    }
    return null;
}

private static boolean equalArraysOK(Object a, Object b)
{
    Object[] aa, bb;
    if (a == b)
        return true;
    if (a == null || b == null)
        return false;
    if (a.equals(b))
        return true;
    if (!(a.getClass().isArray() && b.getClass().isArray()))
        return false;
    aa = (Object[])a;
    bb = (Object[])b;
    if (aa.length != bb.length)
        return false;

    int i;
    int l = aa.length;
    for(i = 0; i < l; i++) {
        if (!equalArraysOK(aa[i], bb[i]))
            return false;
    }

    return true;
}

/* Writes a line break to the output file, for ease in debugging */
public void writeLineBreak()
    throws IOException
{
    writeRawString("\n");
    afterKeyword = false;
}


public void writeRTFHeader()
    throws IOException
{
    int index;

    /* TODO: Should the writer attempt to examine the text it's writing
       and pick a character set which will most compactly represent the
       document? (currently the writer always uses the ansi character
       set, which is roughly ISO-8859 Latin-1, and uses Unicode escapes
       for all other characters. However Unicode is a relatively
       recent addition to RTF, and not all readers will understand it.) */
    writeBegingroup();
    writeControlWord("rtf", 1);
    writeControlWord("ansi");
    outputConversion = outputConversionForName("ansi");
    writeLineBreak();

    /* write font table */
    String[] sortedFontTable = new String[fontCount];
    Enumeration<String> fonts = fontTable.keys();
    String font;
    while(fonts.hasMoreElements()) {
        font = fonts.nextElement();
        Integer num = fontTable.get(font);
        sortedFontTable[num.intValue()] = font;
    }
    writeBegingroup();
    writeControlWord("fonttbl");
    for(index = 0; index < fontCount; index ++) {
        writeControlWord("f", index);
        writeControlWord("fnil");  /* TODO: supply correct font style */
        writeText(sortedFontTable[index]);
        writeText(";");
    }
    writeEndgroup();
    writeLineBreak();

    /* write color table */
    if (colorCount > 1) {
        Color[] sortedColorTable = new Color[colorCount];
        Enumeration<Object> colors = colorTable.keys();
        Color color;
        while(colors.hasMoreElements()) {
            color = (Color)colors.nextElement();
            Integer num = colorTable.get(color);
            sortedColorTable[num.intValue()] = color;
        }
        writeBegingroup();
        writeControlWord("colortbl");
        for(index = 0; index < colorCount; index ++) {
            color = sortedColorTable[index];
            if (color != null) {
                writeControlWord("red", color.getRed());
                writeControlWord("green", color.getGreen());
                writeControlWord("blue", color.getBlue());
            }
            writeRawString(";");
        }
        writeEndgroup();
        writeLineBreak();
    }

    /* write the style sheet */
    if (styleCount > 1) {
        writeBegingroup();
        writeControlWord("stylesheet");
        Enumeration<AttributeSet> styles = styleTable.keys();
        while(styles.hasMoreElements()) {
            Style style = (Style)styles.nextElement();
            int styleNumber = styleTable.get(style).intValue();
            writeBegingroup();
            String styleType = (String)style.getAttribute(Constants.StyleType);
            if (styleType == null)
                styleType = Constants.STParagraph;
            if (styleType.equals(Constants.STCharacter)) {
                writeControlWord("*");
                writeControlWord("cs", styleNumber);
            } else if(styleType.equals(Constants.STSection)) {
                writeControlWord("*");
                writeControlWord("ds", styleNumber);
            } else {
                writeControlWord("s", styleNumber);
            }

            AttributeSet basis = style.getResolveParent();
            MutableAttributeSet goat;
            if (basis == null) {
                goat = new SimpleAttributeSet();
            } else {
                goat = new SimpleAttributeSet(basis);
            }

            updateSectionAttributes(goat, style, false);
            updateParagraphAttributes(goat, style, false);
            updateCharacterAttributes(goat, style, false);

            basis = style.getResolveParent();
            if (basis != null && basis instanceof Style) {
                Integer basedOn = styleTable.get(basis);
                if (basedOn != null) {
                    writeControlWord("sbasedon", basedOn.intValue());
                }
            }

            Style nextStyle = (Style)style.getAttribute(Constants.StyleNext);
            if (nextStyle != null) {
                Integer nextNum = styleTable.get(nextStyle);
                if (nextNum != null) {
                    writeControlWord("snext", nextNum.intValue());
                }
            }

            Boolean hidden = (Boolean)style.getAttribute(Constants.StyleHidden);
            if (hidden != null && hidden.booleanValue())
                writeControlWord("shidden");

            Boolean additive = (Boolean)style.getAttribute(Constants.StyleAdditive);
            if (additive != null && additive.booleanValue())
                writeControlWord("additive");


            writeText(style.getName());
            writeText(";");
            writeEndgroup();
        }
        writeEndgroup();
        writeLineBreak();
    }

    outputAttributes = new SimpleAttributeSet();
}

void writeDocumentProperties(Document doc)
    throws IOException
{
    /* Write the document properties */
    int i;
    boolean wroteSomething = false;

    for(i = 0; i < RTFAttributes.attributes.length; i++) {
        RTFAttribute attr = RTFAttributes.attributes[i];
        if (attr.domain() != RTFAttribute.D_DOCUMENT)
            continue;
        Object prop = doc.getProperty(attr.swingName());
        boolean ok = attr.writeValue(prop, this, false);
        if (ok)
            wroteSomething = true;
    }

    if (wroteSomething)
        writeLineBreak();
}

public void writeRTFTrailer()
    throws IOException
{
    writeEndgroup();
    writeLineBreak();
}

protected void checkNumericControlWord(MutableAttributeSet currentAttributes,
                                       AttributeSet newAttributes,
                                       Object attrName,
                                       String controlWord,
                                       float dflt, float scale)
    throws IOException
{
    Object parm;

    if ((parm = attrDiff(currentAttributes, newAttributes,
                         attrName, MagicToken)) != null) {
        float targ;
        if (parm == MagicToken)
            targ = dflt;
        else
            targ = ((Number)parm).floatValue();
        writeControlWord(controlWord, Math.round(targ * scale));
    }
}

protected void checkControlWord(MutableAttributeSet currentAttributes,
                                AttributeSet newAttributes,
                                RTFAttribute word)
    throws IOException
{
    Object parm;

    if ((parm = attrDiff(currentAttributes, newAttributes,
                         word.swingName(), MagicToken)) != null) {
        if (parm == MagicToken)
            parm = null;
        if (!word.writeValue(parm, this, true) &&
                word instanceof RTFAttributes.AssertiveAttribute ) {
            currentAttributes.removeAttribute(word.swingName());
        }
    }
}

protected void checkControlWords(MutableAttributeSet currentAttributes,
                                 AttributeSet newAttributes,
                                 RTFAttribute[] words,
                                 int domain)
    throws IOException
{
    int wordIndex;
    int wordCount = words.length;
    for(wordIndex = 0; wordIndex < wordCount; wordIndex++) {
        RTFAttribute attr = words[wordIndex];
        if (attr.domain() == domain)
            checkControlWord(currentAttributes, newAttributes, attr);
    }
}

void updateSectionAttributes(MutableAttributeSet current,
                             AttributeSet newAttributes,
                             boolean emitStyleChanges)
    throws IOException
{
    if (emitStyleChanges) {
        Object oldStyle = current.getAttribute("sectionStyle");
        Object newStyle = findStyleNumber(newAttributes, Constants.STSection);
        if (oldStyle != newStyle) {
            if (oldStyle != null) {
                resetSectionAttributes(current);
            }
            if (newStyle != null) {
                writeControlWord("ds", ((Integer)newStyle).intValue());
                current.addAttribute("sectionStyle", newStyle);
            } else {
                current.removeAttribute("sectionStyle");
            }
        }
    }

    checkControlWords(current, newAttributes,
                      RTFAttributes.attributes, RTFAttribute.D_SECTION);
}

protected void resetSectionAttributes(MutableAttributeSet currentAttributes)
    throws IOException
{
    writeControlWord("sectd");

    int wordIndex;
    int wordCount = RTFAttributes.attributes.length;
    for(wordIndex = 0; wordIndex < wordCount; wordIndex++) {
        RTFAttribute attr = RTFAttributes.attributes[wordIndex];
        if (attr.domain() == RTFAttribute.D_SECTION)
            attr.setDefault(currentAttributes);
    }

    currentAttributes.removeAttribute("sectionStyle");
}

void updateParagraphAttributes(MutableAttributeSet current,
                               AttributeSet newAttributes,
                               boolean emitStyleChanges)
    throws IOException
{
    Object parm;
    Object oldStyle, newStyle;

    /* The only way to get rid of tabs or styles is with the \pard keyword,
       emitted by resetParagraphAttributes(). Ideally we should avoid
       emitting \pard if the new paragraph's tabs are a superset of the old
       paragraph's tabs. */

    if (emitStyleChanges) {
        oldStyle = current.getAttribute("paragraphStyle");
        newStyle = findStyleNumber(newAttributes, Constants.STParagraph);
        if (oldStyle != newStyle) {
            if (oldStyle != null) {
                resetParagraphAttributes(current);
                oldStyle = null;
            }
        }
    } else {
        oldStyle = null;
        newStyle = null;
    }

    Object oldTabs = current.getAttribute(Constants.Tabs);
    Object newTabs = newAttributes.getAttribute(Constants.Tabs);
    if (oldTabs != newTabs) {
        if (oldTabs != null) {
            resetParagraphAttributes(current);
            oldTabs = null;
            oldStyle = null;
        }
    }

    if (oldStyle != newStyle && newStyle != null) {
        writeControlWord("s", ((Integer)newStyle).intValue());
        current.addAttribute("paragraphStyle", newStyle);
    }

    checkControlWords(current, newAttributes,
                      RTFAttributes.attributes, RTFAttribute.D_PARAGRAPH);

    if (oldTabs != newTabs && newTabs != null) {
        TabStop[] tabs = (TabStop[])newTabs;
        int index;
        for(index = 0; index < tabs.length; index ++) {
            TabStop tab = tabs[index];
            switch (tab.getAlignment()) {
              case TabStop.ALIGN_LEFT:
              case TabStop.ALIGN_BAR:
                break;
              case TabStop.ALIGN_RIGHT:
                writeControlWord("tqr");
                break;
              case TabStop.ALIGN_CENTER:
                writeControlWord("tqc");
                break;
              case TabStop.ALIGN_DECIMAL:
                writeControlWord("tqdec");
                break;
            }
            switch (tab.getLeader()) {
              case TabStop.LEAD_NONE:
                break;
              case TabStop.LEAD_DOTS:
                writeControlWord("tldot");
                break;
              case TabStop.LEAD_HYPHENS:
                writeControlWord("tlhyph");
                break;
              case TabStop.LEAD_UNDERLINE:
                writeControlWord("tlul");
                break;
              case TabStop.LEAD_THICKLINE:
                writeControlWord("tlth");
                break;
              case TabStop.LEAD_EQUALS:
                writeControlWord("tleq");
                break;
            }
            int twips = Math.round(20f * tab.getPosition());
            if (tab.getAlignment() == TabStop.ALIGN_BAR) {
                writeControlWord("tb", twips);
            } else {
                writeControlWord("tx", twips);
            }
        }
        current.addAttribute(Constants.Tabs, tabs);
    }
}

public void writeParagraphElement(Element el)
    throws IOException
{
    updateParagraphAttributes(outputAttributes, el.getAttributes(), true);

    int sub_count = el.getElementCount();
    for(int idx = 0; idx < sub_count; idx ++) {
        writeTextElement(el.getElement(idx));
    }

    writeControlWord("par");
    writeLineBreak();  /* makes the raw file more readable */
}

/* debugging. TODO: remove.
private static String tabdump(Object tso)
{
    String buf;
    int i;

    if (tso == null)
        return "[none]";

    TabStop[] ts = (TabStop[])tso;

    buf = "[";
    for(i = 0; i < ts.length; i++) {
        buf = buf + ts[i].toString();
        if ((i+1) < ts.length)
            buf = buf + ",";
    }
    return buf + "]";
}
*/

protected void resetParagraphAttributes(MutableAttributeSet currentAttributes)
    throws IOException
{
    writeControlWord("pard");

    currentAttributes.addAttribute(StyleConstants.Alignment, Integer.valueOf(0));

    int wordIndex;
    int wordCount = RTFAttributes.attributes.length;
    for(wordIndex = 0; wordIndex < wordCount; wordIndex++) {
        RTFAttribute attr = RTFAttributes.attributes[wordIndex];
        if (attr.domain() == RTFAttribute.D_PARAGRAPH)
            attr.setDefault(currentAttributes);
    }

    currentAttributes.removeAttribute("paragraphStyle");
    currentAttributes.removeAttribute(Constants.Tabs);
}

void updateCharacterAttributes(MutableAttributeSet current,
                               AttributeSet newAttributes,
                               boolean updateStyleChanges)
    throws IOException
{
    Object parm;

    if (updateStyleChanges) {
        Object oldStyle = current.getAttribute("characterStyle");
        Object newStyle = findStyleNumber(newAttributes,
                                          Constants.STCharacter);
        if (oldStyle != newStyle) {
            if (oldStyle != null) {
                resetCharacterAttributes(current);
            }
            if (newStyle != null) {
                writeControlWord("cs", ((Integer)newStyle).intValue());
                current.addAttribute("characterStyle", newStyle);
            } else {
                current.removeAttribute("characterStyle");
            }
        }
    }

    if ((parm = attrDiff(current, newAttributes,
                         StyleConstants.FontFamily, null)) != null) {
        Integer fontNum = fontTable.get(parm);
        writeControlWord("f", fontNum.intValue());
    }

    checkNumericControlWord(current, newAttributes,
                            StyleConstants.FontSize, "fs",
                            defaultFontSize, 2f);

    checkControlWords(current, newAttributes,
                      RTFAttributes.attributes, RTFAttribute.D_CHARACTER);

    checkNumericControlWord(current, newAttributes,
                            StyleConstants.LineSpacing, "sl",
                            0, 20f); /* TODO: sl wackiness */

    if ((parm = attrDiff(current, newAttributes,
                         StyleConstants.Background, MagicToken)) != null) {
        int colorNum;
        if (parm == MagicToken)
            colorNum = 0;
        else
            colorNum = colorTable.get(parm).intValue();
        writeControlWord("cb", colorNum);
    }

    if ((parm = attrDiff(current, newAttributes,
                         StyleConstants.Foreground, null)) != null) {
        int colorNum;
        if (parm == MagicToken)
            colorNum = 0;
        else
            colorNum = colorTable.get(parm).intValue();
        writeControlWord("cf", colorNum);
    }
}

protected void resetCharacterAttributes(MutableAttributeSet currentAttributes)
    throws IOException
{
    writeControlWord("plain");

    int wordIndex;
    int wordCount = RTFAttributes.attributes.length;
    for(wordIndex = 0; wordIndex < wordCount; wordIndex++) {
        RTFAttribute attr = RTFAttributes.attributes[wordIndex];
        if (attr.domain() == RTFAttribute.D_CHARACTER)
            attr.setDefault(currentAttributes);
    }

    StyleConstants.setFontFamily(currentAttributes, defaultFontFamily);
    currentAttributes.removeAttribute(StyleConstants.FontSize); /* =default */
    currentAttributes.removeAttribute(StyleConstants.Background);
    currentAttributes.removeAttribute(StyleConstants.Foreground);
    currentAttributes.removeAttribute(StyleConstants.LineSpacing);
    currentAttributes.removeAttribute("characterStyle");
}

public void writeTextElement(Element el)
    throws IOException
{
    updateCharacterAttributes(outputAttributes, el.getAttributes(), true);

    if (el.isLeaf()) {
        try {
            el.getDocument().getText(el.getStartOffset(),
                                     el.getEndOffset() - el.getStartOffset(),
                                     this.workingSegment);
        } catch (BadLocationException ble) {
            /* TODO is this the correct error to raise? */
            ble.printStackTrace();
            throw new InternalError(ble.getMessage());
        }
        writeText(this.workingSegment);
    } else {
        int sub_count = el.getElementCount();
        for(int idx = 0; idx < sub_count; idx ++)
            writeTextElement(el.getElement(idx));
    }
}

public void writeText(Segment s)
    throws IOException
{
    int pos, end;
    char[] array;

    pos = s.offset;
    end = pos + s.count;
    array = s.array;
    for( ; pos < end; pos ++)
        writeCharacter(array[pos]);
}

public void writeText(String s)
    throws IOException
{
    int pos, end;

    pos = 0;
    end = s.length();
    for( ; pos < end; pos ++)
        writeCharacter(s.charAt(pos));
}

public void writeRawString(String str)
    throws IOException
{
    int strlen = str.length();
    for (int offset = 0; offset < strlen; offset ++)
        outputStream.write((int)str.charAt(offset));
}

public void writeControlWord(String keyword)
    throws IOException
{
    outputStream.write('\\');
    writeRawString(keyword);
    afterKeyword = true;
}

public void writeControlWord(String keyword, int arg)
    throws IOException
{
    outputStream.write('\\');
    writeRawString(keyword);
    writeRawString(String.valueOf(arg)); /* TODO: correct in all cases? */
    afterKeyword = true;
}

public void writeBegingroup()
    throws IOException
{
    outputStream.write('{');
    afterKeyword = false;
}

public void writeEndgroup()
    throws IOException
{
    outputStream.write('}');
    afterKeyword = false;
}

@SuppressWarnings("fallthrough")
public void writeCharacter(char ch)
    throws IOException
{
    /* Nonbreaking space is in most RTF encodings, but the keyword is
       preferable; same goes for tabs */
    if (ch == 0xA0) { /* nonbreaking space */
        outputStream.write(0x5C);  /* backslash */
        outputStream.write(0x7E);  /* tilde */
        afterKeyword = false; /* non-alpha keywords are self-terminating */
        return;
    }

    if (ch == 0x09) { /* horizontal tab */
        writeControlWord("tab");
        return;
    }

    if (ch == 10 || ch == 13) { /* newline / paragraph */
        /* ignore CRs, we'll write a paragraph element soon enough */
        return;
    }

    int b = convertCharacter(outputConversion, ch);
    if (b == 0) {
        /* Unicode characters which have corresponding RTF keywords */
        int i;
        for(i = 0; i < textKeywords.length; i++) {

/**代码未完, 请加载全部代码(NowJava.com).**/
展开阅读全文

关注时代Java

关注时代Java