home *** CD-ROM | disk | FTP | other *** search
/ Apple Developer Connection Student Program / ADC Tools Sampler CD Disk 3 1999.iso / Metrowerks CodeWarrior / Java Support / Java_Source / IFC_112 / netscape / application / TextStyleRun.java < prev    next >
Encoding:
Text File  |  1999-05-28  |  22.0 KB  |  699 lines  |  [TEXT/CWIE]

  1. // TextStyleRun.java
  2. // By Ned Etcode
  3. // Copyright 1995, 1996, 1997 Netscape Communications Corp.  All rights reserved.
  4.  
  5. package netscape.application;
  6.  
  7. import netscape.util.*;
  8.  
  9. /** @private
  10.   * @note 1.0 changes
  11.   */
  12. public class TextStyleRun extends Object implements Codable {
  13.     private static final String CONTENTS_KEY   = "contents";
  14.     private static final String ATTRIBUTES_KEY = "attributes";
  15.  
  16.  
  17.     TextParagraph       _paragraph;
  18.     FastStringBuffer    _contents;
  19.     Hashtable           _attributes;
  20.     FontMetrics         _fontMetricsCache;
  21.     int                 _remainder;
  22.  
  23. /* constructors */
  24.  
  25.      public TextStyleRun() {
  26.         super();
  27.     }
  28.  
  29.      TextStyleRun(TextParagraph owner) {
  30.         this();
  31.         init(owner);
  32.     }
  33.  
  34.      TextStyleRun(TextParagraph owner, String contents, Hashtable attributes) {
  35.         this();
  36.         init(owner, contents, attributes);
  37.     }
  38.  
  39.     TextStyleRun(TextParagraph owner, String aString, int firstIndex,int lastIndex,Hashtable attributes) {
  40.         this();
  41.         init(owner, aString,firstIndex,lastIndex,attributes);
  42.     }
  43.  
  44. /* initializers */
  45.  
  46.  
  47.      void init(TextParagraph owner) {
  48.         _paragraph = owner;
  49.     }
  50.  
  51.      void init(TextParagraph owner, String contents, Hashtable attributes) {
  52.         init(owner);
  53.         setText(contents);
  54.         setAttributes(attributes);
  55.     }
  56.  
  57.      void init(TextParagraph owner, String contents,int firstIndex,
  58.                int lastIndex,Hashtable attributes) {
  59.          init(owner);
  60.          setText(contents,firstIndex,lastIndex);
  61.          setAttributes(attributes);
  62.      }
  63.  
  64.     TextStyleRun createEmptyRun() {
  65.         return new TextStyleRun(_paragraph, "", TextView.attributesByRemovingStaticAttributes(_attributes));
  66.     }
  67.  
  68.     TextStyleRun createEmptyRun(Hashtable attributes) {
  69.         return new TextStyleRun(_paragraph, "", attributes);
  70.     }
  71.  
  72.      void setParagraph(TextParagraph aParagraph) {
  73.         _paragraph = aParagraph;
  74.     }
  75.  
  76.      TextParagraph paragraph() {
  77.         return _paragraph;
  78.     }
  79.  
  80.      void setText(String text) {
  81.         _contents = new FastStringBuffer(text);
  82.     }
  83.  
  84.     void setText(String text,int firstIndex,int lastIndex) {
  85.       _contents = new FastStringBuffer(text,firstIndex,lastIndex);
  86.     }
  87.  
  88.     void setText(StringBuffer text) {
  89.         setText(text.toString());
  90.     }
  91.  
  92.     int rangeIndex() {
  93.         int result = _paragraph._startChar;
  94.         Vector v = _paragraph.runsBefore(this);
  95.         int i,c;
  96.  
  97.         for(i=0,c=v.count(); i < c ; i++ )
  98.             result += ((TextStyleRun)v.elementAt(i)).charCount();
  99.         return result;
  100.     }
  101.  
  102.     Range range() {
  103.         return TextView.allocateRange(rangeIndex(),charCount());
  104.     }
  105.  
  106.     /** Accessing vital attributes */
  107.     private Font getFont() {
  108.         Font f = null;
  109.         if( _paragraph.owner().usesSingleFont() ) {
  110.           f = (Font) _paragraph.owner().defaultAttributes().get(TextView.FONT_KEY);
  111.           return f;
  112.         }
  113.  
  114.         if( _attributes != null )
  115.             f = (Font) _attributes.get(TextView.FONT_KEY);
  116.         if( f == null )
  117.             f = (Font) _paragraph.owner().defaultAttributes().get(TextView.FONT_KEY);
  118.         return f;
  119.     }
  120.  
  121.     private Color getColor() {
  122.         Color c = null;
  123.         if( _attributes != null ) {
  124.           if( _attributes.get(TextView.LINK_KEY) != null ) {
  125.             if( _attributes.get(TextView.LINK_IS_PRESSED_KEY) != null)
  126.               c = (Color) _attributes.get(TextView.PRESSED_LINK_COLOR_KEY);
  127.             else
  128.               c = (Color) _attributes.get(TextView.LINK_COLOR_KEY);
  129.           } else
  130.             c = (Color) _attributes.get(TextView.TEXT_COLOR_KEY);
  131.         } else
  132.             c = (Color) _paragraph.owner().defaultAttributes().get(TextView.TEXT_COLOR_KEY);
  133.         return c;
  134.     }
  135.  
  136.     private void validateFontMetricsCache() {
  137.         if( _paragraph.owner().usesSingleFont() ||
  138.             _attributes == null ||
  139.             _attributes.get(TextView.FONT_KEY) == null )
  140.             _fontMetricsCache = _paragraph.owner().defaultFontMetrics();
  141.         else {
  142.             if(_fontMetricsCache == null)
  143.                 _fontMetricsCache = getFont().fontMetrics();
  144.         }
  145.     }
  146.  
  147.     private void invalidateFontMetricsCache() {
  148.         _fontMetricsCache = null;
  149.     }
  150.  
  151.      boolean containsATextAttachment() {
  152.         if( _attributes != null && _attributes.get(TextView.TEXT_ATTACHMENT_KEY) != null )
  153.             return true;
  154.         else
  155.             return false;
  156.     }
  157.  
  158.     Rect textAttachmentBoundsForOrigin(int x,int y,int baseline) {
  159.         TextAttachment att;
  160.         Rect result = new Rect();
  161.         int baselineOffset;
  162.  
  163.         if( _attributes != null &&
  164.             (att = (TextAttachment)_attributes.get(TextView.TEXT_ATTACHMENT_KEY)) != null ) {
  165.             result.x = x;
  166.             result.y = y + baseline - att.height() + attachmentBaselineOffset();
  167.             result.width = att.width();
  168.             result.height = att.height();
  169.             return result;
  170.         }
  171.         return null;
  172.     }
  173.  
  174.     /** Returns the character at <b>index</b>.  If <b>index</b> is invalid
  175.       * or the TextStyleRun doesn't contain text, returns '\0';
  176.       */
  177.      char charAt(int index) {
  178.         FastStringBuffer        buffer;
  179.         int                     i;
  180.  
  181.         buffer = (FastStringBuffer)_contents;
  182.         if (buffer.length() == 0 || index >= buffer.length()) {
  183.             return '\0';
  184.         }
  185.         return buffer.charAt(index);
  186.     }
  187.  
  188.     /** Inserts <b>aChar</b> at <b>position</b>.  If <b>index</b> is
  189.       * greater than or equal to the length of text within the run,
  190.       * appends <b>aChar</b>.  If this TextStyleRun doesn't contain text,
  191.       * does nothing.
  192.       */
  193.      void insertCharAt(char aChar, int index) {
  194.         FastStringBuffer        buffer;
  195.  
  196.         if (_contents == null) {
  197.             buffer = new FastStringBuffer(aChar);
  198.             _contents = buffer;
  199.         } else {
  200.             buffer = (FastStringBuffer)_contents;
  201.             buffer.insert(aChar, index);
  202.         }
  203.     }
  204.  
  205.     /** Inserts <b>aString</b> at <b>index</b>.  If <b>index</b> is
  206.       * greater than or equal to the length of text within the run,
  207.       * appends <b>aString</b>.  If this TextStyleRun doesn't contain text,
  208.       * does nothing.
  209.       */
  210.      void insertStringAt(String aString, int index) {
  211.         FastStringBuffer        buffer;
  212.  
  213.         if ( index < 0 || aString == null) {
  214.             return;
  215.         }
  216.  
  217.         if (_contents == null) {
  218.             buffer = new FastStringBuffer(aString);
  219.             _contents = buffer;
  220.         } else {
  221.             buffer = (FastStringBuffer)_contents;
  222.             buffer.insert(aString, index);
  223.         }
  224.     }
  225.  
  226.      void removeCharAt(int index) {
  227.         FastStringBuffer        buffer;
  228.         int                     i;
  229.  
  230.         buffer = (FastStringBuffer)_contents;
  231.         if (buffer.length() == 0 || index >= buffer.length()) {
  232.             return;
  233.         }
  234.         buffer.removeCharAt(index);
  235.     }
  236.  
  237.     TextStyleRun breakAt(int index) {
  238.         TextStyleRun            newRun;
  239.         String                  theString;
  240.         FastStringBuffer        buffer;
  241.  
  242.         buffer = (FastStringBuffer)_contents;
  243.         if (buffer.length() == 0 || index >= buffer.length()) {
  244.             return createEmptyRun(TextView.attributesByRemovingStaticAttributes(_attributes));
  245.         }
  246.         theString = buffer.toString();
  247.         newRun = new TextStyleRun(_paragraph,
  248.                                   theString.substring(index, buffer.length()),
  249.                                   TextView.attributesByRemovingStaticAttributes(_attributes));
  250.  
  251.         buffer.truncateToLength(index);
  252.         return newRun;
  253.     }
  254.  
  255.     void cutBefore(int index) {
  256.         FastStringBuffer        buffer;
  257.         int                     i;
  258.  
  259.         buffer = (FastStringBuffer)_contents;
  260.         if (buffer.length() == 0 || index >= buffer.length()) {
  261.             return;
  262.         }
  263.         buffer.moveChars(index, 0);
  264.     }
  265.  
  266.     void cutAfter(int index) {
  267.         FastStringBuffer        buffer;
  268.  
  269.         buffer = (FastStringBuffer)_contents;
  270.         if (buffer.length() == 0 || index >= buffer.length()) {
  271.             return;
  272.         }
  273.         buffer.truncateToLength(index);
  274.     }
  275.  
  276.      String text() {
  277.         return _contents.toString();
  278.     }
  279.  
  280.      public String toString() {
  281.         String res = "";
  282.         if( _attributes != null )
  283.             res += _attributes.toString();
  284.         else
  285.             res += "{DefAttr}";
  286.         res += "**(";
  287.         res += _contents.toString();
  288.         res += ")**";
  289.         return res;
  290.     }
  291.  
  292.     /** Returns the number of characters the TextStyleRun contains.  If the
  293.       * run contains an Image or TextAttachment, returns 1.
  294.       */
  295.      int charCount() {
  296.          return _contents.length();
  297.      }
  298.  
  299.      int attachmentBaselineOffset() {
  300.          Integer integer;
  301.          if( _attributes != null &&
  302.              (integer = (Integer) _attributes.get(TextView.TEXT_ATTACHMENT_BASELINE_OFFSET_KEY))
  303.              != null ) {
  304.              return integer.intValue();
  305.          }
  306.          return 0;
  307.      }
  308.  
  309.     /** Returns the distance from the top of the TextStyleRun's contents to
  310.       * the bottom of its contents.  In the case of text, this is just the sum
  311.       * of the font's ascent and descent.  For an image or TextAttachment, it's the
  312.       * height of the image or item.
  313.       */
  314.      int height() {
  315.         TextAttachment     theItem;
  316.         Image        theImage;
  317.  
  318.         if(_attributes != null && (theItem = (TextAttachment) _attributes.get(TextView.TEXT_ATTACHMENT_KEY)) != null ) {
  319.           int baselineOffset = attachmentBaselineOffset();
  320.           if( baselineOffset > 0 )
  321.             return Math.max(theItem.height(),baselineOffset);
  322.           else
  323.             return theItem.height() + Math.abs(baselineOffset);
  324.         } else {
  325.             validateFontMetricsCache();
  326.             return _fontMetricsCache.ascent() + _fontMetricsCache.descent();
  327.         }
  328.     }
  329.  
  330.     /** Returns the distance from the top of the TextStyleRun's contents to
  331.       * the baseline.  In the case of text, this is just the font ascent.
  332.       * For an image or TextAttachment, it's the height of the image or item.
  333.       */
  334.      int baseline() {
  335.         TextAttachment theItem;
  336.  
  337.         if( _attributes != null &&
  338.             (theItem = (TextAttachment)
  339.              _attributes.get(TextView.TEXT_ATTACHMENT_KEY)) != null ) {
  340.           int baselineOffset = attachmentBaselineOffset();
  341.           return Math.max(theItem.height() - baselineOffset,0);
  342.         } else {
  343.             validateFontMetricsCache();
  344.             return _fontMetricsCache.ascent();
  345.         }
  346.     }
  347.  
  348.     int _widthForTab(int position, int[] tabStops) {
  349.         int     i;
  350.         if (tabStops == null) {
  351.             return 0;
  352.         }
  353.         for (i = 0; i < tabStops.length; i++) {
  354.             if (position < tabStops[i]) {
  355.                 return tabStops[i] - position;
  356.             }
  357.         }
  358.         return 0;
  359.     }
  360.  
  361.     int _breakForSubstring(int startingChar, int count, int availableWidth) {
  362.         int     width;
  363.  
  364.         width = _widthOfSubstring(startingChar, count, 0, null);
  365.         while (width > availableWidth && count > 0) {
  366.             count--;
  367.             width = _widthOfSubstring(startingChar, count, 0, null);
  368.         }
  369.         return count;
  370.     }
  371.  
  372.     int charsForWidth(int startingChar, int currentX,
  373.                       int availableWidth, int maxAvailableWidth,
  374.                       int[] tabStops) {
  375.         TextAttachment                theItem;
  376.         Image                   theImage;
  377.         int                     charWidths[], count, i, nextWidth, wordWidth,
  378.                                 spaceWidth, start, tabPosition = -1, width,
  379.                                 breakPoint;
  380.         char                 buf[];
  381.  
  382.         width = availableWidth;
  383.         buf = new char[1];
  384.         if (_contents == null) {
  385.             _remainder = availableWidth;
  386.             return 0;
  387.         } else if( _attributes != null &&
  388.                    (theItem = (TextAttachment)_attributes.get(TextView.TEXT_ATTACHMENT_KEY)) != null ) {
  389.             if (theItem.width() > maxAvailableWidth) {
  390.                 if( width == maxAvailableWidth ) {
  391.                     _remainder = 0;
  392.                     return 1;
  393.                 } else
  394.                     return 0;
  395.             } else if (theItem.width() <= width) {
  396.                 _remainder = width - theItem.width();
  397.                 return 1;
  398.             }
  399.             _remainder = width;
  400.             return 0;
  401.         }
  402.  
  403.         validateFontMetricsCache();
  404.         charWidths = _fontMetricsCache.widthsArray();
  405.  
  406.         count = _contents.length();
  407.         i = startingChar;
  408.         while (i < count && width > 0) {
  409.             wordWidth = spaceWidth = 0;
  410.             start = i;
  411.             breakPoint = -1;
  412.  
  413.           /* find end of current word */
  414.             while (i < count && !(_contents.buffer[i] == ' ' || _contents.buffer[i] == '\t')) {
  415.                 if( _contents.buffer[i] < 256 )
  416.                     wordWidth += charWidths[_contents.buffer[i]];
  417.                 else {
  418.                     buf[0] = _contents.buffer[i];
  419.                     wordWidth += _fontMetricsCache.stringWidth(new String(buf));
  420.                 }
  421.                 i++;
  422.                 if (wordWidth > width && breakPoint == -1) {
  423.                     breakPoint = i;
  424.                     break;
  425.                 }
  426.             }
  427.  
  428.           /* subsume spaces that follow */
  429.             if (i < count && (_contents.buffer[i] == ' ' || _contents.buffer[i] == '\t')) {
  430.                 while (i < count && (_contents.buffer[i] == ' ' || _contents.buffer[i] == '\t')) {
  431.                     if (_contents.buffer[i] == ' ') {
  432.                         spaceWidth += charWidths[' '];
  433.                     } else {
  434.                         spaceWidth += _widthForTab(currentX + wordWidth +
  435.                                                    spaceWidth, tabStops);
  436.                         if (tabPosition == -1) {
  437.                             tabPosition = i;
  438.                         }
  439.                     }
  440.                     i++;
  441.                 }
  442.             }
  443.  
  444.             /* do the word and spaces fit? */
  445.             if ((wordWidth + spaceWidth) <= width) {
  446.                 width -= wordWidth + spaceWidth;
  447.                 continue;
  448.             }
  449.  
  450.             /* they don't - if at end of line and word fits, or we're on a new
  451.              * line, add the word and ignore the space/tab width (but do
  452.              * include them as characters on the line)
  453.              */
  454.  
  455.             if (width <= maxAvailableWidth && wordWidth <= width) {
  456.                 width -= wordWidth;
  457.                 break;
  458.             } else if (wordWidth > width && width >= maxAvailableWidth) {
  459.                 if (breakPoint != -1) {
  460.                     count = _breakForSubstring(start,
  461.                                                breakPoint - start,
  462.                                                width);
  463.                 } else {
  464.                     count = _breakForSubstring(start, i - start,
  465.                                                width);
  466.                 }
  467.                 if (count > 0) {
  468.                     i = startingChar + count;
  469.                     width -= _widthOfSubstring(startingChar, count,
  470.                                                         0, null);
  471.                 } else
  472.                     i = start;
  473.                 break;
  474.             } else
  475.                 i = start; /* We give up the end that does not fit */
  476.             break;
  477.         }
  478.  
  479.         if( width > 0 )
  480.           _remainder = width;
  481.         else
  482.           _remainder = 0;
  483.  
  484.         if( i == startingChar && width == maxAvailableWidth ) {
  485.             _remainder = 0;
  486.             return 1;
  487.         }
  488.         return i - startingChar;
  489.     }
  490.  
  491.     int _widthOfSubstring(int start, int count, int currentX, int[] tabStops) {
  492.         int[]                   charWidths;
  493.         int                     width = 0, i, endChar;
  494.         char buf[];
  495.         buf = new char[1];
  496.         validateFontMetricsCache();
  497.         charWidths = _fontMetricsCache.widthsArray();
  498.         endChar = start + count;
  499.         for (i = start; i < endChar; i++) {
  500.             if (_contents.buffer[i] == '\t' && tabStops != null) {
  501.                 width += _widthForTab(currentX + width, tabStops);
  502.                 continue;
  503.             }
  504.  
  505.             if( _contents.buffer[i] < 256 )
  506.                 width += charWidths[_contents.buffer[i]];
  507.             else {
  508.                 buf[0] = _contents.buffer[i];
  509.                 width += _fontMetricsCache.stringWidth(new String(buf));
  510.             }
  511.         }
  512.  
  513.         return width;
  514.     }
  515.  
  516.     /** Computes the width of <b>charCount</b> characters of the TextStyleRun
  517.       * starting at <b>startingChar</b>.
  518.       */
  519.      int widthOfContents(int startingChar, int charCount, int currentX,
  520.                                int[] tabStops) {
  521.         TextAttachment                theItem;
  522.         Image                   theImage;
  523.  
  524.         if( _attributes != null && (theItem = (TextAttachment) _attributes.get(TextView.TEXT_ATTACHMENT_KEY)) != null ) {
  525.             return theItem.width();
  526.         } else {
  527.             validateFontMetricsCache();
  528.             if( charCount == 0 )
  529.                 return 0;
  530.  
  531.             if (startingChar < 0) {
  532.                 startingChar = 0;
  533.             }
  534.             if ((startingChar + charCount) > _contents.length())
  535.                 charCount = _contents.length() - startingChar;
  536.  
  537.             return _widthOfSubstring(startingChar, charCount, currentX, tabStops);
  538.         }
  539.     }
  540.  
  541.     /** Draws <b>charCount</b> characters beginning with <b>startingChar</b>,
  542.       * at (<b>x</b>, <b>y</b>) within <b>g</b>.  Returns the width of the
  543.       * substring it drew.
  544.       */
  545.      int drawCharacters(Graphics g, int startingChar, int charCount,
  546.                               int x, int y, int[] tabStops) {
  547.         TextAttachment                theItem;
  548.         Rect                    tmpRect;
  549.         char[]                  charArray;
  550.         int[]                   charWidths;
  551.         int                     endChar, width, nextTab, count, totalWidth;
  552.  
  553.         if (g == null) {
  554.             return 0;
  555.         } else if (_attributes != null  &&
  556.                    (theItem = (TextAttachment) _attributes.get(TextView.TEXT_ATTACHMENT_KEY)) != null) {
  557.           int baselineOffset = attachmentBaselineOffset();
  558.             tmpRect = Rect.newRect(x, y - theItem.height() + baselineOffset, 0, 0);
  559.             tmpRect.width = theItem.width();
  560.             tmpRect.height = theItem.height();
  561.             theItem.drawInRect(g, tmpRect);
  562.             Rect.returnRect(tmpRect);
  563.             return theItem.width();
  564.         }
  565.  
  566.         validateFontMetricsCache();
  567.         if( _fontMetricsCache == null || charCount <= 0 ) {
  568.             return 0;
  569.         }
  570.  
  571.         /* setup */
  572.         g.setFont(getFont());
  573.         g.setColor(getColor());
  574.  
  575.         /* clip range */
  576.         if (startingChar < 0) {
  577.             startingChar = 0;
  578.         }
  579.         if (startingChar + charCount > _contents.length()) {
  580.             charCount = _contents.length() - startingChar;
  581.         }
  582.  
  583.         /* draw and compute total length */
  584.         charArray = _contents.charArray();
  585.         charWidths = _fontMetricsCache.widthsArray();
  586.         endChar = startingChar + charCount;
  587.         totalWidth = 0;
  588.         while (startingChar < endChar) {
  589.             nextTab = _contents.indexOf('\t', startingChar);
  590.             if (nextTab == -1) {
  591.                 count = endChar - startingChar;
  592.             } else {
  593.                 count = nextTab - startingChar;
  594.             }
  595.  
  596.             /* draw everything up to the tab */
  597.             if (count > 0) {
  598.                 g.drawChars(charArray, startingChar, count, x, y);
  599.             }
  600.  
  601.             /* include the tabstop in the width calculation */
  602.             if (nextTab != -1) {
  603.                 count++;
  604.             }
  605.  
  606.             /* compute width of drawn text and tabstop */
  607.             width = _widthOfSubstring(startingChar, count, x, tabStops);
  608.             x += width;
  609.             totalWidth += width;
  610.  
  611.             /* advance pointer */
  612.             startingChar += count;
  613.         }
  614.  
  615.         return totalWidth;
  616.     }
  617.  
  618.  
  619.  
  620.  
  621. /* archiving */
  622.  
  623.  
  624.     /** Describes the TextStyleRun class' information.
  625.      * @see Codable#describeClassInfo
  626.      */
  627.      public void describeClassInfo(ClassInfo info) {
  628.         info.addClass("netscape.application.TextStyleRun", 1);
  629.         info.addField(CONTENTS_KEY,STRING_TYPE);
  630.         info.addField(ATTRIBUTES_KEY, OBJECT_TYPE);
  631.     }
  632.  
  633.     /** Encodes the TextStyleRun instance.
  634.      * @see Codable#encode
  635.      */
  636.      public void encode(Encoder encoder) throws CodingException {
  637.         encoder.encodeString(CONTENTS_KEY,_contents.toString());
  638.         encoder.encodeObject(ATTRIBUTES_KEY,_attributes);
  639.     }
  640.  
  641.     /** Decodes a TextStyleRun instance.
  642.      * @see Codable#decode
  643.      */
  644.      public void decode(Decoder decoder) throws CodingException {
  645.         String          text;
  646.         Object          image;
  647.  
  648.         _contents = new FastStringBuffer( decoder.decodeString(CONTENTS_KEY));
  649.         _attributes = (Hashtable)decoder.decodeObject(ATTRIBUTES_KEY);
  650.     }
  651.  
  652.     /** Finishes the TextStyleRun instance decoding.  This method does nothing.
  653.      * @see Codable#finishDecoding
  654.      */
  655.     public  void finishDecoding() throws CodingException {
  656.     }
  657.  
  658.      void setAttributes(Hashtable attributes) {
  659.       if( attributes != null ) {
  660.         invalidateFontMetricsCache();
  661.         _attributes = (Hashtable)attributes.clone();
  662.       } else
  663.         _attributes = null;
  664.  
  665.       if( _attributes != null ) {
  666.         if(_attributes.get(TextView.PARAGRAPH_FORMAT_KEY) != null)
  667.           _attributes.remove( TextView.PARAGRAPH_FORMAT_KEY );
  668.       }
  669.     }
  670.  
  671.     void appendAttributes(Hashtable attributes) {
  672.      Enumeration keys;
  673.      String key;
  674.  
  675.      if( attributes == null )
  676.        return;
  677.      if(_attributes == null )
  678.        _attributes = (Hashtable) _paragraph.owner().defaultAttributes().clone();
  679.  
  680.      keys = attributes.keys();
  681.      while(keys.hasMoreElements()) {
  682.        key = (String)keys.nextElement();
  683.        if( key.equals(TextView.FONT_KEY))
  684.            invalidateFontMetricsCache();
  685.        _attributes.put( key, attributes.get(key));
  686.      }
  687.    }
  688.  
  689.    Hashtable attributes() {
  690.        if( _attributes != null ) {
  691.            _attributes.put(TextView.PARAGRAPH_FORMAT_KEY,_paragraph.currentParagraphFormat());
  692.            return _attributes;
  693.        }
  694.        return null;
  695.   }
  696. }
  697.  
  698.  
  699.