1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 package org.openoffice.xmerge.converter.xml.sxw.pocketword;
25 
26 import java.io.ByteArrayOutputStream;
27 import java.io.IOException;
28 
29 import java.util.Vector;
30 import java.util.Enumeration;
31 
32 import java.awt.Color;
33 
34 import org.openoffice.xmerge.util.EndianConverter;
35 import org.openoffice.xmerge.util.ColourConverter;
36 import org.openoffice.xmerge.converter.xml.ParaStyle;
37 import org.openoffice.xmerge.converter.xml.TextStyle;
38 
39 
40 /**
41  * Represents a paragraph data structure within a Pocket Word document.
42  *
43  * @author  Mark Murnane
44  * @version 1.1
45  */
46 class Paragraph implements PocketWordConstants {
47     /*
48      * The data elements of a Paragraph.
49      *
50      * As the 'unknown' values are not calculated they are declared static.
51      * They are not declared final because they do have a calcuable value.
52      */
53     private static short unknown1 = 0x23;
54     private short dataWords = 0;
55     private short textLength = 0;
56     private short lengthWithFormatting = 0;
57     private short lines   = 0;
58 
59     private static final short marker  = (short)0xFFFF;
60     private static int unknown2 = 0x22;          // May be two short values
61 
62     private short specialIndentation = 0;
63     private short leftIndentation    = 0;
64     private short rightIndentation   = 0;
65 
66     private byte bullets   = 0;
67     private byte alignment = 0;
68 
69     private static int unknown3 = 0;
70 
71     // Will always have at least these formatting settings in each paragraph
72     private short defaultFont = 2;          // Courier New for the time being
73     private short defaultSize = 10;
74 
75 
76     /*
77      * Remaining elements assist in calculating correct values for the paragraph
78      * representation.
79      */
80 
81     private Vector textSegments = null;
82 
83     private Vector lineDescriptors = null;
84 
85     private ParaStyle pStyle = null;
86 
87     private boolean isLastParagraph = false;
88 
89 
90     /*
91      * Private class constructor used by all constructors.  Ensures the proper
92      * initialisation of the Vector storing the paragraph's text.
93      */
Paragraph()94     private Paragraph () {
95         textSegments = new Vector(0, 1);
96     }
97 
98 
99     /**
100      * <p>Constructor for use when converting from SXW format to Pocket Word
101      *    format.</p>
102      *
103      * @param   style   Paragraph style object describing the formatting style
104      *                  of this paragraph.
105      */
Paragraph(ParaStyle style)106     public Paragraph (ParaStyle style) {
107         this();
108 
109         lineDescriptors = new Vector(0, 1);
110         pStyle = style;
111     }
112 
113 
114     /**
115      * <p>Constructor for use when converting from Pocket Word format to SXW
116      *    format.</p>
117      *
118      * @param   data    Byte array containing byte data describing this paragraph
119      *                  from the Pocket Word file.
120      */
Paragraph(byte[] data)121     public Paragraph (byte[] data) {
122         this();
123 
124         /*
125          * Read in all fixed data from the array
126          *
127          * unknown1 appears at data[0] and data[1]
128          */
129         dataWords = EndianConverter.readShort(new byte[] { data[2], data[3] } );
130         textLength = EndianConverter.readShort(new byte[] { data[4], data [5] } );
131         lengthWithFormatting = EndianConverter.readShort(
132                                                 new byte[] { data[6], data[7] } );
133         lines = EndianConverter.readShort(new byte[] { data[8], data [9] } );
134 
135         /*
136          * The marker appears at data[10] and data[11].
137          *
138          * The value of unknown2 is at data[12], data[13], data[14] and data[15].
139          */
140 
141         specialIndentation = EndianConverter.readShort(new byte[] { data[16], data[17] } );
142         leftIndentation = EndianConverter.readShort(new byte[] { data[18], data [19] } );
143         rightIndentation = EndianConverter.readShort(new byte[] { data[20], data [21] } );
144 
145         bullets = data[22];
146         alignment = data[23];
147 
148         // The value of unknown3 is at data[24], data[25], data[26] and data[27].
149 
150         /*
151          * The actual paragraph data is in the remainder of the byte sequence.
152          *
153          * Only the actual text seqence with the embedded formatting tags is
154          * relevant to the conversion from Pocket Word to SXW format.
155          */
156         ByteArrayOutputStream bos = new ByteArrayOutputStream();
157         bos.write(data, 28, lengthWithFormatting);
158         parseText(bos.toByteArray());
159     }
160 
161 
162     /*
163      * Processes the text portion of the raw paragraph data from the Pocket Word
164      * file.  This data also includes formatting settings for the text in the
165      * paragraph.
166      *
167      * Formatting changes appear like XML/HTML tags.  Formatted blocks are
168      * preceded by a sequence of bytes switching on a formatting change and
169      * followed by a sequence switching off that formatting change.
170      */
parseText(byte[] data)171     private void parseText (byte[] data) {
172 
173         int totalLength = data.length;
174 
175         StringBuffer sb = new StringBuffer("");
176 
177         // Setup text style information
178         int mask = TextStyle.BOLD | TextStyle.ITALIC | TextStyle.UNDERLINE
179                     | TextStyle.STRIKETHRU;
180 
181 
182         String fontName = null;
183         int fontSize = 0;
184         Color textColour = null;
185         Color backColour = null;
186         int modifiers = 0;
187 
188         TextStyle ts = null;
189 
190         int attrsSet = 0;   // If this is 0, we have no extra style
191         boolean inSequence = false;
192         boolean sawText = false;
193 
194         String s = new String();  // For debugging
195 
196         // Start from the very beginning
197         for (int i = 0; i < totalLength; i++) {
198             // Will encounter at least two codes first
199             if ((byte)(data[i] & 0xF0) == FORMATTING_TAG) {
200                 if (sawText) {
201                     // Style change so dump previous segment and style info
202                     addTextSegment(sb.toString(), ts);
203                     sb = new StringBuffer("");
204                     sawText = false;
205                 }
206 
207                 switch (data[i]) {
208                     case FONT_TAG:
209                         int index = EndianConverter.readShort(
210                                         new byte[] { data[i + 1], data[i + 2] } );
211 
212                         /*
213                          * Standard font.
214                          *
215                          * Should really be one, but as the only supported font
216                          * currently is Courier New, want to leave it at Courier
217                          * New for round trip conversions.
218                          *
219                          * Also need to account for the fact that Tahoma is the
220                          * correct standard font.
221                          */
222                         if (fontName == null || fontName.equals("2")) {
223                             if (index != 2 && index != 1) {
224                                 fontName = String.valueOf(index);
225                                 attrsSet++;
226                             }
227                         }
228                         else {
229                             // Font is set, but not the default
230                             if (index == 2 || index == 1) {
231                                 fontName = "2";
232                                 attrsSet--;
233                             }
234                             else {
235                                 fontName = String.valueOf(index);
236                             }
237                         }
238                         i += 2;
239                         break;
240 
241 
242                     case FONT_SIZE_TAG:
243                         int size = EndianConverter.readShort(
244                                         new byte[] { data[i + 1], data[i + 2] } );
245 
246                         if (size == 0) {
247                             // Flags the end of the last paragraph
248                             isLastParagraph = true;
249                             i += 2;
250                             break;
251                         }
252 
253                         // Standard size
254                         if (fontSize == 0 || fontSize == 10) {
255                             if (size != 10) {
256                                 fontSize = size;
257                                 attrsSet++;
258                             }
259                         }
260                         else {
261                             // Font size is set, but not to standard
262                             if (size == 10) {
263                                 fontSize = 10;
264                                 attrsSet--;
265                             }
266                             else {
267                                 fontSize = size;
268                             }
269                         }
270                         i += 2;
271                         break;
272 
273 
274                     case COLOUR_TAG:
275                         if (data[i + 1] != 0) {
276 							ColourConverter cc = new ColourConverter();
277                             textColour = cc.convertToRGB(
278                                 EndianConverter.readShort(new byte[] { data[i + 1],
279                                                                     data[i + 2] } ));
280                             attrsSet++;
281                         }
282                         else {
283                             textColour = null;
284                             attrsSet--;
285                         }
286                         i += 2;
287                         break;
288 
289 
290                     case FONT_WEIGHT_TAG:
291                         if (data[i + 1] == FONT_WEIGHT_BOLD
292                                 || data[i + 1] == FONT_WEIGHT_THICK) {
293                             modifiers |= TextStyle.BOLD;
294                             attrsSet++;
295                         }
296                         else {
297                             // Its a bit field so subtracting should work okay.
298                             modifiers ^= TextStyle.BOLD;
299                             attrsSet--;
300                         }
301                         i += 2;
302                         break;
303 
304 
305                     case ITALIC_TAG:
306                         if (data[i + 1] == (byte)0x01) {
307                             modifiers |= TextStyle.ITALIC;
308                             attrsSet++;
309                         }
310                         else {
311                             modifiers ^= TextStyle.ITALIC;
312                             attrsSet--;
313                         }
314                         i++;
315                         break;
316 
317 
318                     case UNDERLINE_TAG:
319                         if (data[i + 1] == (byte)0x01) {
320                             modifiers |= TextStyle.UNDERLINE;
321                             attrsSet++;
322                         }
323                         else {
324                             modifiers ^= TextStyle.UNDERLINE;
325                             attrsSet--;
326                         }
327                         i++;
328                         break;
329 
330 
331                     case STRIKETHROUGH_TAG:
332                         if (data[i + 1] == (byte)0x01) {
333                             modifiers |= TextStyle.STRIKETHRU;
334                             attrsSet++;
335                         }
336                         else {
337                             modifiers ^= TextStyle.STRIKETHRU;
338                             attrsSet--;
339                         }
340                         i++;
341                         break;
342 
343                     case HIGHLIGHT_TAG:
344                         /*
345                          * Highlighting is treated by OpenOffice as a
346                          * background colour.
347                          */
348                         if (data[i + 1] == (byte)0x01) {
349                             backColour =  Color.yellow;
350                             attrsSet++;
351                         }
352                         else {
353                             backColour = null;
354                             attrsSet--;
355                         }
356                         i++;
357                         break;
358                 }
359 
360                 inSequence = true;
361                 continue;
362             }
363 
364             if (inSequence) {
365                 // Style information has been changed.  Create new style here
366 
367                 inSequence = false;
368                 if (attrsSet > 0) {
369                     ts = new TextStyle(null, TEXT_STYLE_FAMILY, DEFAULT_STYLE,
370                                         mask, modifiers, fontSize, fontName, null);
371                     ts.setColors(textColour, backColour);
372                 }
373                 else {
374                     ts = null;
375                 }
376             }
377 
378             /*
379              * C4 xx seems to indicate a control code.  C4 00 indicates the end
380              * of a paragraph; C4 04 indicates a tab space.  Only these two
381              * have been seen so far.
382              */
383             if (data[i] == (byte)0xC4) {
384                 /*
385                  * Redundant nodes are sometimes added to the last paragraph
386                  * because a new sequence is being processed when the flag is
387                  * set.
388                  *
389                  * To avoid this, do nothing with the last paragraph unless no
390                  * text has been added for it already.  In that case, add the
391                  * empty text segment being process to ensure that all
392                  * paragraphs have at least one text segment.
393                  */
394                 if (data[i + 1] == (byte)0x00) {
395                     if (isLastParagraph && textSegments.size() > 0) {
396                         return;
397                     }
398                     addTextSegment(sb.toString(), ts);
399                     return;
400                 }
401                 sb.append("\t");
402                 sawText = true;
403                 i++;
404                 continue;
405             }
406 
407             sb.append((char)data[i]);
408             sawText = true;
409             s = sb.toString();
410         }
411     }
412 
413 
414     /**
415      * <p>Adds details of a new text block to the <code>Paragraph</code> object.
416      * </p>
417      *
418      * @param   text    The text of the new block.
419      * @param   style   Text style object describing the formatting attached
420      *                  to this block of text.
421      */
addTextSegment(String text, TextStyle style)422     public void addTextSegment(String text, TextStyle style) {
423         textLength += text.length();
424         textSegments.add(new ParagraphTextSegment(text, style));
425      }
426 
427 
428     /**
429      * <p>This method alters the state of the <code>Paragraph</code> object to
430      *    indicate whether or not it is the final paragraph in the document.</p>
431      *
432      * <p>It is used during conversion from SXW format to Pocket Word format.
433      *    In Pocket Word files, the last paragraph finishes with a different byte
434      *    sequence to other paragraphs.</p>
435      *
436      * @param   isLast  true if the Paragraph is the last in the document,
437      *                  false otherwise.
438      */
setLastParagraph(boolean isLast)439     public void setLastParagraph(boolean isLast) {
440         isLastParagraph = isLast;
441     }
442 
443 
444     /**
445      * <p>Complementary method to {@link #setLastParagraph(boolean)
446      *    setLastParagraph}.  Returns the terminal status of this
447      *    <code>Paragraph</code> within the Pocket Word document.</p>
448      *
449      * @return  true if the Paragraph is the last in the document; false otherwise.
450      */
getLastParagraph()451     public boolean getLastParagraph () {
452         return isLastParagraph;
453     }
454 
455 
456     /**
457      * <p>This method returns the Pocket Word representation of this
458      *    <code>Paragraph</code> in Little Endian byte order.</p>
459      *
460      * <p>Used when converting from SXW format to Pocket Word format.</p>
461      *
462      * @return  <code>byte</code> array containing the formatted representation
463      *          of this Paragraph.
464      */
getParagraphData()465     public byte[] getParagraphData() {
466         ByteArrayOutputStream bos = new ByteArrayOutputStream();
467 
468         postProcessText();
469 
470         /*
471          * Need information about the paragraph segments in two places
472          * so calculate them first.
473          *
474          * The stream contains the text wrapped in any formatting sequences that
475          * are necessary.
476          */
477         ByteArrayOutputStream segs = new ByteArrayOutputStream();
478 
479         try {
480             for (int i = 0; i < textSegments.size(); i++) {
481                 ParagraphTextSegment pts = (ParagraphTextSegment)textSegments.elementAt(i);
482                 segs.write(pts.getData());
483             }
484         }
485         catch (IOException ioe) {
486             // Should never happen in a memory based stream
487         }
488 
489         /*
490          * Number of data words for this paragraph descriptor:
491          *
492          * 26 is the number of bytes prior to the start of the segment.
493          * 3 comes from the C4 00 00 termintating sequence.
494          */
495         dataWords = (short)(26 + segs.size() + 3 + 4);
496         if (isLastParagraph) {
497             dataWords += 6;
498         }
499         if (dataWords % 4 != 0) {
500             dataWords += (4 - (dataWords % 4));
501         }
502         dataWords /= 4;
503 
504         /*
505          * The 8 bytes are made up of E6 ?0 00 and E5 ?0 00 at the start of the
506          * text along with the C4 00 that terminates it.
507          *
508          * In the event that the paragraph is the last one E6 00 00 is also
509          * present at the end of the text.  Also, as we currently use a font
510          * other than the first in the index (Tahoma) E5 01 00 is also present.
511          *
512          * Make sure this is accurate when font specifications change
513          */
514         lengthWithFormatting = (short)(segs.size() + (isLastParagraph ? 14 : 8));
515 
516         try {
517             bos.write(EndianConverter.writeShort(unknown1));
518             bos.write(EndianConverter.writeShort(dataWords));
519             bos.write(EndianConverter.writeShort((short)(textLength + 1)));
520             bos.write(EndianConverter.writeShort(lengthWithFormatting));
521             bos.write(EndianConverter.writeShort(lines));
522 
523             bos.write(EndianConverter.writeShort(marker));
524             bos.write(EndianConverter.writeInt(unknown2));
525 
526             bos.write(EndianConverter.writeShort(specialIndentation));
527             bos.write(EndianConverter.writeShort(leftIndentation));
528             bos.write(EndianConverter.writeShort(rightIndentation));
529 
530             bos.write(bullets);
531 
532             if (pStyle != null && pStyle.isAttributeSet(ParaStyle.TEXT_ALIGN)) {
533                 switch (pStyle.getAttribute(ParaStyle.TEXT_ALIGN)) {
534 
535                     case ParaStyle.ALIGN_RIGHT:
536                         bos.write(0x01);
537                         break;
538 
539                     case ParaStyle.ALIGN_CENTER:
540                         bos.write(0x02);
541                         break;
542 
543                     default:
544                         bos.write(0x00);    // Left align in all other circumstances
545                         break;
546                 }
547             }
548             else {
549                 bos.write(0x00);
550             }
551 
552             bos.write(EndianConverter.writeInt(unknown3));
553 
554 
555             /*
556              * Write out font and size.
557              *
558              * If font support is added then this should change as the information
559              * will have to be calculated from a Font table.
560              */
561             bos.write(FONT_TAG);
562             bos.write(EndianConverter.writeShort(defaultFont));
563             bos.write(FONT_SIZE_TAG);
564             bos.write(EndianConverter.writeShort(defaultSize));
565 
566             // Write out the text segments
567             bos.write(segs.toByteArray());
568 
569             /*
570             * If this is the last paragraph in the document then we need to make
571             * sure that the paragraph text is terminated correctly with an E6 00 00
572             * before the C4 00 00.
573             */
574             if (isLastParagraph) {
575                 if (defaultFont != 1) {
576                     // Must always go back to the first font.
577                     bos.write(FONT_TAG);
578                     bos.write(EndianConverter.writeShort((short)0x01));
579                 }
580                 bos.write(FONT_SIZE_TAG);
581                 bos.write(EndianConverter.writeShort((short)0x00));
582             }
583 
584             bos.write(new byte[] { (byte)0xC4, 0x00, 0x00 } );
585 
586             int padding = 0;
587             if (bos.size() % 4 != 0) {
588                 padding = 4 - (bos.size() % 4);
589             }
590             for (int i = 0; i < padding; i++) {
591                 bos.write(0x00);
592             }
593 
594             // Third byte should match first byte after 0xFF 0xFF
595             bos.write(new byte[] { 0x42, 0x00, 0x22, 0x00} );
596 
597             /*
598             * Meaning of last two bytes seems to be the number of words describing
599             * lines.  This is calculated at 10 bytes per descriptor.
600             *
601             * May have two extra padding bytes that need to be accounted for too
602             * The division below may lose 2 bytes (integer result).
603             */
604             int wordsRemaining = (lineDescriptors.size() * 10) / 4;
605             if ((lineDescriptors.size() * 10) % 4 != 0) {
606                 wordsRemaining++;
607             }
608             bos.write(EndianConverter.writeShort((short)wordsRemaining));
609 
610 
611             // Now write out the line descriptors
612             for (int i = 0; i < lineDescriptors.size(); i++) {
613                 LineDescriptor ld = (LineDescriptor)lineDescriptors.elementAt(i);
614 
615                 bos.write(ld.getDescriptorInfo());
616             }
617 
618 
619             if (!isLastParagraph) {
620                 /*
621                  * There may be a need to pad this.  Will be writing at
622                  * either start of 4 byte block or 2 bytes into it.
623                  */
624                 if (bos.size() % 4 != 2) {
625                     bos.write(EndianConverter.writeShort((short)0));
626                 }
627                 bos.write(EndianConverter.writeShort((short)0x41));
628             }
629         }
630         catch (IOException ioe) {
631             // Should never occur for a memory based stream
632         }
633 
634         return bos.toByteArray();
635     }
636 
637 
638     /*
639      * This method handles the calculation of correct values for line lengths
640      * in each individual descriptor and the number of lines in the document.
641      *
642      * TODO: Update to take account of different font metrics.
643      */
postProcessText()644     private void postProcessText() {
645         /*
646          * The post-processing ...
647          *
648          * For each line, we need to add a line descriptor and increment
649          * the number of lines in the paragraph data structure.
650          *
651          * To do this, make sure that no sequence goes over the given screen
652          * width unless the last char is a whitespace character.
653          */
654 
655         // In courier, can have no more than 29 chars per line
656 
657         int chunkStart = 0;
658         StringBuffer sb = new StringBuffer("");
659 
660         // Line Descriptor info should be eliminated each time
661         lineDescriptors = new Vector(1, 1);
662         lines = 0;
663 
664         for (int i = 0; i < textSegments.size(); i++) {
665             ParagraphTextSegment pts = (ParagraphTextSegment)textSegments.elementAt(i);
666             sb.append(pts.getText());
667         }
668 
669         if (sb.length() == 0) {
670             lines = 1;
671             lineDescriptors.add(new LineDescriptor((short)1, (short)0));
672             return;
673         }
674 
675         while (chunkStart < sb.length()) {
676             String text = "";
677 
678             try {
679                 text = sb.substring(chunkStart, chunkStart + 30);
680             }
681             catch (StringIndexOutOfBoundsException sioobe) {
682                 // We have less than one line left so just add it
683                 text = sb.substring(chunkStart);
684                 lineDescriptors.add(new LineDescriptor((short)(text.length() + 1), (short)(text.length() * 36)));
685                 chunkStart += text.length();
686                 lines++;
687                 continue;
688             }
689 
690             int lastWhitespace = -1;
691 
692             for (int i = 29; i >= 0; i--) {
693                 if (Character.isWhitespace(text.charAt(i))) {
694                     lastWhitespace = i;
695                     break;
696                 }
697             }
698 
699             if (lastWhitespace != -1) {
700                 // The line can be split
701                 lineDescriptors.add(new LineDescriptor((short)(lastWhitespace + 1), (short)(lastWhitespace  * 36)));
702                 chunkStart += lastWhitespace + 1;
703                 lines++;
704             }
705             else {
706                 // The line is completely occupied by a single word
707                 lineDescriptors.add(new LineDescriptor((short)29, (short)(29 * 36)));
708                 chunkStart += 29;
709                 lines++;
710             }
711         }
712     }
713 
714 
715     /**
716      * <p>Returns the number of lines in the <code>Paragraph</code>.</p>
717      *
718      * @return  The number of lines in the document.
719      */
getLines()720     public short getLines() {
721         postProcessText();
722 
723         return lines;
724     }
725 
726 
727     /**
728      * <p>Toggles the flag indicating that the <code>Paragraph</code> is a
729      *    bulleted paragraph.</p>
730      *
731      * @param   isBulleted  true to enable bulleting for this paragraph, false
732      *                      otherwise.
733      */
setBullets(boolean isBulleted)734     public void setBullets(boolean isBulleted) {
735         if (isBulleted) {
736             bullets = (byte)0xFF;
737         }
738         else {
739             bullets = 0;
740         }
741     }
742 
743     /**
744      * <p>Returns the bulleting status of the <code>Paragraph</code>.</p>
745      *
746      * @return  true if the paragraph is bulleted, false otherwise.
747      */
isBulleted()748     public boolean isBulleted() {
749         if (bullets != 0) {
750             return true;
751         }
752         return false;
753     }
754 
755 
756     /**
757      * <p>Returns the number of text characters in the <code>Paragraph</code>,
758      *    excluding formatting.</p>
759      *
760      * @return  The length of the paragraph.
761      */
getTextLength()762     public int getTextLength () {
763         return textLength;
764     }
765 
766 
767     /**
768      * <p>Returns an <code>Enumeration</code> over the individual text segments
769      *    of the <code>Paragraph</code>.</p>
770      *
771      * @return  An <code>Enumeration</code> of the text segments.
772      */
getSegmentsEnumerator()773     public Enumeration getSegmentsEnumerator () {
774         return textSegments.elements();
775     }
776 
777 
778     /**
779      * <p>Returns a paragraph style object that describes any of the paragraph
780      *    level formatting used by this <code>Paragraph</code>.</p>
781      *
782      * @return  Paragraph style object describing the <code>Paragraph</code>.
783      */
makeStyle()784     public ParaStyle makeStyle() {
785         int attrs[] = new int[] { ParaStyle.MARGIN_LEFT, ParaStyle.MARGIN_RIGHT,
786                                   ParaStyle.TEXT_ALIGN };
787         String values[] = new String[attrs.length];
788 
789         /*
790          * Not interested in left or right indents just yet.  Don't know
791          * how to calculate them.
792          */
793 
794         switch (alignment) {
795             case 2:
796                 values[2] = "center";
797                 break;
798 
799             case 1:
800                 values[2] = "right";
801                 break;
802 
803             case 0:
804             default:
805                 values[2] = "left";
806                 return null;        // Not interested if its the default.
807         }
808 
809         return new ParaStyle(null, PARAGRAPH_STYLE_FAMILY, null, attrs,
810                                 values, null);
811     }
812 
813 
814     /*
815      * Class describing the data structures which appear following the text
816      * of a Paragraph.  For each line on screen that the Paragraph uses, a
817      * LineDescriptor details how many characters are on the line and how much
818      * screen space they occupy.
819      *
820      * The screen space and character breaks are calculated during post-processing
821      * of the paragraph.  See postProcessText().
822      *
823      * The unit of measurement used for screen space is currently unknown.
824      */
825     private class LineDescriptor {
826         private short characters = 0;
827         private int filler = 0;
828         private short screen_space = 0;
829         private short marker = 0;
830 
LineDescriptor(short chars, short space)831         private LineDescriptor(short chars, short space) {
832             characters = chars;
833             screen_space = space;
834             marker = (short)0x040C;    // Not a constant.  Depends on font used.
835         }
836 
837 
getDescriptorInfo()838         private byte[] getDescriptorInfo(){
839             ByteArrayOutputStream bos = new ByteArrayOutputStream();
840 
841             try {
842                 bos.write(EndianConverter.writeShort(characters));
843                 bos.write(EndianConverter.writeInt(filler));
844                 bos.write(EndianConverter.writeShort(screen_space));
845                 bos.write(EndianConverter.writeShort(marker));
846             }
847             catch (IOException ioe) {
848                 // Should never happen in a memory based stream.
849             }
850 
851             return bos.toByteArray();
852         }
853     }
854 }
855