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