1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.pdfbox.filter; 18 19 import java.io.ByteArrayOutputStream; 20 import java.io.IOException; 21 import java.io.InputStream; 22 import java.io.OutputStream; 23 import java.text.SimpleDateFormat; 24 import java.util.Date; 25 26 import org.apache.pdfbox.cos.COSArray; 27 import org.apache.pdfbox.cos.COSBase; 28 import org.apache.pdfbox.cos.COSDictionary; 29 import org.apache.pdfbox.cos.COSName; 30 31 /** 32 * This is a filter for the CCITTFax Decoder. 33 * 34 * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a> 35 * @author Marcel Kammer 36 * @author Paul King 37 * @version $Revision: 1.13 $ 38 */ 39 public class CCITTFaxDecodeFilter implements Filter 40 { 41 // Filter will write 15 TAG's 42 // If you add or remove TAG's you will have to modify this value 43 private static final int TAG_COUNT = 15; 44 45 // HEADERLENGTH(fix 8 Bytes) plus ImageLength(variable) 46 private int offset = 8; 47 48 // Bytecounter for Bytes that will be written after the TAG-DICTIONARY 49 private int tailingBytesCount = 0; 50 51 // Bytes to write after TAG-DICTIONARY 52 private final ByteArrayOutputStream tailer = new ByteArrayOutputStream(); 53 54 /** 55 * Constructor. 56 */ 57 public CCITTFaxDecodeFilter() 58 { 59 } 60 61 /** 62 * {@inheritDoc} 63 */ 64 public void decode(InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex) 65 throws IOException 66 { 67 // log.warn( "Warning: CCITTFaxDecode.decode is not implemented yet, 68 // skipping this stream." ); 69 70 71 // Get ImageParams from PDF 72 COSBase baseObj = options.getDictionaryObject(new String[] {"DecodeParms","DP"}); 73 COSDictionary dict = null; 74 if( baseObj instanceof COSDictionary ) 75 { 76 dict = (COSDictionary)baseObj; 77 } 78 else if( baseObj instanceof COSArray ) 79 { 80 COSArray paramArray = (COSArray)baseObj; 81 if( filterIndex < paramArray.size() ) 82 { 83 dict = (COSDictionary)paramArray.getObject( filterIndex ); 84 } 85 else 86 { 87 throw new IOException( "Error: DecodeParms cannot be null for CCITTFaxDecode" ); 88 } 89 } 90 else if( baseObj == null ) 91 { 92 throw new IOException( "Error: DecodeParms cannot be null for CCITTFaxDecode" ); 93 } 94 else 95 { 96 throw new IOException( "Error: Expected COSArray or COSDictionary and not " 97 + baseObj.getClass().getName() ); 98 } 99 100 int width = options.getInt("Width"); 101 int height = options.getInt("Height"); 102 int length = options.getInt(COSName.LENGTH); 103 int compressionType = dict.getInt("K"); 104 boolean blackIs1 = dict.getBoolean("BlackIs1", false); 105 106 107 // HEADER-INFO and starting point of TAG-DICTIONARY 108 writeTagHeader(result, length); 109 110 // IMAGE-DATA 111 int i = 0; 112 //int sum = 0; 113 byte[] buffer = new byte[32768]; 114 int lentoread = length; 115 116 while ((lentoread > 0) && ((i = compressedData.read(buffer, 0, Math.min(lentoread, 32768))) != -1)) 117 { 118 //sum += i; 119 result.write(buffer, 0, i); 120 lentoread = lentoread - i; 121 } 122 123 // If lentoread is > 0 then we need to write out some padding to equal the header 124 // We'll use what we have in the buffer it's just padding after all 125 while (lentoread > 0) 126 { 127 result.write(buffer, 0, Math.min(lentoread, 32768)); 128 lentoread = lentoread - Math.min(lentoread, 32738); 129 } 130 //System.out.println("Gelesen: " + sum); 131 132 // TAG-COUNT 133 writeTagCount(result); 134 135 // WIDTH 0x0100 136 writeTagWidth(result, width); 137 138 // HEIGHT 0x0101 139 writeTagHeight(result, height); 140 141 // BITSPERSAMPLE 0x0102 142 // Always 1 for CCITTFax 143 writeTagBitsPerSample(result, 1); 144 145 // COMPRESSION 0x0103 146 writeTagCompression(result, compressionType); 147 148 // PHOTOMETRIC 0x0106 149 writeTagPhotometric(result, blackIs1); 150 151 // STRIPOFFSET 0x0111 152 // HERE ALWAYS 8, because ImageData comes before TAG-DICTIONARY 153 writeTagStripOffset(result, 8); 154 155 // ORIENTATION 0x0112 156 writeTagOrientation(result, 1); 157 158 // SamplesPerPixel 0x0115 159 writeTagSamplesPerPixel(result, 1); 160 161 // RowsPerStrip 0x0116 162 writeTagRowsPerStrip(result, height); 163 164 // Stripcount 0x0117 165 writeTagStripByteCount(result, length); 166 167 // XRESOLUTION 0x011A 168 // HERE: 200 DPI 169 writeTagXRes(result, 200, 1); 170 171 // YRESOLITION 0x011B 172 // HERE: 200 DPI 173 writeTagYRes(result, 200, 1); 174 175 // ResolutionUnit 0x0128 176 // HERE: DPI 177 writeTagResolutionUnit(result, 2); 178 179 // SOFTWARE 0x0131 180 // minimum 4 chars 181 writeTagSoftware(result, "pdfbox".getBytes()); 182 183 // DATE AND TIME 0x0132 184 writeTagDateTime(result, new Date()); 185 186 // END OF TAG-DICT 187 writeTagTailer(result); 188 } 189 190 private void writeTagHeader(OutputStream result, int length) throws IOException 191 { 192 byte[] header = { 'M', 'M', 0, '*' };// Big-endian 193 result.write(header); 194 195 196 // Add imagelength to offset 197 offset += length; 198 199 // OFFSET TAG-DICTIONARY 200 int i1 = offset/16777216;//=value/(256*256*256) 201 int i2 = (offset-i1*16777216)/65536; 202 int i3 = (offset-i1*16777216-i2*65536)/256; 203 int i4 = offset % 256; 204 result.write(i1); 205 result.write(i2); 206 result.write(i3); 207 result.write(i4); 208 } 209 210 private void writeTagCount(OutputStream result) throws IOException 211 { 212 result.write(TAG_COUNT / 256); 213 result.write(TAG_COUNT % 256);// tagCount 214 } 215 216 private void writeTagWidth(OutputStream result, int width) throws IOException 217 { 218 // @todo width berechnen 219 220 // TAG-ID 100 221 result.write(1); 222 result.write(0); 223 224 225 // TAG-TYPE SHORT=3 226 result.write(0); 227 result.write(3); 228 229 230 // TAG-LENGTH = 1 231 result.write(0); 232 result.write(0); 233 result.write(0); 234 result.write(1); 235 236 237 // TAG-VALUE = width 238 result.write(width/256); 239 result.write(width%256); 240 result.write(0);// SHORT=0 241 result.write(0);// SHORT=0 242 243 } 244 245 private void writeTagHeight(OutputStream result, int height) throws IOException 246 { 247 //@todo height berechnen 248 // TAG-ID 101 249 result.write(1); 250 result.write(1); 251 252 253 // TAG-TYPE SHORT=3 254 result.write(0); 255 result.write(3); 256 257 258 // TAG-LENGTH = 1 259 result.write(0); 260 result.write(0); 261 result.write(0); 262 result.write(1); 263 264 265 // TAG-VALUE 266 result.write(height/256); 267 result.write(height%256); 268 result.write(0);// SHORT=0 269 result.write(0);// SHORT=0 270 271 } 272 273 private void writeTagBitsPerSample(OutputStream result, int value) throws IOException 274 { 275 // TAG-ID 102 276 result.write(1); 277 result.write(2); 278 279 280 // TAG-TYPE SHORT=3 281 result.write(0); 282 result.write(3); 283 284 // TAG-LENGTH = 1 285 result.write(0); 286 result.write(0); 287 result.write(0); 288 result.write(1); 289 290 291 // TAG-VALUE 292 result.write(value/256); 293 result.write(value%256); 294 result.write(0);//SHORT=0 295 result.write(0);//SHORT=0 296 297 } 298 299 /** 300 * Write the tag compression. 301 * 302 * @param result The stream to write to. 303 * @param type The type to write. 304 * @throws IOException If there is an error writing to the stream. 305 */ 306 public void writeTagCompression(OutputStream result, int type) throws IOException 307 { 308 // TAG-ID 103 309 result.write(1); 310 result.write(3); 311 312 // TAG-TYPE SHORT=3 313 result.write(0); 314 result.write(3); 315 316 317 // TAG-LEGNTH = 1 318 result.write(0); 319 result.write(0); 320 result.write(0); 321 result.write(1); 322 323 // TAG-VALUE 324 //@todo typ eintragen; hier immer 4 325 result.write(0); 326 if (type < 0) 327 { 328 result.write(4);// G4 329 } 330 else if (type == 0) 331 { 332 result.write(3);// G3-1D 333 } 334 else 335 { 336 result.write(2);// G3-2D 337 } 338 result.write(0); 339 result.write(0); 340 341 } 342 343 private void writeTagPhotometric(OutputStream result, boolean blackIs1) throws IOException 344 { 345 // TAG-ID 106 346 result.write(1); 347 result.write(6); 348 349 350 // TAG-TYPE SHORT 351 result.write(0); 352 result.write(3); 353 354 355 // TAG-LENGTH = 1 356 result.write(0); 357 result.write(0); 358 result.write(0); 359 result.write(1); 360 361 362 // TAG-VALUE 363 result.write(0); 364 if (blackIs1) 365 { 366 result.write(1); 367 } 368 else 369 { 370 result.write(0); 371 } 372 result.write(0);// SHORT=0 373 result.write(0);// SHORT=0 374 375 } 376 377 private void writeTagStripOffset(OutputStream result, int value) throws IOException 378 { 379 // TAG-ID 111 380 result.write(1); 381 result.write(17); 382 383 // TAG-TYPE LONG=4 384 result.write(0); 385 result.write(4); 386 387 388 // TAG-LENGTH=1 389 result.write(0); 390 result.write(0); 391 result.write(0); 392 result.write(1); 393 394 395 // TAG-VALUE = 8 //VOR TAG-DICTIONARY 396 int i1 = value/16777216;//=value/(256*256*256) 397 int i2 = (value-i1*16777216)/65536; 398 int i3 = (value-i1*16777216-i2*65536)/256; 399 int i4 = value % 256; 400 result.write(i1); 401 result.write(i2); 402 result.write(i3); 403 result.write(i4); 404 405 } 406 407 private void writeTagSamplesPerPixel(OutputStream result, int value) throws IOException 408 { 409 // TAG-ID 115 410 result.write(1); 411 result.write(21); 412 413 414 // TAG-TYPE SHORT=3 415 result.write(0); 416 result.write(3); 417 418 419 // TAG-LENGTH=1 420 result.write(0); 421 result.write(0); 422 result.write(0); 423 result.write(1); 424 425 426 // TAG-VALUE 427 result.write(value / 256); 428 result.write(value % 256); 429 result.write(0);// SHORT=0 430 result.write(0);// SHORT=0 431 432 } 433 434 private void writeTagRowsPerStrip(OutputStream result, int value) throws IOException 435 { 436 // TAG-ID 116 437 result.write(1); 438 result.write(22); 439 440 441 // TAG-TYPE SHORT=3 442 result.write(0); 443 result.write(3); 444 445 446 // TAG-LENGTH=1 447 result.write(0); 448 result.write(0); 449 result.write(0); 450 result.write(1); 451 452 453 // TAG-VALUE 454 result.write(value / 256); 455 result.write(value % 256); 456 result.write(0);// SHORT=0 457 result.write(0);// SHORT=0 458 459 } 460 461 private void writeTagStripByteCount(OutputStream result, int value) throws IOException 462 { 463 //@todo value auswerten 464 // TAG-ID 117 465 result.write(1); 466 result.write(23); 467 468 // TAG-TYPE LONG=4 469 result.write(0); 470 result.write(4); 471 472 473 // TAG-LENGTH = 1 474 result.write(0); 475 result.write(0); 476 result.write(0); 477 result.write(1); 478 479 // TAG-VALUE 480 int i1 = value/16777216;//=value/(256*256*256) 481 int i2 = (value-i1*16777216)/65536; 482 int i3 = (value-i1*16777216-i2*65536)/256; 483 int i4 = value % 256; 484 result.write(i1); 485 result.write(i2); 486 result.write(i3); 487 result.write(i4); 488 489 } 490 491 private void writeTagXRes(OutputStream result, int value1, int value2) throws IOException 492 { 493 // TAG-ID 11A 494 result.write(1); 495 result.write(26); 496 497 // TAG-TYPE RATIONAL=5 498 result.write(0); 499 result.write(5); 500 501 // TAG-LENGTH=1 502 result.write(0); 503 result.write(0); 504 result.write(0); 505 result.write(1); 506 507 508 // TAG-VALUE=OFFSET TO RATIONAL 509 int valueOffset = offset + 6 + 12 * TAG_COUNT + tailer.size(); 510 int i1 = valueOffset/16777216;//=value/(256*256*256) 511 int i2 = (valueOffset-i1*16777216)/65536; 512 int i3 = (valueOffset-i1*16777216-i2*65536)/256; 513 int i4 = valueOffset % 256; 514 result.write(i1); 515 result.write(i2); 516 result.write(i3); 517 result.write(i4); 518 519 i1 = value1 /16777216; 520 i2 = (value1-i1*16777216)/65536; 521 i3 = (value1-i1*16777216 - i2*65536)/256; 522 i4 = value1 % 256; 523 tailer.write(i1); 524 tailer.write(i2); 525 tailer.write(i3); 526 tailer.write(i4); 527 528 i1 = value2 /16777216; 529 i2 = (value2-i1*16777216)/65536; 530 i3 = (value2-i1*16777216 - i2*65536)/256; 531 i4 = value2 % 256; 532 tailer.write(i1); 533 tailer.write(i2); 534 tailer.write(i3); 535 tailer.write(i4); 536 537 tailingBytesCount += 8; 538 } 539 540 private void writeTagYRes(OutputStream result, int value1, int value2) throws IOException 541 { 542 // TAG-ID 11B 543 result.write(1); 544 result.write(27); 545 546 547 // TAG-TYPE RATIONAL=5 548 result.write(0); 549 result.write(5); 550 551 // TAG-LENGTH=1 552 result.write(0); 553 result.write(0); 554 result.write(0); 555 result.write(1); 556 557 558 // TAG-VALUE=OFFSET TO RATIONAL 559 int valueOffset = offset + 6 + 12 * TAG_COUNT + tailer.size(); 560 int i1 = valueOffset/16777216;//=value/(256*256*256) 561 int i2 = (valueOffset-i1*16777216)/65536; 562 int i3 = (valueOffset-i1*16777216-i2*65536)/256; 563 int i4 = valueOffset % 256; 564 result.write(i1); 565 result.write(i2); 566 result.write(i3); 567 result.write(i4); 568 569 i1 = value1 /16777216; 570 i2 = (value1-i1*16777216)/65536; 571 i3 = (value1-i1*16777216 - i2*65536)/256; 572 i4 = value1 % 256; 573 tailer.write(i1); 574 tailer.write(i2); 575 tailer.write(i3); 576 tailer.write(i4); 577 578 i1 = value2 /16777216; 579 i2 = (value2-i1*16777216)/65536; 580 i3 = (value2-i1*16777216 - i2*65536)/256; 581 i4 = value2 % 256; 582 tailer.write(i1); 583 tailer.write(i2); 584 tailer.write(i3); 585 tailer.write(i4); 586 587 tailingBytesCount += 8; 588 } 589 590 private void writeTagResolutionUnit(OutputStream result, int value) throws IOException 591 { 592 // TAG-ID 128 593 result.write(1); 594 result.write(40); 595 596 // TAG-TYPE SHORT=3 597 result.write(0); 598 result.write(3); 599 600 // TAG-LENGTH = 1 601 result.write(0); 602 result.write(0); 603 result.write(0); 604 result.write(1); 605 606 // TAG-VALUE 607 result.write(value/256); 608 result.write(value%256); 609 result.write(0);// SHORT=0 610 result.write(0);// SHORT=0 611 612 } 613 614 private void writeTagOrientation(OutputStream result, int value) throws IOException 615 { 616 // TAG-ID 112 617 result.write(1); 618 result.write(18); 619 620 // TAG-TYPE SHORT = 3 621 result.write(0); 622 result.write(3); 623 624 625 // TAG-LENGTH=1 626 result.write(0); 627 result.write(0); 628 result.write(0); 629 result.write(1); 630 631 // TAG-VALUE 632 result.write(value / 256); 633 result.write(value % 256); 634 result.write(0);// SHORT=0 635 result.write(0);// SHORT=0 636 637 } 638 639 private void writeTagTailer(OutputStream result) throws IOException 640 { 641 // END OF TAG-DICTIONARY 642 result.write(0); 643 result.write(0); 644 result.write(0); 645 result.write(0); 646 647 // TAILER WITH VALUES OF RATIONALFIELD's 648 result.write(tailer.toByteArray()); 649 } 650 651 private void writeTagSoftware(OutputStream result, byte[] text) throws IOException 652 { 653 // TAG-ID 131 654 result.write(1); 655 result.write(49); 656 657 // TAG-TYPE ASCII=2 658 result.write(0); 659 result.write(2); 660 661 662 // TAG-LENGTH=id.length+1 663 result.write(0); 664 result.write(0); 665 result.write((text.length + 1) / 256); 666 result.write((text.length + 1) % 256); 667 668 // TAG-VALUE 669 int valueOffset = offset + 6 + 12 * TAG_COUNT + tailer.size(); 670 int i1 = valueOffset/16777216;//=value/(256*256*256) 671 int i2 = (valueOffset-i1*16777216)/65536; 672 int i3 = (valueOffset-i1*16777216-i2*65536)/256; 673 int i4 = valueOffset % 256; 674 result.write(i1); 675 result.write(i2); 676 result.write(i3); 677 result.write(i4); 678 679 680 tailer.write(text); 681 tailer.write(0); 682 tailingBytesCount += text.length + 1; 683 } 684 685 private void writeTagDateTime(OutputStream result, Date date) throws IOException 686 { 687 // TAG-ID 132 688 result.write(1); 689 result.write(50); 690 691 692 // TAG-TYPE ASCII=2 693 result.write(0); 694 result.write(2); 695 696 697 // TAG-LENGTH=20 698 result.write(0); 699 result.write(0); 700 result.write(0); 701 result.write(20); 702 703 704 // TAG-VALUE 705 int valueOffset = offset + 6 + 12 * TAG_COUNT + tailer.size(); 706 int i1 = valueOffset/16777216;//=value/(256*256*256) 707 int i2 = (valueOffset-i1*16777216)/65536; 708 int i3 = (valueOffset-i1*16777216-i2*65536)/256; 709 int i4 = valueOffset % 256; 710 result.write(i1); 711 result.write(i2); 712 result.write(i3); 713 result.write(i4); 714 715 SimpleDateFormat sdf = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss"); 716 String datetime = sdf.format(date); 717 tailer.write(datetime.getBytes()); 718 tailer.write(0); 719 720 tailingBytesCount += 20; 721 } 722 723 /** 724 * {@inheritDoc} 725 */ 726 public void encode(InputStream rawData, OutputStream result, COSDictionary options, int filterIndex ) 727 throws IOException 728 { 729 System.err.println("Warning: CCITTFaxDecode.encode is not implemented yet, skipping this stream."); 730 } 731 }